@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.mjs CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env -S npx tsx
2
2
  import { n as __require, t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
3
3
  import { i as ConstructGenerator, t as discover } from "./discover-Cf2jAcgF.mjs";
4
- import { d as parseModuleConfig, f as appKey, l as loadWorkspaceConfig, n as NotABackendApp, o as loadAppConfig, p as derivedApps, r as NotInAnApp, s as loadConfig, t as ConfigNotFound, u as loadWorkspaceSettings } from "./config-20V08LZp.mjs";
5
- import { D as assertDeployedStage, E as TEST_STAGE, O as deployedProblems, T as InvalidStages, a as getAppBuildOrder, b as getDeployTargetError, k as stageProblems, m as stripPublicPrefix, o as getAppGkmConfig, p as getPublicEnvPrefix, t as allConstructGlobs, x as isDeployTargetSupported } from "./workspace-C2mFV53i.mjs";
4
+ import { d as parseModuleConfig, f as appKey, l as loadWorkspaceConfig, n as NotABackendApp, o as loadAppConfig, p as derivedApps, r as NotInAnApp, s as loadConfig, t as ConfigNotFound, u as loadWorkspaceSettings } from "./config-CSOK73pY.mjs";
5
+ import { D as assertDeployedStage, E as TEST_STAGE, O as deployedProblems, T as InvalidStages, a as getAppBuildOrder, b as getDeployTargetError, k as stageProblems, m as stripPublicPrefix, o as getAppGkmConfig, p as getPublicEnvPrefix, t as allConstructGlobs, x as isDeployTargetSupported } from "./workspace-BvNXAXQI.mjs";
6
6
  import { a as withCustomSecret, i as toEmbeddableSecrets, n as maskPassword, t as initStageSecrets } from "./storage-BzaUxmqe.mjs";
7
7
  import { a as getKeyPath, i as FileSecretsStore, n as secretsStoreFor } from "./store-DeGATCS-.mjs";
8
- import { C as loadEnvFiles, S as isPortAvailable, T as prepareEntryCredentials, _ as fakeKey, a as reconcileWorkspace, b as createEntryWrapper, c as LOCAL_CA_PATH, d as postgresDatabaseNames, f as quoteIdentifier, g as describeServices, h as rootSite, i as derivedContainers, l as forgetState, m as resourceName, n as backendsOf$1, p as pgClient, r as constructGlobs, s as COMPOSE_PATH, t as FAKE_ENV, u as applyPostgres, v as primaryPortKey, w as loadSecretsForApp, x as findAvailablePort, y as createCredentialsPreload } from "./workspace-CgWcE_Yr.mjs";
9
- import { n as EndpointGenerator, r as runtimeFor } from "./OpenApiTsGenerator-NThANByQ.mjs";
8
+ import { C as loadEnvFiles, S as isPortAvailable, T as prepareEntryCredentials, _ as fakeKey, a as reconcileWorkspace, b as createEntryWrapper, c as LOCAL_CA_PATH, d as postgresDatabaseNames, f as quoteIdentifier, g as describeServices, h as rootSite, i as derivedContainers, l as forgetState, m as resourceName, n as backendsOf$1, p as pgClient, r as constructGlobs, s as COMPOSE_PATH, t as FAKE_ENV, u as applyPostgres, v as primaryPortKey, w as loadSecretsForApp, x as findAvailablePort, y as createCredentialsPreload } from "./workspace-D-f58jsP.mjs";
9
+ import { i as telemetryFor, n as EndpointGenerator, r as runtimeFor } from "./OpenApiTsGenerator-BRWD1Wit.mjs";
10
10
  import { generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi.mjs";
11
- import { a as writeTestHarness, c as driversFor, i as TEST_MANIFEST_ENV, n as TEST_READY_FILE, o as SubscriberGenerator, s as cacheBackendsIn, t as TEST_READY_ENV } from "./ready-BSpUZnNo.mjs";
12
- import { i as storageBackendFor, n as eventsBackendFor, r as providerOf, t as cacheBackendFor } from "./backends-BcHL7Gy_.mjs";
11
+ import { a as writeTestHarness, c as driversFor, i as TEST_MANIFEST_ENV, n as TEST_READY_FILE, o as SubscriberGenerator, s as cacheBackendsIn, t as TEST_READY_ENV } from "./ready-DbDHneLR.mjs";
12
+ import { i as storageBackendFor, n as eventsBackendFor, r as providerOf, t as cacheBackendFor } from "./backends-CjuCiE8h.mjs";
13
13
  import { n as withCompute, r as withRoutes, t as manifestModule } from "./emit-C1e8qD08.mjs";
14
14
  import { createRequire } from "node:module";
15
15
  import { Command } from "commander";
@@ -42,7 +42,7 @@ import { Queue } from "@geekmidas/constructs/queue";
42
42
  import { Topic } from "@geekmidas/constructs/topic";
43
43
  import { lookup } from "node:dns/promises";
44
44
  //#region package.json
45
- var version = "10.0.0-alpha.56";
45
+ var version = "10.0.0-alpha.58";
46
46
  //#endregion
47
47
  //#region src/auth/credentials.ts
48
48
  /**
@@ -136,15 +136,6 @@ async function getDokployToken(options) {
136
136
  return null;
137
137
  }
138
138
  /**
139
- * Store Dokploy registry ID
140
- */
141
- async function storeDokployRegistryId(registryId, options) {
142
- const credentials = await readCredentials(options);
143
- if (!credentials.dokploy) throw new Error("Dokploy credentials not found. Run \"gkm login --service dokploy\" first.");
144
- credentials.dokploy.registryId = registryId;
145
- await writeCredentials(credentials, options);
146
- }
147
- /**
148
139
  * Get Dokploy registry ID from stored credentials
149
140
  */
150
141
  async function getDokployRegistryId(options) {
@@ -1740,9 +1731,9 @@ async function workspaceDevCommand(configured, options) {
1740
1731
  for (const { id, port } of fakes) logger$9.log(`🎭 ${id} fake: http://localhost:${port}`);
1741
1732
  logger$9.log(options.migrate || options.seed ? "🗄️ Migrating the databases" : "🗄️ Checking for pending migrations");
1742
1733
  if (options.migrate || options.seed) {
1743
- const { prepareDevDatabases } = await import("./migrate-CS3DkO6y.mjs");
1734
+ const { prepareDevDatabases } = await import("./migrate-CXVnh1J3.mjs");
1744
1735
  await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
1745
- } else await import("./migrate-CS3DkO6y.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1736
+ } else await import("./migrate-CXVnh1J3.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1746
1737
  logger$9.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
1747
1738
  });
1748
1739
  const appUrls = {};
@@ -2768,14 +2759,34 @@ var CommandTimedOut = class extends Error {
2768
2759
  * once `timeoutMs` passes (after the child is gone), and with the spawn error
2769
2760
  * itself when the program cannot be started at all — `ENOENT` already names it.
2770
2761
  */
2771
- function run(command, args, options = {}) {
2762
+ async function run(command, args, options = {}) {
2763
+ await spawnChild(command, args, options, false);
2764
+ }
2765
+ /**
2766
+ * {@link run}, resolving with what the program wrote to stdout — for a value
2767
+ * the CLI reads back, such as the digest `docker inspect` reports. stderr still
2768
+ * reaches the terminal, so a failure explains itself the same way.
2769
+ */
2770
+ function runOutput(command, args, options = {}) {
2771
+ return spawnChild(command, args, options, true);
2772
+ }
2773
+ function spawnChild(command, args, options, capture) {
2772
2774
  const { timeoutMs = DEFAULT_TIMEOUT_MS, ...spawnOptions } = options;
2773
2775
  return new Promise((resolve, reject) => {
2774
2776
  const child = spawn(command, [...args], {
2775
2777
  stdio: "inherit",
2776
2778
  ...spawnOptions,
2779
+ ...capture ? { stdio: [
2780
+ "ignore",
2781
+ "pipe",
2782
+ "inherit"
2783
+ ] } : {},
2777
2784
  shell: false
2778
2785
  });
2786
+ let stdout = "";
2787
+ child.stdout?.on("data", (chunk) => {
2788
+ stdout += chunk.toString();
2789
+ });
2779
2790
  let timedOut = false;
2780
2791
  let killer;
2781
2792
  const timer = setTimeout(() => {
@@ -2796,7 +2807,7 @@ function run(command, args, options = {}) {
2796
2807
  child.on("close", (code, signal) => {
2797
2808
  settle();
2798
2809
  if (timedOut) reject(new CommandTimedOut(command, args, timeoutMs));
2799
- else if (code === 0) resolve();
2810
+ else if (code === 0) resolve(stdout);
2800
2811
  else reject(new CommandFailed(command, args, code, signal));
2801
2812
  });
2802
2813
  });
@@ -3103,7 +3114,8 @@ async function buildOneApp(input) {
3103
3114
  hooks,
3104
3115
  markOptional: options.markOptional ?? false,
3105
3116
  skipBundle: options.skipBundle ?? false,
3106
- stage: options.stage
3117
+ stage: options.stage,
3118
+ workspaceName: workspace.name
3107
3119
  });
3108
3120
  }
3109
3121
  /**
@@ -3181,7 +3193,12 @@ async function buildApp(input) {
3181
3193
  appRoot,
3182
3194
  cache: cacheBackendsIn(declared, cacheBackend)
3183
3195
  }),
3184
- markOptional: input.markOptional ?? false
3196
+ markOptional: input.markOptional ?? false,
3197
+ ...production && { telemetry: telemetryFor({
3198
+ appRoot,
3199
+ surfaceId: derived.surface?.id,
3200
+ workspaceName: input.workspaceName
3201
+ }) }
3185
3202
  };
3186
3203
  const endpointGenerator = new EndpointGenerator();
3187
3204
  const functionGenerator = new FunctionGenerator();
@@ -3590,7 +3607,7 @@ const PROVISIONERS = {
3590
3607
  const name = resourceName(declaration.id, declaration.kind, context.stage);
3591
3608
  const { postgres } = await context.api.findOrCreatePostgres(serviceName({
3592
3609
  stage: context.stage,
3593
- app: context.project
3610
+ app: context.scope ?? context.project
3594
3611
  }, declaration.id), context.projectId, context.environmentId, {
3595
3612
  databaseName: name,
3596
3613
  databasePassword: derivedPassword(context, `${name}:master`)
@@ -3724,7 +3741,7 @@ const PROVISIONERS = {
3724
3741
  if (backend !== "minio") throw new UnprovisionableBucket(declaration.id, backend);
3725
3742
  const service = serviceName({
3726
3743
  stage: context.stage,
3727
- app: context.project
3744
+ app: context.scope ?? context.project
3728
3745
  }, declaration.id);
3729
3746
  const bucket = resourceName(declaration.id, declaration.kind, context.stage);
3730
3747
  const user = `${bucket}-root`;
@@ -4185,6 +4202,7 @@ async function provisionDeclared(options) {
4185
4202
  environmentId: options.environmentId,
4186
4203
  stage: options.stage,
4187
4204
  project: workspace.name,
4205
+ scope: options.scope ?? workspace.name,
4188
4206
  cache: cacheBackendFor(providerOf(workspace)),
4189
4207
  events: eventsBackendFor(providerOf(workspace)),
4190
4208
  storage: storageBackendFor(providerOf(workspace)),
@@ -4295,6 +4313,15 @@ function setApplicationId(state, appName, applicationId) {
4295
4313
  state.applications[appName] = applicationId;
4296
4314
  }
4297
4315
  /**
4316
+ * Record the image an app was deployed with (mutates state)
4317
+ */
4318
+ function setDeployedImage(state, appName, image) {
4319
+ state.images = {
4320
+ ...state.images,
4321
+ [appName]: image
4322
+ };
4323
+ }
4324
+ /**
4298
4325
  * Get a generated secret for an app
4299
4326
  */
4300
4327
  function getGeneratedSecret(state, appName, secretName) {
@@ -6072,6 +6099,33 @@ async function pushImage(imageRef) {
6072
6099
  }
6073
6100
  }
6074
6101
  /**
6102
+ * The digest the registry gave a pushed image, `sha256:…`.
6103
+ *
6104
+ * A tag can be pushed over — by the next deploy, or by anything else that
6105
+ * shares the repository — and the digest is what says which image a stage
6106
+ * actually ran. `RepoDigests` has one entry per repository the image was
6107
+ * pushed to, so the one for this ref's repository is picked by name.
6108
+ *
6109
+ * @internal Exported for testing
6110
+ */
6111
+ async function pushedDigest(imageRef) {
6112
+ const repository = imageRef.split("@")[0].replace(/:[\w][\w.-]*$/, "");
6113
+ const output = await runOutput("docker", [
6114
+ "image",
6115
+ "inspect",
6116
+ "--format={{json .RepoDigests}}",
6117
+ imageRef
6118
+ ], { cwd: process.cwd() });
6119
+ let digests;
6120
+ try {
6121
+ digests = JSON.parse(output.trim() || "[]");
6122
+ } catch {
6123
+ return;
6124
+ }
6125
+ if (!Array.isArray(digests)) return void 0;
6126
+ return digests.find((entry) => typeof entry === "string" && entry.startsWith(`${repository}@`))?.slice(repository.length + 1);
6127
+ }
6128
+ /**
6075
6129
  * Deploy using Docker (build and optionally push image)
6076
6130
  */
6077
6131
  async function deployDocker(options) {
@@ -6079,9 +6133,13 @@ async function deployDocker(options) {
6079
6133
  const imageName = config.imageName;
6080
6134
  const imageRef = validateImageRef(getImageRef(config.registry, imageName, tag));
6081
6135
  await buildImage(imageRef, buildArgs, credentials);
6136
+ let digest;
6082
6137
  if (!skipPush) {
6083
6138
  if (!config.registry) logger$5.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
6084
- else await pushImage(imageRef);
6139
+ else {
6140
+ await pushImage(imageRef);
6141
+ digest = await pushedDigest(imageRef);
6142
+ }
6085
6143
  }
6086
6144
  logger$5.log("\n✅ Docker deployment ready!");
6087
6145
  logger$5.log(`\n📋 Deployment details:`);
@@ -6090,25 +6148,10 @@ async function deployDocker(options) {
6090
6148
  if (masterKey) logger$5.log(`\n🔐 Run the container with GKM_MASTER_KEY set to the key with fingerprint ${keyFingerprint(masterKey)}.`);
6091
6149
  return {
6092
6150
  imageRef,
6151
+ ...digest ? { digest } : {},
6093
6152
  masterKey
6094
6153
  };
6095
6154
  }
6096
- /**
6097
- * What one application is called on a provider.
6098
- *
6099
- * The same rule the constructs use, through the same `scopedName`: the
6100
- * application beside `production-kitchen-sink-database` is
6101
- * `production-kitchen-sink-api`, not `api`. Both deploy paths call this — the
6102
- * workspace one named its applications by the bare app key, so a project held
6103
- * an `api` and a `web` that every stage would collide on.
6104
- *
6105
- * The app id is dropped when it repeats the project, so a project named for its
6106
- * one application is `production-kitchen-sink` rather than
6107
- * `production-kitchen-sink-kitchen-sink`.
6108
- */
6109
- function applicationName(stage, project, app) {
6110
- return app === project ? `${stage}-${project}`.toLowerCase() : scopedName([stage, project], app);
6111
- }
6112
6155
  //#endregion
6113
6156
  //#region src/deploy/dokploy-api.ts
6114
6157
  var dokploy_api_exports = /* @__PURE__ */ __exportAll({
@@ -6325,6 +6368,18 @@ var DokployApi = class {
6325
6368
  });
6326
6369
  }
6327
6370
  /**
6371
+ * Change a project's name or description.
6372
+ *
6373
+ * The name is sent with the description because `project.update` validates
6374
+ * the whole project on some Dokploy versions, and a missing name fails it.
6375
+ */
6376
+ async updateProject(projectId, updates) {
6377
+ await this.post("project.update", {
6378
+ projectId,
6379
+ ...updates
6380
+ });
6381
+ }
6382
+ /**
6328
6383
  * Delete a project and all its resources
6329
6384
  */
6330
6385
  async deleteProject(projectId) {
@@ -7024,6 +7079,309 @@ function random() {
7024
7079
  return randomBytes(32).toString("base64url");
7025
7080
  }
7026
7081
  //#endregion
7082
+ //#region src/deploy/identity.ts
7083
+ /**
7084
+ * Who a deploy is, on a target it shares with other workspaces.
7085
+ *
7086
+ * A Dokploy server is one flat namespace of projects, images and Docker
7087
+ * service names. Deploys used to identify everything they made by the
7088
+ * workspace name alone, so two workspaces called `shop` — or `Shop` and
7089
+ * `shop` — deploying to one server landed in the same project, pushed over
7090
+ * each other's images and redeployed each other's applications. A
7091
+ * `DeployIdentity` is what a deploy names and claims its resources by instead.
7092
+ */
7093
+ /** A namespace that cannot be part of a Dokploy name or an image path. */
7094
+ var DeployNamespaceInvalid = class extends Error {
7095
+ namespace;
7096
+ constructor(namespace) {
7097
+ 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.`);
7098
+ this.namespace = namespace;
7099
+ this.name = "DeployNamespaceInvalid";
7100
+ }
7101
+ };
7102
+ /** A workspace name with nothing a target can name a resource by. */
7103
+ var DeployProjectNameInvalid = class extends Error {
7104
+ workspaceName;
7105
+ constructor(workspaceName) {
7106
+ super(`The workspace name '${workspaceName}' has no letters or digits to name deployed resources by. Set "name" in gkm.config.ts.`);
7107
+ this.workspaceName = workspaceName;
7108
+ this.name = "DeployProjectNameInvalid";
7109
+ }
7110
+ };
7111
+ const NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
7112
+ /**
7113
+ * Lowercase `[a-z0-9-]`, runs of anything else collapsed to one `-`: valid as
7114
+ * a Docker image path component, a Docker service name and a Dokploy name.
7115
+ */
7116
+ function slug$1(value) {
7117
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
7118
+ }
7119
+ /** The namespace a workspace is in when it does not name one. */
7120
+ function defaultNamespace(workspaceName) {
7121
+ return slug$1(kebabCase(workspaceName));
7122
+ }
7123
+ function deployIdentity(workspace, stage) {
7124
+ const project = slug$1(workspace.name);
7125
+ if (!project) throw new DeployProjectNameInvalid(workspace.name);
7126
+ const fallback = defaultNamespace(workspace.name);
7127
+ const namespace = workspace.deploy?.namespace ?? fallback;
7128
+ if (!NAME.test(namespace)) throw new DeployNamespaceInvalid(namespace);
7129
+ return {
7130
+ namespace,
7131
+ project,
7132
+ stage,
7133
+ key: `${namespace}/${project}`,
7134
+ scope: namespace === fallback ? project : `${namespace}-${project}`
7135
+ };
7136
+ }
7137
+ /**
7138
+ * The token a Dokploy project's description carries to say which identity
7139
+ * created it. A project with the right name and no marker belongs to someone
7140
+ * else, and a deploy never adopts it.
7141
+ */
7142
+ function ownershipMarker(identity) {
7143
+ return `gkm:${identity.key}`;
7144
+ }
7145
+ /** Whether a project description carries `identity`'s marker. */
7146
+ function isOwnedBy(description, identity) {
7147
+ const marker = ownershipMarker(identity);
7148
+ return (description ?? "").split(/\s+/).includes(marker);
7149
+ }
7150
+ /** Any `gkm:` marker a description carries — whoever's it is. */
7151
+ function markerOf(description) {
7152
+ return (description ?? "").split(/\s+/).find((t) => t.startsWith("gkm:"));
7153
+ }
7154
+ /** What the Dokploy project is called. */
7155
+ function projectName(identity) {
7156
+ return identity.scope;
7157
+ }
7158
+ /**
7159
+ * The repository an app's image is pushed to, below the registry:
7160
+ * `<namespace>/<project>-<app>`.
7161
+ *
7162
+ * The namespace is a path segment of its own so that a registry that maps
7163
+ * paths to owners (an organisation's packages, a Harbor project) can grant it
7164
+ * as one; the app is joined to the project so the two read as one name.
7165
+ */
7166
+ function imageName(identity, app) {
7167
+ return `${identity.namespace}/${identity.project}-${slug$1(kebabCase(app))}`;
7168
+ }
7169
+ /** The full image ref an app is built, pushed and deployed as. */
7170
+ function imageRef(identity, app, registry, tag) {
7171
+ const name = imageName(identity, app);
7172
+ return registry ? `${registry.replace(/\/+$/, "")}/${name}:${tag}` : `${name}:${tag}`;
7173
+ }
7174
+ /**
7175
+ * What one application is called on the target.
7176
+ *
7177
+ * The same rule the constructs use, through the same `scopedName`, scoped by
7178
+ * the stage and the identity: the application beside
7179
+ * `production-kitchen-sink-database` is `production-kitchen-sink-api`. A
7180
+ * Dokploy application's name is also its Docker service name, which is unique
7181
+ * on the whole server, so the namespace has to be in it too — `acme`'s and
7182
+ * `globex`'s `shop` would otherwise both run `production-shop-api`.
7183
+ *
7184
+ * The app id is dropped when it repeats the project, so a project named for its
7185
+ * one application is `production-kitchen-sink` rather than
7186
+ * `production-kitchen-sink-kitchen-sink`.
7187
+ */
7188
+ function applicationName(identity, app) {
7189
+ return slug$1(app) === identity.project ? `${identity.stage}-${identity.scope}` : scopedName([identity.stage, identity.scope], app);
7190
+ }
7191
+ //#endregion
7192
+ //#region src/deploy/ownership.ts
7193
+ /**
7194
+ * Which Dokploy project a deploy is allowed to deploy into.
7195
+ *
7196
+ * Deploy used to take any project whose name matched the workspace's, in any
7197
+ * case — so a second workspace called `shop` (or `Shop`) on the same server
7198
+ * deployed into the first one's project, over its images and applications. A
7199
+ * project is now claimed by an ownership marker in its description, and a
7200
+ * project with the right name and no marker is somebody else's.
7201
+ */
7202
+ /**
7203
+ * A Dokploy project with the name this deploy would use exists, and nothing
7204
+ * says this workspace created it.
7205
+ */
7206
+ var ProjectNotOwned = class extends Error {
7207
+ projectName;
7208
+ projectId;
7209
+ identity;
7210
+ ownedBy;
7211
+ constructor(projectName, projectId, identity, ownedBy) {
7212
+ 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.`);
7213
+ this.projectName = projectName;
7214
+ this.projectId = projectId;
7215
+ this.identity = identity;
7216
+ this.ownedBy = ownedBy;
7217
+ this.name = "ProjectNotOwned";
7218
+ }
7219
+ };
7220
+ /** A description with the marker added, keeping what was there. */
7221
+ function withMarker(description, marker) {
7222
+ const existing = (description ?? "").trim();
7223
+ return existing ? `${existing}\n${marker}` : marker;
7224
+ }
7225
+ /** `project.one`, or `null` when Dokploy no longer has the project. */
7226
+ async function projectById(api, projectId) {
7227
+ try {
7228
+ return await api.getProject(projectId) ?? null;
7229
+ } catch (error) {
7230
+ if (error instanceof DokployApiError) return null;
7231
+ throw error;
7232
+ }
7233
+ }
7234
+ /**
7235
+ * The project this deploy deploys into:
7236
+ *
7237
+ * 1. The one the stage's state names, if Dokploy still has it. State written
7238
+ * before markers existed is trusted — it holds an id this workspace was
7239
+ * given when it created the project — and the marker is written now, so
7240
+ * the project is claimed for the next deploy that has no state.
7241
+ * 2. One with this name *and* this identity's marker.
7242
+ * 3. A new one, marked.
7243
+ *
7244
+ * A project that matches by name (in any case) but carries no marker, or
7245
+ * another identity's, raises `ProjectNotOwned` — it is never adopted.
7246
+ */
7247
+ async function resolveProject(api, identity, stateProjectId, log = () => {}) {
7248
+ const name = projectName(identity);
7249
+ const marker = ownershipMarker(identity);
7250
+ if (stateProjectId) {
7251
+ const known = await projectById(api, stateProjectId);
7252
+ if (known) {
7253
+ const claimed = markerOf(known.description);
7254
+ if (claimed && claimed !== marker) throw new ProjectNotOwned(known.name, known.projectId, identity.key, claimed);
7255
+ if (!claimed) {
7256
+ await api.updateProject(known.projectId, {
7257
+ name: known.name,
7258
+ description: withMarker(known.description, marker)
7259
+ });
7260
+ log(` Claimed project ${known.name} for ${identity.key}`);
7261
+ }
7262
+ return {
7263
+ projectId: known.projectId,
7264
+ name: known.name,
7265
+ environments: known.environments ?? [],
7266
+ via: "state"
7267
+ };
7268
+ }
7269
+ }
7270
+ const projects = await api.listProjects();
7271
+ const owned = projects.find((p) => p.name.toLowerCase() === name.toLowerCase() && isOwnedBy(p.description, identity));
7272
+ if (owned) {
7273
+ const details = await api.getProject(owned.projectId);
7274
+ return {
7275
+ projectId: owned.projectId,
7276
+ name: owned.name,
7277
+ environments: details.environments ?? [],
7278
+ via: "marker"
7279
+ };
7280
+ }
7281
+ const clash = projects.find((p) => p.name.toLowerCase() === name.toLowerCase());
7282
+ if (clash) throw new ProjectNotOwned(clash.name, clash.projectId, identity.key, markerOf(clash.description));
7283
+ const created = await api.createProject(name, withMarker("Deployed by gkm.", marker));
7284
+ return {
7285
+ projectId: created.project.projectId,
7286
+ name: created.project.name,
7287
+ environments: [created.environment],
7288
+ via: "created"
7289
+ };
7290
+ }
7291
+ //#endregion
7292
+ //#region src/deploy/registry.ts
7293
+ /**
7294
+ * Which Dokploy registry a stage's images are pulled through.
7295
+ *
7296
+ * It used to be one id stored per machine (`~/.gkm/credentials.json`), and
7297
+ * when that was empty, the first registry Dokploy listed — so an image could be
7298
+ * deployed with credentials for a registry nobody chose, and two workspaces
7299
+ * deployed from one machine shared whichever was stored last. The id is now
7300
+ * kept in the stage's state, and found only from what the workspace
7301
+ * configured.
7302
+ */
7303
+ /** There is nowhere to push images to, so Dokploy has nothing to pull. */
7304
+ var RegistryNotConfigured = class extends Error {
7305
+ stage;
7306
+ constructor(stage) {
7307
+ 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.`);
7308
+ this.stage = stage;
7309
+ this.name = "RegistryNotConfigured";
7310
+ }
7311
+ };
7312
+ /** `deploy.dokploy.registryId` names a registry Dokploy does not have. */
7313
+ var RegistryNotFound = class extends Error {
7314
+ registryId;
7315
+ constructor(registryId) {
7316
+ 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.`);
7317
+ this.registryId = registryId;
7318
+ this.name = "RegistryNotFound";
7319
+ }
7320
+ };
7321
+ /** More than one Dokploy registry serves the configured registry. */
7322
+ var RegistryAmbiguous = class extends Error {
7323
+ registry;
7324
+ registryIds;
7325
+ constructor(registry, registryIds) {
7326
+ 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.`);
7327
+ this.registry = registry;
7328
+ this.registryIds = registryIds;
7329
+ this.name = "RegistryAmbiguous";
7330
+ }
7331
+ };
7332
+ /** `https://ghcr.io/acme/` → `{ host: 'ghcr.io', path: 'acme' }`. */
7333
+ function parts(url) {
7334
+ const [host = "", ...path] = url.replace(/^[a-z]+:\/\//i, "").replace(/\/+$/, "").toLowerCase().split("/");
7335
+ return {
7336
+ host,
7337
+ path: path.join("/")
7338
+ };
7339
+ }
7340
+ /**
7341
+ * The Dokploy registries that serve `registry`: the same host, and — where
7342
+ * Dokploy's own entry names one — the same path.
7343
+ */
7344
+ function serving(registries, registry) {
7345
+ const wanted = parts(registry);
7346
+ const sameHost = registries.filter((r) => parts(r.registryUrl ?? "").host === wanted.host);
7347
+ if (sameHost.length <= 1) return sameHost;
7348
+ return sameHost.filter((r) => {
7349
+ return (parts(r.registryUrl ?? "").path || (r.imagePrefix ?? "").toLowerCase()) === wanted.path;
7350
+ });
7351
+ }
7352
+ /** `registry.one`, or `null` when Dokploy says it has no such registry. */
7353
+ async function registryById(api, registryId) {
7354
+ try {
7355
+ return await api.getRegistry(registryId) ?? null;
7356
+ } catch (error) {
7357
+ if (error instanceof DokployApiError) return null;
7358
+ throw error;
7359
+ }
7360
+ }
7361
+ /**
7362
+ * The registry id for a stage, in order: the configured id, the one in the
7363
+ * stage's state, the one Dokploy has for the configured registry URL, and
7364
+ * otherwise a new one. Never a registry the workspace did not point at.
7365
+ */
7366
+ async function resolveRegistry(api, options) {
7367
+ const { stage, registry, configuredId, stateId, log = () => {} } = options;
7368
+ if (!registry) throw new RegistryNotConfigured(stage);
7369
+ if (configuredId) {
7370
+ const configured = await registryById(api, configuredId);
7371
+ if (!configured) throw new RegistryNotFound(configuredId);
7372
+ return configured;
7373
+ }
7374
+ if (stateId) {
7375
+ const known = await registryById(api, stateId);
7376
+ if (known) return known;
7377
+ log(` ⚠ The stage's registry ${stateId} no longer exists in Dokploy`);
7378
+ }
7379
+ const matches = serving(await api.listRegistries(), registry);
7380
+ if (matches.length > 1) throw new RegistryAmbiguous(registry, matches.map((r) => r.registryId));
7381
+ if (matches[0]) return matches[0];
7382
+ return options.create(registry);
7383
+ }
7384
+ //#endregion
7027
7385
  //#region src/deploy/StateProvider.ts
7028
7386
  /**
7029
7387
  * Check if value is a StateProvider implementation.
@@ -7796,6 +8154,7 @@ async function workspaceDeployCommand(configured, options) {
7796
8154
  apps: units
7797
8155
  } : configured;
7798
8156
  if (provider !== "dokploy") throw new Error(`Workspace deployment only supports Dokploy. Got: ${provider}`);
8157
+ const identity = deployIdentity(workspace, stage);
7799
8158
  logger$4.log(`\n🚀 Deploying workspace "${workspace.name}" to Dokploy...`);
7800
8159
  logger$4.log(` Stage: ${stage}`);
7801
8160
  const imageTag = tag ?? generateTag(stage);
@@ -7862,38 +8221,29 @@ async function workspaceDeployCommand(configured, options) {
7862
8221
  baseUrl: creds.endpoint,
7863
8222
  token: creds.token
7864
8223
  });
7865
- logger$4.log("\n📁 Setting up Dokploy project...");
7866
- const projectName = workspace.name;
7867
- let project = (await api.listProjects()).find((p) => p.name.toLowerCase() === projectName.toLowerCase());
7868
- let environmentId;
7869
- if (project) {
7870
- logger$4.log(` Found existing project: ${project.name}`);
7871
- const matchingEnv = ((await api.getProject(project.projectId)).environments ?? []).find((e) => e.name.toLowerCase() === stage.toLowerCase());
7872
- if (matchingEnv) {
7873
- environmentId = matchingEnv.environmentId;
7874
- logger$4.log(` Using environment: ${matchingEnv.name}`);
7875
- } else {
7876
- logger$4.log(` Creating "${stage}" environment...`);
7877
- environmentId = (await api.createEnvironment(project.projectId, stage)).environmentId;
7878
- logger$4.log(` ✓ Created environment: ${stage}`);
7879
- }
7880
- } else {
7881
- logger$4.log(` Creating project: ${projectName}`);
7882
- const result = await api.createProject(projectName);
7883
- project = result.project;
7884
- if (result.environment.name.toLowerCase() !== stage.toLowerCase()) {
7885
- logger$4.log(` Creating "${stage}" environment...`);
7886
- environmentId = (await api.createEnvironment(project.projectId, stage)).environmentId;
7887
- } else environmentId = result.environment.environmentId;
7888
- logger$4.log(` ✓ Created project: ${project.projectId}`);
7889
- }
7890
8224
  logger$4.log("\n📋 Loading deploy state...");
7891
8225
  const stateProvider = await createStateProvider({
7892
8226
  config: workspace.state,
7893
8227
  workspaceRoot: workspace.root,
7894
8228
  workspaceName: workspace.name
7895
8229
  });
7896
- let state = await stateProvider.read(stage);
8230
+ const previous = await stateProvider.read(stage);
8231
+ logger$4.log("\n📁 Setting up Dokploy project...");
8232
+ logger$4.log(` Identity: ${identity.key}`);
8233
+ const project = await resolveProject(api, identity, previous?.projectId, (message) => logger$4.log(message));
8234
+ if (project.via === "created") logger$4.log(` ✓ Created project: ${project.projectId}`);
8235
+ else logger$4.log(` Found existing project: ${project.name}`);
8236
+ let environmentId;
8237
+ const matchingEnv = project.environments.find((e) => e.name.toLowerCase() === stage.toLowerCase());
8238
+ if (matchingEnv) {
8239
+ environmentId = matchingEnv.environmentId;
8240
+ logger$4.log(` Using environment: ${matchingEnv.name}`);
8241
+ } else {
8242
+ logger$4.log(` Creating "${stage}" environment...`);
8243
+ environmentId = (await api.createEnvironment(project.projectId, stage)).environmentId;
8244
+ logger$4.log(` ✓ Created environment: ${stage}`);
8245
+ }
8246
+ let state = previous;
7897
8247
  if (state) {
7898
8248
  logger$4.log(` Found existing state for stage "${stage}"`);
7899
8249
  if (state.projectId !== project.projectId) {
@@ -7908,33 +8258,27 @@ async function workspaceDeployCommand(configured, options) {
7908
8258
  logger$4.log(` Creating new state for stage "${stage}"`);
7909
8259
  state = createEmptyState(stage, project.projectId, environmentId);
7910
8260
  }
8261
+ state.identity = identity.key;
7911
8262
  logger$4.log("\n🐳 Checking registry...");
7912
- let registryId = await getDokployRegistryId();
7913
8263
  const registry = workspace.deploy.dokploy?.registry;
7914
- if (registryId) try {
7915
- const reg = await api.getRegistry(registryId);
7916
- logger$4.log(` Using registry: ${reg.registryName}`);
7917
- } catch {
7918
- logger$4.log(" ⚠ Stored registry not found, clearing...");
7919
- registryId = void 0;
7920
- await storeDokployRegistryId("");
7921
- }
7922
- if (!registryId) {
7923
- const registries = await api.listRegistries();
7924
- if (registries.length > 0) {
7925
- registryId = registries[0].registryId;
7926
- await storeDokployRegistryId(registryId);
7927
- logger$4.log(` Using registry: ${registries[0].registryName}`);
7928
- } else if (registry) {
7929
- logger$4.log(" No registries found in Dokploy. Let's create one.");
7930
- logger$4.log(` Registry URL: ${registry}`);
8264
+ const dokployRegistry = await resolveRegistry(api, {
8265
+ stage,
8266
+ registry,
8267
+ configuredId: workspace.deploy.dokploy?.registryId,
8268
+ stateId: state.registryId,
8269
+ log: (message) => logger$4.log(message),
8270
+ create: async (url) => {
8271
+ logger$4.log(` Dokploy has no registry for ${url}. Let's create one.`);
7931
8272
  const username = await prompt("Registry username: ");
7932
8273
  const password = await prompt("Registry password/token: ", true);
7933
- registryId = (await api.createRegistry("Default Registry", registry, username, password)).registryId;
7934
- await storeDokployRegistryId(registryId);
7935
- logger$4.log(` ✓ Registry created: ${registryId}`);
7936
- } else logger$4.log(" ⚠ No registry configured. Set deploy.dokploy.registry in workspace config");
7937
- }
8274
+ const created = await api.createRegistry("Default Registry", url, username, password);
8275
+ logger$4.log(` ✓ Registry created: ${created.registryId}`);
8276
+ return created;
8277
+ }
8278
+ });
8279
+ const registryId = dokployRegistry.registryId;
8280
+ state.registryId = registryId;
8281
+ logger$4.log(` Using registry: ${dokployRegistry.registryName}`);
7938
8282
  const skippedMobileApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "mobile");
7939
8283
  if (skippedMobileApps.length > 0) logger$4.log(`\n📱 Skipping ${skippedMobileApps.length} mobile app(s) — deploy via framework toolchain: ${skippedMobileApps.join(", ")}`);
7940
8284
  const backendApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "backend");
@@ -7955,6 +8299,7 @@ async function workspaceDeployCommand(configured, options) {
7955
8299
  projectId: project.projectId,
7956
8300
  environmentId,
7957
8301
  stage,
8302
+ scope: identity.scope,
7958
8303
  appUrls,
7959
8304
  supplied: stageSecrets.custom,
7960
8305
  seed: stageSecrets.seed,
@@ -7980,7 +8325,7 @@ async function workspaceDeployCommand(configured, options) {
7980
8325
  const backupState = await provisionBackupDestination({
7981
8326
  api,
7982
8327
  projectId: project.projectId,
7983
- projectName: workspace.name,
8328
+ projectName: identity.scope,
7984
8329
  stage,
7985
8330
  config: workspace.deploy.backups,
7986
8331
  existingState: getBackupState(state),
@@ -8021,7 +8366,7 @@ async function workspaceDeployCommand(configured, options) {
8021
8366
  const app = workspace.apps[appName];
8022
8367
  logger$4.log(`\n ⚙️ Deploying ${appName}...`);
8023
8368
  try {
8024
- const dokployAppName = applicationName(stage, workspace.name, appName);
8369
+ const dokployAppName = applicationName(identity, appName);
8025
8370
  let application = null;
8026
8371
  const cachedAppId = getApplicationId(state, appName);
8027
8372
  if (cachedAppId) {
@@ -8043,20 +8388,23 @@ async function workspaceDeployCommand(configured, options) {
8043
8388
  iv: appSecrets.payload.iv
8044
8389
  } : void 0;
8045
8390
  if (appSecrets && credentials) logger$4.log(` Encrypted ${appSecrets.secretCount} secrets`);
8046
- const imageName = `${workspace.name}-${appName}`;
8047
- const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
8048
- logger$4.log(` Building Docker image: ${imageRef}`);
8049
- await deployDocker({
8391
+ const imageRef$1 = imageRef(identity, appName, registry, imageTag);
8392
+ logger$4.log(` Building Docker image: ${imageRef$1}`);
8393
+ const built = await deployDocker({
8050
8394
  stage,
8051
8395
  tag: imageTag,
8052
8396
  skipPush: false,
8053
8397
  config: {
8054
8398
  registry,
8055
- imageName,
8399
+ imageName: imageName(identity, appName),
8056
8400
  appName
8057
8401
  },
8058
8402
  credentials
8059
8403
  });
8404
+ setDeployedImage(state, appName, {
8405
+ ref: built.imageRef ?? imageRef$1,
8406
+ ...built.digest ? { digest: built.digest } : {}
8407
+ });
8060
8408
  const backendHost = resolveHost(appName, app, stage, workspace.deploy?.domains, false);
8061
8409
  const dependencyUrls = {};
8062
8410
  if (app.dependencies) {
@@ -8087,7 +8435,7 @@ async function workspaceDeployCommand(configured, options) {
8087
8435
  };
8088
8436
  const envVars = Object.entries(withDeclared).map(([key, value]) => `${key}=${value}`);
8089
8437
  if (Object.keys(withDeclared).length > 0) logger$4.log(` Resolved ${Object.keys(withDeclared).length} env vars: ${Object.keys(withDeclared).sort().join(", ")}`);
8090
- await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
8438
+ await api.saveDockerProvider(application.applicationId, imageRef$1, { registryId });
8091
8439
  await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
8092
8440
  logger$4.log(` Deploying to Dokploy...`);
8093
8441
  await api.deployApplication(application.applicationId);
@@ -8120,7 +8468,8 @@ async function workspaceDeployCommand(configured, options) {
8120
8468
  type: app.type,
8121
8469
  success: true,
8122
8470
  applicationId: application.applicationId,
8123
- imageRef
8471
+ imageRef: imageRef$1,
8472
+ ...built.digest ? { digest: built.digest } : {}
8124
8473
  });
8125
8474
  logger$4.log(` ✓ ${appName} deployed successfully`);
8126
8475
  } catch (error) {
@@ -8142,7 +8491,7 @@ async function workspaceDeployCommand(configured, options) {
8142
8491
  const app = workspace.apps[appName];
8143
8492
  logger$4.log(`\n 🌐 Deploying ${appName}...`);
8144
8493
  try {
8145
- const dokployAppName = applicationName(stage, workspace.name, appName);
8494
+ const dokployAppName = applicationName(identity, appName);
8146
8495
  let application = null;
8147
8496
  const cachedAppId = getApplicationId(state, appName);
8148
8497
  if (cachedAppId) {
@@ -8187,28 +8536,31 @@ async function workspaceDeployCommand(configured, options) {
8187
8536
  }
8188
8537
  }
8189
8538
  if (buildArgs.length > 0) logger$4.log(` Build args: ${publicUrlArgNames.join(", ")}`);
8190
- const imageName = `${workspace.name}-${appName}`;
8191
- const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
8192
- logger$4.log(` Building Docker image: ${imageRef}`);
8193
- await deployDocker({
8539
+ const imageRef$2 = imageRef(identity, appName, registry, imageTag);
8540
+ logger$4.log(` Building Docker image: ${imageRef$2}`);
8541
+ const built = await deployDocker({
8194
8542
  stage,
8195
8543
  tag: imageTag,
8196
8544
  skipPush: false,
8197
8545
  config: {
8198
8546
  registry,
8199
- imageName,
8547
+ imageName: imageName(identity, appName),
8200
8548
  appName
8201
8549
  },
8202
8550
  buildArgs,
8203
8551
  publicUrlArgs: publicUrlArgNames
8204
8552
  });
8553
+ setDeployedImage(state, appName, {
8554
+ ref: built.imageRef ?? imageRef$2,
8555
+ ...built.digest ? { digest: built.digest } : {}
8556
+ });
8205
8557
  const envVars = [
8206
8558
  `NODE_ENV=production`,
8207
8559
  `PORT=${app.port}`,
8208
8560
  `STAGE=${stage}`
8209
8561
  ];
8210
8562
  for (const [key, value] of Object.entries(resolved)) envVars.push(`${key}=${value}`);
8211
- await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
8563
+ await api.saveDockerProvider(application.applicationId, imageRef$2, { registryId });
8212
8564
  await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
8213
8565
  logger$4.log(` Deploying to Dokploy...`);
8214
8566
  await api.deployApplication(application.applicationId);
@@ -8241,7 +8593,8 @@ async function workspaceDeployCommand(configured, options) {
8241
8593
  type: app.type,
8242
8594
  success: true,
8243
8595
  applicationId: application.applicationId,
8244
- imageRef
8596
+ imageRef: imageRef$2,
8597
+ ...built.digest ? { digest: built.digest } : {}
8245
8598
  });
8246
8599
  logger$4.log(` ✓ ${appName} deployed successfully`);
8247
8600
  } catch (error) {
@@ -8837,7 +9190,7 @@ async function createStateStore(options) {
8837
9190
  }
8838
9191
  if (config.provider === "ssm") {
8839
9192
  if (!workspaceName) throw new StateStoreNeedsWorkspaceName("ssm");
8840
- const { SSMStateStore } = await import("./SSMStateStore-oda9xOGc.mjs");
9193
+ const { SSMStateStore } = await import("./SSMStateStore-BjZZm-nV.mjs");
8841
9194
  const ssm = config;
8842
9195
  return SSMStateStore.create({
8843
9196
  workspaceName,
@@ -10107,25 +10460,25 @@ function loadPackageJson() {
10107
10460
  * Run: pnpm --filter @geekmidas/cli sync-versions
10108
10461
  */
10109
10462
  const GEEKMIDAS_VERSIONS = {
10110
- "@geekmidas/audit": "~10.0.0-alpha.56",
10111
- "@geekmidas/auth": "~10.0.0-alpha.56",
10112
- "@geekmidas/cache": "~10.0.0-alpha.56",
10113
- "@geekmidas/client": "~10.0.0-alpha.56",
10114
- "@geekmidas/cloud": "~10.0.0-alpha.56",
10115
- "@geekmidas/constructs": "~10.0.0-alpha.56",
10116
- "@geekmidas/db": "~10.0.0-alpha.56",
10117
- "@geekmidas/emailkit": "~10.0.0-alpha.56",
10118
- "@geekmidas/envkit": "~10.0.0-alpha.56",
10119
- "@geekmidas/errors": "~10.0.0-alpha.56",
10120
- "@geekmidas/events": "~10.0.0-alpha.56",
10121
- "@geekmidas/logger": "~10.0.0-alpha.56",
10122
- "@geekmidas/manifest": "~10.0.0-alpha.56",
10123
- "@geekmidas/rate-limit": "~10.0.0-alpha.56",
10124
- "@geekmidas/schema": "~10.0.0-alpha.56",
10125
- "@geekmidas/services": "~10.0.0-alpha.56",
10126
- "@geekmidas/storage": "~10.0.0-alpha.56",
10127
- "@geekmidas/telescope": "~10.0.0-alpha.56",
10128
- "@geekmidas/testkit": "~10.0.0-alpha.56",
10463
+ "@geekmidas/audit": "~10.0.0-alpha.58",
10464
+ "@geekmidas/auth": "~10.0.0-alpha.58",
10465
+ "@geekmidas/cache": "~10.0.0-alpha.58",
10466
+ "@geekmidas/client": "~10.0.0-alpha.58",
10467
+ "@geekmidas/cloud": "~10.0.0-alpha.58",
10468
+ "@geekmidas/constructs": "~10.0.0-alpha.58",
10469
+ "@geekmidas/db": "~10.0.0-alpha.58",
10470
+ "@geekmidas/emailkit": "~10.0.0-alpha.58",
10471
+ "@geekmidas/envkit": "~10.0.0-alpha.58",
10472
+ "@geekmidas/errors": "~10.0.0-alpha.58",
10473
+ "@geekmidas/events": "~10.0.0-alpha.58",
10474
+ "@geekmidas/logger": "~10.0.0-alpha.58",
10475
+ "@geekmidas/manifest": "~10.0.0-alpha.58",
10476
+ "@geekmidas/rate-limit": "~10.0.0-alpha.58",
10477
+ "@geekmidas/schema": "~10.0.0-alpha.58",
10478
+ "@geekmidas/services": "~10.0.0-alpha.58",
10479
+ "@geekmidas/storage": "~10.0.0-alpha.58",
10480
+ "@geekmidas/telescope": "~10.0.0-alpha.58",
10481
+ "@geekmidas/testkit": "~10.0.0-alpha.58",
10129
10482
  "@geekmidas/cli": `~${loadPackageJson().version}`
10130
10483
  };
10131
10484
  //#endregion
@@ -16007,7 +16360,7 @@ function testDatabases(reconciled) {
16007
16360
  * starts without the reference data the app needs.
16008
16361
  */
16009
16362
  async function migrateTestStage(workspace, env) {
16010
- const { migrateAndSeed } = await import("./migrate-CS3DkO6y.mjs");
16363
+ const { migrateAndSeed } = await import("./migrate-CXVnh1J3.mjs");
16011
16364
  const { discover } = await import("./discover-Cf2jAcgF.mjs").then((n) => n.n);
16012
16365
  const sources = {};
16013
16366
  const manifest = await discover({
@@ -16438,7 +16791,7 @@ program.command("migrate").description("Apply each database construct's migratio
16438
16791
  try {
16439
16792
  const globalOptions = program.opts();
16440
16793
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16441
- const { migrateCommand } = await import("./migrate-CS3DkO6y.mjs");
16794
+ const { migrateCommand } = await import("./migrate-CXVnh1J3.mjs");
16442
16795
  await migrateCommand({
16443
16796
  ...options,
16444
16797
  ...construct ? { construct } : {}
@@ -16452,7 +16805,7 @@ program.command("seed").description("Migrate, then run each database construct's
16452
16805
  try {
16453
16806
  const globalOptions = program.opts();
16454
16807
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16455
- const { seedCommand } = await import("./migrate-CS3DkO6y.mjs");
16808
+ const { seedCommand } = await import("./migrate-CXVnh1J3.mjs");
16456
16809
  await seedCommand({
16457
16810
  ...options,
16458
16811
  ...construct ? { construct } : {}
@@ -16466,7 +16819,7 @@ program.command("migration").description("Write a construct's next migration: an
16466
16819
  try {
16467
16820
  const globalOptions = program.opts();
16468
16821
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16469
- const { migrationCommand } = await import("./migrate-CS3DkO6y.mjs");
16822
+ const { migrationCommand } = await import("./migrate-CXVnh1J3.mjs");
16470
16823
  await migrationCommand({
16471
16824
  construct,
16472
16825
  ...name ? { name } : {}
@@ -16564,7 +16917,7 @@ program.command("encryption:rotate").description("Add a new current key to an En
16564
16917
  try {
16565
16918
  const globalOptions = program.opts();
16566
16919
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16567
- const { encryptionRotateCommand } = await import("./encryption-KQTYFEzU.mjs");
16920
+ const { encryptionRotateCommand } = await import("./encryption-Dlnl5XEU.mjs");
16568
16921
  await encryptionRotateCommand(construct, options);
16569
16922
  } catch (error) {
16570
16923
  console.error(formatError(error));
@@ -16575,7 +16928,7 @@ program.command("encryption:retire").description("Remove an old key from an Encr
16575
16928
  try {
16576
16929
  const globalOptions = program.opts();
16577
16930
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16578
- const { encryptionRetireCommand } = await import("./encryption-KQTYFEzU.mjs");
16931
+ const { encryptionRetireCommand } = await import("./encryption-Dlnl5XEU.mjs");
16579
16932
  await encryptionRetireCommand(construct, key, options);
16580
16933
  } catch (error) {
16581
16934
  console.error(formatError(error));
@@ -16627,7 +16980,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
16627
16980
  console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
16628
16981
  process.exit(1);
16629
16982
  }
16630
- const { derivedContainers } = await import("./workspace-CgWcE_Yr.mjs").then((n) => n.o);
16983
+ const { derivedContainers } = await import("./workspace-D-f58jsP.mjs").then((n) => n.o);
16631
16984
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
16632
16985
  if (!result) {
16633
16986
  console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
@@ -16686,7 +17039,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
16686
17039
  try {
16687
17040
  const globalOptions = program.opts();
16688
17041
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16689
- const { deployGithubCommand } = await import("./github-y7OEj59M.mjs");
17042
+ const { deployGithubCommand } = await import("./github-BdF9tsvl.mjs");
16690
17043
  await deployGithubCommand(options);
16691
17044
  } catch (error) {
16692
17045
  console.error(formatError(error));