@geekmidas/cli 10.0.0-alpha.34 → 10.0.0-alpha.36
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.
- package/dist/{config-DMpzmsQL.cjs → config-DgAHy8nY.cjs} +9 -5
- package/dist/config-DgAHy8nY.cjs.map +1 -0
- package/dist/{config-Du8AQJRK.mjs → config-Dss1WwtH.mjs} +9 -5
- package/dist/config-Dss1WwtH.mjs.map +1 -0
- package/dist/config.cjs +1 -1
- package/dist/config.mjs +1 -1
- package/dist/{github-DN-92LWP.mjs → github-C99BMxf0.mjs} +2 -2
- package/dist/{github-DN-92LWP.mjs.map → github-C99BMxf0.mjs.map} +1 -1
- package/dist/{github-JhvrmjQp.cjs → github-tZmMZ7td.cjs} +2 -2
- package/dist/{github-JhvrmjQp.cjs.map → github-tZmMZ7td.cjs.map} +1 -1
- package/dist/index.cjs +209 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +210 -88
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-u1oRHz6K.cjs → migrate-BsW4xBEO.cjs} +3 -3
- package/dist/{migrate-u1oRHz6K.cjs.map → migrate-BsW4xBEO.cjs.map} +1 -1
- package/dist/{migrate-BbhXxQer.mjs → migrate-CT8b3nt_.mjs} +3 -3
- package/dist/{migrate-BbhXxQer.mjs.map → migrate-CT8b3nt_.mjs.map} +1 -1
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.mjs +1 -1
- package/dist/{workspace-LKFnEBX8.cjs → workspace-C0X8xSXV.cjs} +89 -15
- package/dist/workspace-C0X8xSXV.cjs.map +1 -0
- package/dist/{workspace-DB2YHgOl.mjs → workspace-EmXwO2qM.mjs} +90 -16
- package/dist/workspace-EmXwO2qM.mjs.map +1 -0
- package/package.json +14 -14
- package/dist/config-DMpzmsQL.cjs.map +0 -1
- package/dist/config-Du8AQJRK.mjs.map +0 -1
- package/dist/workspace-DB2YHgOl.mjs.map +0 -1
- package/dist/workspace-LKFnEBX8.cjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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 { c as isPartitionedRoutes, i as ConstructGenerator, t as discover } from "./discover-CMxesskG.mjs";
|
|
4
|
-
import { a as loadConfig, c as parseModuleConfig, i as loadAppConfig, l as appKey, r as getAppNameFromCwd, s as loadWorkspaceConfig, u as derivedApps } from "./config-
|
|
4
|
+
import { a as loadConfig, c as parseModuleConfig, i as loadAppConfig, l as appKey, r as getAppNameFromCwd, s as loadWorkspaceConfig, u as derivedApps } from "./config-Dss1WwtH.mjs";
|
|
5
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-CsL76tpc.mjs";
|
|
6
6
|
import { c as getKeyPath, i as setCustomSecret, n as readStageSecrets, o as toEmbeddableSecrets, r as secretsExist, s as writeStageSecrets, t as maskPassword } from "./storage-BjTLjDDp.mjs";
|
|
7
|
-
import { _ as findAvailablePort, b as loadSecretsForApp, c as applyPostgres, d as cacheBackendFor, f as eventsBackendFor, g as createEntryWrapper, h as createCredentialsPreload, i as reconcileWorkspace, l as resourceName, m as storageBackendFor, n as constructGlobs, o as COMPOSE_PATH, p as providerOf, r as derivedContainers, s as LOCAL_CA_PATH, t as backendsOf, u as rootSite, v as isPortAvailable, x as prepareEntryCredentials, y as loadEnvFiles } from "./workspace-
|
|
7
|
+
import { _ as findAvailablePort, b as loadSecretsForApp, c as applyPostgres, d as cacheBackendFor, f as eventsBackendFor, g as createEntryWrapper, h as createCredentialsPreload, i as reconcileWorkspace, l as resourceName, m as storageBackendFor, n as constructGlobs, o as COMPOSE_PATH, p as providerOf, r as derivedContainers, s as LOCAL_CA_PATH, t as backendsOf, u as rootSite, v as isPortAvailable, x as prepareEntryCredentials, y as loadEnvFiles } from "./workspace-EmXwO2qM.mjs";
|
|
8
8
|
import { n as EndpointGenerator, r as runtimeFor } from "./OpenApiTsGenerator-hDINScnz.mjs";
|
|
9
9
|
import { generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi.mjs";
|
|
10
10
|
import { a as writeTestHarness, i as TEST_MANIFEST_ENV, n as TEST_READY_FILE, o as cacheBackendsIn, s as driversFor, t as TEST_READY_ENV } from "./ready-D1BCiaYU.mjs";
|
|
@@ -19,7 +19,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
19
19
|
import { homedir } from "node:os";
|
|
20
20
|
import { basename, dirname, isAbsolute, join, parse, relative, resolve } from "node:path";
|
|
21
21
|
import { execFile, execSync, spawn } from "node:child_process";
|
|
22
|
-
import { cacheTable, canonicalId, cloudName, cookieDomain, dependentsOf, kebabCase, migrationFolder, provideKey, provisionOrder, scopedName, serviceKey } from "@geekmidas/manifest";
|
|
22
|
+
import { cacheTable, canonicalId, cloudName, cookieDomain, dependentsOf, kebabCase, migrationFolder, mobileOrigins, provideKey, provisionOrder, schemeBase, scopedName, serviceKey } from "@geekmidas/manifest";
|
|
23
23
|
import fg from "fast-glob";
|
|
24
24
|
import chokidar from "chokidar";
|
|
25
25
|
import { createCipheriv, createHash, randomBytes } from "node:crypto";
|
|
@@ -37,7 +37,7 @@ import { Subscriber } from "@geekmidas/constructs/subscribers";
|
|
|
37
37
|
import { Topic } from "@geekmidas/constructs/topic";
|
|
38
38
|
import { lookup } from "node:dns/promises";
|
|
39
39
|
//#region package.json
|
|
40
|
-
var version = "10.0.0-alpha.
|
|
40
|
+
var version = "10.0.0-alpha.36";
|
|
41
41
|
//#endregion
|
|
42
42
|
//#region src/auth/credentials.ts
|
|
43
43
|
/**
|
|
@@ -1087,9 +1087,9 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1087
1087
|
...reconciled.env
|
|
1088
1088
|
};
|
|
1089
1089
|
if (options.migrate || options.seed) {
|
|
1090
|
-
const { prepareDevDatabases } = await import("./migrate-
|
|
1090
|
+
const { prepareDevDatabases } = await import("./migrate-CT8b3nt_.mjs");
|
|
1091
1091
|
await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
|
|
1092
|
-
} else await import("./migrate-
|
|
1092
|
+
} else await import("./migrate-CT8b3nt_.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
|
|
1093
1093
|
logger$10.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
|
|
1094
1094
|
});
|
|
1095
1095
|
const appUrls = {};
|
|
@@ -1119,6 +1119,13 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1119
1119
|
for (const appName of buildOrder) {
|
|
1120
1120
|
const app = workspace.apps[appName];
|
|
1121
1121
|
if (!app) continue;
|
|
1122
|
+
if (app.type === "mobile") {
|
|
1123
|
+
const own = reconciled.plan.resources.find((r) => r.kind === "mobile-app" && appKey(r.id) === appName);
|
|
1124
|
+
const scheme = own ? reconciled.env[own.envKey] : void 0;
|
|
1125
|
+
const device = Object.entries(reconciled.env).find(([key]) => key.endsWith("_DEVICE_URL"))?.[1];
|
|
1126
|
+
logger$10.log(` ${appName.padEnd(width)} ${scheme ? `${scheme}://` : "mobile"}${device ? ` — devices reach ${new URL(device).hostname}` : ""}`);
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1122
1129
|
const local = `http://localhost:${app.port}`;
|
|
1123
1130
|
const url = Object.entries(appUrls).find(([id]) => appKey(id) === appName)?.[1];
|
|
1124
1131
|
logger$10.log(` ${appName.padEnd(width)} ${url ? `${url} -> ${local}` : local}`);
|
|
@@ -3313,9 +3320,13 @@ const PROVISIONERS = {
|
|
|
3313
3320
|
if (!url) throw new SurfaceHasNoAddress(declaration.id);
|
|
3314
3321
|
const origins = [...new Set(dependentsOf(context.manifest, declaration.id).map((caller) => context.addresses?.[caller]).filter((address) => Boolean(address)))].sort();
|
|
3315
3322
|
const domain = cookieDomain([url, ...origins]);
|
|
3323
|
+
const schemes = dependentsOf(context.manifest, declaration.id).flatMap((caller) => {
|
|
3324
|
+
const app = context.manifest[caller];
|
|
3325
|
+
return app?.kind === "mobile-app" ? mobileOrigins(schemeBase(context.project, app.scheme)) : [];
|
|
3326
|
+
});
|
|
3316
3327
|
return { provides: {
|
|
3317
3328
|
[provideKey(declaration.id, "url")]: url,
|
|
3318
|
-
[provideKey(declaration.id, "trustedOrigins")]: origins.join(","),
|
|
3329
|
+
[provideKey(declaration.id, "trustedOrigins")]: [...origins, ...schemes].join(","),
|
|
3319
3330
|
...domain ? { [provideKey(declaration.id, "cookieDomain")]: domain } : {}
|
|
3320
3331
|
} };
|
|
3321
3332
|
}
|
|
@@ -8000,11 +8011,11 @@ function routesGlob(structure) {
|
|
|
8000
8011
|
}
|
|
8001
8012
|
}
|
|
8002
8013
|
/**
|
|
8003
|
-
* Plain ids — `Database`, not `
|
|
8014
|
+
* Plain ids — `Database`, not `Shop`.
|
|
8004
8015
|
*
|
|
8005
8016
|
* The workspace `name` already scopes every physical name
|
|
8006
|
-
* (`production-
|
|
8007
|
-
* would say it twice: `production-
|
|
8017
|
+
* (`production-shop-database`), so an id that carried the project too
|
|
8018
|
+
* would say it twice: `production-shop-shop-cache`. Within a
|
|
8008
8019
|
* workspace the constructs are declared once, at its root, so there is no
|
|
8009
8020
|
* second app's `Database` to collide with.
|
|
8010
8021
|
*/
|
|
@@ -8730,30 +8741,32 @@ const TOOLCHAIN_VERSIONS = {
|
|
|
8730
8741
|
"@storybook/react-vite": "^10.6.0"
|
|
8731
8742
|
};
|
|
8732
8743
|
/**
|
|
8733
|
-
* The Expo app's packages, which track an Expo SDK release (
|
|
8734
|
-
* their own latest: SDK
|
|
8744
|
+
* The Expo app's packages, which track an Expo SDK release (57) rather than
|
|
8745
|
+
* their own latest: SDK 57 is tested against exactly these, React Native and
|
|
8735
8746
|
* React included, so they move when the SDK does.
|
|
8736
8747
|
*/
|
|
8737
8748
|
const EXPO_VERSIONS = {
|
|
8738
|
-
expo: "~
|
|
8739
|
-
"expo-constants": "~
|
|
8740
|
-
"expo-dev-client": "~
|
|
8741
|
-
"expo-
|
|
8742
|
-
"expo-
|
|
8743
|
-
"expo-
|
|
8744
|
-
"expo-
|
|
8745
|
-
"expo-
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
"react-
|
|
8749
|
-
"react-native
|
|
8750
|
-
"react-native-
|
|
8751
|
-
"react-native-
|
|
8752
|
-
"react-native-
|
|
8749
|
+
expo: "~57.0.26",
|
|
8750
|
+
"expo-constants": "~57.0.20",
|
|
8751
|
+
"expo-dev-client": "~57.0.19",
|
|
8752
|
+
"expo-device": "~57.0.2",
|
|
8753
|
+
"expo-linking": "~57.0.11",
|
|
8754
|
+
"expo-router": "~57.0.24",
|
|
8755
|
+
"expo-splash-screen": "~57.0.9",
|
|
8756
|
+
"expo-status-bar": "~57.0.1",
|
|
8757
|
+
"expo-secure-store": "~57.0.4",
|
|
8758
|
+
react: "19.2.3",
|
|
8759
|
+
"react-dom": "19.2.3",
|
|
8760
|
+
"react-native": "0.86.3",
|
|
8761
|
+
"react-native-gesture-handler": "~2.32.0",
|
|
8762
|
+
"react-native-reanimated": "4.5.1",
|
|
8763
|
+
"react-native-worklets": "0.10.1",
|
|
8764
|
+
"react-native-safe-area-context": "~5.7.0",
|
|
8765
|
+
"react-native-screens": "~4.26.0",
|
|
8753
8766
|
"react-native-web": "~0.21.0",
|
|
8754
8767
|
"@react-navigation/native": "^7.1.0",
|
|
8755
8768
|
"@babel/core": "^7.25.0",
|
|
8756
|
-
"@types/react": "~19.
|
|
8769
|
+
"@types/react": "~19.2.2",
|
|
8757
8770
|
nativewind: "~4.2.0",
|
|
8758
8771
|
tailwindcss: "~3.4.0"
|
|
8759
8772
|
};
|
|
@@ -8785,26 +8798,26 @@ function loadPackageJson() {
|
|
|
8785
8798
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
8786
8799
|
*/
|
|
8787
8800
|
const GEEKMIDAS_VERSIONS = {
|
|
8788
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
8789
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
8790
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
8791
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
8792
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
8793
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
8794
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
8795
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
8796
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
8797
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
8798
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
8799
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
8800
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
8801
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
8802
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
8803
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
8804
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
8805
|
-
"@geekmidas/studio": "~10.0.0-alpha.
|
|
8806
|
-
"@geekmidas/telescope": "~10.0.0-alpha.
|
|
8807
|
-
"@geekmidas/testkit": "~10.0.0-alpha.
|
|
8801
|
+
"@geekmidas/audit": "~10.0.0-alpha.36",
|
|
8802
|
+
"@geekmidas/auth": "~10.0.0-alpha.36",
|
|
8803
|
+
"@geekmidas/cache": "~10.0.0-alpha.36",
|
|
8804
|
+
"@geekmidas/client": "~10.0.0-alpha.36",
|
|
8805
|
+
"@geekmidas/cloud": "~10.0.0-alpha.36",
|
|
8806
|
+
"@geekmidas/constructs": "~10.0.0-alpha.36",
|
|
8807
|
+
"@geekmidas/db": "~10.0.0-alpha.36",
|
|
8808
|
+
"@geekmidas/emailkit": "~10.0.0-alpha.36",
|
|
8809
|
+
"@geekmidas/envkit": "~10.0.0-alpha.36",
|
|
8810
|
+
"@geekmidas/errors": "~10.0.0-alpha.36",
|
|
8811
|
+
"@geekmidas/events": "~10.0.0-alpha.36",
|
|
8812
|
+
"@geekmidas/logger": "~10.0.0-alpha.36",
|
|
8813
|
+
"@geekmidas/manifest": "~10.0.0-alpha.36",
|
|
8814
|
+
"@geekmidas/rate-limit": "~10.0.0-alpha.36",
|
|
8815
|
+
"@geekmidas/schema": "~10.0.0-alpha.36",
|
|
8816
|
+
"@geekmidas/services": "~10.0.0-alpha.36",
|
|
8817
|
+
"@geekmidas/storage": "~10.0.0-alpha.36",
|
|
8818
|
+
"@geekmidas/studio": "~10.0.0-alpha.36",
|
|
8819
|
+
"@geekmidas/telescope": "~10.0.0-alpha.36",
|
|
8820
|
+
"@geekmidas/testkit": "~10.0.0-alpha.36",
|
|
8808
8821
|
"@geekmidas/cli": `~${loadPackageJson().version}`
|
|
8809
8822
|
};
|
|
8810
8823
|
//#endregion
|
|
@@ -9526,8 +9539,7 @@ function generateExpoAppFiles(options) {
|
|
|
9526
9539
|
const apiPackage = `@${options.name}/api`;
|
|
9527
9540
|
const modelsPackage = `@${options.name}/models`;
|
|
9528
9541
|
const slug = options.name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
9529
|
-
const scheme =
|
|
9530
|
-
const bundleId = `com.${scheme}.app`;
|
|
9542
|
+
const scheme = schemeBase(options.name);
|
|
9531
9543
|
const packageJson = {
|
|
9532
9544
|
name: packageName,
|
|
9533
9545
|
version: "0.0.1",
|
|
@@ -9552,6 +9564,7 @@ function generateExpoAppFiles(options) {
|
|
|
9552
9564
|
expo: EXPO_VERSIONS["expo"],
|
|
9553
9565
|
"expo-constants": EXPO_VERSIONS["expo-constants"],
|
|
9554
9566
|
"expo-dev-client": EXPO_VERSIONS["expo-dev-client"],
|
|
9567
|
+
"expo-device": EXPO_VERSIONS["expo-device"],
|
|
9555
9568
|
"expo-linking": EXPO_VERSIONS["expo-linking"],
|
|
9556
9569
|
"expo-router": EXPO_VERSIONS["expo-router"],
|
|
9557
9570
|
"expo-secure-store": EXPO_VERSIONS["expo-secure-store"],
|
|
@@ -9563,6 +9576,7 @@ function generateExpoAppFiles(options) {
|
|
|
9563
9576
|
"react-native": EXPO_VERSIONS["react-native"],
|
|
9564
9577
|
"react-native-gesture-handler": EXPO_VERSIONS["react-native-gesture-handler"],
|
|
9565
9578
|
"react-native-reanimated": EXPO_VERSIONS["react-native-reanimated"],
|
|
9579
|
+
"react-native-worklets": EXPO_VERSIONS["react-native-worklets"],
|
|
9566
9580
|
"react-native-safe-area-context": EXPO_VERSIONS["react-native-safe-area-context"],
|
|
9567
9581
|
"react-native-screens": EXPO_VERSIONS["react-native-screens"],
|
|
9568
9582
|
"react-native-web": EXPO_VERSIONS["react-native-web"],
|
|
@@ -9575,27 +9589,51 @@ function generateExpoAppFiles(options) {
|
|
|
9575
9589
|
}
|
|
9576
9590
|
};
|
|
9577
9591
|
const appConfig = `import type { ConfigContext, ExpoConfig } from '@expo/config';
|
|
9592
|
+
import { EnvironmentParser } from '@geekmidas/envkit';
|
|
9578
9593
|
|
|
9579
9594
|
/**
|
|
9580
|
-
*
|
|
9581
|
-
*
|
|
9595
|
+
* What the app is built with, from the environment \`gkm\` injects — the
|
|
9596
|
+
* \`MobileApp\` construct's \`.dependsOn([api, auth])\` is where every value
|
|
9597
|
+
* here comes from, so none of it is written down.
|
|
9598
|
+
*
|
|
9599
|
+
* - \`APP_SCHEME\`: the URL scheme — \`${scheme}\`, the same on every stage.
|
|
9600
|
+
* - \`EXPO_PUBLIC_API_URL\` / \`EXPO_PUBLIC_AUTH_URL\`: where the servers
|
|
9601
|
+
* answer. Locally that is their own port on \`localhost\`, which
|
|
9602
|
+
* \`config.ts\` points at the machine Metro was served from.
|
|
9603
|
+
*
|
|
9604
|
+
* Read at runtime from \`Constants.expoConfig.extra.config\` — see \`config.ts\`.
|
|
9582
9605
|
*/
|
|
9583
|
-
|
|
9606
|
+
const config = new EnvironmentParser({ ...process.env })
|
|
9607
|
+
.create((get) => ({
|
|
9608
|
+
scheme: get('APP_SCHEME').string(),
|
|
9609
|
+
apiUrl: get('EXPO_PUBLIC_API_URL').string(),
|
|
9610
|
+
authUrl: get('EXPO_PUBLIC_AUTH_URL').string(),
|
|
9611
|
+
}))
|
|
9612
|
+
.parse();
|
|
9613
|
+
|
|
9614
|
+
export type Config = typeof config;
|
|
9615
|
+
|
|
9616
|
+
// A bundle id and an Android package allow no hyphens; one per scheme, so
|
|
9617
|
+
// each stage installs beside the others.
|
|
9618
|
+
const bundleId = \`com.\${config.scheme.replace(/[^a-z0-9]/g, '')}.app\`;
|
|
9619
|
+
|
|
9620
|
+
export default function expoConfig({ config: base }: ConfigContext): ExpoConfig {
|
|
9584
9621
|
return {
|
|
9622
|
+
...base,
|
|
9585
9623
|
name: '${options.name}',
|
|
9586
9624
|
slug: '${slug}',
|
|
9587
9625
|
version: '0.0.1',
|
|
9588
9626
|
orientation: 'portrait',
|
|
9589
9627
|
icon: './assets/icon.png',
|
|
9590
|
-
scheme:
|
|
9628
|
+
scheme: config.scheme,
|
|
9591
9629
|
userInterfaceStyle: 'automatic',
|
|
9592
9630
|
newArchEnabled: true,
|
|
9593
9631
|
ios: {
|
|
9594
9632
|
supportsTablet: true,
|
|
9595
|
-
bundleIdentifier:
|
|
9633
|
+
bundleIdentifier: bundleId,
|
|
9596
9634
|
},
|
|
9597
9635
|
android: {
|
|
9598
|
-
package:
|
|
9636
|
+
package: bundleId,
|
|
9599
9637
|
adaptiveIcon: {
|
|
9600
9638
|
foregroundImage: './assets/adaptive-icon.png',
|
|
9601
9639
|
backgroundColor: '#ffffff',
|
|
@@ -9621,6 +9659,7 @@ export default function config(_context: ConfigContext): ExpoConfig {
|
|
|
9621
9659
|
experiments: {
|
|
9622
9660
|
typedRoutes: true,
|
|
9623
9661
|
},
|
|
9662
|
+
extra: { ...base.extra, config },
|
|
9624
9663
|
};
|
|
9625
9664
|
}
|
|
9626
9665
|
`;
|
|
@@ -9633,16 +9672,13 @@ export default function config(_context: ConfigContext): ExpoConfig {
|
|
|
9633
9672
|
dev: {
|
|
9634
9673
|
developmentClient: true,
|
|
9635
9674
|
distribution: "internal",
|
|
9636
|
-
environment: "development"
|
|
9637
|
-
env: {
|
|
9638
|
-
EXPO_PUBLIC_API_URL: "http://localhost:3000",
|
|
9639
|
-
EXPO_PUBLIC_AUTH_URL: "http://localhost:3002"
|
|
9640
|
-
}
|
|
9675
|
+
environment: "development"
|
|
9641
9676
|
},
|
|
9642
9677
|
preview: {
|
|
9643
9678
|
distribution: "internal",
|
|
9644
9679
|
environment: "preview",
|
|
9645
9680
|
env: {
|
|
9681
|
+
APP_SCHEME: scheme,
|
|
9646
9682
|
EXPO_PUBLIC_API_URL: "https://api.example.com",
|
|
9647
9683
|
EXPO_PUBLIC_AUTH_URL: "https://auth.example.com"
|
|
9648
9684
|
}
|
|
@@ -9651,6 +9687,7 @@ export default function config(_context: ConfigContext): ExpoConfig {
|
|
|
9651
9687
|
autoIncrement: true,
|
|
9652
9688
|
environment: "production",
|
|
9653
9689
|
env: {
|
|
9690
|
+
APP_SCHEME: scheme,
|
|
9654
9691
|
EXPO_PUBLIC_API_URL: "https://api.example.com",
|
|
9655
9692
|
EXPO_PUBLIC_AUTH_URL: "https://auth.example.com"
|
|
9656
9693
|
}
|
|
@@ -9712,23 +9749,49 @@ export default {
|
|
|
9712
9749
|
`;
|
|
9713
9750
|
const expoEnvDts = `/// <reference types="expo/types" />\n`;
|
|
9714
9751
|
const nativewindEnvDts = `/// <reference types="nativewind/types" />\n`;
|
|
9715
|
-
const configTs = `import
|
|
9752
|
+
const configTs = `import Constants from 'expo-constants';
|
|
9753
|
+
import * as Device from 'expo-device';
|
|
9754
|
+
import { Platform } from 'react-native';
|
|
9755
|
+
|
|
9756
|
+
import type { Config } from './app.config.ts';
|
|
9757
|
+
|
|
9758
|
+
const built = Constants.expoConfig?.extra?.config as Config;
|
|
9759
|
+
|
|
9760
|
+
/** The host this device loaded the bundle from: Metro's, as the device sees it. */
|
|
9761
|
+
const metro = (Constants.expoConfig?.hostUri ?? '').split(':')[0];
|
|
9716
9762
|
|
|
9717
9763
|
/**
|
|
9718
|
-
*
|
|
9719
|
-
*
|
|
9764
|
+
* A private address on the local network — what a phone on the same Wi-Fi
|
|
9765
|
+
* reaches this machine on. A tunnel's \`*.exp.direct\` host is not one: a
|
|
9766
|
+
* tunnel forwards only Metro, so the servers are not behind it.
|
|
9720
9767
|
*/
|
|
9721
|
-
const
|
|
9722
|
-
|
|
9723
|
-
|
|
9724
|
-
|
|
9768
|
+
const isLan = (host: string) =>
|
|
9769
|
+
/^10\\./.test(host) ||
|
|
9770
|
+
/^192\\.168\\./.test(host) ||
|
|
9771
|
+
/^172\\.(1[6-9]|2\\d|3[01])\\./.test(host);
|
|
9725
9772
|
|
|
9726
|
-
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
9730
|
-
|
|
9731
|
-
|
|
9773
|
+
/** \`localhost\` from inside the Android emulator is the emulator itself. */
|
|
9774
|
+
const isAndroidEmulator = Platform.OS === 'android' && !Device.isDevice;
|
|
9775
|
+
|
|
9776
|
+
/**
|
|
9777
|
+
* A server's URL as this device reaches it.
|
|
9778
|
+
*
|
|
9779
|
+
* Locally the servers are injected as \`http://localhost:<port>\`. That is this
|
|
9780
|
+
* device only on the iOS simulator. The Android emulator reaches this machine
|
|
9781
|
+
* at \`10.0.2.2\`, and a phone at the LAN address Metro was served from.
|
|
9782
|
+
* Deployed URLs have no \`localhost\` in them and pass through untouched.
|
|
9783
|
+
*/
|
|
9784
|
+
export function reachable(url: string): string {
|
|
9785
|
+
const host = isLan(metro) ? metro : isAndroidEmulator ? '10.0.2.2' : undefined;
|
|
9786
|
+
if (!host) return url;
|
|
9787
|
+
return url.replace(/\\/\\/(localhost|127\\.0\\.0\\.1)(?=[:/]|$)/, \`//\${host}\`);
|
|
9788
|
+
}
|
|
9789
|
+
|
|
9790
|
+
export const config = {
|
|
9791
|
+
scheme: built.scheme,
|
|
9792
|
+
apiUrl: reachable(built.apiUrl),
|
|
9793
|
+
authUrl: reachable(built.authUrl),
|
|
9794
|
+
};
|
|
9732
9795
|
`;
|
|
9733
9796
|
const queryClientTs = `import { QueryClient } from '@tanstack/react-query';
|
|
9734
9797
|
|
|
@@ -9740,21 +9803,23 @@ export const queryClient = new QueryClient({
|
|
|
9740
9803
|
},
|
|
9741
9804
|
});
|
|
9742
9805
|
`;
|
|
9743
|
-
const authClientTs = `import { expoClient } from '@better-auth/expo/client';
|
|
9806
|
+
const authClientTs = `import { expoClient, getSetCookie } from '@better-auth/expo/client';
|
|
9744
9807
|
import { magicLinkClient } from 'better-auth/client/plugins';
|
|
9745
9808
|
import { createAuthClient } from 'better-auth/react';
|
|
9809
|
+
import * as Linking from 'expo-linking';
|
|
9746
9810
|
import * as SecureStore from 'expo-secure-store';
|
|
9811
|
+
import { useEffect } from 'react';
|
|
9747
9812
|
|
|
9748
9813
|
import { config } from '@/config.ts';
|
|
9749
9814
|
|
|
9750
|
-
export const STORAGE_PREFIX =
|
|
9815
|
+
export const STORAGE_PREFIX = config.scheme;
|
|
9751
9816
|
export const COOKIE_STORE_KEY = \`\${STORAGE_PREFIX}_cookie\`;
|
|
9752
9817
|
|
|
9753
9818
|
export const authClient = createAuthClient({
|
|
9754
9819
|
baseURL: config.authUrl,
|
|
9755
9820
|
plugins: [
|
|
9756
9821
|
expoClient({
|
|
9757
|
-
scheme:
|
|
9822
|
+
scheme: config.scheme,
|
|
9758
9823
|
storagePrefix: STORAGE_PREFIX,
|
|
9759
9824
|
storage: SecureStore,
|
|
9760
9825
|
}),
|
|
@@ -9764,6 +9829,36 @@ export const authClient = createAuthClient({
|
|
|
9764
9829
|
|
|
9765
9830
|
export const { signIn, useSession } = authClient;
|
|
9766
9831
|
|
|
9832
|
+
/**
|
|
9833
|
+
* Sign in from the emailed link.
|
|
9834
|
+
*
|
|
9835
|
+
* The link opens in the phone's browser, which is what receives the session
|
|
9836
|
+
* cookie — not the app. The auth server's Expo plugin carries the cookie on the
|
|
9837
|
+
* redirect back into the app (\`?cookie=\`), and this stores it where the
|
|
9838
|
+
* Expo client keeps its own, merged with what is already there, then tells the
|
|
9839
|
+
* client the session changed. The Expo client does this itself only for a
|
|
9840
|
+
* sign-in it opened (social); a link opened from mail arrives as a deep link.
|
|
9841
|
+
*
|
|
9842
|
+
* Called once, from the root layout.
|
|
9843
|
+
*/
|
|
9844
|
+
export function useSessionFromLink() {
|
|
9845
|
+
const url = Linking.useLinkingURL();
|
|
9846
|
+
|
|
9847
|
+
useEffect(() => {
|
|
9848
|
+
const cookie = url ? Linking.parse(url).queryParams?.cookie : undefined;
|
|
9849
|
+
if (typeof cookie !== 'string') return;
|
|
9850
|
+
|
|
9851
|
+
void (async () => {
|
|
9852
|
+
const previous = await SecureStore.getItemAsync(COOKIE_STORE_KEY);
|
|
9853
|
+
await SecureStore.setItemAsync(
|
|
9854
|
+
COOKIE_STORE_KEY,
|
|
9855
|
+
getSetCookie(cookie, previous ?? undefined),
|
|
9856
|
+
);
|
|
9857
|
+
authClient.$store.notify('$sessionSignal');
|
|
9858
|
+
})();
|
|
9859
|
+
}, [url]);
|
|
9860
|
+
}
|
|
9861
|
+
|
|
9767
9862
|
export async function signOut() {
|
|
9768
9863
|
try {
|
|
9769
9864
|
await authClient.signOut();
|
|
@@ -9830,7 +9925,7 @@ import { StatusBar } from 'expo-status-bar';
|
|
|
9830
9925
|
import { useEffect, useMemo } from 'react';
|
|
9831
9926
|
import 'react-native-reanimated';
|
|
9832
9927
|
|
|
9833
|
-
import { authClient } from '@/lib/auth-client.ts';
|
|
9928
|
+
import { authClient, useSessionFromLink } from '@/lib/auth-client.ts';
|
|
9834
9929
|
import { ApiProvider } from '@/lib/api-context.tsx';
|
|
9835
9930
|
import { createAppApi } from '@/lib/api.ts';
|
|
9836
9931
|
import { queryClient } from '@/lib/query-client.ts';
|
|
@@ -9855,6 +9950,8 @@ function AuthGate({ children }: { children: React.ReactNode }) {
|
|
|
9855
9950
|
|
|
9856
9951
|
export default function RootLayout() {
|
|
9857
9952
|
const api = useMemo(() => createAppApi(), []);
|
|
9953
|
+
// The emailed sign-in link comes back as a deep link carrying the session.
|
|
9954
|
+
useSessionFromLink();
|
|
9858
9955
|
|
|
9859
9956
|
return (
|
|
9860
9957
|
<QueryClientProvider client={queryClient}>
|
|
@@ -10376,7 +10473,8 @@ function rootConstructDependencies(options) {
|
|
|
10376
10473
|
pg: DEPENDENCY_VERSIONS.pg,
|
|
10377
10474
|
...options.constructs.uploads ? { "@geekmidas/storage": v["@geekmidas/storage"] } : {},
|
|
10378
10475
|
...options.constructs.mail ? { "@geekmidas/emailkit": v["@geekmidas/emailkit"] } : {},
|
|
10379
|
-
...options.constructs.cache ? { "@geekmidas/cache": v["@geekmidas/cache"] } : {}
|
|
10476
|
+
...options.constructs.cache ? { "@geekmidas/cache": v["@geekmidas/cache"] } : {},
|
|
10477
|
+
...options.frontendFramework === "expo" ? { "@better-auth/expo": DEPENDENCY_VERSIONS["better-auth"] } : {}
|
|
10380
10478
|
};
|
|
10381
10479
|
}
|
|
10382
10480
|
/**
|
|
@@ -10809,7 +10907,7 @@ export const authDb = database.schema<Record<string, never>, 'AuthDb'>('AuthDb')
|
|
|
10809
10907
|
files.push(usersMigration());
|
|
10810
10908
|
files.push({
|
|
10811
10909
|
path: "constructs/auth.ts",
|
|
10812
|
-
content:
|
|
10910
|
+
content: `${frontendFramework === "expo" ? "import { expo } from '@better-auth/expo';\n" : ""}import { BetterAuth } from '@geekmidas/constructs/auth';
|
|
10813
10911
|
import { authDb } from './database.ts';
|
|
10814
10912
|
|
|
10815
10913
|
/**
|
|
@@ -10824,7 +10922,11 @@ import { authDb } from './database.ts';
|
|
|
10824
10922
|
export const auth = new BetterAuth('Auth', {
|
|
10825
10923
|
path: 'apps/auth',
|
|
10826
10924
|
database: authDb,
|
|
10827
|
-
basePath: '/api/auth'
|
|
10925
|
+
basePath: '/api/auth',${frontendFramework === "expo" ? `
|
|
10926
|
+
// The mobile app signs in through Better Auth's Expo plugin: it sends its
|
|
10927
|
+
// scheme as the origin, and this is what reads it. The construct refuses to
|
|
10928
|
+
// start without it once a MobileApp depends on this server.
|
|
10929
|
+
options: { plugins: [expo()] },` : ""}
|
|
10828
10930
|
});
|
|
10829
10931
|
`
|
|
10830
10932
|
});
|
|
@@ -10853,6 +10955,26 @@ export const api = new RestApi('Api', {
|
|
|
10853
10955
|
`
|
|
10854
10956
|
});
|
|
10855
10957
|
const variant = frontendFramework === "tanstack-start" ? "{ path: 'apps/web', variant: 'tanstack' }" : frontendFramework === "expo" ? void 0 : "{ path: 'apps/web', variant: 'next' }";
|
|
10958
|
+
if (frontendFramework === "expo") files.push({
|
|
10959
|
+
path: "constructs/app.ts",
|
|
10960
|
+
content: `import { MobileApp } from '@geekmidas/constructs/mobile-app';
|
|
10961
|
+
import { api } from './api.ts';
|
|
10962
|
+
import { auth } from './auth.ts';
|
|
10963
|
+
|
|
10964
|
+
/**
|
|
10965
|
+
* The mobile app — a construct like a site, which is what makes it an app.
|
|
10966
|
+
*
|
|
10967
|
+
* \`.dependsOn()\` is the single fact behind what a mobile app otherwise has
|
|
10968
|
+
* written down by hand: its API and auth URLs, its URL scheme, the
|
|
10969
|
+
* scheme in the auth server's trusted origins, Better Auth's Expo plugin, and
|
|
10970
|
+
* sign-in links a phone can open.
|
|
10971
|
+
*/
|
|
10972
|
+
export const app = new MobileApp('App', { path: 'apps/app' }).dependsOn([
|
|
10973
|
+
api,
|
|
10974
|
+
auth,
|
|
10975
|
+
]);
|
|
10976
|
+
`
|
|
10977
|
+
});
|
|
10856
10978
|
if (variant !== void 0) files.push({
|
|
10857
10979
|
path: "constructs/site.ts",
|
|
10858
10980
|
content: `import { StaticSite } from '@geekmidas/constructs/site';
|
|
@@ -15027,7 +15149,7 @@ var NoStoredSecrets = class extends Error {
|
|
|
15027
15149
|
}
|
|
15028
15150
|
};
|
|
15029
15151
|
async function reconcile(secrets, workspace, stage) {
|
|
15030
|
-
const { derivedContainers } = await import("./workspace-
|
|
15152
|
+
const { derivedContainers } = await import("./workspace-EmXwO2qM.mjs").then((n) => n.a);
|
|
15031
15153
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
15032
15154
|
return result ? {
|
|
15033
15155
|
secrets: result.secrets,
|
|
@@ -15426,7 +15548,7 @@ async function testCommand(options = {}) {
|
|
|
15426
15548
|
* starts without the reference data the app needs.
|
|
15427
15549
|
*/
|
|
15428
15550
|
async function migrateTestStage(workspace, env) {
|
|
15429
|
-
const { migrateAndSeed } = await import("./migrate-
|
|
15551
|
+
const { migrateAndSeed } = await import("./migrate-CT8b3nt_.mjs");
|
|
15430
15552
|
const { discover } = await import("./discover-CMxesskG.mjs").then((n) => n.n);
|
|
15431
15553
|
const sources = {};
|
|
15432
15554
|
const manifest = await discover({
|
|
@@ -15871,7 +15993,7 @@ program.command("migrate").description("Apply each database construct's migratio
|
|
|
15871
15993
|
try {
|
|
15872
15994
|
const globalOptions = program.opts();
|
|
15873
15995
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
15874
|
-
const { migrateCommand } = await import("./migrate-
|
|
15996
|
+
const { migrateCommand } = await import("./migrate-CT8b3nt_.mjs");
|
|
15875
15997
|
await migrateCommand({
|
|
15876
15998
|
...options,
|
|
15877
15999
|
...construct ? { construct } : {}
|
|
@@ -15885,7 +16007,7 @@ program.command("seed").description("Migrate, then run each database construct's
|
|
|
15885
16007
|
try {
|
|
15886
16008
|
const globalOptions = program.opts();
|
|
15887
16009
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
15888
|
-
const { seedCommand } = await import("./migrate-
|
|
16010
|
+
const { seedCommand } = await import("./migrate-CT8b3nt_.mjs");
|
|
15889
16011
|
await seedCommand({
|
|
15890
16012
|
...options,
|
|
15891
16013
|
...construct ? { construct } : {}
|
|
@@ -15899,7 +16021,7 @@ program.command("migration").description("Write a construct's next migration: an
|
|
|
15899
16021
|
try {
|
|
15900
16022
|
const globalOptions = program.opts();
|
|
15901
16023
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
15902
|
-
const { migrationCommand } = await import("./migrate-
|
|
16024
|
+
const { migrationCommand } = await import("./migrate-CT8b3nt_.mjs");
|
|
15903
16025
|
await migrationCommand({
|
|
15904
16026
|
construct,
|
|
15905
16027
|
...name ? { name } : {}
|
|
@@ -16067,7 +16189,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
16067
16189
|
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
16068
16190
|
process.exit(1);
|
|
16069
16191
|
}
|
|
16070
|
-
const { derivedContainers } = await import("./workspace-
|
|
16192
|
+
const { derivedContainers } = await import("./workspace-EmXwO2qM.mjs").then((n) => n.a);
|
|
16071
16193
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
16072
16194
|
if (!result) {
|
|
16073
16195
|
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
@@ -16126,7 +16248,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
|
|
|
16126
16248
|
try {
|
|
16127
16249
|
const globalOptions = program.opts();
|
|
16128
16250
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16129
|
-
const { deployGithubCommand } = await import("./github-
|
|
16251
|
+
const { deployGithubCommand } = await import("./github-C99BMxf0.mjs");
|
|
16130
16252
|
await deployGithubCommand(options);
|
|
16131
16253
|
} catch (error) {
|
|
16132
16254
|
console.error(formatError(error));
|