@geekmidas/cli 10.0.0-alpha.74 → 10.0.0-alpha.75

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.
Files changed (94) hide show
  1. package/dist/{LegacyStateStore-D_78eaHM.cjs → LegacyStateStore-BFnXUFlx.cjs} +2 -2
  2. package/dist/{LegacyStateStore-D_78eaHM.cjs.map → LegacyStateStore-BFnXUFlx.cjs.map} +1 -1
  3. package/dist/{LegacyStateStore-DTo7z924.mjs → LegacyStateStore-cpt3K1vx.mjs} +2 -2
  4. package/dist/{LegacyStateStore-DTo7z924.mjs.map → LegacyStateStore-cpt3K1vx.mjs.map} +1 -1
  5. package/dist/{LocalStateStore-CNjgkRk4.mjs → LocalStateStore-RH9F2ZEy.mjs} +2 -2
  6. package/dist/{LocalStateStore-CNjgkRk4.mjs.map → LocalStateStore-RH9F2ZEy.mjs.map} +1 -1
  7. package/dist/{LocalStateStore-BhrlQIXD.cjs → LocalStateStore-zQGZ9b4J.cjs} +2 -2
  8. package/dist/{LocalStateStore-BhrlQIXD.cjs.map → LocalStateStore-zQGZ9b4J.cjs.map} +1 -1
  9. package/dist/{S3StateStore-oCxRs6HM.cjs → S3StateStore-CT-ZMlQY.cjs} +2 -2
  10. package/dist/{S3StateStore-oCxRs6HM.cjs.map → S3StateStore-CT-ZMlQY.cjs.map} +1 -1
  11. package/dist/{S3StateStore-g2P7T_Mo.mjs → S3StateStore-DBeBc0c0.mjs} +2 -2
  12. package/dist/{S3StateStore-g2P7T_Mo.mjs.map → S3StateStore-DBeBc0c0.mjs.map} +1 -1
  13. package/dist/{SSMStateStore-DXp0m7XI.cjs → SSMStateStore-HmsH3c4v.cjs} +2 -2
  14. package/dist/{SSMStateStore-DXp0m7XI.cjs.map → SSMStateStore-HmsH3c4v.cjs.map} +1 -1
  15. package/dist/{SSMStateStore-CYenunmK.mjs → SSMStateStore-rnzNfOaV.mjs} +2 -2
  16. package/dist/{SSMStateStore-CYenunmK.mjs.map → SSMStateStore-rnzNfOaV.mjs.map} +1 -1
  17. package/dist/{add-Djrr12Xc.cjs → add-CHBin8TF.cjs} +2 -2
  18. package/dist/{add-Djrr12Xc.cjs.map → add-CHBin8TF.cjs.map} +1 -1
  19. package/dist/{add-BTH7WaH3.mjs → add-Cvzdw8cf.mjs} +2 -2
  20. package/dist/{add-BTH7WaH3.mjs.map → add-Cvzdw8cf.mjs.map} +1 -1
  21. package/dist/{bundler-utHuESvL.cjs → bundler-_8AEZhrs.cjs} +5 -5
  22. package/dist/{bundler-utHuESvL.cjs.map → bundler-_8AEZhrs.cjs.map} +1 -1
  23. package/dist/{bundler-BUojOkof.mjs → bundler-xjp5RwRS.mjs} +5 -5
  24. package/dist/{bundler-BUojOkof.mjs.map → bundler-xjp5RwRS.mjs.map} +1 -1
  25. package/dist/{cli-Bx0sUzxH.cjs → cli-B4ktxAsM.cjs} +3 -3
  26. package/dist/{cli-Bx0sUzxH.cjs.map → cli-B4ktxAsM.cjs.map} +1 -1
  27. package/dist/{cli-CyYceKTS.mjs → cli-BTGfA0B7.mjs} +3 -3
  28. package/dist/{cli-CyYceKTS.mjs.map → cli-BTGfA0B7.mjs.map} +1 -1
  29. package/dist/{compose-DZp8DJ0A.mjs → compose-DdH2JQJD.mjs} +219 -152
  30. package/dist/compose-DdH2JQJD.mjs.map +1 -0
  31. package/dist/{compose-iuw8wfHk.cjs → compose-UKyv_1yU.cjs} +241 -150
  32. package/dist/compose-UKyv_1yU.cjs.map +1 -0
  33. package/dist/deploy/public.cjs +7 -6
  34. package/dist/deploy/public.d.cts +11 -0
  35. package/dist/deploy/public.d.cts.map +1 -1
  36. package/dist/deploy/public.d.mts +11 -0
  37. package/dist/deploy/public.d.mts.map +1 -1
  38. package/dist/deploy/public.mjs +7 -7
  39. package/dist/{deploy-C17K7Imy.mjs → deploy-CuSsiFIc.mjs} +21 -5
  40. package/dist/deploy-CuSsiFIc.mjs.map +1 -0
  41. package/dist/{deploy-eWorhvQp.cjs → deploy-XUZoA1us.cjs} +26 -4
  42. package/dist/deploy-XUZoA1us.cjs.map +1 -0
  43. package/dist/{dokploy-DKzcI-RL.cjs → dokploy-Cvgg_pID.cjs} +2 -2
  44. package/dist/{dokploy-DKzcI-RL.cjs.map → dokploy-Cvgg_pID.cjs.map} +1 -1
  45. package/dist/{dokploy-DKa49Nqm.mjs → dokploy-DZXTQX10.mjs} +2 -2
  46. package/dist/{dokploy-DKa49Nqm.mjs.map → dokploy-DZXTQX10.mjs.map} +1 -1
  47. package/dist/{encryption-sTMz3aWi.mjs → encryption-JkeMPugf.mjs} +2 -2
  48. package/dist/{encryption-sTMz3aWi.mjs.map → encryption-JkeMPugf.mjs.map} +1 -1
  49. package/dist/{encryption-DSkJdoyk.cjs → encryption-w-Ehh9DW.cjs} +2 -2
  50. package/dist/{encryption-DSkJdoyk.cjs.map → encryption-w-Ehh9DW.cjs.map} +1 -1
  51. package/dist/{engine-B1b4I34Z.mjs → engine-CmozkHxq.mjs} +192 -4
  52. package/dist/engine-CmozkHxq.mjs.map +1 -0
  53. package/dist/{engine-Bh76sAHy.cjs → engine-zpWBMsQq.cjs} +195 -7
  54. package/dist/engine-zpWBMsQq.cjs.map +1 -0
  55. package/dist/{github-BvN3CfxM.cjs → github-BGdJ-Lnm.cjs} +2 -2
  56. package/dist/{github-BvN3CfxM.cjs.map → github-BGdJ-Lnm.cjs.map} +1 -1
  57. package/dist/{github-C9ODoyZU.mjs → github-BNngHoId.mjs} +2 -2
  58. package/dist/{github-C9ODoyZU.mjs.map → github-BNngHoId.mjs.map} +1 -1
  59. package/dist/index.cjs +70 -45
  60. package/dist/index.cjs.map +1 -1
  61. package/dist/index.mjs +69 -44
  62. package/dist/index.mjs.map +1 -1
  63. package/dist/{migrate-CTtJhGV9.mjs → migrate-BLL1RTAj.mjs} +2 -2
  64. package/dist/{migrate-CTtJhGV9.mjs.map → migrate-BLL1RTAj.mjs.map} +1 -1
  65. package/dist/{migrate-B0FCZNkp.cjs → migrate-CgjlYeun.cjs} +2 -2
  66. package/dist/{migrate-B0FCZNkp.cjs.map → migrate-CgjlYeun.cjs.map} +1 -1
  67. package/dist/{resolve-DSAXj_47.mjs → resolve-DZNx7rLy.mjs} +3 -3
  68. package/dist/{resolve-DSAXj_47.mjs.map → resolve-DZNx7rLy.mjs.map} +1 -1
  69. package/dist/{resolve-D_BU-wKA.cjs → resolve-fevXn-wZ.cjs} +3 -3
  70. package/dist/{resolve-D_BU-wKA.cjs.map → resolve-fevXn-wZ.cjs.map} +1 -1
  71. package/dist/{rollback-CWYTUhfW.mjs → rollback-CLi5Pln_.mjs} +4 -4
  72. package/dist/{rollback-CWYTUhfW.mjs.map → rollback-CLi5Pln_.mjs.map} +1 -1
  73. package/dist/{rollback-DJy3t696.cjs → rollback-Dw-FW3X2.cjs} +4 -4
  74. package/dist/{rollback-DJy3t696.cjs.map → rollback-Dw-FW3X2.cjs.map} +1 -1
  75. package/dist/{sniffer-BFmdZpZ2.mjs → sniffer-BmCvVyhq.mjs} +59 -7
  76. package/dist/sniffer-BmCvVyhq.mjs.map +1 -0
  77. package/dist/{sniffer-C-T7dSMN.cjs → sniffer-bMzrX4cO.cjs} +75 -5
  78. package/dist/{sniffer-C-T7dSMN.cjs.map → sniffer-bMzrX4cO.cjs.map} +1 -1
  79. package/dist/{state-DpkxSwzI.mjs → state-BrWIOce_.mjs} +46 -284
  80. package/dist/state-BrWIOce_.mjs.map +1 -0
  81. package/dist/{state-DCwRWyh-.cjs → state-kA4Fkufx.cjs} +56 -312
  82. package/dist/state-kA4Fkufx.cjs.map +1 -0
  83. package/dist/target/public.cjs +1 -1
  84. package/dist/target/public.mjs +1 -1
  85. package/package.json +17 -17
  86. package/dist/compose-DZp8DJ0A.mjs.map +0 -1
  87. package/dist/compose-iuw8wfHk.cjs.map +0 -1
  88. package/dist/deploy-C17K7Imy.mjs.map +0 -1
  89. package/dist/deploy-eWorhvQp.cjs.map +0 -1
  90. package/dist/engine-B1b4I34Z.mjs.map +0 -1
  91. package/dist/engine-Bh76sAHy.cjs.map +0 -1
  92. package/dist/sniffer-BFmdZpZ2.mjs.map +0 -1
  93. package/dist/state-DCwRWyh-.cjs.map +0 -1
  94. package/dist/state-DpkxSwzI.mjs.map +0 -1
