@geekmidas/cli 10.0.0-alpha.33 → 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.
- 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 +221 -86
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +222 -87
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-D2rtGKYi.mjs → migrate-C-D9xIbb.mjs} +3 -3
- package/dist/{migrate-D2rtGKYi.mjs.map → migrate-C-D9xIbb.mjs.map} +1 -1
- package/dist/{migrate-BkytlJlU.cjs → migrate-nlvT-GnY.cjs} +3 -3
- package/dist/{migrate-BkytlJlU.cjs.map → migrate-nlvT-GnY.cjs.map} +1 -1
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.mjs +1 -1
- package/dist/{workspace-BNurJoFx.mjs → workspace-CIiGijNi.mjs} +91 -16
- package/dist/workspace-CIiGijNi.mjs.map +1 -0
- package/dist/{workspace-C8TbIWxO.cjs → workspace-CRjZkCTW.cjs} +90 -15
- package/dist/workspace-CRjZkCTW.cjs.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-BNurJoFx.mjs.map +0 -1
- package/dist/workspace-C8TbIWxO.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-CIiGijNi.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.35";
|
|
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-C-D9xIbb.mjs");
|
|
1091
1091
|
await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
|
|
1092
|
-
} else await import("./migrate-
|
|
1092
|
+
} else await import("./migrate-C-D9xIbb.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}`);
|
|
@@ -3102,6 +3109,7 @@ const PROVISIONERS = {
|
|
|
3102
3109
|
context.deferred.push(...statementsFor({
|
|
3103
3110
|
id: declaration.id,
|
|
3104
3111
|
database: postgres.databaseName,
|
|
3112
|
+
ownsDatabase: true,
|
|
3105
3113
|
runtime,
|
|
3106
3114
|
owner,
|
|
3107
3115
|
...reads ? { reader: readerRole(runtime) } : {},
|
|
@@ -3312,9 +3320,13 @@ const PROVISIONERS = {
|
|
|
3312
3320
|
if (!url) throw new SurfaceHasNoAddress(declaration.id);
|
|
3313
3321
|
const origins = [...new Set(dependentsOf(context.manifest, declaration.id).map((caller) => context.addresses?.[caller]).filter((address) => Boolean(address)))].sort();
|
|
3314
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
|
+
});
|
|
3315
3327
|
return { provides: {
|
|
3316
3328
|
[provideKey(declaration.id, "url")]: url,
|
|
3317
|
-
[provideKey(declaration.id, "trustedOrigins")]: origins.join(","),
|
|
3329
|
+
[provideKey(declaration.id, "trustedOrigins")]: [...origins, ...schemes].join(","),
|
|
3318
3330
|
...domain ? { [provideKey(declaration.id, "cookieDomain")]: domain } : {}
|
|
3319
3331
|
} };
|
|
3320
3332
|
}
|
|
@@ -3337,6 +3349,7 @@ function statementsFor(options) {
|
|
|
3337
3349
|
owner,
|
|
3338
3350
|
...reader ? { reader } : {},
|
|
3339
3351
|
schema,
|
|
3352
|
+
...options.ownsDatabase ? { database } : {},
|
|
3340
3353
|
passwords: {
|
|
3341
3354
|
runtime: derivedPassword(context, runtime),
|
|
3342
3355
|
owner: derivedPassword(context, owner),
|
|
@@ -7998,11 +8011,11 @@ function routesGlob(structure) {
|
|
|
7998
8011
|
}
|
|
7999
8012
|
}
|
|
8000
8013
|
/**
|
|
8001
|
-
* Plain ids — `Database`, not `
|
|
8014
|
+
* Plain ids — `Database`, not `Shop`.
|
|
8002
8015
|
*
|
|
8003
8016
|
* The workspace `name` already scopes every physical name
|
|
8004
|
-
* (`production-
|
|
8005
|
-
* 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
|
|
8006
8019
|
* workspace the constructs are declared once, at its root, so there is no
|
|
8007
8020
|
* second app's `Database` to collide with.
|
|
8008
8021
|
*/
|
|
@@ -8728,30 +8741,32 @@ const TOOLCHAIN_VERSIONS = {
|
|
|
8728
8741
|
"@storybook/react-vite": "^10.6.0"
|
|
8729
8742
|
};
|
|
8730
8743
|
/**
|
|
8731
|
-
* The Expo app's packages, which track an Expo SDK release (
|
|
8732
|
-
* 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
|
|
8733
8746
|
* React included, so they move when the SDK does.
|
|
8734
8747
|
*/
|
|
8735
8748
|
const EXPO_VERSIONS = {
|
|
8736
|
-
expo: "~
|
|
8737
|
-
"expo-constants": "~
|
|
8738
|
-
"expo-dev-client": "~
|
|
8739
|
-
"expo-
|
|
8740
|
-
"expo-
|
|
8741
|
-
"expo-
|
|
8742
|
-
"expo-
|
|
8743
|
-
"expo-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
"react-
|
|
8747
|
-
"react-native
|
|
8748
|
-
"react-native-
|
|
8749
|
-
"react-native-
|
|
8750
|
-
"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",
|
|
8751
8766
|
"react-native-web": "~0.21.0",
|
|
8752
8767
|
"@react-navigation/native": "^7.1.0",
|
|
8753
8768
|
"@babel/core": "^7.25.0",
|
|
8754
|
-
"@types/react": "~19.
|
|
8769
|
+
"@types/react": "~19.2.2",
|
|
8755
8770
|
nativewind: "~4.2.0",
|
|
8756
8771
|
tailwindcss: "~3.4.0"
|
|
8757
8772
|
};
|
|
@@ -8783,26 +8798,26 @@ function loadPackageJson() {
|
|
|
8783
8798
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
8784
8799
|
*/
|
|
8785
8800
|
const GEEKMIDAS_VERSIONS = {
|
|
8786
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
8787
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
8788
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
8789
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
8790
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
8791
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
8792
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
8793
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
8794
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
8795
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
8796
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
8797
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
8798
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
8799
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
8800
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
8801
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
8802
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
8803
|
-
"@geekmidas/studio": "~10.0.0-alpha.
|
|
8804
|
-
"@geekmidas/telescope": "~10.0.0-alpha.
|
|
8805
|
-
"@geekmidas/testkit": "~10.0.0-alpha.
|
|
8801
|
+
"@geekmidas/audit": "~10.0.0-alpha.35",
|
|
8802
|
+
"@geekmidas/auth": "~10.0.0-alpha.35",
|
|
8803
|
+
"@geekmidas/cache": "~10.0.0-alpha.35",
|
|
8804
|
+
"@geekmidas/client": "~10.0.0-alpha.35",
|
|
8805
|
+
"@geekmidas/cloud": "~10.0.0-alpha.35",
|
|
8806
|
+
"@geekmidas/constructs": "~10.0.0-alpha.35",
|
|
8807
|
+
"@geekmidas/db": "~10.0.0-alpha.35",
|
|
8808
|
+
"@geekmidas/emailkit": "~10.0.0-alpha.35",
|
|
8809
|
+
"@geekmidas/envkit": "~10.0.0-alpha.35",
|
|
8810
|
+
"@geekmidas/errors": "~10.0.0-alpha.35",
|
|
8811
|
+
"@geekmidas/events": "~10.0.0-alpha.35",
|
|
8812
|
+
"@geekmidas/logger": "~10.0.0-alpha.35",
|
|
8813
|
+
"@geekmidas/manifest": "~10.0.0-alpha.35",
|
|
8814
|
+
"@geekmidas/rate-limit": "~10.0.0-alpha.35",
|
|
8815
|
+
"@geekmidas/schema": "~10.0.0-alpha.35",
|
|
8816
|
+
"@geekmidas/services": "~10.0.0-alpha.35",
|
|
8817
|
+
"@geekmidas/storage": "~10.0.0-alpha.35",
|
|
8818
|
+
"@geekmidas/studio": "~10.0.0-alpha.35",
|
|
8819
|
+
"@geekmidas/telescope": "~10.0.0-alpha.35",
|
|
8820
|
+
"@geekmidas/testkit": "~10.0.0-alpha.35",
|
|
8806
8821
|
"@geekmidas/cli": `~${loadPackageJson().version}`
|
|
8807
8822
|
};
|
|
8808
8823
|
//#endregion
|
|
@@ -9524,8 +9539,7 @@ function generateExpoAppFiles(options) {
|
|
|
9524
9539
|
const apiPackage = `@${options.name}/api`;
|
|
9525
9540
|
const modelsPackage = `@${options.name}/models`;
|
|
9526
9541
|
const slug = options.name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
9527
|
-
const scheme =
|
|
9528
|
-
const bundleId = `com.${scheme}.app`;
|
|
9542
|
+
const scheme = schemeBase(options.name);
|
|
9529
9543
|
const packageJson = {
|
|
9530
9544
|
name: packageName,
|
|
9531
9545
|
version: "0.0.1",
|
|
@@ -9550,6 +9564,7 @@ function generateExpoAppFiles(options) {
|
|
|
9550
9564
|
expo: EXPO_VERSIONS["expo"],
|
|
9551
9565
|
"expo-constants": EXPO_VERSIONS["expo-constants"],
|
|
9552
9566
|
"expo-dev-client": EXPO_VERSIONS["expo-dev-client"],
|
|
9567
|
+
"expo-device": EXPO_VERSIONS["expo-device"],
|
|
9553
9568
|
"expo-linking": EXPO_VERSIONS["expo-linking"],
|
|
9554
9569
|
"expo-router": EXPO_VERSIONS["expo-router"],
|
|
9555
9570
|
"expo-secure-store": EXPO_VERSIONS["expo-secure-store"],
|
|
@@ -9561,6 +9576,7 @@ function generateExpoAppFiles(options) {
|
|
|
9561
9576
|
"react-native": EXPO_VERSIONS["react-native"],
|
|
9562
9577
|
"react-native-gesture-handler": EXPO_VERSIONS["react-native-gesture-handler"],
|
|
9563
9578
|
"react-native-reanimated": EXPO_VERSIONS["react-native-reanimated"],
|
|
9579
|
+
"react-native-worklets": EXPO_VERSIONS["react-native-worklets"],
|
|
9564
9580
|
"react-native-safe-area-context": EXPO_VERSIONS["react-native-safe-area-context"],
|
|
9565
9581
|
"react-native-screens": EXPO_VERSIONS["react-native-screens"],
|
|
9566
9582
|
"react-native-web": EXPO_VERSIONS["react-native-web"],
|
|
@@ -9573,27 +9589,53 @@ function generateExpoAppFiles(options) {
|
|
|
9573
9589
|
}
|
|
9574
9590
|
};
|
|
9575
9591
|
const appConfig = `import type { ConfigContext, ExpoConfig } from '@expo/config';
|
|
9592
|
+
import { EnvironmentParser } from '@geekmidas/envkit';
|
|
9576
9593
|
|
|
9577
9594
|
/**
|
|
9578
|
-
*
|
|
9579
|
-
*
|
|
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 for this stage — \`${scheme}\` deployed,
|
|
9600
|
+
* \`${scheme}-dev\` locally, so a development build and the store build on
|
|
9601
|
+
* one phone never answer each other's links.
|
|
9602
|
+
* - \`EXPO_PUBLIC_API_URL\` / \`EXPO_PUBLIC_AUTH_URL\`: where the servers
|
|
9603
|
+
* answer. Locally that is their own port on \`localhost\`, which
|
|
9604
|
+
* \`config.ts\` points at the machine Metro was served from.
|
|
9605
|
+
*
|
|
9606
|
+
* Read at runtime from \`Constants.expoConfig.extra.config\` — see \`config.ts\`.
|
|
9580
9607
|
*/
|
|
9581
|
-
|
|
9608
|
+
const config = new EnvironmentParser({ ...process.env })
|
|
9609
|
+
.create((get) => ({
|
|
9610
|
+
scheme: get('APP_SCHEME').string(),
|
|
9611
|
+
apiUrl: get('EXPO_PUBLIC_API_URL').string(),
|
|
9612
|
+
authUrl: get('EXPO_PUBLIC_AUTH_URL').string(),
|
|
9613
|
+
}))
|
|
9614
|
+
.parse();
|
|
9615
|
+
|
|
9616
|
+
export type Config = typeof config;
|
|
9617
|
+
|
|
9618
|
+
// A bundle id and an Android package allow no hyphens; one per scheme, so
|
|
9619
|
+
// each stage installs beside the others.
|
|
9620
|
+
const bundleId = \`com.\${config.scheme.replace(/[^a-z0-9]/g, '')}.app\`;
|
|
9621
|
+
|
|
9622
|
+
export default function expoConfig({ config: base }: ConfigContext): ExpoConfig {
|
|
9582
9623
|
return {
|
|
9624
|
+
...base,
|
|
9583
9625
|
name: '${options.name}',
|
|
9584
9626
|
slug: '${slug}',
|
|
9585
9627
|
version: '0.0.1',
|
|
9586
9628
|
orientation: 'portrait',
|
|
9587
9629
|
icon: './assets/icon.png',
|
|
9588
|
-
scheme:
|
|
9630
|
+
scheme: config.scheme,
|
|
9589
9631
|
userInterfaceStyle: 'automatic',
|
|
9590
9632
|
newArchEnabled: true,
|
|
9591
9633
|
ios: {
|
|
9592
9634
|
supportsTablet: true,
|
|
9593
|
-
bundleIdentifier:
|
|
9635
|
+
bundleIdentifier: bundleId,
|
|
9594
9636
|
},
|
|
9595
9637
|
android: {
|
|
9596
|
-
package:
|
|
9638
|
+
package: bundleId,
|
|
9597
9639
|
adaptiveIcon: {
|
|
9598
9640
|
foregroundImage: './assets/adaptive-icon.png',
|
|
9599
9641
|
backgroundColor: '#ffffff',
|
|
@@ -9619,6 +9661,7 @@ export default function config(_context: ConfigContext): ExpoConfig {
|
|
|
9619
9661
|
experiments: {
|
|
9620
9662
|
typedRoutes: true,
|
|
9621
9663
|
},
|
|
9664
|
+
extra: { ...base.extra, config },
|
|
9622
9665
|
};
|
|
9623
9666
|
}
|
|
9624
9667
|
`;
|
|
@@ -9631,16 +9674,13 @@ export default function config(_context: ConfigContext): ExpoConfig {
|
|
|
9631
9674
|
dev: {
|
|
9632
9675
|
developmentClient: true,
|
|
9633
9676
|
distribution: "internal",
|
|
9634
|
-
environment: "development"
|
|
9635
|
-
env: {
|
|
9636
|
-
EXPO_PUBLIC_API_URL: "http://localhost:3000",
|
|
9637
|
-
EXPO_PUBLIC_AUTH_URL: "http://localhost:3002"
|
|
9638
|
-
}
|
|
9677
|
+
environment: "development"
|
|
9639
9678
|
},
|
|
9640
9679
|
preview: {
|
|
9641
9680
|
distribution: "internal",
|
|
9642
9681
|
environment: "preview",
|
|
9643
9682
|
env: {
|
|
9683
|
+
APP_SCHEME: scheme,
|
|
9644
9684
|
EXPO_PUBLIC_API_URL: "https://api.example.com",
|
|
9645
9685
|
EXPO_PUBLIC_AUTH_URL: "https://auth.example.com"
|
|
9646
9686
|
}
|
|
@@ -9649,6 +9689,7 @@ export default function config(_context: ConfigContext): ExpoConfig {
|
|
|
9649
9689
|
autoIncrement: true,
|
|
9650
9690
|
environment: "production",
|
|
9651
9691
|
env: {
|
|
9692
|
+
APP_SCHEME: scheme,
|
|
9652
9693
|
EXPO_PUBLIC_API_URL: "https://api.example.com",
|
|
9653
9694
|
EXPO_PUBLIC_AUTH_URL: "https://auth.example.com"
|
|
9654
9695
|
}
|
|
@@ -9710,23 +9751,49 @@ export default {
|
|
|
9710
9751
|
`;
|
|
9711
9752
|
const expoEnvDts = `/// <reference types="expo/types" />\n`;
|
|
9712
9753
|
const nativewindEnvDts = `/// <reference types="nativewind/types" />\n`;
|
|
9713
|
-
const configTs = `import
|
|
9754
|
+
const configTs = `import Constants from 'expo-constants';
|
|
9755
|
+
import * as Device from 'expo-device';
|
|
9756
|
+
import { Platform } from 'react-native';
|
|
9757
|
+
|
|
9758
|
+
import type { Config } from './app.config.ts';
|
|
9759
|
+
|
|
9760
|
+
const built = Constants.expoConfig?.extra?.config as Config;
|
|
9761
|
+
|
|
9762
|
+
/** The host this device loaded the bundle from: Metro's, as the device sees it. */
|
|
9763
|
+
const metro = (Constants.expoConfig?.hostUri ?? '').split(':')[0];
|
|
9714
9764
|
|
|
9715
9765
|
/**
|
|
9716
|
-
*
|
|
9717
|
-
*
|
|
9766
|
+
* A private address on the local network — what a phone on the same Wi-Fi
|
|
9767
|
+
* reaches this machine on. A tunnel's \`*.exp.direct\` host is not one: a
|
|
9768
|
+
* tunnel forwards only Metro, so the servers are not behind it.
|
|
9718
9769
|
*/
|
|
9719
|
-
const
|
|
9720
|
-
|
|
9721
|
-
|
|
9722
|
-
|
|
9770
|
+
const isLan = (host: string) =>
|
|
9771
|
+
/^10\\./.test(host) ||
|
|
9772
|
+
/^192\\.168\\./.test(host) ||
|
|
9773
|
+
/^172\\.(1[6-9]|2\\d|3[01])\\./.test(host);
|
|
9723
9774
|
|
|
9724
|
-
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
9775
|
+
/** \`localhost\` from inside the Android emulator is the emulator itself. */
|
|
9776
|
+
const isAndroidEmulator = Platform.OS === 'android' && !Device.isDevice;
|
|
9777
|
+
|
|
9778
|
+
/**
|
|
9779
|
+
* A server's URL as this device reaches it.
|
|
9780
|
+
*
|
|
9781
|
+
* Locally the servers are injected as \`http://localhost:<port>\`. That is this
|
|
9782
|
+
* device only on the iOS simulator. The Android emulator reaches this machine
|
|
9783
|
+
* at \`10.0.2.2\`, and a phone at the LAN address Metro was served from.
|
|
9784
|
+
* Deployed URLs have no \`localhost\` in them and pass through untouched.
|
|
9785
|
+
*/
|
|
9786
|
+
export function reachable(url: string): string {
|
|
9787
|
+
const host = isLan(metro) ? metro : isAndroidEmulator ? '10.0.2.2' : undefined;
|
|
9788
|
+
if (!host) return url;
|
|
9789
|
+
return url.replace(/\\/\\/(localhost|127\\.0\\.0\\.1)(?=[:/]|$)/, \`//\${host}\`);
|
|
9790
|
+
}
|
|
9791
|
+
|
|
9792
|
+
export const config = {
|
|
9793
|
+
scheme: built.scheme,
|
|
9794
|
+
apiUrl: reachable(built.apiUrl),
|
|
9795
|
+
authUrl: reachable(built.authUrl),
|
|
9796
|
+
};
|
|
9730
9797
|
`;
|
|
9731
9798
|
const queryClientTs = `import { QueryClient } from '@tanstack/react-query';
|
|
9732
9799
|
|
|
@@ -9738,21 +9805,23 @@ export const queryClient = new QueryClient({
|
|
|
9738
9805
|
},
|
|
9739
9806
|
});
|
|
9740
9807
|
`;
|
|
9741
|
-
const authClientTs = `import { expoClient } from '@better-auth/expo/client';
|
|
9808
|
+
const authClientTs = `import { expoClient, getSetCookie } from '@better-auth/expo/client';
|
|
9742
9809
|
import { magicLinkClient } from 'better-auth/client/plugins';
|
|
9743
9810
|
import { createAuthClient } from 'better-auth/react';
|
|
9811
|
+
import * as Linking from 'expo-linking';
|
|
9744
9812
|
import * as SecureStore from 'expo-secure-store';
|
|
9813
|
+
import { useEffect } from 'react';
|
|
9745
9814
|
|
|
9746
9815
|
import { config } from '@/config.ts';
|
|
9747
9816
|
|
|
9748
|
-
export const STORAGE_PREFIX =
|
|
9817
|
+
export const STORAGE_PREFIX = config.scheme;
|
|
9749
9818
|
export const COOKIE_STORE_KEY = \`\${STORAGE_PREFIX}_cookie\`;
|
|
9750
9819
|
|
|
9751
9820
|
export const authClient = createAuthClient({
|
|
9752
9821
|
baseURL: config.authUrl,
|
|
9753
9822
|
plugins: [
|
|
9754
9823
|
expoClient({
|
|
9755
|
-
scheme:
|
|
9824
|
+
scheme: config.scheme,
|
|
9756
9825
|
storagePrefix: STORAGE_PREFIX,
|
|
9757
9826
|
storage: SecureStore,
|
|
9758
9827
|
}),
|
|
@@ -9762,6 +9831,36 @@ export const authClient = createAuthClient({
|
|
|
9762
9831
|
|
|
9763
9832
|
export const { signIn, useSession } = authClient;
|
|
9764
9833
|
|
|
9834
|
+
/**
|
|
9835
|
+
* Sign in from the emailed link.
|
|
9836
|
+
*
|
|
9837
|
+
* The link opens in the phone's browser, which is what receives the session
|
|
9838
|
+
* cookie — not the app. The auth server's Expo plugin carries the cookie on the
|
|
9839
|
+
* redirect back into the app (\`?cookie=\`), and this stores it where the
|
|
9840
|
+
* Expo client keeps its own, merged with what is already there, then tells the
|
|
9841
|
+
* client the session changed. The Expo client does this itself only for a
|
|
9842
|
+
* sign-in it opened (social); a link opened from mail arrives as a deep link.
|
|
9843
|
+
*
|
|
9844
|
+
* Called once, from the root layout.
|
|
9845
|
+
*/
|
|
9846
|
+
export function useSessionFromLink() {
|
|
9847
|
+
const url = Linking.useLinkingURL();
|
|
9848
|
+
|
|
9849
|
+
useEffect(() => {
|
|
9850
|
+
const cookie = url ? Linking.parse(url).queryParams?.cookie : undefined;
|
|
9851
|
+
if (typeof cookie !== 'string') return;
|
|
9852
|
+
|
|
9853
|
+
void (async () => {
|
|
9854
|
+
const previous = await SecureStore.getItemAsync(COOKIE_STORE_KEY);
|
|
9855
|
+
await SecureStore.setItemAsync(
|
|
9856
|
+
COOKIE_STORE_KEY,
|
|
9857
|
+
getSetCookie(cookie, previous ?? undefined),
|
|
9858
|
+
);
|
|
9859
|
+
authClient.$store.notify('$sessionSignal');
|
|
9860
|
+
})();
|
|
9861
|
+
}, [url]);
|
|
9862
|
+
}
|
|
9863
|
+
|
|
9765
9864
|
export async function signOut() {
|
|
9766
9865
|
try {
|
|
9767
9866
|
await authClient.signOut();
|
|
@@ -9828,7 +9927,7 @@ import { StatusBar } from 'expo-status-bar';
|
|
|
9828
9927
|
import { useEffect, useMemo } from 'react';
|
|
9829
9928
|
import 'react-native-reanimated';
|
|
9830
9929
|
|
|
9831
|
-
import { authClient } from '@/lib/auth-client.ts';
|
|
9930
|
+
import { authClient, useSessionFromLink } from '@/lib/auth-client.ts';
|
|
9832
9931
|
import { ApiProvider } from '@/lib/api-context.tsx';
|
|
9833
9932
|
import { createAppApi } from '@/lib/api.ts';
|
|
9834
9933
|
import { queryClient } from '@/lib/query-client.ts';
|
|
@@ -9853,6 +9952,8 @@ function AuthGate({ children }: { children: React.ReactNode }) {
|
|
|
9853
9952
|
|
|
9854
9953
|
export default function RootLayout() {
|
|
9855
9954
|
const api = useMemo(() => createAppApi(), []);
|
|
9955
|
+
// The emailed sign-in link comes back as a deep link carrying the session.
|
|
9956
|
+
useSessionFromLink();
|
|
9856
9957
|
|
|
9857
9958
|
return (
|
|
9858
9959
|
<QueryClientProvider client={queryClient}>
|
|
@@ -10374,7 +10475,20 @@ function rootConstructDependencies(options) {
|
|
|
10374
10475
|
pg: DEPENDENCY_VERSIONS.pg,
|
|
10375
10476
|
...options.constructs.uploads ? { "@geekmidas/storage": v["@geekmidas/storage"] } : {},
|
|
10376
10477
|
...options.constructs.mail ? { "@geekmidas/emailkit": v["@geekmidas/emailkit"] } : {},
|
|
10377
|
-
...options.constructs.cache ? { "@geekmidas/cache": v["@geekmidas/cache"] } : {}
|
|
10478
|
+
...options.constructs.cache ? { "@geekmidas/cache": v["@geekmidas/cache"] } : {},
|
|
10479
|
+
...options.frontendFramework === "expo" ? { "@better-auth/expo": DEPENDENCY_VERSIONS["better-auth"] } : {}
|
|
10480
|
+
};
|
|
10481
|
+
}
|
|
10482
|
+
/**
|
|
10483
|
+
* What `test/factories/` imports at the root: the factory and faker, and
|
|
10484
|
+
* Kysely's types where the root does not already install it for the
|
|
10485
|
+
* constructs.
|
|
10486
|
+
*/
|
|
10487
|
+
function rootFactoryDependencies(options) {
|
|
10488
|
+
return {
|
|
10489
|
+
"@geekmidas/testkit": GEEKMIDAS_VERSIONS["@geekmidas/testkit"],
|
|
10490
|
+
"@faker-js/faker": DEPENDENCY_VERSIONS["@faker-js/faker"],
|
|
10491
|
+
...options.template === "fullstack" ? {} : { kysely: DEPENDENCY_VERSIONS.kysely }
|
|
10378
10492
|
};
|
|
10379
10493
|
}
|
|
10380
10494
|
/**
|
|
@@ -10416,6 +10530,7 @@ function generateMonorepoFiles(options, _template) {
|
|
|
10416
10530
|
turbo: DEPENDENCY_VERSIONS["turbo"],
|
|
10417
10531
|
typescript: TOOLCHAIN_VERSIONS["typescript"],
|
|
10418
10532
|
vitest: TOOLCHAIN_VERSIONS["vitest"],
|
|
10533
|
+
...options.constructs.database ? rootFactoryDependencies(options) : {},
|
|
10419
10534
|
...deploy.devDependencies
|
|
10420
10535
|
}
|
|
10421
10536
|
};
|
|
@@ -10838,6 +10953,26 @@ export const api = new RestApi('Api', {
|
|
|
10838
10953
|
`
|
|
10839
10954
|
});
|
|
10840
10955
|
const variant = frontendFramework === "tanstack-start" ? "{ path: 'apps/web', variant: 'tanstack' }" : frontendFramework === "expo" ? void 0 : "{ path: 'apps/web', variant: 'next' }";
|
|
10956
|
+
if (frontendFramework === "expo") files.push({
|
|
10957
|
+
path: "constructs/app.ts",
|
|
10958
|
+
content: `import { MobileApp } from '@geekmidas/constructs/mobile-app';
|
|
10959
|
+
import { api } from './api.ts';
|
|
10960
|
+
import { auth } from './auth.ts';
|
|
10961
|
+
|
|
10962
|
+
/**
|
|
10963
|
+
* The mobile app — a construct like a site, which is what makes it an app.
|
|
10964
|
+
*
|
|
10965
|
+
* \`.dependsOn()\` is the single fact behind what a mobile app otherwise has
|
|
10966
|
+
* written down by hand: its API and auth URLs, its URL scheme per stage, the
|
|
10967
|
+
* scheme in the auth server's trusted origins, Better Auth's Expo plugin, and
|
|
10968
|
+
* sign-in links a phone can open.
|
|
10969
|
+
*/
|
|
10970
|
+
export const app = new MobileApp('App', { path: 'apps/app' }).dependsOn([
|
|
10971
|
+
api,
|
|
10972
|
+
auth,
|
|
10973
|
+
]);
|
|
10974
|
+
`
|
|
10975
|
+
});
|
|
10841
10976
|
if (variant !== void 0) files.push({
|
|
10842
10977
|
path: "constructs/site.ts",
|
|
10843
10978
|
content: `import { StaticSite } from '@geekmidas/constructs/site';
|
|
@@ -12044,7 +12179,7 @@ describe('example', () => {
|
|
|
12044
12179
|
function generateTestFactoryFiles(options) {
|
|
12045
12180
|
if (!options.constructs.database) return [];
|
|
12046
12181
|
const db = databaseFor();
|
|
12047
|
-
const schema = options.monorepo ? `@${options.name}/constructs/database.ts` : "../../src/constructs/database.ts";
|
|
12182
|
+
const schema = options.monorepo && options.template === "fullstack" ? `@${options.name}/constructs/database.ts` : options.monorepo ? `../../${options.apiPath}/src/constructs/database.ts` : "../../src/constructs/database.ts";
|
|
12048
12183
|
return [{
|
|
12049
12184
|
path: `test/factories/${kebabCase(db.id)}.ts`,
|
|
12050
12185
|
content: `import type { Kysely } from 'kysely';
|
|
@@ -15012,7 +15147,7 @@ var NoStoredSecrets = class extends Error {
|
|
|
15012
15147
|
}
|
|
15013
15148
|
};
|
|
15014
15149
|
async function reconcile(secrets, workspace, stage) {
|
|
15015
|
-
const { derivedContainers } = await import("./workspace-
|
|
15150
|
+
const { derivedContainers } = await import("./workspace-CIiGijNi.mjs").then((n) => n.a);
|
|
15016
15151
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
15017
15152
|
return result ? {
|
|
15018
15153
|
secrets: result.secrets,
|
|
@@ -15411,7 +15546,7 @@ async function testCommand(options = {}) {
|
|
|
15411
15546
|
* starts without the reference data the app needs.
|
|
15412
15547
|
*/
|
|
15413
15548
|
async function migrateTestStage(workspace, env) {
|
|
15414
|
-
const { migrateAndSeed } = await import("./migrate-
|
|
15549
|
+
const { migrateAndSeed } = await import("./migrate-C-D9xIbb.mjs");
|
|
15415
15550
|
const { discover } = await import("./discover-CMxesskG.mjs").then((n) => n.n);
|
|
15416
15551
|
const sources = {};
|
|
15417
15552
|
const manifest = await discover({
|
|
@@ -15856,7 +15991,7 @@ program.command("migrate").description("Apply each database construct's migratio
|
|
|
15856
15991
|
try {
|
|
15857
15992
|
const globalOptions = program.opts();
|
|
15858
15993
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
15859
|
-
const { migrateCommand } = await import("./migrate-
|
|
15994
|
+
const { migrateCommand } = await import("./migrate-C-D9xIbb.mjs");
|
|
15860
15995
|
await migrateCommand({
|
|
15861
15996
|
...options,
|
|
15862
15997
|
...construct ? { construct } : {}
|
|
@@ -15870,7 +16005,7 @@ program.command("seed").description("Migrate, then run each database construct's
|
|
|
15870
16005
|
try {
|
|
15871
16006
|
const globalOptions = program.opts();
|
|
15872
16007
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
15873
|
-
const { seedCommand } = await import("./migrate-
|
|
16008
|
+
const { seedCommand } = await import("./migrate-C-D9xIbb.mjs");
|
|
15874
16009
|
await seedCommand({
|
|
15875
16010
|
...options,
|
|
15876
16011
|
...construct ? { construct } : {}
|
|
@@ -15884,7 +16019,7 @@ program.command("migration").description("Write a construct's next migration: an
|
|
|
15884
16019
|
try {
|
|
15885
16020
|
const globalOptions = program.opts();
|
|
15886
16021
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
15887
|
-
const { migrationCommand } = await import("./migrate-
|
|
16022
|
+
const { migrationCommand } = await import("./migrate-C-D9xIbb.mjs");
|
|
15888
16023
|
await migrationCommand({
|
|
15889
16024
|
construct,
|
|
15890
16025
|
...name ? { name } : {}
|
|
@@ -16052,7 +16187,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
16052
16187
|
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
16053
16188
|
process.exit(1);
|
|
16054
16189
|
}
|
|
16055
|
-
const { derivedContainers } = await import("./workspace-
|
|
16190
|
+
const { derivedContainers } = await import("./workspace-CIiGijNi.mjs").then((n) => n.a);
|
|
16056
16191
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
16057
16192
|
if (!result) {
|
|
16058
16193
|
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
@@ -16111,7 +16246,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
|
|
|
16111
16246
|
try {
|
|
16112
16247
|
const globalOptions = program.opts();
|
|
16113
16248
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16114
|
-
const { deployGithubCommand } = await import("./github-
|
|
16249
|
+
const { deployGithubCommand } = await import("./github-C99BMxf0.mjs");
|
|
16115
16250
|
await deployGithubCommand(options);
|
|
16116
16251
|
} catch (error) {
|
|
16117
16252
|
console.error(formatError(error));
|