create-100x-mobile 0.4.2 → 0.4.4

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.
@@ -4,15 +4,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.cmdNew = cmdNew;
7
+ exports.runNewCommand = runNewCommand;
7
8
  const node_path_1 = require("node:path");
8
9
  const prompts_1 = require("@clack/prompts");
9
10
  const picocolors_1 = __importDefault(require("picocolors"));
11
+ const args_1 = require("./new/args");
12
+ const scaffold_1 = require("./new/scaffold");
13
+ const steps_1 = require("./new/steps");
10
14
  const fs_1 = require("../lib/fs");
11
15
  const projectName_1 = require("../lib/projectName");
12
- const run_1 = require("../lib/run");
13
- const dotenv_1 = require("../lib/dotenv");
14
- const fs_2 = require("../lib/fs");
15
- const clerk_1 = require("../lib/clerk");
16
16
  // Config templates
17
17
  const packageJson_1 = require("../templates/config/packageJson");
18
18
  const appJson_1 = require("../templates/config/appJson");
@@ -25,140 +25,25 @@ const readme_1 = require("../templates/config/readme");
25
25
  // Convex templates
26
26
  const schema_1 = require("../templates/convex/schema");
27
27
  const todos_1 = require("../templates/convex/todos");
28
- const users_1 = require("../templates/convex/users");
29
28
  const authConfig_1 = require("../templates/convex/authConfig");
29
+ const users_1 = require("../templates/convex/users");
30
30
  // App templates
31
- const rootLayout_1 = require("../templates/app/rootLayout");
32
- const notFound_1 = require("../templates/app/notFound");
33
- const authProvider_1 = require("../templates/app/authProvider");
34
31
  const authLayout_1 = require("../templates/app/authLayout");
32
+ const authProvider_1 = require("../templates/app/authProvider");
33
+ const notFound_1 = require("../templates/app/notFound");
34
+ const rootLayout_1 = require("../templates/app/rootLayout");
35
+ const settingsScreen_1 = require("../templates/app/settingsScreen");
35
36
  const signIn_1 = require("../templates/app/signIn");
36
37
  const tabsLayout_1 = require("../templates/app/tabsLayout");
37
38
  const todosScreen_1 = require("../templates/app/todosScreen");
38
- const settingsScreen_1 = require("../templates/app/settingsScreen");
39
39
  // Component templates
40
40
  const addTodoForm_1 = require("../templates/components/addTodoForm");
41
- const todoItem_1 = require("../templates/components/todoItem");
42
- const filterTabs_1 = require("../templates/components/filterTabs");
43
41
  const emptyState_1 = require("../templates/components/emptyState");
42
+ const filterTabs_1 = require("../templates/components/filterTabs");
43
+ const todoItem_1 = require("../templates/components/todoItem");
44
44
  // Hook templates
45
45
  const useFrameworkReady_1 = require("../templates/hooks/useFrameworkReady");
