create-alta-app 1.5.1 → 1.6.1

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 +158 -58
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { execSync } from 'node:child_process';
4
4
  import fs from 'node:fs';
5
+ import os from 'node:os';
5
6
  import path from 'node:path';
6
7
  import prompts from 'prompts';
7
8
  import ora from 'ora';
@@ -39,7 +40,11 @@ async function createProject(name, password) {
39
40
  throw new Error(err.error || `Server error: ${res.status}`);
40
41
  }
41
42
 
42
- return res.json();
43
+ const data = await res.json();
44
+ if (data.error) {
45
+ throw new Error(data.error);
46
+ }
47
+ return data;
43
48
  }
44
49
 
45
50
  function removeAuth(targetDir) {
@@ -259,6 +264,7 @@ async function main() {
259
264
  console.log(pc.magenta(' ┗' + '━'.repeat(W) + '┛'));
260
265
  console.log('');
261
266
 
267
+ const MONOREPO_BASE = path.join(os.homedir(), 'alta', 'apps', 'ai-engineer');
262
268
  const argName = process.argv[2];
263
269
 
264
270
  const response = await prompts(
@@ -289,7 +295,13 @@ async function main() {
289
295
  );
290
296
 
291
297
  const projectName = argName || response.projectName;
292
- const targetDir = path.resolve(process.cwd(), projectName);
298
+
299
+ // Always install under the Nx monorepo
300
+ if (!fs.existsSync(MONOREPO_BASE)) {
301
+ fs.mkdirSync(MONOREPO_BASE, { recursive: true });
302
+ }
303
+
304
+ const targetDir = path.resolve(MONOREPO_BASE, projectName);
293
305
 
294
306
  if (fs.existsSync(targetDir)) {
295
307
  console.log(`\n ${pc.red('✗')} Directory ${pc.bold(`"${projectName}"`)} already exists.\n`);
@@ -301,7 +313,7 @@ async function main() {
301
313
  // ── Step 1: Clone template ──
302
314
  const spinnerClone = ora({ text: 'Downloading template...', indent: 2 }).start();
303
315
  try {
304
- run(`npx --yes degit ${TEMPLATE_REPO}#${BRANCH} "${projectName}"`, process.cwd());
316
+ run(`npx --yes degit ${TEMPLATE_REPO}#${BRANCH} "${targetDir}"`, process.cwd());
305
317
 
306
318
  const cliDir = path.join(targetDir, 'packages', 'create-alta-app');
307
319
  if (fs.existsSync(cliDir)) fs.rmSync(cliDir, { recursive: true, force: true });
@@ -315,6 +327,80 @@ async function main() {
315
327
  rootPkg.name = projectName;
316
328
  fs.writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, 2) + '\n');
317
329
 
330
+ // Generate Nx project.json
331
+ const nxProject = {
332
+ name: projectName,
333
+ $schema: '../../node_modules/nx/schemas/project-schema.json',
334
+ sourceRoot: `apps/ai-engineer/${projectName}/src`,
335
+ projectType: 'application',
336
+ tags: [],
337
+ targets: {
338
+ build: {
339
+ executor: '@nx/vite:build',
340
+ outputs: ['{options.outputPath}'],
341
+ defaultConfiguration: 'production',
342
+ options: {
343
+ outputPath: `dist/apps/ai-engineer/${projectName}`,
344
+ emptyOutDir: true,
345
+ },
346
+ configurations: {
347
+ development: { mode: 'development' },
348
+ staging: { mode: 'staging' },
349
+ production: { mode: 'production' },
350
+ },
351
+ },
352
+ serve: {
353
+ executor: '@nx/vite:dev-server',
354
+ defaultConfiguration: 'development',
355
+ options: {
356
+ buildTarget: `${projectName}:build`,
357
+ },
358
+ configurations: {
359
+ development: {
360
+ buildTarget: `${projectName}:build:development`,
361
+ hmr: true,
362
+ },
363
+ production: {
364
+ buildTarget: `${projectName}:build:production`,
365
+ hmr: false,
366
+ },
367
+ },
368
+ },
369
+ preview: {
370
+ executor: '@nx/vite:preview-server',
371
+ defaultConfiguration: 'development',
372
+ options: {
373
+ buildTarget: `${projectName}:build`,
374
+ },
375
+ configurations: {
376
+ development: {
377
+ buildTarget: `${projectName}:build:development`,
378
+ },
379
+ production: {
380
+ buildTarget: `${projectName}:build:production`,
381
+ },
382
+ },
383
+ dependsOn: ['build'],
384
+ },
385
+ test: {
386
+ executor: '@nx/vitest:test',
387
+ outputs: ['{options.reportsDirectory}'],
388
+ options: {
389
+ passWithNoTests: true,
390
+ reportsDirectory: `../../coverage/apps/ai-engineer/${projectName}`,
391
+ },
392
+ },
393
+ lint: {
394
+ executor: '@nx/eslint:lint',
395
+ outputs: ['{options.outputFile}'],
396
+ },
397
+ },
398
+ };
399
+ fs.writeFileSync(
400
+ path.join(targetDir, 'project.json'),
401
+ JSON.stringify(nxProject, null, 2) + '\n'
402
+ );
403
+
318
404
  spinnerClone.succeed(pc.green('Template downloaded'));
319
405
  } catch (err) {
320
406
  spinnerClone.fail(pc.red('Failed to download template'));
@@ -346,26 +432,21 @@ async function main() {
346
432
  credentials = null;
347
433
  }
348
434
 
349
- // ── Step 4: Write env files ──
435
+ // ── Step 4: Write project config (committed to git — no .env needed) ──
350
436
  if (credentials) {
351
- const spinnerEnv = ora({ text: 'Writing environment variables...', indent: 2 }).start();
352
- const githubToken = credentials.githubRepoUrl
353
- ? credentials.githubRepoUrl.match(/https:\/\/([^@]+)@/)?.[1] || ''
354
- : '';
355
- const env = [
356
- `VITE_SUPABASE_URL=${credentials.supabaseUrl}`,
357
- `VITE_SUPABASE_ANON_KEY=${credentials.supabaseAnonKey}`,
358
- `SUPABASE_PROJECT_REF=${credentials.supabaseProjectRef}`,
359
- `SUPABASE_DB_PASSWORD=${credentials.dbPassword}`,
360
- `DATABASE_URL=${credentials.databaseUrl}`,
361
- `GITHUB_TOKEN=${githubToken}`,
362
- `GITHUB_REPO=${credentials.githubFullName || ''}`,
363
- `VERCEL_URL=${credentials.vercelUrl || ''}`,
364
- `SUPABASE_DASHBOARD=https://supabase.com/dashboard/project/${credentials.supabaseProjectRef}`,
365
- '',
366
- ].join('\n');
367
- fs.writeFileSync(path.join(targetDir, '.env'), env);
368
- spinnerEnv.succeed(pc.green('Environment configured'));
437
+ const spinnerEnv = ora({ text: 'Writing project config...', indent: 2 }).start();
438
+ const altaConfig = {
439
+ supabaseProjectRef: credentials.supabaseProjectRef,
440
+ supabaseUrl: credentials.supabaseUrl,
441
+ supabaseAnonKey: credentials.supabaseAnonKey,
442
+ vercelProjectName: projectName,
443
+ vercelUrl: credentials.vercelUrl || '',
444
+ };
445
+ fs.writeFileSync(
446
+ path.join(targetDir, 'alta.config.json'),
447
+ JSON.stringify(altaConfig, null, 2) + '\n'
448
+ );
449
+ spinnerEnv.succeed(pc.green('Project configured'));
369
450
  }
370
451
 
371
452
  // ── Step 5: Install dependencies ──
@@ -383,7 +464,58 @@ async function main() {
383
464
  console.log(` ${pc.dim(`Then run: cd ${projectName} && pnpm install`)}`);
384
465
  }
385
466
 
386
- // ── Step 6: Install Claude Skills ──
467
+ // ── Step 6: Setup shell credentials (like AWS sandbox keys in ~/.zshrc) ──
468
+ const shellRc = path.join(os.homedir(), '.zshrc');
469
+ const tokensToSet = {
470
+ SUPABASE_ACCESS_TOKEN: 'sbp_85c565540fbc50b75873cf643226881cd4f58af5',
471
+ };
472
+
473
+ try {
474
+ const rcContent = fs.existsSync(shellRc) ? fs.readFileSync(shellRc, 'utf-8') : '';
475
+ const missing = Object.entries(tokensToSet).filter(([key]) => !rcContent.includes(`export ${key}=`));
476
+
477
+ if (missing.length > 0) {
478
+ const spinnerShell = ora({ text: 'Setting up shell credentials...', indent: 2 }).start();
479
+ const lines = ['\n# Alta'];
480
+ for (const [key, value] of missing) {
481
+ lines.push(`export ${key}=${value}`);
482
+ }
483
+ fs.appendFileSync(shellRc, lines.join('\n') + '\n');
484
+ spinnerShell.succeed(pc.green('Shell credentials configured'));
485
+ }
486
+ } catch {
487
+ console.log(` ${pc.dim('Could not update ~/.zshrc — add SUPABASE_ACCESS_TOKEN manually')}`);
488
+ }
489
+
490
+ // ── Step 7: Write project-specific MCP config ──
491
+ if (credentials) {
492
+ const spinnerMcp = ora({ text: 'Configuring Claude MCP...', indent: 2 }).start();
493
+ try {
494
+ const claudeDir = path.join(targetDir, '.claude');
495
+ if (!fs.existsSync(claudeDir)) fs.mkdirSync(claudeDir, { recursive: true });
496
+
497
+ const mcpConfig = {
498
+ mcpServers: {
499
+ supabase: {
500
+ url: `https://mcp.supabase.com/mcp`,
501
+ headers: {
502
+ 'x-supabase-access-token': '${SUPABASE_ACCESS_TOKEN}',
503
+ 'x-project-ref': credentials.supabaseProjectRef,
504
+ },
505
+ },
506
+ },
507
+ };
508
+ fs.writeFileSync(
509
+ path.join(claudeDir, 'mcp.json'),
510
+ JSON.stringify(mcpConfig, null, 2) + '\n'
511
+ );
512
+ spinnerMcp.succeed(pc.green('Claude MCP configured'));
513
+ } catch {
514
+ console.log(` ${pc.dim('Could not write .claude/mcp.json — configure MCP manually')}`);
515
+ }
516
+ }
517
+
518
+ // ── Step 8: Install Claude Skills ──
387
519
  const spinnerSkills = ora({ text: 'Installing Claude Skills...', indent: 2 }).start();
388
520
  try {
389
521
  run('npx --yes skills add https://github.com/supabase/agent-skills --skill supabase-postgres-best-practices', targetDir);
@@ -396,30 +528,6 @@ async function main() {
396
528
  console.log(` ${pc.dim('Install manually: npx skills add <package>')}`);
397
529
  }
398
530
 
399
- // ── Step 7: Init git + push ──
400
- const spinnerGit = ora({ text: 'Initializing git...', indent: 2 }).start();
401
- try {
402
- run('git init', targetDir);
403
- run('git add -A', targetDir);
404
- run('git commit -m "Initial commit from create-alta-app"', targetDir);
405
- spinnerGit.succeed(pc.green('Git initialized'));
406
- } catch {
407
- spinnerGit.warn(pc.yellow('Git init completed (commit may have failed)'));
408
- }
409
-
410
- if (credentials?.githubRepoUrl) {
411
- const spinnerPush = ora({ text: 'Pushing to GitHub...', indent: 2 }).start();
412
- try {
413
- run(`git remote add origin ${credentials.githubRepoUrl}`, targetDir);
414
- run('git branch -M main', targetDir);
415
- run('git push -u origin main', targetDir);
416
- spinnerPush.succeed(pc.green('Pushed to GitHub'));
417
- } catch {
418
- spinnerPush.warn(pc.yellow('Could not push to GitHub'));
419
- console.log(` ${pc.dim('Push manually: git push -u origin main')}`);
420
- }
421
- }
422
-
423
531
  // ── Done ──
424
532
  console.log('');
425
533
  console.log(pc.bold(pc.green(' Your project is ready!')));
@@ -431,21 +539,13 @@ async function main() {
431
539
  console.log(` ${pc.magenta('◆')} ${pc.dim('Supabase')} ${credentials.supabaseUrl}`);
432
540
  if (credentials.vercelUrl) {
433
541
  console.log(` ${pc.magenta('◆')} ${pc.dim('Vercel')} ${credentials.vercelUrl} ${pc.dim('(first deploy may take a few minutes)')}`);
434
-
435
- }
436
- if (credentials.githubRepoUrl) {
437
- const cleanGitUrl = credentials.githubRepoUrl.replace(/https:\/\/[^@]+@/, 'https://');
438
- console.log(` ${pc.magenta('◆')} ${pc.dim('GitHub')} ${cleanGitUrl}`);
439
542
  }
440
- console.log(` ${pc.magenta('◆')} ${pc.dim('DB pass')} Saved in .env`);
441
- console.log('');
442
- console.log(` ${pc.dim('───────────────────────────────────────────')}`);
443
- console.log(` ${pc.dim('Auto-deploy: every push to GitHub → Vercel')}`);
444
- console.log(` ${pc.dim('───────────────────────────────────────────')}`);
543
+ console.log(` ${pc.magenta('◆')} ${pc.dim('Config')} alta.config.json`);
544
+ console.log(` ${pc.magenta('')} ${pc.dim('Location')} ${targetDir}`);
445
545
  console.log('');
446
546
  }
447
547
 
448
- // ── Step 8: Start dev server ──
548
+ // ── Step 9: Start dev server ──
449
549
  console.log(` ${pc.bold('Starting dev server...')}`);
450
550
  console.log('');
451
551
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-alta-app",
3
- "version": "1.5.1",
3
+ "version": "1.6.1",
4
4
  "description": "Create a new Alta project",
5
5
  "bin": {
6
6
  "create-alta-app": "./index.mjs"