@geekmidas/cli 10.0.0-alpha.34 → 10.0.0-alpha.35

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 (29) hide show
  1. package/dist/{config-DMpzmsQL.cjs → config-DgAHy8nY.cjs} +9 -5
  2. package/dist/config-DgAHy8nY.cjs.map +1 -0
  3. package/dist/{config-Du8AQJRK.mjs → config-Dss1WwtH.mjs} +9 -5
  4. package/dist/config-Dss1WwtH.mjs.map +1 -0
  5. package/dist/config.cjs +1 -1
  6. package/dist/config.mjs +1 -1
  7. package/dist/{github-DN-92LWP.mjs → github-C99BMxf0.mjs} +2 -2
  8. package/dist/{github-DN-92LWP.mjs.map → github-C99BMxf0.mjs.map} +1 -1
  9. package/dist/{github-JhvrmjQp.cjs → github-tZmMZ7td.cjs} +2 -2
  10. package/dist/{github-JhvrmjQp.cjs.map → github-tZmMZ7td.cjs.map} +1 -1
  11. package/dist/index.cjs +205 -85
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.mjs +206 -86
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/{migrate-BbhXxQer.mjs → migrate-C-D9xIbb.mjs} +3 -3
  16. package/dist/{migrate-BbhXxQer.mjs.map → migrate-C-D9xIbb.mjs.map} +1 -1
  17. package/dist/{migrate-u1oRHz6K.cjs → migrate-nlvT-GnY.cjs} +3 -3
  18. package/dist/{migrate-u1oRHz6K.cjs.map → migrate-nlvT-GnY.cjs.map} +1 -1
  19. package/dist/openapi.cjs +1 -1
  20. package/dist/openapi.mjs +1 -1
  21. package/dist/{workspace-DB2YHgOl.mjs → workspace-CIiGijNi.mjs} +90 -16
  22. package/dist/workspace-CIiGijNi.mjs.map +1 -0
  23. package/dist/{workspace-LKFnEBX8.cjs → workspace-CRjZkCTW.cjs} +89 -15
  24. package/dist/workspace-CRjZkCTW.cjs.map +1 -0
  25. package/package.json +14 -14
  26. package/dist/config-DMpzmsQL.cjs.map +0 -1
  27. package/dist/config-Du8AQJRK.mjs.map +0 -1
  28. package/dist/workspace-DB2YHgOl.mjs.map +0 -1
  29. package/dist/workspace-LKFnEBX8.cjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -2,10 +2,10 @@
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-CroTIm0K.cjs");
5
- const require_config = require("./config-DMpzmsQL.cjs");
5
+ const require_config = require("./config-DgAHy8nY.cjs");
6
6
  const require_workspace = require("./workspace-CAJk6UmC.cjs");
7
7
  const require_storage = require("./storage-C2JC31V9.cjs");
8
- const require_workspace$1 = require("./workspace-LKFnEBX8.cjs");
8
+ const require_workspace$1 = require("./workspace-CRjZkCTW.cjs");
9
9
  const require_OpenApiTsGenerator = require("./OpenApiTsGenerator-D1nhN98m.cjs");
10
10
  const require_openapi = require("./openapi.cjs");
11
11
  const require_ready = require("./ready-CXhoutix.cjs");
