create-100x-mobile 0.1.0
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 +43 -0
- package/dist/commands/new.js +260 -0
- package/dist/lib/dotenv.js +48 -0
- package/dist/lib/fs.js +42 -0
- package/dist/lib/projectName.js +10 -0
- package/dist/lib/run.js +21 -0
- package/dist/templates/app/authLayout.js +28 -0
- package/dist/templates/app/authProvider.js +145 -0
- package/dist/templates/app/notFound.js +39 -0
- package/dist/templates/app/rootLayout.js +23 -0
- package/dist/templates/app/settingsScreen.js +279 -0
- package/dist/templates/app/signIn.js +201 -0
- package/dist/templates/app/signUp.js +298 -0
- package/dist/templates/app/tabsLayout.js +70 -0
- package/dist/templates/app/todosScreen.js +158 -0
- package/dist/templates/components/addTodoForm.js +87 -0
- package/dist/templates/components/emptyState.js +83 -0
- package/dist/templates/components/filterTabs.js +111 -0
- package/dist/templates/components/todoItem.js +99 -0
- package/dist/templates/config/appJson.js +35 -0
- package/dist/templates/config/envExample.js +12 -0
- package/dist/templates/config/gitignore.js +19 -0
- package/dist/templates/config/packageJson.js +53 -0
- package/dist/templates/config/tsconfig.js +28 -0
- package/dist/templates/convex/authConfig.js +35 -0
- package/dist/templates/convex/schema.js +23 -0
- package/dist/templates/convex/todos.js +84 -0
- package/dist/templates/convex/users.js +89 -0
- package/dist/templates/hooks/useFrameworkReady.js +19 -0
- package/package.json +36 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const prompts_1 = require("@clack/prompts");
|
|
5
|
+
const new_1 = require("./commands/new");
|
|
6
|
+
function printUsage() {
|
|
7
|
+
console.log(`create-100x-mobile
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
bunx create-100x-mobile [name]
|
|
11
|
+
bun create 100x-mobile [name]
|
|
12
|
+
npx create-100x-mobile [name]
|
|
13
|
+
|
|
14
|
+
Scaffolds a full-stack mobile app with Expo + Convex + Clerk.
|
|
15
|
+
`);
|
|
16
|
+
}
|
|
17
|
+
async function main() {
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
const command = args[0];
|
|
20
|
+
if (command === "-h" || command === "--help" || command === "help") {
|
|
21
|
+
printUsage();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (command === "-v" || command === "--version" || command === "version") {
|
|
25
|
+
const pkg = require("../package.json");
|
|
26
|
+
console.log(pkg.version ?? "0.0.0");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
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
|
+
}
|
|
39
|
+
main().catch((err) => {
|
|
40
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
41
|
+
prompts_1.log.error(message);
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
});
|
|
@@ -0,0 +1,260 @@
|
|
|
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.cmdNew = cmdNew;
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const prompts_1 = require("@clack/prompts");
|
|
9
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
10
|
+
const fs_1 = require("../lib/fs");
|
|
11
|
+
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
|
+
// Config templates
|
|
16
|
+
const packageJson_1 = require("../templates/config/packageJson");
|
|
17
|
+
const appJson_1 = require("../templates/config/appJson");
|
|
18
|
+
const tsconfig_1 = require("../templates/config/tsconfig");
|
|
19
|
+
const gitignore_1 = require("../templates/config/gitignore");
|
|
20
|
+
const envExample_1 = require("../templates/config/envExample");
|
|
21
|
+
// Convex templates
|
|
22
|
+
const schema_1 = require("../templates/convex/schema");
|
|
23
|
+
const todos_1 = require("../templates/convex/todos");
|
|
24
|
+
const users_1 = require("../templates/convex/users");
|
|
25
|
+
const authConfig_1 = require("../templates/convex/authConfig");
|
|
26
|
+
// App templates
|
|
27
|
+
const rootLayout_1 = require("../templates/app/rootLayout");
|
|
28
|
+
const notFound_1 = require("../templates/app/notFound");
|
|
29
|
+
const authProvider_1 = require("../templates/app/authProvider");
|
|
30
|
+
const authLayout_1 = require("../templates/app/authLayout");
|
|
31
|
+
const signIn_1 = require("../templates/app/signIn");
|
|
32
|
+
const signUp_1 = require("../templates/app/signUp");
|
|
33
|
+
const tabsLayout_1 = require("../templates/app/tabsLayout");
|
|
34
|
+
const todosScreen_1 = require("../templates/app/todosScreen");
|
|
35
|
+
const settingsScreen_1 = require("../templates/app/settingsScreen");
|
|
36
|
+
// Component templates
|
|
37
|
+
const addTodoForm_1 = require("../templates/components/addTodoForm");
|
|
38
|
+
const todoItem_1 = require("../templates/components/todoItem");
|
|
39
|
+
const filterTabs_1 = require("../templates/components/filterTabs");
|
|
40
|
+
const emptyState_1 = require("../templates/components/emptyState");
|
|
41
|
+
// Hook templates
|
|
42
|
+
const useFrameworkReady_1 = require("../templates/hooks/useFrameworkReady");
|
|
43
|
+
async function cmdNew(args) {
|
|
44
|
+
(0, prompts_1.intro)(picocolors_1.default.bold(picocolors_1.default.cyan("create-100x-mobile")));
|
|
45
|
+
// Step 1: Parse & validate project name
|
|
46
|
+
let projectName = args[0];
|
|
47
|
+
if (!projectName) {
|
|
48
|
+
const nameInput = await (0, prompts_1.text)({
|
|
49
|
+
message: "What is your project name?",
|
|
50
|
+
placeholder: "my-app",
|
|
51
|
+
defaultValue: "my-app",
|
|
52
|
+
});
|
|
53
|
+
if ((0, prompts_1.isCancel)(nameInput) || !nameInput) {
|
|
54
|
+
(0, prompts_1.cancel)("Operation cancelled.");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
projectName = nameInput;
|
|
58
|
+
}
|
|
59
|
+
const sanitizedName = (0, projectName_1.sanitizeProjectName)(projectName);
|
|
60
|
+
if (sanitizedName !== projectName) {
|
|
61
|
+
prompts_1.log.warn(`Project name sanitized: "${projectName}" → "${sanitizedName}"`);
|
|
62
|
+
projectName = sanitizedName;
|
|
63
|
+
}
|
|
64
|
+
if (!projectName) {
|
|
65
|
+
prompts_1.log.error("Invalid project name.");
|
|
66
|
+
process.exitCode = 1;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const projectDir = (0, node_path_1.resolve)(process.cwd(), projectName);
|
|
70
|
+
// Check if directory exists
|
|
71
|
+
if (await (0, fs_1.pathExists)(projectDir)) {
|
|
72
|
+
prompts_1.log.warn(`Directory "${projectName}" already exists.`);
|
|
73
|
+
const choice = await (0, prompts_1.select)({
|
|
74
|
+
message: "What would you like to do?",
|
|
75
|
+
options: [
|
|
76
|
+
{ value: "overwrite", label: "Overwrite the existing directory" },
|
|
77
|
+
{ value: "cancel", label: "Cancel" },
|
|
78
|
+
],
|
|
79
|
+
initialValue: "cancel",
|
|
80
|
+
});
|
|
81
|
+
if ((0, prompts_1.isCancel)(choice) || choice === "cancel") {
|
|
82
|
+
(0, prompts_1.cancel)("Operation cancelled.");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
await (0, fs_1.removeDir)(projectDir);
|
|
86
|
+
}
|
|
87
|
+
// Step 2: Create directories
|
|
88
|
+
const s = (0, prompts_1.spinner)();
|
|
89
|
+
s.start("Creating project structure");
|
|
90
|
+
const dirs = [
|
|
91
|
+
"app/(auth)",
|
|
92
|
+
"app/(tabs)",
|
|
93
|
+
"app/providers",
|
|
94
|
+
"components",
|
|
95
|
+
"convex",
|
|
96
|
+
"hooks",
|
|
97
|
+
"assets/images",
|
|
98
|
+
];
|
|
99
|
+
for (const dir of dirs) {
|
|
100
|
+
await (0, fs_1.ensureDir)((0, node_path_1.join)(projectDir, dir));
|
|
101
|
+
}
|
|
102
|
+
// Step 3: Write all template files
|
|
103
|
+
const files = [
|
|
104
|
+
// Config
|
|
105
|
+
["package.json", (0, packageJson_1.packageJsonTemplate)(projectName)],
|
|
106
|
+
["app.json", (0, appJson_1.appJsonTemplate)(projectName)],
|
|
107
|
+
["tsconfig.json", (0, tsconfig_1.tsconfigTemplate)()],
|
|
108
|
+
[".gitignore", (0, gitignore_1.gitignoreTemplate)()],
|
|
109
|
+
[".env.example", (0, envExample_1.envExampleTemplate)()],
|
|
110
|
+
["expo-env.d.ts", (0, tsconfig_1.expoEnvDtsTemplate)()],
|
|
111
|
+
// Convex
|
|
112
|
+
["convex/schema.ts", (0, schema_1.schemaTemplate)()],
|
|
113
|
+
["convex/todos.ts", (0, todos_1.todosTemplate)()],
|
|
114
|
+
["convex/users.ts", (0, users_1.usersTemplate)()],
|
|
115
|
+
["convex/auth.config.ts", (0, authConfig_1.authConfigTemplate)()],
|
|
116
|
+
["convex/tsconfig.json", (0, authConfig_1.convexTsconfigTemplate)()],
|
|
117
|
+
["convex/http.ts", (0, authConfig_1.httpTemplate)()],
|
|
118
|
+
// App
|
|
119
|
+
["app/_layout.tsx", (0, rootLayout_1.rootLayoutTemplate)()],
|
|
120
|
+
["app/+not-found.tsx", (0, notFound_1.notFoundTemplate)()],
|
|
121
|
+
["app/providers/AuthProvider.tsx", (0, authProvider_1.authProviderTemplate)()],
|
|
122
|
+
["app/(auth)/_layout.tsx", (0, authLayout_1.authLayoutTemplate)()],
|
|
123
|
+
["app/(auth)/sign-in.tsx", (0, signIn_1.signInTemplate)()],
|
|
124
|
+
["app/(auth)/sign-up.tsx", (0, signUp_1.signUpTemplate)()],
|
|
125
|
+
["app/(tabs)/_layout.tsx", (0, tabsLayout_1.tabsLayoutTemplate)()],
|
|
126
|
+
["app/(tabs)/index.tsx", (0, todosScreen_1.todosScreenTemplate)()],
|
|
127
|
+
["app/(tabs)/settings.tsx", (0, settingsScreen_1.settingsScreenTemplate)()],
|
|
128
|
+
// Components
|
|
129
|
+
["components/AddTodoForm.tsx", (0, addTodoForm_1.addTodoFormTemplate)()],
|
|
130
|
+
["components/TodoItem.tsx", (0, todoItem_1.todoItemTemplate)()],
|
|
131
|
+
["components/FilterTabs.tsx", (0, filterTabs_1.filterTabsTemplate)()],
|
|
132
|
+
["components/EmptyState.tsx", (0, emptyState_1.emptyStateTemplate)()],
|
|
133
|
+
// Hooks
|
|
134
|
+
["hooks/useFrameworkReady.ts", (0, useFrameworkReady_1.useFrameworkReadyTemplate)()],
|
|
135
|
+
];
|
|
136
|
+
for (const [filePath, content] of files) {
|
|
137
|
+
await (0, fs_1.writeTextFile)((0, node_path_1.join)(projectDir, filePath), content);
|
|
138
|
+
}
|
|
139
|
+
s.stop("Project files created.");
|
|
140
|
+
// Step 4: Install dependencies
|
|
141
|
+
s.start("Installing dependencies");
|
|
142
|
+
try {
|
|
143
|
+
await (0, run_1.run)("bun", ["install"], { cwd: projectDir });
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
prompts_1.log.warn("bun install failed, trying npm install...");
|
|
147
|
+
try {
|
|
148
|
+
await (0, run_1.run)("npm", ["install"], { cwd: projectDir });
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
prompts_1.log.error("Failed to install dependencies. Run `bun install` or `npm install` manually.");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
s.stop("Dependencies installed.");
|
|
155
|
+
// Step 5: Initialize Convex
|
|
156
|
+
s.start("Setting up Convex");
|
|
157
|
+
try {
|
|
158
|
+
await (0, run_1.run)("bunx", ["convex", "dev", "--once"], { cwd: projectDir });
|
|
159
|
+
s.stop("Convex initialized.");
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
s.stop("Convex setup needs manual configuration.");
|
|
163
|
+
prompts_1.log.warn("Convex initialization failed. Run `bunx convex dev --once` in your project directory.");
|
|
164
|
+
}
|
|
165
|
+
// Step 6: Prompt for Clerk keys
|
|
166
|
+
prompts_1.log.info("");
|
|
167
|
+
prompts_1.log.info(picocolors_1.default.bold("Clerk Authentication Setup"));
|
|
168
|
+
prompts_1.log.info(picocolors_1.default.dim("Get your keys from https://dashboard.clerk.com → Your App → API Keys"));
|
|
169
|
+
prompts_1.log.info(picocolors_1.default.dim("Press Enter to skip and configure later."));
|
|
170
|
+
const clerkKey = await (0, prompts_1.text)({
|
|
171
|
+
message: "Clerk Publishable Key",
|
|
172
|
+
placeholder: "pk_test_...",
|
|
173
|
+
defaultValue: "",
|
|
174
|
+
});
|
|
175
|
+
const clerkKeyValue = (0, prompts_1.isCancel)(clerkKey) || !clerkKey ? "" : clerkKey.trim();
|
|
176
|
+
let clerkDomain = "";
|
|
177
|
+
if (clerkKeyValue) {
|
|
178
|
+
const domainInput = await (0, prompts_1.text)({
|
|
179
|
+
message: "Clerk JWT Issuer Domain",
|
|
180
|
+
placeholder: "https://your-app.clerk.accounts.dev",
|
|
181
|
+
defaultValue: "",
|
|
182
|
+
});
|
|
183
|
+
clerkDomain =
|
|
184
|
+
(0, prompts_1.isCancel)(domainInput) || !domainInput ? "" : domainInput.trim();
|
|
185
|
+
}
|
|
186
|
+
// Step 7: Write env vars
|
|
187
|
+
const envLocalPath = (0, node_path_1.join)(projectDir, ".env.local");
|
|
188
|
+
let envContents = "";
|
|
189
|
+
// Read existing .env.local (Convex may have written EXPO_PUBLIC_CONVEX_URL)
|
|
190
|
+
try {
|
|
191
|
+
envContents = await (0, fs_2.readTextFile)(envLocalPath);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
envContents = "";
|
|
195
|
+
}
|
|
196
|
+
if (clerkKeyValue) {
|
|
197
|
+
envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY", clerkKeyValue);
|
|
198
|
+
}
|
|
199
|
+
if (clerkDomain) {
|
|
200
|
+
envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "CLERK_JWT_ISSUER_DOMAIN", clerkDomain);
|
|
201
|
+
}
|
|
202
|
+
if (envContents) {
|
|
203
|
+
await (0, fs_1.writeTextFile)(envLocalPath, envContents);
|
|
204
|
+
}
|
|
205
|
+
// Step 8: Set Convex env var for Clerk
|
|
206
|
+
if (clerkDomain) {
|
|
207
|
+
s.start("Setting Convex environment variable");
|
|
208
|
+
try {
|
|
209
|
+
await (0, run_1.run)("bunx", ["convex", "env", "set", "CLERK_JWT_ISSUER_DOMAIN", clerkDomain], { cwd: projectDir });
|
|
210
|
+
s.stop("Convex environment variable set.");
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
s.stop("Could not set Convex env var.");
|
|
214
|
+
prompts_1.log.warn(`Run manually: bunx convex env set CLERK_JWT_ISSUER_DOMAIN ${clerkDomain}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Step 9: Push Convex functions (if Clerk was configured)
|
|
218
|
+
if (clerkKeyValue && clerkDomain) {
|
|
219
|
+
s.start("Deploying Convex functions");
|
|
220
|
+
try {
|
|
221
|
+
await (0, run_1.run)("bunx", ["convex", "dev", "--once"], { cwd: projectDir });
|
|
222
|
+
s.stop("Convex functions deployed.");
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
s.stop("Convex deployment needs retry.");
|
|
226
|
+
prompts_1.log.warn("Run `bunx convex dev --once` in your project directory.");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// Step 10: Success message
|
|
230
|
+
prompts_1.log.info("");
|
|
231
|
+
if (clerkKeyValue && clerkDomain) {
|
|
232
|
+
prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green("Your app is ready!")));
|
|
233
|
+
prompts_1.log.info("");
|
|
234
|
+
prompts_1.log.info(" Next steps:");
|
|
235
|
+
prompts_1.log.info(` ${picocolors_1.default.cyan("cd")} ${projectName}`);
|
|
236
|
+
prompts_1.log.info(` ${picocolors_1.default.cyan("bunx expo start")}`);
|
|
237
|
+
prompts_1.log.info("");
|
|
238
|
+
prompts_1.log.info(picocolors_1.default.dim('Important: In your Clerk dashboard, create a JWT template named "convex"'));
|
|
239
|
+
prompts_1.log.info(picocolors_1.default.dim("with issuer set to your Convex deployment URL."));
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green("Project scaffolded!")));
|
|
243
|
+
prompts_1.log.info("");
|
|
244
|
+
prompts_1.log.info(" To finish setup:");
|
|
245
|
+
prompts_1.log.info("");
|
|
246
|
+
prompts_1.log.info(` 1. Get your Clerk keys from ${picocolors_1.default.cyan("https://dashboard.clerk.com")}`);
|
|
247
|
+
prompts_1.log.info(` 2. Add them to ${picocolors_1.default.cyan(".env.local")}:`);
|
|
248
|
+
prompts_1.log.info(` ${picocolors_1.default.dim("EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...")}`);
|
|
249
|
+
prompts_1.log.info(` ${picocolors_1.default.dim("CLERK_JWT_ISSUER_DOMAIN=https://your-app.clerk.accounts.dev")}`);
|
|
250
|
+
prompts_1.log.info(` 3. Set the Convex env var:`);
|
|
251
|
+
prompts_1.log.info(` ${picocolors_1.default.dim("bunx convex env set CLERK_JWT_ISSUER_DOMAIN <domain>")}`);
|
|
252
|
+
prompts_1.log.info(` 4. Create a JWT template named ${picocolors_1.default.bold('"convex"')} in Clerk dashboard`);
|
|
253
|
+
prompts_1.log.info(` 5. Deploy Convex: ${picocolors_1.default.dim("bunx convex dev --once")}`);
|
|
254
|
+
prompts_1.log.info("");
|
|
255
|
+
prompts_1.log.info(" Then start your app:");
|
|
256
|
+
prompts_1.log.info(` ${picocolors_1.default.cyan("cd")} ${projectName}`);
|
|
257
|
+
prompts_1.log.info(` ${picocolors_1.default.cyan("bunx expo start")}`);
|
|
258
|
+
}
|
|
259
|
+
(0, prompts_1.outro)(picocolors_1.default.dim("Happy building!"));
|
|
260
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseDotenv = parseDotenv;
|
|
4
|
+
exports.upsertDotenvVar = upsertDotenvVar;
|
|
5
|
+
const LINE_SPLIT_REGEX = /\r?\n/;
|
|
6
|
+
const TRAILING_NEWLINES_REGEX = /\n+$/;
|
|
7
|
+
function parseDotenv(contents) {
|
|
8
|
+
const out = {};
|
|
9
|
+
for (const line of contents.split(LINE_SPLIT_REGEX)) {
|
|
10
|
+
const trimmed = line.trim();
|
|
11
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
const idx = trimmed.indexOf("=");
|
|
15
|
+
if (idx === -1) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const key = trimmed.slice(0, idx).trim();
|
|
19
|
+
let value = trimmed.slice(idx + 1).trim();
|
|
20
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
21
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
22
|
+
value = value.slice(1, -1);
|
|
23
|
+
}
|
|
24
|
+
if (key) {
|
|
25
|
+
out[key] = value;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
function upsertDotenvVar(contents, key, value) {
|
|
31
|
+
const lines = contents ? contents.split(LINE_SPLIT_REGEX) : [];
|
|
32
|
+
const newLine = `${key}=${value}`;
|
|
33
|
+
let found = false;
|
|
34
|
+
for (let i = 0; i < lines.length; i++) {
|
|
35
|
+
if (lines[i].startsWith(`${key}=`)) {
|
|
36
|
+
lines[i] = newLine;
|
|
37
|
+
found = true;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (!found) {
|
|
42
|
+
if (lines.length && lines.at(-1) !== "") {
|
|
43
|
+
lines.push("");
|
|
44
|
+
}
|
|
45
|
+
lines.push(newLine);
|
|
46
|
+
}
|
|
47
|
+
return lines.join("\n").replace(TRAILING_NEWLINES_REGEX, "\n");
|
|
48
|
+
}
|
package/dist/lib/fs.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pathExists = pathExists;
|
|
4
|
+
exports.ensureDir = ensureDir;
|
|
5
|
+
exports.removeDir = removeDir;
|
|
6
|
+
exports.readTextFile = readTextFile;
|
|
7
|
+
exports.writeTextFile = writeTextFile;
|
|
8
|
+
exports.writeTextFileIfChanged = writeTextFileIfChanged;
|
|
9
|
+
const promises_1 = require("node:fs/promises");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
11
|
+
async function pathExists(p) {
|
|
12
|
+
try {
|
|
13
|
+
await (0, promises_1.stat)(p);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function ensureDir(dirPath) {
|
|
21
|
+
await (0, promises_1.mkdir)(dirPath, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
async function removeDir(dirPath) {
|
|
24
|
+
await (0, promises_1.rm)(dirPath, { recursive: true, force: true });
|
|
25
|
+
}
|
|
26
|
+
async function readTextFile(filePath) {
|
|
27
|
+
return await (0, promises_1.readFile)(filePath, "utf8");
|
|
28
|
+
}
|
|
29
|
+
async function writeTextFile(filePath, contents) {
|
|
30
|
+
await ensureDir((0, node_path_1.dirname)(filePath));
|
|
31
|
+
await (0, promises_1.writeFile)(filePath, contents);
|
|
32
|
+
}
|
|
33
|
+
async function writeTextFileIfChanged(filePath, contents) {
|
|
34
|
+
const exists = await pathExists(filePath);
|
|
35
|
+
if (exists) {
|
|
36
|
+
const current = await readTextFile(filePath);
|
|
37
|
+
if (current === contents) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
await writeTextFile(filePath, contents);
|
|
42
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sanitizeProjectName = sanitizeProjectName;
|
|
4
|
+
function sanitizeProjectName(name) {
|
|
5
|
+
return name
|
|
6
|
+
.replace(/[^a-z0-9-]/gi, "-")
|
|
7
|
+
.replace(/-+/g, "-")
|
|
8
|
+
.replace(/^-|-$/g, "")
|
|
9
|
+
.toLowerCase();
|
|
10
|
+
}
|
package/dist/lib/run.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.run = run;
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
async function run(cmd, args, options = {}) {
|
|
6
|
+
await new Promise((resolve, reject) => {
|
|
7
|
+
const child = (0, node_child_process_1.spawn)(cmd, args, {
|
|
8
|
+
cwd: options.cwd,
|
|
9
|
+
env: options.env ?? process.env,
|
|
10
|
+
stdio: "inherit",
|
|
11
|
+
shell: process.platform === "win32",
|
|
12
|
+
});
|
|
13
|
+
child.on("error", reject);
|
|
14
|
+
child.on("exit", (code, signal) => {
|
|
15
|
+
if (code === 0) {
|
|
16
|
+
return resolve();
|
|
17
|
+
}
|
|
18
|
+
reject(new Error(`${cmd} ${args.join(" ")} exited with code ${code ?? "null"} signal ${signal ?? "null"}`));
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.authLayoutTemplate = authLayoutTemplate;
|
|
4
|
+
function authLayoutTemplate() {
|
|
5
|
+
return `import { Stack } from "expo-router";
|
|
6
|
+
|
|
7
|
+
export default function AuthLayout() {
|
|
8
|
+
return (
|
|
9
|
+
<Stack>
|
|
10
|
+
<Stack.Screen
|
|
11
|
+
name="sign-in"
|
|
12
|
+
options={{
|
|
13
|
+
title: "Sign In",
|
|
14
|
+
headerBackTitle: "Back",
|
|
15
|
+
}}
|
|
16
|
+
/>
|
|
17
|
+
<Stack.Screen
|
|
18
|
+
name="sign-up"
|
|
19
|
+
options={{
|
|
20
|
+
title: "Sign Up",
|
|
21
|
+
headerBackTitle: "Back",
|
|
22
|
+
}}
|
|
23
|
+
/>
|
|
24
|
+
</Stack>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.authProviderTemplate = authProviderTemplate;
|
|
4
|
+
function authProviderTemplate() {
|
|
5
|
+
return `import React from "react";
|
|
6
|
+
import { ClerkProvider, ClerkLoaded, useAuth } from "@clerk/clerk-expo";
|
|
7
|
+
import { ConvexReactClient } from "convex/react";
|
|
8
|
+
import { ConvexProviderWithClerk } from "convex/react-clerk";
|
|
9
|
+
import * as SecureStore from "expo-secure-store";
|
|
10
|
+
import { View, Text, StyleSheet } from "react-native";
|
|
11
|
+
|
|
12
|
+
const convexUrl = process.env.EXPO_PUBLIC_CONVEX_URL;
|
|
13
|
+
const isConvexConfigured = convexUrl && convexUrl.startsWith("https://");
|
|
14
|
+
|
|
15
|
+
const convex = isConvexConfigured ? new ConvexReactClient(convexUrl) : null;
|
|
16
|
+
|
|
17
|
+
const tokenCache = {
|
|
18
|
+
async getToken(key: string) {
|
|
19
|
+
try {
|
|
20
|
+
return await SecureStore.getItemAsync(key);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error("SecureStore getToken error:", error);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
async saveToken(key: string, value: string) {
|
|
27
|
+
try {
|
|
28
|
+
await SecureStore.setItemAsync(key, value);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error("SecureStore saveToken error:", error);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
async deleteToken(key: string) {
|
|
34
|
+
try {
|
|
35
|
+
await SecureStore.deleteItemAsync(key);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error("SecureStore deleteToken error:", error);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function ConvexProviderWithAuth({ children }: { children: React.ReactNode }) {
|
|
43
|
+
if (!convex) {
|
|
44
|
+
return (
|
|
45
|
+
<View style={styles.setupContainer}>
|
|
46
|
+
<View style={styles.setupCard}>
|
|
47
|
+
<Text style={styles.setupTitle}>Setup Required</Text>
|
|
48
|
+
<Text style={styles.setupText}>
|
|
49
|
+
Configure Convex and Clerk to use this app.
|
|
50
|
+
</Text>
|
|
51
|
+
<Text style={styles.setupStep}>
|
|
52
|
+
1. Set up your Convex project{"\\n"}
|
|
53
|
+
2. Set up your Clerk application{"\\n"}
|
|
54
|
+
3. Add environment variables to .env.local
|
|
55
|
+
</Text>
|
|
56
|
+
</View>
|
|
57
|
+
</View>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
|
|
63
|
+
{children}
|
|
64
|
+
</ConvexProviderWithClerk>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
69
|
+
const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
|
|
70
|
+
|
|
71
|
+
if (!publishableKey) {
|
|
72
|
+
return (
|
|
73
|
+
<View style={styles.setupContainer}>
|
|
74
|
+
<View style={styles.setupCard}>
|
|
75
|
+
<Text style={styles.setupTitle}>Setup Required</Text>
|
|
76
|
+
<Text style={styles.setupText}>
|
|
77
|
+
Configure Convex and Clerk to use this app.
|
|
78
|
+
</Text>
|
|
79
|
+
<Text style={styles.setupStep}>
|
|
80
|
+
1. Set up your Convex project{"\\n"}
|
|
81
|
+
2. Set up your Clerk application{"\\n"}
|
|
82
|
+
3. Add environment variables to .env.local
|
|
83
|
+
</Text>
|
|
84
|
+
</View>
|
|
85
|
+
</View>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<ClerkProvider tokenCache={tokenCache} publishableKey={publishableKey}>
|
|
91
|
+
<ClerkLoaded>
|
|
92
|
+
<ConvexProviderWithAuth>{children}</ConvexProviderWithAuth>
|
|
93
|
+
</ClerkLoaded>
|
|
94
|
+
</ClerkProvider>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export default AuthProvider;
|
|
99
|
+
|
|
100
|
+
const styles = StyleSheet.create({
|
|
101
|
+
setupContainer: {
|
|
102
|
+
flex: 1,
|
|
103
|
+
backgroundColor: "#f5f5f5",
|
|
104
|
+
justifyContent: "center",
|
|
105
|
+
alignItems: "center",
|
|
106
|
+
padding: 20,
|
|
107
|
+
},
|
|
108
|
+
setupCard: {
|
|
109
|
+
backgroundColor: "#fff",
|
|
110
|
+
borderRadius: 12,
|
|
111
|
+
padding: 24,
|
|
112
|
+
maxWidth: 400,
|
|
113
|
+
width: "100%",
|
|
114
|
+
shadowColor: "#000",
|
|
115
|
+
shadowOffset: { width: 0, height: 2 },
|
|
116
|
+
shadowOpacity: 0.1,
|
|
117
|
+
shadowRadius: 8,
|
|
118
|
+
elevation: 3,
|
|
119
|
+
},
|
|
120
|
+
setupTitle: {
|
|
121
|
+
fontSize: 24,
|
|
122
|
+
fontWeight: "bold",
|
|
123
|
+
color: "#333",
|
|
124
|
+
marginBottom: 16,
|
|
125
|
+
textAlign: "center",
|
|
126
|
+
},
|
|
127
|
+
setupText: {
|
|
128
|
+
fontSize: 16,
|
|
129
|
+
color: "#666",
|
|
130
|
+
marginBottom: 12,
|
|
131
|
+
lineHeight: 22,
|
|
132
|
+
textAlign: "center",
|
|
133
|
+
},
|
|
134
|
+
setupStep: {
|
|
135
|
+
fontSize: 14,
|
|
136
|
+
color: "#888",
|
|
137
|
+
marginTop: 16,
|
|
138
|
+
padding: 16,
|
|
139
|
+
backgroundColor: "#f8f9fa",
|
|
140
|
+
borderRadius: 8,
|
|
141
|
+
lineHeight: 20,
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
`;
|
|
145
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.notFoundTemplate = notFoundTemplate;
|
|
4
|
+
function notFoundTemplate() {
|
|
5
|
+
return `import { Link, Stack } from "expo-router";
|
|
6
|
+
import { StyleSheet, Text, View } from "react-native";
|
|
7
|
+
|
|
8
|
+
export default function NotFoundScreen() {
|
|
9
|
+
return (
|
|
10
|
+
<>
|
|
11
|
+
<Stack.Screen options={{ title: "Oops!" }} />
|
|
12
|
+
<View style={styles.container}>
|
|
13
|
+
<Text style={styles.text}>This screen doesn't exist.</Text>
|
|
14
|
+
<Link href="/" style={styles.link}>
|
|
15
|
+
<Text>Go to home screen!</Text>
|
|
16
|
+
</Link>
|
|
17
|
+
</View>
|
|
18
|
+
</>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const styles = StyleSheet.create({
|
|
23
|
+
container: {
|
|
24
|
+
flex: 1,
|
|
25
|
+
alignItems: "center",
|
|
26
|
+
justifyContent: "center",
|
|
27
|
+
padding: 20,
|
|
28
|
+
},
|
|
29
|
+
text: {
|
|
30
|
+
fontSize: 20,
|
|
31
|
+
fontWeight: "600",
|
|
32
|
+
},
|
|
33
|
+
link: {
|
|
34
|
+
marginTop: 15,
|
|
35
|
+
paddingVertical: 15,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
`;
|
|
39
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rootLayoutTemplate = rootLayoutTemplate;
|
|
4
|
+
function rootLayoutTemplate() {
|
|
5
|
+
return `import { Stack } from "expo-router";
|
|
6
|
+
import { StatusBar } from "expo-status-bar";
|
|
7
|
+
import { useFrameworkReady } from "@/hooks/useFrameworkReady";
|
|
8
|
+
import { AuthProvider } from "./providers/AuthProvider";
|
|
9
|
+
|
|
10
|
+
export default function RootLayout() {
|
|
11
|
+
useFrameworkReady();
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<AuthProvider>
|
|
15
|
+
<Stack screenOptions={{ headerShown: false }}>
|
|
16
|
+
<Stack.Screen name="+not-found" />
|
|
17
|
+
</Stack>
|
|
18
|
+
<StatusBar style="auto" />
|
|
19
|
+
</AuthProvider>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
}
|