create-alta-app 1.0.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.
Files changed (2) hide show
  1. package/index.mjs +405 -0
  2. package/package.json +15 -0
package/index.mjs ADDED
@@ -0,0 +1,405 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execSync } from 'node:child_process';
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import prompts from 'prompts';
7
+
8
+ const TEMPLATE_REPO = 'adiel-hub/alta-boilerplate';
9
+ const BRANCH = 'main';
10
+
11
+ // ── Alta project provisioning service ──
12
+ const ALTA_SERVICE_URL = 'https://ikbbbmmzxeemjwzrzsmt.supabase.co/functions/v1/create-project';
13
+
14
+ function run(cmd, cwd) {
15
+ execSync(cmd, { cwd, stdio: 'inherit' });
16
+ }
17
+
18
+ function canRun(cmd) {
19
+ try {
20
+ execSync(cmd, { stdio: 'ignore' });
21
+ return true;
22
+ } catch {
23
+ return false;
24
+ }
25
+ }
26
+
27
+ async function createProject(name, password) {
28
+ const res = await fetch(ALTA_SERVICE_URL, {
29
+ method: 'POST',
30
+ headers: {
31
+ 'Content-Type': 'application/json',
32
+ 'x-api-key': password,
33
+ },
34
+ body: JSON.stringify({ name }),
35
+ });
36
+
37
+ if (!res.ok) {
38
+ const err = await res.json().catch(() => ({ error: res.statusText }));
39
+ throw new Error(err.error || `Server error: ${res.status}`);
40
+ }
41
+
42
+ return res.json();
43
+ }
44
+
45
+ function removeAuth(targetDir) {
46
+ const appDir = path.join(targetDir, 'app');
47
+
48
+ // Remove auth routes
49
+ fs.rmSync(path.join(appDir, 'routes', 'auth'), { recursive: true, force: true });
50
+
51
+ // Remove auth provider
52
+ fs.unlinkSync(path.join(appDir, 'providers', 'auth-provider.tsx'));
53
+
54
+ // Rewrite providers/index.ts — no auth exports
55
+ fs.writeFileSync(path.join(appDir, 'providers', 'index.ts'), '');
56
+
57
+ // Rewrite routes.ts — no auth routes
58
+ fs.writeFileSync(
59
+ path.join(appDir, 'routes.ts'),
60
+ `import { type RouteConfig, route, layout } from '@react-router/dev/routes';
61
+
62
+ export default [
63
+ route('/', './routes/_index.tsx'),
64
+
65
+ layout('./routes/app/_layout.tsx', [
66
+ route('dashboard', './routes/app/dashboard.tsx'),
67
+ route('settings', './routes/app/settings.tsx'),
68
+ ]),
69
+ ] satisfies RouteConfig;
70
+ `
71
+ );
72
+
73
+ // Rewrite _index.tsx — redirect to dashboard
74
+ fs.writeFileSync(
75
+ path.join(appDir, 'routes', '_index.tsx'),
76
+ `import { useEffect } from 'react';
77
+ import { useNavigate } from 'react-router';
78
+
79
+ export default function IndexRoute() {
80
+ const navigate = useNavigate();
81
+
82
+ useEffect(() => {
83
+ navigate('/dashboard', { replace: true });
84
+ }, [navigate]);
85
+
86
+ return null;
87
+ }
88
+ `
89
+ );
90
+
91
+ // Rewrite app/_layout.tsx — no auth guard
92
+ fs.writeFileSync(
93
+ path.join(appDir, 'routes', 'app', '_layout.tsx'),
94
+ `import { Outlet } from 'react-router';
95
+ import { AppSidebar } from '~/components/layout/sidebar';
96
+ import { Header } from '~/components/layout/header';
97
+
98
+ export default function AppLayoutRoute() {
99
+ return (
100
+ <div className="flex h-screen">
101
+ <AppSidebar />
102
+ <div className="flex flex-1 flex-col">
103
+ <Header />
104
+ <main className="flex-1 overflow-auto p-6">
105
+ <Outlet />
106
+ </main>
107
+ </div>
108
+ </div>
109
+ );
110
+ }
111
+ `
112
+ );
113
+
114
+ // Rewrite root.tsx — no AuthProvider
115
+ fs.writeFileSync(
116
+ path.join(appDir, 'root.tsx'),
117
+ `import { Links, Meta, Outlet, Scripts, ScrollRestoration } from 'react-router';
118
+ import './styles/tailwind.css';
119
+
120
+ export function Layout({ children }: { children: React.ReactNode }) {
121
+ return (
122
+ <html lang="en">
123
+ <head>
124
+ <meta charSet="utf-8" />
125
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
126
+ <link rel="icon" type="image/png" href="/alta-icon.png" />
127
+ <Meta />
128
+ <Links />
129
+ </head>
130
+ <body className="bg-background text-foreground antialiased">
131
+ {children}
132
+ <ScrollRestoration />
133
+ <Scripts />
134
+ </body>
135
+ </html>
136
+ );
137
+ }
138
+
139
+ export function HydrateFallback() {
140
+ return (
141
+ <div className="flex h-screen items-center justify-center">
142
+ <p className="text-muted-foreground">Loading...</p>
143
+ </div>
144
+ );
145
+ }
146
+
147
+ export default function Root() {
148
+ return <Outlet />;
149
+ }
150
+ `
151
+ );
152
+
153
+ // Rewrite header.tsx — no user/signOut
154
+ fs.writeFileSync(
155
+ path.join(appDir, 'components', 'layout', 'header.tsx'),
156
+ `import { Separator } from '@altahq/design-system/components/ui/separator';
157
+ import { Text } from '@altahq/design-system/components/ui/text';
158
+
159
+ export function Header() {
160
+ return (
161
+ <header className="flex h-14 items-center gap-2 border-b px-4">
162
+ <Separator orientation="vertical" className="h-4" />
163
+ <div className="ml-auto flex items-center gap-3">
164
+ <Text variant="muted">Alta App</Text>
165
+ </div>
166
+ </header>
167
+ );
168
+ }
169
+ `
170
+ );
171
+
172
+ // Rewrite dashboard — no user reference
173
+ fs.writeFileSync(
174
+ path.join(appDir, 'routes', 'app', 'dashboard.tsx'),
175
+ `import { Card, CardContent, CardHeader, CardTitle } from '@altahq/design-system/components/ui/card';
176
+ import { Text } from '@altahq/design-system/components/ui/text';
177
+ import { Badge } from '@altahq/design-system/components/ui/badge';
178
+ import { Separator } from '@altahq/design-system/components/ui/separator';
179
+
180
+ export default function DashboardRoute() {
181
+ return (
182
+ <div className="space-y-6">
183
+ <div>
184
+ <Text variant="heading3">Dashboard</Text>
185
+ <Text variant="muted" className="mt-1">Welcome to your app</Text>
186
+ </div>
187
+ <Separator />
188
+ <div className="grid gap-4 md:grid-cols-3">
189
+ <Card>
190
+ <CardHeader className="flex flex-row items-center justify-between pb-2">
191
+ <CardTitle className="text-sm font-medium text-muted-foreground">Status</CardTitle>
192
+ <Badge variant="default">Active</Badge>
193
+ </CardHeader>
194
+ <CardContent>
195
+ <Text variant="heading3">All systems go</Text>
196
+ <Text variant="small" className="mt-1 text-muted-foreground">Your app is running smoothly</Text>
197
+ </CardContent>
198
+ </Card>
199
+ <Card>
200
+ <CardHeader className="pb-2">
201
+ <CardTitle className="text-sm font-medium text-muted-foreground">Environment</CardTitle>
202
+ </CardHeader>
203
+ <CardContent>
204
+ <Text variant="heading3">Development</Text>
205
+ <Text variant="small" className="mt-1 text-muted-foreground">Local dev server</Text>
206
+ </CardContent>
207
+ </Card>
208
+ <Card>
209
+ <CardHeader className="pb-2">
210
+ <CardTitle className="text-sm font-medium text-muted-foreground">Framework</CardTitle>
211
+ </CardHeader>
212
+ <CardContent>
213
+ <Text variant="heading3">Alta</Text>
214
+ <Text variant="small" className="mt-1 text-muted-foreground">React + Supabase + Vercel</Text>
215
+ </CardContent>
216
+ </Card>
217
+ </div>
218
+ </div>
219
+ );
220
+ }
221
+ `
222
+ );
223
+
224
+ // Rewrite settings — no user reference
225
+ fs.writeFileSync(
226
+ path.join(appDir, 'routes', 'app', 'settings.tsx'),
227
+ `import { Card, CardContent, CardHeader, CardTitle } from '@altahq/design-system/components/ui/card';
228
+ import { Text } from '@altahq/design-system/components/ui/text';
229
+
230
+ export default function SettingsRoute() {
231
+ return (
232
+ <div className="space-y-6">
233
+ <div>
234
+ <Text variant="heading3">Settings</Text>
235
+ <Text variant="muted" className="mt-1">Manage your app preferences</Text>
236
+ </div>
237
+ <Card>
238
+ <CardHeader>
239
+ <CardTitle>App Settings</CardTitle>
240
+ </CardHeader>
241
+ <CardContent>
242
+ <Text variant="paragraph">Configure your app here.</Text>
243
+ </CardContent>
244
+ </Card>
245
+ </div>
246
+ );
247
+ }
248
+ `
249
+ );
250
+ }
251
+
252
+ async function main() {
253
+ console.log('\n create-alta-app\n');
254
+
255
+ const argName = process.argv[2];
256
+
257
+ const response = await prompts(
258
+ [
259
+ {
260
+ type: argName ? null : 'text',
261
+ name: 'projectName',
262
+ message: 'Project name:',
263
+ initial: 'my-alta-app',
264
+ validate: (v) => (v.length > 0 ? true : 'Project name is required'),
265
+ },
266
+ {
267
+ type: 'password',
268
+ name: 'password',
269
+ message: 'Alta password:',
270
+ validate: (v) => (v.length > 0 ? true : 'Password is required'),
271
+ },
272
+ {
273
+ type: 'toggle',
274
+ name: 'includeAuth',
275
+ message: 'Include Supabase authentication? (login, signup, forgot password)',
276
+ initial: true,
277
+ active: 'Yes',
278
+ inactive: 'No',
279
+ },
280
+ ],
281
+ { onCancel: () => process.exit(0) }
282
+ );
283
+
284
+ const projectName = argName || response.projectName;
285
+ const targetDir = path.resolve(process.cwd(), projectName);
286
+
287
+ if (fs.existsSync(targetDir)) {
288
+ console.error(`\n Error: Directory "${projectName}" already exists.\n`);
289
+ process.exit(1);
290
+ }
291
+
292
+ // ── Clone template ──
293
+ console.log(' Downloading template...\n');
294
+ run(`npx --yes degit ${TEMPLATE_REPO}#${BRANCH} "${projectName}"`, process.cwd());
295
+
296
+ // Remove the create-alta-app package from the cloned template
297
+ const cliDir = path.join(targetDir, 'packages', 'create-alta-app');
298
+ if (fs.existsSync(cliDir)) {
299
+ fs.rmSync(cliDir, { recursive: true, force: true });
300
+ }
301
+
302
+ // Remove the packages dir if empty
303
+ const packagesDir = path.join(targetDir, 'packages');
304
+ if (fs.existsSync(packagesDir) && fs.readdirSync(packagesDir).length === 0) {
305
+ fs.rmSync(packagesDir, { recursive: true, force: true });
306
+ }
307
+
308
+ // Update package.json name
309
+ const rootPkgPath = path.join(targetDir, 'package.json');
310
+ const rootPkg = JSON.parse(fs.readFileSync(rootPkgPath, 'utf-8'));
311
+ rootPkg.name = projectName;
312
+ fs.writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, 2) + '\n');
313
+
314
+ // ── Remove auth if not needed ──
315
+ if (!response.includeAuth) {
316
+ console.log(' Removing auth...\n');
317
+ removeAuth(targetDir);
318
+ }
319
+
320
+ // ── Create Supabase + Vercel projects ──
321
+ console.log(' Creating Supabase & Vercel projects...\n');
322
+
323
+ let credentials;
324
+ try {
325
+ credentials = await createProject(projectName, response.password);
326
+ } catch (err) {
327
+ console.error(` Error creating projects: ${err.message}`);
328
+ console.error(' You can set up manually later.\n');
329
+ credentials = null;
330
+ }
331
+
332
+ if (credentials) {
333
+ // Write .env at project root — used by Vite dev server
334
+ const env = [
335
+ `VITE_SUPABASE_URL=${credentials.supabaseUrl}`,
336
+ `VITE_SUPABASE_ANON_KEY=${credentials.supabaseAnonKey}`,
337
+ `SUPABASE_PROJECT_REF=${credentials.supabaseProjectRef}`,
338
+ `SUPABASE_DB_PASSWORD=${credentials.dbPassword}`,
339
+ `DATABASE_URL=${credentials.databaseUrl}`,
340
+ '',
341
+ ].join('\n');
342
+ fs.writeFileSync(path.join(targetDir, '.env'), env);
343
+
344
+ console.log(' Supabase project created.');
345
+ if (credentials.vercelUrl) {
346
+ console.log(' Vercel project created.');
347
+ }
348
+ console.log('');
349
+ }
350
+
351
+ // ── Install dependencies ──
352
+ console.log(' Installing dependencies...\n');
353
+ if (canRun('pnpm --version')) {
354
+ run('pnpm install', targetDir);
355
+ } else {
356
+ console.log(' pnpm not found. Install it with: npm install -g pnpm');
357
+ console.log(` Then run: cd ${projectName} && pnpm install\n`);
358
+ }
359
+
360
+ // ── Init git + push to GitHub ──
361
+ run('git init', targetDir);
362
+ run('git add -A', targetDir);
363
+ try {
364
+ run('git commit -m "Initial commit from create-alta-app"', targetDir);
365
+ } catch {
366
+ // git commit can fail if no user is configured
367
+ }
368
+
369
+ if (credentials?.githubRepoUrl) {
370
+ console.log(' Pushing to GitHub...\n');
371
+ try {
372
+ run(`git remote add origin ${credentials.githubRepoUrl}`, targetDir);
373
+ run('git branch -M main', targetDir);
374
+ run('git push -u origin main', targetDir);
375
+ console.log(' Pushed to GitHub.\n');
376
+ } catch {
377
+ console.log(' Could not push to GitHub. Push manually with: git push -u origin main\n');
378
+ }
379
+ }
380
+
381
+ // ── Done ──
382
+ console.log(`\n Done! Your project is ready.\n`);
383
+ if (credentials) {
384
+ console.log(` Supabase: ${credentials.supabaseUrl}`);
385
+ if (credentials.vercelUrl) {
386
+ console.log(` Vercel: ${credentials.vercelUrl}`);
387
+ }
388
+ if (credentials.githubRepoUrl) {
389
+ console.log(` GitHub: ${credentials.githubRepoUrl}`);
390
+ }
391
+ console.log(` DB pass: ${credentials.dbPassword} (save this!)`);
392
+ console.log('');
393
+ console.log(' Auto-deploy: every push deploys to Vercel');
394
+ console.log('');
395
+ }
396
+ console.log(` Next steps:\n`);
397
+ console.log(` cd ${projectName}`);
398
+ console.log(` pnpm dev`);
399
+ console.log('');
400
+ }
401
+
402
+ main().catch((err) => {
403
+ console.error(err);
404
+ process.exit(1);
405
+ });
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "create-alta-app",
3
+ "version": "1.0.0",
4
+ "description": "Create a new Alta project",
5
+ "bin": {
6
+ "create-alta-app": "./index.mjs"
7
+ },
8
+ "files": ["index.mjs"],
9
+ "type": "module",
10
+ "keywords": ["alta", "boilerplate", "supabase", "react", "vite"],
11
+ "license": "MIT",
12
+ "dependencies": {
13
+ "prompts": "^2.4.2"
14
+ }
15
+ }