create-100x-mobile 0.4.1 → 0.4.3

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 CHANGED
@@ -8,9 +8,18 @@ function printUsage() {
8
8
 
9
9
  Usage:
10
10
  bunx create-100x-mobile [name]
11
+ bunx create-100x-mobile [name] [options]
11
12
  bun create 100x-mobile [name]
12
13
  npx create-100x-mobile [name]
13
14
 
15
+ Options:
16
+ --yes Run without prompts (defaults name to "my-app")
17
+ --no-install Skip dependency installation
18
+ --skip-git Skip git init/commit
19
+ --sdk <value> Use "latest", "previous", or a specific version (e.g. 54.0.33)
20
+ --clerk-pk <key> Pre-fill Clerk publishable key
21
+ --clerk-domain <domain> Pre-fill Clerk issuer domain
22
+
14
23
  Scaffolds a full-stack mobile app with Expo + Convex + Clerk.
15
24
  `);
16
25
  }
@@ -26,15 +35,7 @@ async function main() {
26
35
  console.log(pkg.version ?? "0.0.0");
27
36
  return;
28
37
  }
29
- // Default action: scaffold a new app
30
- // If first arg looks like a flag, show help; otherwise treat as app name
31
- if (command && command.startsWith("-")) {
32
- prompts_1.log.error(`Unknown flag: ${command}`);
33
- printUsage();
34
- process.exitCode = 1;
35
- return;
36
- }
37
- await (0, new_1.cmdNew)(args);
38
+ await (0, new_1.runNewCommand)(args);
38
39
  }
39
40
  main().catch((err) => {
40
41
  const message = err instanceof Error ? err.message : String(err);
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseNewCommandArgs = parseNewCommandArgs;
4
+ function getInlineOrNextValue(arg, args, i) {
5
+ const eqIndex = arg.indexOf("=");
6
+ if (eqIndex !== -1) {
7
+ const value = arg.slice(eqIndex + 1).trim();
8
+ if (!value) {
9
+ throw new Error(`Flag "${arg.slice(0, eqIndex)}" requires a value.`);
10
+ }
11
+ return { value, nextIndex: i };
12
+ }
13
+ const nextValue = args[i + 1];
14
+ if (!nextValue || nextValue.startsWith("-")) {
15
+ throw new Error(`Flag "${arg}" requires a value.`);
16
+ }
17
+ return { value: nextValue.trim(), nextIndex: i + 1 };
18
+ }
19
+ function parseNewCommandArgs(args) {
20
+ const options = {
21
+ interactive: true,
22
+ installDependencies: true,
23
+ initializeGit: true,
24
+ sdk: null,
25
+ clerkPublishableKey: null,
26
+ clerkDomain: null,
27
+ };
28
+ let projectName = null;
29
+ for (let i = 0; i < args.length; i++) {
30
+ const arg = args[i];
31
+ if (!arg)
32
+ continue;
33
+ if (!arg.startsWith("-")) {
34
+ if (projectName) {
35
+ throw new Error(`Unexpected argument "${arg}". Only one project name is allowed.`);
36
+ }
37
+ projectName = arg;
38
+ continue;
39
+ }
40
+ if (arg === "--yes") {
41
+ options.interactive = false;
42
+ continue;
43
+ }
44
+ if (arg === "--no-install") {
45
+ options.installDependencies = false;
46
+ continue;
47
+ }
48
+ if (arg === "--skip-git") {
49
+ options.initializeGit = false;
50
+ continue;
51
+ }
52
+ if (arg === "--sdk" || arg.startsWith("--sdk=")) {
53
+ const { value, nextIndex } = getInlineOrNextValue(arg, args, i);
54
+ options.sdk = value;
55
+ i = nextIndex;
56
+ continue;
57
+ }
58
+ if (arg === "--clerk-pk" || arg.startsWith("--clerk-pk=")) {
59
+ const { value, nextIndex } = getInlineOrNextValue(arg, args, i);
60
+ options.clerkPublishableKey = value;
61
+ i = nextIndex;
62
+ continue;
63
+ }
64
+ if (arg === "--clerk-domain" || arg.startsWith("--clerk-domain=")) {
65
+ const { value, nextIndex } = getInlineOrNextValue(arg, args, i);
66
+ options.clerkDomain = value;
67
+ i = nextIndex;
68
+ continue;
69
+ }
70
+ throw new Error(`Unknown flag: ${arg}`);
71
+ }
72
+ if (!projectName && !options.interactive) {
73
+ projectName = "my-app";
74
+ }
75
+ return { projectName, options };
76
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createProjectDirectories = createProjectDirectories;
4
+ exports.writeScaffoldFiles = writeScaffoldFiles;
5
+ exports.writeDefaultAssetFiles = writeDefaultAssetFiles;
6
+ const node_path_1 = require("node:path");
7
+ const fs_1 = require("../../lib/fs");
8
+ // 1x1 transparent PNG placeholder.
9
+ const TRANSPARENT_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO7+P5kAAAAASUVORK5CYII=";
10
+ async function createProjectDirectories(projectDir) {
11
+ const dirs = [
12
+ "app/(auth)",
13
+ "app/(tabs)",
14
+ "app/providers",
15
+ "components",
16
+ "convex",
17
+ "hooks",
18
+ "assets/images",
19
+ ];
20
+ for (const dir of dirs) {
21
+ await (0, fs_1.ensureDir)((0, node_path_1.join)(projectDir, dir));
22
+ }
23
+ }
24
+ async function writeScaffoldFiles(projectDir, files) {
25
+ for (const file of files) {
26
+ await (0, fs_1.writeTextFile)((0, node_path_1.join)(projectDir, file.path), file.content);
27
+ }
28
+ }
29
+ async function writeDefaultAssetFiles(projectDir) {
30
+ const png = Buffer.from(TRANSPARENT_PNG_BASE64, "base64");
31
+ await (0, fs_1.writeBinaryFile)((0, node_path_1.join)(projectDir, "assets/images/icon.png"), png);
32
+ await (0, fs_1.writeBinaryFile)((0, node_path_1.join)(projectDir, "assets/images/favicon.png"), png);
33
+ }
@@ -0,0 +1,379 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.normalizeClerkDomain = normalizeClerkDomain;
7
+ exports.toErrorMessage = toErrorMessage;
8
+ exports.resolveExpoVersion = resolveExpoVersion;
9
+ exports.installDependencies = installDependencies;
10
+ exports.resolveExpoDependencies = resolveExpoDependencies;
11
+ exports.promptClerkSetup = promptClerkSetup;
12
+ exports.writeAuthEnvironment = writeAuthEnvironment;
13
+ exports.initializeConvex = initializeConvex;
14
+ exports.ensureExpoPublicConvexUrl = ensureExpoPublicConvexUrl;
15
+ exports.setConvexClerkEnv = setConvexClerkEnv;
16
+ exports.initializeGit = initializeGit;
17
+ exports.runHealthChecks = runHealthChecks;
18
+ const node_path_1 = require("node:path");
19
+ const prompts_1 = require("@clack/prompts");
20
+ const picocolors_1 = __importDefault(require("picocolors"));
21
+ const clerk_1 = require("../../lib/clerk");
22
+ const dotenv_1 = require("../../lib/dotenv");
23
+ const fs_1 = require("../../lib/fs");
24
+ const run_1 = require("../../lib/run");
25
+ function normalizeClerkDomain(rawDomain) {
26
+ const trimmed = rawDomain.trim();
27
+ if (!trimmed)
28
+ return "";
29
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
30
+ return trimmed;
31
+ }
32
+ return `https://${trimmed}`;
33
+ }
34
+ function toErrorMessage(error) {
35
+ if (error instanceof Error)
36
+ return error.message;
37
+ return String(error);
38
+ }
39
+ function isErrnoException(error) {
40
+ return error instanceof Error && "code" in error;
41
+ }
42
+ function normalizeSdkArg(sdk) {
43
+ const value = sdk.trim();
44
+ if (!value) {
45
+ throw new Error("`--sdk` cannot be empty.");
46
+ }
47
+ if (value === "latest") {
48
+ return "latest";
49
+ }
50
+ if (value === "previous") {
51
+ return "previous";
52
+ }
53
+ if (/^\d+\.\d+\.\d+$/.test(value)) {
54
+ return `~${value}`;
55
+ }
56
+ if (/^~\d+\.\d+\.\d+$/.test(value)) {
57
+ return value;
58
+ }
59
+ throw new Error(`Invalid --sdk value "${value}". Use "latest", "previous", or a version like "54.0.33".`);
60
+ }
61
+ async function resolveExpoVersion(options) {
62
+ if (options.sdk) {
63
+ const normalized = normalizeSdkArg(options.sdk);
64
+ if (normalized !== "previous") {
65
+ return normalized;
66
+ }
67
+ }
68
+ const sdkSpinner = (0, prompts_1.spinner)();
69
+ const shouldPrompt = options.interactive && !options.sdk;
70
+ sdkSpinner.start("Fetching available Expo SDK versions");
71
+ try {
72
+ const latestVersion = (await (0, run_1.runCapture)("npm", ["view", "expo", "version"])).trim();
73
+ const latestMajor = parseInt(latestVersion.split(".")[0], 10);
74
+ if (Number.isNaN(latestMajor)) {
75
+ sdkSpinner.stop("Could not parse Expo version, using latest.");
76
+ return "latest";
77
+ }
78
+ const prevMajor = latestMajor - 1;
79
+ const prevJson = await (0, run_1.runCapture)("npm", [
80
+ "view",
81
+ `expo@^${prevMajor}.0.0`,
82
+ "version",
83
+ "--json",
84
+ ]);
85
+ const prevParsed = JSON.parse(prevJson);
86
+ const prevVersion = Array.isArray(prevParsed)
87
+ ? prevParsed[prevParsed.length - 1]
88
+ : prevParsed;
89
+ if (!prevVersion) {
90
+ sdkSpinner.stop("Could not resolve previous Expo SDK version.");
91
+ return `~${latestVersion}`;
92
+ }
93
+ if (options.sdk && normalizeSdkArg(options.sdk) === "previous") {
94
+ sdkSpinner.stop("Using previous Expo SDK.");
95
+ return `~${prevVersion}`;
96
+ }
97
+ if (!shouldPrompt) {
98
+ sdkSpinner.stop("Using latest Expo SDK.");
99
+ return `~${latestVersion}`;
100
+ }
101
+ sdkSpinner.stop("Expo SDK versions fetched.");
102
+ const sdkChoice = await (0, prompts_1.select)({
103
+ message: "Which Expo SDK version?",
104
+ options: [
105
+ {
106
+ value: latestVersion,
107
+ label: `SDK ${latestMajor} (latest)`,
108
+ hint: `expo ~${latestVersion}`,
109
+ },
110
+ {
111
+ value: prevVersion,
112
+ label: `SDK ${prevMajor}`,
113
+ hint: `expo ~${prevVersion}`,
114
+ },
115
+ ],
116
+ });
117
+ if ((0, prompts_1.isCancel)(sdkChoice)) {
118
+ return `~${latestVersion}`;
119
+ }
120
+ return `~${sdkChoice}`;
121
+ }
122
+ catch (error) {
123
+ sdkSpinner.stop("Could not fetch Expo versions, using latest.");
124
+ prompts_1.log.warn(picocolors_1.default.dim(` Details: ${toErrorMessage(error)}`));
125
+ return "latest";
126
+ }
127
+ }
128
+ async function installDependencies(projectDir) {
129
+ try {
130
+ await (0, run_1.run)("bun", ["install"], { cwd: projectDir });
131
+ return true;
132
+ }
133
+ catch (bunError) {
134
+ prompts_1.log.warn(`bun install failed: ${toErrorMessage(bunError)}`);
135
+ prompts_1.log.warn("Trying npm install...");
136
+ try {
137
+ await (0, run_1.run)("npm", ["install"], { cwd: projectDir });
138
+ return true;
139
+ }
140
+ catch (npmError) {
141
+ prompts_1.log.error(`Failed to install dependencies. ${toErrorMessage(npmError)}`);
142
+ prompts_1.log.info(picocolors_1.default.dim("Run `bun install` or `npm install` manually."));
143
+ return false;
144
+ }
145
+ }
146
+ }
147
+ async function resolveExpoDependencies(projectDir) {
148
+ const expoPackages = [
149
+ "react",
150
+ "react-dom",
151
+ "react-native",
152
+ "expo-router",
153
+ "@expo/vector-icons",
154
+ "expo-auth-session",
155
+ "expo-blur",
156
+ "expo-constants",
157
+ "expo-font",
158
+ "expo-haptics",
159
+ "expo-linking",
160
+ "expo-secure-store",
161
+ "expo-splash-screen",
162
+ "expo-status-bar",
163
+ "expo-system-ui",
164
+ "expo-web-browser",
165
+ "react-native-gesture-handler",
166
+ "react-native-reanimated",
167
+ "react-native-safe-area-context",
168
+ "react-native-screens",
169
+ "react-native-svg",
170
+ "react-native-web",
171
+ ];
172
+ try {
173
+ await (0, run_1.run)("npx", ["expo", "install", ...expoPackages], { cwd: projectDir });
174
+ return true;
175
+ }
176
+ catch (error) {
177
+ prompts_1.log.warn(`Could not resolve Expo deps automatically: ${toErrorMessage(error)}`);
178
+ prompts_1.log.info(picocolors_1.default.dim("Run manually: npx expo install --fix"));
179
+ return false;
180
+ }
181
+ }
182
+ async function promptClerkSetup(options) {
183
+ let publishableKey = options.clerkPublishableKey?.trim() ?? "";
184
+ let domain = options.clerkDomain ? normalizeClerkDomain(options.clerkDomain) : "";
185
+ let jwtCreated = false;
186
+ if (!publishableKey && options.interactive) {
187
+ prompts_1.log.info("");
188
+ prompts_1.log.info(picocolors_1.default.bold("Clerk Authentication Setup"));
189
+ prompts_1.log.info(picocolors_1.default.dim("Get your keys from https://dashboard.clerk.com → Your App → API Keys"));
190
+ prompts_1.log.info(picocolors_1.default.dim("Press Enter to skip and configure later."));
191
+ const clerkKey = await (0, prompts_1.text)({
192
+ message: "Clerk Publishable Key",
193
+ placeholder: "pk_test_...",
194
+ defaultValue: "",
195
+ });
196
+ if (!(0, prompts_1.isCancel)(clerkKey) && clerkKey) {
197
+ publishableKey = clerkKey.trim();
198
+ }
199
+ }
200
+ if (publishableKey && !domain) {
201
+ const derivedDomain = (0, clerk_1.extractDomainFromPublishableKey)(publishableKey);
202
+ if (derivedDomain) {
203
+ domain = derivedDomain;
204
+ prompts_1.log.info(` Detected issuer domain: ${picocolors_1.default.cyan(domain)}`);
205
+ }
206
+ else if (options.interactive) {
207
+ const domainInput = await (0, prompts_1.text)({
208
+ message: "Clerk JWT Issuer Domain",
209
+ placeholder: "https://your-app.clerk.accounts.dev",
210
+ defaultValue: "",
211
+ });
212
+ if (!(0, prompts_1.isCancel)(domainInput) && domainInput) {
213
+ domain = normalizeClerkDomain(domainInput);
214
+ }
215
+ }
216
+ }
217
+ if (publishableKey && options.interactive) {
218
+ prompts_1.log.info("");
219
+ prompts_1.log.info(picocolors_1.default.dim('Provide your Clerk Secret Key to auto-create the "convex" JWT template.'));
220
+ prompts_1.log.info(picocolors_1.default.dim("Press Enter to skip if you already have one."));
221
+ const secretInput = await (0, prompts_1.text)({
222
+ message: "Clerk Secret Key",
223
+ placeholder: "sk_test_...",
224
+ defaultValue: "",
225
+ });
226
+ const secretKey = (0, prompts_1.isCancel)(secretInput) || !secretInput ? "" : secretInput.trim();
227
+ if (secretKey) {
228
+ const jwtSpinner = (0, prompts_1.spinner)();
229
+ jwtSpinner.start('Creating "convex" JWT template in Clerk');
230
+ const result = await (0, clerk_1.createConvexJwtTemplate)(secretKey);
231
+ if (result.success) {
232
+ jwtSpinner.stop('JWT template "convex" created in Clerk.');
233
+ jwtCreated = true;
234
+ }
235
+ else {
236
+ jwtSpinner.stop("Could not create JWT template.");
237
+ prompts_1.log.warn(`Clerk API: ${result.error}`);
238
+ }
239
+ }
240
+ }
241
+ return { publishableKey, domain, jwtCreated };
242
+ }
243
+ async function writeAuthEnvironment(projectDir, clerk) {
244
+ const envLocalPath = (0, node_path_1.join)(projectDir, ".env.local");
245
+ let envContents = "";
246
+ try {
247
+ envContents = await (0, fs_1.readTextFile)(envLocalPath);
248
+ }
249
+ catch (error) {
250
+ if (isErrnoException(error) && error.code !== "ENOENT") {
251
+ prompts_1.log.warn(`Could not read .env.local: ${toErrorMessage(error)}`);
252
+ }
253
+ }
254
+ if (clerk.publishableKey) {
255
+ envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY", clerk.publishableKey);
256
+ }
257
+ if (clerk.domain) {
258
+ envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "CLERK_JWT_ISSUER_DOMAIN", clerk.domain);
259
+ }
260
+ if (envContents) {
261
+ await (0, fs_1.writeTextFile)(envLocalPath, envContents);
262
+ }
263
+ if (clerk.domain) {
264
+ const serializedDomain = JSON.stringify(clerk.domain);
265
+ 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
+ }
267
+ }
268
+ async function initializeConvex(projectDir) {
269
+ try {
270
+ await (0, run_1.run)("bunx", ["convex", "dev", "--once"], { cwd: projectDir });
271
+ return true;
272
+ }
273
+ catch (error) {
274
+ prompts_1.log.warn(`Convex setup needs attention: ${toErrorMessage(error)}`);
275
+ return false;
276
+ }
277
+ }
278
+ async function ensureExpoPublicConvexUrl(projectDir) {
279
+ const envLocalPath = (0, node_path_1.join)(projectDir, ".env.local");
280
+ try {
281
+ let updatedEnv = "";
282
+ try {
283
+ updatedEnv = await (0, fs_1.readTextFile)(envLocalPath);
284
+ }
285
+ catch (error) {
286
+ if (isErrnoException(error) && error.code !== "ENOENT") {
287
+ prompts_1.log.warn(`Could not read .env.local: ${toErrorMessage(error)}`);
288
+ }
289
+ }
290
+ if (!updatedEnv) {
291
+ return false;
292
+ }
293
+ if (updatedEnv.includes("EXPO_PUBLIC_CONVEX_URL")) {
294
+ return true;
295
+ }
296
+ const parsed = (0, dotenv_1.parseDotenv)(updatedEnv);
297
+ let convexUrl = parsed["CONVEX_URL"] || "";
298
+ if (!convexUrl) {
299
+ try {
300
+ convexUrl = await (0, run_1.runCapture)("bunx", ["convex", "url"], {
301
+ cwd: projectDir,
302
+ });
303
+ }
304
+ catch (error) {
305
+ prompts_1.log.warn(picocolors_1.default.dim(`Could not auto-detect Convex URL: ${toErrorMessage(error)}`));
306
+ }
307
+ }
308
+ if (!convexUrl) {
309
+ return false;
310
+ }
311
+ updatedEnv = (0, dotenv_1.upsertDotenvVar)(updatedEnv, "EXPO_PUBLIC_CONVEX_URL", convexUrl);
312
+ await (0, fs_1.writeTextFile)(envLocalPath, updatedEnv);
313
+ return true;
314
+ }
315
+ catch (error) {
316
+ prompts_1.log.warn(`Could not finalize EXPO_PUBLIC_CONVEX_URL: ${toErrorMessage(error)}`);
317
+ return false;
318
+ }
319
+ }
320
+ async function setConvexClerkEnv(projectDir, clerkDomain) {
321
+ if (!clerkDomain)
322
+ return true;
323
+ try {
324
+ await (0, run_1.run)("bunx", ["convex", "env", "set", "CLERK_JWT_ISSUER_DOMAIN", clerkDomain], { cwd: projectDir });
325
+ return true;
326
+ }
327
+ catch (error) {
328
+ prompts_1.log.warn(`Could not set Convex environment variable automatically: ${toErrorMessage(error)}`);
329
+ return false;
330
+ }
331
+ }
332
+ async function initializeGit(projectDir) {
333
+ try {
334
+ await (0, run_1.run)("git", ["init"], { cwd: projectDir });
335
+ await (0, run_1.run)("git", ["add", "-A"], { cwd: projectDir });
336
+ await (0, run_1.run)("git", ["commit", "-m", "Initial commit from create-100x-mobile"], {
337
+ cwd: projectDir,
338
+ });
339
+ return true;
340
+ }
341
+ catch (error) {
342
+ prompts_1.log.warn(`Git initialization skipped: ${toErrorMessage(error)}`);
343
+ return false;
344
+ }
345
+ }
346
+ async function runHealthChecks(projectDir, clerkPublishableKey) {
347
+ const envLocalPath = (0, node_path_1.join)(projectDir, ".env.local");
348
+ const checks = [];
349
+ const envExists = await (0, fs_1.pathExists)(envLocalPath);
350
+ checks.push({ label: ".env.local exists", ok: envExists });
351
+ if (!envExists) {
352
+ checks.push({ label: "EXPO_PUBLIC_CONVEX_URL is set", ok: false });
353
+ if (clerkPublishableKey) {
354
+ checks.push({
355
+ label: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY is set",
356
+ ok: false,
357
+ });
358
+ }
359
+ return checks;
360
+ }
361
+ let envContent = "";
362
+ try {
363
+ envContent = await (0, fs_1.readTextFile)(envLocalPath);
364
+ }
365
+ catch (error) {
366
+ prompts_1.log.warn(`Could not read .env.local during health checks: ${toErrorMessage(error)}`);
367
+ }
368
+ checks.push({
369
+ label: "EXPO_PUBLIC_CONVEX_URL is set",
370
+ ok: envContent.includes("EXPO_PUBLIC_CONVEX_URL"),
371
+ });
372
+ if (clerkPublishableKey) {
373
+ checks.push({
374
+ label: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY is set",
375
+ ok: envContent.includes("EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY"),
376
+ });
377
+ }
378
+ return checks;
379
+ }