@geekmidas/cli 10.0.0-alpha.36 → 10.0.0-alpha.38

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 (107) hide show
  1. package/dist/{HostingerProvider-GF6NAZAK.mjs → HostingerProvider-Bh6K7QYz.mjs} +2 -2
  2. package/dist/{HostingerProvider-GF6NAZAK.mjs.map → HostingerProvider-Bh6K7QYz.mjs.map} +1 -1
  3. package/dist/{ssm-FxeSAt35.cjs → aws-DkHdTV-p.cjs} +7 -6
  4. package/dist/aws-DkHdTV-p.cjs.map +1 -0
  5. package/dist/{ssm-BKN1Cg4z.mjs → aws-DojzQnLd.mjs} +7 -6
  6. package/dist/aws-DojzQnLd.mjs.map +1 -0
  7. package/dist/{bundler-D9IPNvDt.mjs → bundler--fyybf1N.mjs} +13 -7
  8. package/dist/bundler--fyybf1N.mjs.map +1 -0
  9. package/dist/{bundler-CYF5mWi9.cjs → bundler-By6_sXiL.cjs} +12 -6
  10. package/dist/bundler-By6_sXiL.cjs.map +1 -0
  11. package/dist/{config-Dss1WwtH.mjs → config-D1S2r0JN.mjs} +93 -70
  12. package/dist/config-D1S2r0JN.mjs.map +1 -0
  13. package/dist/{config-DgAHy8nY.cjs → config-j4krJZVq.cjs} +114 -73
  14. package/dist/config-j4krJZVq.cjs.map +1 -0
  15. package/dist/config.cjs +6 -3
  16. package/dist/config.d.cts +40 -19
  17. package/dist/config.d.cts.map +1 -1
  18. package/dist/config.d.mts +40 -19
  19. package/dist/config.d.mts.map +1 -1
  20. package/dist/config.mjs +3 -3
  21. package/dist/{github-C99BMxf0.mjs → github-BPfmXtqk.mjs} +8 -11
  22. package/dist/github-BPfmXtqk.mjs.map +1 -0
  23. package/dist/{github-tZmMZ7td.cjs → github-BvK076ph.cjs} +10 -13
  24. package/dist/github-BvK076ph.cjs.map +1 -0
  25. package/dist/{index-DxXMtQNT.d.mts → index-CLH1DgsO.d.cts} +2 -2
  26. package/dist/{index-CAp22ul5.d.cts.map → index-CLH1DgsO.d.cts.map} +1 -1
  27. package/dist/{index-CAp22ul5.d.cts → index-CST2jOPH.d.mts} +2 -2
  28. package/dist/{index-DxXMtQNT.d.mts.map → index-CST2jOPH.d.mts.map} +1 -1
  29. package/dist/index.cjs +1071 -338
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.mjs +1059 -326
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/{migrate-CT8b3nt_.mjs → migrate-C2aYSZTn.mjs} +8 -6
  34. package/dist/migrate-C2aYSZTn.mjs.map +1 -0
  35. package/dist/{migrate-BsW4xBEO.cjs → migrate-DKn83skp.cjs} +7 -5
  36. package/dist/migrate-DKn83skp.cjs.map +1 -0
  37. package/dist/openapi.cjs +1 -1
  38. package/dist/openapi.d.cts +1 -1
  39. package/dist/openapi.d.mts +1 -1
  40. package/dist/openapi.mjs +1 -1
  41. package/dist/{ready-D1BCiaYU.mjs → ready-BTm_sMNU.mjs} +90 -6
  42. package/dist/ready-BTm_sMNU.mjs.map +1 -0
  43. package/dist/{ready-CXhoutix.cjs → ready-DxC0Z3No.cjs} +94 -4
  44. package/dist/ready-DxC0Z3No.cjs.map +1 -0
  45. package/dist/reconcile/public.d.cts +1 -1
  46. package/dist/reconcile/public.d.mts +1 -1
  47. package/dist/reconcile--I7V4Klj.mjs +34 -0
  48. package/dist/reconcile--I7V4Klj.mjs.map +1 -0
  49. package/dist/reconcile-CuCQBa5-.cjs +34 -0
  50. package/dist/reconcile-CuCQBa5-.cjs.map +1 -0
  51. package/dist/storage-BzaUxmqe.mjs +124 -0
  52. package/dist/storage-BzaUxmqe.mjs.map +1 -0
  53. package/dist/storage-CSGoC3bK.cjs +152 -0
  54. package/dist/storage-CSGoC3bK.cjs.map +1 -0
  55. package/dist/store-DQgxT_WR.cjs +211 -0
  56. package/dist/store-DQgxT_WR.cjs.map +1 -0
  57. package/dist/store-DeGATCS-.mjs +182 -0
  58. package/dist/store-DeGATCS-.mjs.map +1 -0
  59. package/dist/{types-BTiu5kwo.d.mts → types-B0EVAF6U.d.mts} +24 -10
  60. package/dist/types-B0EVAF6U.d.mts.map +1 -0
  61. package/dist/{types-Bj_FpeTc.d.cts → types-CHKCUKM0.d.cts} +24 -10
  62. package/dist/types-CHKCUKM0.d.cts.map +1 -0
  63. package/dist/vitest.cjs +8 -4
  64. package/dist/vitest.cjs.map +1 -1
  65. package/dist/vitest.d.cts +5 -4
  66. package/dist/vitest.d.cts.map +1 -1
  67. package/dist/vitest.d.mts +5 -4
  68. package/dist/vitest.d.mts.map +1 -1
  69. package/dist/vitest.mjs +8 -4
  70. package/dist/vitest.mjs.map +1 -1
  71. package/dist/workspace/index.cjs +1 -1
  72. package/dist/workspace/index.d.cts +2 -2
  73. package/dist/workspace/index.d.mts +2 -2
  74. package/dist/workspace/index.mjs +1 -1
  75. package/dist/{workspace-CsL76tpc.mjs → workspace-DOmIhiau.mjs} +2 -2
  76. package/dist/{workspace-CsL76tpc.mjs.map → workspace-DOmIhiau.mjs.map} +1 -1
  77. package/dist/{workspace-EmXwO2qM.mjs → workspace-Lm6dv2nz.mjs} +121 -40
  78. package/dist/workspace-Lm6dv2nz.mjs.map +1 -0
  79. package/dist/{workspace-CAJk6UmC.cjs → workspace-RYSYtHP7.cjs} +2 -2
  80. package/dist/{workspace-CAJk6UmC.cjs.map → workspace-RYSYtHP7.cjs.map} +1 -1
  81. package/dist/{workspace-C0X8xSXV.cjs → workspace-gfIrSEUp.cjs} +155 -38
  82. package/dist/workspace-gfIrSEUp.cjs.map +1 -0
  83. package/package.json +14 -14
  84. package/dist/bundler-CYF5mWi9.cjs.map +0 -1
  85. package/dist/bundler-D9IPNvDt.mjs.map +0 -1
  86. package/dist/config-DgAHy8nY.cjs.map +0 -1
  87. package/dist/config-Dss1WwtH.mjs.map +0 -1
  88. package/dist/github-C99BMxf0.mjs.map +0 -1
  89. package/dist/github-tZmMZ7td.cjs.map +0 -1
  90. package/dist/migrate-BsW4xBEO.cjs.map +0 -1
  91. package/dist/migrate-CT8b3nt_.mjs.map +0 -1
  92. package/dist/ready-CXhoutix.cjs.map +0 -1
  93. package/dist/ready-D1BCiaYU.mjs.map +0 -1
  94. package/dist/reconcile-BxZTV2vl.cjs +0 -332
  95. package/dist/reconcile-BxZTV2vl.cjs.map +0 -1
  96. package/dist/reconcile-yNq6Shau.mjs +0 -279
  97. package/dist/reconcile-yNq6Shau.mjs.map +0 -1
  98. package/dist/ssm-BKN1Cg4z.mjs.map +0 -1
  99. package/dist/ssm-FxeSAt35.cjs.map +0 -1
  100. package/dist/storage-BjTLjDDp.mjs +0 -297
  101. package/dist/storage-BjTLjDDp.mjs.map +0 -1
  102. package/dist/storage-C2JC31V9.cjs +0 -344
  103. package/dist/storage-C2JC31V9.cjs.map +0 -1
  104. package/dist/types-BTiu5kwo.d.mts.map +0 -1
  105. package/dist/types-Bj_FpeTc.d.cts.map +0 -1
  106. package/dist/workspace-C0X8xSXV.cjs.map +0 -1
  107. package/dist/workspace-EmXwO2qM.mjs.map +0 -1
