@ramonclaudio/create-vexpo 0.1.0 → 0.1.2
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 +10 -10
- package/dist/index.js +8 -7
- package/dist/templates/default/.eas/workflows/asc-events.yml +9 -6
- package/dist/templates/default/.eas/workflows/deploy-production.yml +28 -15
- package/dist/templates/default/.eas/workflows/e2e-tests.yml +3 -2
- package/dist/templates/default/.eas/workflows/pr-preview.yml +12 -21
- package/dist/templates/default/.eas/workflows/release.yml +3 -7
- package/dist/templates/default/.eas/workflows/rollback.yml +54 -28
- package/dist/templates/default/.eas/workflows/rollout.yml +27 -33
- package/dist/templates/default/.eas/workflows/rotate-apple-jwt.yml +1 -5
- package/dist/templates/default/.eas/workflows/testflight.yml +3 -7
- package/dist/templates/default/.github/workflows/check.yml +20 -12
- package/dist/templates/default/.maestro/launch.yaml +19 -10
- package/dist/templates/default/AGENTS.md +25 -8
- package/dist/templates/default/DESIGN.md +14 -10
- package/dist/templates/default/README.md +83 -78
- package/dist/templates/default/SETUP.md +159 -152
- package/dist/templates/default/__tests__/convex/_auth-harness.test.ts +112 -0
- package/dist/templates/default/__tests__/convex/_harness.ts +132 -0
- package/dist/templates/default/__tests__/convex/appAttest.test.ts +172 -0
- package/dist/templates/default/__tests__/convex/appAttestStore.test.ts +48 -0
- package/dist/templates/default/__tests__/convex/pushTokens-remove.test.ts +106 -0
- package/dist/templates/default/__tests__/convex/pushTokens-upsert.test.ts +146 -0
- package/dist/templates/default/__tests__/convex/users-deleteAccount.test.ts +140 -0
- package/dist/templates/default/__tests__/convex/users-deleteAvatar.test.ts +104 -0
- package/dist/templates/default/__tests__/convex/users-getMe.test.ts +98 -0
- package/dist/templates/default/__tests__/convex/users-getUser.test.ts +120 -0
- package/dist/templates/default/__tests__/convex/users-hardDeleteExpired.test.ts +67 -0
- package/dist/templates/default/__tests__/convex/users-restoreAccount.test.ts +96 -0
- package/dist/templates/default/__tests__/convex/users-updateAvatar.test.ts +92 -0
- package/dist/templates/default/__tests__/convex/users-updateProfile.test.ts +126 -0
- package/dist/templates/default/__tests__/convex/webhook.test.ts +31 -0
- package/dist/templates/default/__tests__/lib/deep-link.test.ts +51 -6
- package/dist/templates/default/__tests__/lib/schemas.test.ts +205 -0
- package/dist/templates/default/__tests__/lib/text-style.test.ts +31 -0
- package/dist/templates/default/_env.example +7 -7
- package/dist/templates/default/_gitattributes +1 -1
- package/dist/templates/default/_gitignore +17 -2
- package/dist/templates/default/_npmrc +7 -0
- package/dist/templates/default/_oxlintrc.json +1 -1
- package/dist/templates/default/app-store/accessibility.config.json +20 -0
- package/dist/templates/default/app-store/privacy.config.json +27 -0
- package/dist/templates/default/app.config.ts +105 -33
- package/dist/templates/default/app.json +1 -9
- package/dist/templates/default/convex/_generated/api.d.ts +12 -0
- package/dist/templates/default/convex/admin.ts +0 -13
- package/dist/templates/default/convex/appAttest.ts +467 -0
- package/dist/templates/default/convex/appAttestStore.ts +141 -0
- package/dist/templates/default/convex/apple.ts +53 -0
- package/dist/templates/default/convex/auth.ts +6 -45
- package/dist/templates/default/convex/constants.ts +2 -7
- package/dist/templates/default/convex/crons.ts +12 -5
- package/dist/templates/default/convex/email.ts +4 -24
- package/dist/templates/default/convex/env.ts +0 -4
- package/dist/templates/default/convex/errors.ts +0 -7
- package/dist/templates/default/convex/functions.ts +0 -26
- package/dist/templates/default/convex/http.ts +3 -5
- package/dist/templates/default/convex/log.ts +2 -25
- package/dist/templates/default/convex/pushSender.ts +145 -0
- package/dist/templates/default/convex/pushTokens.ts +110 -13
- package/dist/templates/default/convex/rateLimit.ts +8 -39
- package/dist/templates/default/convex/schema.ts +48 -5
- package/dist/templates/default/convex/tsconfig.json +1 -0
- package/dist/templates/default/convex/users.ts +143 -61
- package/dist/templates/default/convex/validators.ts +1 -38
- package/dist/templates/default/convex/webhook.ts +1 -31
- package/dist/templates/default/convex.json +1 -2
- package/dist/templates/default/metro.config.js +9 -1
- package/dist/templates/default/package.json +67 -70
- package/dist/templates/default/plugins/README.md +5 -1
- package/dist/templates/default/scripts/README.md +9 -9
- package/dist/templates/default/scripts/_run.mjs +3 -20
- package/dist/templates/default/scripts/clean.ts +81 -69
- package/dist/templates/default/scripts/gen-update-cert.mjs +98 -0
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/(home)/index.tsx +21 -6
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/(home,search)/_layout.tsx +9 -8
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/(search)/index.tsx +26 -24
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/_layout.tsx +3 -4
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/settings/_layout.tsx +10 -6
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/settings/index.tsx +81 -51
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/settings/preferences.tsx +72 -12
- package/dist/templates/default/src/app/(app)/_layout.tsx +147 -0
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/_layout.tsx +4 -5
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/forgot-password.tsx +15 -9
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/reset-password.tsx +88 -14
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/sign-in.tsx +65 -35
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/sign-up.tsx +131 -196
- package/dist/templates/default/src/app/(app)/debug.tsx +479 -0
- package/dist/templates/default/{app → src/app}/(app)/help.tsx +76 -64
- package/dist/templates/default/{app → src/app}/(app)/linked.tsx +21 -27
- package/dist/templates/default/{app → src/app}/(app)/privacy.tsx +35 -8
- package/dist/templates/default/src/app/(app)/profile/change-password.tsx +264 -0
- package/dist/templates/default/{app/(app)/profile.tsx → src/app/(app)/profile/index.tsx} +179 -255
- package/dist/templates/default/src/app/(app)/restore-account.tsx +192 -0
- package/dist/templates/default/src/app/(app)/sessions.tsx +287 -0
- package/dist/templates/default/src/app/(app)/welcome.tsx +194 -0
- package/dist/templates/default/src/app/+native-intent.tsx +25 -0
- package/dist/templates/default/src/app/+not-found.tsx +43 -0
- package/dist/templates/default/{app → src/app}/_layout.tsx +28 -37
- package/dist/templates/default/src/components/auth/apple-button.tsx +51 -0
- package/dist/templates/default/{components → src/components}/auth/otp-verification.tsx +79 -58
- package/dist/templates/default/{components → src/components}/auth/password-field.tsx +74 -18
- package/dist/templates/default/src/components/auth/segmented-toggle.tsx +71 -0
- package/dist/templates/default/src/components/ui/content-unavailable.tsx +81 -0
- package/dist/templates/default/src/components/ui/convex-error.tsx +21 -0
- package/dist/templates/default/src/components/ui/error-boundary.tsx +89 -0
- package/dist/templates/default/{components → src/components}/ui/loading-screen.tsx +5 -4
- package/dist/templates/default/{components → src/components}/ui/material.tsx +50 -17
- package/dist/templates/default/src/components/ui/offline-banner.tsx +59 -0
- package/dist/templates/default/{components → src/components}/ui/prominent-button.tsx +8 -11
- package/dist/templates/default/{components → src/components}/ui/skeleton.tsx +31 -13
- package/dist/templates/default/src/components/ui/status-text.tsx +64 -0
- package/dist/templates/default/src/components/ui/update-banner.tsx +85 -0
- package/dist/templates/default/{constants → src/constants}/layout.ts +0 -6
- package/dist/templates/default/{constants → src/constants}/theme.ts +49 -64
- package/dist/templates/default/{constants → src/constants}/ui.ts +13 -4
- package/dist/templates/default/src/hooks/use-debounce.ts +12 -0
- package/dist/templates/default/src/hooks/use-deep-link.ts +51 -0
- package/dist/templates/default/src/hooks/use-delete-account.ts +35 -0
- package/dist/templates/default/src/hooks/use-motion-screen-options.ts +13 -0
- package/dist/templates/default/src/hooks/use-network.ts +34 -0
- package/dist/templates/default/{hooks → src/hooks}/use-notifications.ts +39 -30
- package/dist/templates/default/src/hooks/use-reduce-transparency.ts +30 -0
- package/dist/templates/default/{hooks → src/hooks}/use-theme.ts +0 -5
- package/dist/templates/default/src/lib/appAttest.ts +78 -0
- package/dist/templates/default/src/lib/assets.ts +9 -0
- package/dist/templates/default/src/lib/deep-link.ts +82 -0
- package/dist/templates/default/{lib → src/lib}/dev-menu.ts +0 -4
- package/dist/templates/default/{lib → src/lib}/device.ts +1 -13
- package/dist/templates/default/{lib → src/lib}/dynamic-font.ts +13 -10
- package/dist/templates/default/src/lib/dynamic-symbol-size.ts +33 -0
- package/dist/templates/default/src/lib/masks.ts +21 -0
- package/dist/templates/default/src/lib/native-state.ts +20 -0
- package/dist/templates/default/{lib → src/lib}/notifications.ts +7 -45
- package/dist/templates/default/{lib → src/lib}/preferences.ts +0 -2
- package/dist/templates/default/{lib → src/lib}/schemas.ts +19 -16
- package/dist/templates/default/src/lib/text-style.ts +20 -0
- package/dist/templates/default/{lib → src/lib}/updates.ts +0 -7
- package/dist/templates/default/store.config.json +1 -1
- package/dist/templates/default/tsconfig.json +3 -1
- package/dist/templates/default/vitest.config.ts +8 -1
- package/package.json +5 -5
- package/dist/templates/default/app/(app)/_layout.tsx +0 -73
- package/dist/templates/default/app/(app)/debug.tsx +0 -389
- package/dist/templates/default/app/(app)/sessions.tsx +0 -191
- package/dist/templates/default/app/(app)/welcome.tsx +0 -140
- package/dist/templates/default/app/+native-intent.tsx +0 -14
- package/dist/templates/default/app/+not-found.tsx +0 -51
- package/dist/templates/default/bun.lock +0 -1860
- package/dist/templates/default/components/auth/segmented-toggle.tsx +0 -47
- package/dist/templates/default/components/ui/convex-error.tsx +0 -32
- package/dist/templates/default/components/ui/error-boundary.tsx +0 -57
- package/dist/templates/default/components/ui/offline-banner.tsx +0 -58
- package/dist/templates/default/components/ui/status-text.tsx +0 -49
- package/dist/templates/default/components/ui/update-banner.tsx +0 -82
- package/dist/templates/default/fingerprint.config.js +0 -9
- package/dist/templates/default/hooks/use-debounce.ts +0 -20
- package/dist/templates/default/hooks/use-deep-link.ts +0 -43
- package/dist/templates/default/hooks/use-network.ts +0 -11
- package/dist/templates/default/lib/assets.ts +0 -17
- package/dist/templates/default/lib/deep-link.ts +0 -71
- package/dist/templates/default/patches/PR-368.patch +0 -91
- package/dist/templates/default/patches/convex-dev-better-auth-0.12.2.tgz +0 -0
- /package/dist/templates/default/{hooks → src/hooks}/use-navigation-tracking.ts +0 -0
- /package/dist/templates/default/{hooks → src/hooks}/use-onboarding.ts +0 -0
- /package/dist/templates/default/{hooks → src/hooks}/use-reduced-motion.ts +0 -0
- /package/dist/templates/default/{hooks → src/hooks}/use-updates.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/a11y.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/app.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/auth-client.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/convex-auth.tsx +0 -0
- /package/dist/templates/default/{lib → src/lib}/env.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/haptics.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/storage.ts +0 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { textStyleForSize } from "@/lib/text-style";
|
|
4
|
+
|
|
5
|
+
describe("textStyleForSize", () => {
|
|
6
|
+
test("maps every point size in the template's type scale to a Font.TextStyle", () => {
|
|
7
|
+
expect(textStyleForSize(34)).toBe("largeTitle");
|
|
8
|
+
expect(textStyleForSize(32)).toBe("largeTitle");
|
|
9
|
+
expect(textStyleForSize(30)).toBe("title");
|
|
10
|
+
expect(textStyleForSize(28)).toBe("title");
|
|
11
|
+
expect(textStyleForSize(24)).toBe("title2");
|
|
12
|
+
expect(textStyleForSize(22)).toBe("title2");
|
|
13
|
+
expect(textStyleForSize(20)).toBe("title3");
|
|
14
|
+
expect(textStyleForSize(18)).toBe("title3");
|
|
15
|
+
expect(textStyleForSize(17)).toBe("body");
|
|
16
|
+
expect(textStyleForSize(16)).toBe("callout");
|
|
17
|
+
expect(textStyleForSize(15)).toBe("subheadline");
|
|
18
|
+
expect(textStyleForSize(14)).toBe("footnote");
|
|
19
|
+
expect(textStyleForSize(13)).toBe("footnote");
|
|
20
|
+
expect(textStyleForSize(12)).toBe("caption");
|
|
21
|
+
expect(textStyleForSize(11)).toBe("caption2");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("clamps sizes below the scale to caption2", () => {
|
|
25
|
+
expect(textStyleForSize(8)).toBe("caption2");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("clamps sizes above the scale to largeTitle", () => {
|
|
29
|
+
expect(textStyleForSize(64)).toBe("largeTitle");
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# .env.local template. Copy to .env.local then run `
|
|
2
|
-
# `
|
|
1
|
+
# .env.local template. Copy to .env.local then run `npm run setup`.
|
|
2
|
+
# `npm run setup` writes most of these for you. The two identity vars
|
|
3
3
|
# (EXPO_PUBLIC_APP_BUNDLE_ID and EXPO_PUBLIC_APPLE_TEAM_ID) are prompted by
|
|
4
|
-
# `
|
|
4
|
+
# `npm run setup:convex`. Edit by hand if you prefer.
|
|
5
5
|
|
|
6
6
|
# ---------------------------------------------------------------------------
|
|
7
|
-
# Convex deployment (written by `
|
|
7
|
+
# Convex deployment (written by `npm run setup:convex`)
|
|
8
8
|
# ---------------------------------------------------------------------------
|
|
9
9
|
CONVEX_DEPLOYMENT=
|
|
10
10
|
EXPO_PUBLIC_CONVEX_URL=
|
|
@@ -12,7 +12,7 @@ EXPO_PUBLIC_CONVEX_SITE_URL=
|
|
|
12
12
|
EXPO_PUBLIC_SITE_URL=
|
|
13
13
|
|
|
14
14
|
# ---------------------------------------------------------------------------
|
|
15
|
-
# iOS identity (prompted by `
|
|
15
|
+
# iOS identity (prompted by `npm run setup:convex`, also pushed to Convex env)
|
|
16
16
|
# ---------------------------------------------------------------------------
|
|
17
17
|
# Reverse-DNS bundle id, e.g. com.you.vexpo.
|
|
18
18
|
EXPO_PUBLIC_APP_BUNDLE_ID=
|
|
@@ -26,9 +26,9 @@ EXPO_PUBLIC_APPLE_TEAM_ID=
|
|
|
26
26
|
# EXPO_PUBLIC_EXPO_OWNER=
|
|
27
27
|
# Toggles `name` to "Vexpo (Dev)" so dev and prod can install side-by-side.
|
|
28
28
|
# APP_VARIANT=development
|
|
29
|
-
# Pre-fills the full-access Resend key for `
|
|
29
|
+
# Pre-fills the full-access Resend key for `npm run setup:resend` (else prompts).
|
|
30
30
|
# RESEND_FULL_ACCESS_KEY=
|
|
31
|
-
# Skips prompts in `
|
|
31
|
+
# Skips prompts in `npm run setup:apple` / `setup:asc`.
|
|
32
32
|
# APPLE_P8_PATH=
|
|
33
33
|
# APPLE_AUTH_KEY_PATH=
|
|
34
34
|
# APPLE_ASC_ISSUER_ID=
|
|
@@ -5,6 +5,7 @@ node_modules/
|
|
|
5
5
|
.env
|
|
6
6
|
.env.*
|
|
7
7
|
!.env.example
|
|
8
|
+
.envrc
|
|
8
9
|
|
|
9
10
|
# Expo
|
|
10
11
|
.expo/
|
|
@@ -22,12 +23,26 @@ ios/
|
|
|
22
23
|
AuthKey_*
|
|
23
24
|
SubscriptionKey_*
|
|
24
25
|
|
|
26
|
+
# EAS Update code-signing private key. The matching public cert at
|
|
27
|
+
# `certs/certificate.pem` IS committed (it's bundled in the .ipa and used
|
|
28
|
+
# on-device to verify OTA bundles). The private key lives only on the
|
|
29
|
+
# author's machine + as an EAS file-type secret (`EAS_UPDATE_PRIVATE_KEY`).
|
|
30
|
+
keys/
|
|
31
|
+
private-key.pem
|
|
32
|
+
*.private-key.pem
|
|
33
|
+
|
|
25
34
|
# EAS (CLI state; keep workflows tracked)
|
|
26
35
|
.eas/*
|
|
27
36
|
!.eas/workflows/
|
|
28
37
|
|
|
29
|
-
# Agents / local AI tooling
|
|
30
|
-
.claude/
|
|
38
|
+
# Agents / local AI tooling.
|
|
39
|
+
# The monorepo .gitignore drops .claude/ repo-wide; the template re-includes
|
|
40
|
+
# its own .claude/settings.json so the scaffolded SDK 56 agent config ships
|
|
41
|
+
# with the template. Everything else inside .claude/ (transcripts, hooks,
|
|
42
|
+
# local state) stays untracked.
|
|
43
|
+
!.claude/
|
|
44
|
+
.claude/*
|
|
45
|
+
!.claude/settings.json
|
|
31
46
|
.agents/
|
|
32
47
|
.cursor/
|
|
33
48
|
.aider*
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Expo's preview tags advance React + React DOM independently of the rest of
|
|
2
|
+
# the dep tree, so transitive peerOptional `react-dom` resolves to a newer
|
|
3
|
+
# version than the root `react` pin. npm 7+ rejects this by default (ERESOLVE);
|
|
4
|
+
# bun installs it loose. Setting legacy-peer-deps mirrors bun's behavior and
|
|
5
|
+
# matches Expo's own template defaults. Drop this once the Expo SDK ships with
|
|
6
|
+
# react + react-dom aligned and EAS Build's npm version is happy with it.
|
|
7
|
+
legacy-peer-deps=true
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"import/no-unassigned-import": "off",
|
|
20
20
|
"no-await-in-loop": "off",
|
|
21
21
|
"unicorn/consistent-function-scoping": "off",
|
|
22
|
-
"no-underscore-dangle": ["warn", { "allow": ["_id", "_creationTime"] }],
|
|
22
|
+
"no-underscore-dangle": ["warn", { "allow": ["_id", "_creationTime", "_contentAvailable"] }],
|
|
23
23
|
"no-shadow": ["warn", { "allow": ["resolve", "reject"] }]
|
|
24
24
|
},
|
|
25
25
|
"ignorePatterns": [
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Drives `vexpo asc:accessibility lint`. The starter template ships VoiceOver labels, DynamicType, reduced-motion respect, and DynamicColorIOS for high-contrast support. Audit each feature against your actual user flows before declaring FULLY_SUPPORTS in production.",
|
|
3
|
+
"entries": [
|
|
4
|
+
{
|
|
5
|
+
"deviceFamily": "IPHONE",
|
|
6
|
+
"features": {
|
|
7
|
+
"VOICE_OVER": "FULLY_SUPPORTS",
|
|
8
|
+
"VOICE_CONTROL": "FULLY_SUPPORTS",
|
|
9
|
+
"LARGER_TEXT": "FULLY_SUPPORTS",
|
|
10
|
+
"DARK_INTERFACE": "FULLY_SUPPORTS",
|
|
11
|
+
"SUFFICIENT_CONTRAST": "FULLY_SUPPORTS",
|
|
12
|
+
"DIFFERENTIATION_WITHOUT_COLOR_ALONE": "FULLY_SUPPORTS",
|
|
13
|
+
"REDUCED_MOTION": "FULLY_SUPPORTS",
|
|
14
|
+
"CAPTIONS": "NOT_APPLICABLE",
|
|
15
|
+
"AUDIO_DESCRIPTIONS": "NOT_APPLICABLE"
|
|
16
|
+
},
|
|
17
|
+
"notes": "Verify against the actual screens in your app before submission. The defaults reflect what the vexpo template ships, not what your app delivers."
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Drives `vexpo asc:privacy lint`. The dashboard form at App Store Connect is the source of truth for submission; this file is for keeping a versioned, reviewable copy alongside the code. Default state: the starter collects no data beyond what Better Auth needs for sign-in (email + opaque user id), which `IDENTIFIERS` covers. Tighten or expand to match your app.",
|
|
3
|
+
"collectsData": true,
|
|
4
|
+
"entries": [
|
|
5
|
+
{
|
|
6
|
+
"category": "CONTACT_INFO",
|
|
7
|
+
"collected": true,
|
|
8
|
+
"usedForTracking": false,
|
|
9
|
+
"linkedToUser": true,
|
|
10
|
+
"purposes": ["APP_FUNCTIONALITY"]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"category": "IDENTIFIERS",
|
|
14
|
+
"collected": true,
|
|
15
|
+
"usedForTracking": false,
|
|
16
|
+
"linkedToUser": true,
|
|
17
|
+
"purposes": ["APP_FUNCTIONALITY"]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"category": "DIAGNOSTICS",
|
|
21
|
+
"collected": true,
|
|
22
|
+
"usedForTracking": false,
|
|
23
|
+
"linkedToUser": false,
|
|
24
|
+
"purposes": ["ANALYTICS", "APP_FUNCTIONALITY"]
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -1,10 +1,22 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
|
|
1
4
|
import type { ConfigContext, ExpoConfig } from "expo/config";
|
|
2
5
|
|
|
3
|
-
import
|
|
6
|
+
// Read JSON via fs, not a static import or `require`. Node 24 strips TS types
|
|
7
|
+
// natively and loads this file as ESM (it has `export default`), where bare
|
|
8
|
+
// JSON imports need an attribute and `require`/`__dirname` don't exist. The
|
|
9
|
+
// expo CLI path transpiles to CJS via sucrase. `fs` + `process.cwd()` is the
|
|
10
|
+
// only shape that reads identically under both, and cwd is the project root in
|
|
11
|
+
// every context that evaluates this config (expo, eas, prebuild).
|
|
12
|
+
const pkg = JSON.parse(readFileSync(resolve(process.cwd(), "package.json"), "utf8")) as {
|
|
13
|
+
name: string;
|
|
14
|
+
version: string;
|
|
15
|
+
};
|
|
4
16
|
|
|
5
17
|
const IS_DEV = process.env.APP_VARIANT === "development";
|
|
6
18
|
|
|
7
|
-
// Identity comes from .env.local (written by `
|
|
19
|
+
// Identity comes from .env.local (written by `npm run setup:convex`). Fallbacks
|
|
8
20
|
// keep `expo prebuild` from crashing on a fresh checkout, but a real build
|
|
9
21
|
// requires real values.
|
|
10
22
|
const BUNDLE_ID = process.env.EXPO_PUBLIC_APP_BUNDLE_ID ?? `com.example.${pkg.name}`;
|
|
@@ -24,7 +36,9 @@ type StoreConfig = {
|
|
|
24
36
|
};
|
|
25
37
|
const storeConfig: StoreConfig | undefined = (() => {
|
|
26
38
|
try {
|
|
27
|
-
return
|
|
39
|
+
return JSON.parse(
|
|
40
|
+
readFileSync(resolve(process.cwd(), "store.config.json"), "utf8"),
|
|
41
|
+
) as StoreConfig;
|
|
28
42
|
} catch {
|
|
29
43
|
return undefined;
|
|
30
44
|
}
|
|
@@ -38,11 +52,24 @@ const SUPPORT = {
|
|
|
38
52
|
};
|
|
39
53
|
|
|
40
54
|
export default ({ config }: ConfigContext): ExpoConfig => {
|
|
41
|
-
// `eas init` writes `extra.eas.projectId` into app.json
|
|
42
|
-
// is merged into our return value via `...config
|
|
43
|
-
// `config.extra?.eas?.projectId`
|
|
44
|
-
//
|
|
45
|
-
|
|
55
|
+
// Primary source: `eas init` writes `extra.eas.projectId` into app.json, and
|
|
56
|
+
// the static config is merged into our return value via `...config`. Reading
|
|
57
|
+
// from `config.extra?.eas?.projectId` picks that up without hand-editing
|
|
58
|
+
// this file.
|
|
59
|
+
//
|
|
60
|
+
// Fallback: `process.env.EAS_PROJECT_ID`. Useful when the var is in the
|
|
61
|
+
// parent process env at eas-cli invocation time — e.g. shell export, direnv
|
|
62
|
+
// auto-load, or an eas.json profile `env` entry. Note that eas-cli itself
|
|
63
|
+
// does NOT auto-load `.env.local` for projectId resolution (it spawns
|
|
64
|
+
// `expo config` with `EXPO_NO_DOTENV=1` to keep config eval deterministic),
|
|
65
|
+
// so dropping `EAS_PROJECT_ID` into `.env.local` alone won't skip the
|
|
66
|
+
// `Configure this project?` prompt on a fresh checkout. The fallback DOES
|
|
67
|
+
// help vexpo's own CLI commands (which load `.env.local` themselves) and
|
|
68
|
+
// any tooling that pre-populates process.env before invoking expo/eas.
|
|
69
|
+
// See `README.md` for the recommended direnv-based local setup.
|
|
70
|
+
const projectId =
|
|
71
|
+
(config.extra as { eas?: { projectId?: string } } | undefined)?.eas?.projectId ??
|
|
72
|
+
process.env.EAS_PROJECT_ID;
|
|
46
73
|
|
|
47
74
|
return {
|
|
48
75
|
...config,
|
|
@@ -61,41 +88,60 @@ export default ({ config }: ConfigContext): ExpoConfig => {
|
|
|
61
88
|
scheme: "vexpo",
|
|
62
89
|
icon: "./assets/icon.png",
|
|
63
90
|
...(EXPO_OWNER ? { owner: EXPO_OWNER } : {}),
|
|
91
|
+
// Fingerprint policy: a native change auto-bumps the runtime hash so OTA
|
|
92
|
+
// updates never load against an incompatible binary. The local-vs-EAS
|
|
93
|
+
// drift that broke earlier SDK 56 builds no longer reproduces on current
|
|
94
|
+
// deps (precompiled modules + codegen land in `ios/`, which fingerprint
|
|
95
|
+
// ignores; jsi artifacts covered by @expo/fingerprint >=0.19.3 defaults),
|
|
96
|
+
// so no autolinking workaround is needed. Verified by unpatched EAS builds
|
|
97
|
+
// clearing CONFIGURE_EXPO_UPDATES.
|
|
64
98
|
runtimeVersion: { policy: "fingerprint" },
|
|
65
99
|
developmentClient: {
|
|
66
100
|
silentLaunch: true,
|
|
67
101
|
},
|
|
68
102
|
updates: {
|
|
103
|
+
// Derived from projectId, which resolves identically locally and on EAS
|
|
104
|
+
// workers because both read the plaintext `EAS_PROJECT_ID` env var. That
|
|
105
|
+
// symmetry keeps `extra.eas`/`updates.url`/`enabled` in fingerprint parity
|
|
106
|
+
// with no `sourceSkips`. If projectId is unset (fresh fork before setup),
|
|
107
|
+
// this is false on both sides: still consistent, OTA just stays inactive
|
|
108
|
+
// until configured.
|
|
69
109
|
enabled: !!projectId,
|
|
70
110
|
checkAutomatically: "ON_LOAD",
|
|
71
|
-
|
|
72
|
-
|
|
111
|
+
// Brief patience for the update server before falling back to the
|
|
112
|
+
// bundled JS. Zero blocks every cold launch indefinitely on flaky
|
|
113
|
+
// networks (hotel WiFi, captive portals). 2 seconds is enough for
|
|
114
|
+
// a fast check on good networks and a graceful timeout otherwise.
|
|
115
|
+
fallbackToCacheTimeout: 2000,
|
|
73
116
|
...(projectId ? { url: `https://u.expo.dev/${projectId}` } : {}),
|
|
74
117
|
// `expo-channel-name` request header is required for runtime channel
|
|
75
|
-
// surfing via `Updates.setUpdateRequestHeadersOverride`.
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
|
|
118
|
+
// surfing via `Updates.setUpdateRequestHeadersOverride`. EAS Build
|
|
119
|
+
// overwrites the baseline per-build from the eas.json build profile,
|
|
120
|
+
// so the value here only matters for local dev/prebuild without an
|
|
121
|
+
// EAS profile. `development` is the safe default; a missed profile
|
|
122
|
+
// override won't accidentally pull production OTA into a dev build.
|
|
123
|
+
requestHeaders: { "expo-channel-name": "development" },
|
|
80
124
|
// Only ship icon + splash with each OTA. Fonts, sounds, and other
|
|
81
125
|
// build-baked assets stay in the .ipa and never download. Shrinks
|
|
82
126
|
// bundle by ~95% on diff-able updates.
|
|
83
127
|
assetPatternsToBeBundled: ["assets/icon.png", "assets/splash-image-*.png"],
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
// (
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
128
|
+
// End-to-end OTA code signing. The cert is committed at
|
|
129
|
+
// `./certs/certificate.pem` and bundled with each .ipa; the device
|
|
130
|
+
// verifies every update against it before applying. The matching
|
|
131
|
+
// private key lives ONLY as an EAS file-type secret
|
|
132
|
+
// (`EAS_UPDATE_PRIVATE_KEY`) and never lands in the repo.
|
|
133
|
+
// Generate the keypair once with `npm run updates:gen-cert`, upload
|
|
134
|
+
// the key to EAS, and every subsequent `eas update` signs locally
|
|
135
|
+
// before publishing. A compromised CDN or EAS account cannot ship
|
|
136
|
+
// arbitrary JS. Until the cert exists the block stays off and
|
|
137
|
+
// updates ship unsigned (fine for prototyping; not for production).
|
|
138
|
+
// https://docs.expo.dev/eas-update/code-signing/
|
|
139
|
+
...(existsSync(resolve(process.cwd(), "certs", "certificate.pem"))
|
|
140
|
+
? {
|
|
141
|
+
codeSigningCertificate: "./certs/certificate.pem",
|
|
142
|
+
codeSigningMetadata: { keyid: "main", alg: "rsa-v1_5-sha256" },
|
|
143
|
+
}
|
|
144
|
+
: {}),
|
|
99
145
|
},
|
|
100
146
|
ios: {
|
|
101
147
|
supportsTablet: false,
|
|
@@ -108,6 +154,17 @@ export default ({ config }: ConfigContext): ExpoConfig => {
|
|
|
108
154
|
ITSAppUsesNonExemptEncryption: false,
|
|
109
155
|
LSApplicationQueriesSchemes: ["mailto", "tel", "sms", "itms-apps"],
|
|
110
156
|
},
|
|
157
|
+
// App Attest entitlement. `@expo/app-integrity` calls the
|
|
158
|
+
// DCAppAttestService API; iOS rejects unentitled access. The value
|
|
159
|
+
// `production` keeps every signed/distributed build (TestFlight,
|
|
160
|
+
// App Store) in the production AAGUID. Local debug builds with the
|
|
161
|
+
// Xcode debugger attached attest against the development AAGUID
|
|
162
|
+
// automatically without changing this value, so the same entry
|
|
163
|
+
// works across simulator-impossible and real-device paths.
|
|
164
|
+
// https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.devicecheck.appattest-environment
|
|
165
|
+
entitlements: {
|
|
166
|
+
"com.apple.developer.devicecheck.appattest-environment": "production",
|
|
167
|
+
},
|
|
111
168
|
associatedDomains: [
|
|
112
169
|
`applinks:${process.env.EXPO_PUBLIC_CONVEX_SITE_URL?.replace(/^https?:\/\//, "") ?? "example.convex.site"}`,
|
|
113
170
|
],
|
|
@@ -138,9 +195,8 @@ export default ({ config }: ConfigContext): ExpoConfig => {
|
|
|
138
195
|
[
|
|
139
196
|
"expo-router",
|
|
140
197
|
{
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
},
|
|
198
|
+
sitemap: false,
|
|
199
|
+
headOrigin: process.env.EXPO_PUBLIC_HEAD_ORIGIN,
|
|
144
200
|
},
|
|
145
201
|
],
|
|
146
202
|
[
|
|
@@ -179,6 +235,8 @@ export default ({ config }: ConfigContext): ExpoConfig => {
|
|
|
179
235
|
faceIDPermission: "Allow $(PRODUCT_NAME) to use Face ID to confirm sensitive actions.",
|
|
180
236
|
},
|
|
181
237
|
],
|
|
238
|
+
"expo-image",
|
|
239
|
+
"expo-sqlite",
|
|
182
240
|
"expo-system-ui",
|
|
183
241
|
"expo-secure-store",
|
|
184
242
|
"expo-web-browser",
|
|
@@ -191,6 +249,20 @@ export default ({ config }: ConfigContext): ExpoConfig => {
|
|
|
191
249
|
extra: {
|
|
192
250
|
...config.extra,
|
|
193
251
|
support: SUPPORT,
|
|
252
|
+
// Inject the resolved projectId so eas-cli commands that read
|
|
253
|
+
// `extra.eas.projectId` directly (e.g. `eas project:info`) see the
|
|
254
|
+
// env-var fallback too, not just `updates.url`. Existing
|
|
255
|
+
// `config.extra.eas` keys (if any) survive the merge. projectId stays
|
|
256
|
+
// in fingerprint parity because local and EAS both resolve it from the
|
|
257
|
+
// plaintext `EAS_PROJECT_ID` env var, so no `sourceSkips` are needed.
|
|
258
|
+
...(projectId
|
|
259
|
+
? {
|
|
260
|
+
eas: {
|
|
261
|
+
...(config.extra as { eas?: Record<string, unknown> } | undefined)?.eas,
|
|
262
|
+
projectId,
|
|
263
|
+
},
|
|
264
|
+
}
|
|
265
|
+
: {}),
|
|
194
266
|
},
|
|
195
267
|
experiments: {
|
|
196
268
|
typedRoutes: true,
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type * as admin from "../admin.js";
|
|
12
|
+
import type * as appAttest from "../appAttest.js";
|
|
13
|
+
import type * as appAttestStore from "../appAttestStore.js";
|
|
14
|
+
import type * as apple from "../apple.js";
|
|
12
15
|
import type * as auth from "../auth.js";
|
|
13
16
|
import type * as constants from "../constants.js";
|
|
14
17
|
import type * as crons from "../crons.js";
|
|
@@ -17,10 +20,13 @@ import type * as env from "../env.js";
|
|
|
17
20
|
import type * as errors from "../errors.js";
|
|
18
21
|
import type * as functions from "../functions.js";
|
|
19
22
|
import type * as http from "../http.js";
|
|
23
|
+
import type * as log from "../log.js";
|
|
24
|
+
import type * as pushSender from "../pushSender.js";
|
|
20
25
|
import type * as pushTokens from "../pushTokens.js";
|
|
21
26
|
import type * as rateLimit from "../rateLimit.js";
|
|
22
27
|
import type * as users from "../users.js";
|
|
23
28
|
import type * as validators from "../validators.js";
|
|
29
|
+
import type * as webhook from "../webhook.js";
|
|
24
30
|
|
|
25
31
|
import type {
|
|
26
32
|
ApiFromModules,
|
|
@@ -30,6 +36,9 @@ import type {
|
|
|
30
36
|
|
|
31
37
|
declare const fullApi: ApiFromModules<{
|
|
32
38
|
admin: typeof admin;
|
|
39
|
+
appAttest: typeof appAttest;
|
|
40
|
+
appAttestStore: typeof appAttestStore;
|
|
41
|
+
apple: typeof apple;
|
|
33
42
|
auth: typeof auth;
|
|
34
43
|
constants: typeof constants;
|
|
35
44
|
crons: typeof crons;
|
|
@@ -38,10 +47,13 @@ declare const fullApi: ApiFromModules<{
|
|
|
38
47
|
errors: typeof errors;
|
|
39
48
|
functions: typeof functions;
|
|
40
49
|
http: typeof http;
|
|
50
|
+
log: typeof log;
|
|
51
|
+
pushSender: typeof pushSender;
|
|
41
52
|
pushTokens: typeof pushTokens;
|
|
42
53
|
rateLimit: typeof rateLimit;
|
|
43
54
|
users: typeof users;
|
|
44
55
|
validators: typeof validators;
|
|
56
|
+
webhook: typeof webhook;
|
|
45
57
|
}>;
|
|
46
58
|
|
|
47
59
|
/**
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Admin actions for fixture / review accounts. Internal-only, never exposed
|
|
3
|
-
* to client code. Run via `bunx convex run admin:<fn>` (or your PM's dlx).
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import { v } from "convex/values";
|
|
7
2
|
|
|
8
3
|
import { components } from "./_generated/api";
|
|
@@ -11,9 +6,6 @@ import { createAuth } from "./auth";
|
|
|
11
6
|
import { rateLimiter, type RateLimitName } from "./rateLimit";
|
|
12
7
|
|
|
13
8
|
/**
|
|
14
|
-
* Create a fully verified review account. Used by `setup:review-account`
|
|
15
|
-
* to seed Apple's App Review with a working sign-in.
|
|
16
|
-
*
|
|
17
9
|
* Idempotent: if the user already exists, just re-asserts emailVerified=true.
|
|
18
10
|
* Does NOT rotate the password on re-run, delete the user from the dashboard
|
|
19
11
|
* first if you need a fresh password.
|
|
@@ -83,11 +75,6 @@ export const createReviewAccount = internalAction({
|
|
|
83
75
|
},
|
|
84
76
|
});
|
|
85
77
|
|
|
86
|
-
/**
|
|
87
|
-
* Reset a rate-limit bucket. Run from the dashboard:
|
|
88
|
-
* `bunx convex run admin:resetRateLimit '{"name":"avatarUpload","key":"<userId>"}'`
|
|
89
|
-
* Omit `key` to reset the shared bucket.
|
|
90
|
-
*/
|
|
91
78
|
export const resetRateLimit = internalMutation({
|
|
92
79
|
args: { name: v.string(), key: v.optional(v.string()) },
|
|
93
80
|
returns: v.object({
|