@geekmidas/cli 10.0.0-alpha.56 → 10.0.0-alpha.58

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 (77) hide show
  1. package/dist/{OpenApiTsGenerator-BLRQ7Iu9.cjs → OpenApiTsGenerator-BCWPRZwD.cjs} +153 -4
  2. package/dist/OpenApiTsGenerator-BCWPRZwD.cjs.map +1 -0
  3. package/dist/{OpenApiTsGenerator-NThANByQ.mjs → OpenApiTsGenerator-BRWD1Wit.mjs} +149 -6
  4. package/dist/OpenApiTsGenerator-BRWD1Wit.mjs.map +1 -0
  5. package/dist/{SSMStateStore-oda9xOGc.mjs → SSMStateStore-BjZZm-nV.mjs} +14 -7
  6. package/dist/SSMStateStore-BjZZm-nV.mjs.map +1 -0
  7. package/dist/{SSMStateStore-DD7jKrCO.cjs → SSMStateStore-DC43R7zG.cjs} +14 -7
  8. package/dist/SSMStateStore-DC43R7zG.cjs.map +1 -0
  9. package/dist/{backends-BcHL7Gy_.mjs → backends-CjuCiE8h.mjs} +2 -2
  10. package/dist/{backends-BcHL7Gy_.mjs.map → backends-CjuCiE8h.mjs.map} +1 -1
  11. package/dist/{backends-_gQdAzBK.cjs → backends-v3xDuhO0.cjs} +2 -2
  12. package/dist/{backends-_gQdAzBK.cjs.map → backends-v3xDuhO0.cjs.map} +1 -1
  13. package/dist/{config-20V08LZp.mjs → config-CSOK73pY.mjs} +2 -2
  14. package/dist/{config-20V08LZp.mjs.map → config-CSOK73pY.mjs.map} +1 -1
  15. package/dist/{config-CP6wsLHA.cjs → config-DdIu1O_I.cjs} +2 -2
  16. package/dist/{config-CP6wsLHA.cjs.map → config-DdIu1O_I.cjs.map} +1 -1
  17. package/dist/config.cjs +2 -2
  18. package/dist/config.d.cts +2 -2
  19. package/dist/config.d.mts +2 -2
  20. package/dist/config.mjs +2 -2
  21. package/dist/{encryption-10Srqiix.cjs → encryption-BspqzZIb.cjs} +4 -4
  22. package/dist/{encryption-10Srqiix.cjs.map → encryption-BspqzZIb.cjs.map} +1 -1
  23. package/dist/{encryption-KQTYFEzU.mjs → encryption-Dlnl5XEU.mjs} +4 -4
  24. package/dist/{encryption-KQTYFEzU.mjs.map → encryption-Dlnl5XEU.mjs.map} +1 -1
  25. package/dist/{github-y7OEj59M.mjs → github-BdF9tsvl.mjs} +3 -3
  26. package/dist/{github-y7OEj59M.mjs.map → github-BdF9tsvl.mjs.map} +1 -1
  27. package/dist/{github-DtRUkHN8.cjs → github-mQorSS36.cjs} +3 -3
  28. package/dist/{github-DtRUkHN8.cjs.map → github-mQorSS36.cjs.map} +1 -1
  29. package/dist/{index-DMcjpvd-.d.cts → index-BorYE_uO.d.cts} +2 -2
  30. package/dist/{index-DMcjpvd-.d.cts.map → index-BorYE_uO.d.cts.map} +1 -1
  31. package/dist/{index-RM92lx8A.d.mts → index-C3JmluyO.d.mts} +2 -2
  32. package/dist/{index-RM92lx8A.d.mts.map → index-C3JmluyO.d.mts.map} +1 -1
  33. package/dist/index.cjs +488 -135
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.mjs +488 -135
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/{migrate-CS3DkO6y.mjs → migrate-CXVnh1J3.mjs} +4 -4
  38. package/dist/{migrate-CS3DkO6y.mjs.map → migrate-CXVnh1J3.mjs.map} +1 -1
  39. package/dist/{migrate-DQ54y2WD.cjs → migrate-DzYqA-e3.cjs} +4 -4
  40. package/dist/{migrate-DQ54y2WD.cjs.map → migrate-DzYqA-e3.cjs.map} +1 -1
  41. package/dist/openapi.cjs +2 -2
  42. package/dist/openapi.d.cts +1 -1
  43. package/dist/openapi.d.mts +1 -1
  44. package/dist/openapi.mjs +2 -2
  45. package/dist/{ready-BSpUZnNo.mjs → ready-DbDHneLR.mjs} +2 -2
  46. package/dist/{ready-BSpUZnNo.mjs.map → ready-DbDHneLR.mjs.map} +1 -1
  47. package/dist/{ready-MUhVJDHe.cjs → ready-mR96orkJ.cjs} +2 -2
  48. package/dist/{ready-MUhVJDHe.cjs.map → ready-mR96orkJ.cjs.map} +1 -1
  49. package/dist/reconcile/public.d.cts +1 -1
  50. package/dist/reconcile/public.d.mts +1 -1
  51. package/dist/{types-BIyIQUR-.d.cts → types-D7j6QWeX.d.mts} +32 -2
  52. package/dist/types-D7j6QWeX.d.mts.map +1 -0
  53. package/dist/{types-CmQl6zaP.d.mts → types-JV4XOY01.d.cts} +33 -1
  54. package/dist/types-JV4XOY01.d.cts.map +1 -0
  55. package/dist/vitest.cjs +1 -1
  56. package/dist/vitest.mjs +1 -1
  57. package/dist/workspace/index.cjs +1 -1
  58. package/dist/workspace/index.d.cts +2 -2
  59. package/dist/workspace/index.d.mts +2 -2
  60. package/dist/workspace/index.mjs +1 -1
  61. package/dist/{workspace-BbPJVCto.cjs → workspace-B14L2TrY.cjs} +5 -5
  62. package/dist/{workspace-BbPJVCto.cjs.map → workspace-B14L2TrY.cjs.map} +1 -1
  63. package/dist/{workspace-BpdXIeWV.cjs → workspace-Bah36xSG.cjs} +3 -1
  64. package/dist/workspace-Bah36xSG.cjs.map +1 -0
  65. package/dist/{workspace-C2mFV53i.mjs → workspace-BvNXAXQI.mjs} +3 -1
  66. package/dist/workspace-BvNXAXQI.mjs.map +1 -0
  67. package/dist/{workspace-CgWcE_Yr.mjs → workspace-D-f58jsP.mjs} +5 -5
  68. package/dist/{workspace-CgWcE_Yr.mjs.map → workspace-D-f58jsP.mjs.map} +1 -1
  69. package/package.json +13 -13
  70. package/dist/OpenApiTsGenerator-BLRQ7Iu9.cjs.map +0 -1
  71. package/dist/OpenApiTsGenerator-NThANByQ.mjs.map +0 -1
  72. package/dist/SSMStateStore-DD7jKrCO.cjs.map +0 -1
  73. package/dist/SSMStateStore-oda9xOGc.mjs.map +0 -1
  74. package/dist/types-BIyIQUR-.d.cts.map +0 -1
  75. package/dist/types-CmQl6zaP.d.mts.map +0 -1
  76. package/dist/workspace-BpdXIeWV.cjs.map +0 -1
  77. package/dist/workspace-C2mFV53i.mjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -2,15 +2,15 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
