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 +10 -9
- package/dist/commands/new/args.js +76 -0
- package/dist/commands/new/scaffold.js +33 -0
- package/dist/commands/new/steps.js +379 -0
- package/dist/commands/new.js +136 -364
- package/dist/lib/clerk.js +3 -0
- package/dist/lib/fs.js +5 -0
- package/dist/lib/run.js +5 -1
- package/dist/templates/app/authLayout.js +1 -0
- package/dist/templates/app/settingsScreen.js +5 -2
- package/dist/templates/app/signIn.js +40 -24
- package/dist/templates/hooks/useFrameworkReady.js +4 -2
- package/package.json +7 -3
- package/dist/commands/CLAUDE.md +0 -7
- package/dist/templates/config/CLAUDE.md +0 -7
package/dist/commands/new.js
CHANGED
|
@@ -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,26 @@ 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");
|
|
37
|
+
const signUp_1 = require("../templates/app/signUp");
|
|
36
38
|
const tabsLayout_1 = require("../templates/app/tabsLayout");
|
|
37
39
|
const todosScreen_1 = require("../templates/app/todosScreen");
|
|
38
|
-
const settingsScreen_1 = require("../templates/app/settingsScreen");
|
|
39
40
|
// Component templates
|
|
40
41
|
const addTodoForm_1 = require("../templates/components/addTodoForm");
|
|
41
|
-
const todoItem_1 = require("../templates/components/todoItem");
|
|
42
|
-
const filterTabs_1 = require("../templates/components/filterTabs");
|
|
43
42
|
const emptyState_1 = require("../templates/components/emptyState");
|
|
43
|
+
const filterTabs_1 = require("../templates/components/filterTabs");
|
|
44
|
+
const todoItem_1 = require("../templates/components/todoItem");
|
|
44
45
|
// Hook templates
|
|
45
46
|
const useFrameworkReady_1 = require("../templates/hooks/useFrameworkReady");
|
|
46
|
-
|
|
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 ───────────────────
|
|
47
|
+
function buildTemplateFiles(projectName, expoVersion) {
|
|
162
48
|
const files = [
|
|
163
49
|
// Config
|
|
164
50
|
["package.json", (0, packageJson_1.packageJsonTemplate)(projectName, expoVersion)],
|
|
@@ -183,6 +69,7 @@ async function cmdNew(args) {
|
|
|
183
69
|
["app/providers/AuthProvider.tsx", (0, authProvider_1.authProviderTemplate)()],
|
|
184
70
|
["app/(auth)/_layout.tsx", (0, authLayout_1.authLayoutTemplate)()],
|
|
185
71
|
["app/(auth)/sign-in.tsx", (0, signIn_1.signInTemplate)()],
|
|
72
|
+
["app/(auth)/sign-up.tsx", (0, signUp_1.signUpTemplate)()],
|
|
186
73
|
["app/(tabs)/_layout.tsx", (0, tabsLayout_1.tabsLayoutTemplate)()],
|
|
187
74
|
["app/(tabs)/index.tsx", (0, todosScreen_1.todosScreenTemplate)()],
|
|
188
75
|
["app/(tabs)/settings.tsx", (0, settingsScreen_1.settingsScreenTemplate)()],
|
|
@@ -194,253 +81,136 @@ async function cmdNew(args) {
|
|
|
194
81
|
// Hooks
|
|
195
82
|
["hooks/useFrameworkReady.ts", (0, useFrameworkReady_1.useFrameworkReadyTemplate)()],
|
|
196
83
|
];
|
|
197
|
-
|
|
198
|
-
|
|
84
|
+
return files.map(([path, content]) => ({ path, content }));
|
|
85
|
+
}
|
|
86
|
+
async function cmdNew(rawArgs) {
|
|
87
|
+
(0, prompts_1.intro)(picocolors_1.default.bold(picocolors_1.default.cyan("create-100x-mobile")));
|
|
88
|
+
const { options, projectName: projectNameArg } = (0, args_1.parseNewCommandArgs)(rawArgs);
|
|
89
|
+
let projectName = projectNameArg;
|
|
90
|
+
if (!projectName && options.interactive) {
|
|
91
|
+
const nameInput = await (0, prompts_1.text)({
|
|
92
|
+
message: "What is your project name?",
|
|
93
|
+
placeholder: "my-app",
|
|
94
|
+
defaultValue: "my-app",
|
|
95
|
+
});
|
|
96
|
+
if ((0, prompts_1.isCancel)(nameInput) || !nameInput) {
|
|
97
|
+
(0, prompts_1.cancel)("Operation cancelled.");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
projectName = nameInput;
|
|
199
101
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
currentStep++;
|
|
203
|
-
prompts_1.log.step(`Installing dependencies ${stepLabel()}`);
|
|
204
|
-
try {
|
|
205
|
-
await (0, run_1.run)("bun", ["install"], { cwd: projectDir });
|
|
102
|
+
if (!projectName) {
|
|
103
|
+
throw new Error("Project name is required in non-interactive mode.");
|
|
206
104
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
catch {
|
|
213
|
-
prompts_1.log.error("Failed to install dependencies. Run `bun install` or `npm install` manually.");
|
|
214
|
-
}
|
|
105
|
+
const sanitizedName = (0, projectName_1.sanitizeProjectName)(projectName);
|
|
106
|
+
if (sanitizedName !== projectName) {
|
|
107
|
+
prompts_1.log.warn(`Project name sanitized: "${projectName}" → "${sanitizedName}"`);
|
|
108
|
+
projectName = sanitizedName;
|
|
215
109
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
"
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
"expo-system-ui",
|
|
233
|
-
"expo-web-browser",
|
|
234
|
-
"react-native-gesture-handler",
|
|
235
|
-
"react-native-reanimated",
|
|
236
|
-
"react-native-safe-area-context",
|
|
237
|
-
"react-native-screens",
|
|
238
|
-
"react-native-svg",
|
|
239
|
-
"react-native-web",
|
|
240
|
-
];
|
|
241
|
-
try {
|
|
242
|
-
await (0, run_1.run)("npx", ["expo", "install", ...expoPackages], {
|
|
243
|
-
cwd: projectDir,
|
|
110
|
+
if (!projectName) {
|
|
111
|
+
throw new Error("Invalid project name.");
|
|
112
|
+
}
|
|
113
|
+
const projectDir = (0, node_path_1.resolve)(process.cwd(), projectName);
|
|
114
|
+
if (await (0, fs_1.pathExists)(projectDir)) {
|
|
115
|
+
if (!options.interactive) {
|
|
116
|
+
throw new Error(`Directory "${projectName}" already exists. Remove it or choose another name.`);
|
|
117
|
+
}
|
|
118
|
+
prompts_1.log.warn(`Directory "${projectName}" already exists.`);
|
|
119
|
+
const choice = await (0, prompts_1.select)({
|
|
120
|
+
message: "What would you like to do?",
|
|
121
|
+
options: [
|
|
122
|
+
{ value: "overwrite", label: "Overwrite the existing directory" },
|
|
123
|
+
{ value: "cancel", label: "Cancel" },
|
|
124
|
+
],
|
|
125
|
+
initialValue: "cancel",
|
|
244
126
|
});
|
|
245
|
-
prompts_1.
|
|
127
|
+
if ((0, prompts_1.isCancel)(choice) || choice === "cancel") {
|
|
128
|
+
(0, prompts_1.cancel)("Operation cancelled.");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
await (0, fs_1.removeDir)(projectDir);
|
|
246
132
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
prompts_1.log.info(picocolors_1.default.dim(" Run manually: npx expo install --fix"));
|
|
133
|
+
if (options.clerkDomain) {
|
|
134
|
+
options.clerkDomain = (0, steps_1.normalizeClerkDomain)(options.clerkDomain);
|
|
250
135
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
136
|
+
const expoVersion = await (0, steps_1.resolveExpoVersion)(options);
|
|
137
|
+
const clerk = await (0, steps_1.promptClerkSetup)(options);
|
|
138
|
+
const totalSteps = 2 + // project generation + convex init
|
|
139
|
+
(options.installDependencies ? 2 : 0) +
|
|
140
|
+
(clerk.domain ? 1 : 0) +
|
|
141
|
+
(options.initializeGit ? 1 : 0) +
|
|
142
|
+
1; // health check
|
|
143
|
+
let currentStep = 0;
|
|
144
|
+
const stepLabel = () => `(${currentStep}/${totalSteps})`;
|
|
145
|
+
const structureSpinner = (0, prompts_1.spinner)();
|
|
146
|
+
currentStep++;
|
|
147
|
+
structureSpinner.start(`Creating project structure ${stepLabel()}`);
|
|
148
|
+
await (0, scaffold_1.createProjectDirectories)(projectDir);
|
|
149
|
+
await (0, scaffold_1.writeScaffoldFiles)(projectDir, buildTemplateFiles(projectName, expoVersion));
|
|
150
|
+
await (0, scaffold_1.writeDefaultAssetFiles)(projectDir);
|
|
151
|
+
await (0, steps_1.writeAuthEnvironment)(projectDir, clerk);
|
|
152
|
+
structureSpinner.stop("Project files created.");
|
|
153
|
+
if (options.installDependencies) {
|
|
154
|
+
currentStep++;
|
|
155
|
+
prompts_1.log.step(`Installing dependencies ${stepLabel()}`);
|
|
156
|
+
const installed = await (0, steps_1.installDependencies)(projectDir);
|
|
157
|
+
if (installed) {
|
|
158
|
+
prompts_1.log.success("Dependencies installed.");
|
|
271
159
|
}
|
|
272
160
|
else {
|
|
273
|
-
|
|
274
|
-
message: "Clerk JWT Issuer Domain",
|
|
275
|
-
placeholder: "https://your-app.clerk.accounts.dev",
|
|
276
|
-
defaultValue: "",
|
|
277
|
-
});
|
|
278
|
-
clerkDomain =
|
|
279
|
-
(0, prompts_1.isCancel)(domainInput) || !domainInput ? "" : domainInput.trim();
|
|
161
|
+
prompts_1.log.warn("Dependencies were not installed automatically.");
|
|
280
162
|
}
|
|
281
|
-
|
|
282
|
-
prompts_1.log.
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
message: "Clerk Secret Key",
|
|
287
|
-
placeholder: "sk_test_...",
|
|
288
|
-
defaultValue: "",
|
|
289
|
-
});
|
|
290
|
-
const clerkSecretKey = (0, prompts_1.isCancel)(secretInput) || !secretInput ? "" : secretInput.trim();
|
|
291
|
-
if (clerkSecretKey) {
|
|
292
|
-
const jwtSpinner = (0, prompts_1.spinner)();
|
|
293
|
-
jwtSpinner.start('Creating "convex" JWT template in Clerk');
|
|
294
|
-
const result = await (0, clerk_1.createConvexJwtTemplate)(clerkSecretKey);
|
|
295
|
-
if (result.success) {
|
|
296
|
-
jwtSpinner.stop('JWT template "convex" created in Clerk.');
|
|
297
|
-
jwtCreated = true;
|
|
298
|
-
}
|
|
299
|
-
else {
|
|
300
|
-
jwtSpinner.stop("Could not create JWT template.");
|
|
301
|
-
prompts_1.log.warn(`Clerk API: ${result.error}`);
|
|
302
|
-
prompts_1.log.info(picocolors_1.default.dim('You may need to create a JWT template named "convex" in your Clerk dashboard.'));
|
|
303
|
-
}
|
|
163
|
+
currentStep++;
|
|
164
|
+
prompts_1.log.step(`Installing Expo dependencies ${stepLabel()}`);
|
|
165
|
+
const resolved = await (0, steps_1.resolveExpoDependencies)(projectDir);
|
|
166
|
+
if (resolved) {
|
|
167
|
+
prompts_1.log.success("Expo dependencies resolved.");
|
|
304
168
|
}
|
|
305
169
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
totalSteps = 7; // adds convex env step
|
|
309
|
-
}
|
|
310
|
-
// ── Write Clerk env vars to .env.local ─────────
|
|
311
|
-
const envLocalPath = (0, node_path_1.join)(projectDir, ".env.local");
|
|
312
|
-
let envContents = "";
|
|
313
|
-
try {
|
|
314
|
-
envContents = await (0, fs_2.readTextFile)(envLocalPath);
|
|
315
|
-
}
|
|
316
|
-
catch {
|
|
317
|
-
envContents = "";
|
|
318
|
-
}
|
|
319
|
-
if (clerkKeyValue) {
|
|
320
|
-
envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY", clerkKeyValue);
|
|
321
|
-
}
|
|
322
|
-
if (clerkDomain) {
|
|
323
|
-
envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "CLERK_JWT_ISSUER_DOMAIN", clerkDomain);
|
|
324
|
-
}
|
|
325
|
-
if (envContents) {
|
|
326
|
-
await (0, fs_1.writeTextFile)(envLocalPath, envContents);
|
|
327
|
-
}
|
|
328
|
-
// ── Step 6b: Hardcode domain in auth.config.ts ─────────
|
|
329
|
-
// Convex auth.config.ts is evaluated at deploy time, so process.env
|
|
330
|
-
// won't have the value yet (we set it after deploy). Hardcode it.
|
|
331
|
-
if (clerkDomain) {
|
|
332
|
-
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`);
|
|
170
|
+
else {
|
|
171
|
+
prompts_1.log.info(picocolors_1.default.dim("Skipping dependency installation (--no-install)."));
|
|
333
172
|
}
|
|
334
|
-
// ── Step: Initialize Convex (single run) ─────────────
|
|
335
173
|
currentStep++;
|
|
336
174
|
prompts_1.log.step(`Setting up Convex ${stepLabel()}`);
|
|
337
|
-
|
|
338
|
-
|
|
175
|
+
const convexInitialized = await (0, steps_1.initializeConvex)(projectDir);
|
|
176
|
+
if (convexInitialized) {
|
|
339
177
|
prompts_1.log.success("Convex initialized.");
|
|
340
178
|
}
|
|
341
|
-
|
|
342
|
-
prompts_1.log.
|
|
343
|
-
prompts_1.log.info(picocolors_1.default.dim(" Run manually: cd " + projectName + " && bunx convex dev --once"));
|
|
179
|
+
else {
|
|
180
|
+
prompts_1.log.info(picocolors_1.default.dim(`Run manually: cd ${projectName} && bunx convex dev --once`));
|
|
344
181
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
let updatedEnv = "";
|
|
349
|
-
try {
|
|
350
|
-
updatedEnv = await (0, fs_2.readTextFile)(envLocalPath);
|
|
351
|
-
}
|
|
352
|
-
catch {
|
|
353
|
-
updatedEnv = "";
|
|
354
|
-
}
|
|
355
|
-
if (updatedEnv && !updatedEnv.includes("EXPO_PUBLIC_CONVEX_URL")) {
|
|
356
|
-
const parsed = (0, dotenv_1.parseDotenv)(updatedEnv);
|
|
357
|
-
let convexUrl = parsed["CONVEX_URL"] || "";
|
|
358
|
-
if (!convexUrl) {
|
|
359
|
-
// Try the convex url command as fallback
|
|
360
|
-
try {
|
|
361
|
-
convexUrl = await (0, run_1.runCapture)("bunx", ["convex", "url"], {
|
|
362
|
-
cwd: projectDir,
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
catch {
|
|
366
|
-
// Non-critical
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
if (convexUrl) {
|
|
370
|
-
updatedEnv = (0, dotenv_1.upsertDotenvVar)(updatedEnv, "EXPO_PUBLIC_CONVEX_URL", convexUrl);
|
|
371
|
-
await (0, fs_1.writeTextFile)(envLocalPath, updatedEnv);
|
|
372
|
-
prompts_1.log.success(`Convex URL configured for Expo.`);
|
|
373
|
-
}
|
|
374
|
-
else {
|
|
375
|
-
prompts_1.log.info(picocolors_1.default.dim(" Note: Add EXPO_PUBLIC_CONVEX_URL to .env.local with your Convex deployment URL."));
|
|
376
|
-
}
|
|
377
|
-
}
|
|
182
|
+
const convexUrlReady = await (0, steps_1.ensureExpoPublicConvexUrl)(projectDir);
|
|
183
|
+
if (convexUrlReady) {
|
|
184
|
+
prompts_1.log.success("Convex URL configured for Expo.");
|
|
378
185
|
}
|
|
379
|
-
|
|
380
|
-
|
|
186
|
+
else {
|
|
187
|
+
prompts_1.log.info(picocolors_1.default.dim("Add EXPO_PUBLIC_CONVEX_URL to .env.local with your Convex deployment URL."));
|
|
381
188
|
}
|
|
382
|
-
|
|
383
|
-
if (clerkDomain) {
|
|
189
|
+
if (clerk.domain) {
|
|
384
190
|
currentStep++;
|
|
385
191
|
prompts_1.log.step(`Setting Convex environment variable ${stepLabel()}`);
|
|
386
|
-
|
|
387
|
-
|
|
192
|
+
const setEnvOk = await (0, steps_1.setConvexClerkEnv)(projectDir, clerk.domain);
|
|
193
|
+
if (setEnvOk) {
|
|
388
194
|
prompts_1.log.success("Convex environment variable set.");
|
|
389
195
|
}
|
|
390
|
-
|
|
391
|
-
prompts_1.log.info(picocolors_1.default.dim(`
|
|
196
|
+
else {
|
|
197
|
+
prompts_1.log.info(picocolors_1.default.dim(`Run manually: bunx convex env set CLERK_JWT_ISSUER_DOMAIN ${clerk.domain}`));
|
|
392
198
|
}
|
|
393
199
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
await (0, run_1.run)("git", ["commit", "-m", "Initial commit from create-100x-mobile"], {
|
|
401
|
-
cwd: projectDir,
|
|
402
|
-
});
|
|
403
|
-
prompts_1.log.success("Git repository initialized.");
|
|
404
|
-
}
|
|
405
|
-
catch {
|
|
406
|
-
prompts_1.log.info(picocolors_1.default.dim(" Note: git is not available. You can initialize a repo later."));
|
|
407
|
-
}
|
|
408
|
-
// ── Step: Health check ──────────────────────────────
|
|
409
|
-
currentStep++;
|
|
410
|
-
prompts_1.log.step(`Running health check ${stepLabel()}`);
|
|
411
|
-
const healthChecks = [];
|
|
412
|
-
// Check .env.local exists
|
|
413
|
-
const envExists = await (0, fs_1.pathExists)(envLocalPath);
|
|
414
|
-
healthChecks.push({ label: ".env.local exists", ok: envExists });
|
|
415
|
-
// Check EXPO_PUBLIC_CONVEX_URL is set
|
|
416
|
-
if (envExists) {
|
|
417
|
-
let envContent = "";
|
|
418
|
-
try {
|
|
419
|
-
envContent = await (0, fs_2.readTextFile)(envLocalPath);
|
|
420
|
-
}
|
|
421
|
-
catch {
|
|
422
|
-
// ignore
|
|
423
|
-
}
|
|
424
|
-
healthChecks.push({
|
|
425
|
-
label: "EXPO_PUBLIC_CONVEX_URL is set",
|
|
426
|
-
ok: envContent.includes("EXPO_PUBLIC_CONVEX_URL"),
|
|
427
|
-
});
|
|
428
|
-
if (clerkKeyValue) {
|
|
429
|
-
healthChecks.push({
|
|
430
|
-
label: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY is set",
|
|
431
|
-
ok: envContent.includes("EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY"),
|
|
432
|
-
});
|
|
200
|
+
if (options.initializeGit) {
|
|
201
|
+
currentStep++;
|
|
202
|
+
prompts_1.log.step(`Initializing git repository ${stepLabel()}`);
|
|
203
|
+
const gitReady = await (0, steps_1.initializeGit)(projectDir);
|
|
204
|
+
if (gitReady) {
|
|
205
|
+
prompts_1.log.success("Git repository initialized.");
|
|
433
206
|
}
|
|
434
207
|
}
|
|
435
208
|
else {
|
|
436
|
-
|
|
437
|
-
if (clerkKeyValue) {
|
|
438
|
-
healthChecks.push({
|
|
439
|
-
label: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY is set",
|
|
440
|
-
ok: false,
|
|
441
|
-
});
|
|
442
|
-
}
|
|
209
|
+
prompts_1.log.info(picocolors_1.default.dim("Skipping git initialization (--skip-git)."));
|
|
443
210
|
}
|
|
211
|
+
currentStep++;
|
|
212
|
+
prompts_1.log.step(`Running health check ${stepLabel()}`);
|
|
213
|
+
const healthChecks = await (0, steps_1.runHealthChecks)(projectDir, clerk.publishableKey);
|
|
444
214
|
for (const check of healthChecks) {
|
|
445
215
|
if (check.ok) {
|
|
446
216
|
prompts_1.log.info(` ${picocolors_1.default.green("✓")} ${check.label}`);
|
|
@@ -449,39 +219,41 @@ async function cmdNew(args) {
|
|
|
449
219
|
prompts_1.log.info(` ${picocolors_1.default.yellow("⚠")} ${check.label}`);
|
|
450
220
|
}
|
|
451
221
|
}
|
|
452
|
-
// ── Success message ───────────────────────────────
|
|
453
222
|
prompts_1.log.info("");
|
|
454
|
-
if (
|
|
455
|
-
// Clerk is configured — app is ready
|
|
223
|
+
if (clerk.publishableKey && clerk.domain) {
|
|
456
224
|
prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green("Your app is ready!")));
|
|
457
225
|
prompts_1.log.info("");
|
|
458
226
|
prompts_1.log.info(" Next steps:");
|
|
459
227
|
prompts_1.log.info(` ${picocolors_1.default.cyan("cd")} ${projectName}`);
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
228
|
+
if (options.installDependencies) {
|
|
229
|
+
prompts_1.log.info(` ${picocolors_1.default.cyan("bunx expo start")}`);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
prompts_1.log.info(` ${picocolors_1.default.cyan("bun install")} ${picocolors_1.default.dim("# or npm install")}`);
|
|
233
|
+
prompts_1.log.info(` ${picocolors_1.default.cyan("bunx expo start")}`);
|
|
234
|
+
}
|
|
235
|
+
if (!clerk.jwtCreated) {
|
|
463
236
|
prompts_1.log.info(picocolors_1.default.dim(' Note: Ensure a JWT template named "convex" exists in your Clerk dashboard.'));
|
|
464
237
|
}
|
|
465
|
-
prompts_1.log.info(picocolors_1.default.dim(" Remember to enable Google and Apple OAuth providers in your Clerk dashboard."));
|
|
466
238
|
}
|
|
467
239
|
else {
|
|
468
|
-
// No Clerk keys — manual setup needed
|
|
469
240
|
prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green("Project scaffolded!")));
|
|
470
241
|
prompts_1.log.info("");
|
|
471
242
|
prompts_1.log.info(" To finish setup:");
|
|
472
|
-
prompts_1.log.info("");
|
|
473
|
-
prompts_1.log.info(` 1. Create a Clerk app at ${picocolors_1.default.cyan("https://dashboard.clerk.com")}`);
|
|
474
|
-
prompts_1.log.info(` 2. Enable Google and Apple OAuth providers`);
|
|
475
|
-
prompts_1.log.info(` 3. Add keys to ${picocolors_1.default.cyan(".env.local")}:`);
|
|
243
|
+
prompts_1.log.info(` 1. Add keys to ${picocolors_1.default.cyan(".env.local")}`);
|
|
476
244
|
prompts_1.log.info(` ${picocolors_1.default.dim("EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...")}`);
|
|
477
245
|
prompts_1.log.info(` ${picocolors_1.default.dim("CLERK_JWT_ISSUER_DOMAIN=https://your-app.clerk.accounts.dev")}`);
|
|
478
|
-
prompts_1.log.info(`
|
|
479
|
-
prompts_1.log.info(`
|
|
480
|
-
prompts_1.log.info(` 5. Create a JWT template named ${picocolors_1.default.bold('"convex"')} in Clerk dashboard`);
|
|
481
|
-
prompts_1.log.info("");
|
|
482
|
-
prompts_1.log.info(" Then start your app:");
|
|
483
|
-
prompts_1.log.info(` ${picocolors_1.default.cyan("cd")} ${projectName}`);
|
|
484
|
-
prompts_1.log.info(` ${picocolors_1.default.cyan("bunx expo start")}`);
|
|
246
|
+
prompts_1.log.info(` 2. ${picocolors_1.default.dim("bunx convex env set CLERK_JWT_ISSUER_DOMAIN <domain>")}`);
|
|
247
|
+
prompts_1.log.info(` 3. Create a JWT template named ${picocolors_1.default.bold('"convex"')} in Clerk dashboard`);
|
|
485
248
|
}
|
|
486
249
|
(0, prompts_1.outro)(picocolors_1.default.dim("Happy building!"));
|
|
487
250
|
}
|
|
251
|
+
async function runNewCommand(rawArgs) {
|
|
252
|
+
try {
|
|
253
|
+
await cmdNew(rawArgs);
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
prompts_1.log.error((0, steps_1.toErrorMessage)(error));
|
|
257
|
+
process.exitCode = 1;
|
|
258
|
+
}
|
|
259
|
+
}
|
package/dist/lib/clerk.js
CHANGED
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
|
-
|
|
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
|
}
|