create-100x-mobile 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +3 -1
- package/dist/commands/new/args.js +18 -0
- package/dist/commands/new/scaffold.js +6 -6
- package/dist/commands/new/steps.js +99 -19
- package/dist/commands/new.js +108 -38
- package/dist/templates/app/authLayout.js +0 -1
- package/dist/templates/app/signIn.js +1 -15
- package/dist/templates/config/envExample.js +6 -1
- package/dist/templates/config/packageJson.js +13 -3
- package/dist/templates/config/readme.js +45 -1
- package/dist/templates/instant/instantLib.js +28 -0
- package/dist/templates/instant/rootLayout.js +23 -0
- package/dist/templates/instant/settingsScreen.js +83 -0
- package/dist/templates/instant/tabsLayout.js +54 -0
- package/dist/templates/instant/todosScreen.js +408 -0
- package/package.json +1 -1
- package/dist/templates/app/signUp.js +0 -298
package/dist/cli.js
CHANGED
|
@@ -16,11 +16,13 @@ Options:
|
|
|
16
16
|
--yes Run without prompts (defaults name to "my-app")
|
|
17
17
|
--no-install Skip dependency installation
|
|
18
18
|
--skip-git Skip git init/commit
|
|
19
|
+
--backend <provider> Backend: "convex" or "instantdb"
|
|
19
20
|
--sdk <value> Use "latest", "previous", or a specific version (e.g. 54.0.33)
|
|
20
21
|
--clerk-pk <key> Pre-fill Clerk publishable key
|
|
21
22
|
--clerk-domain <domain> Pre-fill Clerk issuer domain
|
|
23
|
+
--instant-app-id <id> Pre-fill InstantDB app id
|
|
22
24
|
|
|
23
|
-
Scaffolds a
|
|
25
|
+
Scaffolds a mobile app with Expo + Convex/Clerk or Expo + InstantDB.
|
|
24
26
|
`);
|
|
25
27
|
}
|
|
26
28
|
async function main() {
|
|
@@ -22,8 +22,10 @@ function parseNewCommandArgs(args) {
|
|
|
22
22
|
installDependencies: true,
|
|
23
23
|
initializeGit: true,
|
|
24
24
|
sdk: null,
|
|
25
|
+
backend: null,
|
|
25
26
|
clerkPublishableKey: null,
|
|
26
27
|
clerkDomain: null,
|
|
28
|
+
instantAppId: null,
|
|
27
29
|
};
|
|
28
30
|
let projectName = null;
|
|
29
31
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -55,6 +57,16 @@ function parseNewCommandArgs(args) {
|
|
|
55
57
|
i = nextIndex;
|
|
56
58
|
continue;
|
|
57
59
|
}
|
|
60
|
+
if (arg === "--backend" || arg.startsWith("--backend=")) {
|
|
61
|
+
const { value, nextIndex } = getInlineOrNextValue(arg, args, i);
|
|
62
|
+
const normalized = value.toLowerCase();
|
|
63
|
+
if (normalized !== "convex" && normalized !== "instantdb") {
|
|
64
|
+
throw new Error(`Invalid --backend value "${value}". Use "convex" or "instantdb".`);
|
|
65
|
+
}
|
|
66
|
+
options.backend = normalized;
|
|
67
|
+
i = nextIndex;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
58
70
|
if (arg === "--clerk-pk" || arg.startsWith("--clerk-pk=")) {
|
|
59
71
|
const { value, nextIndex } = getInlineOrNextValue(arg, args, i);
|
|
60
72
|
options.clerkPublishableKey = value;
|
|
@@ -67,6 +79,12 @@ function parseNewCommandArgs(args) {
|
|
|
67
79
|
i = nextIndex;
|
|
68
80
|
continue;
|
|
69
81
|
}
|
|
82
|
+
if (arg === "--instant-app-id" || arg.startsWith("--instant-app-id=")) {
|
|
83
|
+
const { value, nextIndex } = getInlineOrNextValue(arg, args, i);
|
|
84
|
+
options.instantAppId = value;
|
|
85
|
+
i = nextIndex;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
70
88
|
throw new Error(`Unknown flag: ${arg}`);
|
|
71
89
|
}
|
|
72
90
|
if (!projectName && !options.interactive) {
|
|
@@ -7,16 +7,16 @@ const node_path_1 = require("node:path");
|
|
|
7
7
|
const fs_1 = require("../../lib/fs");
|
|
8
8
|
// 1x1 transparent PNG placeholder.
|
|
9
9
|
const TRANSPARENT_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO7+P5kAAAAASUVORK5CYII=";
|
|
10
|
-
async function createProjectDirectories(projectDir) {
|
|
11
|
-
const
|
|
12
|
-
"app/(auth)",
|
|
10
|
+
async function createProjectDirectories(projectDir, backend) {
|
|
11
|
+
const commonDirs = [
|
|
13
12
|
"app/(tabs)",
|
|
14
|
-
"app/providers",
|
|
15
|
-
"components",
|
|
16
|
-
"convex",
|
|
17
13
|
"hooks",
|
|
18
14
|
"assets/images",
|
|
19
15
|
];
|
|
16
|
+
const backendDirs = backend === "convex"
|
|
17
|
+
? ["app/(auth)", "app/providers", "components", "convex"]
|
|
18
|
+
: ["lib"];
|
|
19
|
+
const dirs = [...commonDirs, ...backendDirs];
|
|
20
20
|
for (const dir of dirs) {
|
|
21
21
|
await (0, fs_1.ensureDir)((0, node_path_1.join)(projectDir, dir));
|
|
22
22
|
}
|
|
@@ -5,11 +5,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.normalizeClerkDomain = normalizeClerkDomain;
|
|
7
7
|
exports.toErrorMessage = toErrorMessage;
|
|
8
|
+
exports.resolveBackendChoice = resolveBackendChoice;
|
|
8
9
|
exports.resolveExpoVersion = resolveExpoVersion;
|
|
9
10
|
exports.installDependencies = installDependencies;
|
|
10
11
|
exports.resolveExpoDependencies = resolveExpoDependencies;
|
|
11
12
|
exports.promptClerkSetup = promptClerkSetup;
|
|
13
|
+
exports.promptInstantSetup = promptInstantSetup;
|
|
12
14
|
exports.writeAuthEnvironment = writeAuthEnvironment;
|
|
15
|
+
exports.writeInstantEnvironment = writeInstantEnvironment;
|
|
13
16
|
exports.initializeConvex = initializeConvex;
|
|
14
17
|
exports.ensureExpoPublicConvexUrl = ensureExpoPublicConvexUrl;
|
|
15
18
|
exports.setConvexClerkEnv = setConvexClerkEnv;
|
|
@@ -58,6 +61,34 @@ function normalizeSdkArg(sdk) {
|
|
|
58
61
|
}
|
|
59
62
|
throw new Error(`Invalid --sdk value "${value}". Use "latest", "previous", or a version like "54.0.33".`);
|
|
60
63
|
}
|
|
64
|
+
async function resolveBackendChoice(options) {
|
|
65
|
+
if (options.backend) {
|
|
66
|
+
return options.backend;
|
|
67
|
+
}
|
|
68
|
+
if (!options.interactive) {
|
|
69
|
+
return "convex";
|
|
70
|
+
}
|
|
71
|
+
const choice = await (0, prompts_1.select)({
|
|
72
|
+
message: "Which backend do you want?",
|
|
73
|
+
options: [
|
|
74
|
+
{
|
|
75
|
+
value: "convex",
|
|
76
|
+
label: "Convex + Clerk",
|
|
77
|
+
hint: "Auth + realtime backend",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
value: "instantdb",
|
|
81
|
+
label: "InstantDB",
|
|
82
|
+
hint: "Client-first realtime backend",
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
initialValue: "convex",
|
|
86
|
+
});
|
|
87
|
+
if ((0, prompts_1.isCancel)(choice)) {
|
|
88
|
+
return "convex";
|
|
89
|
+
}
|
|
90
|
+
return choice;
|
|
91
|
+
}
|
|
61
92
|
async function resolveExpoVersion(options) {
|
|
62
93
|
if (options.sdk) {
|
|
63
94
|
const normalized = normalizeSdkArg(options.sdk);
|
|
@@ -144,24 +175,21 @@ async function installDependencies(projectDir) {
|
|
|
144
175
|
}
|
|
145
176
|
}
|
|
146
177
|
}
|
|
147
|
-
async function resolveExpoDependencies(projectDir) {
|
|
148
|
-
const
|
|
178
|
+
async function resolveExpoDependencies(projectDir, backend) {
|
|
179
|
+
const commonExpoPackages = [
|
|
149
180
|
"react",
|
|
150
181
|
"react-dom",
|
|
151
182
|
"react-native",
|
|
152
183
|
"expo-router",
|
|
153
184
|
"@expo/vector-icons",
|
|
154
|
-
"expo-auth-session",
|
|
155
185
|
"expo-blur",
|
|
156
186
|
"expo-constants",
|
|
157
187
|
"expo-font",
|
|
158
188
|
"expo-haptics",
|
|
159
189
|
"expo-linking",
|
|
160
|
-
"expo-secure-store",
|
|
161
190
|
"expo-splash-screen",
|
|
162
191
|
"expo-status-bar",
|
|
163
192
|
"expo-system-ui",
|
|
164
|
-
"expo-web-browser",
|
|
165
193
|
"react-native-gesture-handler",
|
|
166
194
|
"react-native-reanimated",
|
|
167
195
|
"react-native-safe-area-context",
|
|
@@ -169,6 +197,10 @@ async function resolveExpoDependencies(projectDir) {
|
|
|
169
197
|
"react-native-svg",
|
|
170
198
|
"react-native-web",
|
|
171
199
|
];
|
|
200
|
+
const backendExpoPackages = backend === "convex"
|
|
201
|
+
? ["expo-auth-session", "expo-secure-store", "expo-web-browser"]
|
|
202
|
+
: ["@react-native-async-storage/async-storage", "@react-native-community/netinfo"];
|
|
203
|
+
const expoPackages = [...commonExpoPackages, ...backendExpoPackages];
|
|
172
204
|
try {
|
|
173
205
|
await (0, run_1.run)("npx", ["expo", "install", ...expoPackages], { cwd: projectDir });
|
|
174
206
|
return true;
|
|
@@ -240,6 +272,24 @@ async function promptClerkSetup(options) {
|
|
|
240
272
|
}
|
|
241
273
|
return { publishableKey, domain, jwtCreated };
|
|
242
274
|
}
|
|
275
|
+
async function promptInstantSetup(options) {
|
|
276
|
+
let appId = options.instantAppId?.trim() ?? "";
|
|
277
|
+
if (!appId && options.interactive) {
|
|
278
|
+
prompts_1.log.info("");
|
|
279
|
+
prompts_1.log.info(picocolors_1.default.bold("InstantDB Setup"));
|
|
280
|
+
prompts_1.log.info(picocolors_1.default.dim("Create an app id with: npx instant-cli init-without-files --title my-app"));
|
|
281
|
+
prompts_1.log.info(picocolors_1.default.dim("Press Enter to skip and configure later."));
|
|
282
|
+
const appIdInput = await (0, prompts_1.text)({
|
|
283
|
+
message: "InstantDB App ID",
|
|
284
|
+
placeholder: "your-instant-app-id",
|
|
285
|
+
defaultValue: "",
|
|
286
|
+
});
|
|
287
|
+
if (!(0, prompts_1.isCancel)(appIdInput) && appIdInput) {
|
|
288
|
+
appId = appIdInput.trim();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return { appId };
|
|
292
|
+
}
|
|
243
293
|
async function writeAuthEnvironment(projectDir, clerk) {
|
|
244
294
|
const envLocalPath = (0, node_path_1.join)(projectDir, ".env.local");
|
|
245
295
|
let envContents = "";
|
|
@@ -265,6 +315,23 @@ async function writeAuthEnvironment(projectDir, clerk) {
|
|
|
265
315
|
await (0, fs_1.writeTextFile)((0, node_path_1.join)(projectDir, "convex/auth.config.ts"), `export default {\n providers: [\n {\n domain: ${serializedDomain},\n applicationID: "convex",\n },\n ],\n};\n`);
|
|
266
316
|
}
|
|
267
317
|
}
|
|
318
|
+
async function writeInstantEnvironment(projectDir, instant) {
|
|
319
|
+
if (!instant.appId) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const envLocalPath = (0, node_path_1.join)(projectDir, ".env.local");
|
|
323
|
+
let envContents = "";
|
|
324
|
+
try {
|
|
325
|
+
envContents = await (0, fs_1.readTextFile)(envLocalPath);
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
if (isErrnoException(error) && error.code !== "ENOENT") {
|
|
329
|
+
prompts_1.log.warn(`Could not read .env.local: ${toErrorMessage(error)}`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "EXPO_PUBLIC_INSTANT_APP_ID", instant.appId);
|
|
333
|
+
await (0, fs_1.writeTextFile)(envLocalPath, envContents);
|
|
334
|
+
}
|
|
268
335
|
async function initializeConvex(projectDir) {
|
|
269
336
|
try {
|
|
270
337
|
await (0, run_1.run)("bunx", ["convex", "dev", "--once"], { cwd: projectDir });
|
|
@@ -343,18 +410,23 @@ async function initializeGit(projectDir) {
|
|
|
343
410
|
return false;
|
|
344
411
|
}
|
|
345
412
|
}
|
|
346
|
-
async function runHealthChecks(projectDir, clerkPublishableKey) {
|
|
413
|
+
async function runHealthChecks(projectDir, backend, clerkPublishableKey) {
|
|
347
414
|
const envLocalPath = (0, node_path_1.join)(projectDir, ".env.local");
|
|
348
415
|
const checks = [];
|
|
349
416
|
const envExists = await (0, fs_1.pathExists)(envLocalPath);
|
|
350
417
|
checks.push({ label: ".env.local exists", ok: envExists });
|
|
351
418
|
if (!envExists) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
419
|
+
if (backend === "instantdb") {
|
|
420
|
+
checks.push({ label: "EXPO_PUBLIC_INSTANT_APP_ID is set", ok: false });
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
checks.push({ label: "EXPO_PUBLIC_CONVEX_URL is set", ok: false });
|
|
424
|
+
if (clerkPublishableKey) {
|
|
425
|
+
checks.push({
|
|
426
|
+
label: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY is set",
|
|
427
|
+
ok: false,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
358
430
|
}
|
|
359
431
|
return checks;
|
|
360
432
|
}
|
|
@@ -365,15 +437,23 @@ async function runHealthChecks(projectDir, clerkPublishableKey) {
|
|
|
365
437
|
catch (error) {
|
|
366
438
|
prompts_1.log.warn(`Could not read .env.local during health checks: ${toErrorMessage(error)}`);
|
|
367
439
|
}
|
|
368
|
-
|
|
369
|
-
label: "EXPO_PUBLIC_CONVEX_URL is set",
|
|
370
|
-
ok: envContent.includes("EXPO_PUBLIC_CONVEX_URL"),
|
|
371
|
-
});
|
|
372
|
-
if (clerkPublishableKey) {
|
|
440
|
+
if (backend === "instantdb") {
|
|
373
441
|
checks.push({
|
|
374
|
-
label: "
|
|
375
|
-
ok: envContent.includes("
|
|
442
|
+
label: "EXPO_PUBLIC_INSTANT_APP_ID is set",
|
|
443
|
+
ok: envContent.includes("EXPO_PUBLIC_INSTANT_APP_ID"),
|
|
376
444
|
});
|
|
377
445
|
}
|
|
446
|
+
else {
|
|
447
|
+
checks.push({
|
|
448
|
+
label: "EXPO_PUBLIC_CONVEX_URL is set",
|
|
449
|
+
ok: envContent.includes("EXPO_PUBLIC_CONVEX_URL"),
|
|
450
|
+
});
|
|
451
|
+
if (clerkPublishableKey) {
|
|
452
|
+
checks.push({
|
|
453
|
+
label: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY is set",
|
|
454
|
+
ok: envContent.includes("EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY"),
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
378
458
|
return checks;
|
|
379
459
|
}
|
package/dist/commands/new.js
CHANGED
|
@@ -34,9 +34,14 @@ const notFound_1 = require("../templates/app/notFound");
|
|
|
34
34
|
const rootLayout_1 = require("../templates/app/rootLayout");
|
|
35
35
|
const settingsScreen_1 = require("../templates/app/settingsScreen");
|
|
36
36
|
const signIn_1 = require("../templates/app/signIn");
|
|
37
|
-
const signUp_1 = require("../templates/app/signUp");
|
|
38
37
|
const tabsLayout_1 = require("../templates/app/tabsLayout");
|
|
39
38
|
const todosScreen_1 = require("../templates/app/todosScreen");
|
|
39
|
+
// Instant templates
|
|
40
|
+
const instantLib_1 = require("../templates/instant/instantLib");
|
|
41
|
+
const rootLayout_2 = require("../templates/instant/rootLayout");
|
|
42
|
+
const settingsScreen_2 = require("../templates/instant/settingsScreen");
|
|
43
|
+
const tabsLayout_2 = require("../templates/instant/tabsLayout");
|
|
44
|
+
const todosScreen_2 = require("../templates/instant/todosScreen");
|
|
40
45
|
// Component templates
|
|
41
46
|
const addTodoForm_1 = require("../templates/components/addTodoForm");
|
|
42
47
|
const emptyState_1 = require("../templates/components/emptyState");
|
|
@@ -44,18 +49,42 @@ const filterTabs_1 = require("../templates/components/filterTabs");
|
|
|
44
49
|
const todoItem_1 = require("../templates/components/todoItem");
|
|
45
50
|
// Hook templates
|
|
46
51
|
const useFrameworkReady_1 = require("../templates/hooks/useFrameworkReady");
|
|
47
|
-
function buildTemplateFiles(projectName, expoVersion) {
|
|
52
|
+
function buildTemplateFiles(projectName, expoVersion, backend) {
|
|
53
|
+
if (backend === "instantdb") {
|
|
54
|
+
const files = [
|
|
55
|
+
// Config
|
|
56
|
+
["package.json", (0, packageJson_1.packageJsonTemplate)(projectName, expoVersion, backend)],
|
|
57
|
+
["app.json", (0, appJson_1.appJsonTemplate)(projectName)],
|
|
58
|
+
["tsconfig.json", (0, tsconfig_1.tsconfigTemplate)()],
|
|
59
|
+
[".gitignore", (0, gitignore_1.gitignoreTemplate)()],
|
|
60
|
+
[".env.example", (0, envExample_1.envExampleTemplate)(backend)],
|
|
61
|
+
["expo-env.d.ts", (0, tsconfig_1.expoEnvDtsTemplate)()],
|
|
62
|
+
[".prettierrc", (0, prettierrc_1.prettierrcTemplate)()],
|
|
63
|
+
["eas.json", (0, easJson_1.easJsonTemplate)()],
|
|
64
|
+
["README.md", (0, readme_1.readmeTemplate)(projectName, backend)],
|
|
65
|
+
// Instant app
|
|
66
|
+
["app/_layout.tsx", (0, rootLayout_2.instantRootLayoutTemplate)()],
|
|
67
|
+
["app/+not-found.tsx", (0, notFound_1.notFoundTemplate)()],
|
|
68
|
+
["app/(tabs)/_layout.tsx", (0, tabsLayout_2.instantTabsLayoutTemplate)()],
|
|
69
|
+
["app/(tabs)/index.tsx", (0, todosScreen_2.instantTodosScreenTemplate)()],
|
|
70
|
+
["app/(tabs)/settings.tsx", (0, settingsScreen_2.instantSettingsScreenTemplate)()],
|
|
71
|
+
["lib/instant.ts", (0, instantLib_1.instantLibTemplate)()],
|
|
72
|
+
// Hooks
|
|
73
|
+
["hooks/useFrameworkReady.ts", (0, useFrameworkReady_1.useFrameworkReadyTemplate)()],
|
|
74
|
+
];
|
|
75
|
+
return files.map(([path, content]) => ({ path, content }));
|
|
76
|
+
}
|
|
48
77
|
const files = [
|
|
49
78
|
// Config
|
|
50
|
-
["package.json", (0, packageJson_1.packageJsonTemplate)(projectName, expoVersion)],
|
|
79
|
+
["package.json", (0, packageJson_1.packageJsonTemplate)(projectName, expoVersion, backend)],
|
|
51
80
|
["app.json", (0, appJson_1.appJsonTemplate)(projectName)],
|
|
52
81
|
["tsconfig.json", (0, tsconfig_1.tsconfigTemplate)()],
|
|
53
82
|
[".gitignore", (0, gitignore_1.gitignoreTemplate)()],
|
|
54
|
-
[".env.example", (0, envExample_1.envExampleTemplate)()],
|
|
83
|
+
[".env.example", (0, envExample_1.envExampleTemplate)(backend)],
|
|
55
84
|
["expo-env.d.ts", (0, tsconfig_1.expoEnvDtsTemplate)()],
|
|
56
85
|
[".prettierrc", (0, prettierrc_1.prettierrcTemplate)()],
|
|
57
86
|
["eas.json", (0, easJson_1.easJsonTemplate)()],
|
|
58
|
-
["README.md", (0, readme_1.readmeTemplate)(projectName)],
|
|
87
|
+
["README.md", (0, readme_1.readmeTemplate)(projectName, backend)],
|
|
59
88
|
// Convex
|
|
60
89
|
["convex/schema.ts", (0, schema_1.schemaTemplate)()],
|
|
61
90
|
["convex/todos.ts", (0, todos_1.todosTemplate)()],
|
|
@@ -69,7 +98,6 @@ function buildTemplateFiles(projectName, expoVersion) {
|
|
|
69
98
|
["app/providers/AuthProvider.tsx", (0, authProvider_1.authProviderTemplate)()],
|
|
70
99
|
["app/(auth)/_layout.tsx", (0, authLayout_1.authLayoutTemplate)()],
|
|
71
100
|
["app/(auth)/sign-in.tsx", (0, signIn_1.signInTemplate)()],
|
|
72
|
-
["app/(auth)/sign-up.tsx", (0, signUp_1.signUpTemplate)()],
|
|
73
101
|
["app/(tabs)/_layout.tsx", (0, tabsLayout_1.tabsLayoutTemplate)()],
|
|
74
102
|
["app/(tabs)/index.tsx", (0, todosScreen_1.todosScreenTemplate)()],
|
|
75
103
|
["app/(tabs)/settings.tsx", (0, settingsScreen_1.settingsScreenTemplate)()],
|
|
@@ -130,14 +158,20 @@ async function cmdNew(rawArgs) {
|
|
|
130
158
|
}
|
|
131
159
|
await (0, fs_1.removeDir)(projectDir);
|
|
132
160
|
}
|
|
133
|
-
|
|
161
|
+
const backend = await (0, steps_1.resolveBackendChoice)(options);
|
|
162
|
+
if (backend === "convex" && options.clerkDomain) {
|
|
134
163
|
options.clerkDomain = (0, steps_1.normalizeClerkDomain)(options.clerkDomain);
|
|
135
164
|
}
|
|
136
165
|
const expoVersion = await (0, steps_1.resolveExpoVersion)(options);
|
|
137
|
-
const clerk =
|
|
138
|
-
|
|
166
|
+
const clerk = backend === "convex"
|
|
167
|
+
? await (0, steps_1.promptClerkSetup)(options)
|
|
168
|
+
: { publishableKey: "", domain: "", jwtCreated: false };
|
|
169
|
+
const instant = backend === "instantdb"
|
|
170
|
+
? await (0, steps_1.promptInstantSetup)(options)
|
|
171
|
+
: { appId: "" };
|
|
172
|
+
const totalSteps = 1 + // project generation
|
|
139
173
|
(options.installDependencies ? 2 : 0) +
|
|
140
|
-
(clerk.domain ? 1 : 0) +
|
|
174
|
+
(backend === "convex" ? 2 + (clerk.domain ? 1 : 0) : 1) +
|
|
141
175
|
(options.initializeGit ? 1 : 0) +
|
|
142
176
|
1; // health check
|
|
143
177
|
let currentStep = 0;
|
|
@@ -145,11 +179,26 @@ async function cmdNew(rawArgs) {
|
|
|
145
179
|
const structureSpinner = (0, prompts_1.spinner)();
|
|
146
180
|
currentStep++;
|
|
147
181
|
structureSpinner.start(`Creating project structure ${stepLabel()}`);
|
|
148
|
-
await (0, scaffold_1.createProjectDirectories)(projectDir);
|
|
149
|
-
await (0, scaffold_1.writeScaffoldFiles)(projectDir, buildTemplateFiles(projectName, expoVersion));
|
|
182
|
+
await (0, scaffold_1.createProjectDirectories)(projectDir, backend);
|
|
183
|
+
await (0, scaffold_1.writeScaffoldFiles)(projectDir, buildTemplateFiles(projectName, expoVersion, backend));
|
|
150
184
|
await (0, scaffold_1.writeDefaultAssetFiles)(projectDir);
|
|
151
|
-
await (0, steps_1.writeAuthEnvironment)(projectDir, clerk);
|
|
152
185
|
structureSpinner.stop("Project files created.");
|
|
186
|
+
currentStep++;
|
|
187
|
+
if (backend === "convex") {
|
|
188
|
+
prompts_1.log.step(`Configuring Convex + Clerk ${stepLabel()}`);
|
|
189
|
+
await (0, steps_1.writeAuthEnvironment)(projectDir, clerk);
|
|
190
|
+
prompts_1.log.success("Convex and Clerk environment configured.");
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
prompts_1.log.step(`Configuring InstantDB ${stepLabel()}`);
|
|
194
|
+
await (0, steps_1.writeInstantEnvironment)(projectDir, instant);
|
|
195
|
+
if (instant.appId) {
|
|
196
|
+
prompts_1.log.success("InstantDB environment configured.");
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
prompts_1.log.info(picocolors_1.default.dim("Set EXPO_PUBLIC_INSTANT_APP_ID in .env.local to connect InstantDB."));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
153
202
|
if (options.installDependencies) {
|
|
154
203
|
currentStep++;
|
|
155
204
|
prompts_1.log.step(`Installing dependencies ${stepLabel()}`);
|
|
@@ -162,7 +211,7 @@ async function cmdNew(rawArgs) {
|
|
|
162
211
|
}
|
|
163
212
|
currentStep++;
|
|
164
213
|
prompts_1.log.step(`Installing Expo dependencies ${stepLabel()}`);
|
|
165
|
-
const resolved = await (0, steps_1.resolveExpoDependencies)(projectDir);
|
|
214
|
+
const resolved = await (0, steps_1.resolveExpoDependencies)(projectDir, backend);
|
|
166
215
|
if (resolved) {
|
|
167
216
|
prompts_1.log.success("Expo dependencies resolved.");
|
|
168
217
|
}
|
|
@@ -170,31 +219,33 @@ async function cmdNew(rawArgs) {
|
|
|
170
219
|
else {
|
|
171
220
|
prompts_1.log.info(picocolors_1.default.dim("Skipping dependency installation (--no-install)."));
|
|
172
221
|
}
|
|
173
|
-
|
|
174
|
-
prompts_1.log.step(`Setting up Convex ${stepLabel()}`);
|
|
175
|
-
const convexInitialized = await (0, steps_1.initializeConvex)(projectDir);
|
|
176
|
-
if (convexInitialized) {
|
|
177
|
-
prompts_1.log.success("Convex initialized.");
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
prompts_1.log.info(picocolors_1.default.dim(`Run manually: cd ${projectName} && bunx convex dev --once`));
|
|
181
|
-
}
|
|
182
|
-
const convexUrlReady = await (0, steps_1.ensureExpoPublicConvexUrl)(projectDir);
|
|
183
|
-
if (convexUrlReady) {
|
|
184
|
-
prompts_1.log.success("Convex URL configured for Expo.");
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
prompts_1.log.info(picocolors_1.default.dim("Add EXPO_PUBLIC_CONVEX_URL to .env.local with your Convex deployment URL."));
|
|
188
|
-
}
|
|
189
|
-
if (clerk.domain) {
|
|
222
|
+
if (backend === "convex") {
|
|
190
223
|
currentStep++;
|
|
191
|
-
prompts_1.log.step(`Setting Convex
|
|
192
|
-
const
|
|
193
|
-
if (
|
|
194
|
-
prompts_1.log.success("Convex
|
|
224
|
+
prompts_1.log.step(`Setting up Convex ${stepLabel()}`);
|
|
225
|
+
const convexInitialized = await (0, steps_1.initializeConvex)(projectDir);
|
|
226
|
+
if (convexInitialized) {
|
|
227
|
+
prompts_1.log.success("Convex initialized.");
|
|
195
228
|
}
|
|
196
229
|
else {
|
|
197
|
-
prompts_1.log.info(picocolors_1.default.dim(`Run manually: bunx convex
|
|
230
|
+
prompts_1.log.info(picocolors_1.default.dim(`Run manually: cd ${projectName} && bunx convex dev --once`));
|
|
231
|
+
}
|
|
232
|
+
const convexUrlReady = await (0, steps_1.ensureExpoPublicConvexUrl)(projectDir);
|
|
233
|
+
if (convexUrlReady) {
|
|
234
|
+
prompts_1.log.success("Convex URL configured for Expo.");
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
prompts_1.log.info(picocolors_1.default.dim("Add EXPO_PUBLIC_CONVEX_URL to .env.local with your Convex deployment URL."));
|
|
238
|
+
}
|
|
239
|
+
if (clerk.domain) {
|
|
240
|
+
currentStep++;
|
|
241
|
+
prompts_1.log.step(`Setting Convex environment variable ${stepLabel()}`);
|
|
242
|
+
const setEnvOk = await (0, steps_1.setConvexClerkEnv)(projectDir, clerk.domain);
|
|
243
|
+
if (setEnvOk) {
|
|
244
|
+
prompts_1.log.success("Convex environment variable set.");
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
prompts_1.log.info(picocolors_1.default.dim(`Run manually: bunx convex env set CLERK_JWT_ISSUER_DOMAIN ${clerk.domain}`));
|
|
248
|
+
}
|
|
198
249
|
}
|
|
199
250
|
}
|
|
200
251
|
if (options.initializeGit) {
|
|
@@ -210,7 +261,7 @@ async function cmdNew(rawArgs) {
|
|
|
210
261
|
}
|
|
211
262
|
currentStep++;
|
|
212
263
|
prompts_1.log.step(`Running health check ${stepLabel()}`);
|
|
213
|
-
const healthChecks = await (0, steps_1.runHealthChecks)(projectDir, clerk.publishableKey);
|
|
264
|
+
const healthChecks = await (0, steps_1.runHealthChecks)(projectDir, backend, clerk.publishableKey);
|
|
214
265
|
for (const check of healthChecks) {
|
|
215
266
|
if (check.ok) {
|
|
216
267
|
prompts_1.log.info(` ${picocolors_1.default.green("✓")} ${check.label}`);
|
|
@@ -220,7 +271,26 @@ async function cmdNew(rawArgs) {
|
|
|
220
271
|
}
|
|
221
272
|
}
|
|
222
273
|
prompts_1.log.info("");
|
|
223
|
-
if (
|
|
274
|
+
if (backend === "instantdb") {
|
|
275
|
+
prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green("Project scaffolded with InstantDB!")));
|
|
276
|
+
prompts_1.log.info("");
|
|
277
|
+
prompts_1.log.info(" Next steps:");
|
|
278
|
+
prompts_1.log.info(` ${picocolors_1.default.cyan("cd")} ${projectName}`);
|
|
279
|
+
if (options.installDependencies) {
|
|
280
|
+
prompts_1.log.info(` ${picocolors_1.default.cyan("bunx expo start")}`);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
prompts_1.log.info(` ${picocolors_1.default.cyan("bun install")} ${picocolors_1.default.dim("# or npm install")}`);
|
|
284
|
+
prompts_1.log.info(` ${picocolors_1.default.cyan("bunx expo start")}`);
|
|
285
|
+
}
|
|
286
|
+
if (!instant.appId) {
|
|
287
|
+
prompts_1.log.info("");
|
|
288
|
+
prompts_1.log.info(picocolors_1.default.dim(" Add EXPO_PUBLIC_INSTANT_APP_ID to .env.local first."));
|
|
289
|
+
prompts_1.log.info(picocolors_1.default.dim(" Create one with: npx instant-cli init-without-files --title " +
|
|
290
|
+
projectName));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
else if (clerk.publishableKey && clerk.domain) {
|
|
224
294
|
prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green("Your app is ready!")));
|
|
225
295
|
prompts_1.log.info("");
|
|
226
296
|
prompts_1.log.info(" Next steps:");
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "react-native";
|
|
15
15
|
import { SafeAreaView } from "react-native-safe-area-context";
|
|
16
16
|
import { useOAuth } from "@clerk/clerk-expo";
|
|
17
|
-
import {
|
|
17
|
+
import { useRouter } from "expo-router";
|
|
18
18
|
import { SquareCheck as CheckSquare } from "lucide-react-native";
|
|
19
19
|
import Svg, { Path } from "react-native-svg";
|
|
20
20
|
import * as WebBrowser from "expo-web-browser";
|
|
@@ -127,11 +127,6 @@ export default function SignInScreen() {
|
|
|
127
127
|
<Text style={styles.terms}>
|
|
128
128
|
By continuing, you agree to our Terms of Service and Privacy Policy
|
|
129
129
|
</Text>
|
|
130
|
-
<Link href="/(auth)/sign-up" asChild>
|
|
131
|
-
<TouchableOpacity style={styles.emailLinkWrap} activeOpacity={0.7}>
|
|
132
|
-
<Text style={styles.emailLinkText}>Use email and password instead</Text>
|
|
133
|
-
</TouchableOpacity>
|
|
134
|
-
</Link>
|
|
135
130
|
</View>
|
|
136
131
|
</SafeAreaView>
|
|
137
132
|
);
|
|
@@ -219,15 +214,6 @@ const styles = StyleSheet.create({
|
|
|
219
214
|
lineHeight: 20,
|
|
220
215
|
paddingHorizontal: 16,
|
|
221
216
|
},
|
|
222
|
-
emailLinkWrap: {
|
|
223
|
-
marginTop: 16,
|
|
224
|
-
alignItems: "center",
|
|
225
|
-
},
|
|
226
|
-
emailLinkText: {
|
|
227
|
-
fontSize: 14,
|
|
228
|
-
fontWeight: "600",
|
|
229
|
-
color: "#4B5563",
|
|
230
|
-
},
|
|
231
217
|
});
|
|
232
218
|
`;
|
|
233
219
|
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.envExampleTemplate = envExampleTemplate;
|
|
4
|
-
function envExampleTemplate() {
|
|
4
|
+
function envExampleTemplate(backend) {
|
|
5
|
+
if (backend === "instantdb") {
|
|
6
|
+
return `# InstantDB
|
|
7
|
+
EXPO_PUBLIC_INSTANT_APP_ID=
|
|
8
|
+
`;
|
|
9
|
+
}
|
|
5
10
|
return `# Convex
|
|
6
11
|
EXPO_PUBLIC_CONVEX_URL=
|
|
7
12
|
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.packageJsonTemplate = packageJsonTemplate;
|
|
4
|
-
function packageJsonTemplate(appName, expoVersion = "latest") {
|
|
4
|
+
function packageJsonTemplate(appName, expoVersion = "latest", backend = "convex") {
|
|
5
|
+
const backendDependencies = backend === "instantdb"
|
|
6
|
+
? {
|
|
7
|
+
"@instantdb/react-native": "^0.20.0",
|
|
8
|
+
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
9
|
+
"@react-native-community/netinfo": "^11.4.1",
|
|
10
|
+
"react-native-get-random-values": "^1.11.0",
|
|
11
|
+
}
|
|
12
|
+
: {
|
|
13
|
+
"@clerk/clerk-expo": "^2.14.24",
|
|
14
|
+
convex: "^1.26.1",
|
|
15
|
+
};
|
|
5
16
|
const pkg = {
|
|
6
17
|
name: appName,
|
|
7
18
|
main: "expo-router/entry",
|
|
@@ -14,12 +25,11 @@ function packageJsonTemplate(appName, expoVersion = "latest") {
|
|
|
14
25
|
format: "prettier --write .",
|
|
15
26
|
},
|
|
16
27
|
dependencies: {
|
|
17
|
-
"@clerk/clerk-expo": "^2.14.24",
|
|
18
28
|
"@react-navigation/bottom-tabs": "^7.3.10",
|
|
19
29
|
"@react-navigation/native": "^7.1.6",
|
|
20
|
-
convex: "^1.26.1",
|
|
21
30
|
expo: expoVersion,
|
|
22
31
|
"lucide-react-native": "^0.542.0",
|
|
32
|
+
...backendDependencies,
|
|
23
33
|
},
|
|
24
34
|
devDependencies: {
|
|
25
35
|
"@babel/core": "^7.25.2",
|
|
@@ -1,7 +1,51 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.readmeTemplate = readmeTemplate;
|
|
4
|
-
function readmeTemplate(projectName) {
|
|
4
|
+
function readmeTemplate(projectName, backend) {
|
|
5
|
+
if (backend === "instantdb") {
|
|
6
|
+
return `# ${projectName}
|
|
7
|
+
|
|
8
|
+
A mobile app built with **Expo** and **InstantDB**.
|
|
9
|
+
|
|
10
|
+
## Tech Stack
|
|
11
|
+
|
|
12
|
+
- **[Expo](https://expo.dev)** — React Native framework
|
|
13
|
+
- **[InstantDB](https://instantdb.com)** — Real-time backend and sync
|
|
14
|
+
- **[Expo Router](https://docs.expo.dev/router/introduction/)** — File-based navigation
|
|
15
|
+
|
|
16
|
+
## Getting Started
|
|
17
|
+
|
|
18
|
+
\`\`\`bash
|
|
19
|
+
# Install dependencies
|
|
20
|
+
bun install
|
|
21
|
+
|
|
22
|
+
# Add your Instant app id
|
|
23
|
+
cp .env.example .env.local
|
|
24
|
+
|
|
25
|
+
# Start Expo
|
|
26
|
+
bunx expo start
|
|
27
|
+
\`\`\`
|
|
28
|
+
|
|
29
|
+
Set \`EXPO_PUBLIC_INSTANT_APP_ID\` in \`.env.local\`. You can create an Instant app id via:
|
|
30
|
+
|
|
31
|
+
\`\`\`bash
|
|
32
|
+
npx instant-cli init-without-files --title ${projectName}
|
|
33
|
+
\`\`\`
|
|
34
|
+
|
|
35
|
+
## Scripts
|
|
36
|
+
|
|
37
|
+
| Command | Description |
|
|
38
|
+
|---------|-------------|
|
|
39
|
+
| \`bun run dev\` | Start Expo dev server |
|
|
40
|
+
| \`bun run lint\` | Run ESLint |
|
|
41
|
+
| \`bun run format\` | Format code with Prettier |
|
|
42
|
+
|
|
43
|
+
## Learn More
|
|
44
|
+
|
|
45
|
+
- [Expo Docs](https://docs.expo.dev)
|
|
46
|
+
- [InstantDB Docs](https://instantdb.com/docs)
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
5
49
|
return `# ${projectName}
|
|
6
50
|
|
|
7
51
|
A full-stack mobile app built with **Expo**, **Convex**, and **Clerk**.
|