4
4
  const require_discover = require("./discover-DXNviAh-.cjs");
5
- const require_config = require("./config-CP6wsLHA.cjs");
6
- const require_workspace = require("./workspace-BpdXIeWV.cjs");
5
+ const require_config = require("./config-DdIu1O_I.cjs");
6
+ const require_workspace = require("./workspace-Bah36xSG.cjs");
7
7
  const require_storage = require("./storage-CSGoC3bK.cjs");
8
8
  const require_store = require("./store-DQgxT_WR.cjs");
9
- const require_workspace$1 = require("./workspace-BbPJVCto.cjs");
10
- const require_OpenApiTsGenerator = require("./OpenApiTsGenerator-BLRQ7Iu9.cjs");
9
+ const require_workspace$1 = require("./workspace-B14L2TrY.cjs");
10
+ const require_OpenApiTsGenerator = require("./OpenApiTsGenerator-BCWPRZwD.cjs");
11
11
  const require_openapi = require("./openapi.cjs");
12
- const require_ready = require("./ready-MUhVJDHe.cjs");
13
- const require_backends = require("./backends-_gQdAzBK.cjs");
12
+ const require_ready = require("./ready-mR96orkJ.cjs");
13
+ const require_backends = require("./backends-v3xDuhO0.cjs");
14
14
  const require_emit = require("./emit-Dmc1nvdS.cjs");
15
15
  let commander = require("commander");
16
16
  let node_process = require("node:process");
@@ -48,7 +48,7 @@ let _geekmidas_constructs_queue = require("@geekmidas/constructs/queue");
48
48
  let _geekmidas_constructs_topic = require("@geekmidas/constructs/topic");
49
49
  let node_dns_promises = require("node:dns/promises");
50
50
  //#region package.json
51
- var version = "10.0.0-alpha.56";
51
+ var version = "10.0.0-alpha.58";
52
52
  //#endregion
53
53
  //#region src/auth/credentials.ts
54
54
  /**
@@ -142,15 +142,6 @@ async function getDokployToken(options) {
142
142
  return null;
143
143
  }
144
144
  /**
145
- * Store Dokploy registry ID
146
- */
147
- async function storeDokployRegistryId(registryId, options) {
148
- const credentials = await readCredentials(options);
149
- if (!credentials.dokploy) throw new Error("Dokploy credentials not found. Run \"gkm login --service dokploy\" first.");
150
- credentials.dokploy.registryId = registryId;
151
- await writeCredentials(credentials, options);
152
- }
153
- /**
154
145
  * Get Dokploy registry ID from stored credentials
155
146
  */