@@ -42,7 +42,7 @@ let _geekmidas_constructs_subscribers = require("@geekmidas/constructs/subscribe
42
42
  let _geekmidas_constructs_topic = require("@geekmidas/constructs/topic");
43
43
  let node_dns_promises = require("node:dns/promises");
44
44
  //#region package.json
45
- var version = "10.0.0-alpha.34";
45
+ var version = "10.0.0-alpha.35";
46
46
  //#endregion
47
47
  //#region src/auth/credentials.ts
48
48
  /**
@@ -1092,9 +1092,9 @@ async function workspaceDevCommand(workspace, options) {
1092
1092
  ...reconciled.env
1093
1093
  };
1094
1094
  if (options.migrate || options.seed) {
1095
- const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-u1oRHz6K.cjs"));
1095
+ const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-nlvT-GnY.cjs"));
1096
1096
  await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
1097
- } else await Promise.resolve().then(() => require("./migrate-u1oRHz6K.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1097
+ } else await Promise.resolve().then(() => require("./migrate-nlvT-GnY.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1098
1098
  logger$10.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
1099
1099
  });
1100
1100
  const appUrls = {};
@@ -1124,6 +1124,13 @@ async function workspaceDevCommand(workspace, options) {
1124
1124
  for (const appName of buildOrder) {
1125
1125
  const app = workspace.apps[appName];
1126
1126
  if (!app) continue;
1127
+ if (app.type === "mobile") {
1128
+ const own = reconciled.plan.resources.find((r) => r.kind === "mobile-app" && require_config.appKey(r.id) === appName);
1129
+ const scheme = own ? reconciled.env[own.envKey] : void 0;
1130
+ const device = Object.entries(reconciled.env).find(([key]) => key.endsWith("_DEVICE_URL"))?.[1];
1131
+ logger$10.log(` ${appName.padEnd(width)} ${scheme ? `${scheme}://` : "mobile"}${device ? ` — devices reach ${new URL(device).hostname}` : ""}`);
1132
+ continue;
1133
+ }
1127
1134
  const local = `http://localhost:${app.port}`;
1128
1135
  const url = Object.entries(appUrls).find(([id]) => require_config.appKey(id) === appName)?.[1];
1129
1136
  logger$10.log(` ${appName.padEnd(width)} ${url ? `${url} -> ${local}` : local}`);
@@ -3318,9 +3325,13 @@ const PROVISIONERS = {
3318
3325
  if (!url) throw new SurfaceHasNoAddress(declaration.id);
3319
3326
  const origins = [...new Set((0, _geekmidas_manifest.dependentsOf)(context.manifest, declaration.id).map((caller) => context.addresses?.[caller]).filter((address) => Boolean(address)))].sort();
3320
3327
  const domain = (0, _geekmidas_manifest.cookieDomain)([url, ...origins]);
3328
+ const schemes = (0, _geekmidas_manifest.dependentsOf)(context.manifest, declaration.id).flatMap((caller) => {
3329
+ const app = context.manifest[caller];
3330
+ return app?.kind === "mobile-app" ? (0, _geekmidas_manifest.mobileOrigins)((0, _geekmidas_manifest.schemeBase)(context.project, app.scheme)) : [];
3331
+ });
3321
3332
  return { provides: {
3322
3333
  [(0, _geekmidas_manifest.provideKey)(declaration.id, "url")]: url,
3323
- [(0, _geekmidas_manifest.provideKey)(declaration.id, "trustedOrigins")]: origins.join(","),
3334
+ [(0, _geekmidas_manifest.provideKey)(declaration.id, "trustedOrigins")]: [...origins, ...schemes].join(","),
3324
3335
  ...domain ? { [(0, _geekmidas_manifest.provideKey)(declaration.id, "cookieDomain")]: domain } : {}
3325
3336
  } };
3326
3337
  }
@@ -8011,11 +8022,11 @@ function routesGlob(structure) {
8011
8022
  }
8012
8023
  }
8013
8024
  /**
8014
- * Plain ids — `Database`, not `Beetlefit`.
8025
+ * Plain ids — `Database`, not `Shop`.
8015
8026
  *
8016
8027
  * The workspace `name` already scopes every physical name
8017
- * (`production-beetlefit-database`), so an id that carried the project too
8018
- * would say it twice: `production-beetlefit-beetlefit-cache`. Within a
8028
+ * (`production-shop-database`), so an id that carried the project too
8029
+ * would say it twice: `production-shop-shop-cache`. Within a
8019
8030
  * workspace the constructs are declared once, at its root, so there is no
8020
8031
  * second app's `Database` to collide with.
8021
8032
  */
@@ -8741,30 +8752,32 @@ const TOOLCHAIN_VERSIONS = {
8741
8752
  "@storybook/react-vite": "^10.6.0"
8742
8753
  };
8743
8754
  /**
8744
- * The Expo app's packages, which track an Expo SDK release (55) rather than
8745
- * their own latest: SDK 55 is tested against exactly these, React Native and
8755
+ * The Expo app's packages, which track an Expo SDK release (57) rather than
8756
+ * their own latest: SDK 57 is tested against exactly these, React Native and
8746
8757
  * React included, so they move when the SDK does.
8747
8758
  */
8748
8759
  const EXPO_VERSIONS = {
8749
- expo: "~55.0.0",
8750
- "expo-constants": "~55.0.0",
8751
- "expo-dev-client": "~55.0.0",
8752
- "expo-linking": "~55.0.0",
8753
- "expo-router": "~55.0.0",
8754
- "expo-splash-screen": "~55.0.0",
8755
- "expo-status-bar": "~55.0.0",
8756
- "expo-secure-store": "~14.2.0",
8757
- react: "19.2.0",
8758
- "react-dom": "19.2.0",
8759
- "react-native": "0.83.6",
8760
- "react-native-gesture-handler": "~2.30.0",
8761
- "react-native-reanimated": "~4.2.0",
8762
- "react-native-safe-area-context": "~5.6.0",
8763
- "react-native-screens": "~4.23.0",
8760
+ expo: "~57.0.26",
8761
+ "expo-constants": "~57.0.20",
8762
+ "expo-dev-client": "~57.0.19",
8763
+ "expo-device": "~57.0.2",
8764
+ "expo-linking": "~57.0.11",
8765
+ "expo-router": "~57.0.24",
8766
+ "expo-splash-screen": "~57.0.9",
8767
+ "expo-status-bar": "~57.0.1",
8768
+ "expo-secure-store": "~57.0.4",
8769
+ react: "19.2.3",
8770
+ "react-dom": "19.2.3",
8771
+ "react-native": "0.86.3",
8772
+ "react-native-gesture-handler": "~2.32.0",
8773
+ "react-native-reanimated": "4.5.1",
8774
+ "react-native-worklets": "0.10.1",
8775
+ "react-native-safe-area-context": "~5.7.0",
8776
+ "react-native-screens": "~4.26.0",
8764
8777
  "react-native-web": "~0.21.0",
8765
8778
  "@react-navigation/native": "^7.1.0",
8766
8779
  "@babel/core": "^7.25.0",
8767
- "@types/react": "~19.0.0",
8780
+ "@types/react": "~19.2.2",
8768
8781
  nativewind: "~4.2.0",
8769
8782
  tailwindcss: "~3.4.0"
8770
8783
  };
@@ -8796,26 +8809,26 @@ function loadPackageJson() {
8796
8809
  * Run: pnpm --filter @geekmidas/cli sync-versions
8797
8810
  */
8798
8811
  const GEEKMIDAS_VERSIONS = {
8799
- "@geekmidas/audit": "~10.0.0-alpha.34",
8800
- "@geekmidas/auth": "~10.0.0-alpha.34",
8801
- "@geekmidas/cache": "~10.0.0-alpha.34",
8802
- "@geekmidas/client": "~10.0.0-alpha.34",
8803
- "@geekmidas/cloud": "~10.0.0-alpha.34",
8804
- "@geekmidas/constructs": "~10.0.0-alpha.34",
8805
- "@geekmidas/db": "~10.0.0-alpha.34",
8806
- "@geekmidas/emailkit": "~10.0.0-alpha.34",
8807
- "@geekmidas/envkit": "~10.0.0-alpha.34",
8808
- "@geekmidas/errors": "~10.0.0-alpha.34",
8809
- "@geekmidas/events": "~10.0.0-alpha.34",
8810
- "@geekmidas/logger": "~10.0.0-alpha.34",
8811
- "@geekmidas/manifest": "~10.0.0-alpha.34",
8812
- "@geekmidas/rate-limit": "~10.0.0-alpha.34",
8813
- "@geekmidas/schema": "~10.0.0-alpha.34",
8814
- "@geekmidas/services": "~10.0.0-alpha.34",
8815
- "@geekmidas/storage": "~10.0.0-alpha.34",
8816
- "@geekmidas/studio": "~10.0.0-alpha.34",
8817
- "@geekmidas/telescope": "~10.0.0-alpha.34",
8818
- "@geekmidas/testkit": "~10.0.0-alpha.34",
8812
+ "@geekmidas/audit": "~10.0.0-alpha.35",
8813
+ "@geekmidas/auth": "~10.0.0-alpha.35",
8814
+ "@geekmidas/cache": "~10.0.0-alpha.35",
8815
+ "@geekmidas/client": "~10.0.0-alpha.35",
8816
+ "@geekmidas/cloud": "~10.0.0-alpha.35",
8817
+ "@geekmidas/constructs": "~10.0.0-alpha.35",
8818
+ "@geekmidas/db": "~10.0.0-alpha.35",
8819
+ "@geekmidas/emailkit": "~10.0.0-alpha.35",
8820
+ "@geekmidas/envkit": "~10.0.0-alpha.35",
8821
+ "@geekmidas/errors": "~10.0.0-alpha.35",
8822
+ "@geekmidas/events": "~10.0.0-alpha.35",
8823
+ "@geekmidas/logger": "~10.0.0-alpha.35",
8824
+ "@geekmidas/manifest": "~10.0.0-alpha.35",
8825
+ "@geekmidas/rate-limit": "~10.0.0-alpha.35",
8826
+ "@geekmidas/schema": "~10.0.0-alpha.35",
8827
+ "@geekmidas/services": "~10.0.0-alpha.35",
8828
+ "@geekmidas/storage": "~10.0.0-alpha.35",
8829
+ "@geekmidas/studio": "~10.0.0-alpha.35",
8830
+ "@geekmidas/telescope": "~10.0.0-alpha.35",
8831
+ "@geekmidas/testkit": "~10.0.0-alpha.35",
8819
8832
  "@geekmidas/cli": `~${loadPackageJson().version}`
8820
8833
  };
8821
8834
  //#endregion
@@ -9537,8 +9550,7 @@ function generateExpoAppFiles(options) {
9537
9550
  const apiPackage = `@${options.name}/api`;
9538
9551
  const modelsPackage = `@${options.name}/models`;
9539
9552
  const slug = options.name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
9540
- const scheme = slug.replace(/-/g, "");
9541
- const bundleId = `com.${scheme}.app`;
9553
+ const scheme = (0, _geekmidas_manifest.schemeBase)(options.name);
9542
9554
  const packageJson = {
9543
9555
  name: packageName,
9544
9556
  version: "0.0.1",
@@ -9563,6 +9575,7 @@ function generateExpoAppFiles(options) {
9563
9575
  expo: EXPO_VERSIONS["expo"],
9564
9576
  "expo-constants": EXPO_VERSIONS["expo-constants"],
9565
9577
  "expo-dev-client": EXPO_VERSIONS["expo-dev-client"],
9578
+ "expo-device": EXPO_VERSIONS["expo-device"],
9566
9579
  "expo-linking": EXPO_VERSIONS["expo-linking"],
9567
9580
  "expo-router": EXPO_VERSIONS["expo-router"],
9568
9581
  "expo-secure-store": EXPO_VERSIONS["expo-secure-store"],
@@ -9574,6 +9587,7 @@ function generateExpoAppFiles(options) {
9574
9587
  "react-native": EXPO_VERSIONS["react-native"],
9575
9588
  "react-native-gesture-handler": EXPO_VERSIONS["react-native-gesture-handler"],
9576
9589
  "react-native-reanimated": EXPO_VERSIONS["react-native-reanimated"],
9590
+ "react-native-worklets": EXPO_VERSIONS["react-native-worklets"],
9577
9591
  "react-native-safe-area-context": EXPO_VERSIONS["react-native-safe-area-context"],
9578
9592
  "react-native-screens": EXPO_VERSIONS["react-native-screens"],
9579
9593
  "react-native-web": EXPO_VERSIONS["react-native-web"],
@@ -9586,27 +9600,53 @@ function generateExpoAppFiles(options) {
9586
9600
  }
9587
9601
  };
9588
9602
  const appConfig = `import type { ConfigContext, ExpoConfig } from '@expo/config';
9603
+ import { EnvironmentParser } from '@geekmidas/envkit';
9589
9604
 
9590
9605
  /**
9591
- * Expo runtime config. Public env vars (\`EXPO_PUBLIC_*\`) are inlined
9592
- * at build time; everything else is read at runtime from \`extra\`.
9606
+ * What the app is built with, from the environment \`gkm\` injects — the
9607
+ * \`MobileApp\` construct's \`.dependsOn([api, auth])\` is where every value
9608
+ * here comes from, so none of it is written down.
9609
+ *
9610
+ * - \`APP_SCHEME\`: the URL scheme for this stage — \`${scheme}\` deployed,
9611
+ * \`${scheme}-dev\` locally, so a development build and the store build on
9612
+ * one phone never answer each other's links.
9613
+ * - \`EXPO_PUBLIC_API_URL\` / \`EXPO_PUBLIC_AUTH_URL\`: where the servers
9614
+ * answer. Locally that is their own port on \`localhost\`, which
9615
+ * \`config.ts\` points at the machine Metro was served from.
9616
+ *
9617
+ * Read at runtime from \`Constants.expoConfig.extra.config\` — see \`config.ts\`.
9593
9618
  */
9594
- export default function config(_context: ConfigContext): ExpoConfig {
9619
+ const config = new EnvironmentParser({ ...process.env })
9620
+ .create((get) => ({
9621
+ scheme: get('APP_SCHEME').string(),
9622
+ apiUrl: get('EXPO_PUBLIC_API_URL').string(),
9623
+ authUrl: get('EXPO_PUBLIC_AUTH_URL').string(),
9624
+ }))
9625
+ .parse();
9626
+
9627
+ export type Config = typeof config;
9628
+
9629
+ // A bundle id and an Android package allow no hyphens; one per scheme, so
9630
+ // each stage installs beside the others.
9631
+ const bundleId = \`com.\${config.scheme.replace(/[^a-z0-9]/g, '')}.app\`;
9632
+
9633
+ export default function expoConfig({ config: base }: ConfigContext): ExpoConfig {
9595
9634
  return {
9635
+ ...base,
9596
9636
  name: '${options.name}',
9597
9637
  slug: '${slug}',
9598
9638
  version: '0.0.1',
9599
9639
  orientation: 'portrait',
9600
9640
  icon: './assets/icon.png',
9601
- scheme: '${scheme}',
9641
+ scheme: config.scheme,
9602
9642
  userInterfaceStyle: 'automatic',
9603
9643
  newArchEnabled: true,
9604
9644
  ios: {
9605
9645
  supportsTablet: true,
9606
- bundleIdentifier: '${bundleId}',
9646
+ bundleIdentifier: bundleId,
9607
9647
  },
9608
9648
  android: {
9609
- package: '${bundleId}',
9649
+ package: bundleId,
9610
9650
  adaptiveIcon: {
9611
9651
  foregroundImage: './assets/adaptive-icon.png',
9612
9652
  backgroundColor: '#ffffff',
@@ -9632,6 +9672,7 @@ export default function config(_context: ConfigContext): ExpoConfig {
9632
9672
  experiments: {
9633
9673
  typedRoutes: true,
9634
9674
  },
9675
+ extra: { ...base.extra, config },
9635
9676
  };
9636
9677
  }
9637
9678
  `;
@@ -9644,16 +9685,13 @@ export default function config(_context: ConfigContext): ExpoConfig {
9644
9685
  dev: {
9645
9686
  developmentClient: true,
9646
9687
  distribution: "internal",
9647
- environment: "development",
9648
- env: {
9649
- EXPO_PUBLIC_API_URL: "http://localhost:3000",
9650
- EXPO_PUBLIC_AUTH_URL: "http://localhost:3002"
9651
- }
9688
+ environment: "development"
9652
9689
  },
9653
9690
  preview: {
9654
9691
  distribution: "internal",
9655
9692
  environment: "preview",
9656
9693
  env: {
9694
+ APP_SCHEME: scheme,
9657
9695
  EXPO_PUBLIC_API_URL: "https://api.example.com",
9658
9696
  EXPO_PUBLIC_AUTH_URL: "https://auth.example.com"
9659
9697
  }
@@ -9662,6 +9700,7 @@ export default function config(_context: ConfigContext): ExpoConfig {
9662
9700
  autoIncrement: true,
9663
9701
  environment: "production",
9664
9702
  env: {
9703
+ APP_SCHEME: scheme,
9665
9704
  EXPO_PUBLIC_API_URL: "https://api.example.com",
9666
9705
  EXPO_PUBLIC_AUTH_URL: "https://auth.example.com"
9667
9706
  }
@@ -9723,23 +9762,49 @@ export default {
9723
9762
  `;
9724
9763
  const expoEnvDts = `/// <reference types="expo/types" />\n`;
9725
9764
  const nativewindEnvDts = `/// <reference types="nativewind/types" />\n`;
9726
- const configTs = `import { EnvironmentParser } from '@geekmidas/envkit';
9765
+ const configTs = `import Constants from 'expo-constants';
9766
+ import * as Device from 'expo-device';
9767
+ import { Platform } from 'react-native';
9768
+
9769
+ import type { Config } from './app.config.ts';
9770
+
9771
+ const built = Constants.expoConfig?.extra?.config as Config;
9772
+
9773
+ /** The host this device loaded the bundle from: Metro's, as the device sees it. */
9774
+ const metro = (Constants.expoConfig?.hostUri ?? '').split(':')[0];
9727
9775
 
9728
9776
  /**
9729
- * Public app config. EXPO_PUBLIC_* vars are inlined into the bundle at
9730
- * build time, so they're safe to read in client code.
9777
+ * A private address on the local network — what a phone on the same Wi-Fi
9778
+ * reaches this machine on. A tunnel's \`*.exp.direct\` host is not one: a
9779
+ * tunnel forwards only Metro, so the servers are not behind it.
9731
9780
  */
9732
- const envParser = new EnvironmentParser({
9733
- EXPO_PUBLIC_API_URL: process.env.EXPO_PUBLIC_API_URL,
9734
- EXPO_PUBLIC_AUTH_URL: process.env.EXPO_PUBLIC_AUTH_URL,
9735
- });
9781
+ const isLan = (host: string) =>
9782
+ /^10\\./.test(host) ||
9783
+ /^192\\.168\\./.test(host) ||
9784
+ /^172\\.(1[6-9]|2\\d|3[01])\\./.test(host);
9736
9785
 
9737
- export const config = envParser
9738
- .create((get) => ({
9739
- apiUrl: get('EXPO_PUBLIC_API_URL').string(),
9740
- authUrl: get('EXPO_PUBLIC_AUTH_URL').string(),
9741
- }))
9742
- .parse();
9786
+ /** \`localhost\` from inside the Android emulator is the emulator itself. */
9787
+ const isAndroidEmulator = Platform.OS === 'android' && !Device.isDevice;
9788
+
9789
+ /**
9790
+ * A server's URL as this device reaches it.
9791
+ *
9792
+ * Locally the servers are injected as \`http://localhost:<port>\`. That is this
9793
+ * device only on the iOS simulator. The Android emulator reaches this machine
9794
+ * at \`10.0.2.2\`, and a phone at the LAN address Metro was served from.
9795
+ * Deployed URLs have no \`localhost\` in them and pass through untouched.
9796
+ */
9797
+ export function reachable(url: string): string {
9798
+ const host = isLan(metro) ? metro : isAndroidEmulator ? '10.0.2.2' : undefined;
9799
+ if (!host) return url;
9800
+ return url.replace(/\\/\\/(localhost|127\\.0\\.0\\.1)(?=[:/]|$)/, \`//\${host}\`);
9801
+ }
9802
+
9803
+ export const config = {
9804
+ scheme: built.scheme,
9805
+ apiUrl: reachable(built.apiUrl),
9806
+ authUrl: reachable(built.authUrl),
9807
+ };
9743
9808
  `;
9744
9809
  const queryClientTs = `import { QueryClient } from '@tanstack/react-query';
9745
9810
 
@@ -9751,21 +9816,23 @@ export const queryClient = new QueryClient({
9751
9816
  },
9752
9817
  });
9753
9818
  `;
9754
- const authClientTs = `import { expoClient } from '@better-auth/expo/client';
9819
+ const authClientTs = `import { expoClient, getSetCookie } from '@better-auth/expo/client';
9755
9820
  import { magicLinkClient } from 'better-auth/client/plugins';
9756
9821
  import { createAuthClient } from 'better-auth/react';
9822
+ import * as Linking from 'expo-linking';
9757
9823
  import * as SecureStore from 'expo-secure-store';
9824
+ import { useEffect } from 'react';
9758
9825
 
9759
9826
  import { config } from '@/config.ts';
9760
9827
 
9761
- export const STORAGE_PREFIX = '${scheme}';
9828
+ export const STORAGE_PREFIX = config.scheme;
9762
9829
  export const COOKIE_STORE_KEY = \`\${STORAGE_PREFIX}_cookie\`;
9763
9830
 
9764
9831
  export const authClient = createAuthClient({
9765
9832
  baseURL: config.authUrl,
9766
9833
  plugins: [
9767
9834
  expoClient({
9768
- scheme: '${scheme}',
9835
+ scheme: config.scheme,
9769
9836
  storagePrefix: STORAGE_PREFIX,
9770
9837
  storage: SecureStore,
9771
9838
  }),
@@ -9775,6 +9842,36 @@ export const authClient = createAuthClient({
9775
9842
 
9776
9843
  export const { signIn, useSession } = authClient;
9777
9844
 
9845
+ /**
9846
+ * Sign in from the emailed link.
9847
+ *
9848
+ * The link opens in the phone's browser, which is what receives the session
9849
+ * cookie — not the app. The auth server's Expo plugin carries the cookie on the
9850
+ * redirect back into the app (\`?cookie=\`), and this stores it where the
9851
+ * Expo client keeps its own, merged with what is already there, then tells the
9852
+ * client the session changed. The Expo client does this itself only for a
9853
+ * sign-in it opened (social); a link opened from mail arrives as a deep link.
9854
+ *
9855
+ * Called once, from the root layout.
9856
+ */
9857
+ export function useSessionFromLink() {
9858
+ const url = Linking.useLinkingURL();
9859
+
9860
+ useEffect(() => {
9861
+ const cookie = url ? Linking.parse(url).queryParams?.cookie : undefined;
9862
+ if (typeof cookie !== 'string') return;
9863
+
9864
+ void (async () => {
9865
+ const previous = await SecureStore.getItemAsync(COOKIE_STORE_KEY);
9866
+ await SecureStore.setItemAsync(
9867
+ COOKIE_STORE_KEY,
9868
+ getSetCookie(cookie, previous ?? undefined),
9869
+ );
9870
+ authClient.$store.notify('$sessionSignal');
9871
+ })();
9872
+ }, [url]);
9873
+ }
9874
+
9778
9875
  export async function signOut() {
9779
9876
  try {
9780
9877
  await authClient.signOut();
@@ -9841,7 +9938,7 @@ import { StatusBar } from 'expo-status-bar';
9841
9938
  import { useEffect, useMemo } from 'react';
9842
9939
  import 'react-native-reanimated';
9843
9940
 
9844
- import { authClient } from '@/lib/auth-client.ts';
9941
+ import { authClient, useSessionFromLink } from '@/lib/auth-client.ts';
9845
9942
  import { ApiProvider } from '@/lib/api-context.tsx';
9846
9943
  import { createAppApi } from '@/lib/api.ts';
9847
9944
  import { queryClient } from '@/lib/query-client.ts';
@@ -9866,6 +9963,8 @@ function AuthGate({ children }: { children: React.ReactNode }) {
9866
9963
 
9867
9964
  export default function RootLayout() {
9868
9965
  const api = useMemo(() => createAppApi(), []);
9966
+ // The emailed sign-in link comes back as a deep link carrying the session.
9967
+ useSessionFromLink();
9869
9968
 
9870
9969
  return (
9871
9970
  <QueryClientProvider client={queryClient}>
@@ -10387,7 +10486,8 @@ function rootConstructDependencies(options) {
10387
10486
  pg: DEPENDENCY_VERSIONS.pg,
10388
10487
  ...options.constructs.uploads ? { "@geekmidas/storage": v["@geekmidas/storage"] } : {},
10389
10488
  ...options.constructs.mail ? { "@geekmidas/emailkit": v["@geekmidas/emailkit"] } : {},
10390
- ...options.constructs.cache ? { "@geekmidas/cache": v["@geekmidas/cache"] } : {}
10489
+ ...options.constructs.cache ? { "@geekmidas/cache": v["@geekmidas/cache"] } : {},
10490
+ ...options.frontendFramework === "expo" ? { "@better-auth/expo": DEPENDENCY_VERSIONS["better-auth"] } : {}
10391
10491
  };
10392
10492
  }
10393
10493
  /**
@@ -10864,6 +10964,26 @@ export const api = new RestApi('Api', {
10864
10964
  `
10865
10965
  });
10866
10966
  const variant = frontendFramework === "tanstack-start" ? "{ path: 'apps/web', variant: 'tanstack' }" : frontendFramework === "expo" ? void 0 : "{ path: 'apps/web', variant: 'next' }";
10967
+ if (frontendFramework === "expo") files.push({
10968
+ path: "constructs/app.ts",
10969
+ content: `import { MobileApp } from '@geekmidas/constructs/mobile-app';
10970
+ import { api } from './api.ts';
10971
+ import { auth } from './auth.ts';
10972
+
10973
+ /**
10974
+ * The mobile app — a construct like a site, which is what makes it an app.
10975
+ *
10976
+ * \`.dependsOn()\` is the single fact behind what a mobile app otherwise has
10977
+ * written down by hand: its API and auth URLs, its URL scheme per stage, the
10978
+ * scheme in the auth server's trusted origins, Better Auth's Expo plugin, and
10979
+ * sign-in links a phone can open.
10980
+ */
10981
+ export const app = new MobileApp('App', { path: 'apps/app' }).dependsOn([
10982
+ api,
10983
+ auth,
10984
+ ]);
10985
+ `
10986
+ });
10867
10987
  if (variant !== void 0) files.push({
10868
10988
  path: "constructs/site.ts",
10869
10989
  content: `import { StaticSite } from '@geekmidas/constructs/site';
@@ -15038,7 +15158,7 @@ var NoStoredSecrets = class extends Error {
15038
15158
  }
15039
15159
  };
15040
15160
  async function reconcile(secrets, workspace, stage) {
15041
- const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-LKFnEBX8.cjs")).then((n) => n.workspace_exports);
15161
+ const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-CRjZkCTW.cjs")).then((n) => n.workspace_exports);
15042
15162
  const result = require_reconcile.reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
15043
15163
  return result ? {
15044
15164
  secrets: result.secrets,
@@ -15437,7 +15557,7 @@ async function testCommand(options = {}) {
15437
15557
  * starts without the reference data the app needs.
15438
15558
  */
15439
15559
  async function migrateTestStage(workspace, env) {
15440
- const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-u1oRHz6K.cjs"));
15560
+ const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-nlvT-GnY.cjs"));
15441
15561
  const { discover } = await Promise.resolve().then(() => require("./discover-CroTIm0K.cjs")).then((n) => n.discover_exports);
15442
15562
  const sources = {};
15443
15563
  const manifest = await discover({
@@ -15882,7 +16002,7 @@ program.command("migrate").description("Apply each database construct's migratio
15882
16002
  try {
15883
16003
  const globalOptions = program.opts();
15884
16004
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
15885
- const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-u1oRHz6K.cjs"));
16005
+ const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-nlvT-GnY.cjs"));
15886
16006
  await migrateCommand({
15887
16007
  ...options,
15888
16008
  ...construct ? { construct } : {}
@@ -15896,7 +16016,7 @@ program.command("seed").description("Migrate, then run each database construct's
15896
16016
  try {
15897
16017
  const globalOptions = program.opts();
15898
16018
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
15899
- const { seedCommand } = await Promise.resolve().then(() => require("./migrate-u1oRHz6K.cjs"));
16019
+ const { seedCommand } = await Promise.resolve().then(() => require("./migrate-nlvT-GnY.cjs"));
15900
16020
  await seedCommand({
15901
16021
  ...options,
15902
16022
  ...construct ? { construct } : {}
@@ -15910,7 +16030,7 @@ program.command("migration").description("Write a construct's next migration: an
15910
16030
  try {
15911
16031
  const globalOptions = program.opts();
15912
16032
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
15913
- const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-u1oRHz6K.cjs"));
16033
+ const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-nlvT-GnY.cjs"));
15914
16034
  await migrationCommand({
15915
16035
  construct,
15916
16036
  ...name ? { name } : {}
@@ -16078,7 +16198,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
16078
16198
  console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
16079
16199
  process.exit(1);
16080
16200
  }
16081
- const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-LKFnEBX8.cjs")).then((n) => n.workspace_exports);
16201
+ const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-CRjZkCTW.cjs")).then((n) => n.workspace_exports);
16082
16202
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
16083
16203
  if (!result) {
16084
16204
  console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
@@ -16137,7 +16257,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
16137
16257
  try {
16138
16258
  const globalOptions = program.opts();
16139
16259
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16140
- const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-JhvrmjQp.cjs"));
16260
+ const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-tZmMZ7td.cjs"));
16141
16261
  await deployGithubCommand(options);
16142
16262
  } catch (error) {
16143
16263
  console.error(formatError(error));