@@ -1,14 +1,16 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
2
2
  import { a as DEFAULT_CACHE, o as DEFAULT_EVENTS$1, s as DEFAULT_STORAGE, t as discover } from "./discover-CMxesskG.mjs";
3
- import { l as appKey, o as loadWorkspaceAppInfo, r as getAppNameFromCwd, s as loadWorkspaceConfig } from "./config-Dss1WwtH.mjs";
4
- import { E as TEST_STAGE, t as allConstructGlobs } from "./workspace-CsL76tpc.mjs";
5
- import { n as readStageSecrets, o as toEmbeddableSecrets, r as secretsExist } from "./storage-BjTLjDDp.mjs";
3
+ import { c as loadWorkspaceAppInfo, f as appKey, l as loadWorkspaceConfig, r as NotInAnApp, t as ConfigNotFound } from "./config-D1S2r0JN.mjs";
4
+ import { E as TEST_STAGE, t as allConstructGlobs } from "./workspace-DOmIhiau.mjs";
5
+ import { i as toEmbeddableSecrets } from "./storage-BzaUxmqe.mjs";
6
+ import { i as FileSecretsStore, n as secretsStoreFor } from "./store-DeGATCS-.mjs";
7
+ import { c as readFakes } from "./ready-BTm_sMNU.mjs";
6
8
  import { existsSync } from "node:fs";