156
147
  async function getDokployRegistryId(options) {
@@ -1746,9 +1737,9 @@ async function workspaceDevCommand(configured, options) {
1746
1737
  for (const { id, port } of fakes) logger$9.log(`🎭 ${id} fake: http://localhost:${port}`);
1747
1738
  logger$9.log(options.migrate || options.seed ? "🗄️ Migrating the databases" : "🗄️ Checking for pending migrations");
1748
1739
  if (options.migrate || options.seed) {
1749
- const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-DQ54y2WD.cjs"));
1740
+ const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-DzYqA-e3.cjs"));
1750
1741
  await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
1751
- } else await Promise.resolve().then(() => require("./migrate-DQ54y2WD.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1742
+ } else await Promise.resolve().then(() => require("./migrate-DzYqA-e3.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1752
1743
  logger$9.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
1753
1744
  });
1754
1745
  const appUrls = {};
@@ -2774,14 +2765,34 @@ var CommandTimedOut = class extends Error {
2774
2765
  * once `timeoutMs` passes (after the child is gone), and with the spawn error
2775
2766
  * itself when the program cannot be started at all — `ENOENT` already names it.
2776
2767
  */
2777
- function run(command, args, options = {}) {
2768
+ async function run(command, args, options = {}) {
2769
+ await spawnChild(command, args, options, false);
2770
+ }
2771
+ /**
2772
+ * {@link run}, resolving with what the program wrote to stdout — for a value
2773
+ * the CLI reads back, such as the digest `docker inspect` reports. stderr still
2774
+ * reaches the terminal, so a failure explains itself the same way.
2775
+ */
2776
+ function runOutput(command, args, options = {}) {
2777
+ return spawnChild(command, args, options, true);
2778
+ }
2779
+ function spawnChild(command, args, options, capture) {
2778
2780
  const { timeoutMs = DEFAULT_TIMEOUT_MS, ...spawnOptions } = options;
2779
2781
  return new Promise((resolve, reject) => {
2780
2782
  const child = (0, node_child_process.spawn)(command, [...args], {
2781
2783
  stdio: "inherit",
2782
2784
  ...spawnOptions,
2785
+ ...capture ? { stdio: [
2786
+ "ignore",
2787
+ "pipe",
2788
+ "inherit"
2789
+ ] } : {},
2783
2790
  shell: false
2784
2791
  });
2792
+ let stdout = "";
2793
+ child.stdout?.on("data", (chunk) => {
2794
+ stdout += chunk.toString();
2795
+ });
2785
2796
  let timedOut = false;
2786
2797
  let killer;
2787
2798
  const timer = setTimeout(() => {
@@ -2802,7 +2813,7 @@ function run(command, args, options = {}) {
2802
2813
  child.on("close", (code, signal) => {
2803
2814
  settle();
2804
2815
  if (timedOut) reject(new CommandTimedOut(command, args, timeoutMs));
2805
- else if (code === 0) resolve();
2816
+ else if (code === 0) resolve(stdout);
2806
2817
  else reject(new CommandFailed(command, args, code, signal));
2807
2818
  });
2808
2819
  });
@@ -3109,7 +3120,8 @@ async function buildOneApp(input) {
3109
3120
  hooks,
3110
3121
  markOptional: options.markOptional ?? false,
3111
3122
  skipBundle: options.skipBundle ?? false,
3112
- stage: options.stage
3123
+ stage: options.stage,
3124
+ workspaceName: workspace.name
3113
3125
  });
3114
3126
  }
3115
3127
  /**
@@ -3187,7 +3199,12 @@ async function buildApp(input) {
3187
3199
  appRoot,
3188
3200
  cache: require_ready.cacheBackendsIn(declared, cacheBackend)
3189
3201
  }),
3190
- markOptional: input.markOptional ?? false
3202
+ markOptional: input.markOptional ?? false,
3203
+ ...production && { telemetry: require_OpenApiTsGenerator.telemetryFor({
3204
+ appRoot,
3205
+ surfaceId: derived.surface?.id,
3206
+ workspaceName: input.workspaceName
3207
+ }) }
3191
3208
  };
3192
3209
  const endpointGenerator = new require_OpenApiTsGenerator.EndpointGenerator();
3193
3210
  const functionGenerator = new FunctionGenerator();
@@ -3596,7 +3613,7 @@ const PROVISIONERS = {
3596
3613
  const name = require_workspace$1.resourceName(declaration.id, declaration.kind, context.stage);
3597
3614
  const { postgres } = await context.api.findOrCreatePostgres(serviceName({
3598
3615
  stage: context.stage,
3599
- app: context.project
3616
+ app: context.scope ?? context.project
3600
3617
  }, declaration.id), context.projectId, context.environmentId, {
3601
3618
  databaseName: name,
3602
3619
  databasePassword: derivedPassword(context, `${name}:master`)
@@ -3730,7 +3747,7 @@ const PROVISIONERS = {
3730
3747
  if (backend !== "minio") throw new UnprovisionableBucket(declaration.id, backend);
3731
3748
  const service = serviceName({
3732
3749
  stage: context.stage,
3733
- app: context.project
3750
+ app: context.scope ?? context.project
3734
3751
  }, declaration.id);
3735
3752
  const bucket = require_workspace$1.resourceName(declaration.id, declaration.kind, context.stage);
3736
3753
  const user = `${bucket}-root`;
@@ -4191,6 +4208,7 @@ async function provisionDeclared(options) {
4191
4208
  environmentId: options.environmentId,
4192
4209
  stage: options.stage,
4193
4210
  project: workspace.name,
4211
+ scope: options.scope ?? workspace.name,
4194
4212
  cache: require_backends.cacheBackendFor(require_backends.providerOf(workspace)),
4195
4213
  events: require_backends.eventsBackendFor(require_backends.providerOf(workspace)),
4196
4214
  storage: require_backends.storageBackendFor(require_backends.providerOf(workspace)),
@@ -4307,6 +4325,15 @@ function setApplicationId(state, appName, applicationId) {
4307
4325
  state.applications[appName] = applicationId;
4308
4326
  }
4309
4327
  /**
4328
+ * Record the image an app was deployed with (mutates state)
4329
+ */
4330
+ function setDeployedImage(state, appName, image) {
4331
+ state.images = {
4332
+ ...state.images,
4333
+ [appName]: image
4334
+ };
4335
+ }
4336
+ /**
4310
4337
  * Get a generated secret for an app
4311
4338
  */
4312
4339
  function getGeneratedSecret(state, appName, secretName) {
@@ -6084,6 +6111,33 @@ async function pushImage(imageRef) {
6084
6111
  }
6085
6112
  }
6086
6113
  /**
6114
+ * The digest the registry gave a pushed image, `sha256:…`.
6115
+ *
6116
+ * A tag can be pushed over — by the next deploy, or by anything else that
6117
+ * shares the repository — and the digest is what says which image a stage
6118
+ * actually ran. `RepoDigests` has one entry per repository the image was
6119
+ * pushed to, so the one for this ref's repository is picked by name.
6120
+ *
6121
+ * @internal Exported for testing
6122
+ */
6123
+ async function pushedDigest(imageRef) {
6124
+ const repository = imageRef.split("@")[0].replace(/:[\w][\w.-]*$/, "");
6125
+ const output = await runOutput("docker", [
6126
+ "image",
6127
+ "inspect",
6128
+ "--format={{json .RepoDigests}}",
6129
+ imageRef
6130
+ ], { cwd: process.cwd() });
6131
+ let digests;
6132
+ try {
6133
+ digests = JSON.parse(output.trim() || "[]");
6134
+ } catch {
6135
+ return;
6136
+ }
6137
+ if (!Array.isArray(digests)) return void 0;
6138
+ return digests.find((entry) => typeof entry === "string" && entry.startsWith(`${repository}@`))?.slice(repository.length + 1);
6139
+ }
6140
+ /**
6087
6141
  * Deploy using Docker (build and optionally push image)
6088
6142
  */
6089
6143
  async function deployDocker(options) {
@@ -6091,9 +6145,13 @@ async function deployDocker(options) {
6091
6145
  const imageName = config.imageName;
6092
6146
  const imageRef = validateImageRef(getImageRef(config.registry, imageName, tag));
6093
6147
  await buildImage(imageRef, buildArgs, credentials);
6148
+ let digest;
6094
6149
  if (!skipPush) {
6095
6150
  if (!config.registry) logger$5.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
6096
- else await pushImage(imageRef);
6151
+ else {
6152
+ await pushImage(imageRef);
6153
+ digest = await pushedDigest(imageRef);
6154
+ }
6097
6155
  }
6098
6156
  logger$5.log("\n✅ Docker deployment ready!");
6099
6157
  logger$5.log(`\n📋 Deployment details:`);
@@ -6102,25 +6160,10 @@ async function deployDocker(options) {
6102
6160
  if (masterKey) logger$5.log(`\n🔐 Run the container with GKM_MASTER_KEY set to the key with fingerprint ${keyFingerprint(masterKey)}.`);
6103
6161
  return {
6104
6162
  imageRef,
6163
+ ...digest ? { digest } : {},
6105
6164
  masterKey
6106
6165
  };
6107
6166
  }
6108
- /**
6109
- * What one application is called on a provider.
6110
- *
6111
- * The same rule the constructs use, through the same `scopedName`: the
6112
- * application beside `production-kitchen-sink-database` is
6113
- * `production-kitchen-sink-api`, not `api`. Both deploy paths call this — the
6114
- * workspace one named its applications by the bare app key, so a project held
6115
- * an `api` and a `web` that every stage would collide on.
6116
- *
6117
- * The app id is dropped when it repeats the project, so a project named for its
6118
- * one application is `production-kitchen-sink` rather than
6119
- * `production-kitchen-sink-kitchen-sink`.
6120
- */
6121
- function applicationName(stage, project, app) {
6122
- return app === project ? `${stage}-${project}`.toLowerCase() : (0, _geekmidas_manifest.scopedName)([stage, project], app);
6123
- }
6124
6167
  //#endregion
6125
6168
  //#region src/deploy/dokploy-api.ts
6126
6169
  var dokploy_api_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
@@ -6337,6 +6380,18 @@ var DokployApi = class {
6337
6380
  });
6338
6381
  }
6339
6382
  /**
6383
+ * Change a project's name or description.
6384
+ *
6385
+ * The name is sent with the description because `project.update` validates
6386
+ * the whole project on some Dokploy versions, and a missing name fails it.
6387
+ */
6388
+ async updateProject(projectId, updates) {
6389
+ await this.post("project.update", {
6390
+ projectId,
6391
+ ...updates
6392
+ });
6393
+ }
6394
+ /**
6340
6395
  * Delete a project and all its resources
6341
6396
  */
6342
6397
  async deleteProject(projectId) {
@@ -7036,6 +7091,309 @@ function random() {
7036
7091
  return (0, node_crypto.randomBytes)(32).toString("base64url");
7037
7092
  }
7038
7093
  //#endregion
7094
+ //#region src/deploy/identity.ts
7095
+ /**
7096
+ * Who a deploy is, on a target it shares with other workspaces.
7097
+ *
7098
+ * A Dokploy server is one flat namespace of projects, images and Docker
7099
+ * service names. Deploys used to identify everything they made by the
7100
+ * workspace name alone, so two workspaces called `shop` — or `Shop` and
7101
+ * `shop` — deploying to one server landed in the same project, pushed over
7102
+ * each other's images and redeployed each other's applications. A
7103
+ * `DeployIdentity` is what a deploy names and claims its resources by instead.
7104
+ */
7105
+ /** A namespace that cannot be part of a Dokploy name or an image path. */
7106
+ var DeployNamespaceInvalid = class extends Error {
7107
+ namespace;
7108
+ constructor(namespace) {
7109
+ super(`deploy.namespace '${namespace}' is not usable as a name: use lowercase letters, digits and single '-' between them (e.g. 'acme' or 'acme-platform') in gkm.config.ts.`);
7110
+ this.namespace = namespace;
7111
+ this.name = "DeployNamespaceInvalid";
7112
+ }
7113
+ };
7114
+ /** A workspace name with nothing a target can name a resource by. */
7115
+ var DeployProjectNameInvalid = class extends Error {
7116
+ workspaceName;
7117
+ constructor(workspaceName) {
7118
+ super(`The workspace name '${workspaceName}' has no letters or digits to name deployed resources by. Set "name" in gkm.config.ts.`);
7119
+ this.workspaceName = workspaceName;
7120
+ this.name = "DeployProjectNameInvalid";
7121
+ }
7122
+ };
7123
+ const NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
7124
+ /**
7125
+ * Lowercase `[a-z0-9-]`, runs of anything else collapsed to one `-`: valid as
7126
+ * a Docker image path component, a Docker service name and a Dokploy name.
7127
+ */
7128
+ function slug$1(value) {
7129
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
7130
+ }
7131
+ /** The namespace a workspace is in when it does not name one. */
7132
+ function defaultNamespace(workspaceName) {
7133
+ return slug$1((0, _geekmidas_manifest.kebabCase)(workspaceName));
7134
+ }
7135
+ function deployIdentity(workspace, stage) {
7136
+ const project = slug$1(workspace.name);
7137
+ if (!project) throw new DeployProjectNameInvalid(workspace.name);
7138
+ const fallback = defaultNamespace(workspace.name);
7139
+ const namespace = workspace.deploy?.namespace ?? fallback;
7140
+ if (!NAME.test(namespace)) throw new DeployNamespaceInvalid(namespace);
7141
+ return {
7142
+ namespace,
7143
+ project,
7144
+ stage,
7145
+ key: `${namespace}/${project}`,
7146
+ scope: namespace === fallback ? project : `${namespace}-${project}`
7147
+ };
7148
+ }
7149
+ /**
7150
+ * The token a Dokploy project's description carries to say which identity
7151
+ * created it. A project with the right name and no marker belongs to someone
7152
+ * else, and a deploy never adopts it.
7153
+ */
7154
+ function ownershipMarker(identity) {
7155
+ return `gkm:${identity.key}`;
7156
+ }
7157
+ /** Whether a project description carries `identity`'s marker. */
7158
+ function isOwnedBy(description, identity) {
7159
+ const marker = ownershipMarker(identity);
7160
+ return (description ?? "").split(/\s+/).includes(marker);
7161
+ }
7162
+ /** Any `gkm:` marker a description carries — whoever's it is. */
7163
+ function markerOf(description) {
7164
+ return (description ?? "").split(/\s+/).find((t) => t.startsWith("gkm:"));
7165
+ }
7166
+ /** What the Dokploy project is called. */
7167
+ function projectName(identity) {
7168
+ return identity.scope;
7169
+ }
7170
+ /**
7171
+ * The repository an app's image is pushed to, below the registry:
7172
+ * `<namespace>/<project>-<app>`.
7173
+ *
7174
+ * The namespace is a path segment of its own so that a registry that maps
7175
+ * paths to owners (an organisation's packages, a Harbor project) can grant it
7176
+ * as one; the app is joined to the project so the two read as one name.
7177
+ */
7178
+ function imageName(identity, app) {
7179
+ return `${identity.namespace}/${identity.project}-${slug$1((0, _geekmidas_manifest.kebabCase)(app))}`;
7180
+ }
7181
+ /** The full image ref an app is built, pushed and deployed as. */
7182
+ function imageRef(identity, app, registry, tag) {
7183
+ const name = imageName(identity, app);
7184
+ return registry ? `${registry.replace(/\/+$/, "")}/${name}:${tag}` : `${name}:${tag}`;
7185
+ }
7186
+ /**
7187
+ * What one application is called on the target.
7188
+ *
7189
+ * The same rule the constructs use, through the same `scopedName`, scoped by
7190
+ * the stage and the identity: the application beside
7191
+ * `production-kitchen-sink-database` is `production-kitchen-sink-api`. A
7192
+ * Dokploy application's name is also its Docker service name, which is unique
7193
+ * on the whole server, so the namespace has to be in it too — `acme`'s and
7194
+ * `globex`'s `shop` would otherwise both run `production-shop-api`.
7195
+ *
7196
+ * The app id is dropped when it repeats the project, so a project named for its
7197
+ * one application is `production-kitchen-sink` rather than
7198
+ * `production-kitchen-sink-kitchen-sink`.
7199
+ */
7200
+ function applicationName(identity, app) {
7201
+ return slug$1(app) === identity.project ? `${identity.stage}-${identity.scope}` : (0, _geekmidas_manifest.scopedName)([identity.stage, identity.scope], app);
7202
+ }
7203
+ //#endregion
7204
+ //#region src/deploy/ownership.ts
7205
+ /**
7206
+ * Which Dokploy project a deploy is allowed to deploy into.
7207
+ *
7208
+ * Deploy used to take any project whose name matched the workspace's, in any
7209
+ * case — so a second workspace called `shop` (or `Shop`) on the same server
7210
+ * deployed into the first one's project, over its images and applications. A
7211
+ * project is now claimed by an ownership marker in its description, and a
7212
+ * project with the right name and no marker is somebody else's.
7213
+ */
7214
+ /**
7215
+ * A Dokploy project with the name this deploy would use exists, and nothing
7216
+ * says this workspace created it.
7217
+ */
7218
+ var ProjectNotOwned = class extends Error {
7219
+ projectName;
7220
+ projectId;
7221
+ identity;
7222
+ ownedBy;
7223
+ constructor(projectName, projectId, identity, ownedBy) {
7224
+ super(`The Dokploy project '${projectName}' (${projectId}) was not created by this workspace (${identity})${ownedBy ? ` — it is marked '${ownedBy}'` : ""}, so deploy will not use it. Set deploy.namespace in gkm.config.ts to deploy under a name of your own, or, if this project really is this workspace's, add 'gkm:${identity}' to its description in Dokploy.`);
7225
+ this.projectName = projectName;
7226
+ this.projectId = projectId;
7227
+ this.identity = identity;
7228
+ this.ownedBy = ownedBy;
7229
+ this.name = "ProjectNotOwned";
7230
+ }
7231
+ };
7232
+ /** A description with the marker added, keeping what was there. */
7233
+ function withMarker(description, marker) {
7234
+ const existing = (description ?? "").trim();
7235
+ return existing ? `${existing}\n${marker}` : marker;
7236
+ }
7237
+ /** `project.one`, or `null` when Dokploy no longer has the project. */
7238
+ async function projectById(api, projectId) {
7239
+ try {
7240
+ return await api.getProject(projectId) ?? null;
7241
+ } catch (error) {
7242
+ if (error instanceof DokployApiError) return null;
7243
+ throw error;
7244
+ }
7245
+ }
7246
+ /**
7247
+ * The project this deploy deploys into:
7248
+ *
7249
+ * 1. The one the stage's state names, if Dokploy still has it. State written
7250
+ * before markers existed is trusted — it holds an id this workspace was
7251
+ * given when it created the project — and the marker is written now, so
7252
+ * the project is claimed for the next deploy that has no state.
7253
+ * 2. One with this name *and* this identity's marker.
7254
+ * 3. A new one, marked.
7255
+ *
7256
+ * A project that matches by name (in any case) but carries no marker, or
7257
+ * another identity's, raises `ProjectNotOwned` — it is never adopted.
7258
+ */
7259
+ async function resolveProject(api, identity, stateProjectId, log = () => {}) {
7260
+ const name = projectName(identity);
7261
+ const marker = ownershipMarker(identity);
7262
+ if (stateProjectId) {
7263
+ const known = await projectById(api, stateProjectId);
7264
+ if (known) {
7265
+ const claimed = markerOf(known.description);
7266
+ if (claimed && claimed !== marker) throw new ProjectNotOwned(known.name, known.projectId, identity.key, claimed);
7267
+ if (!claimed) {
7268
+ await api.updateProject(known.projectId, {
7269
+ name: known.name,
7270
+ description: withMarker(known.description, marker)
7271
+ });
7272
+ log(` Claimed project ${known.name} for ${identity.key}`);
7273
+ }
7274
+ return {
7275
+ projectId: known.projectId,
7276
+ name: known.name,
7277
+ environments: known.environments ?? [],
7278
+ via: "state"
7279
+ };
7280
+ }
7281
+ }
7282
+ const projects = await api.listProjects();
7283
+ const owned = projects.find((p) => p.name.toLowerCase() === name.toLowerCase() && isOwnedBy(p.description, identity));
7284
+ if (owned) {
7285
+ const details = await api.getProject(owned.projectId);
7286
+ return {
7287
+ projectId: owned.projectId,
7288
+ name: owned.name,
7289
+ environments: details.environments ?? [],
7290
+ via: "marker"
7291
+ };
7292
+ }
7293
+ const clash = projects.find((p) => p.name.toLowerCase() === name.toLowerCase());
7294
+ if (clash) throw new ProjectNotOwned(clash.name, clash.projectId, identity.key, markerOf(clash.description));
7295
+ const created = await api.createProject(name, withMarker("Deployed by gkm.", marker));
7296
+ return {
7297
+ projectId: created.project.projectId,
7298
+ name: created.project.name,
7299
+ environments: [created.environment],
7300
+ via: "created"
7301
+ };
7302
+ }
7303
+ //#endregion
7304
+ //#region src/deploy/registry.ts
7305
+ /**
7306
+ * Which Dokploy registry a stage's images are pulled through.
7307
+ *
7308
+ * It used to be one id stored per machine (`~/.gkm/credentials.json`), and
7309
+ * when that was empty, the first registry Dokploy listed — so an image could be
7310
+ * deployed with credentials for a registry nobody chose, and two workspaces
7311
+ * deployed from one machine shared whichever was stored last. The id is now
7312
+ * kept in the stage's state, and found only from what the workspace
7313
+ * configured.
7314
+ */
7315
+ /** There is nowhere to push images to, so Dokploy has nothing to pull. */
7316
+ var RegistryNotConfigured = class extends Error {
7317
+ stage;
7318
+ constructor(stage) {
7319
+ super(`Deploying '${stage}' needs a container registry: Dokploy pulls each app's image from one. Set deploy.dokploy.registry in gkm.config.ts (e.g. 'ghcr.io/acme'), and deploy.dokploy.registryId if Dokploy already holds that registry's credentials.`);
7320
+ this.stage = stage;
7321
+ this.name = "RegistryNotConfigured";
7322
+ }
7323
+ };
7324
+ /** `deploy.dokploy.registryId` names a registry Dokploy does not have. */
7325
+ var RegistryNotFound = class extends Error {
7326
+ registryId;
7327
+ constructor(registryId) {
7328
+ super(`deploy.dokploy.registryId '${registryId}' is not a registry on this Dokploy server. Find the right id under Settings → Docker Registry in Dokploy and set it in gkm.config.ts, or remove it to have deploy find the registry by its URL.`);
7329
+ this.registryId = registryId;
7330
+ this.name = "RegistryNotFound";
7331
+ }
7332
+ };
7333
+ /** More than one Dokploy registry serves the configured registry. */
7334
+ var RegistryAmbiguous = class extends Error {
7335
+ registry;
7336
+ registryIds;
7337
+ constructor(registry, registryIds) {
7338
+ super(`Dokploy has ${registryIds.length} registries for '${registry}' (${registryIds.join(", ")}). Set deploy.dokploy.registryId in gkm.config.ts to the one this workspace pushes with.`);
7339
+ this.registry = registry;
7340
+ this.registryIds = registryIds;
7341
+ this.name = "RegistryAmbiguous";
7342
+ }
7343
+ };
7344
+ /** `https://ghcr.io/acme/` → `{ host: 'ghcr.io', path: 'acme' }`. */
7345
+ function parts(url) {
7346
+ const [host = "", ...path] = url.replace(/^[a-z]+:\/\//i, "").replace(/\/+$/, "").toLowerCase().split("/");
7347
+ return {
7348
+ host,
7349
+ path: path.join("/")
7350
+ };
7351
+ }
7352
+ /**
7353
+ * The Dokploy registries that serve `registry`: the same host, and — where
7354
+ * Dokploy's own entry names one — the same path.
7355
+ */
7356
+ function serving(registries, registry) {
7357
+ const wanted = parts(registry);
7358
+ const sameHost = registries.filter((r) => parts(r.registryUrl ?? "").host === wanted.host);
7359
+ if (sameHost.length <= 1) return sameHost;
7360
+ return sameHost.filter((r) => {
7361
+ return (parts(r.registryUrl ?? "").path || (r.imagePrefix ?? "").toLowerCase()) === wanted.path;
7362
+ });
7363
+ }
7364
+ /** `registry.one`, or `null` when Dokploy says it has no such registry. */
7365
+ async function registryById(api, registryId) {
7366
+ try {
7367
+ return await api.getRegistry(registryId) ?? null;
7368
+ } catch (error) {
7369
+ if (error instanceof DokployApiError) return null;
7370
+ throw error;
7371
+ }
7372
+ }
7373
+ /**
7374
+ * The registry id for a stage, in order: the configured id, the one in the
7375
+ * stage's state, the one Dokploy has for the configured registry URL, and
7376
+ * otherwise a new one. Never a registry the workspace did not point at.
7377
+ */
7378
+ async function resolveRegistry(api, options) {
7379
+ const { stage, registry, configuredId, stateId, log = () => {} } = options;
7380
+ if (!registry) throw new RegistryNotConfigured(stage);
7381
+ if (configuredId) {
7382
+ const configured = await registryById(api, configuredId);
7383
+ if (!configured) throw new RegistryNotFound(configuredId);
7384
+ return configured;
7385
+ }
7386
+ if (stateId) {
7387
+ const known = await registryById(api, stateId);
7388
+ if (known) return known;
7389
+ log(` ⚠ The stage's registry ${stateId} no longer exists in Dokploy`);
7390
+ }
7391
+ const matches = serving(await api.listRegistries(), registry);
7392
+ if (matches.length > 1) throw new RegistryAmbiguous(registry, matches.map((r) => r.registryId));
7393
+ if (matches[0]) return matches[0];
7394
+ return options.create(registry);
7395
+ }
7396
+ //#endregion
7039
7397
  //#region src/deploy/StateProvider.ts
7040
7398
  /**
7041
7399
  * Check if value is a StateProvider implementation.
@@ -7808,6 +8166,7 @@ async function workspaceDeployCommand(configured, options) {
7808
8166
  apps: units
7809
8167
  } : configured;
7810
8168
  if (provider !== "dokploy") throw new Error(`Workspace deployment only supports Dokploy. Got: ${provider}`);
8169
+ const identity = deployIdentity(workspace, stage);
7811
8170
  logger$4.log(`\n🚀 Deploying workspace "${workspace.name}" to Dokploy...`);
7812
8171
  logger$4.log(` Stage: ${stage}`);
7813
8172
  const imageTag = tag ?? generateTag(stage);
@@ -7874,38 +8233,29 @@ async function workspaceDeployCommand(configured, options) {
7874
8233
  baseUrl: creds.endpoint,
7875
8234
  token: creds.token
7876
8235
  });
7877
- logger$4.log("\n📁 Setting up Dokploy project...");
7878
- const projectName = workspace.name;
7879
- let project = (await api.listProjects()).find((p) => p.name.toLowerCase() === projectName.toLowerCase());
7880
- let environmentId;
7881
- if (project) {
7882
- logger$4.log(` Found existing project: ${project.name}`);
7883
- const matchingEnv = ((await api.getProject(project.projectId)).environments ?? []).find((e) => e.name.toLowerCase() === stage.toLowerCase());
7884
- if (matchingEnv) {
7885
- environmentId = matchingEnv.environmentId;
7886
- logger$4.log(` Using environment: ${matchingEnv.name}`);
7887
- } else {
7888
- logger$4.log(` Creating "${stage}" environment...`);
7889
- environmentId = (await api.createEnvironment(project.projectId, stage)).environmentId;
7890
- logger$4.log(` ✓ Created environment: ${stage}`);
7891
- }
7892
- } else {
7893
- logger$4.log(` Creating project: ${projectName}`);
7894
- const result = await api.createProject(projectName);
7895
- project = result.project;
7896
- if (result.environment.name.toLowerCase() !== stage.toLowerCase()) {
7897
- logger$4.log(` Creating "${stage}" environment...`);
7898
- environmentId = (await api.createEnvironment(project.projectId, stage)).environmentId;
7899
- } else environmentId = result.environment.environmentId;
7900
- logger$4.log(` ✓ Created project: ${project.projectId}`);
7901
- }
7902
8236
  logger$4.log("\n📋 Loading deploy state...");
7903
8237
  const stateProvider = await createStateProvider({
7904
8238
  config: workspace.state,
7905
8239
  workspaceRoot: workspace.root,
7906
8240
  workspaceName: workspace.name
7907
8241
  });
7908
- let state = await stateProvider.read(stage);
8242
+ const previous = await stateProvider.read(stage);
8243
+ logger$4.log("\n📁 Setting up Dokploy project...");
8244
+ logger$4.log(` Identity: ${identity.key}`);
8245
+ const project = await resolveProject(api, identity, previous?.projectId, (message) => logger$4.log(message));
8246
+ if (project.via === "created") logger$4.log(` ✓ Created project: ${project.projectId}`);
8247
+ else logger$4.log(` Found existing project: ${project.name}`);
8248
+ let environmentId;
8249
+ const matchingEnv = project.environments.find((e) => e.name.toLowerCase() === stage.toLowerCase());
8250
+ if (matchingEnv) {
8251
+ environmentId = matchingEnv.environmentId;
8252
+ logger$4.log(` Using environment: ${matchingEnv.name}`);
8253
+ } else {
8254
+ logger$4.log(` Creating "${stage}" environment...`);
8255
+ environmentId = (await api.createEnvironment(project.projectId, stage)).environmentId;
8256
+ logger$4.log(` ✓ Created environment: ${stage}`);
8257
+ }
8258
+ let state = previous;
7909
8259
  if (state) {
7910
8260
  logger$4.log(` Found existing state for stage "${stage}"`);
7911
8261
  if (state.projectId !== project.projectId) {
@@ -7920,33 +8270,27 @@ async function workspaceDeployCommand(configured, options) {
7920
8270
  logger$4.log(` Creating new state for stage "${stage}"`);
7921
8271
  state = createEmptyState(stage, project.projectId, environmentId);
7922
8272
  }
8273
+ state.identity = identity.key;
7923
8274
  logger$4.log("\n🐳 Checking registry...");
7924
- let registryId = await getDokployRegistryId();
7925
8275
  const registry = workspace.deploy.dokploy?.registry;
7926
- if (registryId) try {
7927
- const reg = await api.getRegistry(registryId);
7928
- logger$4.log(` Using registry: ${reg.registryName}`);
7929
- } catch {
7930
- logger$4.log(" ⚠ Stored registry not found, clearing...");
7931
- registryId = void 0;
7932
- await storeDokployRegistryId("");
7933
- }
7934
- if (!registryId) {
7935
- const registries = await api.listRegistries();
7936
- if (registries.length > 0) {
7937
- registryId = registries[0].registryId;
7938
- await storeDokployRegistryId(registryId);
7939
- logger$4.log(` Using registry: ${registries[0].registryName}`);
7940
- } else if (registry) {
7941
- logger$4.log(" No registries found in Dokploy. Let's create one.");
7942
- logger$4.log(` Registry URL: ${registry}`);
8276
+ const dokployRegistry = await resolveRegistry(api, {
8277
+ stage,
8278
+ registry,
8279
+ configuredId: workspace.deploy.dokploy?.registryId,
8280
+ stateId: state.registryId,
8281
+ log: (message) => logger$4.log(message),
8282
+ create: async (url) => {
8283
+ logger$4.log(` Dokploy has no registry for ${url}. Let's create one.`);
7943
8284
  const username = await prompt("Registry username: ");
7944
8285
  const password = await prompt("Registry password/token: ", true);
7945
- registryId = (await api.createRegistry("Default Registry", registry, username, password)).registryId;
7946
- await storeDokployRegistryId(registryId);
7947
- logger$4.log(` ✓ Registry created: ${registryId}`);
7948
- } else logger$4.log(" ⚠ No registry configured. Set deploy.dokploy.registry in workspace config");
7949
- }
8286
+ const created = await api.createRegistry("Default Registry", url, username, password);
8287
+ logger$4.log(` ✓ Registry created: ${created.registryId}`);
8288
+ return created;
8289
+ }
8290
+ });
8291
+ const registryId = dokployRegistry.registryId;
8292
+ state.registryId = registryId;
8293
+ logger$4.log(` Using registry: ${dokployRegistry.registryName}`);
7950
8294
  const skippedMobileApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "mobile");
7951
8295
  if (skippedMobileApps.length > 0) logger$4.log(`\n📱 Skipping ${skippedMobileApps.length} mobile app(s) — deploy via framework toolchain: ${skippedMobileApps.join(", ")}`);
7952
8296
  const backendApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "backend");
@@ -7967,6 +8311,7 @@ async function workspaceDeployCommand(configured, options) {
7967
8311
  projectId: project.projectId,
7968
8312
  environmentId,
7969
8313
  stage,
8314
+ scope: identity.scope,
7970
8315
  appUrls,
7971
8316
  supplied: stageSecrets.custom,
7972
8317
  seed: stageSecrets.seed,
@@ -7992,7 +8337,7 @@ async function workspaceDeployCommand(configured, options) {
7992
8337
  const backupState = await provisionBackupDestination({
7993
8338
  api,
7994
8339
  projectId: project.projectId,
7995
- projectName: workspace.name,
8340
+ projectName: identity.scope,
7996
8341
  stage,
7997
8342
  config: workspace.deploy.backups,
7998
8343
  existingState: getBackupState(state),
@@ -8033,7 +8378,7 @@ async function workspaceDeployCommand(configured, options) {
8033
8378
  const app = workspace.apps[appName];
8034
8379
  logger$4.log(`\n ⚙️ Deploying ${appName}...`);
8035
8380
  try {
8036
- const dokployAppName = applicationName(stage, workspace.name, appName);
8381
+ const dokployAppName = applicationName(identity, appName);
8037
8382
  let application = null;
8038
8383
  const cachedAppId = getApplicationId(state, appName);
8039
8384
  if (cachedAppId) {
@@ -8055,20 +8400,23 @@ async function workspaceDeployCommand(configured, options) {
8055
8400
  iv: appSecrets.payload.iv
8056
8401
  } : void 0;
8057
8402
  if (appSecrets && credentials) logger$4.log(` Encrypted ${appSecrets.secretCount} secrets`);
8058
- const imageName = `${workspace.name}-${appName}`;
8059
- const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
8060
- logger$4.log(` Building Docker image: ${imageRef}`);
8061
- await deployDocker({
8403
+ const imageRef$1 = imageRef(identity, appName, registry, imageTag);
8404
+ logger$4.log(` Building Docker image: ${imageRef$1}`);
8405
+ const built = await deployDocker({
8062
8406
  stage,
8063
8407
  tag: imageTag,
8064
8408
  skipPush: false,
8065
8409
  config: {
8066
8410
  registry,
8067
- imageName,
8411
+ imageName: imageName(identity, appName),
8068
8412
  appName
8069
8413
  },
8070
8414
  credentials
8071
8415
  });
8416
+ setDeployedImage(state, appName, {
8417
+ ref: built.imageRef ?? imageRef$1,
8418
+ ...built.digest ? { digest: built.digest } : {}
8419
+ });
8072
8420
  const backendHost = resolveHost(appName, app, stage, workspace.deploy?.domains, false);
8073
8421
  const dependencyUrls = {};
8074
8422
  if (app.dependencies) {
@@ -8099,7 +8447,7 @@ async function workspaceDeployCommand(configured, options) {
8099
8447
  };
8100
8448
  const envVars = Object.entries(withDeclared).map(([key, value]) => `${key}=${value}`);
8101
8449
  if (Object.keys(withDeclared).length > 0) logger$4.log(` Resolved ${Object.keys(withDeclared).length} env vars: ${Object.keys(withDeclared).sort().join(", ")}`);
8102
- await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
8450
+ await api.saveDockerProvider(application.applicationId, imageRef$1, { registryId });
8103
8451
  await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
8104
8452
  logger$4.log(` Deploying to Dokploy...`);
8105
8453
  await api.deployApplication(application.applicationId);
@@ -8132,7 +8480,8 @@ async function workspaceDeployCommand(configured, options) {
8132
8480
  type: app.type,
8133
8481
  success: true,
8134
8482
  applicationId: application.applicationId,
8135
- imageRef
8483
+ imageRef: imageRef$1,
8484
+ ...built.digest ? { digest: built.digest } : {}
8136
8485
  });
8137
8486
  logger$4.log(` ✓ ${appName} deployed successfully`);
8138
8487
  } catch (error) {
@@ -8154,7 +8503,7 @@ async function workspaceDeployCommand(configured, options) {
8154
8503
  const app = workspace.apps[appName];
8155
8504
  logger$4.log(`\n 🌐 Deploying ${appName}...`);
8156
8505
  try {
8157
- const dokployAppName = applicationName(stage, workspace.name, appName);
8506
+ const dokployAppName = applicationName(identity, appName);
8158
8507
  let application = null;
8159
8508
  const cachedAppId = getApplicationId(state, appName);
8160
8509
  if (cachedAppId) {
@@ -8199,28 +8548,31 @@ async function workspaceDeployCommand(configured, options) {
8199
8548
  }
8200
8549
  }
8201
8550
  if (buildArgs.length > 0) logger$4.log(` Build args: ${publicUrlArgNames.join(", ")}`);
8202
- const imageName = `${workspace.name}-${appName}`;
8203
- const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
8204
- logger$4.log(` Building Docker image: ${imageRef}`);
8205
- await deployDocker({
8551
+ const imageRef$2 = imageRef(identity, appName, registry, imageTag);
8552
+ logger$4.log(` Building Docker image: ${imageRef$2}`);
8553
+ const built = await deployDocker({
8206
8554
  stage,
8207
8555
  tag: imageTag,
8208
8556
  skipPush: false,
8209
8557
  config: {
8210
8558
  registry,
8211
- imageName,
8559
+ imageName: imageName(identity, appName),
8212
8560
  appName
8213
8561
  },
8214
8562
  buildArgs,
8215
8563
  publicUrlArgs: publicUrlArgNames
8216
8564
  });
8565
+ setDeployedImage(state, appName, {
8566
+ ref: built.imageRef ?? imageRef$2,
8567
+ ...built.digest ? { digest: built.digest } : {}
8568
+ });
8217
8569
  const envVars = [
8218
8570
  `NODE_ENV=production`,
8219
8571
  `PORT=${app.port}`,
8220
8572
  `STAGE=${stage}`
8221
8573
  ];
8222
8574
  for (const [key, value] of Object.entries(resolved)) envVars.push(`${key}=${value}`);
8223
- await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
8575
+ await api.saveDockerProvider(application.applicationId, imageRef$2, { registryId });
8224
8576
  await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
8225
8577
  logger$4.log(` Deploying to Dokploy...`);
8226
8578
  await api.deployApplication(application.applicationId);
@@ -8253,7 +8605,8 @@ async function workspaceDeployCommand(configured, options) {
8253
8605
  type: app.type,
8254
8606
  success: true,
8255
8607
  applicationId: application.applicationId,
8256
- imageRef
8608
+ imageRef: imageRef$2,
8609
+ ...built.digest ? { digest: built.digest } : {}
8257
8610
  });
8258
8611
  logger$4.log(` ✓ ${appName} deployed successfully`);
8259
8612
  } catch (error) {
@@ -8849,7 +9202,7 @@ async function createStateStore(options) {
8849
9202
  }
8850
9203
  if (config.provider === "ssm") {
8851
9204
  if (!workspaceName) throw new StateStoreNeedsWorkspaceName("ssm");
8852
- const { SSMStateStore } = await Promise.resolve().then(() => require("./SSMStateStore-DD7jKrCO.cjs"));
9205
+ const { SSMStateStore } = await Promise.resolve().then(() => require("./SSMStateStore-DC43R7zG.cjs"));
8853
9206
  const ssm = config;
8854
9207
  return SSMStateStore.create({
8855
9208
  workspaceName,
@@ -10119,25 +10472,25 @@ function loadPackageJson() {
10119
10472
  * Run: pnpm --filter @geekmidas/cli sync-versions
10120
10473
  */
10121
10474
  const GEEKMIDAS_VERSIONS = {
10122
- "@geekmidas/audit": "~10.0.0-alpha.56",
10123
- "@geekmidas/auth": "~10.0.0-alpha.56",
10124
- "@geekmidas/cache": "~10.0.0-alpha.56",
10125
- "@geekmidas/client": "~10.0.0-alpha.56",
10126
- "@geekmidas/cloud": "~10.0.0-alpha.56",
10127
- "@geekmidas/constructs": "~10.0.0-alpha.56",
10128
- "@geekmidas/db": "~10.0.0-alpha.56",
10129
- "@geekmidas/emailkit": "~10.0.0-alpha.56",
10130
- "@geekmidas/envkit": "~10.0.0-alpha.56",
10131
- "@geekmidas/errors": "~10.0.0-alpha.56",
10132
- "@geekmidas/events": "~10.0.0-alpha.56",
10133
- "@geekmidas/logger": "~10.0.0-alpha.56",
10134
- "@geekmidas/manifest": "~10.0.0-alpha.56",
10135
- "@geekmidas/rate-limit": "~10.0.0-alpha.56",
10136
- "@geekmidas/schema": "~10.0.0-alpha.56",
10137
- "@geekmidas/services": "~10.0.0-alpha.56",
10138
- "@geekmidas/storage": "~10.0.0-alpha.56",
10139
- "@geekmidas/telescope": "~10.0.0-alpha.56",
10140
- "@geekmidas/testkit": "~10.0.0-alpha.56",
10475
+ "@geekmidas/audit": "~10.0.0-alpha.58",
10476
+ "@geekmidas/auth": "~10.0.0-alpha.58",
10477
+ "@geekmidas/cache": "~10.0.0-alpha.58",
10478
+ "@geekmidas/client": "~10.0.0-alpha.58",
10479
+ "@geekmidas/cloud": "~10.0.0-alpha.58",
10480
+ "@geekmidas/constructs": "~10.0.0-alpha.58",
10481
+ "@geekmidas/db": "~10.0.0-alpha.58",
10482
+ "@geekmidas/emailkit": "~10.0.0-alpha.58",
10483
+ "@geekmidas/envkit": "~10.0.0-alpha.58",
10484
+ "@geekmidas/errors": "~10.0.0-alpha.58",
10485
+ "@geekmidas/events": "~10.0.0-alpha.58",
10486
+ "@geekmidas/logger": "~10.0.0-alpha.58",
10487
+ "@geekmidas/manifest": "~10.0.0-alpha.58",
10488
+ "@geekmidas/rate-limit": "~10.0.0-alpha.58",
10489
+ "@geekmidas/schema": "~10.0.0-alpha.58",
10490
+ "@geekmidas/services": "~10.0.0-alpha.58",
10491
+ "@geekmidas/storage": "~10.0.0-alpha.58",
10492
+ "@geekmidas/telescope": "~10.0.0-alpha.58",
10493
+ "@geekmidas/testkit": "~10.0.0-alpha.58",
10141
10494
  "@geekmidas/cli": `~${loadPackageJson().version}`
10142
10495
  };
10143
10496
  //#endregion
@@ -16019,7 +16372,7 @@ function testDatabases(reconciled) {
16019
16372
  * starts without the reference data the app needs.
16020
16373
  */
16021
16374
  async function migrateTestStage(workspace, env) {
16022
- const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-DQ54y2WD.cjs"));
16375
+ const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-DzYqA-e3.cjs"));
16023
16376
  const { discover } = await Promise.resolve().then(() => require("./discover-DXNviAh-.cjs")).then((n) => n.discover_exports);
16024
16377
  const sources = {};
16025
16378
  const manifest = await discover({
@@ -16450,7 +16803,7 @@ program.command("migrate").description("Apply each database construct's migratio
16450
16803
  try {
16451
16804
  const globalOptions = program.opts();
16452
16805
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16453
- const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-DQ54y2WD.cjs"));
16806
+ const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-DzYqA-e3.cjs"));
16454
16807
  await migrateCommand({
16455
16808
  ...options,
16456
16809
  ...construct ? { construct } : {}
@@ -16464,7 +16817,7 @@ program.command("seed").description("Migrate, then run each database construct's
16464
16817
  try {
16465
16818
  const globalOptions = program.opts();
16466
16819
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16467
- const { seedCommand } = await Promise.resolve().then(() => require("./migrate-DQ54y2WD.cjs"));
16820
+ const { seedCommand } = await Promise.resolve().then(() => require("./migrate-DzYqA-e3.cjs"));
16468
16821
  await seedCommand({
16469
16822
  ...options,
16470
16823
  ...construct ? { construct } : {}
@@ -16478,7 +16831,7 @@ program.command("migration").description("Write a construct's next migration: an
16478
16831
  try {
16479
16832
  const globalOptions = program.opts();
16480
16833
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16481
- const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-DQ54y2WD.cjs"));
16834
+ const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-DzYqA-e3.cjs"));
16482
16835
  await migrationCommand({
16483
16836
  construct,
16484
16837
  ...name ? { name } : {}
@@ -16576,7 +16929,7 @@ program.command("encryption:rotate").description("Add a new current key to an En
16576
16929
  try {
16577
16930
  const globalOptions = program.opts();
16578
16931
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16579
- const { encryptionRotateCommand } = await Promise.resolve().then(() => require("./encryption-10Srqiix.cjs"));
16932
+ const { encryptionRotateCommand } = await Promise.resolve().then(() => require("./encryption-BspqzZIb.cjs"));
16580
16933
  await encryptionRotateCommand(construct, options);
16581
16934
  } catch (error) {
16582
16935
  console.error(formatError(error));
@@ -16587,7 +16940,7 @@ program.command("encryption:retire").description("Remove an old key from an Encr
16587
16940
  try {
16588
16941
  const globalOptions = program.opts();
16589
16942
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16590
- const { encryptionRetireCommand } = await Promise.resolve().then(() => require("./encryption-10Srqiix.cjs"));
16943
+ const { encryptionRetireCommand } = await Promise.resolve().then(() => require("./encryption-BspqzZIb.cjs"));
16591
16944
  await encryptionRetireCommand(construct, key, options);
16592
16945
  } catch (error) {
16593
16946
  console.error(formatError(error));
@@ -16639,7 +16992,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
16639
16992
  console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
16640
16993
  process.exit(1);
16641
16994
  }
16642
- const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-BbPJVCto.cjs")).then((n) => n.workspace_exports);
16995
+ const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-B14L2TrY.cjs")).then((n) => n.workspace_exports);
16643
16996
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
16644
16997
  if (!result) {
16645
16998
  console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
@@ -16698,7 +17051,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
16698
17051
  try {
16699
17052
  const globalOptions = program.opts();
16700
17053
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16701
- const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-DtRUkHN8.cjs"));
17054
+ const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-mQorSS36.cjs"));
16702
17055
  await deployGithubCommand(options);
16703
17056
  } catch (error) {
16704
17057
  console.error(formatError(error));