create-alta-app 1.5.0 → 1.6.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/index.mjs +157 -55
- 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
|
-
|
|
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
|
-
|
|
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`);
|
|
@@ -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,24 +432,21 @@ async function main() {
|
|
|
346
432
|
credentials = null;
|
|
347
433
|
}
|
|
348
434
|
|
|
349
|
-
// ── Step 4: Write env
|
|
435
|
+
// ── Step 4: Write project config (committed to git — no .env needed) ──
|
|
350
436
|
if (credentials) {
|
|
351
|
-
const spinnerEnv = ora({ text: 'Writing
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
:
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
].join('\n');
|
|
365
|
-
fs.writeFileSync(path.join(targetDir, '.env'), env);
|
|
366
|
-
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'));
|
|
367
450
|
}
|
|
368
451
|
|
|
369
452
|
// ── Step 5: Install dependencies ──
|
|
@@ -381,7 +464,58 @@ async function main() {
|
|
|
381
464
|
console.log(` ${pc.dim(`Then run: cd ${projectName} && pnpm install`)}`);
|
|
382
465
|
}
|
|
383
466
|
|
|
384
|
-
// ── Step 6:
|
|
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 ──
|
|
385
519
|
const spinnerSkills = ora({ text: 'Installing Claude Skills...', indent: 2 }).start();
|
|
386
520
|
try {
|
|
387
521
|
run('npx --yes skills add https://github.com/supabase/agent-skills --skill supabase-postgres-best-practices', targetDir);
|
|
@@ -394,30 +528,6 @@ async function main() {
|
|
|
394
528
|
console.log(` ${pc.dim('Install manually: npx skills add <package>')}`);
|
|
395
529
|
}
|
|
396
530
|
|
|
397
|
-
// ── Step 7: Init git + push ──
|
|
398
|
-
const spinnerGit = ora({ text: 'Initializing git...', indent: 2 }).start();
|
|
399
|
-
try {
|
|
400
|
-
run('git init', targetDir);
|
|
401
|
-
run('git add -A', targetDir);
|
|
402
|
-
run('git commit -m "Initial commit from create-alta-app"', targetDir);
|
|
403
|
-
spinnerGit.succeed(pc.green('Git initialized'));
|
|
404
|
-
} catch {
|
|
405
|
-
spinnerGit.warn(pc.yellow('Git init completed (commit may have failed)'));
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if (credentials?.githubRepoUrl) {
|
|
409
|
-
const spinnerPush = ora({ text: 'Pushing to GitHub...', indent: 2 }).start();
|
|
410
|
-
try {
|
|
411
|
-
run(`git remote add origin ${credentials.githubRepoUrl}`, targetDir);
|
|
412
|
-
run('git branch -M main', targetDir);
|
|
413
|
-
run('git push -u origin main', targetDir);
|
|
414
|
-
spinnerPush.succeed(pc.green('Pushed to GitHub'));
|
|
415
|
-
} catch {
|
|
416
|
-
spinnerPush.warn(pc.yellow('Could not push to GitHub'));
|
|
417
|
-
console.log(` ${pc.dim('Push manually: git push -u origin main')}`);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
531
|
// ── Done ──
|
|
422
532
|
console.log('');
|
|
423
533
|
console.log(pc.bold(pc.green(' Your project is ready!')));
|
|
@@ -429,21 +539,13 @@ async function main() {
|
|
|
429
539
|
console.log(` ${pc.magenta('◆')} ${pc.dim('Supabase')} ${credentials.supabaseUrl}`);
|
|
430
540
|
if (credentials.vercelUrl) {
|
|
431
541
|
console.log(` ${pc.magenta('◆')} ${pc.dim('Vercel')} ${credentials.vercelUrl} ${pc.dim('(first deploy may take a few minutes)')}`);
|
|
432
|
-
|
|
433
|
-
}
|
|
434
|
-
if (credentials.githubRepoUrl) {
|
|
435
|
-
const cleanGitUrl = credentials.githubRepoUrl.replace(/https:\/\/[^@]+@/, 'https://');
|
|
436
|
-
console.log(` ${pc.magenta('◆')} ${pc.dim('GitHub')} ${cleanGitUrl}`);
|
|
437
542
|
}
|
|
438
|
-
console.log(` ${pc.magenta('◆')} ${pc.dim('
|
|
439
|
-
console.log('');
|
|
440
|
-
console.log(` ${pc.dim('───────────────────────────────────────────')}`);
|
|
441
|
-
console.log(` ${pc.dim('Auto-deploy: every push to GitHub → Vercel')}`);
|
|
442
|
-
console.log(` ${pc.dim('───────────────────────────────────────────')}`);
|
|
543
|
+
console.log(` ${pc.magenta('◆')} ${pc.dim('Config')} alta.config.json`);
|
|
544
|
+
console.log(` ${pc.magenta('◆')} ${pc.dim('Location')} ${targetDir}`);
|
|
443
545
|
console.log('');
|
|
444
546
|
}
|
|
445
547
|
|
|
446
|
-
// ── Step
|
|
548
|
+
// ── Step 9: Start dev server ──
|
|
447
549
|
console.log(` ${pc.bold('Starting dev server...')}`);
|
|
448
550
|
console.log('');
|
|
449
551
|
try {
|