@@ -4,7 +4,7 @@ const require_discover = require("./discover-D1It5r_h.cjs");
4
4
  const require_derive = require("./derive-DAbo49sm.cjs");
5
5
  const require_workspace = require("./workspace-vdrcRI-m.cjs");
6
6
  const require_define = require("./define-DzRz8FgH.cjs");
7
- const require_state = require("./state-DCwRWyh-.cjs");
7
+ const require_state = require("./state-kA4Fkufx.cjs");
8
8
  const require_SubscriberGenerator = require("./SubscriberGenerator-CajwOa5W.cjs");
9
9
  const require_drivers = require("./drivers-BdvtCKc7.cjs");
10
10
  const require_databases = require("./databases-C2p5LGAp.cjs");
@@ -64,6 +64,33 @@ var ServicePortUnknown = class extends Error {
64
64
  this.name = "ServicePortUnknown";
65
65
  }
66
66
  };
67
+ /**
68
+ * Whether `ref` names a registry on this machine's loopback — which the
69
+ * daemon pulls from and pushes to over plain HTTP by default, and `docker
70
+ * manifest inspect` reaches only when told `--insecure` (without it, a local
71
+ * registry's image is reported as no such manifest).
72
+ */
73
+ function isLoopbackRegistry(ref) {
74
+ const first = ref.split("/")[0];
75
+ if (!ref.includes("/") || !/[.:]|^localhost$/.test(first)) return false;
76
+ const host = first.replace(/:\d+$/, "");
77
+ return host === "localhost" || /^127(?:\.\d{1,3}){3}$/.test(host);
78
+ }
79
+ /** `docker push` finished without saying what digest the registry stored. */
80
+ var PushDigestUnknown = class extends Error {
81
+ ref;
82
+ output;
83
+ constructor(ref, output) {
84
+ super(`Pushed ${ref}, but docker did not say which digest the registry stored (it said: ${output.trim().split("\n").slice(-3).join(" / ") || "nothing"}). Check the push with \`docker buildx imagetools inspect ${ref}\`.`);
85
+ this.ref = ref;
86
+ this.output = output;
87
+ this.name = "PushDigestUnknown";
88
+ }
89
+ };
90
+ /** The digest `docker push` reports last: `<tag>: digest: sha256:… size: …`. */
91
+ function pushedDigest(output) {
92
+ return [...output.matchAll(/digest: (sha256:[0-9a-f]{64})/g)].at(-1)?.[1];
93
+ }
67
94
  function compose(stack, args) {
68
95
  return [
69
96
  "compose",
@@ -127,6 +154,7 @@ const dockerCompose = {
127
154
  const { code, stdout, stderr } = await capture("docker", [
128
155
  "manifest",
129
156
  "inspect",
157
+ ...isLoopbackRegistry(ref) ? ["--insecure"] : [],
130
158
  ref
131
159
  ]);
132
160
  if (code === 0) return {
@@ -196,6 +224,15 @@ const dockerCompose = {
196
224
  to
197
225
  ]), runOptions(stack));
198
226
  },
227
+ async push(stack, ref) {
228
+ const output = await require_run.runOutput("docker", ["push", ref], {
229
+ cwd: stack.cwd,
230
+ ...stack.signal ? { signal: stack.signal } : {}
231
+ });
232
+ const digest = pushedDigest(output);
233
+ if (!digest) throw new PushDigestUnknown(ref, output);
234
+ return digest;
235
+ },
199
236
  async digest(ref) {
200
237
  const { code, stdout } = await capture("docker", [
201
238
  "image",
@@ -266,6 +303,92 @@ async function assertImagesExist(docker, tag, images) {
266
303
  const missing = lookups.filter((lookup) => lookup.status === "missing").map((lookup) => lookup.ref);
267
304
  if (missing.length > 0) throw new ImageTagNotFound(tag, missing);
268
305
  }
306
+ /**
307
+ * A push or a pull with no `deploy.registry` to push to or pull from.
308
+ *
309
+ * Without one an image is named `<namespace>/<project>-<app>:<tag>`, which
310
+ * Docker resolves to Docker Hub — so a release would be pushed to, or pulled
311
+ * from, somebody else's account. It is never a fallback.
312
+ */
313
+ var RegistryRequired = class extends Error {
314
+ operation;
315
+ ref;
316
+ constructor(operation, ref) {
317
+ super(`${operation === "push" ? "Pushing" : "Pulling"} images needs a registry, and gkm.config.ts sets no deploy.registry: '${ref}' would be ${operation === "push" ? "pushed to" : "pulled from"} Docker Hub. Set deploy.registry (e.g. registry: 'ghcr.io/acme') in gkm.config.ts.` + (operation === "pull" ? " To run images built here instead, pass --build." : ""));
318
+ this.operation = operation;
319
+ this.ref = ref;
320
+ this.name = "RegistryRequired";
321
+ }
322
+ };
323
+ /** An image pinned to its digest: `<ref>@sha256:…`. */
324
+ function pinnedRef(ref, digest) {
325
+ return `${ref.split("@")[0]}@${digest}`;
326
+ }
327
+ /** A digests file with no entry for an app the stack runs. */
328
+ var ImageDigestMissing = class extends Error {
329
+ file;
330
+ apps;
331
+ constructor(file, apps) {
332
+ super(`${file} pins no digest for ${apps.join(", ")}. Use the file the same release's \`gkm compose --build --push --digests-file\` wrote, or run without --digests-file to pull by tag.`);
333
+ this.file = file;
334
+ this.apps = apps;
335
+ this.name = "ImageDigestMissing";
336
+ }
337
+ };
338
+ /** A digests file entry that is not this app's image. */
339
+ var ImageDigestMismatch = class extends Error {
340
+ app;
341
+ expected;
342
+ found;
343
+ constructor(app, expected, found) {
344
+ super(`The digests file pins '${app}' to ${found}, but this release runs ${expected}. It was written for another registry, project, stage or tag; use the file this stage's push of this tag wrote.`);
345
+ this.app = app;
346
+ this.expected = expected;
347
+ this.found = found;
348
+ this.name = "ImageDigestMismatch";
349
+ }
350
+ };
351
+ /** A digests file that is not a JSON object of app to pinned ref. */
352
+ var ImageDigestsInvalid = class extends Error {
353
+ file;
354
+ detail;
355
+ constructor(file, detail) {
356
+ super(`${file} is not a digests file (${detail}). It should be the JSON object \`gkm compose --build --push --digests-file\` writes: { "api": "<registry>/<image>:<tag>@sha256:…" }.`);
357
+ this.file = file;
358
+ this.detail = detail;
359
+ this.name = "ImageDigestsInvalid";
360
+ }
361
+ };
362
+ /** Parse a digests file's content, checking its shape. */
363
+ function parseDigests(file, content) {
364
+ let parsed;
365
+ try {
366
+ parsed = JSON.parse(content);
367
+ } catch (error) {
368
+ throw new ImageDigestsInvalid(file, error instanceof Error ? error.message : String(error));
369
+ }
370
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new ImageDigestsInvalid(file, "not a JSON object");
371
+ for (const [app, value] of Object.entries(parsed)) if (typeof value !== "string" || !/@sha256:[0-9a-f]{64}$/.test(value)) throw new ImageDigestsInvalid(file, `'${app}' is not an <image>:<tag>@sha256:… ref`);
372
+ return parsed;
373
+ }
374
+ /**
375
+ * Each image pinned to the digest the file names for it.
376
+ *
377
+ * @throws {ImageDigestMissing} naming every app the file has no entry for
378
+ * @throws {ImageDigestMismatch} when an entry is another image or tag
379
+ */
380
+ function pinImages(file, digests, images) {
381
+ const missing = images.filter((image) => !digests[image.app]).map((image) => image.app);
382
+ if (missing.length > 0) throw new ImageDigestMissing(file, missing);
383
+ return images.map((image) => {
384
+ const pinned = digests[image.app];
385
+ if (pinned.split("@")[0] !== image.ref) throw new ImageDigestMismatch(image.app, image.ref, pinned);
386
+ return {
387
+ ...image,
388
+ ref: pinned
389
+ };
390
+ });
391
+ }
269
392
  //#endregion
270
393
  //#region src/compose/logs.ts
271
394
  /**
@@ -950,6 +1073,7 @@ function composeStack(input) {
950
1073
  if (app.build && Object.keys(args).length > 0) app.build.args = args;
951
1074
  continue;
952
1075
  }
1076
+ if (input.buildOnly) continue;
953
1077
  const keys = require_state.appEnvKeys(manifest, app.name, input.runnables) ?? /* @__PURE__ */ new Set();
954
1078
  const values = valuesFor(app, keys);
955
1079
  const trusted = (0, _geekmidas_manifest.provideKey)(app.id, "trustedOrigins");
@@ -970,7 +1094,7 @@ function composeStack(input) {
970
1094
  };
971
1095
  for (const [key, value] of Object.entries(app.env)) if (/[\r\n]/.test(value)) throw new EnvValueMultiline(app.name, key);
972
1096
  }
973
- for (const worker of workers) {
1097
+ for (const worker of input.buildOnly ? [] : workers) {
974
1098
  const keys = require_state.workerEnvKeys(manifest, worker.id, input.runnables) ?? /* @__PURE__ */ new Set();
975
1099
  worker.env = {
976
1100
  ...valuesFor({
@@ -1003,7 +1127,8 @@ function composeStack(input) {
1003
1127
  http,
1004
1128
  composeDir: (0, node_path.join)(workspace.root, stackDir(stage)),
1005
1129
  buildRoot: layout.buildRoot,
1006
- workspaceRoot: workspace.root
1130
+ workspaceRoot: workspace.root,
1131
+ envFiles: !input.buildOnly
1007
1132
  });
1008
1133
  const caddyfile = edgeCaddyfile([
1009
1134
  ...apps.map((app) => ({
@@ -1254,10 +1379,13 @@ function stackFile(options) {
1254
1379
  retries: 5
1255
1380
  }
1256
1381
  };
1257
- for (const service of Object.values(services)) service.logging = {
1258
- driver: LOG_ROTATION.driver,
1259
- options: { ...LOG_ROTATION.options }
1260
- };
1382
+ for (const service of Object.values(services)) {
1383
+ if (!options.envFiles) delete service.env_file;
1384
+ service.logging = {
1385
+ driver: LOG_ROTATION.driver,
1386
+ options: { ...LOG_ROTATION.options }
1387
+ };
1388
+ }
1261
1389
  return {
1262
1390
  name: project,
1263
1391
  services,
@@ -1280,93 +1408,6 @@ function defaultLayout(root) {
1280
1408
  gkmPaths: ["gkm.config.*"]
1281
1409
  };
1282
1410
  }
1283
- /** The id the stack's compose file names an app's build credentials by. */
1284
- function credentialsSecret(app) {
1285
- return `${app}_credentials`;
1286
- }
1287
- /** The file beside the compose file an app's build credentials are read from. */
1288
- function credentialsFile(app) {
1289
- return `${app}.credentials`;
1290
- }
1291
- /**
1292
- * The stack with each backend's image built with its encrypted credentials:
1293
- * a BuildKit secret per app — the file `credentialsFile` names, mounted as
1294
- * `gkm_credentials` — and the hash naming them as a build arg, so a new key
1295
- * rebuilds the layer that embeds them. Each backend's env file gains the
1296
- * `GKM_MASTER_KEY` that decrypts them, the way a Dokploy deploy injects it.
1297
- *
1298
- * Pure: the caller encrypted them.
1299
- */
1300
- function withBuildCredentials(stack, credentials) {
1301
- const services = { ...stack.compose.services };
1302
- const secrets = { ...stack.compose.secrets };
1303
- const apps = stack.apps.map((app) => {
1304
- const own = credentials[app.name];
1305
- const service = services[app.name];
1306
- if (!own || !service?.build) return app;
1307
- const [key, value] = own.buildArg.split("=");
1308
- services[app.name] = {
1309
- ...service,
1310
- build: {
1311
- ...service.build,
1312
- args: {
1313
- ...service.build.args,
1314
- [key]: value
1315
- },
1316
- secrets: [{
1317
- source: credentialsSecret(app.name),
1318
- target: "gkm_credentials"
1319
- }]
1320
- }
1321
- };
1322
- secrets[credentialsSecret(app.name)] = { file: `./${credentialsFile(app.name)}` };
1323
- return {
1324
- ...app,
1325
- env: {
1326
- ...app.env,
1327
- GKM_MASTER_KEY: own.masterKey
1328
- }
1329
- };
1330
- });
1331
- const workers = stack.workers.map((worker) => {
1332
- const own = credentials[worker.name];
1333
- const service = services[worker.name];
1334
- if (!own || !service?.build) return worker;
1335
- const [key, value] = own.buildArg.split("=");
1336
- services[worker.name] = {
1337
- ...service,
1338
- build: {
1339
- ...service.build,
1340
- args: {
1341
- ...service.build.args,
1342
- [key]: value
1343
- },
1344
- secrets: [{
1345
- source: credentialsSecret(worker.name),
1346
- target: "gkm_credentials"
1347
- }]
1348
- }
1349
- };
1350
- secrets[credentialsSecret(worker.name)] = { file: `./${credentialsFile(worker.name)}` };
1351
- return {
1352
- ...worker,
1353
- env: {
1354
- ...worker.env,
1355
- GKM_MASTER_KEY: own.masterKey
1356
- }
1357
- };
1358
- });
1359
- return {
1360
- ...stack,
1361
- apps,
1362
- workers,
1363
- compose: {
1364
- ...stack.compose,
1365
- services,
1366
- ...Object.keys(secrets).length ? { secrets } : {}
1367
- }
1368
- };
1369
- }
1370
1411
  /** Render an app's env file: one `KEY=value` per line, read raw by compose. */
1371
1412
  function envFile(env) {
1372
1413
  return `${Object.entries(env).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}=${value}`).join("\n")}\n`;
@@ -1462,6 +1503,20 @@ var ComposeAppsUnhealthy = class extends Error {
1462
1503
  * injected, so the rules are asserted without a daemon; the defaults are the
1463
1504
  * real ones.
1464
1505
  */
1506
+ /** `--push` without `--build`: there is nothing built here to push. */
1507
+ var ComposePushNeedsBuild = class extends Error {
1508
+ constructor() {
1509
+ super("--push pushes the images this checkout builds, so it needs --build (and never --pull): gkm compose --stage <stage> --build --push --tag <tag>.");
1510
+ this.name = "ComposePushNeedsBuild";
1511
+ }
1512
+ };
1513
+ /** Digests to run by, on a run that pulls nothing. */
1514
+ var ComposePinNeedsPull = class extends Error {
1515
+ constructor() {
1516
+ super("--digests-file pins the images a release pulls, so it needs --tag (or --pull) — or, with --build --push, it is where the pushed digests are written.");
1517
+ this.name = "ComposePinNeedsPull";
1518
+ }
1519
+ };
1465
1520
  /** A port variable that does not hold a port. */
1466
1521
  var EdgePortInvalid = class extends Error {
1467
1522
  variable;
@@ -1540,10 +1595,15 @@ const defaultDeps = {
1540
1595
  * every image it names exists. Nothing is written: a missing image stops the
1541
1596
  * run here, before a file, a secret or a container is touched.
1542
1597
  */
1543
- async function validateCompose(ctx, deps, forced) {
1598
+ async function validateCompose(ctx, deps, options = {}) {
1544
1599
  const { workspace, stage, identity, tag } = ctx;
1545
1600
  const root = workspace.root;
1546
- const mode = forced ?? (ctx.tagGiven ? "pull" : "build");
1601
+ const mode = options.mode ?? (ctx.tagGiven ? "pull" : "build");
1602
+ const push = options.push === true;
1603
+ if (push && mode !== "build") throw new ComposePushNeedsBuild();
1604
+ if (options.pin && mode !== "pull") throw new ComposePinNeedsPull();
1605
+ const registry = workspace.deploy?.registry;
1606
+ if (!registry && (push || mode === "pull")) throw new RegistryRequired(push ? "push" : "pull", require_state.imageRef(identity, ctx.apps[0] ?? workspace.name, void 0, tag));
1547
1607
  const runnables = {};
1548
1608
  const background = {};
1549
1609
  const manifest = await require_discover.discover({
@@ -1553,14 +1613,13 @@ async function validateCompose(ctx, deps, forced) {
1553
1613
  background
1554
1614
  });
1555
1615
  const { secrets, generated } = await stageSecrets(ctx, manifest);
1556
- await require_credentialSchemas.assertStageCredentials({
1616
+ if (!push) await require_credentialSchemas.assertStageCredentials({
1557
1617
  root,
1558
1618
  patterns: require_state.constructGlobs(workspace),
1559
1619
  manifest,
1560
1620
  stage,
1561
1621
  supplied: secrets?.custom ?? {}
1562
1622
  });
1563
- const masterKeys = {};
1564
1623
  const layout = mode === "build" ? require_state.imageLayout(workspace) : void 0;
1565
1624
  const composed = composeStack({
1566
1625
  workspace,
@@ -1572,51 +1631,17 @@ async function validateCompose(ctx, deps, forced) {
1572
1631
  images: {
1573
1632
  mode,
1574
1633
  tag,
1575
- ...workspace.deploy?.registry ? { registry: workspace.deploy.registry } : {}
1634
+ ...registry ? { registry } : {}
1576
1635
  },
1577
1636
  secrets,
1578
1637
  allowDevServices: ctx.allowDevServices,
1579
1638
  ports: edgePorts(deps.env),
1580
- ...layout ? { layout } : {}
1639
+ ...layout ? { layout } : {},
1640
+ ...push ? { buildOnly: true } : {}
1581
1641
  });
1582
1642
  require_state.reportDevServices(ctx, composed.devServices);
1583
1643
  if (mode === "build") assertRedisClient(workspace, composed);
1584
- const credentials = {};
1585
- if (mode === "build") {
1586
- for (const app of composed.apps) {
1587
- if (app.kind !== "rest-api" || !app.env || !app.build) continue;
1588
- const { encrypted, iv, masterKey } = require_state.encryptSecrets(app.env);
1589
- credentials[app.name] = {
1590
- encrypted,
1591
- iv
1592
- };
1593
- ctx.secrets.mask(masterKey);
1594
- masterKeys[app.name] = {
1595
- masterKey,
1596
- buildArg: require_state.credentialsBuildArg({
1597
- encrypted,
1598
- iv
1599
- })
1600
- };
1601
- }
1602
- for (const worker of composed.workers) {
1603
- if (!worker.env || !worker.build) continue;
1604
- const { encrypted, iv, masterKey } = require_state.encryptSecrets(worker.env);
1605
- credentials[worker.name] = {
1606
- encrypted,
1607
- iv
1608
- };
1609
- ctx.secrets.mask(masterKey);
1610
- masterKeys[worker.name] = {
1611
- masterKey,
1612
- buildArg: require_state.credentialsBuildArg({
1613
- encrypted,
1614
- iv
1615
- })
1616
- };
1617
- }
1618
- }
1619
- const stack = withBuildCredentials(composed, masterKeys);
1644
+ const stack = options.pin ? withPinnedImages(composed, options.pin.file, options.pin.digests) : composed;
1620
1645
  if (mode === "pull" && !ctx.dryRun) await assertImagesExist(deps.docker, tag, [...stack.apps, ...stack.workers]);
1621
1646
  if (stack.redis && !stack.local) ctx.secrets.mask(stack.redis.password);
1622
1647
  if (stack.logs && (!stack.local || stack.logs.passwordFromSecrets)) {
@@ -1627,7 +1652,8 @@ async function validateCompose(ctx, deps, forced) {
1627
1652
  const override = stackOverrideFile(root, stage);
1628
1653
  const overrides = (0, node_fs.existsSync)(override) ? [override] : [];
1629
1654
  if (overrides.length > 0) ctx.logger.info(`🧩 Merging docker-compose.${stage}.yml over the generated stack`);
1630
- const previous = Object.fromEntries(Object.entries((await ctx.state.read(stage))?.state.releases ?? {}).map(([name, releases]) => {
1655
+ const recorded = push ? void 0 : await ctx.state.read(stage);
1656
+ const previous = Object.fromEntries(Object.entries(recorded?.state.releases ?? {}).map(([name, releases]) => {
1631
1657
  const { ref, tag, digest } = releases.current;
1632
1658
  return [name, {
1633
1659
  ref,
@@ -1637,6 +1663,7 @@ async function validateCompose(ctx, deps, forced) {
1637
1663
  }));
1638
1664
  return {
1639
1665
  mode,
1666
+ push,
1640
1667
  stack,
1641
1668
  dir,
1642
1669
  ref: {
@@ -1651,7 +1678,6 @@ async function validateCompose(ctx, deps, forced) {
1651
1678
  generated,
1652
1679
  previous,
1653
1680
  files: [],
1654
- credentials,
1655
1681
  images: {},
1656
1682
  changes: []
1657
1683
  };
@@ -1713,13 +1739,36 @@ async function stageSecrets(ctx, manifest) {
1713
1739
  };
1714
1740
  }
1715
1741
  /**
1742
+ * The stack with each app's and worker's image pinned to the digest the file
1743
+ * names: the refs it is checked, pulled and recorded by, and the image each
1744
+ * compose service runs.
1745
+ */
1746
+ function withPinnedImages(stack, file, digests) {
1747
+ const apps = pinImages(file, digests, stack.apps);
1748
+ const workers = pinImages(file, digests, stack.workers);
1749
+ const services = { ...stack.compose.services };
1750
+ for (const image of [...apps, ...workers]) services[image.name] = {
1751
+ ...services[image.name],
1752
+ image: image.ref
1753
+ };
1754
+ return {
1755
+ ...stack,
1756
+ apps,
1757
+ workers,
1758
+ compose: {
1759
+ ...stack.compose,
1760
+ services
1761
+ }
1762
+ };
1763
+ }
1764
+ /**
1716
1765
  * A dry run: the stack's files, written so they can be read, and what a real
1717
1766
  * run would do — no image is built or pulled, no container touched, nothing
1718
1767
  * recorded and no secret kept.
1719
1768
  */
1720
1769
  async function planCompose(ctx, run) {
1721
1770
  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.`);
1722
- run.files = await writeStack(ctx.cwd, run.dir, run.stack, run.credentials);
1771
+ run.files = await writeStack(ctx.cwd, run.dir, run.stack);
1723
1772
  printPlan(ctx, run);
1724
1773
  const planned = (change) => {
1725
1774
  run.changes.push(change);
@@ -1728,7 +1777,7 @@ async function planCompose(ctx, run) {
1728
1777
  ...change
1729
1778
  });
1730
1779
  };
1731
- for (const service of run.stack.infra) planned({
1780
+ for (const service of run.push ? [] : run.stack.infra) planned({
1732
1781
  key: `service:${service}`,
1733
1782
  resourceType: "service",
1734
1783
  action: "ensure"
@@ -1740,6 +1789,7 @@ async function planCompose(ctx, run) {
1740
1789
  action: run.mode === "build" ? "build" : "reuse",
1741
1790
  id: app.ref
1742
1791
  });
1792
+ if (run.push) continue;
1743
1793
  planned({
1744
1794
  key: `service:${app.name}`,
1745
1795
  resourceType: "service",
@@ -1753,13 +1803,14 @@ async function planCompose(ctx, run) {
1753
1803
  action: run.mode === "build" ? "build" : "reuse",
1754
1804
  id: worker.ref
1755
1805
  });
1806
+ if (run.push) continue;
1756
1807
  planned({
1757
1808
  key: `service:${worker.name}`,
1758
1809
  resourceType: "service",
1759
1810
  action: run.previous[worker.name] ? "reuse" : "create"
1760
1811
  });
1761
1812
  }
1762
- planned({
1813
+ if (!run.push) planned({
1763
1814
  key: "service:caddy",
1764
1815
  resourceType: "service",
1765
1816
  action: "ensure"
@@ -1772,11 +1823,12 @@ async function planCompose(ctx, run) {
1772
1823
  */
1773
1824
  async function provisionCompose(ctx, run, deps) {
1774
1825
  const { stack, ref } = run;
1826
+ if (run.push) return;
1775
1827
  if (run.generated.length > 0 && run.secrets) {
1776
1828
  await ctx.secrets.write(run.secrets);
1777
1829
  ctx.logger.info(`🔑 Generated for "${ctx.stage}" (${ctx.secrets.store}): ${run.generated.join(", ")}`);
1778
1830
  }
1779
- run.files = await writeStack(ctx.cwd, run.dir, stack, run.credentials);
1831
+ run.files = await writeStack(ctx.cwd, run.dir, stack);
1780
1832
  printPlan(ctx, run);
1781
1833
  if (stack.infra.length > 0) {
1782
1834
  ctx.logger.info(`\n🗄️ Starting ${stack.infra.join(", ")}…`);
@@ -1844,6 +1896,10 @@ async function buildCompose(ctx, run, deps) {
1844
1896
  const { stack, ref } = run;
1845
1897
  const images = [...stack.apps, ...stack.workers];
1846
1898
  const apps = images.map((image) => image.app);
1899
+ if (run.push) {
1900
+ run.files = await writeStack(ctx.cwd, run.dir, stack);
1901
+ printPlan(ctx, run);
1902
+ }
1847
1903
  if (run.mode === "build") {
1848
1904
  ctx.logger.info("\n🐳 Building images…");
1849
1905
  await deps.docker.build(ref, apps);
@@ -1851,8 +1907,10 @@ async function buildCompose(ctx, run, deps) {
1851
1907
  ctx.logger.info(`\n🐳 Pulling ${ctx.tag}…`);
1852
1908
  await deps.docker.pull(ref, apps);
1853
1909
  }
1910
+ if (run.push) ctx.logger.info("\n📤 Pushing images…");
1854
1911
  for (const image of images) {
1855
- const digest = await deps.docker.digest(image.ref);
1912
+ const digest = run.push ? await deps.docker.push(ref, image.ref) : await deps.docker.digest(image.ref);
1913
+ if (run.push) ctx.logger.info(` ${image.app.padEnd(12)} ${image.ref}@${digest}`);
1856
1914
  run.images[image.app] = {
1857
1915
  ref: image.ref,
1858
1916
  tag: image.tag,
@@ -1878,6 +1936,7 @@ async function buildCompose(ctx, run, deps) {
1878
1936
  */
1879
1937
  async function releaseCompose(ctx, run, deps) {
1880
1938
  const { stack, ref } = run;
1939
+ if (run.push) return;
1881
1940
  ctx.logger.info("\n🚀 Starting the stack…");
1882
1941
  await deps.docker.up(ref);
1883
1942
  if (stack.local) await deps.docker.copyOut(ref, "caddy", "/data/caddy/pki/authorities/local/root.crt", caFile(run)).catch(() => {});
@@ -1936,6 +1995,7 @@ async function recordImages(ctx, run) {
1936
1995
  */
1937
1996
  async function verifyCompose(ctx, run, deps) {
1938
1997
  const { stack } = run;
1998
+ if (run.push) return;
1939
1999
  const ca = stack.local ? await (0, node_fs_promises.readFile)(caFile(run), "utf-8").catch(() => void 0) : void 0;
1940
2000
  ctx.logger.info("\n🩺 Checking each app through Caddy…");
1941
2001
  const results = await Promise.all(stack.apps.map((app) => checkApp(ctx, app, deps, {
@@ -2115,7 +2175,7 @@ function applied(ctx, run, change) {
2115
2175
  });
2116
2176
  }
2117
2177
  /** Write the stack's files: owner-only, since the env files hold secrets. */
2118
- async function writeStack(root, dir, stack, credentials = {}) {
2178
+ async function writeStack(root, dir, stack) {
2119
2179
  await (0, node_fs_promises.mkdir)(dir, {
2120
2180
  recursive: true,
2121
2181
  mode: 448
@@ -2131,9 +2191,9 @@ async function writeStack(root, dir, stack, credentials = {}) {
2131
2191
  await write((0, node_path.join)(dir, "docker-compose.yml"), composeYaml(stack));
2132
2192
  await write((0, node_path.join)(dir, "Caddyfile"), stack.caddyfile);
2133
2193
  for (const app of [...stack.apps, ...stack.workers]) if (app.env) await write((0, node_path.join)(dir, `${app.name}.env`), envFile(app.env), 384);
2134
- if (stack.logs) await write((0, node_path.join)(dir, `${LOGS_SERVICE}.env`), envFile(stack.logs.env), 384);
2135
- if (stack.redis) await write((0, node_path.join)(dir, `${REDIS_SERVICE}.env`), envFile(stack.redis.env), 384);
2136
- for (const [name, payload] of Object.entries(credentials)) await write((0, node_path.join)(dir, credentialsFile(name)), require_state.credentialsFileContent(payload), 384);
2194
+ if (stack.logs && stack.compose.services["openobserve"]?.env_file) await write((0, node_path.join)(dir, `${LOGS_SERVICE}.env`), envFile(stack.logs.env), 384);
2195
+ if (stack.redis && stack.compose.services["redis"]?.env_file) await write((0, node_path.join)(dir, `${REDIS_SERVICE}.env`), envFile(stack.redis.env), 384);
2196
+ for (const entry of await (0, node_fs_promises.readdir)(dir)) if (entry.endsWith(".credentials")) await (0, node_fs_promises.rm)((0, node_path.join)(dir, entry));
2137
2197
  for (const [path, content] of Object.entries(stack.dockerfiles)) await write((0, node_path.join)(root, path), content);
2138
2198
  await require_state.ensureDockerignore(require_state.findBuildRoot(root));
2139
2199
  return files;
@@ -2183,13 +2243,16 @@ function printPlan(ctx, run) {
2183
2243
  * digest in the stage's state
2184
2244
  * - `verify`: each app asked through Caddy over HTTPS
2185
2245
  *
2246
+ * With `push` (`gkm compose --build --push`), run with `buildOnly`, it builds
2247
+ * every image and pushes it to `deploy.registry`, and does nothing else.
2248
+ *
2186
2249
  * `gkm compose` is this target with a few more switches (`--build`, `--pull`,
2187
2250
  * `--down`), run through the same `deploy()`.
2188
2251
  */
2189
2252
  var compose_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ composeTarget: () => composeTarget });
2190
2253
  /** The compose target, with its Docker, bundler and probe as given. */
2191
2254
  function composeTarget(options = {}) {
2192
- const { mode, report, ...given } = options;
2255
+ const { mode, push, pin, report, ...given } = options;
2193
2256
  const deps = {
2194
2257
  ...defaultDeps,
2195
2258
  ...given
@@ -2205,7 +2268,11 @@ function composeTarget(options = {}) {
2205
2268
  },
2206
2269
  credentials: [],
2207
2270
  tag: ({ cwd }) => deps.revision(cwd),
2208
- validate: (ctx) => validateCompose(ctx, deps, mode),
2271
+ validate: (ctx) => validateCompose(ctx, deps, {
2272
+ ...mode ? { mode } : {},
2273
+ ...push ? { push } : {},
2274
+ ...pin ? { pin } : {}
2275
+ }),
2209
2276
  plan: (ctx, run) => planCompose(ctx, run),
2210
2277
  provision: (ctx, run) => provisionCompose(ctx, run, deps),
2211
2278
  build: (ctx, run) => buildCompose(ctx, run, deps),
@@ -2218,6 +2285,18 @@ function composeTarget(options = {}) {
2218
2285
  });
2219
2286
  }
2220
2287
  //#endregion
2288
+ Object.defineProperty(exports, "ComposePinNeedsPull", {
2289
+ enumerable: true,
2290
+ get: function() {
2291
+ return ComposePinNeedsPull;
2292
+ }
2293
+ });
2294
+ Object.defineProperty(exports, "ComposePushNeedsBuild", {
2295
+ enumerable: true,
2296
+ get: function() {
2297
+ return ComposePushNeedsBuild;
2298
+ }
2299
+ });
2221
2300
  Object.defineProperty(exports, "composeProject", {
2222
2301
  enumerable: true,
2223
2302
  get: function() {
@@ -2242,6 +2321,18 @@ Object.defineProperty(exports, "dockerCompose", {
2242
2321
  return dockerCompose;
2243
2322
  }
2244
2323
  });
2324
+ Object.defineProperty(exports, "parseDigests", {
2325
+ enumerable: true,
2326
+ get: function() {
2327
+ return parseDigests;
2328
+ }
2329
+ });
2330
+ Object.defineProperty(exports, "pinnedRef", {
2331
+ enumerable: true,
2332
+ get: function() {
2333
+ return pinnedRef;
2334
+ }
2335
+ });
2245
2336
  Object.defineProperty(exports, "stackDir", {
2246
2337
  enumerable: true,
2247
2338
  get: function() {
@@ -2255,4 +2346,4 @@ Object.defineProperty(exports, "stackOverrideFile", {
2255
2346
  }
2256
2347
  });
2257
2348
 
2258
- //# sourceMappingURL=compose-iuw8wfHk.cjs.map
2349
+ //# sourceMappingURL=compose-UKyv_1yU.cjs.map