46
- async function cmdNew(args) {
47
- (0, prompts_1.intro)(picocolors_1.default.bold(picocolors_1.default.cyan("create-100x-mobile")));
48
- // ── Step 1: Parse & validate project name ──────────────
49
- let projectName = args[0];
50
- if (!projectName) {
51
- const nameInput = await (0, prompts_1.text)({
52
- message: "What is your project name?",
53
- placeholder: "my-app",
54
- defaultValue: "my-app",
55
- });
56
- if ((0, prompts_1.isCancel)(nameInput) || !nameInput) {
57
- (0, prompts_1.cancel)("Operation cancelled.");
58
- return;
59
- }
60
- projectName = nameInput;
61
- }
62
- const sanitizedName = (0, projectName_1.sanitizeProjectName)(projectName);
63
- if (sanitizedName !== projectName) {
64
- prompts_1.log.warn(`Project name sanitized: "${projectName}" → "${sanitizedName}"`);
65
- projectName = sanitizedName;
66
- }
67
- if (!projectName) {
68
- prompts_1.log.error("Invalid project name.");
69
- process.exitCode = 1;
70
- return;
71
- }
72
- const projectDir = (0, node_path_1.resolve)(process.cwd(), projectName);
73
- // Check if directory exists
74
- if (await (0, fs_1.pathExists)(projectDir)) {
75
- prompts_1.log.warn(`Directory "${projectName}" already exists.`);
76
- const choice = await (0, prompts_1.select)({
77
- message: "What would you like to do?",
78
- options: [
79
- { value: "overwrite", label: "Overwrite the existing directory" },
80
- { value: "cancel", label: "Cancel" },
81
- ],
82
- initialValue: "cancel",
83
- });
84
- if ((0, prompts_1.isCancel)(choice) || choice === "cancel") {
85
- (0, prompts_1.cancel)("Operation cancelled.");
86
- return;
87
- }
88
- await (0, fs_1.removeDir)(projectDir);
89
- }
90
- // ── Expo SDK version selection ───────────────────────────
91
- let expoVersion = "latest";
92
- const sdkSpinner = (0, prompts_1.spinner)();
93
- sdkSpinner.start("Fetching available Expo SDK versions");
94
- try {
95
- const latestVersion = (await (0, run_1.runCapture)("npm", ["view", "expo", "version"])).trim();
96
- const latestMajor = parseInt(latestVersion.split(".")[0], 10);
97
- if (!isNaN(latestMajor)) {
98
- const prevMajor = latestMajor - 1;
99
- const prevJson = await (0, run_1.runCapture)("npm", [
100
- "view",
101
- `expo@^${prevMajor}.0.0`,
102
- "version",
103
- "--json",
104
- ]);
105
- const prevParsed = JSON.parse(prevJson);
106
- const prevVersion = Array.isArray(prevParsed)
107
- ? prevParsed[prevParsed.length - 1]
108
- : prevParsed;
109
- sdkSpinner.stop("Expo SDK versions fetched.");
110
- const sdkChoice = await (0, prompts_1.select)({
111
- message: "Which Expo SDK version?",
112
- options: [
113
- {
114
- value: latestVersion,
115
- label: `SDK ${latestMajor} (latest)`,
116
- hint: `expo ~${latestVersion}`,
117
- },
118
- {
119
- value: prevVersion,
120
- label: `SDK ${prevMajor}`,
121
- hint: `expo ~${prevVersion}`,
122
- },
123
- ],
124
- });
125
- if (!(0, prompts_1.isCancel)(sdkChoice)) {
126
- expoVersion = `~${sdkChoice}`;
127
- }
128
- }
129
- else {
130
- sdkSpinner.stop("Could not parse Expo version.");
131
- }
132
- }
133
- catch {
134
- sdkSpinner.stop("Could not fetch Expo versions, using latest.");
135
- }
136
- // ── Determine total steps ────────────────────────────────
137
- // Steps: 1=Project structure, 2=Dependencies, 3=Convex setup,
138
- // 4=Convex env (if clerkDomain), 5=Git init, 6=Health check
139
- // We'll calculate totalSteps after Clerk prompts, but we create the
140
- // counter now. We'll adjust totalSteps after Clerk prompts.
141
- let currentStep = 0;
142
- let totalSteps = 6; // base: structure, deps, expo deps, convex, git, health check
143
- // totalSteps += 1 if clerkDomain (convex env step) — adjusted after Clerk prompts
144
- const stepLabel = () => `(${currentStep}/${totalSteps})`;
145
- // ── Step: Create directories ─────────────────────────
146
- const s = (0, prompts_1.spinner)();
147
- currentStep++;
148
- s.start(`Creating project structure ${stepLabel()}`);
149
- const dirs = [
150
- "app/(auth)",
151
- "app/(tabs)",
152
- "app/providers",
153
- "components",
154
- "convex",
155
- "hooks",
156
- "assets/images",
157
- ];
158
- for (const dir of dirs) {
159
- await (0, fs_1.ensureDir)((0, node_path_1.join)(projectDir, dir));
160
- }
161
- // ── Step 3: Write all template files ───────────────────
46
+ function buildTemplateFiles(projectName, expoVersion) {
162
47
  const files = [
163
48
  // Config
164
49
  ["package.json", (0, packageJson_1.packageJsonTemplate)(projectName, expoVersion)],
@@ -194,256 +79,136 @@ async function cmdNew(args) {
194
79
  // Hooks
195
80
  ["hooks/useFrameworkReady.ts", (0, useFrameworkReady_1.useFrameworkReadyTemplate)()],
196
81
  ];
197
- for (const [filePath, content] of files) {
198
- await (0, fs_1.writeTextFile)((0, node_path_1.join)(projectDir, filePath), content);
82
+ return files.map(([path, content]) => ({ path, content }));
83
+ }
84
+ async function cmdNew(rawArgs) {
85
+ (0, prompts_1.intro)(picocolors_1.default.bold(picocolors_1.default.cyan("create-100x-mobile")));
86
+ const { options, projectName: projectNameArg } = (0, args_1.parseNewCommandArgs)(rawArgs);
87
+ let projectName = projectNameArg;
88
+ if (!projectName && options.interactive) {
89
+ const nameInput = await (0, prompts_1.text)({
90
+ message: "What is your project name?",
91
+ placeholder: "my-app",
92
+ defaultValue: "my-app",
93
+ });
94
+ if ((0, prompts_1.isCancel)(nameInput) || !nameInput) {
95
+ (0, prompts_1.cancel)("Operation cancelled.");
96
+ return;
97
+ }
98
+ projectName = nameInput;
199
99
  }
200
- s.stop("Project files created.");
201
- // ── Step: Install dependencies ───────────────────────
202
- currentStep++;
203
- prompts_1.log.step(`Installing dependencies ${stepLabel()}`);
204
- try {
205
- await (0, run_1.run)("bun", ["install"], { cwd: projectDir });
100
+ if (!projectName) {
101
+ throw new Error("Project name is required in non-interactive mode.");
206
102
  }
207
- catch {
208
- prompts_1.log.warn("bun install failed, trying npm install...");
209
- try {
210
- await (0, run_1.run)("npm", ["install"], { cwd: projectDir });
211
- }
212
- catch {
213
- prompts_1.log.error("Failed to install dependencies. Run `bun install` or `npm install` manually.");
214
- }
103
+ const sanitizedName = (0, projectName_1.sanitizeProjectName)(projectName);
104
+ if (sanitizedName !== projectName) {
105
+ prompts_1.log.warn(`Project name sanitized: "${projectName}" → "${sanitizedName}"`);
106
+ projectName = sanitizedName;
215
107
  }
216
- prompts_1.log.success("Dependencies installed.");
217
- // ── Step: Resolve Expo dependencies ────────────────────
218
- currentStep++;
219
- prompts_1.log.step(`Installing Expo dependencies ${stepLabel()}`);
220
- const expoPackages = [
221
- "react",
222
- "react-dom",
223
- "react-native",
224
- "expo-router",
225
- "@expo/vector-icons",
226
- "expo-auth-session",
227
- "expo-blur",
228
- "expo-constants",
229
- "expo-font",
230
- "expo-haptics",
231
- "expo-linking",
232
- "expo-secure-store",
233
- "expo-splash-screen",
234
- "expo-status-bar",
235
- "expo-system-ui",
236
- "expo-web-browser",
237
- "react-native-gesture-handler",
238
- "react-native-reanimated",
239
- "react-native-safe-area-context",
240
- "react-native-screens",
241
- "react-native-svg",
242
- "react-native-web",
243
- ];
244
- try {
245
- await (0, run_1.run)("npx", ["expo", "install", ...expoPackages], {
246
- cwd: projectDir,
108
+ if (!projectName) {
109
+ throw new Error("Invalid project name.");
110
+ }
111
+ const projectDir = (0, node_path_1.resolve)(process.cwd(), projectName);
112
+ if (await (0, fs_1.pathExists)(projectDir)) {
113
+ if (!options.interactive) {
114
+ throw new Error(`Directory "${projectName}" already exists. Remove it or choose another name.`);
115
+ }
116
+ prompts_1.log.warn(`Directory "${projectName}" already exists.`);
117
+ const choice = await (0, prompts_1.select)({
118
+ message: "What would you like to do?",
119
+ options: [
120
+ { value: "overwrite", label: "Overwrite the existing directory" },
121
+ { value: "cancel", label: "Cancel" },
122
+ ],
123
+ initialValue: "cancel",
247
124
  });
248
- prompts_1.log.success("Expo dependencies resolved.");
125
+ if ((0, prompts_1.isCancel)(choice) || choice === "cancel") {
126
+ (0, prompts_1.cancel)("Operation cancelled.");
127
+ return;
128
+ }
129
+ await (0, fs_1.removeDir)(projectDir);
249
130
  }
250
- catch {
251
- prompts_1.log.warn("Could not resolve Expo deps automatically.");
252
- prompts_1.log.info(picocolors_1.default.dim(" Run manually: npx expo install --fix"));
131
+ if (options.clerkDomain) {
132
+ options.clerkDomain = (0, steps_1.normalizeClerkDomain)(options.clerkDomain);
253
133
  }
254
- // ── Step 5: Prompt for Clerk keys ──────────────────────
255
- // Moved BEFORE Convex init so we only need a single `convex dev --once` run.
256
- prompts_1.log.info("");
257
- prompts_1.log.info(picocolors_1.default.bold("Clerk Authentication Setup"));
258
- prompts_1.log.info(picocolors_1.default.dim("Get your keys from https://dashboard.clerk.com Your App API Keys"));
259
- prompts_1.log.info(picocolors_1.default.dim("Press Enter to skip and configure later."));
260
- const clerkKey = await (0, prompts_1.text)({
261
- message: "Clerk Publishable Key",
262
- placeholder: "pk_test_...",
263
- defaultValue: "",
264
- });
265
- const clerkKeyValue = (0, prompts_1.isCancel)(clerkKey) || !clerkKey ? "" : clerkKey.trim();
266
- let clerkDomain = "";
267
- let jwtCreated = false;
268
- if (clerkKeyValue) {
269
- // Try to extract domain from publishable key
270
- const derivedDomain = (0, clerk_1.extractDomainFromPublishableKey)(clerkKeyValue);
271
- if (derivedDomain) {
272
- clerkDomain = derivedDomain;
273
- prompts_1.log.info(` Detected issuer domain: ${picocolors_1.default.cyan(clerkDomain)}`);
134
+ const expoVersion = await (0, steps_1.resolveExpoVersion)(options);
135
+ const clerk = await (0, steps_1.promptClerkSetup)(options);
136
+ const totalSteps = 2 + // project generation + convex init
137
+ (options.installDependencies ? 2 : 0) +
138
+ (clerk.domain ? 1 : 0) +
139
+ (options.initializeGit ? 1 : 0) +
140
+ 1; // health check
141
+ let currentStep = 0;
142
+ const stepLabel = () => `(${currentStep}/${totalSteps})`;
143
+ const structureSpinner = (0, prompts_1.spinner)();
144
+ currentStep++;
145
+ structureSpinner.start(`Creating project structure ${stepLabel()}`);
146
+ await (0, scaffold_1.createProjectDirectories)(projectDir);
147
+ await (0, scaffold_1.writeScaffoldFiles)(projectDir, buildTemplateFiles(projectName, expoVersion));
148
+ await (0, scaffold_1.writeDefaultAssetFiles)(projectDir);
149
+ await (0, steps_1.writeAuthEnvironment)(projectDir, clerk);
150
+ structureSpinner.stop("Project files created.");
151
+ if (options.installDependencies) {
152
+ currentStep++;
153
+ prompts_1.log.step(`Installing dependencies ${stepLabel()}`);
154
+ const installed = await (0, steps_1.installDependencies)(projectDir);
155
+ if (installed) {
156
+ prompts_1.log.success("Dependencies installed.");
274
157
  }
275
158
  else {
276
- const domainInput = await (0, prompts_1.text)({
277
- message: "Clerk JWT Issuer Domain",
278
- placeholder: "https://your-app.clerk.accounts.dev",
279
- defaultValue: "",
280
- });
281
- clerkDomain =
282
- (0, prompts_1.isCancel)(domainInput) || !domainInput ? "" : domainInput.trim();
159
+ prompts_1.log.warn("Dependencies were not installed automatically.");
283
160
  }
284
- // Secret key prompt (optional — only needed to auto-create JWT template)
285
- prompts_1.log.info("");
286
- prompts_1.log.info(picocolors_1.default.dim('Provide your Clerk Secret Key to auto-create the "convex" JWT template.'));
287
- prompts_1.log.info(picocolors_1.default.dim("Press Enter to skip if you already have one."));
288
- const secretInput = await (0, prompts_1.text)({
289
- message: "Clerk Secret Key",
290
- placeholder: "sk_test_...",
291
- defaultValue: "",
292
- });
293
- const clerkSecretKey = (0, prompts_1.isCancel)(secretInput) || !secretInput ? "" : secretInput.trim();
294
- if (clerkSecretKey) {
295
- const jwtSpinner = (0, prompts_1.spinner)();
296
- jwtSpinner.start('Creating "convex" JWT template in Clerk');
297
- const result = await (0, clerk_1.createConvexJwtTemplate)(clerkSecretKey);
298
- if (result.success) {
299
- jwtSpinner.stop('JWT template "convex" created in Clerk.');
300
- jwtCreated = true;
301
- }
302
- else {
303
- jwtSpinner.stop("Could not create JWT template.");
304
- prompts_1.log.warn(`Clerk API: ${result.error}`);
305
- prompts_1.log.info(picocolors_1.default.dim('You may need to create a JWT template named "convex" in your Clerk dashboard.'));
306
- }
161
+ currentStep++;
162
+ prompts_1.log.step(`Installing Expo dependencies ${stepLabel()}`);
163
+ const resolved = await (0, steps_1.resolveExpoDependencies)(projectDir);
164
+ if (resolved) {
165
+ prompts_1.log.success("Expo dependencies resolved.");
307
166
  }
308
167
  }
309
- // Adjust total steps if Clerk domain is configured
310
- if (clerkDomain) {
311
- totalSteps = 7; // adds convex env step
312
- }
313
- // ── Write Clerk env vars to .env.local ─────────
314
- const envLocalPath = (0, node_path_1.join)(projectDir, ".env.local");
315
- let envContents = "";
316
- try {
317
- envContents = await (0, fs_2.readTextFile)(envLocalPath);
318
- }
319
- catch {
320
- envContents = "";
321
- }
322
- if (clerkKeyValue) {
323
- envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY", clerkKeyValue);
324
- }
325
- if (clerkDomain) {
326
- envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "CLERK_JWT_ISSUER_DOMAIN", clerkDomain);
327
- }
328
- if (envContents) {
329
- await (0, fs_1.writeTextFile)(envLocalPath, envContents);
330
- }
331
- // ── Step 6b: Hardcode domain in auth.config.ts ─────────
332
- // Convex auth.config.ts is evaluated at deploy time, so process.env
333
- // won't have the value yet (we set it after deploy). Hardcode it.
334
- if (clerkDomain) {
335
- await (0, fs_1.writeTextFile)((0, node_path_1.join)(projectDir, "convex/auth.config.ts"), `export default {\n providers: [\n {\n domain: "${clerkDomain}",\n applicationID: "convex",\n },\n ],\n};\n`);
168
+ else {
169
+ prompts_1.log.info(picocolors_1.default.dim("Skipping dependency installation (--no-install)."));
336
170
  }
337
- // ── Step: Initialize Convex (single run) ─────────────
338
171
  currentStep++;
339
172
  prompts_1.log.step(`Setting up Convex ${stepLabel()}`);
340
- try {
341
- await (0, run_1.run)("bunx", ["convex", "dev", "--once"], { cwd: projectDir });
173
+ const convexInitialized = await (0, steps_1.initializeConvex)(projectDir);
174
+ if (convexInitialized) {
342
175
  prompts_1.log.success("Convex initialized.");
343
176
  }
344
- catch {
345
- prompts_1.log.warn("Convex setup needs attention.");
346
- prompts_1.log.info(picocolors_1.default.dim(" Run manually: cd " + projectName + " && bunx convex dev --once"));
177
+ else {
178
+ prompts_1.log.info(picocolors_1.default.dim(`Run manually: cd ${projectName} && bunx convex dev --once`));
347
179
  }
348
- // ── Step 8: Ensure EXPO_PUBLIC_CONVEX_URL is set ───────
349
- // Convex CLI saves the URL as CONVEX_URL, but Expo needs EXPO_PUBLIC_CONVEX_URL.
350
- try {
351
- let updatedEnv = "";
352
- try {
353
- updatedEnv = await (0, fs_2.readTextFile)(envLocalPath);
354
- }
355
- catch {
356
- updatedEnv = "";
357
- }
358
- if (updatedEnv && !updatedEnv.includes("EXPO_PUBLIC_CONVEX_URL")) {
359
- const parsed = (0, dotenv_1.parseDotenv)(updatedEnv);
360
- let convexUrl = parsed["CONVEX_URL"] || "";
361
- if (!convexUrl) {
362
- // Try the convex url command as fallback
363
- try {
364
- convexUrl = await (0, run_1.runCapture)("bunx", ["convex", "url"], {
365
- cwd: projectDir,
366
- });
367
- }
368
- catch {
369
- // Non-critical
370
- }
371
- }
372
- if (convexUrl) {
373
- updatedEnv = (0, dotenv_1.upsertDotenvVar)(updatedEnv, "EXPO_PUBLIC_CONVEX_URL", convexUrl);
374
- await (0, fs_1.writeTextFile)(envLocalPath, updatedEnv);
375
- prompts_1.log.success(`Convex URL configured for Expo.`);
376
- }
377
- else {
378
- prompts_1.log.info(picocolors_1.default.dim(" Note: Add EXPO_PUBLIC_CONVEX_URL to .env.local with your Convex deployment URL."));
379
- }
380
- }
180
+ const convexUrlReady = await (0, steps_1.ensureExpoPublicConvexUrl)(projectDir);
181
+ if (convexUrlReady) {
182
+ prompts_1.log.success("Convex URL configured for Expo.");
381
183
  }
382
- catch {
383
- // Non-critical user can set manually
184
+ else {
185
+ prompts_1.log.info(picocolors_1.default.dim("Add EXPO_PUBLIC_CONVEX_URL to .env.local with your Convex deployment URL."));
384
186
  }
385
- // ── Step: Set Convex env var for Clerk ───────────────
386
- if (clerkDomain) {
187
+ if (clerk.domain) {
387
188
  currentStep++;
388
189
  prompts_1.log.step(`Setting Convex environment variable ${stepLabel()}`);
389
- try {
390
- await (0, run_1.run)("bunx", ["convex", "env", "set", "CLERK_JWT_ISSUER_DOMAIN", clerkDomain], { cwd: projectDir });
190
+ const setEnvOk = await (0, steps_1.setConvexClerkEnv)(projectDir, clerk.domain);
191
+ if (setEnvOk) {
391
192
  prompts_1.log.success("Convex environment variable set.");
392
193
  }
393
- catch {
394
- prompts_1.log.info(picocolors_1.default.dim(` Note: Run manually: bunx convex env set CLERK_JWT_ISSUER_DOMAIN ${clerkDomain}`));
194
+ else {
195
+ prompts_1.log.info(picocolors_1.default.dim(`Run manually: bunx convex env set CLERK_JWT_ISSUER_DOMAIN ${clerk.domain}`));
395
196
  }
396
197
  }
397
- // ── Step: Git init ───────────────────────────────────
398
- currentStep++;
399
- prompts_1.log.step(`Initializing git repository ${stepLabel()}`);
400
- try {
401
- await (0, run_1.run)("git", ["init"], { cwd: projectDir });
402
- await (0, run_1.run)("git", ["add", "-A"], { cwd: projectDir });
403
- await (0, run_1.run)("git", ["commit", "-m", "Initial commit from create-100x-mobile"], {
404
- cwd: projectDir,
405
- });
406
- prompts_1.log.success("Git repository initialized.");
407
- }
408
- catch {
409
- prompts_1.log.info(picocolors_1.default.dim(" Note: git is not available. You can initialize a repo later."));
410
- }
411
- // ── Step: Health check ──────────────────────────────
412
- currentStep++;
413
- prompts_1.log.step(`Running health check ${stepLabel()}`);
414
- const healthChecks = [];
415
- // Check .env.local exists
416
- const envExists = await (0, fs_1.pathExists)(envLocalPath);
417
- healthChecks.push({ label: ".env.local exists", ok: envExists });
418
- // Check EXPO_PUBLIC_CONVEX_URL is set
419
- if (envExists) {
420
- let envContent = "";
421
- try {
422
- envContent = await (0, fs_2.readTextFile)(envLocalPath);
423
- }
424
- catch {
425
- // ignore
426
- }
427
- healthChecks.push({
428
- label: "EXPO_PUBLIC_CONVEX_URL is set",
429
- ok: envContent.includes("EXPO_PUBLIC_CONVEX_URL"),
430
- });
431
- if (clerkKeyValue) {
432
- healthChecks.push({
433
- label: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY is set",
434
- ok: envContent.includes("EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY"),
435
- });
198
+ if (options.initializeGit) {
199
+ currentStep++;
200
+ prompts_1.log.step(`Initializing git repository ${stepLabel()}`);
201
+ const gitReady = await (0, steps_1.initializeGit)(projectDir);
202
+ if (gitReady) {
203
+ prompts_1.log.success("Git repository initialized.");
436
204
  }
437
205
  }
438
206
  else {
439
- healthChecks.push({ label: "EXPO_PUBLIC_CONVEX_URL is set", ok: false });
440
- if (clerkKeyValue) {
441
- healthChecks.push({
442
- label: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY is set",
443
- ok: false,
444
- });
445
- }
207
+ prompts_1.log.info(picocolors_1.default.dim("Skipping git initialization (--skip-git)."));
446
208
  }
209
+ currentStep++;
210
+ prompts_1.log.step(`Running health check ${stepLabel()}`);
211
+ const healthChecks = await (0, steps_1.runHealthChecks)(projectDir, clerk.publishableKey);
447
212
  for (const check of healthChecks) {
448
213
  if (check.ok) {
449
214
  prompts_1.log.info(` ${picocolors_1.default.green("✓")} ${check.label}`);
@@ -452,39 +217,41 @@ async function cmdNew(args) {
452
217
  prompts_1.log.info(` ${picocolors_1.default.yellow("⚠")} ${check.label}`);
453
218
  }
454
219
  }
455
- // ── Success message ───────────────────────────────
456
220
  prompts_1.log.info("");
457
- if (clerkKeyValue && clerkDomain) {
458
- // Clerk is configured — app is ready
221
+ if (clerk.publishableKey && clerk.domain) {
459
222
  prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green("Your app is ready!")));
460
223
  prompts_1.log.info("");
461
224
  prompts_1.log.info(" Next steps:");
462
225
  prompts_1.log.info(` ${picocolors_1.default.cyan("cd")} ${projectName}`);
463
- prompts_1.log.info(` ${picocolors_1.default.cyan("bunx expo start")}`);
464
- prompts_1.log.info("");
465
- if (!jwtCreated) {
226
+ if (options.installDependencies) {
227
+ prompts_1.log.info(` ${picocolors_1.default.cyan("bunx expo start")}`);
228
+ }
229
+ else {
230
+ prompts_1.log.info(` ${picocolors_1.default.cyan("bun install")} ${picocolors_1.default.dim("# or npm install")}`);
231
+ prompts_1.log.info(` ${picocolors_1.default.cyan("bunx expo start")}`);
232
+ }
233
+ if (!clerk.jwtCreated) {
466
234
  prompts_1.log.info(picocolors_1.default.dim(' Note: Ensure a JWT template named "convex" exists in your Clerk dashboard.'));
467
235
  }
468
- prompts_1.log.info(picocolors_1.default.dim(" Remember to enable Google and Apple OAuth providers in your Clerk dashboard."));
469
236
  }
470
237
  else {
471
- // No Clerk keys — manual setup needed
472
238
  prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green("Project scaffolded!")));
473
239
  prompts_1.log.info("");
474
240
  prompts_1.log.info(" To finish setup:");
475
- prompts_1.log.info("");
476
- prompts_1.log.info(` 1. Create a Clerk app at ${picocolors_1.default.cyan("https://dashboard.clerk.com")}`);
477
- prompts_1.log.info(` 2. Enable Google and Apple OAuth providers`);
478
- prompts_1.log.info(` 3. Add keys to ${picocolors_1.default.cyan(".env.local")}:`);
241
+ prompts_1.log.info(` 1. Add keys to ${picocolors_1.default.cyan(".env.local")}`);
479
242
  prompts_1.log.info(` ${picocolors_1.default.dim("EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...")}`);
480
243
  prompts_1.log.info(` ${picocolors_1.default.dim("CLERK_JWT_ISSUER_DOMAIN=https://your-app.clerk.accounts.dev")}`);
481
- prompts_1.log.info(` 4. Set the Convex env var:`);
482
- prompts_1.log.info(` ${picocolors_1.default.dim("bunx convex env set CLERK_JWT_ISSUER_DOMAIN <domain>")}`);
483
- prompts_1.log.info(` 5. Create a JWT template named ${picocolors_1.default.bold('"convex"')} in Clerk dashboard`);
484
- prompts_1.log.info("");
485
- prompts_1.log.info(" Then start your app:");
486
- prompts_1.log.info(` ${picocolors_1.default.cyan("cd")} ${projectName}`);
487
- prompts_1.log.info(` ${picocolors_1.default.cyan("bunx expo start")}`);
244
+ prompts_1.log.info(` 2. ${picocolors_1.default.dim("bunx convex env set CLERK_JWT_ISSUER_DOMAIN <domain>")}`);
245
+ prompts_1.log.info(` 3. Create a JWT template named ${picocolors_1.default.bold('"convex"')} in Clerk dashboard`);
488
246
  }
489
247
  (0, prompts_1.outro)(picocolors_1.default.dim("Happy building!"));
490
248
  }
249
+ async function runNewCommand(rawArgs) {
250
+ try {
251
+ await cmdNew(rawArgs);
252
+ }
253
+ catch (error) {
254
+ prompts_1.log.error((0, steps_1.toErrorMessage)(error));
255
+ process.exitCode = 1;
256
+ }
257
+ }
package/dist/lib/clerk.js CHANGED
@@ -60,6 +60,9 @@ function clerkApi(secretKey, method, path, body) {
60
60
  }
61
61
  });
62
62
  });
63
+ req.setTimeout(10000, () => {
64
+ req.destroy(new Error("Request timeout after 10s"));
65
+ });
63
66
  req.on("error", reject);
64
67
  if (data)
65
68
  req.write(data);
package/dist/lib/fs.js CHANGED
@@ -5,6 +5,7 @@ exports.ensureDir = ensureDir;
5
5
  exports.removeDir = removeDir;
6
6
  exports.readTextFile = readTextFile;
7
7
  exports.writeTextFile = writeTextFile;
8
+ exports.writeBinaryFile = writeBinaryFile;
8
9
  exports.writeTextFileIfChanged = writeTextFileIfChanged;
9
10
  const promises_1 = require("node:fs/promises");
10
11
  const node_path_1 = require("node:path");
@@ -30,6 +31,10 @@ async function writeTextFile(filePath, contents) {
30
31
  await ensureDir((0, node_path_1.dirname)(filePath));
31
32
  await (0, promises_1.writeFile)(filePath, contents);
32
33
  }
34
+ async function writeBinaryFile(filePath, contents) {
35
+ await ensureDir((0, node_path_1.dirname)(filePath));
36
+ await (0, promises_1.writeFile)(filePath, contents);
37
+ }
33
38
  async function writeTextFileIfChanged(filePath, contents) {
34
39
  const exists = await pathExists(filePath);
35
40
  if (exists) {
package/dist/lib/run.js CHANGED
@@ -29,12 +29,16 @@ async function runCapture(cmd, args, options = {}) {
29
29
  shell: process.platform === "win32",
30
30
  });
31
31
  let stdout = "";
32
+ let stderr = "";
32
33
  child.stdout?.on("data", (data) => (stdout += data));
34
+ child.stderr?.on("data", (data) => (stderr += data));
33
35
  child.on("error", reject);
34
36
  child.on("exit", (code) => {
35
37
  if (code === 0)
36
38
  return resolve(stdout.trim());
37
- reject(new Error(`${cmd} exited with code ${code}`));
39
+ const details = stderr.trim() || stdout.trim();
40
+ const suffix = details ? `: ${details}` : "";
41
+ reject(new Error(`${cmd} exited with code ${code}${suffix}`));
38
42
  });
39
43
  });
40
44
  }