@ramonclaudio/create-vexpo 0.1.0
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/README.md +50 -0
- package/dist/index.js +183 -0
- package/dist/templates/default/.eas/workflows/asc-events.yml +84 -0
- package/dist/templates/default/.eas/workflows/deploy-production.yml +129 -0
- package/dist/templates/default/.eas/workflows/development-builds.yml +19 -0
- package/dist/templates/default/.eas/workflows/e2e-tests.yml +42 -0
- package/dist/templates/default/.eas/workflows/pr-preview.yml +98 -0
- package/dist/templates/default/.eas/workflows/release.yml +44 -0
- package/dist/templates/default/.eas/workflows/rollback.yml +86 -0
- package/dist/templates/default/.eas/workflows/rollout.yml +84 -0
- package/dist/templates/default/.eas/workflows/rotate-apple-jwt.yml +42 -0
- package/dist/templates/default/.eas/workflows/testflight.yml +57 -0
- package/dist/templates/default/.github/workflows/check.yml +28 -0
- package/dist/templates/default/.maestro/launch.yaml +18 -0
- package/dist/templates/default/AGENTS.md +79 -0
- package/dist/templates/default/DESIGN.md +331 -0
- package/dist/templates/default/LICENSE +21 -0
- package/dist/templates/default/README.md +153 -0
- package/dist/templates/default/SETUP.md +618 -0
- package/dist/templates/default/__tests__/convex/constants.test.ts +49 -0
- package/dist/templates/default/__tests__/convex/validators.test.ts +23 -0
- package/dist/templates/default/__tests__/convex/webhook.test.ts +343 -0
- package/dist/templates/default/__tests__/lib/deep-link.test.ts +67 -0
- package/dist/templates/default/_easignore +22 -0
- package/dist/templates/default/_editorconfig +9 -0
- package/dist/templates/default/_env.example +34 -0
- package/dist/templates/default/_fingerprintignore +24 -0
- package/dist/templates/default/_gitattributes +7 -0
- package/dist/templates/default/_gitignore +69 -0
- package/dist/templates/default/_oxfmtrc.json +3 -0
- package/dist/templates/default/_oxlintrc.json +34 -0
- package/dist/templates/default/app/(app)/(tabs)/(home)/index.tsx +50 -0
- package/dist/templates/default/app/(app)/(tabs)/(home,search)/_layout.tsx +44 -0
- package/dist/templates/default/app/(app)/(tabs)/(search)/index.tsx +247 -0
- package/dist/templates/default/app/(app)/(tabs)/_layout.tsx +77 -0
- package/dist/templates/default/app/(app)/(tabs)/settings/_layout.tsx +37 -0
- package/dist/templates/default/app/(app)/(tabs)/settings/index.tsx +362 -0
- package/dist/templates/default/app/(app)/(tabs)/settings/preferences.tsx +184 -0
- package/dist/templates/default/app/(app)/_layout.tsx +73 -0
- package/dist/templates/default/app/(app)/debug.tsx +389 -0
- package/dist/templates/default/app/(app)/help.tsx +254 -0
- package/dist/templates/default/app/(app)/linked.tsx +116 -0
- package/dist/templates/default/app/(app)/privacy.tsx +159 -0
- package/dist/templates/default/app/(app)/profile.tsx +915 -0
- package/dist/templates/default/app/(app)/sessions.tsx +191 -0
- package/dist/templates/default/app/(app)/welcome.tsx +140 -0
- package/dist/templates/default/app/(auth)/_layout.tsx +31 -0
- package/dist/templates/default/app/(auth)/forgot-password.tsx +168 -0
- package/dist/templates/default/app/(auth)/reset-password.tsx +314 -0
- package/dist/templates/default/app/(auth)/sign-in.tsx +453 -0
- package/dist/templates/default/app/(auth)/sign-up.tsx +563 -0
- package/dist/templates/default/app/+native-intent.tsx +14 -0
- package/dist/templates/default/app/+not-found.tsx +51 -0
- package/dist/templates/default/app/_layout.tsx +102 -0
- package/dist/templates/default/app-store/screenshots/.gitkeep +0 -0
- package/dist/templates/default/app-store/screenshots/README.md +13 -0
- package/dist/templates/default/app.config.ts +201 -0
- package/dist/templates/default/app.json +11 -0
- package/dist/templates/default/assets/brand-icon-dark.png +0 -0
- package/dist/templates/default/assets/brand-icon-light.png +0 -0
- package/dist/templates/default/assets/fonts/Geist-Black.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-BlackItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Bold.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-BoldItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-ExtraBold.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-ExtraBoldItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-ExtraLight.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-ExtraLightItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Italic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Light.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-LightItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Medium.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-MediumItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Regular.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-SemiBold.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-SemiBoldItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Thin.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-ThinItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Variable-Italic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Variable.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistMono-Bold.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistMono-BoldItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistMono-Italic.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistMono-Medium.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistMono-MediumItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistMono-Regular.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistPixel-Square.ttf +0 -0
- package/dist/templates/default/assets/icon.png +0 -0
- package/dist/templates/default/assets/sounds/notification.wav +0 -0
- package/dist/templates/default/assets/splash-image-dark.png +0 -0
- package/dist/templates/default/assets/splash-image-light.png +0 -0
- package/dist/templates/default/bun.lock +1860 -0
- package/dist/templates/default/components/auth/otp-verification.tsx +255 -0
- package/dist/templates/default/components/auth/password-field.tsx +121 -0
- package/dist/templates/default/components/auth/segmented-toggle.tsx +47 -0
- package/dist/templates/default/components/ui/convex-error.tsx +32 -0
- package/dist/templates/default/components/ui/error-boundary.tsx +57 -0
- package/dist/templates/default/components/ui/loading-screen.tsx +31 -0
- package/dist/templates/default/components/ui/material.tsx +94 -0
- package/dist/templates/default/components/ui/offline-banner.tsx +58 -0
- package/dist/templates/default/components/ui/prominent-button.tsx +71 -0
- package/dist/templates/default/components/ui/skeleton.tsx +107 -0
- package/dist/templates/default/components/ui/status-text.tsx +49 -0
- package/dist/templates/default/components/ui/update-banner.tsx +82 -0
- package/dist/templates/default/constants/layout.ts +102 -0
- package/dist/templates/default/constants/theme.ts +401 -0
- package/dist/templates/default/constants/ui.ts +77 -0
- package/dist/templates/default/convex/_generated/api.d.ts +77 -0
- package/dist/templates/default/convex/_generated/api.js +23 -0
- package/dist/templates/default/convex/_generated/dataModel.d.ts +60 -0
- package/dist/templates/default/convex/_generated/server.d.ts +143 -0
- package/dist/templates/default/convex/_generated/server.js +93 -0
- package/dist/templates/default/convex/admin.ts +102 -0
- package/dist/templates/default/convex/auth.config.ts +6 -0
- package/dist/templates/default/convex/auth.ts +335 -0
- package/dist/templates/default/convex/constants.ts +46 -0
- package/dist/templates/default/convex/convex.config.ts +11 -0
- package/dist/templates/default/convex/crons.ts +42 -0
- package/dist/templates/default/convex/email.ts +109 -0
- package/dist/templates/default/convex/env.ts +31 -0
- package/dist/templates/default/convex/errors.ts +33 -0
- package/dist/templates/default/convex/functions.ts +54 -0
- package/dist/templates/default/convex/http.ts +176 -0
- package/dist/templates/default/convex/log.ts +81 -0
- package/dist/templates/default/convex/pushTokens.ts +114 -0
- package/dist/templates/default/convex/rateLimit.ts +92 -0
- package/dist/templates/default/convex/schema.ts +28 -0
- package/dist/templates/default/convex/tsconfig.json +18 -0
- package/dist/templates/default/convex/users.ts +279 -0
- package/dist/templates/default/convex/validators.ts +74 -0
- package/dist/templates/default/convex/webhook.ts +193 -0
- package/dist/templates/default/convex.json +6 -0
- package/dist/templates/default/eas.json +56 -0
- package/dist/templates/default/fingerprint.config.js +9 -0
- package/dist/templates/default/hooks/use-debounce.ts +20 -0
- package/dist/templates/default/hooks/use-deep-link.ts +43 -0
- package/dist/templates/default/hooks/use-navigation-tracking.ts +15 -0
- package/dist/templates/default/hooks/use-network.ts +11 -0
- package/dist/templates/default/hooks/use-notifications.ts +107 -0
- package/dist/templates/default/hooks/use-onboarding.ts +15 -0
- package/dist/templates/default/hooks/use-reduced-motion.ts +11 -0
- package/dist/templates/default/hooks/use-theme.ts +53 -0
- package/dist/templates/default/hooks/use-updates.ts +86 -0
- package/dist/templates/default/lib/a11y.ts +5 -0
- package/dist/templates/default/lib/app.ts +14 -0
- package/dist/templates/default/lib/assets.ts +17 -0
- package/dist/templates/default/lib/auth-client.ts +21 -0
- package/dist/templates/default/lib/convex-auth.tsx +79 -0
- package/dist/templates/default/lib/deep-link.ts +71 -0
- package/dist/templates/default/lib/dev-menu.ts +119 -0
- package/dist/templates/default/lib/device.ts +40 -0
- package/dist/templates/default/lib/dynamic-font.ts +49 -0
- package/dist/templates/default/lib/env.ts +10 -0
- package/dist/templates/default/lib/haptics.ts +24 -0
- package/dist/templates/default/lib/notifications.ts +276 -0
- package/dist/templates/default/lib/preferences.ts +45 -0
- package/dist/templates/default/lib/schemas.ts +137 -0
- package/dist/templates/default/lib/storage.ts +47 -0
- package/dist/templates/default/lib/updates.ts +107 -0
- package/dist/templates/default/metro.config.js +14 -0
- package/dist/templates/default/package.json +129 -0
- package/dist/templates/default/patches/PR-368.patch +91 -0
- package/dist/templates/default/patches/convex-dev-better-auth-0.12.2.tgz +0 -0
- package/dist/templates/default/plugins/README.md +9 -0
- package/dist/templates/default/plugins/with-auto-signing.js +45 -0
- package/dist/templates/default/plugins/with-pod-deployment-target.js +35 -0
- package/dist/templates/default/scripts/README.md +36 -0
- package/dist/templates/default/scripts/_run.mjs +77 -0
- package/dist/templates/default/scripts/clean.ts +543 -0
- package/dist/templates/default/scripts/rotate-apple-jwt.mjs +80 -0
- package/dist/templates/default/store.config.json +58 -0
- package/dist/templates/default/tsconfig.json +13 -0
- package/dist/templates/default/vitest.config.ts +21 -0
- package/package.json +69 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
|
|
3
|
+
import { createStorage } from "@/lib/storage";
|
|
4
|
+
|
|
5
|
+
export type ReduceMotionPref = "system" | "always" | "never";
|
|
6
|
+
|
|
7
|
+
const hapticsStore = createStorage<boolean>("pref.hapticsEnabled", true);
|
|
8
|
+
const reduceMotionStore = createStorage<ReduceMotionPref>("pref.reduceMotion", "system");
|
|
9
|
+
// Default on in dev, off in production. Reveals the Debug screen with version,
|
|
10
|
+
// device, OTA update, and push diagnostics.
|
|
11
|
+
const debugEnabledStore = createStorage<boolean>("pref.debugEnabled", __DEV__);
|
|
12
|
+
|
|
13
|
+
export const preferences = {
|
|
14
|
+
hapticsEnabled: () => hapticsStore.get(),
|
|
15
|
+
setHapticsEnabled: (v: boolean) => hapticsStore.set(v),
|
|
16
|
+
|
|
17
|
+
reduceMotion: () => reduceMotionStore.get(),
|
|
18
|
+
setReduceMotion: (v: ReduceMotionPref) => reduceMotionStore.set(v),
|
|
19
|
+
|
|
20
|
+
debugEnabled: () => debugEnabledStore.get(),
|
|
21
|
+
setDebugEnabled: (v: boolean) => debugEnabledStore.set(v),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function useHapticsEnabled(): [boolean, (v: boolean) => void] {
|
|
25
|
+
const v = useSyncExternalStore(hapticsStore.subscribe, hapticsStore.get, hapticsStore.get);
|
|
26
|
+
return [v, hapticsStore.set];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function useReduceMotionPref(): [ReduceMotionPref, (v: ReduceMotionPref) => void] {
|
|
30
|
+
const v = useSyncExternalStore(
|
|
31
|
+
reduceMotionStore.subscribe,
|
|
32
|
+
reduceMotionStore.get,
|
|
33
|
+
reduceMotionStore.get,
|
|
34
|
+
);
|
|
35
|
+
return [v, reduceMotionStore.set];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function useDebugEnabled(): [boolean, (v: boolean) => void] {
|
|
39
|
+
const v = useSyncExternalStore(
|
|
40
|
+
debugEnabledStore.subscribe,
|
|
41
|
+
debugEnabledStore.get,
|
|
42
|
+
debugEnabledStore.get,
|
|
43
|
+
);
|
|
44
|
+
return [v, debugEnabledStore.set];
|
|
45
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form validation schemas.
|
|
3
|
+
*
|
|
4
|
+
* Each form parses raw input via `schema.safeParse(values)` inside
|
|
5
|
+
* `useActionState`. Errors flatten to inline `Section.footer` text under each
|
|
6
|
+
* field. Constants and reserved-name helpers come from `@/convex/constants`
|
|
7
|
+
* to keep client/server in sync.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
USERNAME_FORMAT_REGEX,
|
|
14
|
+
USERNAME_MAX_LENGTH,
|
|
15
|
+
USERNAME_MIN_LENGTH,
|
|
16
|
+
isReservedUsername,
|
|
17
|
+
} from "@/convex/constants";
|
|
18
|
+
|
|
19
|
+
export const PASSWORD_MIN_LENGTH = 10;
|
|
20
|
+
export const PASSWORD_MAX_LENGTH = 128;
|
|
21
|
+
|
|
22
|
+
const usernameSchema = z
|
|
23
|
+
.string()
|
|
24
|
+
.trim()
|
|
25
|
+
.toLowerCase()
|
|
26
|
+
.min(USERNAME_MIN_LENGTH, {
|
|
27
|
+
error: `Username must be at least ${USERNAME_MIN_LENGTH} characters`,
|
|
28
|
+
})
|
|
29
|
+
.max(USERNAME_MAX_LENGTH, {
|
|
30
|
+
error: `Username must be ${USERNAME_MAX_LENGTH} characters or fewer`,
|
|
31
|
+
})
|
|
32
|
+
.regex(USERNAME_FORMAT_REGEX, { error: "Letters, numbers, dots, and underscores only" })
|
|
33
|
+
.refine((value) => !isReservedUsername(value), { error: "That username is reserved" });
|
|
34
|
+
|
|
35
|
+
// Optional variant used at sign-up: empty string is valid (the user can pick
|
|
36
|
+
// a handle later from the profile screen). Format and reserved checks only
|
|
37
|
+
// apply when the user actually typed something.
|
|
38
|
+
const optionalUsernameSchema = z
|
|
39
|
+
.string()
|
|
40
|
+
.trim()
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.refine((value) => value === "" || value.length >= USERNAME_MIN_LENGTH, {
|
|
43
|
+
error: `Username must be at least ${USERNAME_MIN_LENGTH} characters`,
|
|
44
|
+
})
|
|
45
|
+
.refine((value) => value === "" || value.length <= USERNAME_MAX_LENGTH, {
|
|
46
|
+
error: `Username must be ${USERNAME_MAX_LENGTH} characters or fewer`,
|
|
47
|
+
})
|
|
48
|
+
.refine((value) => value === "" || USERNAME_FORMAT_REGEX.test(value), {
|
|
49
|
+
error: "Letters, numbers, dots, and underscores only",
|
|
50
|
+
})
|
|
51
|
+
.refine((value) => value === "" || !isReservedUsername(value), {
|
|
52
|
+
error: "That username is reserved",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const passwordSchema = z
|
|
56
|
+
.string()
|
|
57
|
+
.min(PASSWORD_MIN_LENGTH, {
|
|
58
|
+
error: `Password must be at least ${PASSWORD_MIN_LENGTH} characters`,
|
|
59
|
+
})
|
|
60
|
+
.max(PASSWORD_MAX_LENGTH, {
|
|
61
|
+
error: `Password must be ${PASSWORD_MAX_LENGTH} characters or fewer`,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const emailSchema = z.string().trim().toLowerCase().email({ error: "Enter a valid email address" });
|
|
65
|
+
|
|
66
|
+
const nameSchema = z.string().trim().min(1, { error: "Name is required" });
|
|
67
|
+
|
|
68
|
+
const otpSchema = z.string().regex(/^\d{6}$/, { error: "Enter the 6-digit code" });
|
|
69
|
+
|
|
70
|
+
export const signInSchema = z.object({
|
|
71
|
+
identifier: z.string().trim().min(1, { error: "Username or email is required" }),
|
|
72
|
+
password: z.string().min(1, { error: "Password is required" }),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export const signInEmailSchema = z.object({
|
|
76
|
+
email: emailSchema,
|
|
77
|
+
password: z.string().min(1, { error: "Password is required" }),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export const signInUsernameSchema = z.object({
|
|
81
|
+
username: z
|
|
82
|
+
.string()
|
|
83
|
+
.trim()
|
|
84
|
+
.toLowerCase()
|
|
85
|
+
.min(USERNAME_MIN_LENGTH, {
|
|
86
|
+
error: `Username must be at least ${USERNAME_MIN_LENGTH} characters`,
|
|
87
|
+
}),
|
|
88
|
+
password: z.string().min(1, { error: "Password is required" }),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export const signUpSchema = z.object({
|
|
92
|
+
name: nameSchema,
|
|
93
|
+
username: optionalUsernameSchema,
|
|
94
|
+
email: emailSchema,
|
|
95
|
+
password: passwordSchema,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
export const forgotPasswordSchema = z.object({
|
|
99
|
+
email: emailSchema,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
export const resetPasswordSchema = z
|
|
103
|
+
.object({
|
|
104
|
+
email: emailSchema,
|
|
105
|
+
otp: otpSchema,
|
|
106
|
+
password: passwordSchema,
|
|
107
|
+
confirmPassword: z.string(),
|
|
108
|
+
})
|
|
109
|
+
.refine((data) => data.password === data.confirmPassword, {
|
|
110
|
+
error: "Passwords do not match",
|
|
111
|
+
path: ["confirmPassword"],
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
export const profileUpdateSchema = z.object({
|
|
115
|
+
name: nameSchema,
|
|
116
|
+
username: usernameSchema,
|
|
117
|
+
email: emailSchema,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
export type SignInValues = z.infer<typeof signInSchema>;
|
|
121
|
+
export type SignInEmailValues = z.infer<typeof signInEmailSchema>;
|
|
122
|
+
export type SignInUsernameValues = z.infer<typeof signInUsernameSchema>;
|
|
123
|
+
export type SignUpValues = z.infer<typeof signUpSchema>;
|
|
124
|
+
export type ForgotPasswordValues = z.infer<typeof forgotPasswordSchema>;
|
|
125
|
+
export type ResetPasswordValues = z.infer<typeof resetPasswordSchema>;
|
|
126
|
+
export type ProfileUpdateValues = z.infer<typeof profileUpdateSchema>;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Extract the first error message from a zod safeParse result, formatted for
|
|
130
|
+
* inline display. Returns `null` if validation succeeded.
|
|
131
|
+
*/
|
|
132
|
+
export function firstError(
|
|
133
|
+
result: { success: false; error: z.ZodError } | { success: true; data: unknown },
|
|
134
|
+
): string | null {
|
|
135
|
+
if (result.success) return null;
|
|
136
|
+
return result.error.issues[0]?.message ?? "Invalid input";
|
|
137
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import "expo-sqlite/localStorage/install";
|
|
2
|
+
|
|
3
|
+
type Listener = () => void;
|
|
4
|
+
|
|
5
|
+
const listeners = new Map<string, Set<Listener>>();
|
|
6
|
+
|
|
7
|
+
function notify(key: string) {
|
|
8
|
+
listeners.get(key)?.forEach((fn) => fn());
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function read<T>(key: string, defaultValue: T): T {
|
|
12
|
+
const raw = localStorage.getItem(key);
|
|
13
|
+
if (raw === null) return defaultValue;
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(raw) as T;
|
|
16
|
+
} catch {
|
|
17
|
+
return defaultValue;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type Storage<T> = {
|
|
22
|
+
get: () => T;
|
|
23
|
+
set: (value: T) => void;
|
|
24
|
+
subscribe: (listener: Listener) => () => void;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function createStorage<T>(key: string, defaultValue: T): Storage<T> {
|
|
28
|
+
return {
|
|
29
|
+
get: () => read(key, defaultValue),
|
|
30
|
+
set: (value: T) => {
|
|
31
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
32
|
+
notify(key);
|
|
33
|
+
},
|
|
34
|
+
subscribe: (listener: Listener) => {
|
|
35
|
+
let set = listeners.get(key);
|
|
36
|
+
if (!set) {
|
|
37
|
+
set = new Set();
|
|
38
|
+
listeners.set(key, set);
|
|
39
|
+
}
|
|
40
|
+
set.add(listener);
|
|
41
|
+
return () => {
|
|
42
|
+
set!.delete(listener);
|
|
43
|
+
if (set!.size === 0) listeners.delete(key);
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {
|
|
2
|
+
// Constants
|
|
3
|
+
isEnabled,
|
|
4
|
+
updateId,
|
|
5
|
+
channel,
|
|
6
|
+
runtimeVersion,
|
|
7
|
+
checkAutomatically,
|
|
8
|
+
isEmergencyLaunch,
|
|
9
|
+
emergencyLaunchReason,
|
|
10
|
+
isEmbeddedLaunch,
|
|
11
|
+
manifest,
|
|
12
|
+
createdAt,
|
|
13
|
+
launchDuration,
|
|
14
|
+
|
|
15
|
+
// Methods
|
|
16
|
+
checkForUpdateAsync,
|
|
17
|
+
fetchUpdateAsync,
|
|
18
|
+
reloadAsync,
|
|
19
|
+
readLogEntriesAsync,
|
|
20
|
+
clearLogEntriesAsync,
|
|
21
|
+
getExtraParamsAsync,
|
|
22
|
+
setExtraParamAsync,
|
|
23
|
+
setUpdateRequestHeadersOverride,
|
|
24
|
+
setUpdateURLAndRequestHeadersOverride,
|
|
25
|
+
showReloadScreen,
|
|
26
|
+
hideReloadScreen,
|
|
27
|
+
|
|
28
|
+
// Enums (runtime values)
|
|
29
|
+
UpdateCheckResultNotAvailableReason,
|
|
30
|
+
UpdatesLogEntryCode,
|
|
31
|
+
UpdatesLogEntryLevel,
|
|
32
|
+
UpdatesCheckAutomaticallyValue,
|
|
33
|
+
UpdateInfoType,
|
|
34
|
+
|
|
35
|
+
// Hook
|
|
36
|
+
useUpdates,
|
|
37
|
+
} from "expo-updates";
|
|
38
|
+
|
|
39
|
+
import type { ReloadScreenOptions } from "expo-updates";
|
|
40
|
+
|
|
41
|
+
export type {
|
|
42
|
+
ReloadScreenOptions,
|
|
43
|
+
ReloadScreenImageSource,
|
|
44
|
+
Manifest,
|
|
45
|
+
UpdateCheckResult,
|
|
46
|
+
UpdateCheckResultAvailable,
|
|
47
|
+
UpdateCheckResultNotAvailable,
|
|
48
|
+
UpdateCheckResultRollBack,
|
|
49
|
+
UpdateFetchResult,
|
|
50
|
+
UpdateFetchResultSuccess,
|
|
51
|
+
UpdateFetchResultFailure,
|
|
52
|
+
UpdateFetchResultRollBackToEmbedded,
|
|
53
|
+
UpdatesLogEntry,
|
|
54
|
+
CurrentlyRunningInfo,
|
|
55
|
+
UpdateInfo,
|
|
56
|
+
UpdateInfoNew,
|
|
57
|
+
UpdateInfoRollback,
|
|
58
|
+
UseUpdatesReturnType,
|
|
59
|
+
} from "expo-updates";
|
|
60
|
+
|
|
61
|
+
export {
|
|
62
|
+
UpdateCheckResultNotAvailableReason,
|
|
63
|
+
UpdatesLogEntryCode,
|
|
64
|
+
UpdatesLogEntryLevel,
|
|
65
|
+
UpdatesCheckAutomaticallyValue,
|
|
66
|
+
UpdateInfoType,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export { useUpdates };
|
|
70
|
+
|
|
71
|
+
export {
|
|
72
|
+
isEnabled,
|
|
73
|
+
updateId,
|
|
74
|
+
channel,
|
|
75
|
+
runtimeVersion,
|
|
76
|
+
checkAutomatically,
|
|
77
|
+
isEmergencyLaunch,
|
|
78
|
+
emergencyLaunchReason,
|
|
79
|
+
isEmbeddedLaunch,
|
|
80
|
+
manifest,
|
|
81
|
+
createdAt,
|
|
82
|
+
launchDuration,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export function buildReloadScreenConfig(
|
|
86
|
+
scheme: "light" | "dark",
|
|
87
|
+
reduceMotion = false,
|
|
88
|
+
): ReloadScreenOptions {
|
|
89
|
+
const dark = scheme === "dark";
|
|
90
|
+
return {
|
|
91
|
+
backgroundColor: dark ? "#0E0E0E" : "#FFFFFF",
|
|
92
|
+
fade: !reduceMotion,
|
|
93
|
+
spinner: { color: dark ? "#FFFFFF" : "#0E0E0E", enabled: true, size: "medium" },
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const checkForUpdate = isEnabled ? checkForUpdateAsync : async () => {};
|
|
98
|
+
export const fetchUpdate = isEnabled ? fetchUpdateAsync : async () => {};
|
|
99
|
+
export const reload = reloadAsync;
|
|
100
|
+
export const readLogEntries = readLogEntriesAsync;
|
|
101
|
+
export const clearLogEntries = clearLogEntriesAsync;
|
|
102
|
+
export const getExtraParams = getExtraParamsAsync;
|
|
103
|
+
export const setExtraParam = setExtraParamAsync;
|
|
104
|
+
export const setRequestHeadersOverride = setUpdateRequestHeadersOverride;
|
|
105
|
+
export const setURLAndHeadersOverride = setUpdateURLAndRequestHeadersOverride;
|
|
106
|
+
|
|
107
|
+
export { showReloadScreen, hideReloadScreen };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const { getDefaultConfig } = require("expo/metro-config");
|
|
2
|
+
|
|
3
|
+
/** @type {import('expo/metro-config').MetroConfig} */
|
|
4
|
+
const config = getDefaultConfig(__dirname);
|
|
5
|
+
|
|
6
|
+
config.resolver.blockList = [/\.env\.convex\.local$/];
|
|
7
|
+
|
|
8
|
+
config.transformer.minifierConfig = {
|
|
9
|
+
compress: {
|
|
10
|
+
drop_console: ["log", "info"],
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
module.exports = config;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vexpo",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"main": "expo-router/entry",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "expo start --dev-client",
|
|
8
|
+
"start": "expo start --dev-client --clear",
|
|
9
|
+
"convex:dev": "convex dev",
|
|
10
|
+
"convex:deploy": "convex deploy",
|
|
11
|
+
"convex:logs": "convex logs",
|
|
12
|
+
"convex:logs:prod": "convex logs --prod",
|
|
13
|
+
"convex:env": "convex env list",
|
|
14
|
+
"convex:env:prod": "convex env list --prod",
|
|
15
|
+
"convex:insights": "convex insights",
|
|
16
|
+
"convex:insights:prod": "convex insights --prod",
|
|
17
|
+
"convex:dashboard": "convex dashboard",
|
|
18
|
+
"convex:codegen": "convex codegen",
|
|
19
|
+
"ios": "node scripts/_run.mjs scripts/clean.ts --metro && expo prebuild --clean --platform ios && expo run:ios --no-build-cache",
|
|
20
|
+
"ios:dev": "expo run:ios",
|
|
21
|
+
"ios:device": "node scripts/_run.mjs scripts/clean.ts --metro && expo prebuild --clean --platform ios && expo run:ios --device --no-build-cache",
|
|
22
|
+
"prebuild": "expo prebuild --clean --platform ios",
|
|
23
|
+
"eas:dev": "bunx eas build -p ios --profile development:simulator",
|
|
24
|
+
"eas:dev:device": "bunx eas build -p ios --profile development:device",
|
|
25
|
+
"eas:tf": "bunx eas build -p ios --profile production --auto-submit-with-profile testflight",
|
|
26
|
+
"eas:prod": "bunx eas build -p ios --profile production",
|
|
27
|
+
"metadata:lint": "bunx eas metadata:lint",
|
|
28
|
+
"metadata:push": "bunx eas metadata:lint && bunx eas metadata:push",
|
|
29
|
+
"metadata:pull": "bunx eas metadata:pull",
|
|
30
|
+
"env:pull": "bunx eas env:pull --environment development",
|
|
31
|
+
"env:pull:prod": "bunx eas env:pull --environment production",
|
|
32
|
+
"clean": "node scripts/_run.mjs scripts/clean.ts",
|
|
33
|
+
"clean:metro": "node scripts/_run.mjs scripts/clean.ts --metro",
|
|
34
|
+
"clean:state": "node scripts/_run.mjs scripts/clean.ts --state",
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
36
|
+
"fp": "bunx @expo/fingerprint fingerprint:generate",
|
|
37
|
+
"fp:diff": "bunx @expo/fingerprint fingerprint:diff",
|
|
38
|
+
"lint": "oxlint",
|
|
39
|
+
"format": "oxfmt",
|
|
40
|
+
"format:check": "oxfmt --check",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:watch": "vitest",
|
|
43
|
+
"upgrade": "bunx expo install expo@canary && bunx expo install --fix",
|
|
44
|
+
"upgrade:stable": "bunx expo install expo@latest && bunx expo install --fix"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@better-auth/expo": "1.6.9",
|
|
48
|
+
"@convex-dev/better-auth": "file:./patches/convex-dev-better-auth-0.12.2.tgz",
|
|
49
|
+
"@convex-dev/rate-limiter": "^0.3.2",
|
|
50
|
+
"@convex-dev/resend": "^0.2.3",
|
|
51
|
+
"@expo/ui": "56.0.0-canary-20260506-03817f5",
|
|
52
|
+
"@react-navigation/native": "7.2.3",
|
|
53
|
+
"better-auth": "1.6.9",
|
|
54
|
+
"convex": "^1.37.0",
|
|
55
|
+
"convex-helpers": "^0.1.116",
|
|
56
|
+
"expo": "56.0.0-canary-20260506-03817f5",
|
|
57
|
+
"expo-apple-authentication": "56.0.0-canary-20260506-03817f5",
|
|
58
|
+
"expo-application": "56.0.0-canary-20260506-03817f5",
|
|
59
|
+
"expo-asset": "56.0.0-canary-20260506-03817f5",
|
|
60
|
+
"expo-blur": "56.0.0-canary-20260506-03817f5",
|
|
61
|
+
"expo-build-properties": "56.0.0-canary-20260506-03817f5",
|
|
62
|
+
"expo-clipboard": "56.0.0-canary-20260506-03817f5",
|
|
63
|
+
"expo-constants": "56.0.0-canary-20260506-03817f5",
|
|
64
|
+
"expo-dev-client": "56.0.0-canary-20260506-03817f5",
|
|
65
|
+
"expo-device": "56.0.0-canary-20260506-03817f5",
|
|
66
|
+
"expo-font": "56.0.0-canary-20260506-03817f5",
|
|
67
|
+
"expo-glass-effect": "56.0.0-canary-20260506-03817f5",
|
|
68
|
+
"expo-haptics": "56.0.0-canary-20260506-03817f5",
|
|
69
|
+
"expo-image": "56.0.0-canary-20260506-03817f5",
|
|
70
|
+
"expo-image-picker": "56.0.0-canary-20260506-03817f5",
|
|
71
|
+
"expo-insights": "56.0.0-canary-20260506-03817f5",
|
|
72
|
+
"expo-linking": "56.0.0-canary-20260506-03817f5",
|
|
73
|
+
"expo-local-authentication": "56.0.0-canary-20260506-03817f5",
|
|
74
|
+
"expo-network": "56.0.0-canary-20260506-03817f5",
|
|
75
|
+
"expo-notifications": "56.0.0-canary-20260506-03817f5",
|
|
76
|
+
"expo-router": "56.0.0-canary-20260506-03817f5",
|
|
77
|
+
"expo-secure-store": "56.0.0-canary-20260506-03817f5",
|
|
78
|
+
"expo-sharing": "56.0.0-canary-20260506-03817f5",
|
|
79
|
+
"expo-splash-screen": "56.0.0-canary-20260506-03817f5",
|
|
80
|
+
"expo-sqlite": "56.0.0-canary-20260506-03817f5",
|
|
81
|
+
"expo-status-bar": "56.0.0-canary-20260506-03817f5",
|
|
82
|
+
"expo-symbols": "56.0.0-canary-20260506-03817f5",
|
|
83
|
+
"expo-system-ui": "56.0.0-canary-20260506-03817f5",
|
|
84
|
+
"expo-task-manager": "56.0.0-canary-20260506-03817f5",
|
|
85
|
+
"expo-updates": "56.0.0-canary-20260506-03817f5",
|
|
86
|
+
"expo-web-browser": "56.0.0-canary-20260506-03817f5",
|
|
87
|
+
"react": "19.2.3",
|
|
88
|
+
"react-native": "0.85.3",
|
|
89
|
+
"react-native-gesture-handler": "~2.31.2",
|
|
90
|
+
"react-native-keyboard-controller": "1.21.6",
|
|
91
|
+
"react-native-reanimated": "4.3.0",
|
|
92
|
+
"react-native-safe-area-context": "~5.7.0",
|
|
93
|
+
"react-native-screens": "4.25.0-beta.1",
|
|
94
|
+
"react-native-worklets": "0.8.3",
|
|
95
|
+
"zod": "^4.4.3"
|
|
96
|
+
},
|
|
97
|
+
"devDependencies": {
|
|
98
|
+
"@ramonclaudio/vexpo": "^0.1.0",
|
|
99
|
+
"@types/node": "^25.6.0",
|
|
100
|
+
"@types/react": "~19.2.14",
|
|
101
|
+
"@vitest/ui": "^4.1.5",
|
|
102
|
+
"convex-test": "^0.0.51",
|
|
103
|
+
"oxfmt": "^0.48.0",
|
|
104
|
+
"oxlint": "^1.63.0",
|
|
105
|
+
"tsx": "^4.21.0",
|
|
106
|
+
"typescript": "^6.0.3",
|
|
107
|
+
"vitest": "^4.1.5"
|
|
108
|
+
},
|
|
109
|
+
"overrides": {
|
|
110
|
+
"@better-auth/passkey": "1.6.9",
|
|
111
|
+
"@expo/ui": "56.0.0-canary-20260506-03817f5"
|
|
112
|
+
},
|
|
113
|
+
"expo": {
|
|
114
|
+
"install": {
|
|
115
|
+
"exclude": [
|
|
116
|
+
"react-native-reanimated"
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
"doctor": {
|
|
120
|
+
"reactNativeDirectoryCheck": {
|
|
121
|
+
"enabled": true,
|
|
122
|
+
"listUnknownPackages": true,
|
|
123
|
+
"exclude": [
|
|
124
|
+
"expo-modules-jsi"
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
From 53c46f0ac639231af3e8ac975d36851658f4e0b9 Mon Sep 17 00:00:00 2001
|
|
2
|
+
From: Ray <hello@ramonclaudio.com>
|
|
3
|
+
Date: Thu, 7 May 2026 19:07:36 -0400
|
|
4
|
+
Subject: [PATCH] fix(react): wrap fetchAccessToken in new Promise to fix
|
|
5
|
+
useConvexAuth on Hermes V1
|
|
6
|
+
|
|
7
|
+
The /convex/token response triggers a session rotation (via Better
|
|
8
|
+
Auth's Set-Cookie processing) plus a setCachedToken call inside the
|
|
9
|
+
bridge's .then. The next render rebuilds fetchAccessToken's
|
|
10
|
+
useCallback (keyed on [sessionId]) and fires
|
|
11
|
+
ConvexAuthStateFirstEffect's client.setAuth a second time.
|
|
12
|
+
|
|
13
|
+
On Hermes V1 native async (Expo SDK 56 canary 2026-05-05+ since
|
|
14
|
+
expo/expo#45345 dropped @babel/plugin-transform-async-to-generator),
|
|
15
|
+
that second setAuth lands inside the first setConfig's await window
|
|
16
|
+
in authentication_manager.ts. fetchTokenAndGuardAgainstRace bumps
|
|
17
|
+
configVersion on entry and the original await sees the stale value,
|
|
18
|
+
returning isFromOutdatedConfig: true. setConfig bails without
|
|
19
|
+
resumeSocket() and the chain repeats.
|
|
20
|
+
|
|
21
|
+
Drop the async keyword and wrap the body in new Promise(executor)
|
|
22
|
+
directly. The constructor's resolve(thenable) schedules a
|
|
23
|
+
NewPromiseResolveThenableJob microtask, the same hop regenerator's
|
|
24
|
+
_asyncToGenerator provides. With the hop in place the second setAuth
|
|
25
|
+
lands after the first setConfig finishes rather than during its
|
|
26
|
+
await window.
|
|
27
|
+
---
|
|
28
|
+
src/react/index.tsx | 48 ++++++++++++++++++++++++---------------------
|
|
29
|
+
1 file changed, 26 insertions(+), 22 deletions(-)
|
|
30
|
+
|
|
31
|
+
diff --git a/src/react/index.tsx b/src/react/index.tsx
|
|
32
|
+
index dc10909a..2f906878 100644
|
|
33
|
+
--- a/src/react/index.tsx
|
|
34
|
+
+++ b/src/react/index.tsx
|
|
35
|
+
@@ -127,30 +127,34 @@ function useUseAuthFromBetterAuth(
|
|
36
|
+
}
|
|
37
|
+
}, [session, isSessionPending]);
|
|
38
|
+
const fetchAccessToken = useCallback(
|
|
39
|
+
- async ({
|
|
40
|
+
+ ({
|
|
41
|
+
forceRefreshToken = false,
|
|
42
|
+
}: { forceRefreshToken?: boolean } = {}) => {
|
|
43
|
+
- if (cachedToken && !forceRefreshToken) {
|
|
44
|
+
- return cachedToken;
|
|
45
|
+
- }
|
|
46
|
+
- if (!forceRefreshToken && pendingTokenRef.current) {
|
|
47
|
+
- return pendingTokenRef.current;
|
|
48
|
+
- }
|
|
49
|
+
- pendingTokenRef.current = authClient.convex
|
|
50
|
+
- .token({ fetchOptions: { throw: false } })
|
|
51
|
+
- .then(({ data }) => {
|
|
52
|
+
- const token = data?.token || null;
|
|
53
|
+
- setCachedToken(token);
|
|
54
|
+
- return token;
|
|
55
|
+
- })
|
|
56
|
+
- .catch(() => {
|
|
57
|
+
- setCachedToken(null);
|
|
58
|
+
- return null;
|
|
59
|
+
- })
|
|
60
|
+
- .finally(() => {
|
|
61
|
+
- pendingTokenRef.current = null;
|
|
62
|
+
- });
|
|
63
|
+
- return pendingTokenRef.current;
|
|
64
|
+
+ return new Promise<string | null>((resolve, reject) => {
|
|
65
|
+
+ if (cachedToken && !forceRefreshToken) {
|
|
66
|
+
+ resolve(cachedToken);
|
|
67
|
+
+ return;
|
|
68
|
+
+ }
|
|
69
|
+
+ if (!forceRefreshToken && pendingTokenRef.current) {
|
|
70
|
+
+ pendingTokenRef.current.then(resolve, reject);
|
|
71
|
+
+ return;
|
|
72
|
+
+ }
|
|
73
|
+
+ pendingTokenRef.current = authClient.convex
|
|
74
|
+
+ .token({ fetchOptions: { throw: false } })
|
|
75
|
+
+ .then(({ data }) => {
|
|
76
|
+
+ const token = data?.token || null;
|
|
77
|
+
+ setCachedToken(token);
|
|
78
|
+
+ return token;
|
|
79
|
+
+ })
|
|
80
|
+
+ .catch(() => {
|
|
81
|
+
+ setCachedToken(null);
|
|
82
|
+
+ return null;
|
|
83
|
+
+ })
|
|
84
|
+
+ .finally(() => {
|
|
85
|
+
+ pendingTokenRef.current = null;
|
|
86
|
+
+ });
|
|
87
|
+
+ pendingTokenRef.current.then(resolve, reject);
|
|
88
|
+
+ });
|
|
89
|
+
},
|
|
90
|
+
// Build a new fetchAccessToken to trigger setAuth() whenever the
|
|
91
|
+
// session changes.
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# plugins
|
|
2
|
+
|
|
3
|
+
## `with-auto-signing.js`
|
|
4
|
+
|
|
5
|
+
Forces automatic code signing for the Xcode project during local `prebuild`. Sets `CODE_SIGN_STYLE = Automatic` on every build configuration with a `PRODUCT_BUNDLE_IDENTIFIER`, drops any leftover `PROVISIONING_PROFILE*` keys, and sets `DEVELOPMENT_TEAM` from `ios.appleTeamId`.
|
|
6
|
+
|
|
7
|
+
No-ops when `EAS_BUILD` is set, so EAS continues to use the provisioning profile from the build credentials.
|
|
8
|
+
|
|
9
|
+
Use: local `bun run ios` on a physical device without juggling provisioning profiles in Xcode.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const { withXcodeProject } = require("@expo/config-plugins");
|
|
2
|
+
|
|
3
|
+
const withAutoSigning = (config) => {
|
|
4
|
+
if (process.env.EAS_BUILD) return config;
|
|
5
|
+
|
|
6
|
+
return withXcodeProject(config, async (cfg) => {
|
|
7
|
+
const xcodeProject = cfg.modResults;
|
|
8
|
+
|
|
9
|
+
const targetName = xcodeProject.getFirstTarget()?.firstTarget?.name;
|
|
10
|
+
if (!targetName) {
|
|
11
|
+
console.warn("[with-auto-signing] Could not find main target");
|
|
12
|
+
return cfg;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const buildConfigurations = xcodeProject.pbxXCBuildConfigurationSection();
|
|
16
|
+
for (const key in buildConfigurations) {
|
|
17
|
+
const buildConfig = buildConfigurations[key];
|
|
18
|
+
if (buildConfig.buildSettings && buildConfig.buildSettings.PRODUCT_BUNDLE_IDENTIFIER) {
|
|
19
|
+
buildConfig.buildSettings.CODE_SIGN_STYLE = "Automatic";
|
|
20
|
+
if (cfg.ios?.appleTeamId) {
|
|
21
|
+
buildConfig.buildSettings.DEVELOPMENT_TEAM = cfg.ios.appleTeamId;
|
|
22
|
+
}
|
|
23
|
+
delete buildConfig.buildSettings.PROVISIONING_PROFILE;
|
|
24
|
+
delete buildConfig.buildSettings.PROVISIONING_PROFILE_SPECIFIER;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const projectSection = xcodeProject.pbxProjectSection();
|
|
29
|
+
for (const key in projectSection) {
|
|
30
|
+
const project = projectSection[key];
|
|
31
|
+
if (project.attributes && project.attributes.TargetAttributes) {
|
|
32
|
+
for (const targetId in project.attributes.TargetAttributes) {
|
|
33
|
+
project.attributes.TargetAttributes[targetId].ProvisioningStyle = "Automatic";
|
|
34
|
+
if (cfg.ios?.appleTeamId) {
|
|
35
|
+
project.attributes.TargetAttributes[targetId].DevelopmentTeam = cfg.ios.appleTeamId;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return cfg;
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
module.exports = withAutoSigning;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const { withDangerousMod } = require("@expo/config-plugins");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
const MARKER = "# with-pod-deployment-target";
|
|
6
|
+
|
|
7
|
+
const buildInjection = (target) => `
|
|
8
|
+
${MARKER}
|
|
9
|
+
installer.pods_project.targets.each do |t|
|
|
10
|
+
t.build_configurations.each do |c|
|
|
11
|
+
c.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '${target}'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
const withPodDeploymentTarget = (config, { target = "16.4" } = {}) =>
|
|
17
|
+
withDangerousMod(config, [
|
|
18
|
+
"ios",
|
|
19
|
+
(cfg) => {
|
|
20
|
+
const podfilePath = path.join(cfg.modRequest.platformProjectRoot, "Podfile");
|
|
21
|
+
const podfile = fs.readFileSync(podfilePath, "utf8");
|
|
22
|
+
if (podfile.includes(MARKER)) return cfg;
|
|
23
|
+
|
|
24
|
+
const injection = buildInjection(target);
|
|
25
|
+
const re = /(react_native_post_install\([\s\S]*?\n\s*\)\n)/;
|
|
26
|
+
if (!re.test(podfile)) {
|
|
27
|
+
throw new Error("with-pod-deployment-target: react_native_post_install call not found");
|
|
28
|
+
}
|
|
29
|
+
const next = podfile.replace(re, `$1${injection}`);
|
|
30
|
+
fs.writeFileSync(podfilePath, next);
|
|
31
|
+
return cfg;
|
|
32
|
+
},
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
module.exports = withPodDeploymentTarget;
|