From f55d39110ece0f020d52d8488fc77ff9e865afae Mon Sep 17 00:00:00 2001
From: Henri VDM <30859194+jan-vdm@users.noreply.github.com>
Date: Fri, 30 Aug 2024 22:52:01 +1000
Subject: [PATCH 1/6] feat: add clerk support
---
.changeset/nine-jobs-occur.md | 5 ++
cli/src/commands/create-expo-stack.ts | 8 ++
cli/src/templates/base/.gitignore.ejs | 2 +-
cli/src/templates/base/App.tsx.ejs | 77 +++++++++++--------
cli/src/templates/base/package.json.ejs | 4 +
cli/src/templates/packages/clerk/.env.ejs | 1 +
.../expo-router/drawer/app/_layout.tsx.ejs | 31 ++++++--
.../expo-router/stack/app/_layout.tsx.ejs | 22 +++++-
.../expo-router/tabs/app/_layout.tsx.ejs | 26 ++++++-
.../packages/vexo-analytics/.env.ejs | 3 +
cli/src/types.ts | 3 +-
cli/src/utilities/configureProjectFiles.ts | 9 ++-
cli/src/utilities/generateProjectFiles.ts | 4 +
cli/src/utilities/printOutput.ts | 16 +++-
cli/src/utilities/runCLI.ts | 1 +
cli/src/utilities/showHelp.ts | 1 +
docs/src/content/docs/en/installation.md | 1 +
www/demo/steps/printOutput.js | 23 +++++-
www/demo/steps/setAuthentication.js | 1 +
19 files changed, 188 insertions(+), 50 deletions(-)
create mode 100644 .changeset/nine-jobs-occur.md
create mode 100644 cli/src/templates/packages/clerk/.env.ejs
diff --git a/.changeset/nine-jobs-occur.md b/.changeset/nine-jobs-occur.md
new file mode 100644
index 00000000..3fe19683
--- /dev/null
+++ b/.changeset/nine-jobs-occur.md
@@ -0,0 +1,5 @@
+---
+'create-expo-stack': minor
+---
+
+Add clerk authentication support
diff --git a/cli/src/commands/create-expo-stack.ts b/cli/src/commands/create-expo-stack.ts
index 16e14522..ad65d21c 100644
--- a/cli/src/commands/create-expo-stack.ts
+++ b/cli/src/commands/create-expo-stack.ts
@@ -296,6 +296,14 @@ const command: GluegunCommand = {
});
}
+ if (options.clerk) {
+ // Add clerk package
+ cliResults.packages.push({
+ name: 'clerk',
+ type: 'authentication'
+ });
+ }
+
// Internalization packages
if (options.i18next) {
cliResults.packages.push({
diff --git a/cli/src/templates/base/.gitignore.ejs b/cli/src/templates/base/.gitignore.ejs
index 5040396e..ebc7ec29 100644
--- a/cli/src/templates/base/.gitignore.ejs
+++ b/cli/src/templates/base/.gitignore.ejs
@@ -13,7 +13,7 @@ web-build/
expo-env.d.ts<% } %>
<% if (props.stylingPackage?.name === "tamagui") { %># tamagui
.tamagui/<% } %>
-<% if ((props.authenticationPackage?.name === "supabase") || (props.authenticationPackage?.name === "firebase" || (props.analyticsPackage?.name === 'vexo-analytics'))) { %># firebase/supabase/vexo
+<% if ((props.authenticationPackage?.name === "clerk") || (props.authenticationPackage?.name === "supabase") || (props.authenticationPackage?.name === "firebase" || (props.analyticsPackage?.name === 'vexo-analytics'))) { %># clerk/firebase/supabase/vexo
.env<% } %>
ios
diff --git a/cli/src/templates/base/App.tsx.ejs b/cli/src/templates/base/App.tsx.ejs
index 112a2feb..16159ded 100644
--- a/cli/src/templates/base/App.tsx.ejs
+++ b/cli/src/templates/base/App.tsx.ejs
@@ -1,6 +1,10 @@
import { ScreenContent } from 'components/ScreenContent';
import { StatusBar } from 'expo-status-bar';
+<% if (props.authenticationPackage?.name === "clerk") { %>
+ import { ClerkLoaded, ClerkProvider } from '@clerk/clerk-expo';
+<% } %>
+
<% if (props.internalizationPackage?.name === "i18next") { %>
import './translation';
import { InternalizationExample } from 'components/InternalizationExample';
@@ -28,46 +32,55 @@ import { StatusBar } from 'expo-status-bar';
vexo(process.env.EXPO_PUBLIC_VEXO_API_KEY);
<% } %>
-<% if (props.stylingPackage?.name === "restyle") {%>
- export default function App() {
- return (
-
-
- <% if (props.internalizationPackage?.name === "i18next") { %>
-
- <% } %>
-
-
-
- );
+export default function App() {
+<% if (props.authenticationPackage?.name === "clerk") { %>
+ const clerkPublishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
+ if (!clerkPublishableKey) {
+ throw new Error('Missing EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY, add one in your .env file');
}
+<% } %>
+
+ return (
+<% if (props.authenticationPackage?.name === "clerk") { %>
+
+
+<% } else { %>
+ <>
+<% } %>
+<% if (props.stylingPackage?.name === "restyle") {%>
+
+
+ <% if (props.internalizationPackage?.name === "i18next") { %>
+
+ <% } %>
+
+
+
<% } else if (props.stylingPackage?.name === "tamagui") {%>
- export default function App() {
- return (
-
-
- <% if (props.internalizationPackage?.name === "i18next") { %>
-
- <% } else { %>
- Open up App.tsx to start working on your app!
- <% } %>
-
-
-
- );
- }
+
+
+ <% if (props.internalizationPackage?.name === "i18next") { %>
+
+ <% } else { %>
+ Open up App.tsx to start working on your app!
+ <% } %>
+
+
+
<% } else { %>
- export default function App() {
- return (
- <>
<% if (props.internalizationPackage?.name === "i18next") { %>
<% } %>
- >
- );
- }
<% } %>
+<% if (props.authenticationPackage?.name === "clerk") { %>
+
+
+<% } else { %>
+ >
+<% } %>
+ );
+}
diff --git a/cli/src/templates/base/package.json.ejs b/cli/src/templates/base/package.json.ejs
index ef886880..3a9cf238 100644
--- a/cli/src/templates/base/package.json.ejs
+++ b/cli/src/templates/base/package.json.ejs
@@ -130,6 +130,10 @@
<% if (props.authenticationPackage?.name === "firebase") { %>
"firebase": "^10.5.2",
<% } %>
+
+ <% if (props.authenticationPackage?.name === "clerk") { %>
+ "@clerk/clerk-expo": "^2.2.5",
+ <% } %>
<% if (props.internalizationPackage?.name === "i18next") { %>
"i18next": "^23.7.20",
diff --git a/cli/src/templates/packages/clerk/.env.ejs b/cli/src/templates/packages/clerk/.env.ejs
new file mode 100644
index 00000000..d1bab076
--- /dev/null
+++ b/cli/src/templates/packages/clerk/.env.ejs
@@ -0,0 +1 @@
+EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=
\ No newline at end of file
diff --git a/cli/src/templates/packages/expo-router/drawer/app/_layout.tsx.ejs b/cli/src/templates/packages/expo-router/drawer/app/_layout.tsx.ejs
index c5bf2d2a..bf4b2dd1 100644
--- a/cli/src/templates/packages/expo-router/drawer/app/_layout.tsx.ejs
+++ b/cli/src/templates/packages/expo-router/drawer/app/_layout.tsx.ejs
@@ -12,6 +12,9 @@ import '../unistyles';
import '../translation';
<% } %>
+<% if (props.authenticationPackage?.name === "clerk") { %>
+ import { ClerkLoaded, ClerkProvider } from '@clerk/clerk-expo';
+<% } %>
import 'react-native-gesture-handler';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { Stack } from 'expo-router';
@@ -43,6 +46,14 @@ export const unstable_settings = {
};
export default function RootLayout() {
+
+ <% if (props.authenticationPackage?.name === "clerk") { %>
+ const clerkPublishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
+ if (!clerkPublishableKey) {
+ throw new Error('Missing EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY, add one in your .env file');
+ }
+ <% } %>
+
<% if (props.stylingPackage?.name==="tamagui" ) { %>
const [loaded] = useFonts({
Inter: require("@tamagui/font-inter/otf/Inter-Medium.otf"),
@@ -64,12 +75,20 @@ export default function RootLayout() {
<% } else if (props.stylingPackage?.name === "restyle") { %>
<% } %>
-
-
-
-
-
-
+ <% if (props.authenticationPackage?.name === "clerk") { %>
+
+
+ <% } %>
+
+
+
+
+
+
+ <% if (props.authenticationPackage?.name === "clerk") { %>
+
+
+ <% } %>
<% if (props.stylingPackage?.name === "tamagui") { %>
<% } else if (props.stylingPackage?.name === "restyle") { %>
diff --git a/cli/src/templates/packages/expo-router/stack/app/_layout.tsx.ejs b/cli/src/templates/packages/expo-router/stack/app/_layout.tsx.ejs
index 5220239c..eb782fb6 100644
--- a/cli/src/templates/packages/expo-router/stack/app/_layout.tsx.ejs
+++ b/cli/src/templates/packages/expo-router/stack/app/_layout.tsx.ejs
@@ -9,6 +9,9 @@ import '../unistyles';
import '../translation';
<% } %>
+<% if (props.authenticationPackage?.name === "clerk") { %>
+ import { ClerkLoaded, ClerkProvider } from '@clerk/clerk-expo';
+<% } %>
<% if (props.stylingPackage?.name === "tamagui") { %>
import { useFonts } from 'expo-font';
import { SplashScreen, Stack } from 'expo-router';
@@ -30,6 +33,13 @@ import '../unistyles';
export default function Layout() {
+ <% if (props.authenticationPackage?.name === "clerk") { %>
+ const clerkPublishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
+ if (!clerkPublishableKey) {
+ throw new Error('Missing EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY, add one in your .env file');
+ }
+ <% } %>
+
<% if (props.stylingPackage?.name === "tamagui") { %>
const [loaded] = useFonts({
Inter: require("@tamagui/font-inter/otf/Inter-Medium.otf"),
@@ -46,16 +56,26 @@ export default function Layout() {
<% } %>
return (
+
<% if (props.stylingPackage?.name === "tamagui") { %>
<% } else if (props.stylingPackage?.name === "restyle") { %>
<% } %>
-
+ <% if (props.authenticationPackage?.name === "clerk") { %>
+
+
+ <% } %>
+
+ <% if (props.authenticationPackage?.name === "clerk") { %>
+
+
+ <% } %>
<% if (props.stylingPackage?.name === "tamagui") { %>
<% } else if (props.stylingPackage?.name === "restyle") { %>
<% } %>
+
);
}
diff --git a/cli/src/templates/packages/expo-router/tabs/app/_layout.tsx.ejs b/cli/src/templates/packages/expo-router/tabs/app/_layout.tsx.ejs
index 489ca9de..d2e6462b 100644
--- a/cli/src/templates/packages/expo-router/tabs/app/_layout.tsx.ejs
+++ b/cli/src/templates/packages/expo-router/tabs/app/_layout.tsx.ejs
@@ -11,6 +11,9 @@ import '../unistyles';
import '../translation';
<% } %>
+<% if (props.authenticationPackage?.name === "clerk") { %>
+ import { ClerkLoaded, ClerkProvider } from '@clerk/clerk-expo';
+<% } %>
<% if (props.stylingPackage?.name === "tamagui") { %>
import React, { useEffect } from "react";
import { TamaguiProvider } from 'tamagui'
@@ -40,6 +43,13 @@ export const unstable_settings = {
};
export default function RootLayout() {
+ <% if (props.authenticationPackage?.name === "clerk") { %>
+ const clerkPublishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
+ if (!clerkPublishableKey) {
+ throw new Error('Missing EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY, add one in your .env file');
+ }
+ <% } %>
+
<% if (props.stylingPackage?.name === "tamagui") { %>
const [loaded] = useFonts({
Inter: require("@tamagui/font-inter/otf/Inter-Medium.otf"),
@@ -61,10 +71,18 @@ export default function RootLayout() {
<% } else if (props.stylingPackage?.name === "restyle") { %>
<% } %>
-
-
-
-
+ <% if (props.authenticationPackage?.name === "clerk") { %>
+
+
+ <% } %>
+
+
+
+
+ <% if (props.authenticationPackage?.name === "clerk") { %>
+
+
+ <% } %>
<% if (props.stylingPackage?.name === "tamagui") { %>
<% } else if (props.stylingPackage?.name === "restyle") { %>
diff --git a/cli/src/templates/packages/vexo-analytics/.env.ejs b/cli/src/templates/packages/vexo-analytics/.env.ejs
index 391e06fc..ad70ad79 100644
--- a/cli/src/templates/packages/vexo-analytics/.env.ejs
+++ b/cli/src/templates/packages/vexo-analytics/.env.ejs
@@ -4,4 +4,7 @@ EXPO_PUBLIC_VEXO_API_KEY=
<% } %>
<% if (props.authenticationPackage?.name === "firebase") { %>
<%- include('../firebase/.env') -%>
+<% } %>
+<% if (props.authenticationPackage?.name === "clerk") { %>
+<%- include('../clerk/.env') -%>
<% } %>
\ No newline at end of file
diff --git a/cli/src/types.ts b/cli/src/types.ts
index 48931900..2ba9f8da 100644
--- a/cli/src/types.ts
+++ b/cli/src/types.ts
@@ -10,6 +10,7 @@ export interface CliFlags {
export const availablePackages = [
'@react-navigation/drawer',
+ 'clerk',
'expo-router',
'expoRouter',
'firebase',
@@ -29,7 +30,7 @@ export const availablePackages = [
'vexo-analytics'
] as const;
-export type AuthenticationSelect = 'supabase' | 'firebase' | undefined;
+export type AuthenticationSelect = 'supabase' | 'firebase' | 'clerk' | undefined;
export type NavigationSelect = 'react-navigation' | 'expo-router' | undefined;
diff --git a/cli/src/utilities/configureProjectFiles.ts b/cli/src/utilities/configureProjectFiles.ts
index 9f130d99..2cfbf54e 100644
--- a/cli/src/utilities/configureProjectFiles.ts
+++ b/cli/src/utilities/configureProjectFiles.ts
@@ -308,6 +308,13 @@ export function configureProjectFiles(
files = [...files, ...expoRouterFiles];
}
+ // add clerk files if needed
+ if (authenticationPackage?.name === 'clerk') {
+ const clerkFiles = ['packages/clerk/.env.ejs'];
+
+ files = [...files, ...clerkFiles];
+ }
+
// add supabase files if needed
if (authenticationPackage?.name === 'supabase') {
const supabaseFiles = ['packages/supabase/utils/supabase.ts.ejs', 'packages/supabase/.env.ejs'];
@@ -315,7 +322,7 @@ export function configureProjectFiles(
files = [...files, ...supabaseFiles];
}
- // add supabase files if needed
+ // add firebase files if needed
if (authenticationPackage?.name === 'firebase') {
const firebaseFiles = [
'packages/firebase/utils/firebase.ts.ejs',
diff --git a/cli/src/utilities/generateProjectFiles.ts b/cli/src/utilities/generateProjectFiles.ts
index 15dd1ad5..8be34902 100644
--- a/cli/src/utilities/generateProjectFiles.ts
+++ b/cli/src/utilities/generateProjectFiles.ts
@@ -19,6 +19,10 @@ export function generateProjectFiles(
const template = file;
let target = file.replace('.ejs', '');
+ if (authenticationPackage?.name === 'clerk') {
+ target = target.replace('packages/clerk/', '');
+ }
+
if (authenticationPackage?.name === 'supabase') {
target = target.replace('packages/supabase/', '');
}
diff --git a/cli/src/utilities/printOutput.ts b/cli/src/utilities/printOutput.ts
index 2fcd53ad..ddadfea3 100644
--- a/cli/src/utilities/printOutput.ts
+++ b/cli/src/utilities/printOutput.ts
@@ -142,8 +142,20 @@ export async function printOutput(
info(``);
};
- // check if packages includes package with name "supabase"
- if (cliResults.packages.some((pkg) => pkg.name === 'supabase')) {
+ if (cliResults.packages.some((pkg) => pkg.name === 'clerk')) {
+ success(`\nSuccess! 🎉 Now, here's what's next:`);
+ info(``);
+ highlight('Head over to https://dashboard.clerk.dev to create a new Clerk project.');
+ info(``);
+ highlight(`Get the Clerk Publishable API Key:`);
+ info(`1. Go to the API keys page in the Dashboard.`);
+ info(`2. Find your Publishable API Key on this page.`);
+ info(`3. Copy this key and paste it into your .env file.`);
+ info(`4. Optionally, follow one of these guides to get started with Clerk:`);
+ highlight(`https://clerk.com/docs/quickstarts/expo`);
+ success(`Once you're done, run the following to get started: `);
+ info(``);
+ } else if (cliResults.packages.some((pkg) => pkg.name === 'supabase')) {
success(`\nSuccess! 🎉 Now, here's what's next:`);
info(``);
highlight('Head over to https://database.new to create a new Supabase project.');
diff --git a/cli/src/utilities/runCLI.ts b/cli/src/utilities/runCLI.ts
index 6a7c6553..684fa4b3 100644
--- a/cli/src/utilities/runCLI.ts
+++ b/cli/src/utilities/runCLI.ts
@@ -331,6 +331,7 @@ export async function runCLI(toolbox: Toolbox, projectName: string): Promise p.type === "authentication") || undefined;
- // check if packages includes package with name "supabase"
- if (authenticationPackage && authenticationPackage.name === "supabase") {
+ // check if packages includes package with name "clerk"
+ if (authenticationPackage && authenticationPackage.name === "clerk") {
+ return `${color.green(`\nSuccess! 🎉 Now, here's what's next:`)}
+ \n ${color.blue(
+ "Head over to https://dashboard.clerk.dev to create a new Clerk project.",
+ )}
+ \n${color.blue(`Get the Clerk Publishable API Key:`)}
+ \n1. Go to the API keys page in the Dashboard.
+ \n2. Find your Publishable API Key on this page.
+ \n3. Copy this key and paste it into your .env file.
+ \n4. Optionally, follow one of these guides to get started with Clerk:
+ \n${color.blue(`https://clerk.com/docs/quickstarts/expo`)}
+ \n${color.green(
+ `Once you're done, run the following to get started: `,
+ )}\n\n${stepsToRunProject}
+ `;
+ // check if packages includes package with name "supabase"
+ } else if (
+ authenticationPackage &&
+ authenticationPackage.name === "supabase"
+ ) {
return `${color.green(`\nSuccess! 🎉 Now, here's what's next:`)}
\n${color.blue(
"Head over to https://database.new to create a new Supabase project.",
diff --git a/www/demo/steps/setAuthentication.js b/www/demo/steps/setAuthentication.js
index 380b5e79..cd0c2e7c 100644
--- a/www/demo/steps/setAuthentication.js
+++ b/www/demo/steps/setAuthentication.js
@@ -6,6 +6,7 @@ export async function setAuthentication(cliResults) {
message: "What would you like to use for authentication?",
options: [
{ value: undefined, label: "None" },
+ { value: "clerk", label: "Clerk" },
{ value: "supabase", label: "Supabase" },
{ value: "firebase", label: "Firebase" },
],
From 7c5cdeed53705d14cf9604218452dc9e1a141d70 Mon Sep 17 00:00:00 2001
From: Henri VDM <30859194+jan-vdm@users.noreply.github.com>
Date: Sat, 31 Aug 2024 01:09:11 +1000
Subject: [PATCH 2/6] test: added a clerk expo-router test scenario
---
.../cli-integration.test.ts.snap | 134 ++++++++++++++++++
cli/__tests__/cli-integration.test.ts | 4 +-
cli/src/templates/base/App.tsx.ejs | 3 +-
cli/src/templates/base/package.json.ejs | 5 +-
4 files changed, 142 insertions(+), 4 deletions(-)
diff --git a/cli/__tests__/__snapshots__/cli-integration.test.ts.snap b/cli/__tests__/__snapshots__/cli-integration.test.ts.snap
index 0dced646..e14142f6 100644
--- a/cli/__tests__/__snapshots__/cli-integration.test.ts.snap
+++ b/cli/__tests__/__snapshots__/cli-integration.test.ts.snap
@@ -1388,3 +1388,137 @@ exports[`generates a project with --expo-router --drawer+tabs --nativewindui --b
./myTestProject/tsconfig.json
"
`;
+
+exports[`generates a project with --expo-router --tabs --clerk --nativewind --bun --overwrite: --expo-router, --tabs, --clerk, --nativewind, --bun, --overwrite-package-json 1`] = `
+{
+ "dependencies": {
+ "@expo/vector-icons": "",
+ "@react-navigation/native": "",
+ "expo": "",
+ "expo-constants": "",
+ "expo-linking": "",
+ "expo-router": "",
+ "expo-status-bar": "",
+ "expo-system-ui": "",
+ "expo-web-browser": "",
+ "nativewind": "",
+ "react": "",
+ "react-dom": "",
+ "react-native": "",
+ "react-native-gesture-handler": "",
+ "react-native-reanimated": "",
+ "react-native-safe-area-context": "",
+ "react-native-screens": "",
+ "react-native-web": "",
+ },
+ "devDependencies": {
+ "@babel/core": "",
+ "@types/react": "",
+ "@typescript-eslint/eslint-plugin": "",
+ "@typescript-eslint/parser": "",
+ "ajv": "",
+ "eslint": "",
+ "eslint-config-universe": "",
+ "prettier": "",
+ "prettier-plugin-tailwindcss": "",
+ "tailwindcss": "",
+ "typescript": "",
+ },
+ "eslintConfig": {
+ "extends": "universe/native",
+ "root": true,
+ },
+ "expo": {
+ "install": {
+ "exclude": [
+ "react-native-safe-area-context",
+ ],
+ },
+ },
+ "main": "expo-router/entry",
+ "name": "myTestProject",
+ "private": true,
+ "scripts": {
+ "android": "expo start --android",
+ "format": "eslint "**/*.{js,jsx,ts,tsx}" --fix && prettier "**/*.{js,jsx,ts,tsx,json}" --write",
+ "ios": "expo start --ios",
+ "lint": "eslint "**/*.{js,jsx,ts,tsx}" && prettier -c "**/*.{js,jsx,ts,tsx,json}"",
+ "prebuild": "expo prebuild",
+ "start": "expo start",
+ "web": "expo start --web",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`generates a project with --expo-router --tabs --clerk --nativewind --bun --overwrite: --expo-router, --tabs, --clerk, --nativewind, --bun, --overwrite-ces-config-json 1`] = `
+{
+ "cesVersion": undefined,
+ "flags": {
+ "eas": false,
+ "importAlias": true,
+ "noGit": false,
+ "noInstall": false,
+ "overwrite": true,
+ "packageManager": "bun",
+ },
+ "os": {},
+ "packageManager": {
+ "type": "bun",
+ "version": undefined,
+ },
+ "packages": [
+ {
+ "name": "expo-router",
+ "options": {
+ "type": "stack",
+ },
+ "type": "navigation",
+ },
+ {
+ "name": "nativewind",
+ "type": "styling",
+ },
+ ],
+ "projectName": "myTestProject",
+}
+`;
+
+exports[`generates a project with --expo-router --tabs --clerk --nativewind --bun --overwrite: --expo-router, --tabs, --clerk, --nativewind, --bun, --overwrite-file-list 1`] = `
+"./myTestProject
+./myTestProject/.env
+./myTestProject/app
+./myTestProject/app-env.d.ts
+./myTestProject/app.json
+./myTestProject/app/(tabs)
+./myTestProject/app/(tabs)/_layout.tsx
+./myTestProject/app/(tabs)/index.tsx
+./myTestProject/app/(tabs)/two.tsx
+./myTestProject/app/+html.tsx
+./myTestProject/app/+not-found.tsx
+./myTestProject/app/_layout.tsx
+./myTestProject/app/modal.tsx
+./myTestProject/assets
+./myTestProject/assets/adaptive-icon.png
+./myTestProject/assets/favicon.png
+./myTestProject/assets/icon.png
+./myTestProject/assets/splash.png
+./myTestProject/babel.config.js
+./myTestProject/bun.lockb
+./myTestProject/cesconfig.json
+./myTestProject/components
+./myTestProject/components/Button.tsx
+./myTestProject/components/Container.tsx
+./myTestProject/components/EditScreenInfo.tsx
+./myTestProject/components/HeaderButton.tsx
+./myTestProject/components/ScreenContent.tsx
+./myTestProject/components/TabBarIcon.tsx
+./myTestProject/expo-env.d.ts
+./myTestProject/global.css
+./myTestProject/metro.config.js
+./myTestProject/package.json
+./myTestProject/prettier.config.js
+./myTestProject/tailwind.config.js
+./myTestProject/tsconfig.json
+"
+`;
diff --git a/cli/__tests__/cli-integration.test.ts b/cli/__tests__/cli-integration.test.ts
index 3e0a42d8..71e53358 100644
--- a/cli/__tests__/cli-integration.test.ts
+++ b/cli/__tests__/cli-integration.test.ts
@@ -82,7 +82,9 @@ const popularCombinations = [
// no install is important for the website cli that generates a project zip file
['--nativewindui', '--no-install'],
// nativewindui blank
- ['--expo-router', '--drawer+tabs', '--nativewindui', '--blank', '--expo-router']
+ ['--expo-router', '--drawer+tabs', '--nativewindui', '--blank', '--expo-router'],
+ // clerk expo-router tabs nativewind
+ ['--expo-router', '--tabs', '--clerk', '--nativewind']
] as const;
const projectName = `myTestProject`;
diff --git a/cli/src/templates/base/App.tsx.ejs b/cli/src/templates/base/App.tsx.ejs
index 16159ded..e3ebdecd 100644
--- a/cli/src/templates/base/App.tsx.ejs
+++ b/cli/src/templates/base/App.tsx.ejs
@@ -82,5 +82,4 @@ export default function App() {
>
<% } %>
);
-}
-
+}
\ No newline at end of file
diff --git a/cli/src/templates/base/package.json.ejs b/cli/src/templates/base/package.json.ejs
index 3a9cf238..e1fd8044 100644
--- a/cli/src/templates/base/package.json.ejs
+++ b/cli/src/templates/base/package.json.ejs
@@ -133,8 +133,11 @@
<% if (props.authenticationPackage?.name === "clerk") { %>
"@clerk/clerk-expo": "^2.2.5",
+ <% if (props.navigationPackage?.name !== "expo-router") { %>
+ "react-dom": "18.2.0",
+ <% } %>
<% } %>
-
+
<% if (props.internalizationPackage?.name === "i18next") { %>
"i18next": "^23.7.20",
"react-i18next": "^14.0.1",
From efbf19d3bc072deb5bd34c44d71a8f194bda3f5c Mon Sep 17 00:00:00 2001
From: Henri VDM <30859194+jan-vdm@users.noreply.github.com>
Date: Sat, 31 Aug 2024 01:26:00 +1000
Subject: [PATCH 3/6] feat: added clerk support for react navigation
---
.../packages/react-navigation/App.tsx.ejs | 45 ++++++++++++++-----
1 file changed, 33 insertions(+), 12 deletions(-)
diff --git a/cli/src/templates/packages/react-navigation/App.tsx.ejs b/cli/src/templates/packages/react-navigation/App.tsx.ejs
index 88baafae..68665fdd 100644
--- a/cli/src/templates/packages/react-navigation/App.tsx.ejs
+++ b/cli/src/templates/packages/react-navigation/App.tsx.ejs
@@ -10,6 +10,10 @@ import './unistyles';
<% if (props.internalizationPackage?.name === "i18next") { %>
import './translation';
<% } %>
+
+<% if (props.authenticationPackage?.name === "clerk") { %>
+ import { ClerkLoaded, ClerkProvider } from '@clerk/clerk-expo';
+<% } %>
import "react-native-gesture-handler";
<% if (props.stylingPackage?.name === "tamagui") { %>
import React, { useEffect } from "react";
@@ -36,6 +40,13 @@ import "react-native-gesture-handler";
<% } %>
export default function App() {
+ <% if (props.authenticationPackage?.name === "clerk") { %>
+ const clerkPublishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
+ if (!clerkPublishableKey) {
+ throw new Error('Missing EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY, add one in your .env file');
+ }
+ <% } %>
+
<% if (props.stylingPackage?.name === "tamagui") { %>
const [loaded] = useFonts({
Inter: require("@tamagui/font-inter/otf/Inter-Medium.otf"),
@@ -51,19 +62,29 @@ export default function App() {
if (!loaded) {
return null;
}
+ <% } %>
- return (
-
-
-
- );
- <% } else if (props.stylingPackage?.name === "restyle") { %>
- return (
+ return (
+ <% if (props.stylingPackage?.name === "tamagui") { %>
+
+ <% } %>
+ <% if (props.stylingPackage?.name === "restyle") { %>
-
+ <% } %>
+ <% if (props.authenticationPackage?.name === "clerk") { %>
+
+
+ <% } %>
+
+ <% if (props.authenticationPackage?.name === "clerk") { %>
+
+
+ <% } %>
+ <% if (props.stylingPackage?.name === "restyle") { %>
- );
- <% } else { %>
- return ;
- <% } %>
+ <% } %>
+ <% if (props.stylingPackage?.name === "tamagui") { %>
+
+ <% } %>
+ );
}
From c11d6227e00622343a8a543ff214047460b2521c Mon Sep 17 00:00:00 2001
From: Henri VDM <30859194+jan-vdm@users.noreply.github.com>
Date: Wed, 4 Sep 2024 23:30:43 +1000
Subject: [PATCH 4/6] chore: update dependencies
---
cli/src/templates/base/package.json.ejs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/cli/src/templates/base/package.json.ejs b/cli/src/templates/base/package.json.ejs
index e1fd8044..fc3cef76 100644
--- a/cli/src/templates/base/package.json.ejs
+++ b/cli/src/templates/base/package.json.ejs
@@ -132,7 +132,8 @@
<% } %>
<% if (props.authenticationPackage?.name === "clerk") { %>
- "@clerk/clerk-expo": "^2.2.5",
+ "@clerk/clerk-expo": "2.2.7",
+ "expo-auth-session": "5.5.2",
<% if (props.navigationPackage?.name !== "expo-router") { %>
"react-dom": "18.2.0",
<% } %>
From aed67cca1fd62d4884280c2e174d9031dbb50823 Mon Sep 17 00:00:00 2001
From: Henri VDM <30859194+jan-vdm@users.noreply.github.com>
Date: Wed, 4 Sep 2024 23:42:07 +1000
Subject: [PATCH 5/6] chore: reverted dependency to 2.2.5 as the new versions
have breaking bugs
---
cli/src/templates/base/package.json.ejs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cli/src/templates/base/package.json.ejs b/cli/src/templates/base/package.json.ejs
index fc3cef76..dd58e2a3 100644
--- a/cli/src/templates/base/package.json.ejs
+++ b/cli/src/templates/base/package.json.ejs
@@ -132,7 +132,7 @@
<% } %>
<% if (props.authenticationPackage?.name === "clerk") { %>
- "@clerk/clerk-expo": "2.2.7",
+ "@clerk/clerk-expo": "2.2.5",
"expo-auth-session": "5.5.2",
<% if (props.navigationPackage?.name !== "expo-router") { %>
"react-dom": "18.2.0",
From f65846c4b56e9c32b8765345148eea6b3f1d1141 Mon Sep 17 00:00:00 2001
From: Henri VDM <30859194+jan-vdm@users.noreply.github.com>
Date: Thu, 7 Nov 2024 19:38:07 +1100
Subject: [PATCH 6/6] feat: add expo secure store to clerk template
---
cli/src/templates/base/App.tsx.ejs | 28 ++++++++++++++++++-
cli/src/templates/base/package.json.ejs | 1 +
.../expo-router/drawer/app/_layout.tsx.ejs | 28 ++++++++++++++++++-
.../expo-router/stack/app/_layout.tsx.ejs | 28 ++++++++++++++++++-
.../expo-router/tabs/app/_layout.tsx.ejs | 28 ++++++++++++++++++-
.../packages/react-navigation/App.tsx.ejs | 28 ++++++++++++++++++-
6 files changed, 136 insertions(+), 5 deletions(-)
diff --git a/cli/src/templates/base/App.tsx.ejs b/cli/src/templates/base/App.tsx.ejs
index e3ebdecd..c18c7965 100644
--- a/cli/src/templates/base/App.tsx.ejs
+++ b/cli/src/templates/base/App.tsx.ejs
@@ -2,6 +2,7 @@ import { ScreenContent } from 'components/ScreenContent';
import { StatusBar } from 'expo-status-bar';
<% if (props.authenticationPackage?.name === "clerk") { %>
+ import * as SecureStore from 'expo-secure-store'
import { ClerkLoaded, ClerkProvider } from '@clerk/clerk-expo';
<% } %>
@@ -34,6 +35,31 @@ import { StatusBar } from 'expo-status-bar';
export default function App() {
<% if (props.authenticationPackage?.name === "clerk") { %>
+ const tokenCache = {
+ async getToken(key: string) {
+ try {
+ const item = await SecureStore.getItemAsync(key)
+ if (item) {
+ console.log(`${key} was used 🔐 \n`)
+ } else {
+ console.log('No values stored under key: ' + key)
+ }
+ return item
+ } catch (error) {
+ console.error('SecureStore get item error: ', error)
+ await SecureStore.deleteItemAsync(key)
+ return null
+ }
+ },
+ async saveToken(key: string, value: string) {
+ try {
+ return SecureStore.setItemAsync(key, value)
+ } catch (err) {
+ return
+ }
+ },
+ }
+
const clerkPublishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
if (!clerkPublishableKey) {
throw new Error('Missing EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY, add one in your .env file');
@@ -42,7 +68,7 @@ export default function App() {
return (
<% if (props.authenticationPackage?.name === "clerk") { %>
-
+
<% } else { %>
<>
diff --git a/cli/src/templates/base/package.json.ejs b/cli/src/templates/base/package.json.ejs
index dd58e2a3..3f5b8e5f 100644
--- a/cli/src/templates/base/package.json.ejs
+++ b/cli/src/templates/base/package.json.ejs
@@ -134,6 +134,7 @@
<% if (props.authenticationPackage?.name === "clerk") { %>
"@clerk/clerk-expo": "2.2.5",
"expo-auth-session": "5.5.2",
+ "expo-secure-store": "13.0.2",
<% if (props.navigationPackage?.name !== "expo-router") { %>
"react-dom": "18.2.0",
<% } %>
diff --git a/cli/src/templates/packages/expo-router/drawer/app/_layout.tsx.ejs b/cli/src/templates/packages/expo-router/drawer/app/_layout.tsx.ejs
index bf4b2dd1..d2659e0f 100644
--- a/cli/src/templates/packages/expo-router/drawer/app/_layout.tsx.ejs
+++ b/cli/src/templates/packages/expo-router/drawer/app/_layout.tsx.ejs
@@ -13,6 +13,7 @@ import '../unistyles';
<% } %>
<% if (props.authenticationPackage?.name === "clerk") { %>
+ import * as SecureStore from 'expo-secure-store'
import { ClerkLoaded, ClerkProvider } from '@clerk/clerk-expo';
<% } %>
import 'react-native-gesture-handler';
@@ -48,6 +49,31 @@ export const unstable_settings = {
export default function RootLayout() {
<% if (props.authenticationPackage?.name === "clerk") { %>
+ const tokenCache = {
+ async getToken(key: string) {
+ try {
+ const item = await SecureStore.getItemAsync(key)
+ if (item) {
+ console.log(`${key} was used 🔐 \n`)
+ } else {
+ console.log('No values stored under key: ' + key)
+ }
+ return item
+ } catch (error) {
+ console.error('SecureStore get item error: ', error)
+ await SecureStore.deleteItemAsync(key)
+ return null
+ }
+ },
+ async saveToken(key: string, value: string) {
+ try {
+ return SecureStore.setItemAsync(key, value)
+ } catch (err) {
+ return
+ }
+ },
+ }
+
const clerkPublishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
if (!clerkPublishableKey) {
throw new Error('Missing EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY, add one in your .env file');
@@ -76,7 +102,7 @@ export default function RootLayout() {
<% } %>
<% if (props.authenticationPackage?.name === "clerk") { %>
-
+
<% } %>
diff --git a/cli/src/templates/packages/expo-router/stack/app/_layout.tsx.ejs b/cli/src/templates/packages/expo-router/stack/app/_layout.tsx.ejs
index eb782fb6..93e7c7a5 100644
--- a/cli/src/templates/packages/expo-router/stack/app/_layout.tsx.ejs
+++ b/cli/src/templates/packages/expo-router/stack/app/_layout.tsx.ejs
@@ -10,6 +10,7 @@ import '../unistyles';
<% } %>
<% if (props.authenticationPackage?.name === "clerk") { %>
+ import * as SecureStore from 'expo-secure-store'
import { ClerkLoaded, ClerkProvider } from '@clerk/clerk-expo';
<% } %>
<% if (props.stylingPackage?.name === "tamagui") { %>
@@ -34,6 +35,31 @@ import '../unistyles';
export default function Layout() {
<% if (props.authenticationPackage?.name === "clerk") { %>
+ const tokenCache = {
+ async getToken(key: string) {
+ try {
+ const item = await SecureStore.getItemAsync(key)
+ if (item) {
+ console.log(`${key} was used 🔐 \n`)
+ } else {
+ console.log('No values stored under key: ' + key)
+ }
+ return item
+ } catch (error) {
+ console.error('SecureStore get item error: ', error)
+ await SecureStore.deleteItemAsync(key)
+ return null
+ }
+ },
+ async saveToken(key: string, value: string) {
+ try {
+ return SecureStore.setItemAsync(key, value)
+ } catch (err) {
+ return
+ }
+ },
+ }
+
const clerkPublishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
if (!clerkPublishableKey) {
throw new Error('Missing EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY, add one in your .env file');
@@ -63,7 +89,7 @@ export default function Layout() {
<% } %>
<% if (props.authenticationPackage?.name === "clerk") { %>
-
+
<% } %>
diff --git a/cli/src/templates/packages/expo-router/tabs/app/_layout.tsx.ejs b/cli/src/templates/packages/expo-router/tabs/app/_layout.tsx.ejs
index d2e6462b..26e14d26 100644
--- a/cli/src/templates/packages/expo-router/tabs/app/_layout.tsx.ejs
+++ b/cli/src/templates/packages/expo-router/tabs/app/_layout.tsx.ejs
@@ -12,6 +12,7 @@ import '../unistyles';
<% } %>
<% if (props.authenticationPackage?.name === "clerk") { %>
+ import * as SecureStore from 'expo-secure-store'
import { ClerkLoaded, ClerkProvider } from '@clerk/clerk-expo';
<% } %>
<% if (props.stylingPackage?.name === "tamagui") { %>
@@ -44,6 +45,31 @@ export const unstable_settings = {
export default function RootLayout() {
<% if (props.authenticationPackage?.name === "clerk") { %>
+ const tokenCache = {
+ async getToken(key: string) {
+ try {
+ const item = await SecureStore.getItemAsync(key)
+ if (item) {
+ console.log(`${key} was used 🔐 \n`)
+ } else {
+ console.log('No values stored under key: ' + key)
+ }
+ return item
+ } catch (error) {
+ console.error('SecureStore get item error: ', error)
+ await SecureStore.deleteItemAsync(key)
+ return null
+ }
+ },
+ async saveToken(key: string, value: string) {
+ try {
+ return SecureStore.setItemAsync(key, value)
+ } catch (err) {
+ return
+ }
+ },
+ }
+
const clerkPublishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
if (!clerkPublishableKey) {
throw new Error('Missing EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY, add one in your .env file');
@@ -72,7 +98,7 @@ export default function RootLayout() {
<% } %>
<% if (props.authenticationPackage?.name === "clerk") { %>
-
+
<% } %>
diff --git a/cli/src/templates/packages/react-navigation/App.tsx.ejs b/cli/src/templates/packages/react-navigation/App.tsx.ejs
index 68665fdd..9eeb9d4e 100644
--- a/cli/src/templates/packages/react-navigation/App.tsx.ejs
+++ b/cli/src/templates/packages/react-navigation/App.tsx.ejs
@@ -12,6 +12,7 @@ import './unistyles';
<% } %>
<% if (props.authenticationPackage?.name === "clerk") { %>
+ import * as SecureStore from 'expo-secure-store'
import { ClerkLoaded, ClerkProvider } from '@clerk/clerk-expo';
<% } %>
import "react-native-gesture-handler";
@@ -41,6 +42,31 @@ import "react-native-gesture-handler";
export default function App() {
<% if (props.authenticationPackage?.name === "clerk") { %>
+ const tokenCache = {
+ async getToken(key: string) {
+ try {
+ const item = await SecureStore.getItemAsync(key)
+ if (item) {
+ console.log(`${key} was used 🔐 \n`)
+ } else {
+ console.log('No values stored under key: ' + key)
+ }
+ return item
+ } catch (error) {
+ console.error('SecureStore get item error: ', error)
+ await SecureStore.deleteItemAsync(key)
+ return null
+ }
+ },
+ async saveToken(key: string, value: string) {
+ try {
+ return SecureStore.setItemAsync(key, value)
+ } catch (err) {
+ return
+ }
+ },
+ }
+
const clerkPublishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
if (!clerkPublishableKey) {
throw new Error('Missing EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY, add one in your .env file');
@@ -72,7 +98,7 @@ export default function App() {
<% } %>
<% if (props.authenticationPackage?.name === "clerk") { %>
-
+
<% } %>