7
9
  import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
8
10
  import { networkInterfaces } from "node:os";
9
11
  import { dirname, join, resolve } from "node:path";
10
12
  import { execFile } from "node:child_process";
11
- import { DEFAULT_POSTGRES_VERSION, cacheTable, cookieDomain, dependenciesOf, dependentsOf, mobileOrigins, provideKey, providedKeyFor, provisionOrder, publicEnvFor, schemeBase } from "@geekmidas/manifest";
13
+ import { DEFAULT_POSTGRES_VERSION, cacheTable, cookieDomain, dependenciesOf, dependentsOf, externalApiUrl, kebabCase, mobileOrigins, provideKey, providedKeyFor, provisionOrder, publicEnvFor, schemeBase } from "@geekmidas/manifest";
12
14
  import { createServer } from "node:net";
13
15
  import { config } from "dotenv";
14
16
  import { createHash } from "node:crypto";
@@ -101,14 +103,12 @@ async function savePortState(workspaceRoot, ports) {
101
103
  * For workspace app: maps {APP}_DATABASE_URL → DATABASE_URL.
102
104
  * @internal Exported for testing
103
105
  */
104
- async function loadSecretsForApp(secretsRoot, stage, appName) {
106
+ async function loadSecretsForApp(store, stage, appName) {
105
107
  let secrets = {};
106
- if (secretsExist(stage, secretsRoot)) {
107
- const stageSecrets = await readStageSecrets(stage, secretsRoot);
108
- if (stageSecrets) {
109
- logger.log(`🔐 Loading secrets from stage: ${stage}`);
110
- secrets = toEmbeddableSecrets(stageSecrets);
111
- }
108
+ const stageSecrets = await store.read(stage);
109
+ if (stageSecrets) {
110
+ logger.log(`🔐 Loading secrets from stage: ${stage}`);
111
+ secrets = toEmbeddableSecrets(stageSecrets);
112
112
  }
113
113
  if (Object.keys(secrets).length === 0) return {};
114
114
  if (!appName) return secrets;
@@ -202,20 +202,22 @@ async function prepareEntryCredentials(options) {
202
202
  workspaceAppPort = appInfo.app.port;
203
203
  secretsRoot = appInfo.workspaceRoot;
204
204
  appName = appInfo.appName;
205
- } catch {
205
+ } catch (error) {
206
+ if (!(error instanceof NotInAnApp || error instanceof ConfigNotFound)) throw error;
206
207
  workspace = await loadWorkspaceConfig(cwd).then((loaded) => loaded.workspace).catch(() => void 0);
207
208
  secretsRoot = workspace?.root ?? findSecretsRoot(cwd);
208
- appName = getAppNameFromCwd(cwd) ?? void 0;
209
209
  }
210
210
  const resolvedPort = options.explicitPort ?? workspaceAppPort ?? 3e3;
211
211
  const stage = options.stage ?? workspace?.stages.local;
212
- const credentials = stage ? await loadSecretsForApp(secretsRoot, stage, appName) : {};
212
+ const store = stage ? workspace ? await secretsStoreFor(workspace, stage) : new FileSecretsStore(secretsRoot) : void 0;
213
+ const credentials = store && stage ? await loadSecretsForApp(store, stage, appName) : {};
213
214
  credentials.PORT = String(resolvedPort);
214
215
  if (appInfo?.app.type === "mobile") credentials.RCT_METRO_PORT = String(resolvedPort);
215
216
  const declaredKeys = [];
217
+ let reconciled;
216
218
  if (workspace) {
217
219
  const { reconcileWorkspace } = await Promise.resolve().then(() => workspace_exports);
218
- const reconciled = await reconcileWorkspace(workspace, {
220
+ reconciled = await reconcileWorkspace(workspace, {
219
221
  stage: options.reconcileStage ?? workspace.stages.local,
220
222
  start: options.startDocker ?? false
221
223
  });
@@ -235,7 +237,8 @@ async function prepareEntryCredentials(options) {
235
237
  appName,
236
238
  secretsRoot,
237
239
  appInfo,
238
- declaredKeys
240
+ declaredKeys,
241
+ ...reconciled ? { reconciled } : {}
239
242
  };
240
243
  }
241
244
  //#endregion
@@ -395,8 +398,23 @@ const PORTS = {
395
398
  label: "https edge"
396
399
  }]
397
400
  };
398
- /** The ports a container needs published. Empty for one gkm does not know. */
399
- function portsOf(container) {
401
+ /** The port key — and container name, for an image — of an API's fake. */
402
+ function fakeKey(id) {
403
+ return `${kebabCase(id)}-fake`;
404
+ }
405
+ /**
406
+ * The ports a container needs published. Empty for one gkm does not know.
407
+ *
408
+ * `fakes` answers for the containers no table can: an external API's fake
409
+ * listens wherever its declaration says.
410
+ */
411
+ function portsOf(container, fakes = {}) {
412
+ const fake = fakes[container];
413
+ if (fake?.port !== void 0) return [{
414
+ key: container,
415
+ inside: fake.port,
416
+ label: `${fake.id} fake`
417
+ }];
400
418
  return PORTS[container] ?? [];
401
419
  }
402
420
  /** The key a container's primary port is allocated under. */
@@ -409,8 +427,9 @@ function primaryPortKey(container) {
409
427
  * What allocation is handed: it assigns ports to names and knows nothing about
410
428
  * what listens on them.
411
429
  */
412
- function portKeys(containers) {
413
- return [...containers].sort().flatMap((container) => portsOf(container).map((port) => port.key));
430
+ function portKeys(containers, fakes = {}) {
431
+ const keys = [...containers].sort().flatMap((container) => portsOf(container, fakes).map((port) => port.key));
432
+ return [.../* @__PURE__ */ new Set([...keys, ...Object.keys(fakes).sort()])];
414
433
  }
415
434
  /**
416
435
  * The named volume a container's data lives in.
@@ -692,7 +711,8 @@ const CONTAINERLESS = {
692
711
  credential: true,
693
712
  "rest-api": true,
694
713
  site: true,
695
- "mobile-app": true
714
+ "mobile-app": true,
715
+ "external-api": true
696
716
  };
697
717
  /**
698
718
  * Containers that cannot run alone.
@@ -712,7 +732,7 @@ const REQUIRES = { "redis-http": ["redis"] };
712
732
  const ROLES = {
713
733
  queue: "publisherConnectionString",
714
734
  topic: "publisherConnectionString",
715
- credential: "credential",
735
+ credential: "credentials",
716
736
  "mobile-app": "scheme"
717
737
  };
718
738
  /** The kinds whose container is the events backend's rather than their own. */
@@ -804,12 +824,25 @@ function containerFor(kind, events = DEFAULT_EVENTS, cache = DEFAULT_CACHE.aws,
804
824
  function planFor(manifest, stage, order, options = {}) {
805
825
  const containers = /* @__PURE__ */ new Set();
806
826
  const resources = [];
827
+ const fakes = {};
807
828
  const events = options.events ?? "pgboss";
808
829
  const cache = options.cache ?? DEFAULT_CACHE.aws;
809
830
  for (const id of order) {
810
831
  const declaration = manifest[id];
811
832
  if (!declaration) continue;
812
- const container = containerFor(declaration.kind, events, cache, "of" in declaration && typeof declaration.of === "string", options.edge !== false);
833
+ const local = declaration.kind === "external-api" ? options.fakes?.[id] : void 0;
834
+ const fake = local && {
835
+ key: fakeKey(id),
836
+ ...local
837
+ };
838
+ if (fake) fakes[fake.key] = {
839
+ id,
840
+ ...fake.image ? {
841
+ image: fake.image,
842
+ port: fake.port
843
+ } : {}
844
+ };
845
+ const container = fake?.image ? fake.key : containerFor(declaration.kind, events, cache, "of" in declaration && typeof declaration.of === "string", options.edge !== false);
813
846
  if (!container && !CONTAINERLESS[declaration.kind]) continue;
814
847
  if (container) {
815
848
  containers.add(container);
@@ -829,6 +862,11 @@ function planFor(manifest, stage, order, options = {}) {
829
862
  ...declaration.scheme ? { scheme: declaration.scheme } : {}
830
863
  } : {},
831
864
  ...declaration.kind === "file-server" && declaration.open?.length ? { open: declaration.open } : {},
865
+ ...fake ? { fake: {
866
+ key: fake.key,
867
+ credentials: fake.credentials
868
+ } } : {},
869
+ ...declaration.kind === "external-api" && !fake ? { url: declaration.url } : {},
832
870
  ..."of" in declaration ? { of: declaration.of } : {},
833
871
  ..."schema" in declaration && declaration.schema ? { schema: declaration.schema } : {},
834
872
  ..."version" in declaration && declaration.version ? { version: declaration.version } : {},
@@ -865,6 +903,7 @@ function planFor(manifest, stage, order, options = {}) {
865
903
  cache,
866
904
  containers: [...containers],
867
905
  resources,
906
+ ...Object.keys(fakes).length ? { fakes } : {},
868
907
  ...workerBroker ? { workerBroker: true } : {}
869
908
  };
870
909
  }
@@ -988,6 +1027,7 @@ function envFor(plan, options) {
988
1027
  const owner = ownerUrl(resource, plan, options.ports, options.project ?? "");
989
1028
  if (owner) env[provideKey(resource.id, "ownerUrl")] = owner;
990
1029
  }
1030
+ if (resource.kind === "external-api" && url && resource.fake) env[provideKey(resource.id, "credentials")] = resource.fake.credentials;
991
1031
  if (resource.kind === "email" && url) {
992
1032
  env[provideKey(resource.id, "from")] = options.mailFrom ?? `noreply@${LOCAL_HOST}`;
993
1033
  const inbox = options.ports[MAILPIT_INBOX_PORT];
@@ -1137,6 +1177,14 @@ function urlFor(resource, plan, ports, project, addresses = {}) {
1137
1177
  if (resource.kind === "secret") return localSecret(project, plan, resource);
1138
1178
  if (resource.kind === "mobile-app") return schemeBase(project, resource.scheme);
1139
1179
  if (resource.kind === "credential") return void 0;
1180
+ if (resource.kind === "external-api") {
1181
+ if (!resource.fake) return resource.url === void 0 ? void 0 : externalApiUrl({
1182
+ id: resource.id,
1183
+ url: resource.url
1184
+ }, plan.stage);
1185
+ const port = ports[resource.fake.key];
1186
+ return port === void 0 ? void 0 : `http://${LOCAL_HOST}:${port}`;
1187
+ }
1140
1188
  if (resource.kind === "rest-api" || resource.kind === "site") {
1141
1189
  const address = addresses[resource.id];
1142
1190
  const edge = plan.containers.includes("caddy") ? ports[primaryPortKey("caddy")] : void 0;
@@ -1337,20 +1385,21 @@ function dockerfileOf(appName, path) {
1337
1385
  * two containers listening on the same port inside cannot be confused — and is
1338
1386
  * rewritten to `<container>:<inside>`.
1339
1387
  */
1340
- function inNetworkEnv(workspace, manifest) {
1388
+ function inNetworkEnv(workspace, manifest, fakes = {}) {
1341
1389
  const plan = planFor(manifest, workspace.stages.local, provisionOrder(manifest), {
1342
1390
  localStage: workspace.stages.local,
1343
- ...backendsOf(workspace)
1391
+ ...backendsOf(workspace),
1392
+ fakes
1344
1393
  });
1345
1394
  const placeholders = {};
1346
1395
  const targets = /* @__PURE__ */ new Map();
1347
1396
  let next = 49152;
1348
- for (const container of [...plan.containers].sort()) for (const port of portsOf(container)) {
1397
+ for (const container of [...plan.containers].sort()) for (const port of portsOf(container, plan.fakes)) {
1349
1398
  placeholders[port.key] = next;
1350
1399
  targets.set(next, `${container}:${port.inside}`);
1351
1400
  next += 1;
1352
1401
  }
1353
- for (const key of portKeys(plan.containers)) placeholders[key] ??= next++;
1402
+ for (const key of portKeys(plan.containers, plan.fakes)) placeholders[key] ??= next++;
1354
1403
  const env = envFor(plan, {
1355
1404
  ports: placeholders,
1356
1405
  project: workspace.name,
@@ -1413,8 +1462,8 @@ function appEnvKeys(manifest, appName, runnables = {}) {
1413
1462
  return keys;
1414
1463
  }
1415
1464
  /** One compose service per app the workspace runs, behind the apps profile. */
1416
- function appServices(workspace, manifest, containers, runnables = {}) {
1417
- const env = inNetworkEnv(workspace, manifest);
1465
+ function appServices(workspace, manifest, containers, runnables = {}, fakes = {}) {
1466
+ const env = inNetworkEnv(workspace, manifest, fakes);
1418
1467
  const services = {};
1419
1468
  for (const [name, app] of Object.entries(workspace.apps)) {
1420
1469
  if (app.type === "mobile" || !app.port) continue;
@@ -1568,9 +1617,15 @@ function composeFor(plan, options) {
1568
1617
  const volumes = {};
1569
1618
  for (const container of [...plan.containers].sort()) {
1570
1619
  const declared = container === "postgres" ? plan.resources.find((r) => r.version)?.version : void 0;
1571
- const image = declared ? postgresImage(declared) : DEFAULT_IMAGES[container];
1620
+ const fake = plan.fakes?.[container];
1621
+ const image = declared ? postgresImage(declared) : fake?.image ?? DEFAULT_IMAGES[container];
1572
1622
  if (!image) throw new UnknownContainer(container);
1573
- services[container] = define(container, image, publish(container, options.ports));
1623
+ const published = publish(container, options.ports, plan.fakes);
1624
+ services[container] = fake ? {
1625
+ image,
1626
+ restart: "unless-stopped",
1627
+ ports: published
1628
+ } : define(container, image, published);
1574
1629
  const volume = volumeOf(container);
1575
1630
  if (volume) volumes[volume] = {};
1576
1631
  }
@@ -1789,8 +1844,8 @@ function define(container, image, published) {
1789
1844
  * from the container's first, so MinIO's console cannot land on whatever was
1790
1845
  * allocated to the next container.
1791
1846
  */
1792
- function publish(container, ports) {
1793
- return portsOf(container).map((port) => {
1847
+ function publish(container, ports, fakes) {
1848
+ return portsOf(container, fakes).map((port) => {
1794
1849
  const assigned = ports[port.key];
1795
1850
  if (assigned === void 0) throw new UnassignedPort(port.key);
1796
1851
  return `${assigned}:${port.inside}`;
@@ -2395,6 +2450,13 @@ async function saveState(root, state) {
2395
2450
  await mkdir(dirname(path), { recursive: true });
2396
2451
  await writeFile(path, `${JSON.stringify(state, null, 2)}\n`);
2397
2452
  }
2453
+ /**
2454
+ * Forget what was applied, so the next reconcile of this stage converges from
2455
+ * nothing — creating again whatever was dropped underneath the record.
2456
+ */
2457
+ async function forgetState(root, stage) {
2458
+ await rm(statePath(root, stage), { force: true });
2459
+ }
2398
2460
  function statePath(root, stage) {
2399
2461
  return join(root, STATE_DIR, `${stage}.json`);
2400
2462
  }
@@ -2469,11 +2531,12 @@ async function reconcile(options) {
2469
2531
  events: options.events,
2470
2532
  cache: options.cache,
2471
2533
  extraContainers: options.extraContainers,
2534
+ ...options.fakes ? { fakes: options.fakes } : {},
2472
2535
  ...options.edge === void 0 ? {} : { edge: options.edge }
2473
2536
  });
2474
2537
  const composePath = join(root, COMPOSE_PATH);
2475
- const observed = await observedPorts(docker, composePath, plan.containers);
2476
- const ports = await allocate(project, portKeys(plan.containers), {
2538
+ const observed = await observedPorts(docker, composePath, plan.containers, plan.fakes);
2539
+ const ports = await allocate(project, portKeys(plan.containers, plan.fakes), {
2477
2540
  ...options.saved,
2478
2541
  ...observed
2479
2542
  }, probe);
@@ -2506,6 +2569,7 @@ async function reconcile(options) {
2506
2569
  addresses,
2507
2570
  env,
2508
2571
  provisioned: [],
2572
+ fakes: options.fakes ?? {},
2509
2573
  hash,
2510
2574
  changed: false
2511
2575
  };
@@ -2561,9 +2625,9 @@ async function create(plan, ports, sql, buckets, project) {
2561
2625
  * Keyed the same way allocation keys them, so the two merge without either
2562
2626
  * knowing about the other.
2563
2627
  */
2564
- async function observedPorts(docker, composePath, containers) {
2628
+ async function observedPorts(docker, composePath, containers, fakes) {
2565
2629
  const observed = {};
2566
- for (const container of containers) for (const port of portsOf(container)) {
2630
+ for (const container of containers) for (const port of portsOf(container, fakes)) {
2567
2631
  const running = await docker.publishedPort(composePath, container, port.inside);
2568
2632
  if (running !== void 0) observed[port.key] = running;
2569
2633
  }
@@ -2619,9 +2683,11 @@ async function write(path, content) {
2619
2683
  * same state, differing only in the stage they pass.
2620
2684
  */
2621
2685
  var workspace_exports = /* @__PURE__ */ __exportAll({
2686
+ FAKE_ENV: () => FAKE_ENV,
2622
2687
  backendsOf: () => backendsOf,
2623
2688
  constructGlobs: () => constructGlobs,
2624
2689
  derivedContainers: () => derivedContainers,
2690
+ fakesApply: () => fakesApply,
2625
2691
  metroPorts: () => metroPorts,
2626
2692
  reconcileWorkspace: () => reconcileWorkspace,
2627
2693
  surfaceAddresses: () => surfaceAddresses
@@ -2664,6 +2730,19 @@ async function derivedContainers(workspace, stage, manifest) {
2664
2730
  }).containers;
2665
2731
  }
2666
2732
  /**
2733
+ * Set by `gkm dev --fake` for the apps it starts. Each app's own `gkm dev`
2734
+ * reconciles too, and must answer the way the workspace did.
2735
+ */
2736
+ const FAKE_ENV = "GKM_FAKE";
2737
+ /**
2738
+ * Whether external APIs are faked: always for `gkm test`, and for `gkm dev`
2739
+ * only when asked — by `--fake`, or by the workspace that started this app
2740
+ * with it. Never for anything acting on a deployed stage.
2741
+ */
2742
+ function fakesApply(stage, fake, env = process.env) {
2743
+ return fake === true || stage === "test" || env["GKM_FAKE"] === "1";
2744
+ }
2745
+ /**
2667
2746
  * Reconcile a workspace for one stage.
2668
2747
  *
2669
2748
  * Ports are loaded and saved around the call rather than inside it, so the loop
@@ -2677,6 +2756,7 @@ async function reconcileWorkspace(workspace, options) {
2677
2756
  runnables
2678
2757
  });
2679
2758
  const manifest = options.manifest ?? discovered;
2759
+ const fakes = fakesApply(options.stage, options.fake) ? await readFakes(workspace.root, manifest) : {};
2680
2760
  const result = await reconcile({
2681
2761
  root: workspace.root,
2682
2762
  project: workspace.name,
@@ -2687,7 +2767,8 @@ async function reconcileWorkspace(workspace, options) {
2687
2767
  saved: await loadPortState(workspace.root),
2688
2768
  addresses: surfaceAddresses(workspace, manifest),
2689
2769
  metroPorts: metroPorts(workspace, manifest),
2690
- apps: (containers) => appServices(workspace, manifest, containers, runnables),
2770
+ fakes,
2771
+ apps: (containers) => appServices(workspace, manifest, containers, runnables, fakes),
2691
2772
  ...options.start === void 0 ? {} : { start: options.start }
2692
2773
  });
2693
2774
  await savePortState(workspace.root, { ...result.ports });
@@ -2738,6 +2819,6 @@ function localAddress(port) {
2738
2819
  return `http://localhost:${port}`;
2739
2820
  }
2740
2821
  //#endregion
2741
- export { findAvailablePort as _, workspace_exports as a, loadSecretsForApp as b, applyPostgres as c, cacheBackendFor as d, eventsBackendFor as f, createEntryWrapper as g, createCredentialsPreload as h, reconcileWorkspace as i, resourceName as l, storageBackendFor as m, constructGlobs as n, COMPOSE_PATH as o, providerOf as p, derivedContainers as r, LOCAL_CA_PATH as s, backendsOf as t, rootSite as u, isPortAvailable as v, prepareEntryCredentials as x, loadEnvFiles as y };
2822
+ export { findAvailablePort as C, prepareEntryCredentials as D, loadSecretsForApp as E, createEntryWrapper as S, loadEnvFiles as T, cacheBackendFor as _, reconcileWorkspace as a, storageBackendFor as b, LOCAL_CA_PATH as c, quoteIdentifier as d, pgClient as f, primaryPortKey as g, fakeKey as h, derivedContainers as i, forgetState as l, rootSite as m, backendsOf as n, workspace_exports as o, resourceName as p, constructGlobs as r, COMPOSE_PATH as s, FAKE_ENV as t, applyPostgres as u, eventsBackendFor as v, isPortAvailable as w, createCredentialsPreload as x, providerOf as y };
2742
2823
 
2743
- //# sourceMappingURL=workspace-EmXwO2qM.mjs.map
2824
+ //# sourceMappingURL=workspace-Lm6dv2nz.mjs.map