@geekmidas/cli 10.0.0-alpha.66 → 10.0.0-alpha.68
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 +110 -178
- package/dist/{HostingerProvider-CY_VpVud.mjs → HostingerProvider-D85ayzTi.mjs} +2 -2
- package/dist/{HostingerProvider-CY_VpVud.mjs.map → HostingerProvider-D85ayzTi.mjs.map} +1 -1
- package/dist/{HostingerProvider-BznqRC2S.cjs → HostingerProvider-dxkwynSs.cjs} +2 -2
- package/dist/{HostingerProvider-BznqRC2S.cjs.map → HostingerProvider-dxkwynSs.cjs.map} +1 -1
- package/dist/{LegacyStateStore-BFcTT2QL.cjs → LegacyStateStore-BZiO9-Lw.cjs} +2 -2
- package/dist/{LegacyStateStore-BFcTT2QL.cjs.map → LegacyStateStore-BZiO9-Lw.cjs.map} +1 -1
- package/dist/{LegacyStateStore-CFheFo62.mjs → LegacyStateStore-C39UKwmf.mjs} +2 -2
- package/dist/{LegacyStateStore-CFheFo62.mjs.map → LegacyStateStore-C39UKwmf.mjs.map} +1 -1
- package/dist/{LocalStateStore-BVUexSvu.cjs → LocalStateStore-DHVsZLyF.cjs} +2 -2
- package/dist/{LocalStateStore-BVUexSvu.cjs.map → LocalStateStore-DHVsZLyF.cjs.map} +1 -1
- package/dist/{LocalStateStore-Bm3x9oOg.mjs → LocalStateStore-Dk3Wbvzk.mjs} +2 -2
- package/dist/{LocalStateStore-Bm3x9oOg.mjs.map → LocalStateStore-Dk3Wbvzk.mjs.map} +1 -1
- package/dist/{S3StateStore-CoXBhILd.cjs → S3StateStore--4tYVMAL.cjs} +2 -2
- package/dist/{S3StateStore-CoXBhILd.cjs.map → S3StateStore--4tYVMAL.cjs.map} +1 -1
- package/dist/{S3StateStore-BFHuctWM.mjs → S3StateStore-B_ZF9eHt.mjs} +2 -2
- package/dist/{S3StateStore-BFHuctWM.mjs.map → S3StateStore-B_ZF9eHt.mjs.map} +1 -1
- package/dist/{SSMStateStore-BqCA9TP2.cjs → SSMStateStore-ACZ8hRvZ.cjs} +2 -2
- package/dist/{SSMStateStore-BqCA9TP2.cjs.map → SSMStateStore-ACZ8hRvZ.cjs.map} +1 -1
- package/dist/{SSMStateStore-C1kvHScY.mjs → SSMStateStore-DjPuKqrm.mjs} +2 -2
- package/dist/{SSMStateStore-C1kvHScY.mjs.map → SSMStateStore-DjPuKqrm.mjs.map} +1 -1
- package/dist/{bundler-Dvaiud0h.mjs → bundler-C-w7aGoZ.mjs} +20 -5
- package/dist/bundler-C-w7aGoZ.mjs.map +1 -0
- package/dist/{bundler-orRqUJNZ.cjs → bundler-C1pnNrR0.cjs} +20 -5
- package/dist/bundler-C1pnNrR0.cjs.map +1 -0
- package/dist/{cli-D1JkkOwg.cjs → cli-BLyD4c0O.cjs} +14 -5
- package/dist/cli-BLyD4c0O.cjs.map +1 -0
- package/dist/{cli-CfHm0AO7.mjs → cli-Ce4pLL5O.mjs} +14 -5
- package/dist/cli-Ce4pLL5O.mjs.map +1 -0
- package/dist/{compose-CNQL8BnO.mjs → compose-DFxuYhn6.mjs} +291 -152
- package/dist/compose-DFxuYhn6.mjs.map +1 -0
- package/dist/{compose-jrLy7KGO.cjs → compose-DvsgumY2.cjs} +298 -159
- package/dist/compose-DvsgumY2.cjs.map +1 -0
- package/dist/{config-4XFxX1al.d.cts → config-BnA0kPTY.d.cts} +3 -3
- package/dist/{config-4XFxX1al.d.cts.map → config-BnA0kPTY.d.cts.map} +1 -1
- package/dist/{config-UtKzhpFb.d.mts → config-CxEp_g7j.d.mts} +3 -3
- package/dist/{config-UtKzhpFb.d.mts.map → config-CxEp_g7j.d.mts.map} +1 -1
- package/dist/config.d.cts +3 -3
- package/dist/config.d.mts +3 -3
- package/dist/{credentials-Bzbg23n8.cjs → credentials-BVs7mrtw.cjs} +239 -3
- package/dist/credentials-BVs7mrtw.cjs.map +1 -0
- package/dist/{credentials-Dq1MnWQf.mjs → credentials-pAtB70k3.mjs} +223 -5
- package/dist/credentials-pAtB70k3.mjs.map +1 -0
- package/dist/deploy/public.cjs +21 -17
- package/dist/deploy/public.cjs.map +1 -1
- package/dist/deploy/public.d.cts +15 -4
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +15 -4
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +10 -10
- package/dist/{deploy-B7aJi_Ah.cjs → deploy-C08GONYW.cjs} +14 -10
- package/dist/deploy-C08GONYW.cjs.map +1 -0
- package/dist/{deploy-DWn7YfIc.mjs → deploy-D-EVOWvu.mjs} +11 -7
- package/dist/deploy-D-EVOWvu.mjs.map +1 -0
- package/dist/{dokploy-B4LcYG1E.cjs → dokploy-CZ6nfSV9.cjs} +2 -2
- package/dist/{dokploy-B4LcYG1E.cjs.map → dokploy-CZ6nfSV9.cjs.map} +1 -1
- package/dist/{dokploy-DalwLML5.mjs → dokploy-DU-XJsoT.mjs} +2 -2
- package/dist/{dokploy-DalwLML5.mjs.map → dokploy-DU-XJsoT.mjs.map} +1 -1
- package/dist/{encryption-3KlGS1sl.cjs → encryption-CCcBSLZs.cjs} +2 -2
- package/dist/{encryption-3KlGS1sl.cjs.map → encryption-CCcBSLZs.cjs.map} +1 -1
- package/dist/{encryption-CPfENwWO.mjs → encryption-DSiLau1-.mjs} +2 -2
- package/dist/{encryption-CPfENwWO.mjs.map → encryption-DSiLau1-.mjs.map} +1 -1
- package/dist/{engine-DR6KurWX.cjs → engine-BM9Dy_DT.cjs} +141 -209
- package/dist/engine-BM9Dy_DT.cjs.map +1 -0
- package/dist/{engine-CunvOG3s.mjs → engine-llWnMZH5.mjs} +137 -205
- package/dist/engine-llWnMZH5.mjs.map +1 -0
- package/dist/{github-BJ5gh9bw.cjs → github-ZoMam0OH.cjs} +2 -2
- package/dist/{github-BJ5gh9bw.cjs.map → github-ZoMam0OH.cjs.map} +1 -1
- package/dist/{github-MXA9v7Ge.mjs → github-yy6WpHIl.mjs} +2 -2
- package/dist/{github-MXA9v7Ge.mjs.map → github-yy6WpHIl.mjs.map} +1 -1
- package/dist/{index-DD1FfUHh.d.mts → index-CkX89m6S.d.mts} +2 -2
- package/dist/{index-CyApaHVe.d.cts.map → index-CkX89m6S.d.mts.map} +1 -1
- package/dist/{index-CyApaHVe.d.cts → index-EvZBkycu.d.cts} +2 -2
- package/dist/{index-DD1FfUHh.d.mts.map → index-EvZBkycu.d.cts.map} +1 -1
- package/dist/index.cjs +59 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +55 -66
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-BJBcm4sG.mjs → migrate-BWPuwiXk.mjs} +2 -2
- package/dist/{migrate-BJBcm4sG.mjs.map → migrate-BWPuwiXk.mjs.map} +1 -1
- package/dist/{migrate-CF4J4GQS.cjs → migrate-tOwxQBTI.cjs} +2 -2
- package/dist/{migrate-CF4J4GQS.cjs.map → migrate-tOwxQBTI.cjs.map} +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/reconcile/public.d.cts +1 -1
- package/dist/reconcile/public.d.mts +1 -1
- package/dist/{resolve-CVmch0wI.cjs → resolve-BymzGtuT.cjs} +4 -4
- package/dist/{resolve-CVmch0wI.cjs.map → resolve-BymzGtuT.cjs.map} +1 -1
- package/dist/{resolve-Db0tNlrW.mjs → resolve-pIvXlZ1h.mjs} +4 -4
- package/dist/{resolve-Db0tNlrW.mjs.map → resolve-pIvXlZ1h.mjs.map} +1 -1
- package/dist/{rollback-DvAo4OKS.mjs → rollback-ZlsbQOvj.mjs} +5 -6
- package/dist/{rollback-DvAo4OKS.mjs.map → rollback-ZlsbQOvj.mjs.map} +1 -1
- package/dist/{rollback-DxJK84hy.cjs → rollback-zdfi6Reg.cjs} +6 -7
- package/dist/{rollback-DxJK84hy.cjs.map → rollback-zdfi6Reg.cjs.map} +1 -1
- package/dist/run-BJLIerMM.mjs +133 -0
- package/dist/run-BJLIerMM.mjs.map +1 -0
- package/dist/run-fVZ-rLnc.cjs +174 -0
- package/dist/run-fVZ-rLnc.cjs.map +1 -0
- package/dist/{runtime-BRqFOvPM.d.cts → runtime-B3jNgeP-.d.mts} +2 -2
- package/dist/{runtime-BRqFOvPM.d.cts.map → runtime-B3jNgeP-.d.mts.map} +1 -1
- package/dist/{runtime-CEk5n4VH.d.mts → runtime-DWThEP-r.d.cts} +2 -2
- package/dist/{runtime-CEk5n4VH.d.mts.map → runtime-DWThEP-r.d.cts.map} +1 -1
- package/dist/{sniffer-BAQhLEsI.mjs → sniffer-BdFeLSmA.mjs} +8 -60
- package/dist/sniffer-BdFeLSmA.mjs.map +1 -0
- package/dist/{sniffer-5xmrkh2a.cjs → sniffer-D66HIvi9.cjs} +7 -77
- package/dist/sniffer-D66HIvi9.cjs.map +1 -0
- package/dist/{sst-B2HbrDR3.mjs → sst-DRi3H5TB.mjs} +3 -3
- package/dist/{sst-B2HbrDR3.mjs.map → sst-DRi3H5TB.mjs.map} +1 -1
- package/dist/{sst-DcfsVH6x.cjs → sst-hBrKouBy.cjs} +5 -5
- package/dist/{sst-DcfsVH6x.cjs.map → sst-hBrKouBy.cjs.map} +1 -1
- package/dist/{state-uuYqnRVO.mjs → state-Bn5JDMFT.mjs} +1717 -1334
- package/dist/state-Bn5JDMFT.mjs.map +1 -0
- package/dist/{state-Y6ATakMX.cjs → state-CHZqCDRC.cjs} +2863 -2354
- package/dist/state-CHZqCDRC.cjs.map +1 -0
- package/dist/target/public.cjs +1 -1
- package/dist/target/public.d.cts +2 -2
- package/dist/target/public.d.mts +2 -2
- package/dist/target/public.mjs +1 -1
- package/dist/{terminal-DUhcZ1ND.mjs → terminal-C3cv-OQu.mjs} +2 -2
- package/dist/{terminal-DUhcZ1ND.mjs.map → terminal-C3cv-OQu.mjs.map} +1 -1
- package/dist/{terminal-CHy_MdjG.cjs → terminal-DjzgLu6m.cjs} +2 -2
- package/dist/{terminal-CHy_MdjG.cjs.map → terminal-DjzgLu6m.cjs.map} +1 -1
- package/dist/{types-CSw2w69a.d.mts → types-CZdCxUCK.d.cts} +64 -2
- package/dist/types-CZdCxUCK.d.cts.map +1 -0
- package/dist/{types-D5WIxRDP.d.cts → types-Da0E3Qv7.d.mts} +63 -3
- package/dist/types-Da0E3Qv7.d.mts.map +1 -0
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/package.json +13 -13
- package/dist/bundler-Dvaiud0h.mjs.map +0 -1
- package/dist/bundler-orRqUJNZ.cjs.map +0 -1
- package/dist/cli-CfHm0AO7.mjs.map +0 -1
- package/dist/cli-D1JkkOwg.cjs.map +0 -1
- package/dist/compose-CNQL8BnO.mjs.map +0 -1
- package/dist/compose-jrLy7KGO.cjs.map +0 -1
- package/dist/credentials-Bzbg23n8.cjs.map +0 -1
- package/dist/credentials-Dq1MnWQf.mjs.map +0 -1
- package/dist/deploy-B7aJi_Ah.cjs.map +0 -1
- package/dist/deploy-DWn7YfIc.mjs.map +0 -1
- package/dist/engine-CunvOG3s.mjs.map +0 -1
- package/dist/engine-DR6KurWX.cjs.map +0 -1
- package/dist/local-BdH4XN0E.cjs +0 -409
- package/dist/local-BdH4XN0E.cjs.map +0 -1
- package/dist/local-CWR4Y_kW.mjs +0 -350
- package/dist/local-CWR4Y_kW.mjs.map +0 -1
- package/dist/sniffer-5xmrkh2a.cjs.map +0 -1
- package/dist/sniffer-BAQhLEsI.mjs.map +0 -1
- package/dist/state-Y6ATakMX.cjs.map +0 -1
- package/dist/state-uuYqnRVO.mjs.map +0 -1
- package/dist/types-CSw2w69a.d.mts.map +0 -1
- package/dist/types-D5WIxRDP.d.cts.map +0 -1
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { a as runOutput, i as run } from "./run-BJLIerMM.mjs";
|
|
3
3
|
import { r as discover } from "./discover-BFoJWKGD.mjs";
|
|
4
|
-
import { i as activeSandbox } from "./sandbox-Dm247NGx.mjs";
|
|
5
4
|
import { t as appKey } from "./derive-B4vDpzgP.mjs";
|
|
6
5
|
import { t as defineTarget } from "./define-DR8xGYfL.mjs";
|
|
7
|
-
import {
|
|
6
|
+
import { A as assertExternalServices, C as isMainFrontendApp, Ct as primaryPortKey, F as reportDevServices, Ht as imageRef, I as encryptSecrets, J as applyPolicies, Q as postgresStatements, S as NoDomainForStage, St as portKeys, V as constructGlobs, X as bucketPolicies, Y as applyPostgres, a as recordRelease, at as envFor, b as ensureDockerignore, dt as imageLayout, et as composeFor, ft as TURBO_VERSION, g as credentialsFileContent, h as credentialsBuildArg, it as networkEnv, j as devServicesUsed, lt as hostFor, m as withGeneratedSecrets, mt as findBuildRoot, nt as pgClient, ot as localRolePassword, p as DeployJournal, q as applyBuckets, qt as initStageSecrets, rt as appEnvKeys, st as planFor, t as createEmptyState, tt as bucketClient, ut as composeBuildPaths, v as appDockerfile, w as resolveHost, x as validateImageRef, yt as DEFAULT_IMAGES } from "./state-Bn5JDMFT.mjs";
|
|
8
7
|
import { n as DEFAULT_CACHE, r as DEFAULT_EVENTS } from "./fakes-C9RiT6XG.mjs";
|
|
9
8
|
import { u as migrateDatabases } from "./databases-hUHTuwhx.mjs";
|
|
10
9
|
import { dirname, join } from "node:path";
|
|
11
|
-
import {
|
|
12
|
-
import { appendFile, chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
10
|
+
import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
13
11
|
import { provideKey, provisionOrder, publicEnvFor } from "@geekmidas/manifest";
|
|
14
|
-
import { fileURLToPath } from "node:url";
|
|
15
12
|
import { spawn } from "node:child_process";
|
|
16
13
|
import { stringify } from "yaml";
|
|
17
14
|
import { request } from "node:https";
|
|
@@ -264,22 +261,29 @@ function edgeCaddyfile(sites, options) {
|
|
|
264
261
|
}` : options.email ? `{
|
|
265
262
|
email ${options.email}
|
|
266
263
|
}` : "";
|
|
267
|
-
const blocks = sites.map(({ host, upstream }) => `https://${host} {${options.local ? `
|
|
264
|
+
const blocks = sites.map(({ host, upstream, rewrite }) => `https://${host} {${options.local ? `
|
|
268
265
|
tls internal
|
|
269
|
-
` : ""}
|
|
266
|
+
` : ""}${rewrite ? `
|
|
267
|
+
# A bucket, served at a host of its own: the bucket is a prefix on MinIO,
|
|
268
|
+
# which routes and signs on the Host header — so it is sent the upstream's.
|
|
269
|
+
reverse_proxy ${upstream} {
|
|
270
|
+
rewrite ${rewrite}
|
|
271
|
+
header_up Host {upstream_hostport}
|
|
272
|
+
}` : `
|
|
270
273
|
# Host and X-Forwarded-* are passed through as Caddy does by default, so
|
|
271
274
|
# an app builds redirects, cookies and links on the address a caller used.
|
|
272
275
|
reverse_proxy ${upstream} {
|
|
273
276
|
# Never buffer: a streamed response — server-sent events, a Next.js
|
|
274
277
|
# RSC payload — reaches the client as each chunk is written.
|
|
275
278
|
flush_interval -1
|
|
276
|
-
}
|
|
279
|
+
}`}
|
|
277
280
|
}`);
|
|
278
281
|
return `${HEADER}${global ? `${global}\n\n` : ""}${blocks.join("\n\n")}\n`;
|
|
279
282
|
}
|
|
280
283
|
const HEADER = `# Generated by gkm compose from the construct manifest — do not edit.
|
|
281
284
|
#
|
|
282
|
-
# One host per API and site, each in front of its own container
|
|
285
|
+
# One host per API and site, each in front of its own container, and one per
|
|
286
|
+
# file server in front of the stack's MinIO.
|
|
283
287
|
`;
|
|
284
288
|
//#endregion
|
|
285
289
|
//#region src/compose/stack.ts
|
|
@@ -320,42 +324,32 @@ const EDGE_PORT_ENV = {
|
|
|
320
324
|
/**
|
|
321
325
|
* Containers this target does not run.
|
|
322
326
|
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
+
* The AWS emulator never applies: a stack is a server target, whose events
|
|
328
|
+
* are pg-boss. Reconcile's Caddy fronts `gkm dev`'s host processes; the stack
|
|
329
|
+
* brings its own edge.
|
|
330
|
+
*
|
|
331
|
+
* MinIO and Mailpit run on the local stage always, and on a deployed stage
|
|
332
|
+
* only where `--allow-dev-services` allowed them and its secrets configure no
|
|
333
|
+
* real bucket or mail server — see `devServices.ts`.
|
|
327
334
|
*/
|
|
328
335
|
const NOT_RUN = {
|
|
329
|
-
minio: true,
|
|
330
336
|
localstack: true,
|
|
331
337
|
caddy: true
|
|
332
338
|
};
|
|
333
|
-
/**
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
"
|
|
339
|
+
/** The kinds a deployed stage takes from its secrets, or a dev service. */
|
|
340
|
+
const SERVICE_KINDS = {
|
|
341
|
+
email: "email",
|
|
342
|
+
objects: "objects",
|
|
343
|
+
"file-server": "file-server"
|
|
337
344
|
};
|
|
345
|
+
/** The region MinIO is addressed with. It has no regions; the SDK wants one. */
|
|
346
|
+
const MINIO_REGION = "us-east-1";
|
|
338
347
|
/** The S3 client's credentials, read beside a bucket's URL when it has them. */
|
|
339
348
|
const STORAGE_KEYS = [
|
|
340
349
|
"AWS_ACCESS_KEY_ID",
|
|
341
350
|
"AWS_SECRET_ACCESS_KEY",
|
|
342
351
|
"AWS_REGION"
|
|
343
352
|
];
|
|
344
|
-
/** A bucket the stack needs and the stage was not given. */
|
|
345
|
-
var BucketNotConfigured = class extends Error {
|
|
346
|
-
app;
|
|
347
|
-
id;
|
|
348
|
-
key;
|
|
349
|
-
stage;
|
|
350
|
-
constructor(app, id, key, stage) {
|
|
351
|
-
super(`'${app}' uses the bucket '${id}', and gkm compose runs no object storage: the stage needs an externally hosted bucket. Set its URL in the stage's secrets: gkm secrets:set ${key} 's3://…' --stage ${stage}`);
|
|
352
|
-
this.app = app;
|
|
353
|
-
this.id = id;
|
|
354
|
-
this.key = key;
|
|
355
|
-
this.stage = stage;
|
|
356
|
-
this.name = "BucketNotConfigured";
|
|
357
|
-
}
|
|
358
|
-
};
|
|
359
353
|
/** A value only the stage's secrets can hold, and they do not. */
|
|
360
354
|
var StageSecretMissing = class extends Error {
|
|
361
355
|
app;
|
|
@@ -373,7 +367,7 @@ var StageSecretMissing = class extends Error {
|
|
|
373
367
|
var StageSeedMissing = class extends Error {
|
|
374
368
|
stage;
|
|
375
369
|
constructor(stage) {
|
|
376
|
-
super(`The stage '${stage}' has no seed in its secrets, and a deployed stage's database passwords are derived from it. Run gkm compose without --dry-run once, or gkm deploy, to generate it.`);
|
|
370
|
+
super(`The stage '${stage}' has no seed in its secrets, and a deployed stage's database passwords are derived from it. Run gkm compose --stage ${stage} without --dry-run once, or gkm deploy, to generate it.`);
|
|
377
371
|
this.stage = stage;
|
|
378
372
|
this.name = "StageSeedMissing";
|
|
379
373
|
}
|
|
@@ -442,7 +436,6 @@ function composeStack(input) {
|
|
|
442
436
|
master: local ? "geekmidas" : localRolePassword(workspace.name, plan, "master", seed),
|
|
443
437
|
...seed ? { seed } : {}
|
|
444
438
|
};
|
|
445
|
-
const infra = plan.containers.filter((c) => !NOT_RUN[c]).filter((c) => local || c !== "mailpit").sort();
|
|
446
439
|
const apps = stackApps(workspace, manifest, plan, {
|
|
447
440
|
local,
|
|
448
441
|
stage,
|
|
@@ -451,10 +444,47 @@ function composeStack(input) {
|
|
|
451
444
|
images: input.images
|
|
452
445
|
});
|
|
453
446
|
if (apps.length === 0) throw new NothingToCompose(workspace.root);
|
|
447
|
+
const owners = /* @__PURE__ */ new Map();
|
|
448
|
+
for (const resource of plan.resources) {
|
|
449
|
+
owners.set(resource.envKey, resource);
|
|
450
|
+
if (resource.kind === "email") {
|
|
451
|
+
owners.set(provideKey(resource.id, "from"), resource);
|
|
452
|
+
owners.set(provideKey(resource.id, "inboxUrl"), resource);
|
|
453
|
+
}
|
|
454
|
+
if (resource.kind === "external-api") owners.set(provideKey(resource.id, "credentials"), resource);
|
|
455
|
+
}
|
|
456
|
+
const reads = /* @__PURE__ */ new Map();
|
|
457
|
+
for (const app of apps) {
|
|
458
|
+
const site = manifest[app.id];
|
|
459
|
+
reads.set(app.name, site?.kind === "site" ? Object.values(publicEnvFor(site, manifest)) : [...appEnvKeys(manifest, app.name, input.runnables) ?? []]);
|
|
460
|
+
}
|
|
461
|
+
const domain = workspace.deploy?.domains?.[stage];
|
|
462
|
+
const services = local ? {
|
|
463
|
+
missing: [],
|
|
464
|
+
minio: plan.resources.filter((r) => r.kind === "objects").map((r) => r.id),
|
|
465
|
+
mailpit: plan.resources.filter((r) => r.kind === "email").map((r) => r.id)
|
|
466
|
+
} : assertExternalServices({
|
|
467
|
+
stage,
|
|
468
|
+
declarations: serviceDeclarations(plan, reads, owners),
|
|
469
|
+
supplied: custom,
|
|
470
|
+
allow: input.allowDevServices ?? [],
|
|
471
|
+
...domain ? { domain } : {}
|
|
472
|
+
});
|
|
473
|
+
const infra = plan.containers.filter((c) => !NOT_RUN[c]).filter((c) => c !== "minio" || services.minio.length > 0).filter((c) => c !== "mailpit" || services.mailpit.length > 0).sort();
|
|
474
|
+
const storage = infra.includes("minio") ? (() => {
|
|
475
|
+
const buckets = services.minio.map((id) => byId.get(id)?.name).filter((name) => Boolean(name)).sort();
|
|
476
|
+
return {
|
|
477
|
+
user: custom.AWS_ACCESS_KEY_ID ?? (local ? "geekmidas" : `${workspace.name}-minio`),
|
|
478
|
+
password: custom.AWS_SECRET_ACCESS_KEY ?? (local ? "geekmidas" : localRolePassword(workspace.name, plan, "minio", seed)),
|
|
479
|
+
buckets,
|
|
480
|
+
policies: bucketPolicies(plan).filter((p) => buckets.includes(p.bucket))
|
|
481
|
+
};
|
|
482
|
+
})() : void 0;
|
|
454
483
|
const derivation = {
|
|
455
484
|
project: workspace.name,
|
|
456
485
|
master: credential.master,
|
|
457
|
-
...credential.seed ? { seed: credential.seed } : {}
|
|
486
|
+
...credential.seed ? { seed: credential.seed } : {},
|
|
487
|
+
...!local && domain ? { mailFrom: `noreply@${domain}` } : {}
|
|
458
488
|
};
|
|
459
489
|
const outside = networkEnv(plan, {
|
|
460
490
|
...derivation,
|
|
@@ -464,14 +494,27 @@ function composeStack(input) {
|
|
|
464
494
|
...derivation,
|
|
465
495
|
addresses: Object.fromEntries(apps.map((app) => [app.id, `http://${app.name}:${app.port}`]))
|
|
466
496
|
});
|
|
467
|
-
const
|
|
468
|
-
for (const resource of plan.resources) {
|
|
469
|
-
|
|
470
|
-
if (resource.
|
|
471
|
-
|
|
472
|
-
|
|
497
|
+
const fileServers = /* @__PURE__ */ new Map();
|
|
498
|
+
if (storage) for (const resource of plan.resources) {
|
|
499
|
+
if (resource.kind !== "file-server" || !resource.of) continue;
|
|
500
|
+
if (!services.minio.includes(resource.of)) continue;
|
|
501
|
+
if (custom[resource.envKey] !== void 0) continue;
|
|
502
|
+
const bucket = byId.get(resource.of);
|
|
503
|
+
if (!bucket) continue;
|
|
504
|
+
let host;
|
|
505
|
+
if (local) host = hostFor(resource, workspace.name);
|
|
506
|
+
else {
|
|
507
|
+
if (!domain) throw new NoDomainForStage(stage);
|
|
508
|
+
host = `${resource.subdomain ?? appKey(resource.id)}.${domain}`;
|
|
473
509
|
}
|
|
474
|
-
|
|
510
|
+
fileServers.set(resource.id, {
|
|
511
|
+
url: `https://${host}${local && https !== 443 ? `:${https}` : ""}`,
|
|
512
|
+
site: {
|
|
513
|
+
host,
|
|
514
|
+
upstream: "minio:9000",
|
|
515
|
+
rewrite: `/${bucket.name}{uri}`
|
|
516
|
+
}
|
|
517
|
+
});
|
|
475
518
|
}
|
|
476
519
|
const surfaceUrls = new Map(plan.resources.filter((r) => r.kind === "rest-api" || r.kind === "site").map((r) => [r.envKey, r.id]));
|
|
477
520
|
const appById = new Map(apps.map((app) => [app.id, app]));
|
|
@@ -480,10 +523,10 @@ function composeStack(input) {
|
|
|
480
523
|
* they can hold it, and the derivations otherwise.
|
|
481
524
|
*/
|
|
482
525
|
const valueFor = (app, key, owner) => {
|
|
483
|
-
if (owner &&
|
|
484
|
-
|
|
485
|
-
if (
|
|
486
|
-
return
|
|
526
|
+
if (owner && SERVICE_KINDS[owner.kind]) {
|
|
527
|
+
if (custom[key] !== void 0) return custom[key];
|
|
528
|
+
if (owner.kind === "file-server") return fileServers.get(owner.id)?.url;
|
|
529
|
+
return outside[key];
|
|
487
530
|
}
|
|
488
531
|
if (owner && suppliedBy(owner, key, local)) {
|
|
489
532
|
const value = custom[key];
|
|
@@ -513,7 +556,8 @@ function composeStack(input) {
|
|
|
513
556
|
const owner = owners.get(key) ?? storageOwner(key, plan);
|
|
514
557
|
if (!local && owner?.kind === "email" && key.endsWith("_INBOX_URL")) continue;
|
|
515
558
|
if (STORAGE_KEYS.includes(key)) {
|
|
516
|
-
|
|
559
|
+
const value = custom[key] ?? storageValue(key, storage);
|
|
560
|
+
if (value !== void 0) values[key] = value;
|
|
517
561
|
continue;
|
|
518
562
|
}
|
|
519
563
|
const value = valueFor(app, key, owner);
|
|
@@ -533,34 +577,31 @@ function composeStack(input) {
|
|
|
533
577
|
};
|
|
534
578
|
for (const [key, value] of Object.entries(app.env)) if (/[\r\n]/.test(value)) throw new EnvValueMultiline(app.name, key);
|
|
535
579
|
}
|
|
580
|
+
const layout = input.layout ?? defaultLayout(workspace.root);
|
|
536
581
|
const compose = stackFile({
|
|
537
582
|
project,
|
|
538
583
|
plan,
|
|
539
584
|
infra,
|
|
540
585
|
apps,
|
|
541
586
|
master: credential.master,
|
|
587
|
+
...storage ? { storage } : {},
|
|
542
588
|
https,
|
|
543
|
-
http
|
|
589
|
+
http,
|
|
590
|
+
composeDir: join(workspace.root, stackDir(stage)),
|
|
591
|
+
buildRoot: layout.buildRoot,
|
|
592
|
+
workspaceRoot: workspace.root
|
|
544
593
|
});
|
|
545
|
-
const caddyfile = edgeCaddyfile(apps.map((app) => ({
|
|
594
|
+
const caddyfile = edgeCaddyfile([...apps.map((app) => ({
|
|
546
595
|
host: app.host,
|
|
547
596
|
upstream: `${app.name}:${app.port}`
|
|
548
|
-
})), { local });
|
|
597
|
+
})), ...[...fileServers.values()].map(({ site }) => site)], { local });
|
|
549
598
|
const dockerfiles = {};
|
|
550
599
|
for (const app of apps) {
|
|
551
600
|
if (!app.build) continue;
|
|
552
|
-
dockerfiles[app.build.dockerfile] =
|
|
553
|
-
|
|
554
|
-
|
|
601
|
+
dockerfiles[app.build.dockerfile] = appDockerfile(app.name, workspace.apps[app.name], {
|
|
602
|
+
layout,
|
|
603
|
+
workspaceRoot: workspace.root,
|
|
555
604
|
manifest
|
|
556
|
-
}) : generateSlimDockerfile({
|
|
557
|
-
imageName: app.name,
|
|
558
|
-
baseImage: "node:22-alpine",
|
|
559
|
-
port: app.port,
|
|
560
|
-
healthCheckPath: "/health",
|
|
561
|
-
prebuilt: true,
|
|
562
|
-
packageManager: input.packageManager ?? "pnpm",
|
|
563
|
-
bundle: `${app.path}/.gkm/server/dist/server.mjs`
|
|
564
605
|
});
|
|
565
606
|
}
|
|
566
607
|
return {
|
|
@@ -573,16 +614,56 @@ function composeStack(input) {
|
|
|
573
614
|
caddyfile,
|
|
574
615
|
dockerfiles,
|
|
575
616
|
plan,
|
|
576
|
-
credential
|
|
617
|
+
credential,
|
|
618
|
+
...storage ? { storage } : {},
|
|
619
|
+
devServices: local ? [] : devServicesUsed(services)
|
|
577
620
|
};
|
|
578
621
|
/** Whether only the stage's secrets can supply `key` to `owner`. */
|
|
579
622
|
function suppliedBy(owner, key, isLocal) {
|
|
580
623
|
if (owner.kind === "credential") return true;
|
|
581
624
|
if (owner.kind === "external-api") return key === provideKey(owner.id, "credentials");
|
|
582
625
|
if (isLocal) return false;
|
|
583
|
-
|
|
584
|
-
|
|
626
|
+
return owner.kind === "secret" || owner.kind === "encryption";
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* The mail and storage constructs a deployed stage's apps read, with who
|
|
631
|
+
* reads each — and every file server's bucket, which serves it whether or
|
|
632
|
+
* not an app reads it directly.
|
|
633
|
+
*/
|
|
634
|
+
function serviceDeclarations(plan, reads, owners) {
|
|
635
|
+
const byId = new Map(plan.resources.map((r) => [r.id, r]));
|
|
636
|
+
const readers = /* @__PURE__ */ new Map();
|
|
637
|
+
const add = (id, app) => {
|
|
638
|
+
const set = readers.get(id) ?? /* @__PURE__ */ new Set();
|
|
639
|
+
if (app) set.add(app);
|
|
640
|
+
readers.set(id, set);
|
|
641
|
+
};
|
|
642
|
+
for (const [app, keys] of reads) for (const key of keys) {
|
|
643
|
+
const owner = owners.get(key);
|
|
644
|
+
if (!owner || !SERVICE_KINDS[owner.kind]) continue;
|
|
645
|
+
if (owner.kind === "email" && key.endsWith("_INBOX_URL")) continue;
|
|
646
|
+
add(owner.id, app);
|
|
647
|
+
if (owner.kind === "file-server" && owner.of) add(owner.of);
|
|
585
648
|
}
|
|
649
|
+
return [...readers].flatMap(([id, apps]) => {
|
|
650
|
+
const resource = byId.get(id);
|
|
651
|
+
const kind = resource && SERVICE_KINDS[resource.kind];
|
|
652
|
+
if (!resource || !kind) return [];
|
|
653
|
+
return [{
|
|
654
|
+
id,
|
|
655
|
+
kind,
|
|
656
|
+
...resource.of ? { of: resource.of } : {},
|
|
657
|
+
apps: [...apps].sort()
|
|
658
|
+
}];
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
/** The stack's MinIO's value for one of the S3 client's keys. */
|
|
662
|
+
function storageValue(key, storage) {
|
|
663
|
+
if (!storage) return void 0;
|
|
664
|
+
if (key === "AWS_ACCESS_KEY_ID") return storage.user;
|
|
665
|
+
if (key === "AWS_SECRET_ACCESS_KEY") return storage.password;
|
|
666
|
+
if (key === "AWS_REGION") return MINIO_REGION;
|
|
586
667
|
}
|
|
587
668
|
/** Which bucket an S3 credential key belongs to — the first one declared. */
|
|
588
669
|
function storageOwner(key, plan) {
|
|
@@ -658,11 +739,24 @@ function stackFile(options) {
|
|
|
658
739
|
};
|
|
659
740
|
postgres.ports = ["127.0.0.1::5432"];
|
|
660
741
|
}
|
|
742
|
+
const minio = services.minio;
|
|
743
|
+
if (minio && options.storage) {
|
|
744
|
+
minio.environment = {
|
|
745
|
+
...minio.environment,
|
|
746
|
+
MINIO_ROOT_USER: options.storage.user,
|
|
747
|
+
MINIO_ROOT_PASSWORD: options.storage.password
|
|
748
|
+
};
|
|
749
|
+
minio.ports = ["127.0.0.1::9000"];
|
|
750
|
+
}
|
|
661
751
|
for (const app of apps) services[app.name] = {
|
|
662
752
|
image: app.ref,
|
|
663
753
|
...app.build ? { build: {
|
|
664
|
-
|
|
665
|
-
|
|
754
|
+
...composeBuildPaths({
|
|
755
|
+
composeDir: options.composeDir,
|
|
756
|
+
buildRoot: options.buildRoot,
|
|
757
|
+
workspaceRoot: options.workspaceRoot,
|
|
758
|
+
dockerfile: app.build.dockerfile
|
|
759
|
+
}),
|
|
666
760
|
...app.build.args ? { args: app.build.args } : {}
|
|
667
761
|
} } : {},
|
|
668
762
|
restart: "unless-stopped",
|
|
@@ -702,6 +796,80 @@ function stackFile(options) {
|
|
|
702
796
|
volumes
|
|
703
797
|
};
|
|
704
798
|
}
|
|
799
|
+
/**
|
|
800
|
+
* A build without a layout: the workspace is the build root, with pnpm.
|
|
801
|
+
* What a caller that never looked at the filesystem gets.
|
|
802
|
+
*/
|
|
803
|
+
function defaultLayout(root) {
|
|
804
|
+
return {
|
|
805
|
+
buildRoot: root,
|
|
806
|
+
gkmRoot: ".",
|
|
807
|
+
tools: {
|
|
808
|
+
packageManager: "pnpm",
|
|
809
|
+
turboVersion: TURBO_VERSION,
|
|
810
|
+
monorepo: true
|
|
811
|
+
},
|
|
812
|
+
gkmPaths: ["gkm.config.*"]
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
/** The id the stack's compose file names an app's build credentials by. */
|
|
816
|
+
function credentialsSecret(app) {
|
|
817
|
+
return `${app}_credentials`;
|
|
818
|
+
}
|
|
819
|
+
/** The file beside the compose file an app's build credentials are read from. */
|
|
820
|
+
function credentialsFile(app) {
|
|
821
|
+
return `${app}.credentials`;
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* The stack with each backend's image built with its encrypted credentials:
|
|
825
|
+
* a BuildKit secret per app — the file `credentialsFile` names, mounted as
|
|
826
|
+
* `gkm_credentials` — and the hash naming them as a build arg, so a new key
|
|
827
|
+
* rebuilds the layer that embeds them. Each backend's env file gains the
|
|
828
|
+
* `GKM_MASTER_KEY` that decrypts them, the way a Dokploy deploy injects it.
|
|
829
|
+
*
|
|
830
|
+
* Pure: the caller encrypted them.
|
|
831
|
+
*/
|
|
832
|
+
function withBuildCredentials(stack, credentials) {
|
|
833
|
+
const services = { ...stack.compose.services };
|
|
834
|
+
const secrets = { ...stack.compose.secrets };
|
|
835
|
+
const apps = stack.apps.map((app) => {
|
|
836
|
+
const own = credentials[app.name];
|
|
837
|
+
const service = services[app.name];
|
|
838
|
+
if (!own || !service?.build) return app;
|
|
839
|
+
const [key, value] = own.buildArg.split("=");
|
|
840
|
+
services[app.name] = {
|
|
841
|
+
...service,
|
|
842
|
+
build: {
|
|
843
|
+
...service.build,
|
|
844
|
+
args: {
|
|
845
|
+
...service.build.args,
|
|
846
|
+
[key]: value
|
|
847
|
+
},
|
|
848
|
+
secrets: [{
|
|
849
|
+
source: credentialsSecret(app.name),
|
|
850
|
+
target: "gkm_credentials"
|
|
851
|
+
}]
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
secrets[credentialsSecret(app.name)] = { file: `./${credentialsFile(app.name)}` };
|
|
855
|
+
return {
|
|
856
|
+
...app,
|
|
857
|
+
env: {
|
|
858
|
+
...app.env,
|
|
859
|
+
GKM_MASTER_KEY: own.masterKey
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
});
|
|
863
|
+
return {
|
|
864
|
+
...stack,
|
|
865
|
+
apps,
|
|
866
|
+
compose: {
|
|
867
|
+
...stack.compose,
|
|
868
|
+
services,
|
|
869
|
+
...Object.keys(secrets).length ? { secrets } : {}
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
}
|
|
705
873
|
/** Render an app's env file: one `KEY=value` per line, read raw by compose. */
|
|
706
874
|
function envFile(env) {
|
|
707
875
|
return `${Object.entries(env).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}=${value}`).join("\n")}\n`;
|
|
@@ -789,7 +957,11 @@ var ComposeAppsUnhealthy = class extends Error {
|
|
|
789
957
|
* - What was started answers (`verify`): each app is asked through Caddy,
|
|
790
958
|
* over HTTPS, with the certificate verified.
|
|
791
959
|
*
|
|
792
|
-
* Docker
|
|
960
|
+
* Every image is built inside Docker from a pruned slice of the build root —
|
|
961
|
+
* the same Dockerfiles `gkm docker` writes — so nothing is built on this
|
|
962
|
+
* machine before `docker compose build`.
|
|
963
|
+
*
|
|
964
|
+
* Docker, the registry, Postgres and the health probe are
|
|
793
965
|
* injected, so the rules are asserted without a daemon; the defaults are the
|
|
794
966
|
* real ones.
|
|
795
967
|
*/
|
|
@@ -813,28 +985,6 @@ var NoGitRevision = class extends Error {
|
|
|
813
985
|
this.name = "NoGitRevision";
|
|
814
986
|
}
|
|
815
987
|
};
|
|
816
|
-
/** A backend's production bundle did not build. */
|
|
817
|
-
var BundleFailed = class extends Error {
|
|
818
|
-
app;
|
|
819
|
-
exitCode;
|
|
820
|
-
stderr;
|
|
821
|
-
constructor(app, exitCode, stderr) {
|
|
822
|
-
super(`Bundling '${app}' for production failed (exit code ${exitCode}).${stderr.trim() ? `\n${stderr.trim().slice(-4e3)}` : ""}\nRun \`gkm build --provider server --production\` in the app's directory to see it again.`);
|
|
823
|
-
this.app = app;
|
|
824
|
-
this.exitCode = exitCode;
|
|
825
|
-
this.stderr = stderr;
|
|
826
|
-
this.name = "BundleFailed";
|
|
827
|
-
}
|
|
828
|
-
};
|
|
829
|
-
/** The CLI's own entry, which the bundle step runs, is not where it should be. */
|
|
830
|
-
var CliEntryNotFound = class extends Error {
|
|
831
|
-
from;
|
|
832
|
-
constructor(from) {
|
|
833
|
-
super(`Could not find bin/gkm.mjs above ${from}, which bundling a backend runs. Reinstall @geekmidas/cli.`);
|
|
834
|
-
this.from = from;
|
|
835
|
-
this.name = "CliEntryNotFound";
|
|
836
|
-
}
|
|
837
|
-
};
|
|
838
988
|
/** The commit HEAD is at, short — and `-dirty` when the tree has changes. */
|
|
839
989
|
async function gitRevision(root) {
|
|
840
990
|
let sha;
|
|
@@ -864,48 +1014,15 @@ function edgePorts(env = process.env) {
|
|
|
864
1014
|
http: read(EDGE_PORT_ENV.http, 80)
|
|
865
1015
|
};
|
|
866
1016
|
}
|
|
867
|
-
/** `bin/gkm.mjs` of the CLI that is running. */
|
|
868
|
-
function cliEntry() {
|
|
869
|
-
const from = dirname(fileURLToPath(import.meta.url));
|
|
870
|
-
for (let dir = from; dirname(dir) !== dir; dir = dirname(dir)) {
|
|
871
|
-
const entry = join(dir, "bin", "gkm.mjs");
|
|
872
|
-
if (existsSync(entry)) return entry;
|
|
873
|
-
}
|
|
874
|
-
throw new CliEntryNotFound(from);
|
|
875
|
-
}
|
|
876
|
-
/** A cold production bundle is seconds; a hung one must not hold the lock. */
|
|
877
|
-
const BUNDLE_TIMEOUT_MS = 6e5;
|
|
878
|
-
/**
|
|
879
|
-
* Bundle a backend with the gkm that is running — the same build a deploy
|
|
880
|
-
* makes, so the image holds one file and no dependencies — in the run's
|
|
881
|
-
* sandbox, because the build imports the project's code.
|
|
882
|
-
*/
|
|
883
|
-
async function bundleInSandbox(appRoot, ctx) {
|
|
884
|
-
const sandbox = activeSandbox() ?? new LocalSandbox({ root: ctx.cwd });
|
|
885
|
-
const result = await sandbox.exec("node", [
|
|
886
|
-
cliEntry(),
|
|
887
|
-
"build",
|
|
888
|
-
"--provider",
|
|
889
|
-
"server",
|
|
890
|
-
"--production"
|
|
891
|
-
], {
|
|
892
|
-
cwd: appRoot,
|
|
893
|
-
env: sandbox.env,
|
|
894
|
-
timeoutMs: BUNDLE_TIMEOUT_MS,
|
|
895
|
-
output: ctx.childOutput,
|
|
896
|
-
signal: ctx.signal
|
|
897
|
-
});
|
|
898
|
-
if (result.exitCode !== 0) throw new BundleFailed(appRoot, result.exitCode, result.stderr);
|
|
899
|
-
}
|
|
900
1017
|
/**
|
|
901
1018
|
* The real ones. Each app is asked for three minutes before it counts as
|
|
902
1019
|
* down: a deployed stage's first ACME certificate can take that long.
|
|
903
1020
|
*/
|
|
904
1021
|
const defaultDeps = {
|
|
905
1022
|
docker: dockerCompose,
|
|
906
|
-
bundle: bundleInSandbox,
|
|
907
1023
|
revision: gitRevision,
|
|
908
1024
|
sql: pgClient,
|
|
1025
|
+
buckets: bucketClient,
|
|
909
1026
|
migrate: migrateDatabases,
|
|
910
1027
|
probe: httpsProbe,
|
|
911
1028
|
healthAttempts: 90,
|
|
@@ -928,7 +1045,9 @@ async function validateCompose(ctx, deps, forced) {
|
|
|
928
1045
|
runnables
|
|
929
1046
|
});
|
|
930
1047
|
const { secrets, generated } = await stageSecrets(ctx, manifest);
|
|
931
|
-
const
|
|
1048
|
+
const masterKeys = {};
|
|
1049
|
+
const layout = mode === "build" ? imageLayout(workspace) : void 0;
|
|
1050
|
+
const composed = composeStack({
|
|
932
1051
|
workspace,
|
|
933
1052
|
manifest,
|
|
934
1053
|
runnables,
|
|
@@ -940,10 +1059,29 @@ async function validateCompose(ctx, deps, forced) {
|
|
|
940
1059
|
...workspace.deploy?.registry ? { registry: workspace.deploy.registry } : {}
|
|
941
1060
|
},
|
|
942
1061
|
secrets,
|
|
1062
|
+
allowDevServices: ctx.allowDevServices,
|
|
943
1063
|
ports: edgePorts(deps.env),
|
|
944
|
-
|
|
945
|
-
packages: Object.fromEntries(Object.entries(workspace.apps).map(([name, app]) => [name, appPackageName(root, name, app.path)]))
|
|
1064
|
+
...layout ? { layout } : {}
|
|
946
1065
|
});
|
|
1066
|
+
reportDevServices(ctx, composed.devServices);
|
|
1067
|
+
const credentials = {};
|
|
1068
|
+
if (mode === "build") for (const app of composed.apps) {
|
|
1069
|
+
if (app.kind !== "rest-api" || !app.env || !app.build) continue;
|
|
1070
|
+
const { encrypted, iv, masterKey } = encryptSecrets(app.env);
|
|
1071
|
+
credentials[app.name] = {
|
|
1072
|
+
encrypted,
|
|
1073
|
+
iv
|
|
1074
|
+
};
|
|
1075
|
+
ctx.secrets.mask(masterKey);
|
|
1076
|
+
masterKeys[app.name] = {
|
|
1077
|
+
masterKey,
|
|
1078
|
+
buildArg: credentialsBuildArg({
|
|
1079
|
+
encrypted,
|
|
1080
|
+
iv
|
|
1081
|
+
})
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
const stack = withBuildCredentials(composed, masterKeys);
|
|
947
1085
|
if (mode === "pull" && !ctx.dryRun) await assertImagesExist(deps.docker, tag, stack.apps);
|
|
948
1086
|
const dir = join(root, stackDir(stage));
|
|
949
1087
|
const previous = Object.fromEntries(Object.entries((await ctx.state.read(stage))?.state.releases ?? {}).map(([name, releases]) => {
|
|
@@ -969,6 +1107,7 @@ async function validateCompose(ctx, deps, forced) {
|
|
|
969
1107
|
generated,
|
|
970
1108
|
previous,
|
|
971
1109
|
files: [],
|
|
1110
|
+
credentials,
|
|
972
1111
|
images: {},
|
|
973
1112
|
changes: []
|
|
974
1113
|
};
|
|
@@ -1000,7 +1139,7 @@ async function stageSecrets(ctx, manifest) {
|
|
|
1000
1139
|
*/
|
|
1001
1140
|
async function planCompose(ctx, run) {
|
|
1002
1141
|
if (run.generated.length > 0) ctx.logger.info(`🔑 "${ctx.stage}" has no ${run.generated.join(", ")} yet; this dry run used values it did not keep.`);
|
|
1003
|
-
run.files = await writeStack(ctx.cwd, run.dir, run.stack);
|
|
1142
|
+
run.files = await writeStack(ctx.cwd, run.dir, run.stack, run.credentials);
|
|
1004
1143
|
printPlan(ctx, run);
|
|
1005
1144
|
const planned = (change) => {
|
|
1006
1145
|
run.changes.push(change);
|
|
@@ -1044,7 +1183,7 @@ async function provisionCompose(ctx, run, deps) {
|
|
|
1044
1183
|
await ctx.secrets.write(run.secrets);
|
|
1045
1184
|
ctx.logger.info(`🔑 Generated for "${ctx.stage}" (${ctx.secrets.store}): ${run.generated.join(", ")}`);
|
|
1046
1185
|
}
|
|
1047
|
-
run.files = await writeStack(ctx.cwd, run.dir, stack);
|
|
1186
|
+
run.files = await writeStack(ctx.cwd, run.dir, stack, run.credentials);
|
|
1048
1187
|
printPlan(ctx, run);
|
|
1049
1188
|
if (stack.infra.length > 0) {
|
|
1050
1189
|
ctx.logger.info(`\n🗄️ Starting ${stack.infra.join(", ")}…`);
|
|
@@ -1058,6 +1197,20 @@ async function provisionCompose(ctx, run, deps) {
|
|
|
1058
1197
|
});
|
|
1059
1198
|
}
|
|
1060
1199
|
if (stack.infra.includes("postgres")) await prepareDatabases(ctx, run, deps);
|
|
1200
|
+
if (stack.storage && stack.infra.includes("minio")) await prepareBuckets(ctx, run, deps);
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Create each bucket the stack's MinIO serves, and the open paths of each
|
|
1204
|
+
* one a file server fronts — from this machine, on MinIO's loopback port,
|
|
1205
|
+
* the way `gkm dev` creates them.
|
|
1206
|
+
*/
|
|
1207
|
+
async function prepareBuckets(ctx, run, deps) {
|
|
1208
|
+
const storage = run.stack.storage;
|
|
1209
|
+
if (storage.buckets.length === 0) return;
|
|
1210
|
+
const port = await deps.docker.port(run.ref, "minio", 9e3);
|
|
1211
|
+
const client = deps.buckets(port, storage);
|
|
1212
|
+
const created = [...await applyBuckets(client, storage.buckets), ...await applyPolicies(client, storage.policies)].filter((entry) => entry.created).length;
|
|
1213
|
+
ctx.logger.info(`🪣 Buckets: ${storage.buckets.join(", ")}${created ? ` (${created} change(s))` : ""}`);
|
|
1061
1214
|
}
|
|
1062
1215
|
/** Create what the plan names in the stack's Postgres, then migrate it. */
|
|
1063
1216
|
async function prepareDatabases(ctx, run, deps) {
|
|
@@ -1090,18 +1243,14 @@ async function prepareDatabases(ctx, run, deps) {
|
|
|
1090
1243
|
for (const { target, applied } of runs) if (applied.length > 0) ctx.logger.info(`🗄️ ${target.migrations}: applied ${applied.length}`);
|
|
1091
1244
|
}
|
|
1092
1245
|
/**
|
|
1093
|
-
* Each app's image: built from this checkout — every
|
|
1094
|
-
*
|
|
1246
|
+
* Each app's image: built from this checkout — inside Docker, every one of
|
|
1247
|
+
* them, from a pruned slice of the build root — or pulled at the tag. Nothing
|
|
1248
|
+
* live changes.
|
|
1095
1249
|
*/
|
|
1096
1250
|
async function buildCompose(ctx, run, deps) {
|
|
1097
1251
|
const { stack, ref } = run;
|
|
1098
1252
|
const apps = stack.apps.map((app) => app.name);
|
|
1099
1253
|
if (run.mode === "build") {
|
|
1100
|
-
for (const app of stack.apps.filter((a) => a.kind === "rest-api")) {
|
|
1101
|
-
ctx.signal.throwIfAborted();
|
|
1102
|
-
ctx.logger.info(`\n📦 Bundling ${app.name}…`);
|
|
1103
|
-
await deps.bundle(join(ctx.cwd, app.path), ctx);
|
|
1104
|
-
}
|
|
1105
1254
|
ctx.logger.info("\n🐳 Building images…");
|
|
1106
1255
|
await deps.docker.build(ref, apps);
|
|
1107
1256
|
} else {
|
|
@@ -1284,7 +1433,7 @@ function applied(ctx, run, change) {
|
|
|
1284
1433
|
});
|
|
1285
1434
|
}
|
|
1286
1435
|
/** Write the stack's files: owner-only, since the env files hold secrets. */
|
|
1287
|
-
async function writeStack(root, dir, stack) {
|
|
1436
|
+
async function writeStack(root, dir, stack, credentials = {}) {
|
|
1288
1437
|
await mkdir(dir, {
|
|
1289
1438
|
recursive: true,
|
|
1290
1439
|
mode: 448
|
|
@@ -1300,23 +1449,13 @@ async function writeStack(root, dir, stack) {
|
|
|
1300
1449
|
await write(join(dir, "docker-compose.yml"), composeYaml(stack));
|
|
1301
1450
|
await write(join(dir, "Caddyfile"), stack.caddyfile);
|
|
1302
1451
|
for (const app of stack.apps) if (app.env) await write(join(dir, `${app.name}.env`), envFile(app.env), 384);
|
|
1452
|
+
for (const [name, payload] of Object.entries(credentials)) await write(join(dir, credentialsFile(name)), credentialsFileContent(payload), 384);
|
|
1303
1453
|
for (const [path, content] of Object.entries(stack.dockerfiles)) await write(join(root, path), content);
|
|
1304
|
-
await
|
|
1454
|
+
await ensureDockerignore(findBuildRoot(root));
|
|
1305
1455
|
return files;
|
|
1306
1456
|
}
|
|
1307
|
-
/** Make sure the build context leaves `.gkm/compose` out. */
|
|
1308
|
-
async function ignoreStacks(root) {
|
|
1309
|
-
const path = join(root, ".dockerignore");
|
|
1310
|
-
if (!existsSync(path)) {
|
|
1311
|
-
await writeFile(path, generateDockerignore());
|
|
1312
|
-
return;
|
|
1313
|
-
}
|
|
1314
|
-
const current = await readFile(path, "utf-8");
|
|
1315
|
-
if (/^\/?\.gkm\/compose\/?$|^\/?\.gkm\/?$/m.test(current)) return;
|
|
1316
|
-
await appendFile(path, `${current.endsWith("\n") ? "" : "\n"}\n# gkm compose's stacks: each app's env file holds its stage's secrets\n.gkm/compose\n`);
|
|
1317
|
-
}
|
|
1318
1457
|
function composeYaml(stack) {
|
|
1319
|
-
const stage =
|
|
1458
|
+
const stage = ` --stage ${stack.stage}`;
|
|
1320
1459
|
return `# Generated by gkm compose from the construct manifest — do not edit.
|
|
1321
1460
|
# The ${stack.stage} stage's APIs and sites behind one Caddy. Each backend
|
|
1322
1461
|
# reads exactly the keys in its own env file beside this one.
|
|
@@ -1395,4 +1534,4 @@ function composeTarget(options = {}) {
|
|
|
1395
1534
|
//#endregion
|
|
1396
1535
|
export { dockerCompose as a, stackDir as i, compose_exports as n, composeProject as r, composeTarget as t };
|
|
1397
1536
|
|
|
1398
|
-
//# sourceMappingURL=compose-
|
|
1537
|
+
//# sourceMappingURL=compose-DFxuYhn6.mjs.map
|