@create-lft-app/cli 1.1.0 → 1.1.2
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/bin/cli.js +227 -79
- package/dist/bin/cli.js.map +1 -1
- package/dist/src/index.js +161 -77
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { program } from "commander";
|
|
|
5
5
|
|
|
6
6
|
// src/index.ts
|
|
7
7
|
import path3 from "path";
|
|
8
|
+
import { existsSync as existsSync3 } from "fs";
|
|
8
9
|
import { confirm } from "@inquirer/prompts";
|
|
9
10
|
|
|
10
11
|
// src/config/index.ts
|
|
@@ -110,6 +111,51 @@ async function withSpinner(text, fn, successText) {
|
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
// src/services/github.ts
|
|
115
|
+
async function createGitHubRepo(projectName, config) {
|
|
116
|
+
const octokit = new Octokit({ auth: config.credentials.github.token });
|
|
117
|
+
const org = config.defaults.githubOrg;
|
|
118
|
+
const owner = org || config.credentials.github.username;
|
|
119
|
+
return withSpinner(
|
|
120
|
+
"Creando repositorio en GitHub...",
|
|
121
|
+
async () => {
|
|
122
|
+
let repo;
|
|
123
|
+
if (org) {
|
|
124
|
+
repo = await octokit.rest.repos.createInOrg({
|
|
125
|
+
org,
|
|
126
|
+
name: projectName,
|
|
127
|
+
private: true,
|
|
128
|
+
auto_init: false,
|
|
129
|
+
description: `Proyecto ${projectName} creado con create-lft-app`
|
|
130
|
+
});
|
|
131
|
+
} else {
|
|
132
|
+
repo = await octokit.rest.repos.createForAuthenticatedUser({
|
|
133
|
+
name: projectName,
|
|
134
|
+
private: true,
|
|
135
|
+
auto_init: false,
|
|
136
|
+
description: `Proyecto ${projectName} creado con create-lft-app`
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return repo.data.ssh_url;
|
|
140
|
+
},
|
|
141
|
+
`GitHub: ${owner}/${projectName}`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
async function checkGitHubRepoExists(projectName, config) {
|
|
145
|
+
const octokit = new Octokit({ auth: config.credentials.github.token });
|
|
146
|
+
const org = config.defaults.githubOrg;
|
|
147
|
+
const owner = org || config.credentials.github.username;
|
|
148
|
+
try {
|
|
149
|
+
const existing = await octokit.rest.repos.get({
|
|
150
|
+
owner,
|
|
151
|
+
repo: projectName
|
|
152
|
+
});
|
|
153
|
+
return { exists: true, url: existing.data.ssh_url };
|
|
154
|
+
} catch {
|
|
155
|
+
return { exists: false };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
113
159
|
// src/ui/logger.ts
|
|
114
160
|
import chalk from "chalk";
|
|
115
161
|
var logger = {
|
|
@@ -157,46 +203,6 @@ var logger = {
|
|
|
157
203
|
}
|
|
158
204
|
};
|
|
159
205
|
|
|
160
|
-
// src/services/github.ts
|
|
161
|
-
async function createGitHubRepo(projectName, config) {
|
|
162
|
-
const octokit = new Octokit({ auth: config.credentials.github.token });
|
|
163
|
-
const org = config.defaults.githubOrg;
|
|
164
|
-
const owner = org || config.credentials.github.username;
|
|
165
|
-
try {
|
|
166
|
-
const existing = await octokit.rest.repos.get({
|
|
167
|
-
owner,
|
|
168
|
-
repo: projectName
|
|
169
|
-
});
|
|
170
|
-
logger.success(`GitHub: ${owner}/${projectName} (ya existe)`);
|
|
171
|
-
return existing.data.html_url;
|
|
172
|
-
} catch {
|
|
173
|
-
}
|
|
174
|
-
return withSpinner(
|
|
175
|
-
"Creando repositorio en GitHub...",
|
|
176
|
-
async () => {
|
|
177
|
-
let repo;
|
|
178
|
-
if (org) {
|
|
179
|
-
repo = await octokit.rest.repos.createInOrg({
|
|
180
|
-
org,
|
|
181
|
-
name: projectName,
|
|
182
|
-
private: true,
|
|
183
|
-
auto_init: false,
|
|
184
|
-
description: `Proyecto ${projectName} creado con create-lft-app`
|
|
185
|
-
});
|
|
186
|
-
} else {
|
|
187
|
-
repo = await octokit.rest.repos.createForAuthenticatedUser({
|
|
188
|
-
name: projectName,
|
|
189
|
-
private: true,
|
|
190
|
-
auto_init: false,
|
|
191
|
-
description: `Proyecto ${projectName} creado con create-lft-app`
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
return repo.data.html_url;
|
|
195
|
-
},
|
|
196
|
-
`GitHub: ${owner}/${projectName}`
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
206
|
// src/services/supabase.ts
|
|
201
207
|
var SUPABASE_API_URL = "https://api.supabase.com/v1";
|
|
202
208
|
async function waitForProjectReady(projectId, token, maxAttempts = 60) {
|
|
@@ -297,6 +303,29 @@ function generateSecurePassword() {
|
|
|
297
303
|
}
|
|
298
304
|
return password;
|
|
299
305
|
}
|
|
306
|
+
async function checkSupabaseProjectExists(projectName, config) {
|
|
307
|
+
const token = config.credentials.supabase.accessToken;
|
|
308
|
+
const existing = await findExistingProject(projectName, token);
|
|
309
|
+
if (existing) {
|
|
310
|
+
return { exists: true, url: `https://${existing.id}.supabase.co`, projectId: existing.id };
|
|
311
|
+
}
|
|
312
|
+
return { exists: false };
|
|
313
|
+
}
|
|
314
|
+
async function getExistingSupabaseKeys(projectId, config) {
|
|
315
|
+
return withSpinner(
|
|
316
|
+
"Obteniendo credenciales de Supabase...",
|
|
317
|
+
async () => {
|
|
318
|
+
const token = config.credentials.supabase.accessToken;
|
|
319
|
+
const { anonKey, serviceKey } = await getProjectApiKeys(projectId, token);
|
|
320
|
+
return {
|
|
321
|
+
url: `https://${projectId}.supabase.co`,
|
|
322
|
+
anonKey,
|
|
323
|
+
serviceKey
|
|
324
|
+
};
|
|
325
|
+
},
|
|
326
|
+
"Supabase: credenciales obtenidas"
|
|
327
|
+
);
|
|
328
|
+
}
|
|
300
329
|
|
|
301
330
|
// src/utils/validation.ts
|
|
302
331
|
function validateProjectName(name) {
|
|
@@ -348,11 +377,6 @@ async function createJiraProject(projectName, config) {
|
|
|
348
377
|
const { email, apiToken, domain } = config.credentials.jira;
|
|
349
378
|
const auth = Buffer.from(`${email}:${apiToken}`).toString("base64");
|
|
350
379
|
const projectKey = generateJiraKey(projectName);
|
|
351
|
-
const existing = await findExistingJiraProject(projectName, auth, domain);
|
|
352
|
-
if (existing) {
|
|
353
|
-
logger.success(`Jira: ${existing.key} (ya existe)`);
|
|
354
|
-
return `https://${domain}/browse/${existing.key}`;
|
|
355
|
-
}
|
|
356
380
|
return withSpinner(
|
|
357
381
|
"Creando proyecto en Jira...",
|
|
358
382
|
async () => {
|
|
@@ -412,6 +436,15 @@ async function createJiraProject(projectName, config) {
|
|
|
412
436
|
`Proyecto Jira: ${projectKey}`
|
|
413
437
|
);
|
|
414
438
|
}
|
|
439
|
+
async function checkJiraProjectExists(projectName, config) {
|
|
440
|
+
const { email, apiToken, domain } = config.credentials.jira;
|
|
441
|
+
const auth = Buffer.from(`${email}:${apiToken}`).toString("base64");
|
|
442
|
+
const existing = await findExistingJiraProject(projectName, auth, domain);
|
|
443
|
+
if (existing) {
|
|
444
|
+
return { exists: true, url: `https://${domain}/browse/${existing.key}` };
|
|
445
|
+
}
|
|
446
|
+
return { exists: false };
|
|
447
|
+
}
|
|
415
448
|
|
|
416
449
|
// src/steps/scaffold-nextjs.ts
|
|
417
450
|
import { execa } from "execa";
|
|
@@ -492,10 +525,7 @@ async function setupGit(projectPath, remoteUrl) {
|
|
|
492
525
|
await withSpinner(
|
|
493
526
|
"Configurando Git...",
|
|
494
527
|
async () => {
|
|
495
|
-
|
|
496
|
-
if (!isRepo) {
|
|
497
|
-
await git.init();
|
|
498
|
-
}
|
|
528
|
+
await git.init();
|
|
499
529
|
try {
|
|
500
530
|
await git.addRemote("origin", remoteUrl);
|
|
501
531
|
} catch {
|
|
@@ -518,11 +548,75 @@ async function setupGit(projectPath, remoteUrl) {
|
|
|
518
548
|
// src/ui/banner.ts
|
|
519
549
|
import boxen from "boxen";
|
|
520
550
|
import chalk2 from "chalk";
|
|
551
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
552
|
+
import { fileURLToPath } from "url";
|
|
553
|
+
import { dirname, join } from "path";
|
|
554
|
+
function getVersion() {
|
|
555
|
+
try {
|
|
556
|
+
const __filename2 = fileURLToPath(import.meta.url);
|
|
557
|
+
const __dirname2 = dirname(__filename2);
|
|
558
|
+
const pkgPath = join(__dirname2, "..", "..", "package.json");
|
|
559
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
560
|
+
return pkg.version || "0.0.0";
|
|
561
|
+
} catch {
|
|
562
|
+
return "0.0.0";
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function getKangarooArt() {
|
|
566
|
+
const c = chalk2.cyan;
|
|
567
|
+
return c(`
|
|
568
|
+
\u2588\u2588 \u2588\u2588\u2588
|
|
569
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
570
|
+
\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
571
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
572
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
573
|
+
\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588
|
|
574
|
+
\u2588\u2588 \u2588 \u2588\u2588\u2588\u2588\u2588
|
|
575
|
+
\u2588\u2588 \u2588 \u2588\u2588\u2588\u2588
|
|
576
|
+
\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588 \u2588\u2588
|
|
577
|
+
\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
578
|
+
\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588
|
|
579
|
+
\u2588\u2588\u2588\u2588 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588
|
|
580
|
+
\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
581
|
+
\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
582
|
+
\u2588\u2588 \u2588 \u2588 \u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
583
|
+
\u2588\u2588 \u2588 \u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
584
|
+
\u2588\u2588 \u2588\u2588 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
585
|
+
\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588
|
|
586
|
+
\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588
|
|
587
|
+
\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
588
|
+
\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
589
|
+
\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
590
|
+
\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
591
|
+
\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
592
|
+
\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
593
|
+
\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588
|
|
594
|
+
\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588
|
|
595
|
+
\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588
|
|
596
|
+
\u2588\u2588\u2588 \u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588
|
|
597
|
+
\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588
|
|
598
|
+
\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588
|
|
599
|
+
\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588
|
|
600
|
+
\u2588 \u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588 \u2588\u2588 \u2588
|
|
601
|
+
\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588
|
|
602
|
+
\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588
|
|
603
|
+
\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588 \u2588 \u2588
|
|
604
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588
|
|
605
|
+
\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588
|
|
606
|
+
\u2588\u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
607
|
+
\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588
|
|
608
|
+
\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
609
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
610
|
+
\u2588\u2588
|
|
611
|
+
`);
|
|
612
|
+
}
|
|
521
613
|
function showBanner() {
|
|
614
|
+
const version = getVersion();
|
|
522
615
|
const title = chalk2.bold.cyan("create-lft-app");
|
|
523
|
-
const
|
|
616
|
+
const versionText = chalk2.gray(`v${version}`);
|
|
524
617
|
const description = chalk2.white("Scaffolding para proyectos LFT");
|
|
525
|
-
|
|
618
|
+
console.log(getKangarooArt());
|
|
619
|
+
const banner = boxen(`${title} ${versionText}
|
|
526
620
|
${description}`, {
|
|
527
621
|
padding: 1,
|
|
528
622
|
margin: 1,
|
|
@@ -575,25 +669,72 @@ async function createProject(projectName, options = {}) {
|
|
|
575
669
|
throw new Error("Configuraci\xF3n no encontrada");
|
|
576
670
|
}
|
|
577
671
|
const config = await loadConfig();
|
|
672
|
+
logger.info("Verificando recursos existentes...");
|
|
673
|
+
const status = {
|
|
674
|
+
github: { exists: false },
|
|
675
|
+
supabase: { exists: false },
|
|
676
|
+
jira: { exists: false },
|
|
677
|
+
nextjs: { exists: existsSync3(projectPath) }
|
|
678
|
+
};
|
|
679
|
+
const checks = [];
|
|
680
|
+
if (!options.skipGithub) {
|
|
681
|
+
checks.push(
|
|
682
|
+
checkGitHubRepoExists(projectName, config).then((result) => {
|
|
683
|
+
status.github = result;
|
|
684
|
+
})
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
if (!options.skipSupabase) {
|
|
688
|
+
checks.push(
|
|
689
|
+
checkSupabaseProjectExists(projectName, config).then((result) => {
|
|
690
|
+
status.supabase = result;
|
|
691
|
+
})
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
if (!options.skipJira) {
|
|
695
|
+
checks.push(
|
|
696
|
+
checkJiraProjectExists(projectName, config).then((result) => {
|
|
697
|
+
status.jira = result;
|
|
698
|
+
})
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
await Promise.all(checks);
|
|
578
702
|
logger.newLine();
|
|
579
|
-
logger.title("Resumen de recursos
|
|
703
|
+
logger.title("Resumen de recursos:");
|
|
580
704
|
logger.newLine();
|
|
581
705
|
const resources = [];
|
|
706
|
+
const owner = config.defaults.githubOrg || config.credentials.github.username;
|
|
582
707
|
if (!options.skipGithub) {
|
|
583
|
-
|
|
708
|
+
const githubStatus = status.github.exists ? "\u2713 ya existe" : "se crear\xE1";
|
|
709
|
+
resources.push({
|
|
710
|
+
label: "GitHub",
|
|
711
|
+
value: `${owner}/${projectName} (${githubStatus})`
|
|
712
|
+
});
|
|
584
713
|
}
|
|
585
714
|
if (!options.skipSupabase) {
|
|
586
|
-
|
|
715
|
+
const supabaseStatus = status.supabase.exists ? "\u2713 ya existe" : "se crear\xE1";
|
|
716
|
+
resources.push({
|
|
717
|
+
label: "Supabase",
|
|
718
|
+
value: `${projectName} en ${config.defaults.supabaseRegion} (${supabaseStatus})`
|
|
719
|
+
});
|
|
587
720
|
}
|
|
588
721
|
if (!options.skipJira) {
|
|
589
|
-
|
|
722
|
+
const jiraStatus = status.jira.exists ? "\u2713 ya existe" : "se crear\xE1";
|
|
723
|
+
resources.push({
|
|
724
|
+
label: "Jira",
|
|
725
|
+
value: `Proyecto "${projectName}" (${jiraStatus})`
|
|
726
|
+
});
|
|
590
727
|
}
|
|
591
|
-
|
|
728
|
+
const nextjsStatus = status.nextjs.exists ? "\u2713 ya existe" : "se crear\xE1";
|
|
729
|
+
resources.push({
|
|
730
|
+
label: "Next.js",
|
|
731
|
+
value: `App Router + TypeScript + Tailwind (${nextjsStatus})`
|
|
732
|
+
});
|
|
592
733
|
logger.table(resources);
|
|
593
734
|
logger.newLine();
|
|
594
735
|
if (!options.autoConfirm) {
|
|
595
736
|
const shouldContinue = await confirm({
|
|
596
|
-
message: "\xBFContinuar
|
|
737
|
+
message: "\xBFContinuar?",
|
|
597
738
|
default: true
|
|
598
739
|
});
|
|
599
740
|
if (!shouldContinue) {
|
|
@@ -606,36 +747,43 @@ async function createProject(projectName, options = {}) {
|
|
|
606
747
|
logger.newLine();
|
|
607
748
|
const urls = {};
|
|
608
749
|
let supabaseKeys;
|
|
609
|
-
const externalTasks = [];
|
|
610
750
|
if (!options.skipGithub) {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
751
|
+
if (status.github.exists) {
|
|
752
|
+
urls.github = status.github.url;
|
|
753
|
+
logger.success(`GitHub: ${owner}/${projectName} (ya existe)`);
|
|
754
|
+
} else {
|
|
755
|
+
urls.github = await createGitHubRepo(projectName, config);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
if (!options.skipJira) {
|
|
759
|
+
if (status.jira.exists) {
|
|
760
|
+
urls.jira = status.jira.url;
|
|
761
|
+
logger.success(`Jira: ${projectName} (ya existe)`);
|
|
762
|
+
} else {
|
|
763
|
+
urls.jira = await createJiraProject(projectName, config);
|
|
764
|
+
}
|
|
616
765
|
}
|
|
617
766
|
if (!options.skipSupabase) {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
767
|
+
if (status.supabase.exists && status.supabase.projectId) {
|
|
768
|
+
const result = await getExistingSupabaseKeys(status.supabase.projectId, config);
|
|
769
|
+
urls.supabase = result.url;
|
|
770
|
+
supabaseKeys = result;
|
|
771
|
+
} else {
|
|
772
|
+
const result = await createSupabaseProject(projectName, config);
|
|
773
|
+
urls.supabase = result.url;
|
|
774
|
+
supabaseKeys = result;
|
|
775
|
+
}
|
|
624
776
|
}
|
|
625
|
-
if (!
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
);
|
|
777
|
+
if (!status.nextjs.exists) {
|
|
778
|
+
await scaffoldNextJs(projectName, projectPath);
|
|
779
|
+
await installDependencies(projectPath);
|
|
780
|
+
} else {
|
|
781
|
+
logger.success(`Next.js: ${projectName} (ya existe)`);
|
|
631
782
|
}
|
|
632
|
-
await Promise.all(externalTasks);
|
|
633
|
-
await scaffoldNextJs(projectName, projectPath);
|
|
634
|
-
await installDependencies(projectPath);
|
|
635
783
|
if (supabaseKeys) {
|
|
636
784
|
await createEnvFile(projectPath, supabaseKeys);
|
|
637
785
|
}
|
|
638
|
-
if (!options.skipGit && urls.github) {
|
|
786
|
+
if (!options.skipGit && urls.github && !status.nextjs.exists) {
|
|
639
787
|
await setupGit(projectPath, urls.github);
|
|
640
788
|
}
|
|
641
789
|
logger.newLine();
|
package/dist/bin/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../bin/cli.ts","../../src/index.ts","../../src/config/index.ts","../../src/services/github.ts","../../src/ui/spinner.ts","../../src/ui/logger.ts","../../src/services/supabase.ts","../../src/utils/validation.ts","../../src/services/jira.ts","../../src/steps/scaffold-nextjs.ts","../../src/steps/install-deps.ts","../../src/steps/create-env.ts","../../src/steps/setup-git.ts","../../src/ui/banner.ts"],"sourcesContent":["import { program } from 'commander';\nimport { createProject } from '../src/index.js';\nimport { hasConfig, setConfigPath, getConfigPath } from '../src/config/index.js';\nimport { showBanner } from '../src/ui/banner.js';\nimport { logger } from '../src/ui/logger.js';\n\nconst packageJson = {\n name: 'create-lft-app',\n version: '1.0.0',\n description: 'CLI para crear proyectos LFT con Next.js, GitHub, Supabase y Jira',\n};\n\nprogram\n .name('create-lft-app')\n .description(packageJson.description)\n .version(packageJson.version);\n\nprogram\n .argument('[project-name]', 'Nombre del proyecto a crear')\n .requiredOption('--config <path>', 'Ruta al archivo de credenciales JSON (obligatorio)')\n .option('--skip-github', 'No crear repositorio en GitHub')\n .option('--skip-supabase', 'No crear proyecto en Supabase')\n .option('--skip-jira', 'No crear workspace en Jira')\n .option('--skip-git', 'No inicializar git ni hacer push')\n .option('-y, --yes', 'Aceptar todas las confirmaciones')\n .action(async (projectName: string | undefined, options) => {\n showBanner();\n\n // Establecer la ruta del archivo de config\n setConfigPath(options.config);\n\n if (!projectName) {\n logger.error('Debes especificar un nombre de proyecto');\n logger.info('Uso: create-lft-app <nombre-proyecto> --config <ruta-credenciales>');\n process.exit(1);\n }\n\n if (!await hasConfig()) {\n const configPath = getConfigPath();\n logger.error(`No se encontró el archivo de credenciales: ${configPath}`);\n logger.newLine();\n logger.info('El archivo debe tener el siguiente formato:');\n logger.newLine();\n console.log(`{\n \"github\": {\n \"token\": \"ghp_xxxxxxxxxxxx\",\n \"username\": \"tu-usuario\",\n \"org\": \"tu-organizacion\"\n },\n \"supabase\": {\n \"token\": \"sbp_xxxxxxxxxxxx\",\n \"orgId\": \"tu-org-id\",\n \"region\": \"us-east-1\"\n },\n \"jira\": {\n \"email\": \"tu-email@ejemplo.com\",\n \"token\": \"ATATT3xxxxxxxxxxxx\",\n \"domain\": \"tu-dominio.atlassian.net\"\n }\n}`);\n process.exit(1);\n }\n\n try {\n await createProject(projectName, {\n skipGithub: options.skipGithub,\n skipSupabase: options.skipSupabase,\n skipJira: options.skipJira,\n skipGit: options.skipGit,\n autoConfirm: options.yes,\n });\n } catch (error) {\n if (error instanceof Error) {\n logger.error(error.message);\n }\n process.exit(1);\n }\n });\n\nprogram.parse();\n","import path from 'path';\nimport { confirm } from '@inquirer/prompts';\nimport { loadConfig, hasConfig } from './config/index.js';\nimport { createGitHubRepo } from './services/github.js';\nimport { createSupabaseProject } from './services/supabase.js';\nimport { createJiraProject } from './services/jira.js';\nimport { scaffoldNextJs } from './steps/scaffold-nextjs.js';\nimport { installDependencies } from './steps/install-deps.js';\nimport { createEnvFile } from './steps/create-env.js';\nimport { setupGit } from './steps/setup-git.js';\nimport { logger } from './ui/logger.js';\nimport { showSuccessBanner } from './ui/banner.js';\nimport { validateProjectName } from './utils/validation.js';\n\nexport interface CreateProjectOptions {\n skipGithub?: boolean;\n skipSupabase?: boolean;\n skipJira?: boolean;\n skipGit?: boolean;\n autoConfirm?: boolean;\n}\n\nexport async function createProject(\n projectName: string,\n options: CreateProjectOptions = {}\n): Promise<void> {\n // Validar nombre del proyecto\n const validation = validateProjectName(projectName);\n if (!validation.valid) {\n throw new Error(validation.error);\n }\n\n // Verificar que el directorio no exista\n const projectPath = path.resolve(process.cwd(), projectName);\n\n // Cargar configuración\n if (!await hasConfig()) {\n logger.warning('No se encontró configuración. Ejecuta \"create-lft-app config\" primero.');\n throw new Error('Configuración no encontrada');\n }\n\n const config = await loadConfig();\n\n // Mostrar resumen\n logger.newLine();\n logger.title('Resumen de recursos a crear:');\n logger.newLine();\n\n const resources = [];\n if (!options.skipGithub) {\n resources.push({ label: 'GitHub', value: `${config.defaults.githubOrg || config.credentials.github.username}/${projectName} (privado)` });\n }\n if (!options.skipSupabase) {\n resources.push({ label: 'Supabase', value: `${projectName} en ${config.defaults.supabaseRegion}` });\n }\n if (!options.skipJira) {\n resources.push({ label: 'Jira', value: `Proyecto \"${projectName}\" en ${config.credentials.jira.domain}` });\n }\n resources.push({ label: 'Next.js', value: 'App Router + TypeScript + Tailwind + Dashboard' });\n\n logger.table(resources);\n logger.newLine();\n\n // Confirmar\n if (!options.autoConfirm) {\n const shouldContinue = await confirm({\n message: '¿Continuar con la creación?',\n default: true,\n });\n\n if (!shouldContinue) {\n logger.info('Operación cancelada');\n return;\n }\n }\n\n logger.newLine();\n logger.divider();\n logger.newLine();\n\n const urls: { github?: string; supabase?: string; jira?: string } = {};\n let supabaseKeys: { url: string; anonKey: string; serviceKey: string } | undefined;\n\n // Crear recursos externos en paralelo\n const externalTasks: Promise<void>[] = [];\n\n if (!options.skipGithub) {\n externalTasks.push(\n createGitHubRepo(projectName, config).then((url) => {\n urls.github = url;\n })\n );\n }\n\n if (!options.skipSupabase) {\n externalTasks.push(\n createSupabaseProject(projectName, config).then((result) => {\n urls.supabase = result.url;\n supabaseKeys = result;\n })\n );\n }\n\n if (!options.skipJira) {\n externalTasks.push(\n createJiraProject(projectName, config).then((url) => {\n urls.jira = url;\n })\n );\n }\n\n // Esperar a que terminen los recursos externos\n await Promise.all(externalTasks);\n\n // Scaffold Next.js (incluye template LFT completo)\n await scaffoldNextJs(projectName, projectPath);\n\n // Instalar dependencias\n await installDependencies(projectPath);\n\n // Crear .env.local\n if (supabaseKeys) {\n await createEnvFile(projectPath, supabaseKeys);\n }\n\n // Setup git\n if (!options.skipGit && urls.github) {\n await setupGit(projectPath, urls.github);\n }\n\n // Mostrar resumen final\n logger.newLine();\n showSuccessBanner(projectName, urls);\n}\n","import { existsSync, readFileSync } from 'fs';\n\n// Variable para almacenar la ruta del config\nlet configFilePath: string = '';\n\ninterface ConfigFile {\n github: {\n token: string;\n username: string;\n org: string;\n };\n supabase: {\n token: string;\n orgId: string;\n region: string;\n };\n jira: {\n email: string;\n token: string;\n domain: string;\n };\n}\n\n// Cache de la configuración cargada\nlet loadedConfig: ConfigFile | null = null;\n\n/**\n * Establece la ruta del archivo de configuración\n */\nexport function setConfigPath(path: string): void {\n configFilePath = path;\n loadedConfig = null;\n}\n\n/**\n * Obtiene la ruta actual del archivo de configuración\n */\nexport function getConfigPath(): string {\n return configFilePath;\n}\n\n/**\n * Carga la configuración desde el archivo JSON\n */\nfunction loadConfigFromFile(): ConfigFile | null {\n if (loadedConfig) {\n return loadedConfig;\n }\n\n if (!configFilePath || !existsSync(configFilePath)) {\n return null;\n }\n\n try {\n const content = readFileSync(configFilePath, 'utf-8');\n const parsed = JSON.parse(content);\n\n loadedConfig = {\n github: {\n token: parsed.github?.token || '',\n username: parsed.github?.username || '',\n org: parsed.github?.org || '',\n },\n supabase: {\n token: parsed.supabase?.token || '',\n orgId: parsed.supabase?.orgId || '',\n region: parsed.supabase?.region || 'us-east-1',\n },\n jira: {\n email: parsed.jira?.email || '',\n token: parsed.jira?.token || '',\n domain: parsed.jira?.domain || '',\n },\n };\n\n return loadedConfig;\n } catch {\n return null;\n }\n}\n\nexport interface LFTConfig {\n version: string;\n credentials: {\n github: {\n token: string;\n username: string;\n };\n supabase: {\n accessToken: string;\n organizationId: string;\n };\n jira: {\n email: string;\n apiToken: string;\n domain: string;\n };\n };\n defaults: {\n githubOrg?: string;\n supabaseRegion: string;\n jiraProjectType: string;\n };\n}\n\nexport async function loadConfig(): Promise<LFTConfig> {\n const config = loadConfigFromFile();\n\n if (!config) {\n throw new Error('No se pudo cargar la configuración');\n }\n\n return {\n version: '1.0.0',\n credentials: {\n github: {\n token: config.github.token,\n username: config.github.username,\n },\n supabase: {\n accessToken: config.supabase.token,\n organizationId: config.supabase.orgId,\n },\n jira: {\n email: config.jira.email,\n apiToken: config.jira.token,\n domain: config.jira.domain,\n },\n },\n defaults: {\n githubOrg: config.github.org || undefined,\n supabaseRegion: config.supabase.region,\n jiraProjectType: 'software',\n },\n };\n}\n\nexport async function hasConfig(): Promise<boolean> {\n const config = loadConfigFromFile();\n\n if (!config) {\n return false;\n }\n\n return (\n config.github.token !== '' &&\n config.supabase.token !== '' &&\n config.jira.token !== ''\n );\n}\n","import { Octokit } from 'octokit';\nimport { withSpinner } from '../ui/spinner.js';\nimport { logger } from '../ui/logger.js';\nimport type { LFTConfig } from '../config/index.js';\n\nexport async function createGitHubRepo(\n projectName: string,\n config: LFTConfig\n): Promise<string> {\n const octokit = new Octokit({ auth: config.credentials.github.token });\n const org = config.defaults.githubOrg;\n const owner = org || config.credentials.github.username;\n\n // Verificar si el repo ya existe\n try {\n const existing = await octokit.rest.repos.get({\n owner,\n repo: projectName,\n });\n logger.success(`GitHub: ${owner}/${projectName} (ya existe)`);\n return existing.data.html_url;\n } catch {\n // No existe, continuar con la creación\n }\n\n return withSpinner(\n 'Creando repositorio en GitHub...',\n async () => {\n let repo;\n\n if (org) {\n repo = await octokit.rest.repos.createInOrg({\n org,\n name: projectName,\n private: true,\n auto_init: false,\n description: `Proyecto ${projectName} creado con create-lft-app`,\n });\n } else {\n repo = await octokit.rest.repos.createForAuthenticatedUser({\n name: projectName,\n private: true,\n auto_init: false,\n description: `Proyecto ${projectName} creado con create-lft-app`,\n });\n }\n\n return repo.data.html_url;\n },\n `GitHub: ${owner}/${projectName}`\n );\n}\n\nexport async function validateGitHubToken(token: string): Promise<{ valid: boolean; username?: string }> {\n try {\n const octokit = new Octokit({ auth: token });\n const { data } = await octokit.rest.users.getAuthenticated();\n return { valid: true, username: data.login };\n } catch {\n return { valid: false };\n }\n}\n","import ora, { type Ora } from 'ora';\n\nexport function createSpinner(text: string): Ora {\n return ora({\n text,\n spinner: 'dots',\n });\n}\n\nexport async function withSpinner<T>(\n text: string,\n fn: () => Promise<T>,\n successText?: string\n): Promise<T> {\n const spinner = createSpinner(text).start();\n\n try {\n const result = await fn();\n spinner.succeed(successText || text);\n return result;\n } catch (error) {\n spinner.fail();\n throw error;\n }\n}\n","import chalk from 'chalk';\n\nexport const logger = {\n info: (message: string) => {\n console.log(chalk.blue('ℹ'), message);\n },\n\n success: (message: string) => {\n console.log(chalk.green('✔'), message);\n },\n\n warning: (message: string) => {\n console.log(chalk.yellow('⚠'), message);\n },\n\n error: (message: string) => {\n console.log(chalk.red('✖'), message);\n },\n\n step: (step: number, total: number, message: string) => {\n console.log(chalk.cyan(`[${step}/${total}]`), message);\n },\n\n newLine: () => {\n console.log();\n },\n\n divider: () => {\n console.log(chalk.gray('─'.repeat(50)));\n },\n\n title: (message: string) => {\n console.log(chalk.bold.white(message));\n },\n\n subtitle: (message: string) => {\n console.log(chalk.gray(message));\n },\n\n link: (label: string, url: string) => {\n console.log(` ${chalk.gray(label + ':')} ${chalk.cyan.underline(url)}`);\n },\n\n list: (items: string[]) => {\n items.forEach((item) => {\n console.log(chalk.gray(' •'), item);\n });\n },\n\n table: (rows: Array<{ label: string; value: string }>) => {\n const maxLabelLength = Math.max(...rows.map((r) => r.label.length));\n rows.forEach(({ label, value }) => {\n const paddedLabel = label.padEnd(maxLabelLength);\n console.log(` ${chalk.gray(paddedLabel)} ${value}`);\n });\n },\n};\n","import { withSpinner, createSpinner } from '../ui/spinner.js';\nimport { logger } from '../ui/logger.js';\nimport type { LFTConfig } from '../config/index.js';\n\ninterface SupabaseProjectResponse {\n id: string;\n name: string;\n organization_id: string;\n region: string;\n status: string;\n}\n\ninterface SupabaseApiKey {\n name: string;\n api_key: string;\n}\n\nexport interface SupabaseProjectResult {\n url: string;\n anonKey: string;\n serviceKey: string;\n}\n\nconst SUPABASE_API_URL = 'https://api.supabase.com/v1';\n\nasync function waitForProjectReady(\n projectId: string,\n token: string,\n maxAttempts = 60\n): Promise<void> {\n const spinner = createSpinner('Provisionando base de datos (esto puede tomar ~2 minutos)...').start();\n\n for (let i = 0; i < maxAttempts; i++) {\n const response = await fetch(`${SUPABASE_API_URL}/projects/${projectId}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (response.ok) {\n const project = (await response.json()) as SupabaseProjectResponse;\n if (project.status === 'ACTIVE_HEALTHY') {\n spinner.succeed('Base de datos provisionada');\n return;\n }\n }\n\n // Esperar 5 segundos antes del siguiente intento\n await new Promise((resolve) => setTimeout(resolve, 5000));\n spinner.text = `Provisionando base de datos... (${Math.floor((i + 1) * 5 / 60)}min ${((i + 1) * 5) % 60}s)`;\n }\n\n spinner.fail('Timeout esperando a que el proyecto esté listo');\n throw new Error('Timeout: el proyecto de Supabase no se activó a tiempo');\n}\n\nasync function getProjectApiKeys(\n projectId: string,\n token: string\n): Promise<{ anonKey: string; serviceKey: string }> {\n const response = await fetch(`${SUPABASE_API_URL}/projects/${projectId}/api-keys`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (!response.ok) {\n throw new Error('No se pudieron obtener las API keys de Supabase');\n }\n\n const keys = (await response.json()) as SupabaseApiKey[];\n\n const anonKey = keys.find((k) => k.name === 'anon')?.api_key;\n const serviceKey = keys.find((k) => k.name === 'service_role')?.api_key;\n\n if (!anonKey || !serviceKey) {\n throw new Error('No se encontraron las API keys necesarias');\n }\n\n return { anonKey, serviceKey };\n}\n\nasync function findExistingProject(\n projectName: string,\n token: string\n): Promise<SupabaseProjectResponse | null> {\n const response = await fetch(`${SUPABASE_API_URL}/projects`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (!response.ok) return null;\n\n const projects = (await response.json()) as SupabaseProjectResponse[];\n return projects.find((p) => p.name === projectName) || null;\n}\n\nexport async function createSupabaseProject(\n projectName: string,\n config: LFTConfig\n): Promise<SupabaseProjectResult> {\n const token = config.credentials.supabase.accessToken;\n const orgId = config.credentials.supabase.organizationId;\n const region = config.defaults.supabaseRegion;\n\n // Verificar si ya existe un proyecto con ese nombre\n const existing = await findExistingProject(projectName, token);\n if (existing) {\n logger.success(`Supabase: ${projectName} (ya existe)`);\n const { anonKey, serviceKey } = await getProjectApiKeys(existing.id, token);\n return {\n url: `https://${existing.id}.supabase.co`,\n anonKey,\n serviceKey,\n };\n }\n\n // Generar una contraseña segura para la base de datos\n const dbPassword = generateSecurePassword();\n\n // Crear proyecto\n const project = await withSpinner(\n 'Creando proyecto en Supabase...',\n async () => {\n const response = await fetch(`${SUPABASE_API_URL}/projects`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n name: projectName,\n organization_id: orgId,\n region,\n plan: 'free',\n db_pass: dbPassword,\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Error creando proyecto Supabase: ${error}`);\n }\n\n return response.json() as Promise<SupabaseProjectResponse>;\n }\n );\n\n // Esperar a que el proyecto esté listo\n await waitForProjectReady(project.id, token);\n\n // Obtener API keys\n const { anonKey, serviceKey } = await getProjectApiKeys(project.id, token);\n\n const projectUrl = `https://${project.id}.supabase.co`;\n\n return {\n url: projectUrl,\n anonKey,\n serviceKey,\n };\n}\n\nfunction generateSecurePassword(): string {\n const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';\n let password = '';\n for (let i = 0; i < 32; i++) {\n password += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return password;\n}\n","export interface ValidationResult {\n valid: boolean;\n error?: string;\n}\n\nexport function validateProjectName(name: string): ValidationResult {\n // Verificar que no esté vacío\n if (!name || name.trim() === '') {\n return { valid: false, error: 'El nombre del proyecto no puede estar vacío' };\n }\n\n // Verificar caracteres válidos (solo letras, números, guiones y guiones bajos)\n const validPattern = /^[a-zA-Z0-9_-]+$/;\n if (!validPattern.test(name)) {\n return {\n valid: false,\n error: 'El nombre solo puede contener letras, números, guiones (-) y guiones bajos (_)',\n };\n }\n\n // Verificar que no empiece con guión o número\n if (/^[-_0-9]/.test(name)) {\n return {\n valid: false,\n error: 'El nombre debe empezar con una letra',\n };\n }\n\n // Verificar longitud\n if (name.length < 2) {\n return { valid: false, error: 'El nombre debe tener al menos 2 caracteres' };\n }\n\n if (name.length > 50) {\n return { valid: false, error: 'El nombre no puede tener más de 50 caracteres' };\n }\n\n // No verificamos si el directorio existe porque ahora manejamos recursos existentes\n return { valid: true };\n}\n\nexport function generateJiraKey(projectName: string): string {\n // Generar key de Jira: máximo 10 caracteres, solo mayúsculas\n const cleaned = projectName\n .replace(/[^a-zA-Z0-9]/g, '')\n .toUpperCase()\n .slice(0, 10);\n\n return cleaned || 'PROJ';\n}\n","import { withSpinner } from '../ui/spinner.js';\nimport { logger } from '../ui/logger.js';\nimport { generateJiraKey } from '../utils/validation.js';\nimport type { LFTConfig } from '../config/index.js';\n\ninterface JiraProjectResponse {\n id: string;\n key: string;\n name: string;\n self: string;\n}\n\nasync function findExistingJiraProject(\n projectName: string,\n auth: string,\n domain: string\n): Promise<JiraProjectResponse | null> {\n const response = await fetch(\n `https://${domain}/rest/api/3/project/search?query=${encodeURIComponent(projectName)}`,\n {\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n }\n );\n\n if (!response.ok) return null;\n\n const data = (await response.json()) as { values: JiraProjectResponse[] };\n return data.values.find((p) => p.name === projectName) || null;\n}\n\nexport async function createJiraProject(\n projectName: string,\n config: LFTConfig\n): Promise<string> {\n const { email, apiToken, domain } = config.credentials.jira;\n const auth = Buffer.from(`${email}:${apiToken}`).toString('base64');\n const projectKey = generateJiraKey(projectName);\n\n // Verificar si ya existe un proyecto con ese nombre\n const existing = await findExistingJiraProject(projectName, auth, domain);\n if (existing) {\n logger.success(`Jira: ${existing.key} (ya existe)`);\n return `https://${domain}/browse/${existing.key}`;\n }\n\n return withSpinner(\n 'Creando proyecto en Jira...',\n async () => {\n // Primero obtener el leadAccountId del usuario actual\n const meResponse = await fetch(`https://${domain}/rest/api/3/myself`, {\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!meResponse.ok) {\n throw new Error('No se pudo obtener información del usuario de Jira');\n }\n\n const me = (await meResponse.json()) as { accountId: string };\n\n // Crear el proyecto\n const response = await fetch(`https://${domain}/rest/api/3/project`, {\n method: 'POST',\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n key: projectKey,\n name: projectName,\n projectTypeKey: 'software',\n projectTemplateKey: 'com.pyxis.greenhopper.jira:gh-simplified-agility-scrum',\n leadAccountId: me.accountId,\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n\n // Si el key ya existe, intentar con un sufijo numérico\n if (error.includes('project key')) {\n const newKey = `${projectKey}${Date.now().toString().slice(-4)}`;\n const retryResponse = await fetch(`https://${domain}/rest/api/3/project`, {\n method: 'POST',\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n key: newKey,\n name: projectName,\n projectTypeKey: 'software',\n projectTemplateKey: 'com.pyxis.greenhopper.jira:gh-simplified-agility-scrum',\n leadAccountId: me.accountId,\n }),\n });\n\n if (!retryResponse.ok) {\n throw new Error(`Error creando proyecto Jira: ${await retryResponse.text()}`);\n }\n\n const project = (await retryResponse.json()) as JiraProjectResponse;\n return `https://${domain}/browse/${project.key}`;\n }\n\n throw new Error(`Error creando proyecto Jira: ${error}`);\n }\n\n const project = (await response.json()) as JiraProjectResponse;\n return `https://${domain}/browse/${project.key}`;\n },\n `Proyecto Jira: ${projectKey}`\n );\n}\n\nexport async function validateJiraCredentials(\n email: string,\n apiToken: string,\n domain: string\n): Promise<boolean> {\n try {\n const auth = Buffer.from(`${email}:${apiToken}`).toString('base64');\n const response = await fetch(`https://${domain}/rest/api/3/myself`, {\n headers: { Authorization: `Basic ${auth}` },\n });\n return response.ok;\n } catch {\n return false;\n }\n}\n","import { execa } from 'execa';\nimport { existsSync } from 'fs';\nimport path from 'path';\nimport { withSpinner } from '../ui/spinner.js';\nimport { logger } from '../ui/logger.js';\n\nexport async function scaffoldNextJs(\n projectName: string,\n projectPath: string\n): Promise<void> {\n const targetDir = path.join(process.cwd(), projectName);\n\n if (existsSync(targetDir)) {\n logger.success(`Next.js: ${projectName} (ya existe)`);\n return;\n }\n\n await withSpinner(\n 'Inicializando proyecto Next.js con template LFT...',\n async () => {\n await execa('npx', [\n '@create-lft-app/nextjs@latest',\n projectName,\n '--cwd', process.cwd(),\n ], {\n cwd: process.cwd(),\n stdio: 'pipe',\n });\n },\n `Next.js: ${projectName}`\n );\n}\n","import { execa } from 'execa';\nimport { withSpinner } from '../ui/spinner.js';\n\nexport async function installDependencies(projectPath: string): Promise<void> {\n await withSpinner(\n 'Instalando dependencias...',\n async () => {\n await execa('npm', ['install'], {\n cwd: projectPath,\n stdio: 'pipe',\n });\n },\n 'Dependencias instaladas'\n );\n}\n","import { writeFile, readFile, appendFile } from 'fs/promises';\nimport path from 'path';\nimport { withSpinner } from '../ui/spinner.js';\n\ninterface SupabaseKeys {\n url: string;\n anonKey: string;\n serviceKey: string;\n}\n\nexport async function createEnvFile(\n projectPath: string,\n supabaseKeys: SupabaseKeys\n): Promise<void> {\n await withSpinner(\n 'Creando archivo .env.local...',\n async () => {\n const envContent = `# Supabase\nNEXT_PUBLIC_SUPABASE_URL=${supabaseKeys.url}\nNEXT_PUBLIC_SUPABASE_ANON_KEY=${supabaseKeys.anonKey}\nSUPABASE_SERVICE_ROLE_KEY=${supabaseKeys.serviceKey}\n`;\n\n await writeFile(\n path.join(projectPath, '.env.local'),\n envContent\n );\n\n // Asegurar que .env.local esté en .gitignore\n const gitignorePath = path.join(projectPath, '.gitignore');\n try {\n const gitignore = await readFile(gitignorePath, 'utf-8');\n if (!gitignore.includes('.env.local')) {\n await appendFile(gitignorePath, '\\n# Environment variables\\n.env.local\\n.env*.local\\n');\n }\n } catch {\n // Si no existe .gitignore, crearlo\n await writeFile(gitignorePath, '# Environment variables\\n.env.local\\n.env*.local\\n');\n }\n },\n 'Archivo .env.local creado con credenciales de Supabase'\n );\n}\n","import { simpleGit } from 'simple-git';\nimport { withSpinner } from '../ui/spinner.js';\n\nexport async function setupGit(\n projectPath: string,\n remoteUrl: string\n): Promise<void> {\n const git = simpleGit(projectPath);\n\n await withSpinner(\n 'Configurando Git...',\n async () => {\n // Verificar si ya es un repo git\n const isRepo = await git.checkIsRepo();\n\n if (!isRepo) {\n await git.init();\n }\n\n // Agregar remote\n try {\n await git.addRemote('origin', remoteUrl);\n } catch {\n // Remote ya existe, actualizarlo\n await git.remote(['set-url', 'origin', remoteUrl]);\n }\n\n // Stage todos los archivos\n await git.add('.');\n\n // Commit inicial\n await git.commit('Initial commit - created with create-lft-app', {\n '--author': 'create-lft-app <noreply@lft.dev>',\n });\n\n // Renombrar branch a main si es necesario\n try {\n await git.branch(['-M', 'main']);\n } catch {\n // Ya está en main\n }\n\n // Push\n await git.push(['--set-upstream', 'origin', 'main']);\n },\n 'Git configurado y código pusheado'\n );\n}\n","import boxen from 'boxen';\nimport chalk from 'chalk';\n\nexport function showBanner(): void {\n const title = chalk.bold.cyan('create-lft-app');\n const version = chalk.gray('v1.0.0');\n const description = chalk.white('Scaffolding para proyectos LFT');\n\n const banner = boxen(`${title} ${version}\\n${description}`, {\n padding: 1,\n margin: 1,\n borderStyle: 'round',\n borderColor: 'cyan',\n });\n\n console.log(banner);\n}\n\nexport function showSuccessBanner(projectName: string, urls: {\n github?: string;\n supabase?: string;\n jira?: string;\n}): void {\n const lines = [\n chalk.green.bold(`Proyecto \"${projectName}\" creado exitosamente!`),\n '',\n chalk.white('Directorio:') + ` ./${projectName}`,\n '',\n ];\n\n if (urls.github || urls.supabase || urls.jira) {\n lines.push(chalk.white('Enlaces:'));\n if (urls.github) {\n lines.push(` ${chalk.gray('GitHub:')} ${chalk.cyan(urls.github)}`);\n }\n if (urls.supabase) {\n lines.push(` ${chalk.gray('Supabase:')} ${chalk.cyan(urls.supabase)}`);\n }\n if (urls.jira) {\n lines.push(` ${chalk.gray('Jira:')} ${chalk.cyan(urls.jira)}`);\n }\n lines.push('');\n }\n\n lines.push(chalk.white('Siguiente pasos:'));\n lines.push(` ${chalk.cyan('cd')} ${projectName}`);\n lines.push(` ${chalk.cyan('npm run dev')}`);\n\n const banner = boxen(lines.join('\\n'), {\n padding: 1,\n margin: 1,\n borderStyle: 'round',\n borderColor: 'green',\n });\n\n console.log(banner);\n}\n"],"mappings":"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","names":["path","path","anonKey","serviceKey","project","existsSync","existsSync","execa","execa","path","path","chalk","path"]}
|
|
1
|
+
{"version":3,"sources":["../../bin/cli.ts","../../src/index.ts","../../src/config/index.ts","../../src/services/github.ts","../../src/ui/spinner.ts","../../src/ui/logger.ts","../../src/services/supabase.ts","../../src/utils/validation.ts","../../src/services/jira.ts","../../src/steps/scaffold-nextjs.ts","../../src/steps/install-deps.ts","../../src/steps/create-env.ts","../../src/steps/setup-git.ts","../../src/ui/banner.ts"],"sourcesContent":["import { program } from 'commander';\nimport { createProject } from '../src/index.js';\nimport { hasConfig, setConfigPath, getConfigPath } from '../src/config/index.js';\nimport { showBanner } from '../src/ui/banner.js';\nimport { logger } from '../src/ui/logger.js';\n\nconst packageJson = {\n name: 'create-lft-app',\n version: '1.0.0',\n description: 'CLI para crear proyectos LFT con Next.js, GitHub, Supabase y Jira',\n};\n\nprogram\n .name('create-lft-app')\n .description(packageJson.description)\n .version(packageJson.version);\n\nprogram\n .argument('[project-name]', 'Nombre del proyecto a crear')\n .requiredOption('--config <path>', 'Ruta al archivo de credenciales JSON (obligatorio)')\n .option('--skip-github', 'No crear repositorio en GitHub')\n .option('--skip-supabase', 'No crear proyecto en Supabase')\n .option('--skip-jira', 'No crear workspace en Jira')\n .option('--skip-git', 'No inicializar git ni hacer push')\n .option('-y, --yes', 'Aceptar todas las confirmaciones')\n .action(async (projectName: string | undefined, options) => {\n showBanner();\n\n // Establecer la ruta del archivo de config\n setConfigPath(options.config);\n\n if (!projectName) {\n logger.error('Debes especificar un nombre de proyecto');\n logger.info('Uso: create-lft-app <nombre-proyecto> --config <ruta-credenciales>');\n process.exit(1);\n }\n\n if (!await hasConfig()) {\n const configPath = getConfigPath();\n logger.error(`No se encontró el archivo de credenciales: ${configPath}`);\n logger.newLine();\n logger.info('El archivo debe tener el siguiente formato:');\n logger.newLine();\n console.log(`{\n \"github\": {\n \"token\": \"ghp_xxxxxxxxxxxx\",\n \"username\": \"tu-usuario\",\n \"org\": \"tu-organizacion\"\n },\n \"supabase\": {\n \"token\": \"sbp_xxxxxxxxxxxx\",\n \"orgId\": \"tu-org-id\",\n \"region\": \"us-east-1\"\n },\n \"jira\": {\n \"email\": \"tu-email@ejemplo.com\",\n \"token\": \"ATATT3xxxxxxxxxxxx\",\n \"domain\": \"tu-dominio.atlassian.net\"\n }\n}`);\n process.exit(1);\n }\n\n try {\n await createProject(projectName, {\n skipGithub: options.skipGithub,\n skipSupabase: options.skipSupabase,\n skipJira: options.skipJira,\n skipGit: options.skipGit,\n autoConfirm: options.yes,\n });\n } catch (error) {\n if (error instanceof Error) {\n logger.error(error.message);\n }\n process.exit(1);\n }\n });\n\nprogram.parse();\n","import path from 'path';\nimport { existsSync } from 'fs';\nimport { confirm } from '@inquirer/prompts';\nimport { loadConfig, hasConfig } from './config/index.js';\nimport { createGitHubRepo, checkGitHubRepoExists } from './services/github.js';\nimport { createSupabaseProject, checkSupabaseProjectExists, getExistingSupabaseKeys } from './services/supabase.js';\nimport { createJiraProject, checkJiraProjectExists } from './services/jira.js';\nimport { scaffoldNextJs } from './steps/scaffold-nextjs.js';\nimport { installDependencies } from './steps/install-deps.js';\nimport { createEnvFile } from './steps/create-env.js';\nimport { setupGit } from './steps/setup-git.js';\nimport { logger } from './ui/logger.js';\nimport { showSuccessBanner } from './ui/banner.js';\nimport { validateProjectName } from './utils/validation.js';\n\nexport interface CreateProjectOptions {\n skipGithub?: boolean;\n skipSupabase?: boolean;\n skipJira?: boolean;\n skipGit?: boolean;\n autoConfirm?: boolean;\n}\n\ninterface ResourceStatus {\n github: { exists: boolean; url?: string };\n supabase: { exists: boolean; url?: string; projectId?: string };\n jira: { exists: boolean; url?: string };\n nextjs: { exists: boolean };\n}\n\nexport async function createProject(\n projectName: string,\n options: CreateProjectOptions = {}\n): Promise<void> {\n // Validar nombre del proyecto\n const validation = validateProjectName(projectName);\n if (!validation.valid) {\n throw new Error(validation.error);\n }\n\n const projectPath = path.resolve(process.cwd(), projectName);\n\n // Cargar configuración\n if (!await hasConfig()) {\n logger.warning('No se encontró configuración. Ejecuta \"create-lft-app config\" primero.');\n throw new Error('Configuración no encontrada');\n }\n\n const config = await loadConfig();\n\n // Verificar recursos existentes en paralelo\n logger.info('Verificando recursos existentes...');\n\n const status: ResourceStatus = {\n github: { exists: false },\n supabase: { exists: false },\n jira: { exists: false },\n nextjs: { exists: existsSync(projectPath) },\n };\n\n const checks: Promise<void>[] = [];\n\n if (!options.skipGithub) {\n checks.push(\n checkGitHubRepoExists(projectName, config).then((result) => {\n status.github = result;\n })\n );\n }\n\n if (!options.skipSupabase) {\n checks.push(\n checkSupabaseProjectExists(projectName, config).then((result) => {\n status.supabase = result;\n })\n );\n }\n\n if (!options.skipJira) {\n checks.push(\n checkJiraProjectExists(projectName, config).then((result) => {\n status.jira = result;\n })\n );\n }\n\n await Promise.all(checks);\n\n // Mostrar resumen con estado\n logger.newLine();\n logger.title('Resumen de recursos:');\n logger.newLine();\n\n const resources = [];\n const owner = config.defaults.githubOrg || config.credentials.github.username;\n\n if (!options.skipGithub) {\n const githubStatus = status.github.exists ? '✓ ya existe' : 'se creará';\n resources.push({\n label: 'GitHub',\n value: `${owner}/${projectName} (${githubStatus})`\n });\n }\n\n if (!options.skipSupabase) {\n const supabaseStatus = status.supabase.exists ? '✓ ya existe' : 'se creará';\n resources.push({\n label: 'Supabase',\n value: `${projectName} en ${config.defaults.supabaseRegion} (${supabaseStatus})`\n });\n }\n\n if (!options.skipJira) {\n const jiraStatus = status.jira.exists ? '✓ ya existe' : 'se creará';\n resources.push({\n label: 'Jira',\n value: `Proyecto \"${projectName}\" (${jiraStatus})`\n });\n }\n\n const nextjsStatus = status.nextjs.exists ? '✓ ya existe' : 'se creará';\n resources.push({\n label: 'Next.js',\n value: `App Router + TypeScript + Tailwind (${nextjsStatus})`\n });\n\n logger.table(resources);\n logger.newLine();\n\n // Confirmar\n if (!options.autoConfirm) {\n const shouldContinue = await confirm({\n message: '¿Continuar?',\n default: true,\n });\n\n if (!shouldContinue) {\n logger.info('Operación cancelada');\n return;\n }\n }\n\n logger.newLine();\n logger.divider();\n logger.newLine();\n\n const urls: { github?: string; supabase?: string; jira?: string } = {};\n let supabaseKeys: { url: string; anonKey: string; serviceKey: string } | undefined;\n\n // Crear recursos externos secuencialmente (para evitar conflictos de spinners)\n if (!options.skipGithub) {\n if (status.github.exists) {\n urls.github = status.github.url;\n logger.success(`GitHub: ${owner}/${projectName} (ya existe)`);\n } else {\n urls.github = await createGitHubRepo(projectName, config);\n }\n }\n\n if (!options.skipJira) {\n if (status.jira.exists) {\n urls.jira = status.jira.url;\n logger.success(`Jira: ${projectName} (ya existe)`);\n } else {\n urls.jira = await createJiraProject(projectName, config);\n }\n }\n\n if (!options.skipSupabase) {\n if (status.supabase.exists && status.supabase.projectId) {\n const result = await getExistingSupabaseKeys(status.supabase.projectId, config);\n urls.supabase = result.url;\n supabaseKeys = result;\n } else {\n const result = await createSupabaseProject(projectName, config);\n urls.supabase = result.url;\n supabaseKeys = result;\n }\n }\n\n // Scaffold Next.js (incluye template LFT completo)\n if (!status.nextjs.exists) {\n await scaffoldNextJs(projectName, projectPath);\n await installDependencies(projectPath);\n } else {\n logger.success(`Next.js: ${projectName} (ya existe)`);\n }\n\n // Crear .env.local\n if (supabaseKeys) {\n await createEnvFile(projectPath, supabaseKeys);\n }\n\n // Setup git\n if (!options.skipGit && urls.github && !status.nextjs.exists) {\n await setupGit(projectPath, urls.github);\n }\n\n // Mostrar resumen final\n logger.newLine();\n showSuccessBanner(projectName, urls);\n}\n","import { existsSync, readFileSync } from 'fs';\n\n// Variable para almacenar la ruta del config\nlet configFilePath: string = '';\n\ninterface ConfigFile {\n github: {\n token: string;\n username: string;\n org: string;\n };\n supabase: {\n token: string;\n orgId: string;\n region: string;\n };\n jira: {\n email: string;\n token: string;\n domain: string;\n };\n}\n\n// Cache de la configuración cargada\nlet loadedConfig: ConfigFile | null = null;\n\n/**\n * Establece la ruta del archivo de configuración\n */\nexport function setConfigPath(path: string): void {\n configFilePath = path;\n loadedConfig = null;\n}\n\n/**\n * Obtiene la ruta actual del archivo de configuración\n */\nexport function getConfigPath(): string {\n return configFilePath;\n}\n\n/**\n * Carga la configuración desde el archivo JSON\n */\nfunction loadConfigFromFile(): ConfigFile | null {\n if (loadedConfig) {\n return loadedConfig;\n }\n\n if (!configFilePath || !existsSync(configFilePath)) {\n return null;\n }\n\n try {\n const content = readFileSync(configFilePath, 'utf-8');\n const parsed = JSON.parse(content);\n\n loadedConfig = {\n github: {\n token: parsed.github?.token || '',\n username: parsed.github?.username || '',\n org: parsed.github?.org || '',\n },\n supabase: {\n token: parsed.supabase?.token || '',\n orgId: parsed.supabase?.orgId || '',\n region: parsed.supabase?.region || 'us-east-1',\n },\n jira: {\n email: parsed.jira?.email || '',\n token: parsed.jira?.token || '',\n domain: parsed.jira?.domain || '',\n },\n };\n\n return loadedConfig;\n } catch {\n return null;\n }\n}\n\nexport interface LFTConfig {\n version: string;\n credentials: {\n github: {\n token: string;\n username: string;\n };\n supabase: {\n accessToken: string;\n organizationId: string;\n };\n jira: {\n email: string;\n apiToken: string;\n domain: string;\n };\n };\n defaults: {\n githubOrg?: string;\n supabaseRegion: string;\n jiraProjectType: string;\n };\n}\n\nexport async function loadConfig(): Promise<LFTConfig> {\n const config = loadConfigFromFile();\n\n if (!config) {\n throw new Error('No se pudo cargar la configuración');\n }\n\n return {\n version: '1.0.0',\n credentials: {\n github: {\n token: config.github.token,\n username: config.github.username,\n },\n supabase: {\n accessToken: config.supabase.token,\n organizationId: config.supabase.orgId,\n },\n jira: {\n email: config.jira.email,\n apiToken: config.jira.token,\n domain: config.jira.domain,\n },\n },\n defaults: {\n githubOrg: config.github.org || undefined,\n supabaseRegion: config.supabase.region,\n jiraProjectType: 'software',\n },\n };\n}\n\nexport async function hasConfig(): Promise<boolean> {\n const config = loadConfigFromFile();\n\n if (!config) {\n return false;\n }\n\n return (\n config.github.token !== '' &&\n config.supabase.token !== '' &&\n config.jira.token !== ''\n );\n}\n","import { Octokit } from 'octokit';\nimport { withSpinner } from '../ui/spinner.js';\nimport type { LFTConfig } from '../config/index.js';\n\nexport async function createGitHubRepo(\n projectName: string,\n config: LFTConfig\n): Promise<string> {\n const octokit = new Octokit({ auth: config.credentials.github.token });\n const org = config.defaults.githubOrg;\n const owner = org || config.credentials.github.username;\n\n return withSpinner(\n 'Creando repositorio en GitHub...',\n async () => {\n let repo;\n\n if (org) {\n repo = await octokit.rest.repos.createInOrg({\n org,\n name: projectName,\n private: true,\n auto_init: false,\n description: `Proyecto ${projectName} creado con create-lft-app`,\n });\n } else {\n repo = await octokit.rest.repos.createForAuthenticatedUser({\n name: projectName,\n private: true,\n auto_init: false,\n description: `Proyecto ${projectName} creado con create-lft-app`,\n });\n }\n\n return repo.data.ssh_url;\n },\n `GitHub: ${owner}/${projectName}`\n );\n}\n\nexport async function validateGitHubToken(token: string): Promise<{ valid: boolean; username?: string }> {\n try {\n const octokit = new Octokit({ auth: token });\n const { data } = await octokit.rest.users.getAuthenticated();\n return { valid: true, username: data.login };\n } catch {\n return { valid: false };\n }\n}\n\nexport async function checkGitHubRepoExists(\n projectName: string,\n config: LFTConfig\n): Promise<{ exists: boolean; url?: string }> {\n const octokit = new Octokit({ auth: config.credentials.github.token });\n const org = config.defaults.githubOrg;\n const owner = org || config.credentials.github.username;\n\n try {\n const existing = await octokit.rest.repos.get({\n owner,\n repo: projectName,\n });\n return { exists: true, url: existing.data.ssh_url };\n } catch {\n return { exists: false };\n }\n}\n","import ora, { type Ora } from 'ora';\n\nexport function createSpinner(text: string): Ora {\n return ora({\n text,\n spinner: 'dots',\n });\n}\n\nexport async function withSpinner<T>(\n text: string,\n fn: () => Promise<T>,\n successText?: string\n): Promise<T> {\n const spinner = createSpinner(text).start();\n\n try {\n const result = await fn();\n spinner.succeed(successText || text);\n return result;\n } catch (error) {\n spinner.fail();\n throw error;\n }\n}\n","import chalk from 'chalk';\n\nexport const logger = {\n info: (message: string) => {\n console.log(chalk.blue('ℹ'), message);\n },\n\n success: (message: string) => {\n console.log(chalk.green('✔'), message);\n },\n\n warning: (message: string) => {\n console.log(chalk.yellow('⚠'), message);\n },\n\n error: (message: string) => {\n console.log(chalk.red('✖'), message);\n },\n\n step: (step: number, total: number, message: string) => {\n console.log(chalk.cyan(`[${step}/${total}]`), message);\n },\n\n newLine: () => {\n console.log();\n },\n\n divider: () => {\n console.log(chalk.gray('─'.repeat(50)));\n },\n\n title: (message: string) => {\n console.log(chalk.bold.white(message));\n },\n\n subtitle: (message: string) => {\n console.log(chalk.gray(message));\n },\n\n link: (label: string, url: string) => {\n console.log(` ${chalk.gray(label + ':')} ${chalk.cyan.underline(url)}`);\n },\n\n list: (items: string[]) => {\n items.forEach((item) => {\n console.log(chalk.gray(' •'), item);\n });\n },\n\n table: (rows: Array<{ label: string; value: string }>) => {\n const maxLabelLength = Math.max(...rows.map((r) => r.label.length));\n rows.forEach(({ label, value }) => {\n const paddedLabel = label.padEnd(maxLabelLength);\n console.log(` ${chalk.gray(paddedLabel)} ${value}`);\n });\n },\n};\n","import { withSpinner, createSpinner } from '../ui/spinner.js';\nimport { logger } from '../ui/logger.js';\nimport type { LFTConfig } from '../config/index.js';\n\ninterface SupabaseProjectResponse {\n id: string;\n name: string;\n organization_id: string;\n region: string;\n status: string;\n}\n\ninterface SupabaseApiKey {\n name: string;\n api_key: string;\n}\n\nexport interface SupabaseProjectResult {\n url: string;\n anonKey: string;\n serviceKey: string;\n}\n\nconst SUPABASE_API_URL = 'https://api.supabase.com/v1';\n\nasync function waitForProjectReady(\n projectId: string,\n token: string,\n maxAttempts = 60\n): Promise<void> {\n const spinner = createSpinner('Provisionando base de datos (esto puede tomar ~2 minutos)...').start();\n\n for (let i = 0; i < maxAttempts; i++) {\n const response = await fetch(`${SUPABASE_API_URL}/projects/${projectId}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (response.ok) {\n const project = (await response.json()) as SupabaseProjectResponse;\n if (project.status === 'ACTIVE_HEALTHY') {\n spinner.succeed('Base de datos provisionada');\n return;\n }\n }\n\n // Esperar 5 segundos antes del siguiente intento\n await new Promise((resolve) => setTimeout(resolve, 5000));\n spinner.text = `Provisionando base de datos... (${Math.floor((i + 1) * 5 / 60)}min ${((i + 1) * 5) % 60}s)`;\n }\n\n spinner.fail('Timeout esperando a que el proyecto esté listo');\n throw new Error('Timeout: el proyecto de Supabase no se activó a tiempo');\n}\n\nasync function getProjectApiKeys(\n projectId: string,\n token: string\n): Promise<{ anonKey: string; serviceKey: string }> {\n const response = await fetch(`${SUPABASE_API_URL}/projects/${projectId}/api-keys`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (!response.ok) {\n throw new Error('No se pudieron obtener las API keys de Supabase');\n }\n\n const keys = (await response.json()) as SupabaseApiKey[];\n\n const anonKey = keys.find((k) => k.name === 'anon')?.api_key;\n const serviceKey = keys.find((k) => k.name === 'service_role')?.api_key;\n\n if (!anonKey || !serviceKey) {\n throw new Error('No se encontraron las API keys necesarias');\n }\n\n return { anonKey, serviceKey };\n}\n\nasync function findExistingProject(\n projectName: string,\n token: string\n): Promise<SupabaseProjectResponse | null> {\n const response = await fetch(`${SUPABASE_API_URL}/projects`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (!response.ok) return null;\n\n const projects = (await response.json()) as SupabaseProjectResponse[];\n return projects.find((p) => p.name === projectName) || null;\n}\n\nexport async function createSupabaseProject(\n projectName: string,\n config: LFTConfig\n): Promise<SupabaseProjectResult> {\n const token = config.credentials.supabase.accessToken;\n const orgId = config.credentials.supabase.organizationId;\n const region = config.defaults.supabaseRegion;\n\n // Verificar si ya existe un proyecto con ese nombre\n const existing = await findExistingProject(projectName, token);\n if (existing) {\n logger.success(`Supabase: ${projectName} (ya existe)`);\n const { anonKey, serviceKey } = await getProjectApiKeys(existing.id, token);\n return {\n url: `https://${existing.id}.supabase.co`,\n anonKey,\n serviceKey,\n };\n }\n\n // Generar una contraseña segura para la base de datos\n const dbPassword = generateSecurePassword();\n\n // Crear proyecto\n const project = await withSpinner(\n 'Creando proyecto en Supabase...',\n async () => {\n const response = await fetch(`${SUPABASE_API_URL}/projects`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n name: projectName,\n organization_id: orgId,\n region,\n plan: 'free',\n db_pass: dbPassword,\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Error creando proyecto Supabase: ${error}`);\n }\n\n return response.json() as Promise<SupabaseProjectResponse>;\n }\n );\n\n // Esperar a que el proyecto esté listo\n await waitForProjectReady(project.id, token);\n\n // Obtener API keys\n const { anonKey, serviceKey } = await getProjectApiKeys(project.id, token);\n\n const projectUrl = `https://${project.id}.supabase.co`;\n\n return {\n url: projectUrl,\n anonKey,\n serviceKey,\n };\n}\n\nfunction generateSecurePassword(): string {\n const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';\n let password = '';\n for (let i = 0; i < 32; i++) {\n password += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return password;\n}\n\nexport async function checkSupabaseProjectExists(\n projectName: string,\n config: LFTConfig\n): Promise<{ exists: boolean; url?: string; projectId?: string }> {\n const token = config.credentials.supabase.accessToken;\n const existing = await findExistingProject(projectName, token);\n\n if (existing) {\n return { exists: true, url: `https://${existing.id}.supabase.co`, projectId: existing.id };\n }\n return { exists: false };\n}\n\nexport async function getExistingSupabaseKeys(\n projectId: string,\n config: LFTConfig\n): Promise<SupabaseProjectResult> {\n return withSpinner(\n 'Obteniendo credenciales de Supabase...',\n async () => {\n const token = config.credentials.supabase.accessToken;\n const { anonKey, serviceKey } = await getProjectApiKeys(projectId, token);\n return {\n url: `https://${projectId}.supabase.co`,\n anonKey,\n serviceKey,\n };\n },\n 'Supabase: credenciales obtenidas'\n );\n}\n","export interface ValidationResult {\n valid: boolean;\n error?: string;\n}\n\nexport function validateProjectName(name: string): ValidationResult {\n // Verificar que no esté vacío\n if (!name || name.trim() === '') {\n return { valid: false, error: 'El nombre del proyecto no puede estar vacío' };\n }\n\n // Verificar caracteres válidos (solo letras, números, guiones y guiones bajos)\n const validPattern = /^[a-zA-Z0-9_-]+$/;\n if (!validPattern.test(name)) {\n return {\n valid: false,\n error: 'El nombre solo puede contener letras, números, guiones (-) y guiones bajos (_)',\n };\n }\n\n // Verificar que no empiece con guión o número\n if (/^[-_0-9]/.test(name)) {\n return {\n valid: false,\n error: 'El nombre debe empezar con una letra',\n };\n }\n\n // Verificar longitud\n if (name.length < 2) {\n return { valid: false, error: 'El nombre debe tener al menos 2 caracteres' };\n }\n\n if (name.length > 50) {\n return { valid: false, error: 'El nombre no puede tener más de 50 caracteres' };\n }\n\n // No verificamos si el directorio existe porque ahora manejamos recursos existentes\n return { valid: true };\n}\n\nexport function generateJiraKey(projectName: string): string {\n // Generar key de Jira: máximo 10 caracteres, solo mayúsculas\n const cleaned = projectName\n .replace(/[^a-zA-Z0-9]/g, '')\n .toUpperCase()\n .slice(0, 10);\n\n return cleaned || 'PROJ';\n}\n","import { withSpinner } from '../ui/spinner.js';\nimport { generateJiraKey } from '../utils/validation.js';\nimport type { LFTConfig } from '../config/index.js';\n\ninterface JiraProjectResponse {\n id: string;\n key: string;\n name: string;\n self: string;\n}\n\nasync function findExistingJiraProject(\n projectName: string,\n auth: string,\n domain: string\n): Promise<JiraProjectResponse | null> {\n const response = await fetch(\n `https://${domain}/rest/api/3/project/search?query=${encodeURIComponent(projectName)}`,\n {\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n }\n );\n\n if (!response.ok) return null;\n\n const data = (await response.json()) as { values: JiraProjectResponse[] };\n return data.values.find((p) => p.name === projectName) || null;\n}\n\nexport async function createJiraProject(\n projectName: string,\n config: LFTConfig\n): Promise<string> {\n const { email, apiToken, domain } = config.credentials.jira;\n const auth = Buffer.from(`${email}:${apiToken}`).toString('base64');\n const projectKey = generateJiraKey(projectName);\n\n return withSpinner(\n 'Creando proyecto en Jira...',\n async () => {\n // Primero obtener el leadAccountId del usuario actual\n const meResponse = await fetch(`https://${domain}/rest/api/3/myself`, {\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!meResponse.ok) {\n throw new Error('No se pudo obtener información del usuario de Jira');\n }\n\n const me = (await meResponse.json()) as { accountId: string };\n\n // Crear el proyecto\n const response = await fetch(`https://${domain}/rest/api/3/project`, {\n method: 'POST',\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n key: projectKey,\n name: projectName,\n projectTypeKey: 'software',\n projectTemplateKey: 'com.pyxis.greenhopper.jira:gh-simplified-agility-scrum',\n leadAccountId: me.accountId,\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n\n // Si el key ya existe, intentar con un sufijo numérico\n if (error.includes('project key')) {\n const newKey = `${projectKey}${Date.now().toString().slice(-4)}`;\n const retryResponse = await fetch(`https://${domain}/rest/api/3/project`, {\n method: 'POST',\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n key: newKey,\n name: projectName,\n projectTypeKey: 'software',\n projectTemplateKey: 'com.pyxis.greenhopper.jira:gh-simplified-agility-scrum',\n leadAccountId: me.accountId,\n }),\n });\n\n if (!retryResponse.ok) {\n throw new Error(`Error creando proyecto Jira: ${await retryResponse.text()}`);\n }\n\n const project = (await retryResponse.json()) as JiraProjectResponse;\n return `https://${domain}/browse/${project.key}`;\n }\n\n throw new Error(`Error creando proyecto Jira: ${error}`);\n }\n\n const project = (await response.json()) as JiraProjectResponse;\n return `https://${domain}/browse/${project.key}`;\n },\n `Proyecto Jira: ${projectKey}`\n );\n}\n\nexport async function validateJiraCredentials(\n email: string,\n apiToken: string,\n domain: string\n): Promise<boolean> {\n try {\n const auth = Buffer.from(`${email}:${apiToken}`).toString('base64');\n const response = await fetch(`https://${domain}/rest/api/3/myself`, {\n headers: { Authorization: `Basic ${auth}` },\n });\n return response.ok;\n } catch {\n return false;\n }\n}\n\nexport async function checkJiraProjectExists(\n projectName: string,\n config: LFTConfig\n): Promise<{ exists: boolean; url?: string }> {\n const { email, apiToken, domain } = config.credentials.jira;\n const auth = Buffer.from(`${email}:${apiToken}`).toString('base64');\n\n const existing = await findExistingJiraProject(projectName, auth, domain);\n if (existing) {\n return { exists: true, url: `https://${domain}/browse/${existing.key}` };\n }\n return { exists: false };\n}\n","import { execa } from 'execa';\nimport { existsSync } from 'fs';\nimport path from 'path';\nimport { withSpinner } from '../ui/spinner.js';\nimport { logger } from '../ui/logger.js';\n\nexport async function scaffoldNextJs(\n projectName: string,\n projectPath: string\n): Promise<void> {\n const targetDir = path.join(process.cwd(), projectName);\n\n if (existsSync(targetDir)) {\n logger.success(`Next.js: ${projectName} (ya existe)`);\n return;\n }\n\n await withSpinner(\n 'Inicializando proyecto Next.js con template LFT...',\n async () => {\n await execa('npx', [\n '@create-lft-app/nextjs@latest',\n projectName,\n '--cwd', process.cwd(),\n ], {\n cwd: process.cwd(),\n stdio: 'pipe',\n });\n },\n `Next.js: ${projectName}`\n );\n}\n","import { execa } from 'execa';\nimport { withSpinner } from '../ui/spinner.js';\n\nexport async function installDependencies(projectPath: string): Promise<void> {\n await withSpinner(\n 'Instalando dependencias...',\n async () => {\n await execa('npm', ['install'], {\n cwd: projectPath,\n stdio: 'pipe',\n });\n },\n 'Dependencias instaladas'\n );\n}\n","import { writeFile, readFile, appendFile } from 'fs/promises';\nimport path from 'path';\nimport { withSpinner } from '../ui/spinner.js';\n\ninterface SupabaseKeys {\n url: string;\n anonKey: string;\n serviceKey: string;\n}\n\nexport async function createEnvFile(\n projectPath: string,\n supabaseKeys: SupabaseKeys\n): Promise<void> {\n await withSpinner(\n 'Creando archivo .env.local...',\n async () => {\n const envContent = `# Supabase\nNEXT_PUBLIC_SUPABASE_URL=${supabaseKeys.url}\nNEXT_PUBLIC_SUPABASE_ANON_KEY=${supabaseKeys.anonKey}\nSUPABASE_SERVICE_ROLE_KEY=${supabaseKeys.serviceKey}\n`;\n\n await writeFile(\n path.join(projectPath, '.env.local'),\n envContent\n );\n\n // Asegurar que .env.local esté en .gitignore\n const gitignorePath = path.join(projectPath, '.gitignore');\n try {\n const gitignore = await readFile(gitignorePath, 'utf-8');\n if (!gitignore.includes('.env.local')) {\n await appendFile(gitignorePath, '\\n# Environment variables\\n.env.local\\n.env*.local\\n');\n }\n } catch {\n // Si no existe .gitignore, crearlo\n await writeFile(gitignorePath, '# Environment variables\\n.env.local\\n.env*.local\\n');\n }\n },\n 'Archivo .env.local creado con credenciales de Supabase'\n );\n}\n","import { simpleGit } from 'simple-git';\nimport { withSpinner } from '../ui/spinner.js';\n\nexport async function setupGit(\n projectPath: string,\n remoteUrl: string\n): Promise<void> {\n const git = simpleGit(projectPath);\n\n await withSpinner(\n 'Configurando Git...',\n async () => {\n // Inicializar git (simple-git requiere que el directorio sea un repo)\n await git.init();\n\n // Agregar remote\n try {\n await git.addRemote('origin', remoteUrl);\n } catch {\n // Remote ya existe, actualizarlo\n await git.remote(['set-url', 'origin', remoteUrl]);\n }\n\n // Stage todos los archivos\n await git.add('.');\n\n // Commit inicial\n await git.commit('Initial commit - created with create-lft-app', {\n '--author': 'create-lft-app <noreply@lft.dev>',\n });\n\n // Renombrar branch a main si es necesario\n try {\n await git.branch(['-M', 'main']);\n } catch {\n // Ya está en main\n }\n\n // Push\n await git.push(['--set-upstream', 'origin', 'main']);\n },\n 'Git configurado y código pusheado'\n );\n}\n","import boxen from 'boxen';\nimport chalk from 'chalk';\nimport { readFileSync } from 'fs';\nimport { fileURLToPath } from 'url';\nimport { dirname, join } from 'path';\n\nfunction getVersion(): string {\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n const pkgPath = join(__dirname, '..', '..', 'package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n return pkg.version || '0.0.0';\n } catch {\n return '0.0.0';\n }\n}\n\nfunction getKangarooArt(): string {\n const c = chalk.cyan;\n return c(` \n ██ ███ \n ██████████ \n █ ████████ \n ███████████ \n ███████████ \n █████ █████ \n ██ █ █████ \n ██ █ ████ \n ██ ██ ███ ██ \n ███ ███████████████ \n ███ ██ █████ \n ████ █ ██████ \n ████ ██ ██████████ \n ██ ██ ███████████ \n ██ █ █ ███ ██████████ \n ██ █ ███ █████████████ \n ██ ██ █ █████████ \n ██ ██ █████████ ██████ \n ██ ███ █████ ███████ ██████ \n ███ ██████ ██████████████████ \n ███ █████████████ ████████████████ \n ██ ██████████████████████████████████ \n ██ █████ ███████████████ \n ██ ███████ \n ██ ████████ \n ██ █████ ███ \n ██ ██ ██████ ██ \n ██ ███ ███████ ██ \n ███ █ ████ ██ ███████ ██ \n ███ ██ █████████ ███ ███████ ██ \n ███ ██ ███████████████ ████████ ██ \n █████ ██ ███████ ██ ██ ████ ██ \n █ ███ ███ ████████ ██ ███ ██ █ \n ███ ████ ████ ███████ ██ ██ ██ ██ \n ███ ████████ ███████ ██ ██ ██ ██ \n █████ █████████ ██ █ █ █ \n ███████████████ ███ ██ ██ █ \n ██████ ██ ███ ██ █████ \n ████ ███ ██ █████████ \n █████ ████ ██████████ ███ \n ████ ████ █████████ \n ███████ \n ██ \n`);\n}\n\nexport function showBanner(): void {\n const version = getVersion();\n const title = chalk.bold.cyan('create-lft-app');\n const versionText = chalk.gray(`v${version}`);\n const description = chalk.white('Scaffolding para proyectos LFT');\n\n console.log(getKangarooArt());\n\n const banner = boxen(`${title} ${versionText}\\n${description}`, {\n padding: 1,\n margin: 1,\n borderStyle: 'round',\n borderColor: 'cyan',\n });\n\n console.log(banner);\n}\n\nexport function showSuccessBanner(projectName: string, urls: {\n github?: string;\n supabase?: string;\n jira?: string;\n}): void {\n const lines = [\n chalk.green.bold(`Proyecto \"${projectName}\" creado exitosamente!`),\n '',\n chalk.white('Directorio:') + ` ./${projectName}`,\n '',\n ];\n\n if (urls.github || urls.supabase || urls.jira) {\n lines.push(chalk.white('Enlaces:'));\n if (urls.github) {\n lines.push(` ${chalk.gray('GitHub:')} ${chalk.cyan(urls.github)}`);\n }\n if (urls.supabase) {\n lines.push(` ${chalk.gray('Supabase:')} ${chalk.cyan(urls.supabase)}`);\n }\n if (urls.jira) {\n lines.push(` ${chalk.gray('Jira:')} ${chalk.cyan(urls.jira)}`);\n }\n lines.push('');\n }\n\n lines.push(chalk.white('Siguiente pasos:'));\n lines.push(` ${chalk.cyan('cd')} ${projectName}`);\n lines.push(` ${chalk.cyan('npm run dev')}`);\n\n const banner = boxen(lines.join('\\n'), {\n padding: 1,\n margin: 1,\n borderStyle: 'round',\n borderColor: 'green',\n });\n\n console.log(banner);\n}\n"],"mappings":"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","names":["path","existsSync","path","anonKey","serviceKey","project","existsSync","existsSync","execa","execa","path","path","chalk","readFileSync","__filename","__dirname","path","existsSync"]}
|
package/dist/src/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import path3 from "path";
|
|
5
|
+
import { existsSync as existsSync3 } from "fs";
|
|
5
6
|
import { confirm } from "@inquirer/prompts";
|
|
6
7
|
|
|
7
8
|
// src/config/index.ts
|
|
@@ -100,6 +101,51 @@ async function withSpinner(text, fn, successText) {
|
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
|
|
104
|
+
// src/services/github.ts
|
|
105
|
+
async function createGitHubRepo(projectName, config) {
|
|
106
|
+
const octokit = new Octokit({ auth: config.credentials.github.token });
|
|
107
|
+
const org = config.defaults.githubOrg;
|
|
108
|
+
const owner = org || config.credentials.github.username;
|
|
109
|
+
return withSpinner(
|
|
110
|
+
"Creando repositorio en GitHub...",
|
|
111
|
+
async () => {
|
|
112
|
+
let repo;
|
|
113
|
+
if (org) {
|
|
114
|
+
repo = await octokit.rest.repos.createInOrg({
|
|
115
|
+
org,
|
|
116
|
+
name: projectName,
|
|
117
|
+
private: true,
|
|
118
|
+
auto_init: false,
|
|
119
|
+
description: `Proyecto ${projectName} creado con create-lft-app`
|
|
120
|
+
});
|
|
121
|
+
} else {
|
|
122
|
+
repo = await octokit.rest.repos.createForAuthenticatedUser({
|
|
123
|
+
name: projectName,
|
|
124
|
+
private: true,
|
|
125
|
+
auto_init: false,
|
|
126
|
+
description: `Proyecto ${projectName} creado con create-lft-app`
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return repo.data.ssh_url;
|
|
130
|
+
},
|
|
131
|
+
`GitHub: ${owner}/${projectName}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
async function checkGitHubRepoExists(projectName, config) {
|
|
135
|
+
const octokit = new Octokit({ auth: config.credentials.github.token });
|
|
136
|
+
const org = config.defaults.githubOrg;
|
|
137
|
+
const owner = org || config.credentials.github.username;
|
|
138
|
+
try {
|
|
139
|
+
const existing = await octokit.rest.repos.get({
|
|
140
|
+
owner,
|
|
141
|
+
repo: projectName
|
|
142
|
+
});
|
|
143
|
+
return { exists: true, url: existing.data.ssh_url };
|
|
144
|
+
} catch {
|
|
145
|
+
return { exists: false };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
103
149
|
// src/ui/logger.ts
|
|
104
150
|
import chalk from "chalk";
|
|
105
151
|
var logger = {
|
|
@@ -147,46 +193,6 @@ var logger = {
|
|
|
147
193
|
}
|
|
148
194
|
};
|
|
149
195
|
|
|
150
|
-
// src/services/github.ts
|
|
151
|
-
async function createGitHubRepo(projectName, config) {
|
|
152
|
-
const octokit = new Octokit({ auth: config.credentials.github.token });
|
|
153
|
-
const org = config.defaults.githubOrg;
|
|
154
|
-
const owner = org || config.credentials.github.username;
|
|
155
|
-
try {
|
|
156
|
-
const existing = await octokit.rest.repos.get({
|
|
157
|
-
owner,
|
|
158
|
-
repo: projectName
|
|
159
|
-
});
|
|
160
|
-
logger.success(`GitHub: ${owner}/${projectName} (ya existe)`);
|
|
161
|
-
return existing.data.html_url;
|
|
162
|
-
} catch {
|
|
163
|
-
}
|
|
164
|
-
return withSpinner(
|
|
165
|
-
"Creando repositorio en GitHub...",
|
|
166
|
-
async () => {
|
|
167
|
-
let repo;
|
|
168
|
-
if (org) {
|
|
169
|
-
repo = await octokit.rest.repos.createInOrg({
|
|
170
|
-
org,
|
|
171
|
-
name: projectName,
|
|
172
|
-
private: true,
|
|
173
|
-
auto_init: false,
|
|
174
|
-
description: `Proyecto ${projectName} creado con create-lft-app`
|
|
175
|
-
});
|
|
176
|
-
} else {
|
|
177
|
-
repo = await octokit.rest.repos.createForAuthenticatedUser({
|
|
178
|
-
name: projectName,
|
|
179
|
-
private: true,
|
|
180
|
-
auto_init: false,
|
|
181
|
-
description: `Proyecto ${projectName} creado con create-lft-app`
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
return repo.data.html_url;
|
|
185
|
-
},
|
|
186
|
-
`GitHub: ${owner}/${projectName}`
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
196
|
// src/services/supabase.ts
|
|
191
197
|
var SUPABASE_API_URL = "https://api.supabase.com/v1";
|
|
192
198
|
async function waitForProjectReady(projectId, token, maxAttempts = 60) {
|
|
@@ -287,6 +293,29 @@ function generateSecurePassword() {
|
|
|
287
293
|
}
|
|
288
294
|
return password;
|
|
289
295
|
}
|
|
296
|
+
async function checkSupabaseProjectExists(projectName, config) {
|
|
297
|
+
const token = config.credentials.supabase.accessToken;
|
|
298
|
+
const existing = await findExistingProject(projectName, token);
|
|
299
|
+
if (existing) {
|
|
300
|
+
return { exists: true, url: `https://${existing.id}.supabase.co`, projectId: existing.id };
|
|
301
|
+
}
|
|
302
|
+
return { exists: false };
|
|
303
|
+
}
|
|
304
|
+
async function getExistingSupabaseKeys(projectId, config) {
|
|
305
|
+
return withSpinner(
|
|
306
|
+
"Obteniendo credenciales de Supabase...",
|
|
307
|
+
async () => {
|
|
308
|
+
const token = config.credentials.supabase.accessToken;
|
|
309
|
+
const { anonKey, serviceKey } = await getProjectApiKeys(projectId, token);
|
|
310
|
+
return {
|
|
311
|
+
url: `https://${projectId}.supabase.co`,
|
|
312
|
+
anonKey,
|
|
313
|
+
serviceKey
|
|
314
|
+
};
|
|
315
|
+
},
|
|
316
|
+
"Supabase: credenciales obtenidas"
|
|
317
|
+
);
|
|
318
|
+
}
|
|
290
319
|
|
|
291
320
|
// src/utils/validation.ts
|
|
292
321
|
function validateProjectName(name) {
|
|
@@ -338,11 +367,6 @@ async function createJiraProject(projectName, config) {
|
|
|
338
367
|
const { email, apiToken, domain } = config.credentials.jira;
|
|
339
368
|
const auth = Buffer.from(`${email}:${apiToken}`).toString("base64");
|
|
340
369
|
const projectKey = generateJiraKey(projectName);
|
|
341
|
-
const existing = await findExistingJiraProject(projectName, auth, domain);
|
|
342
|
-
if (existing) {
|
|
343
|
-
logger.success(`Jira: ${existing.key} (ya existe)`);
|
|
344
|
-
return `https://${domain}/browse/${existing.key}`;
|
|
345
|
-
}
|
|
346
370
|
return withSpinner(
|
|
347
371
|
"Creando proyecto en Jira...",
|
|
348
372
|
async () => {
|
|
@@ -402,6 +426,15 @@ async function createJiraProject(projectName, config) {
|
|
|
402
426
|
`Proyecto Jira: ${projectKey}`
|
|
403
427
|
);
|
|
404
428
|
}
|
|
429
|
+
async function checkJiraProjectExists(projectName, config) {
|
|
430
|
+
const { email, apiToken, domain } = config.credentials.jira;
|
|
431
|
+
const auth = Buffer.from(`${email}:${apiToken}`).toString("base64");
|
|
432
|
+
const existing = await findExistingJiraProject(projectName, auth, domain);
|
|
433
|
+
if (existing) {
|
|
434
|
+
return { exists: true, url: `https://${domain}/browse/${existing.key}` };
|
|
435
|
+
}
|
|
436
|
+
return { exists: false };
|
|
437
|
+
}
|
|
405
438
|
|
|
406
439
|
// src/steps/scaffold-nextjs.ts
|
|
407
440
|
import { execa } from "execa";
|
|
@@ -482,10 +515,7 @@ async function setupGit(projectPath, remoteUrl) {
|
|
|
482
515
|
await withSpinner(
|
|
483
516
|
"Configurando Git...",
|
|
484
517
|
async () => {
|
|
485
|
-
|
|
486
|
-
if (!isRepo) {
|
|
487
|
-
await git.init();
|
|
488
|
-
}
|
|
518
|
+
await git.init();
|
|
489
519
|
try {
|
|
490
520
|
await git.addRemote("origin", remoteUrl);
|
|
491
521
|
} catch {
|
|
@@ -552,25 +582,72 @@ async function createProject(projectName, options = {}) {
|
|
|
552
582
|
throw new Error("Configuraci\xF3n no encontrada");
|
|
553
583
|
}
|
|
554
584
|
const config = await loadConfig();
|
|
585
|
+
logger.info("Verificando recursos existentes...");
|
|
586
|
+
const status = {
|
|
587
|
+
github: { exists: false },
|
|
588
|
+
supabase: { exists: false },
|
|
589
|
+
jira: { exists: false },
|
|
590
|
+
nextjs: { exists: existsSync3(projectPath) }
|
|
591
|
+
};
|
|
592
|
+
const checks = [];
|
|
593
|
+
if (!options.skipGithub) {
|
|
594
|
+
checks.push(
|
|
595
|
+
checkGitHubRepoExists(projectName, config).then((result) => {
|
|
596
|
+
status.github = result;
|
|
597
|
+
})
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
if (!options.skipSupabase) {
|
|
601
|
+
checks.push(
|
|
602
|
+
checkSupabaseProjectExists(projectName, config).then((result) => {
|
|
603
|
+
status.supabase = result;
|
|
604
|
+
})
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
if (!options.skipJira) {
|
|
608
|
+
checks.push(
|
|
609
|
+
checkJiraProjectExists(projectName, config).then((result) => {
|
|
610
|
+
status.jira = result;
|
|
611
|
+
})
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
await Promise.all(checks);
|
|
555
615
|
logger.newLine();
|
|
556
|
-
logger.title("Resumen de recursos
|
|
616
|
+
logger.title("Resumen de recursos:");
|
|
557
617
|
logger.newLine();
|
|
558
618
|
const resources = [];
|
|
619
|
+
const owner = config.defaults.githubOrg || config.credentials.github.username;
|
|
559
620
|
if (!options.skipGithub) {
|
|
560
|
-
|
|
621
|
+
const githubStatus = status.github.exists ? "\u2713 ya existe" : "se crear\xE1";
|
|
622
|
+
resources.push({
|
|
623
|
+
label: "GitHub",
|
|
624
|
+
value: `${owner}/${projectName} (${githubStatus})`
|
|
625
|
+
});
|
|
561
626
|
}
|
|
562
627
|
if (!options.skipSupabase) {
|
|
563
|
-
|
|
628
|
+
const supabaseStatus = status.supabase.exists ? "\u2713 ya existe" : "se crear\xE1";
|
|
629
|
+
resources.push({
|
|
630
|
+
label: "Supabase",
|
|
631
|
+
value: `${projectName} en ${config.defaults.supabaseRegion} (${supabaseStatus})`
|
|
632
|
+
});
|
|
564
633
|
}
|
|
565
634
|
if (!options.skipJira) {
|
|
566
|
-
|
|
635
|
+
const jiraStatus = status.jira.exists ? "\u2713 ya existe" : "se crear\xE1";
|
|
636
|
+
resources.push({
|
|
637
|
+
label: "Jira",
|
|
638
|
+
value: `Proyecto "${projectName}" (${jiraStatus})`
|
|
639
|
+
});
|
|
567
640
|
}
|
|
568
|
-
|
|
641
|
+
const nextjsStatus = status.nextjs.exists ? "\u2713 ya existe" : "se crear\xE1";
|
|
642
|
+
resources.push({
|
|
643
|
+
label: "Next.js",
|
|
644
|
+
value: `App Router + TypeScript + Tailwind (${nextjsStatus})`
|
|
645
|
+
});
|
|
569
646
|
logger.table(resources);
|
|
570
647
|
logger.newLine();
|
|
571
648
|
if (!options.autoConfirm) {
|
|
572
649
|
const shouldContinue = await confirm({
|
|
573
|
-
message: "\xBFContinuar
|
|
650
|
+
message: "\xBFContinuar?",
|
|
574
651
|
default: true
|
|
575
652
|
});
|
|
576
653
|
if (!shouldContinue) {
|
|
@@ -583,36 +660,43 @@ async function createProject(projectName, options = {}) {
|
|
|
583
660
|
logger.newLine();
|
|
584
661
|
const urls = {};
|
|
585
662
|
let supabaseKeys;
|
|
586
|
-
const externalTasks = [];
|
|
587
663
|
if (!options.skipGithub) {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
664
|
+
if (status.github.exists) {
|
|
665
|
+
urls.github = status.github.url;
|
|
666
|
+
logger.success(`GitHub: ${owner}/${projectName} (ya existe)`);
|
|
667
|
+
} else {
|
|
668
|
+
urls.github = await createGitHubRepo(projectName, config);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (!options.skipJira) {
|
|
672
|
+
if (status.jira.exists) {
|
|
673
|
+
urls.jira = status.jira.url;
|
|
674
|
+
logger.success(`Jira: ${projectName} (ya existe)`);
|
|
675
|
+
} else {
|
|
676
|
+
urls.jira = await createJiraProject(projectName, config);
|
|
677
|
+
}
|
|
593
678
|
}
|
|
594
679
|
if (!options.skipSupabase) {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
680
|
+
if (status.supabase.exists && status.supabase.projectId) {
|
|
681
|
+
const result = await getExistingSupabaseKeys(status.supabase.projectId, config);
|
|
682
|
+
urls.supabase = result.url;
|
|
683
|
+
supabaseKeys = result;
|
|
684
|
+
} else {
|
|
685
|
+
const result = await createSupabaseProject(projectName, config);
|
|
686
|
+
urls.supabase = result.url;
|
|
687
|
+
supabaseKeys = result;
|
|
688
|
+
}
|
|
601
689
|
}
|
|
602
|
-
if (!
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
);
|
|
690
|
+
if (!status.nextjs.exists) {
|
|
691
|
+
await scaffoldNextJs(projectName, projectPath);
|
|
692
|
+
await installDependencies(projectPath);
|
|
693
|
+
} else {
|
|
694
|
+
logger.success(`Next.js: ${projectName} (ya existe)`);
|
|
608
695
|
}
|
|
609
|
-
await Promise.all(externalTasks);
|
|
610
|
-
await scaffoldNextJs(projectName, projectPath);
|
|
611
|
-
await installDependencies(projectPath);
|
|
612
696
|
if (supabaseKeys) {
|
|
613
697
|
await createEnvFile(projectPath, supabaseKeys);
|
|
614
698
|
}
|
|
615
|
-
if (!options.skipGit && urls.github) {
|
|
699
|
+
if (!options.skipGit && urls.github && !status.nextjs.exists) {
|
|
616
700
|
await setupGit(projectPath, urls.github);
|
|
617
701
|
}
|
|
618
702
|
logger.newLine();
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/index.ts","../../src/config/index.ts","../../src/services/github.ts","../../src/ui/spinner.ts","../../src/ui/logger.ts","../../src/services/supabase.ts","../../src/utils/validation.ts","../../src/services/jira.ts","../../src/steps/scaffold-nextjs.ts","../../src/steps/install-deps.ts","../../src/steps/create-env.ts","../../src/steps/setup-git.ts","../../src/ui/banner.ts"],"sourcesContent":["import path from 'path';\nimport { confirm } from '@inquirer/prompts';\nimport { loadConfig, hasConfig } from './config/index.js';\nimport { createGitHubRepo } from './services/github.js';\nimport { createSupabaseProject } from './services/supabase.js';\nimport { createJiraProject } from './services/jira.js';\nimport { scaffoldNextJs } from './steps/scaffold-nextjs.js';\nimport { installDependencies } from './steps/install-deps.js';\nimport { createEnvFile } from './steps/create-env.js';\nimport { setupGit } from './steps/setup-git.js';\nimport { logger } from './ui/logger.js';\nimport { showSuccessBanner } from './ui/banner.js';\nimport { validateProjectName } from './utils/validation.js';\n\nexport interface CreateProjectOptions {\n skipGithub?: boolean;\n skipSupabase?: boolean;\n skipJira?: boolean;\n skipGit?: boolean;\n autoConfirm?: boolean;\n}\n\nexport async function createProject(\n projectName: string,\n options: CreateProjectOptions = {}\n): Promise<void> {\n // Validar nombre del proyecto\n const validation = validateProjectName(projectName);\n if (!validation.valid) {\n throw new Error(validation.error);\n }\n\n // Verificar que el directorio no exista\n const projectPath = path.resolve(process.cwd(), projectName);\n\n // Cargar configuración\n if (!await hasConfig()) {\n logger.warning('No se encontró configuración. Ejecuta \"create-lft-app config\" primero.');\n throw new Error('Configuración no encontrada');\n }\n\n const config = await loadConfig();\n\n // Mostrar resumen\n logger.newLine();\n logger.title('Resumen de recursos a crear:');\n logger.newLine();\n\n const resources = [];\n if (!options.skipGithub) {\n resources.push({ label: 'GitHub', value: `${config.defaults.githubOrg || config.credentials.github.username}/${projectName} (privado)` });\n }\n if (!options.skipSupabase) {\n resources.push({ label: 'Supabase', value: `${projectName} en ${config.defaults.supabaseRegion}` });\n }\n if (!options.skipJira) {\n resources.push({ label: 'Jira', value: `Proyecto \"${projectName}\" en ${config.credentials.jira.domain}` });\n }\n resources.push({ label: 'Next.js', value: 'App Router + TypeScript + Tailwind + Dashboard' });\n\n logger.table(resources);\n logger.newLine();\n\n // Confirmar\n if (!options.autoConfirm) {\n const shouldContinue = await confirm({\n message: '¿Continuar con la creación?',\n default: true,\n });\n\n if (!shouldContinue) {\n logger.info('Operación cancelada');\n return;\n }\n }\n\n logger.newLine();\n logger.divider();\n logger.newLine();\n\n const urls: { github?: string; supabase?: string; jira?: string } = {};\n let supabaseKeys: { url: string; anonKey: string; serviceKey: string } | undefined;\n\n // Crear recursos externos en paralelo\n const externalTasks: Promise<void>[] = [];\n\n if (!options.skipGithub) {\n externalTasks.push(\n createGitHubRepo(projectName, config).then((url) => {\n urls.github = url;\n })\n );\n }\n\n if (!options.skipSupabase) {\n externalTasks.push(\n createSupabaseProject(projectName, config).then((result) => {\n urls.supabase = result.url;\n supabaseKeys = result;\n })\n );\n }\n\n if (!options.skipJira) {\n externalTasks.push(\n createJiraProject(projectName, config).then((url) => {\n urls.jira = url;\n })\n );\n }\n\n // Esperar a que terminen los recursos externos\n await Promise.all(externalTasks);\n\n // Scaffold Next.js (incluye template LFT completo)\n await scaffoldNextJs(projectName, projectPath);\n\n // Instalar dependencias\n await installDependencies(projectPath);\n\n // Crear .env.local\n if (supabaseKeys) {\n await createEnvFile(projectPath, supabaseKeys);\n }\n\n // Setup git\n if (!options.skipGit && urls.github) {\n await setupGit(projectPath, urls.github);\n }\n\n // Mostrar resumen final\n logger.newLine();\n showSuccessBanner(projectName, urls);\n}\n","import { existsSync, readFileSync } from 'fs';\n\n// Variable para almacenar la ruta del config\nlet configFilePath: string = '';\n\ninterface ConfigFile {\n github: {\n token: string;\n username: string;\n org: string;\n };\n supabase: {\n token: string;\n orgId: string;\n region: string;\n };\n jira: {\n email: string;\n token: string;\n domain: string;\n };\n}\n\n// Cache de la configuración cargada\nlet loadedConfig: ConfigFile | null = null;\n\n/**\n * Establece la ruta del archivo de configuración\n */\nexport function setConfigPath(path: string): void {\n configFilePath = path;\n loadedConfig = null;\n}\n\n/**\n * Obtiene la ruta actual del archivo de configuración\n */\nexport function getConfigPath(): string {\n return configFilePath;\n}\n\n/**\n * Carga la configuración desde el archivo JSON\n */\nfunction loadConfigFromFile(): ConfigFile | null {\n if (loadedConfig) {\n return loadedConfig;\n }\n\n if (!configFilePath || !existsSync(configFilePath)) {\n return null;\n }\n\n try {\n const content = readFileSync(configFilePath, 'utf-8');\n const parsed = JSON.parse(content);\n\n loadedConfig = {\n github: {\n token: parsed.github?.token || '',\n username: parsed.github?.username || '',\n org: parsed.github?.org || '',\n },\n supabase: {\n token: parsed.supabase?.token || '',\n orgId: parsed.supabase?.orgId || '',\n region: parsed.supabase?.region || 'us-east-1',\n },\n jira: {\n email: parsed.jira?.email || '',\n token: parsed.jira?.token || '',\n domain: parsed.jira?.domain || '',\n },\n };\n\n return loadedConfig;\n } catch {\n return null;\n }\n}\n\nexport interface LFTConfig {\n version: string;\n credentials: {\n github: {\n token: string;\n username: string;\n };\n supabase: {\n accessToken: string;\n organizationId: string;\n };\n jira: {\n email: string;\n apiToken: string;\n domain: string;\n };\n };\n defaults: {\n githubOrg?: string;\n supabaseRegion: string;\n jiraProjectType: string;\n };\n}\n\nexport async function loadConfig(): Promise<LFTConfig> {\n const config = loadConfigFromFile();\n\n if (!config) {\n throw new Error('No se pudo cargar la configuración');\n }\n\n return {\n version: '1.0.0',\n credentials: {\n github: {\n token: config.github.token,\n username: config.github.username,\n },\n supabase: {\n accessToken: config.supabase.token,\n organizationId: config.supabase.orgId,\n },\n jira: {\n email: config.jira.email,\n apiToken: config.jira.token,\n domain: config.jira.domain,\n },\n },\n defaults: {\n githubOrg: config.github.org || undefined,\n supabaseRegion: config.supabase.region,\n jiraProjectType: 'software',\n },\n };\n}\n\nexport async function hasConfig(): Promise<boolean> {\n const config = loadConfigFromFile();\n\n if (!config) {\n return false;\n }\n\n return (\n config.github.token !== '' &&\n config.supabase.token !== '' &&\n config.jira.token !== ''\n );\n}\n","import { Octokit } from 'octokit';\nimport { withSpinner } from '../ui/spinner.js';\nimport { logger } from '../ui/logger.js';\nimport type { LFTConfig } from '../config/index.js';\n\nexport async function createGitHubRepo(\n projectName: string,\n config: LFTConfig\n): Promise<string> {\n const octokit = new Octokit({ auth: config.credentials.github.token });\n const org = config.defaults.githubOrg;\n const owner = org || config.credentials.github.username;\n\n // Verificar si el repo ya existe\n try {\n const existing = await octokit.rest.repos.get({\n owner,\n repo: projectName,\n });\n logger.success(`GitHub: ${owner}/${projectName} (ya existe)`);\n return existing.data.html_url;\n } catch {\n // No existe, continuar con la creación\n }\n\n return withSpinner(\n 'Creando repositorio en GitHub...',\n async () => {\n let repo;\n\n if (org) {\n repo = await octokit.rest.repos.createInOrg({\n org,\n name: projectName,\n private: true,\n auto_init: false,\n description: `Proyecto ${projectName} creado con create-lft-app`,\n });\n } else {\n repo = await octokit.rest.repos.createForAuthenticatedUser({\n name: projectName,\n private: true,\n auto_init: false,\n description: `Proyecto ${projectName} creado con create-lft-app`,\n });\n }\n\n return repo.data.html_url;\n },\n `GitHub: ${owner}/${projectName}`\n );\n}\n\nexport async function validateGitHubToken(token: string): Promise<{ valid: boolean; username?: string }> {\n try {\n const octokit = new Octokit({ auth: token });\n const { data } = await octokit.rest.users.getAuthenticated();\n return { valid: true, username: data.login };\n } catch {\n return { valid: false };\n }\n}\n","import ora, { type Ora } from 'ora';\n\nexport function createSpinner(text: string): Ora {\n return ora({\n text,\n spinner: 'dots',\n });\n}\n\nexport async function withSpinner<T>(\n text: string,\n fn: () => Promise<T>,\n successText?: string\n): Promise<T> {\n const spinner = createSpinner(text).start();\n\n try {\n const result = await fn();\n spinner.succeed(successText || text);\n return result;\n } catch (error) {\n spinner.fail();\n throw error;\n }\n}\n","import chalk from 'chalk';\n\nexport const logger = {\n info: (message: string) => {\n console.log(chalk.blue('ℹ'), message);\n },\n\n success: (message: string) => {\n console.log(chalk.green('✔'), message);\n },\n\n warning: (message: string) => {\n console.log(chalk.yellow('⚠'), message);\n },\n\n error: (message: string) => {\n console.log(chalk.red('✖'), message);\n },\n\n step: (step: number, total: number, message: string) => {\n console.log(chalk.cyan(`[${step}/${total}]`), message);\n },\n\n newLine: () => {\n console.log();\n },\n\n divider: () => {\n console.log(chalk.gray('─'.repeat(50)));\n },\n\n title: (message: string) => {\n console.log(chalk.bold.white(message));\n },\n\n subtitle: (message: string) => {\n console.log(chalk.gray(message));\n },\n\n link: (label: string, url: string) => {\n console.log(` ${chalk.gray(label + ':')} ${chalk.cyan.underline(url)}`);\n },\n\n list: (items: string[]) => {\n items.forEach((item) => {\n console.log(chalk.gray(' •'), item);\n });\n },\n\n table: (rows: Array<{ label: string; value: string }>) => {\n const maxLabelLength = Math.max(...rows.map((r) => r.label.length));\n rows.forEach(({ label, value }) => {\n const paddedLabel = label.padEnd(maxLabelLength);\n console.log(` ${chalk.gray(paddedLabel)} ${value}`);\n });\n },\n};\n","import { withSpinner, createSpinner } from '../ui/spinner.js';\nimport { logger } from '../ui/logger.js';\nimport type { LFTConfig } from '../config/index.js';\n\ninterface SupabaseProjectResponse {\n id: string;\n name: string;\n organization_id: string;\n region: string;\n status: string;\n}\n\ninterface SupabaseApiKey {\n name: string;\n api_key: string;\n}\n\nexport interface SupabaseProjectResult {\n url: string;\n anonKey: string;\n serviceKey: string;\n}\n\nconst SUPABASE_API_URL = 'https://api.supabase.com/v1';\n\nasync function waitForProjectReady(\n projectId: string,\n token: string,\n maxAttempts = 60\n): Promise<void> {\n const spinner = createSpinner('Provisionando base de datos (esto puede tomar ~2 minutos)...').start();\n\n for (let i = 0; i < maxAttempts; i++) {\n const response = await fetch(`${SUPABASE_API_URL}/projects/${projectId}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (response.ok) {\n const project = (await response.json()) as SupabaseProjectResponse;\n if (project.status === 'ACTIVE_HEALTHY') {\n spinner.succeed('Base de datos provisionada');\n return;\n }\n }\n\n // Esperar 5 segundos antes del siguiente intento\n await new Promise((resolve) => setTimeout(resolve, 5000));\n spinner.text = `Provisionando base de datos... (${Math.floor((i + 1) * 5 / 60)}min ${((i + 1) * 5) % 60}s)`;\n }\n\n spinner.fail('Timeout esperando a que el proyecto esté listo');\n throw new Error('Timeout: el proyecto de Supabase no se activó a tiempo');\n}\n\nasync function getProjectApiKeys(\n projectId: string,\n token: string\n): Promise<{ anonKey: string; serviceKey: string }> {\n const response = await fetch(`${SUPABASE_API_URL}/projects/${projectId}/api-keys`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (!response.ok) {\n throw new Error('No se pudieron obtener las API keys de Supabase');\n }\n\n const keys = (await response.json()) as SupabaseApiKey[];\n\n const anonKey = keys.find((k) => k.name === 'anon')?.api_key;\n const serviceKey = keys.find((k) => k.name === 'service_role')?.api_key;\n\n if (!anonKey || !serviceKey) {\n throw new Error('No se encontraron las API keys necesarias');\n }\n\n return { anonKey, serviceKey };\n}\n\nasync function findExistingProject(\n projectName: string,\n token: string\n): Promise<SupabaseProjectResponse | null> {\n const response = await fetch(`${SUPABASE_API_URL}/projects`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (!response.ok) return null;\n\n const projects = (await response.json()) as SupabaseProjectResponse[];\n return projects.find((p) => p.name === projectName) || null;\n}\n\nexport async function createSupabaseProject(\n projectName: string,\n config: LFTConfig\n): Promise<SupabaseProjectResult> {\n const token = config.credentials.supabase.accessToken;\n const orgId = config.credentials.supabase.organizationId;\n const region = config.defaults.supabaseRegion;\n\n // Verificar si ya existe un proyecto con ese nombre\n const existing = await findExistingProject(projectName, token);\n if (existing) {\n logger.success(`Supabase: ${projectName} (ya existe)`);\n const { anonKey, serviceKey } = await getProjectApiKeys(existing.id, token);\n return {\n url: `https://${existing.id}.supabase.co`,\n anonKey,\n serviceKey,\n };\n }\n\n // Generar una contraseña segura para la base de datos\n const dbPassword = generateSecurePassword();\n\n // Crear proyecto\n const project = await withSpinner(\n 'Creando proyecto en Supabase...',\n async () => {\n const response = await fetch(`${SUPABASE_API_URL}/projects`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n name: projectName,\n organization_id: orgId,\n region,\n plan: 'free',\n db_pass: dbPassword,\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Error creando proyecto Supabase: ${error}`);\n }\n\n return response.json() as Promise<SupabaseProjectResponse>;\n }\n );\n\n // Esperar a que el proyecto esté listo\n await waitForProjectReady(project.id, token);\n\n // Obtener API keys\n const { anonKey, serviceKey } = await getProjectApiKeys(project.id, token);\n\n const projectUrl = `https://${project.id}.supabase.co`;\n\n return {\n url: projectUrl,\n anonKey,\n serviceKey,\n };\n}\n\nfunction generateSecurePassword(): string {\n const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';\n let password = '';\n for (let i = 0; i < 32; i++) {\n password += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return password;\n}\n","export interface ValidationResult {\n valid: boolean;\n error?: string;\n}\n\nexport function validateProjectName(name: string): ValidationResult {\n // Verificar que no esté vacío\n if (!name || name.trim() === '') {\n return { valid: false, error: 'El nombre del proyecto no puede estar vacío' };\n }\n\n // Verificar caracteres válidos (solo letras, números, guiones y guiones bajos)\n const validPattern = /^[a-zA-Z0-9_-]+$/;\n if (!validPattern.test(name)) {\n return {\n valid: false,\n error: 'El nombre solo puede contener letras, números, guiones (-) y guiones bajos (_)',\n };\n }\n\n // Verificar que no empiece con guión o número\n if (/^[-_0-9]/.test(name)) {\n return {\n valid: false,\n error: 'El nombre debe empezar con una letra',\n };\n }\n\n // Verificar longitud\n if (name.length < 2) {\n return { valid: false, error: 'El nombre debe tener al menos 2 caracteres' };\n }\n\n if (name.length > 50) {\n return { valid: false, error: 'El nombre no puede tener más de 50 caracteres' };\n }\n\n // No verificamos si el directorio existe porque ahora manejamos recursos existentes\n return { valid: true };\n}\n\nexport function generateJiraKey(projectName: string): string {\n // Generar key de Jira: máximo 10 caracteres, solo mayúsculas\n const cleaned = projectName\n .replace(/[^a-zA-Z0-9]/g, '')\n .toUpperCase()\n .slice(0, 10);\n\n return cleaned || 'PROJ';\n}\n","import { withSpinner } from '../ui/spinner.js';\nimport { logger } from '../ui/logger.js';\nimport { generateJiraKey } from '../utils/validation.js';\nimport type { LFTConfig } from '../config/index.js';\n\ninterface JiraProjectResponse {\n id: string;\n key: string;\n name: string;\n self: string;\n}\n\nasync function findExistingJiraProject(\n projectName: string,\n auth: string,\n domain: string\n): Promise<JiraProjectResponse | null> {\n const response = await fetch(\n `https://${domain}/rest/api/3/project/search?query=${encodeURIComponent(projectName)}`,\n {\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n }\n );\n\n if (!response.ok) return null;\n\n const data = (await response.json()) as { values: JiraProjectResponse[] };\n return data.values.find((p) => p.name === projectName) || null;\n}\n\nexport async function createJiraProject(\n projectName: string,\n config: LFTConfig\n): Promise<string> {\n const { email, apiToken, domain } = config.credentials.jira;\n const auth = Buffer.from(`${email}:${apiToken}`).toString('base64');\n const projectKey = generateJiraKey(projectName);\n\n // Verificar si ya existe un proyecto con ese nombre\n const existing = await findExistingJiraProject(projectName, auth, domain);\n if (existing) {\n logger.success(`Jira: ${existing.key} (ya existe)`);\n return `https://${domain}/browse/${existing.key}`;\n }\n\n return withSpinner(\n 'Creando proyecto en Jira...',\n async () => {\n // Primero obtener el leadAccountId del usuario actual\n const meResponse = await fetch(`https://${domain}/rest/api/3/myself`, {\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!meResponse.ok) {\n throw new Error('No se pudo obtener información del usuario de Jira');\n }\n\n const me = (await meResponse.json()) as { accountId: string };\n\n // Crear el proyecto\n const response = await fetch(`https://${domain}/rest/api/3/project`, {\n method: 'POST',\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n key: projectKey,\n name: projectName,\n projectTypeKey: 'software',\n projectTemplateKey: 'com.pyxis.greenhopper.jira:gh-simplified-agility-scrum',\n leadAccountId: me.accountId,\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n\n // Si el key ya existe, intentar con un sufijo numérico\n if (error.includes('project key')) {\n const newKey = `${projectKey}${Date.now().toString().slice(-4)}`;\n const retryResponse = await fetch(`https://${domain}/rest/api/3/project`, {\n method: 'POST',\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n key: newKey,\n name: projectName,\n projectTypeKey: 'software',\n projectTemplateKey: 'com.pyxis.greenhopper.jira:gh-simplified-agility-scrum',\n leadAccountId: me.accountId,\n }),\n });\n\n if (!retryResponse.ok) {\n throw new Error(`Error creando proyecto Jira: ${await retryResponse.text()}`);\n }\n\n const project = (await retryResponse.json()) as JiraProjectResponse;\n return `https://${domain}/browse/${project.key}`;\n }\n\n throw new Error(`Error creando proyecto Jira: ${error}`);\n }\n\n const project = (await response.json()) as JiraProjectResponse;\n return `https://${domain}/browse/${project.key}`;\n },\n `Proyecto Jira: ${projectKey}`\n );\n}\n\nexport async function validateJiraCredentials(\n email: string,\n apiToken: string,\n domain: string\n): Promise<boolean> {\n try {\n const auth = Buffer.from(`${email}:${apiToken}`).toString('base64');\n const response = await fetch(`https://${domain}/rest/api/3/myself`, {\n headers: { Authorization: `Basic ${auth}` },\n });\n return response.ok;\n } catch {\n return false;\n }\n}\n","import { execa } from 'execa';\nimport { existsSync } from 'fs';\nimport path from 'path';\nimport { withSpinner } from '../ui/spinner.js';\nimport { logger } from '../ui/logger.js';\n\nexport async function scaffoldNextJs(\n projectName: string,\n projectPath: string\n): Promise<void> {\n const targetDir = path.join(process.cwd(), projectName);\n\n if (existsSync(targetDir)) {\n logger.success(`Next.js: ${projectName} (ya existe)`);\n return;\n }\n\n await withSpinner(\n 'Inicializando proyecto Next.js con template LFT...',\n async () => {\n await execa('npx', [\n '@create-lft-app/nextjs@latest',\n projectName,\n '--cwd', process.cwd(),\n ], {\n cwd: process.cwd(),\n stdio: 'pipe',\n });\n },\n `Next.js: ${projectName}`\n );\n}\n","import { execa } from 'execa';\nimport { withSpinner } from '../ui/spinner.js';\n\nexport async function installDependencies(projectPath: string): Promise<void> {\n await withSpinner(\n 'Instalando dependencias...',\n async () => {\n await execa('npm', ['install'], {\n cwd: projectPath,\n stdio: 'pipe',\n });\n },\n 'Dependencias instaladas'\n );\n}\n","import { writeFile, readFile, appendFile } from 'fs/promises';\nimport path from 'path';\nimport { withSpinner } from '../ui/spinner.js';\n\ninterface SupabaseKeys {\n url: string;\n anonKey: string;\n serviceKey: string;\n}\n\nexport async function createEnvFile(\n projectPath: string,\n supabaseKeys: SupabaseKeys\n): Promise<void> {\n await withSpinner(\n 'Creando archivo .env.local...',\n async () => {\n const envContent = `# Supabase\nNEXT_PUBLIC_SUPABASE_URL=${supabaseKeys.url}\nNEXT_PUBLIC_SUPABASE_ANON_KEY=${supabaseKeys.anonKey}\nSUPABASE_SERVICE_ROLE_KEY=${supabaseKeys.serviceKey}\n`;\n\n await writeFile(\n path.join(projectPath, '.env.local'),\n envContent\n );\n\n // Asegurar que .env.local esté en .gitignore\n const gitignorePath = path.join(projectPath, '.gitignore');\n try {\n const gitignore = await readFile(gitignorePath, 'utf-8');\n if (!gitignore.includes('.env.local')) {\n await appendFile(gitignorePath, '\\n# Environment variables\\n.env.local\\n.env*.local\\n');\n }\n } catch {\n // Si no existe .gitignore, crearlo\n await writeFile(gitignorePath, '# Environment variables\\n.env.local\\n.env*.local\\n');\n }\n },\n 'Archivo .env.local creado con credenciales de Supabase'\n );\n}\n","import { simpleGit } from 'simple-git';\nimport { withSpinner } from '../ui/spinner.js';\n\nexport async function setupGit(\n projectPath: string,\n remoteUrl: string\n): Promise<void> {\n const git = simpleGit(projectPath);\n\n await withSpinner(\n 'Configurando Git...',\n async () => {\n // Verificar si ya es un repo git\n const isRepo = await git.checkIsRepo();\n\n if (!isRepo) {\n await git.init();\n }\n\n // Agregar remote\n try {\n await git.addRemote('origin', remoteUrl);\n } catch {\n // Remote ya existe, actualizarlo\n await git.remote(['set-url', 'origin', remoteUrl]);\n }\n\n // Stage todos los archivos\n await git.add('.');\n\n // Commit inicial\n await git.commit('Initial commit - created with create-lft-app', {\n '--author': 'create-lft-app <noreply@lft.dev>',\n });\n\n // Renombrar branch a main si es necesario\n try {\n await git.branch(['-M', 'main']);\n } catch {\n // Ya está en main\n }\n\n // Push\n await git.push(['--set-upstream', 'origin', 'main']);\n },\n 'Git configurado y código pusheado'\n );\n}\n","import boxen from 'boxen';\nimport chalk from 'chalk';\n\nexport function showBanner(): void {\n const title = chalk.bold.cyan('create-lft-app');\n const version = chalk.gray('v1.0.0');\n const description = chalk.white('Scaffolding para proyectos LFT');\n\n const banner = boxen(`${title} ${version}\\n${description}`, {\n padding: 1,\n margin: 1,\n borderStyle: 'round',\n borderColor: 'cyan',\n });\n\n console.log(banner);\n}\n\nexport function showSuccessBanner(projectName: string, urls: {\n github?: string;\n supabase?: string;\n jira?: string;\n}): void {\n const lines = [\n chalk.green.bold(`Proyecto \"${projectName}\" creado exitosamente!`),\n '',\n chalk.white('Directorio:') + ` ./${projectName}`,\n '',\n ];\n\n if (urls.github || urls.supabase || urls.jira) {\n lines.push(chalk.white('Enlaces:'));\n if (urls.github) {\n lines.push(` ${chalk.gray('GitHub:')} ${chalk.cyan(urls.github)}`);\n }\n if (urls.supabase) {\n lines.push(` ${chalk.gray('Supabase:')} ${chalk.cyan(urls.supabase)}`);\n }\n if (urls.jira) {\n lines.push(` ${chalk.gray('Jira:')} ${chalk.cyan(urls.jira)}`);\n }\n lines.push('');\n }\n\n lines.push(chalk.white('Siguiente pasos:'));\n lines.push(` ${chalk.cyan('cd')} ${projectName}`);\n lines.push(` ${chalk.cyan('npm run dev')}`);\n\n const banner = boxen(lines.join('\\n'), {\n padding: 1,\n margin: 1,\n borderStyle: 'round',\n borderColor: 'green',\n });\n\n console.log(banner);\n}\n"],"mappings":"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","names":["path","anonKey","serviceKey","project","existsSync","existsSync","execa","execa","path","path","chalk","chalk","path"]}
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts","../../src/config/index.ts","../../src/services/github.ts","../../src/ui/spinner.ts","../../src/ui/logger.ts","../../src/services/supabase.ts","../../src/utils/validation.ts","../../src/services/jira.ts","../../src/steps/scaffold-nextjs.ts","../../src/steps/install-deps.ts","../../src/steps/create-env.ts","../../src/steps/setup-git.ts","../../src/ui/banner.ts"],"sourcesContent":["import path from 'path';\nimport { existsSync } from 'fs';\nimport { confirm } from '@inquirer/prompts';\nimport { loadConfig, hasConfig } from './config/index.js';\nimport { createGitHubRepo, checkGitHubRepoExists } from './services/github.js';\nimport { createSupabaseProject, checkSupabaseProjectExists, getExistingSupabaseKeys } from './services/supabase.js';\nimport { createJiraProject, checkJiraProjectExists } from './services/jira.js';\nimport { scaffoldNextJs } from './steps/scaffold-nextjs.js';\nimport { installDependencies } from './steps/install-deps.js';\nimport { createEnvFile } from './steps/create-env.js';\nimport { setupGit } from './steps/setup-git.js';\nimport { logger } from './ui/logger.js';\nimport { showSuccessBanner } from './ui/banner.js';\nimport { validateProjectName } from './utils/validation.js';\n\nexport interface CreateProjectOptions {\n skipGithub?: boolean;\n skipSupabase?: boolean;\n skipJira?: boolean;\n skipGit?: boolean;\n autoConfirm?: boolean;\n}\n\ninterface ResourceStatus {\n github: { exists: boolean; url?: string };\n supabase: { exists: boolean; url?: string; projectId?: string };\n jira: { exists: boolean; url?: string };\n nextjs: { exists: boolean };\n}\n\nexport async function createProject(\n projectName: string,\n options: CreateProjectOptions = {}\n): Promise<void> {\n // Validar nombre del proyecto\n const validation = validateProjectName(projectName);\n if (!validation.valid) {\n throw new Error(validation.error);\n }\n\n const projectPath = path.resolve(process.cwd(), projectName);\n\n // Cargar configuración\n if (!await hasConfig()) {\n logger.warning('No se encontró configuración. Ejecuta \"create-lft-app config\" primero.');\n throw new Error('Configuración no encontrada');\n }\n\n const config = await loadConfig();\n\n // Verificar recursos existentes en paralelo\n logger.info('Verificando recursos existentes...');\n\n const status: ResourceStatus = {\n github: { exists: false },\n supabase: { exists: false },\n jira: { exists: false },\n nextjs: { exists: existsSync(projectPath) },\n };\n\n const checks: Promise<void>[] = [];\n\n if (!options.skipGithub) {\n checks.push(\n checkGitHubRepoExists(projectName, config).then((result) => {\n status.github = result;\n })\n );\n }\n\n if (!options.skipSupabase) {\n checks.push(\n checkSupabaseProjectExists(projectName, config).then((result) => {\n status.supabase = result;\n })\n );\n }\n\n if (!options.skipJira) {\n checks.push(\n checkJiraProjectExists(projectName, config).then((result) => {\n status.jira = result;\n })\n );\n }\n\n await Promise.all(checks);\n\n // Mostrar resumen con estado\n logger.newLine();\n logger.title('Resumen de recursos:');\n logger.newLine();\n\n const resources = [];\n const owner = config.defaults.githubOrg || config.credentials.github.username;\n\n if (!options.skipGithub) {\n const githubStatus = status.github.exists ? '✓ ya existe' : 'se creará';\n resources.push({\n label: 'GitHub',\n value: `${owner}/${projectName} (${githubStatus})`\n });\n }\n\n if (!options.skipSupabase) {\n const supabaseStatus = status.supabase.exists ? '✓ ya existe' : 'se creará';\n resources.push({\n label: 'Supabase',\n value: `${projectName} en ${config.defaults.supabaseRegion} (${supabaseStatus})`\n });\n }\n\n if (!options.skipJira) {\n const jiraStatus = status.jira.exists ? '✓ ya existe' : 'se creará';\n resources.push({\n label: 'Jira',\n value: `Proyecto \"${projectName}\" (${jiraStatus})`\n });\n }\n\n const nextjsStatus = status.nextjs.exists ? '✓ ya existe' : 'se creará';\n resources.push({\n label: 'Next.js',\n value: `App Router + TypeScript + Tailwind (${nextjsStatus})`\n });\n\n logger.table(resources);\n logger.newLine();\n\n // Confirmar\n if (!options.autoConfirm) {\n const shouldContinue = await confirm({\n message: '¿Continuar?',\n default: true,\n });\n\n if (!shouldContinue) {\n logger.info('Operación cancelada');\n return;\n }\n }\n\n logger.newLine();\n logger.divider();\n logger.newLine();\n\n const urls: { github?: string; supabase?: string; jira?: string } = {};\n let supabaseKeys: { url: string; anonKey: string; serviceKey: string } | undefined;\n\n // Crear recursos externos secuencialmente (para evitar conflictos de spinners)\n if (!options.skipGithub) {\n if (status.github.exists) {\n urls.github = status.github.url;\n logger.success(`GitHub: ${owner}/${projectName} (ya existe)`);\n } else {\n urls.github = await createGitHubRepo(projectName, config);\n }\n }\n\n if (!options.skipJira) {\n if (status.jira.exists) {\n urls.jira = status.jira.url;\n logger.success(`Jira: ${projectName} (ya existe)`);\n } else {\n urls.jira = await createJiraProject(projectName, config);\n }\n }\n\n if (!options.skipSupabase) {\n if (status.supabase.exists && status.supabase.projectId) {\n const result = await getExistingSupabaseKeys(status.supabase.projectId, config);\n urls.supabase = result.url;\n supabaseKeys = result;\n } else {\n const result = await createSupabaseProject(projectName, config);\n urls.supabase = result.url;\n supabaseKeys = result;\n }\n }\n\n // Scaffold Next.js (incluye template LFT completo)\n if (!status.nextjs.exists) {\n await scaffoldNextJs(projectName, projectPath);\n await installDependencies(projectPath);\n } else {\n logger.success(`Next.js: ${projectName} (ya existe)`);\n }\n\n // Crear .env.local\n if (supabaseKeys) {\n await createEnvFile(projectPath, supabaseKeys);\n }\n\n // Setup git\n if (!options.skipGit && urls.github && !status.nextjs.exists) {\n await setupGit(projectPath, urls.github);\n }\n\n // Mostrar resumen final\n logger.newLine();\n showSuccessBanner(projectName, urls);\n}\n","import { existsSync, readFileSync } from 'fs';\n\n// Variable para almacenar la ruta del config\nlet configFilePath: string = '';\n\ninterface ConfigFile {\n github: {\n token: string;\n username: string;\n org: string;\n };\n supabase: {\n token: string;\n orgId: string;\n region: string;\n };\n jira: {\n email: string;\n token: string;\n domain: string;\n };\n}\n\n// Cache de la configuración cargada\nlet loadedConfig: ConfigFile | null = null;\n\n/**\n * Establece la ruta del archivo de configuración\n */\nexport function setConfigPath(path: string): void {\n configFilePath = path;\n loadedConfig = null;\n}\n\n/**\n * Obtiene la ruta actual del archivo de configuración\n */\nexport function getConfigPath(): string {\n return configFilePath;\n}\n\n/**\n * Carga la configuración desde el archivo JSON\n */\nfunction loadConfigFromFile(): ConfigFile | null {\n if (loadedConfig) {\n return loadedConfig;\n }\n\n if (!configFilePath || !existsSync(configFilePath)) {\n return null;\n }\n\n try {\n const content = readFileSync(configFilePath, 'utf-8');\n const parsed = JSON.parse(content);\n\n loadedConfig = {\n github: {\n token: parsed.github?.token || '',\n username: parsed.github?.username || '',\n org: parsed.github?.org || '',\n },\n supabase: {\n token: parsed.supabase?.token || '',\n orgId: parsed.supabase?.orgId || '',\n region: parsed.supabase?.region || 'us-east-1',\n },\n jira: {\n email: parsed.jira?.email || '',\n token: parsed.jira?.token || '',\n domain: parsed.jira?.domain || '',\n },\n };\n\n return loadedConfig;\n } catch {\n return null;\n }\n}\n\nexport interface LFTConfig {\n version: string;\n credentials: {\n github: {\n token: string;\n username: string;\n };\n supabase: {\n accessToken: string;\n organizationId: string;\n };\n jira: {\n email: string;\n apiToken: string;\n domain: string;\n };\n };\n defaults: {\n githubOrg?: string;\n supabaseRegion: string;\n jiraProjectType: string;\n };\n}\n\nexport async function loadConfig(): Promise<LFTConfig> {\n const config = loadConfigFromFile();\n\n if (!config) {\n throw new Error('No se pudo cargar la configuración');\n }\n\n return {\n version: '1.0.0',\n credentials: {\n github: {\n token: config.github.token,\n username: config.github.username,\n },\n supabase: {\n accessToken: config.supabase.token,\n organizationId: config.supabase.orgId,\n },\n jira: {\n email: config.jira.email,\n apiToken: config.jira.token,\n domain: config.jira.domain,\n },\n },\n defaults: {\n githubOrg: config.github.org || undefined,\n supabaseRegion: config.supabase.region,\n jiraProjectType: 'software',\n },\n };\n}\n\nexport async function hasConfig(): Promise<boolean> {\n const config = loadConfigFromFile();\n\n if (!config) {\n return false;\n }\n\n return (\n config.github.token !== '' &&\n config.supabase.token !== '' &&\n config.jira.token !== ''\n );\n}\n","import { Octokit } from 'octokit';\nimport { withSpinner } from '../ui/spinner.js';\nimport type { LFTConfig } from '../config/index.js';\n\nexport async function createGitHubRepo(\n projectName: string,\n config: LFTConfig\n): Promise<string> {\n const octokit = new Octokit({ auth: config.credentials.github.token });\n const org = config.defaults.githubOrg;\n const owner = org || config.credentials.github.username;\n\n return withSpinner(\n 'Creando repositorio en GitHub...',\n async () => {\n let repo;\n\n if (org) {\n repo = await octokit.rest.repos.createInOrg({\n org,\n name: projectName,\n private: true,\n auto_init: false,\n description: `Proyecto ${projectName} creado con create-lft-app`,\n });\n } else {\n repo = await octokit.rest.repos.createForAuthenticatedUser({\n name: projectName,\n private: true,\n auto_init: false,\n description: `Proyecto ${projectName} creado con create-lft-app`,\n });\n }\n\n return repo.data.ssh_url;\n },\n `GitHub: ${owner}/${projectName}`\n );\n}\n\nexport async function validateGitHubToken(token: string): Promise<{ valid: boolean; username?: string }> {\n try {\n const octokit = new Octokit({ auth: token });\n const { data } = await octokit.rest.users.getAuthenticated();\n return { valid: true, username: data.login };\n } catch {\n return { valid: false };\n }\n}\n\nexport async function checkGitHubRepoExists(\n projectName: string,\n config: LFTConfig\n): Promise<{ exists: boolean; url?: string }> {\n const octokit = new Octokit({ auth: config.credentials.github.token });\n const org = config.defaults.githubOrg;\n const owner = org || config.credentials.github.username;\n\n try {\n const existing = await octokit.rest.repos.get({\n owner,\n repo: projectName,\n });\n return { exists: true, url: existing.data.ssh_url };\n } catch {\n return { exists: false };\n }\n}\n","import ora, { type Ora } from 'ora';\n\nexport function createSpinner(text: string): Ora {\n return ora({\n text,\n spinner: 'dots',\n });\n}\n\nexport async function withSpinner<T>(\n text: string,\n fn: () => Promise<T>,\n successText?: string\n): Promise<T> {\n const spinner = createSpinner(text).start();\n\n try {\n const result = await fn();\n spinner.succeed(successText || text);\n return result;\n } catch (error) {\n spinner.fail();\n throw error;\n }\n}\n","import chalk from 'chalk';\n\nexport const logger = {\n info: (message: string) => {\n console.log(chalk.blue('ℹ'), message);\n },\n\n success: (message: string) => {\n console.log(chalk.green('✔'), message);\n },\n\n warning: (message: string) => {\n console.log(chalk.yellow('⚠'), message);\n },\n\n error: (message: string) => {\n console.log(chalk.red('✖'), message);\n },\n\n step: (step: number, total: number, message: string) => {\n console.log(chalk.cyan(`[${step}/${total}]`), message);\n },\n\n newLine: () => {\n console.log();\n },\n\n divider: () => {\n console.log(chalk.gray('─'.repeat(50)));\n },\n\n title: (message: string) => {\n console.log(chalk.bold.white(message));\n },\n\n subtitle: (message: string) => {\n console.log(chalk.gray(message));\n },\n\n link: (label: string, url: string) => {\n console.log(` ${chalk.gray(label + ':')} ${chalk.cyan.underline(url)}`);\n },\n\n list: (items: string[]) => {\n items.forEach((item) => {\n console.log(chalk.gray(' •'), item);\n });\n },\n\n table: (rows: Array<{ label: string; value: string }>) => {\n const maxLabelLength = Math.max(...rows.map((r) => r.label.length));\n rows.forEach(({ label, value }) => {\n const paddedLabel = label.padEnd(maxLabelLength);\n console.log(` ${chalk.gray(paddedLabel)} ${value}`);\n });\n },\n};\n","import { withSpinner, createSpinner } from '../ui/spinner.js';\nimport { logger } from '../ui/logger.js';\nimport type { LFTConfig } from '../config/index.js';\n\ninterface SupabaseProjectResponse {\n id: string;\n name: string;\n organization_id: string;\n region: string;\n status: string;\n}\n\ninterface SupabaseApiKey {\n name: string;\n api_key: string;\n}\n\nexport interface SupabaseProjectResult {\n url: string;\n anonKey: string;\n serviceKey: string;\n}\n\nconst SUPABASE_API_URL = 'https://api.supabase.com/v1';\n\nasync function waitForProjectReady(\n projectId: string,\n token: string,\n maxAttempts = 60\n): Promise<void> {\n const spinner = createSpinner('Provisionando base de datos (esto puede tomar ~2 minutos)...').start();\n\n for (let i = 0; i < maxAttempts; i++) {\n const response = await fetch(`${SUPABASE_API_URL}/projects/${projectId}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (response.ok) {\n const project = (await response.json()) as SupabaseProjectResponse;\n if (project.status === 'ACTIVE_HEALTHY') {\n spinner.succeed('Base de datos provisionada');\n return;\n }\n }\n\n // Esperar 5 segundos antes del siguiente intento\n await new Promise((resolve) => setTimeout(resolve, 5000));\n spinner.text = `Provisionando base de datos... (${Math.floor((i + 1) * 5 / 60)}min ${((i + 1) * 5) % 60}s)`;\n }\n\n spinner.fail('Timeout esperando a que el proyecto esté listo');\n throw new Error('Timeout: el proyecto de Supabase no se activó a tiempo');\n}\n\nasync function getProjectApiKeys(\n projectId: string,\n token: string\n): Promise<{ anonKey: string; serviceKey: string }> {\n const response = await fetch(`${SUPABASE_API_URL}/projects/${projectId}/api-keys`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (!response.ok) {\n throw new Error('No se pudieron obtener las API keys de Supabase');\n }\n\n const keys = (await response.json()) as SupabaseApiKey[];\n\n const anonKey = keys.find((k) => k.name === 'anon')?.api_key;\n const serviceKey = keys.find((k) => k.name === 'service_role')?.api_key;\n\n if (!anonKey || !serviceKey) {\n throw new Error('No se encontraron las API keys necesarias');\n }\n\n return { anonKey, serviceKey };\n}\n\nasync function findExistingProject(\n projectName: string,\n token: string\n): Promise<SupabaseProjectResponse | null> {\n const response = await fetch(`${SUPABASE_API_URL}/projects`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (!response.ok) return null;\n\n const projects = (await response.json()) as SupabaseProjectResponse[];\n return projects.find((p) => p.name === projectName) || null;\n}\n\nexport async function createSupabaseProject(\n projectName: string,\n config: LFTConfig\n): Promise<SupabaseProjectResult> {\n const token = config.credentials.supabase.accessToken;\n const orgId = config.credentials.supabase.organizationId;\n const region = config.defaults.supabaseRegion;\n\n // Verificar si ya existe un proyecto con ese nombre\n const existing = await findExistingProject(projectName, token);\n if (existing) {\n logger.success(`Supabase: ${projectName} (ya existe)`);\n const { anonKey, serviceKey } = await getProjectApiKeys(existing.id, token);\n return {\n url: `https://${existing.id}.supabase.co`,\n anonKey,\n serviceKey,\n };\n }\n\n // Generar una contraseña segura para la base de datos\n const dbPassword = generateSecurePassword();\n\n // Crear proyecto\n const project = await withSpinner(\n 'Creando proyecto en Supabase...',\n async () => {\n const response = await fetch(`${SUPABASE_API_URL}/projects`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n name: projectName,\n organization_id: orgId,\n region,\n plan: 'free',\n db_pass: dbPassword,\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Error creando proyecto Supabase: ${error}`);\n }\n\n return response.json() as Promise<SupabaseProjectResponse>;\n }\n );\n\n // Esperar a que el proyecto esté listo\n await waitForProjectReady(project.id, token);\n\n // Obtener API keys\n const { anonKey, serviceKey } = await getProjectApiKeys(project.id, token);\n\n const projectUrl = `https://${project.id}.supabase.co`;\n\n return {\n url: projectUrl,\n anonKey,\n serviceKey,\n };\n}\n\nfunction generateSecurePassword(): string {\n const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';\n let password = '';\n for (let i = 0; i < 32; i++) {\n password += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return password;\n}\n\nexport async function checkSupabaseProjectExists(\n projectName: string,\n config: LFTConfig\n): Promise<{ exists: boolean; url?: string; projectId?: string }> {\n const token = config.credentials.supabase.accessToken;\n const existing = await findExistingProject(projectName, token);\n\n if (existing) {\n return { exists: true, url: `https://${existing.id}.supabase.co`, projectId: existing.id };\n }\n return { exists: false };\n}\n\nexport async function getExistingSupabaseKeys(\n projectId: string,\n config: LFTConfig\n): Promise<SupabaseProjectResult> {\n return withSpinner(\n 'Obteniendo credenciales de Supabase...',\n async () => {\n const token = config.credentials.supabase.accessToken;\n const { anonKey, serviceKey } = await getProjectApiKeys(projectId, token);\n return {\n url: `https://${projectId}.supabase.co`,\n anonKey,\n serviceKey,\n };\n },\n 'Supabase: credenciales obtenidas'\n );\n}\n","export interface ValidationResult {\n valid: boolean;\n error?: string;\n}\n\nexport function validateProjectName(name: string): ValidationResult {\n // Verificar que no esté vacío\n if (!name || name.trim() === '') {\n return { valid: false, error: 'El nombre del proyecto no puede estar vacío' };\n }\n\n // Verificar caracteres válidos (solo letras, números, guiones y guiones bajos)\n const validPattern = /^[a-zA-Z0-9_-]+$/;\n if (!validPattern.test(name)) {\n return {\n valid: false,\n error: 'El nombre solo puede contener letras, números, guiones (-) y guiones bajos (_)',\n };\n }\n\n // Verificar que no empiece con guión o número\n if (/^[-_0-9]/.test(name)) {\n return {\n valid: false,\n error: 'El nombre debe empezar con una letra',\n };\n }\n\n // Verificar longitud\n if (name.length < 2) {\n return { valid: false, error: 'El nombre debe tener al menos 2 caracteres' };\n }\n\n if (name.length > 50) {\n return { valid: false, error: 'El nombre no puede tener más de 50 caracteres' };\n }\n\n // No verificamos si el directorio existe porque ahora manejamos recursos existentes\n return { valid: true };\n}\n\nexport function generateJiraKey(projectName: string): string {\n // Generar key de Jira: máximo 10 caracteres, solo mayúsculas\n const cleaned = projectName\n .replace(/[^a-zA-Z0-9]/g, '')\n .toUpperCase()\n .slice(0, 10);\n\n return cleaned || 'PROJ';\n}\n","import { withSpinner } from '../ui/spinner.js';\nimport { generateJiraKey } from '../utils/validation.js';\nimport type { LFTConfig } from '../config/index.js';\n\ninterface JiraProjectResponse {\n id: string;\n key: string;\n name: string;\n self: string;\n}\n\nasync function findExistingJiraProject(\n projectName: string,\n auth: string,\n domain: string\n): Promise<JiraProjectResponse | null> {\n const response = await fetch(\n `https://${domain}/rest/api/3/project/search?query=${encodeURIComponent(projectName)}`,\n {\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n }\n );\n\n if (!response.ok) return null;\n\n const data = (await response.json()) as { values: JiraProjectResponse[] };\n return data.values.find((p) => p.name === projectName) || null;\n}\n\nexport async function createJiraProject(\n projectName: string,\n config: LFTConfig\n): Promise<string> {\n const { email, apiToken, domain } = config.credentials.jira;\n const auth = Buffer.from(`${email}:${apiToken}`).toString('base64');\n const projectKey = generateJiraKey(projectName);\n\n return withSpinner(\n 'Creando proyecto en Jira...',\n async () => {\n // Primero obtener el leadAccountId del usuario actual\n const meResponse = await fetch(`https://${domain}/rest/api/3/myself`, {\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!meResponse.ok) {\n throw new Error('No se pudo obtener información del usuario de Jira');\n }\n\n const me = (await meResponse.json()) as { accountId: string };\n\n // Crear el proyecto\n const response = await fetch(`https://${domain}/rest/api/3/project`, {\n method: 'POST',\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n key: projectKey,\n name: projectName,\n projectTypeKey: 'software',\n projectTemplateKey: 'com.pyxis.greenhopper.jira:gh-simplified-agility-scrum',\n leadAccountId: me.accountId,\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n\n // Si el key ya existe, intentar con un sufijo numérico\n if (error.includes('project key')) {\n const newKey = `${projectKey}${Date.now().toString().slice(-4)}`;\n const retryResponse = await fetch(`https://${domain}/rest/api/3/project`, {\n method: 'POST',\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n key: newKey,\n name: projectName,\n projectTypeKey: 'software',\n projectTemplateKey: 'com.pyxis.greenhopper.jira:gh-simplified-agility-scrum',\n leadAccountId: me.accountId,\n }),\n });\n\n if (!retryResponse.ok) {\n throw new Error(`Error creando proyecto Jira: ${await retryResponse.text()}`);\n }\n\n const project = (await retryResponse.json()) as JiraProjectResponse;\n return `https://${domain}/browse/${project.key}`;\n }\n\n throw new Error(`Error creando proyecto Jira: ${error}`);\n }\n\n const project = (await response.json()) as JiraProjectResponse;\n return `https://${domain}/browse/${project.key}`;\n },\n `Proyecto Jira: ${projectKey}`\n );\n}\n\nexport async function validateJiraCredentials(\n email: string,\n apiToken: string,\n domain: string\n): Promise<boolean> {\n try {\n const auth = Buffer.from(`${email}:${apiToken}`).toString('base64');\n const response = await fetch(`https://${domain}/rest/api/3/myself`, {\n headers: { Authorization: `Basic ${auth}` },\n });\n return response.ok;\n } catch {\n return false;\n }\n}\n\nexport async function checkJiraProjectExists(\n projectName: string,\n config: LFTConfig\n): Promise<{ exists: boolean; url?: string }> {\n const { email, apiToken, domain } = config.credentials.jira;\n const auth = Buffer.from(`${email}:${apiToken}`).toString('base64');\n\n const existing = await findExistingJiraProject(projectName, auth, domain);\n if (existing) {\n return { exists: true, url: `https://${domain}/browse/${existing.key}` };\n }\n return { exists: false };\n}\n","import { execa } from 'execa';\nimport { existsSync } from 'fs';\nimport path from 'path';\nimport { withSpinner } from '../ui/spinner.js';\nimport { logger } from '../ui/logger.js';\n\nexport async function scaffoldNextJs(\n projectName: string,\n projectPath: string\n): Promise<void> {\n const targetDir = path.join(process.cwd(), projectName);\n\n if (existsSync(targetDir)) {\n logger.success(`Next.js: ${projectName} (ya existe)`);\n return;\n }\n\n await withSpinner(\n 'Inicializando proyecto Next.js con template LFT...',\n async () => {\n await execa('npx', [\n '@create-lft-app/nextjs@latest',\n projectName,\n '--cwd', process.cwd(),\n ], {\n cwd: process.cwd(),\n stdio: 'pipe',\n });\n },\n `Next.js: ${projectName}`\n );\n}\n","import { execa } from 'execa';\nimport { withSpinner } from '../ui/spinner.js';\n\nexport async function installDependencies(projectPath: string): Promise<void> {\n await withSpinner(\n 'Instalando dependencias...',\n async () => {\n await execa('npm', ['install'], {\n cwd: projectPath,\n stdio: 'pipe',\n });\n },\n 'Dependencias instaladas'\n );\n}\n","import { writeFile, readFile, appendFile } from 'fs/promises';\nimport path from 'path';\nimport { withSpinner } from '../ui/spinner.js';\n\ninterface SupabaseKeys {\n url: string;\n anonKey: string;\n serviceKey: string;\n}\n\nexport async function createEnvFile(\n projectPath: string,\n supabaseKeys: SupabaseKeys\n): Promise<void> {\n await withSpinner(\n 'Creando archivo .env.local...',\n async () => {\n const envContent = `# Supabase\nNEXT_PUBLIC_SUPABASE_URL=${supabaseKeys.url}\nNEXT_PUBLIC_SUPABASE_ANON_KEY=${supabaseKeys.anonKey}\nSUPABASE_SERVICE_ROLE_KEY=${supabaseKeys.serviceKey}\n`;\n\n await writeFile(\n path.join(projectPath, '.env.local'),\n envContent\n );\n\n // Asegurar que .env.local esté en .gitignore\n const gitignorePath = path.join(projectPath, '.gitignore');\n try {\n const gitignore = await readFile(gitignorePath, 'utf-8');\n if (!gitignore.includes('.env.local')) {\n await appendFile(gitignorePath, '\\n# Environment variables\\n.env.local\\n.env*.local\\n');\n }\n } catch {\n // Si no existe .gitignore, crearlo\n await writeFile(gitignorePath, '# Environment variables\\n.env.local\\n.env*.local\\n');\n }\n },\n 'Archivo .env.local creado con credenciales de Supabase'\n );\n}\n","import { simpleGit } from 'simple-git';\nimport { withSpinner } from '../ui/spinner.js';\n\nexport async function setupGit(\n projectPath: string,\n remoteUrl: string\n): Promise<void> {\n const git = simpleGit(projectPath);\n\n await withSpinner(\n 'Configurando Git...',\n async () => {\n // Inicializar git (simple-git requiere que el directorio sea un repo)\n await git.init();\n\n // Agregar remote\n try {\n await git.addRemote('origin', remoteUrl);\n } catch {\n // Remote ya existe, actualizarlo\n await git.remote(['set-url', 'origin', remoteUrl]);\n }\n\n // Stage todos los archivos\n await git.add('.');\n\n // Commit inicial\n await git.commit('Initial commit - created with create-lft-app', {\n '--author': 'create-lft-app <noreply@lft.dev>',\n });\n\n // Renombrar branch a main si es necesario\n try {\n await git.branch(['-M', 'main']);\n } catch {\n // Ya está en main\n }\n\n // Push\n await git.push(['--set-upstream', 'origin', 'main']);\n },\n 'Git configurado y código pusheado'\n );\n}\n","import boxen from 'boxen';\nimport chalk from 'chalk';\nimport { readFileSync } from 'fs';\nimport { fileURLToPath } from 'url';\nimport { dirname, join } from 'path';\n\nfunction getVersion(): string {\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n const pkgPath = join(__dirname, '..', '..', 'package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n return pkg.version || '0.0.0';\n } catch {\n return '0.0.0';\n }\n}\n\nfunction getKangarooArt(): string {\n const c = chalk.cyan;\n return c(` \n ██ ███ \n ██████████ \n █ ████████ \n ███████████ \n ███████████ \n █████ █████ \n ██ █ █████ \n ██ █ ████ \n ██ ██ ███ ██ \n ███ ███████████████ \n ███ ██ █████ \n ████ █ ██████ \n ████ ██ ██████████ \n ██ ██ ███████████ \n ██ █ █ ███ ██████████ \n ██ █ ███ █████████████ \n ██ ██ █ █████████ \n ██ ██ █████████ ██████ \n ██ ███ █████ ███████ ██████ \n ███ ██████ ██████████████████ \n ███ █████████████ ████████████████ \n ██ ██████████████████████████████████ \n ██ █████ ███████████████ \n ██ ███████ \n ██ ████████ \n ██ █████ ███ \n ██ ██ ██████ ██ \n ██ ███ ███████ ██ \n ███ █ ████ ██ ███████ ██ \n ███ ██ █████████ ███ ███████ ██ \n ███ ██ ███████████████ ████████ ██ \n █████ ██ ███████ ██ ██ ████ ██ \n █ ███ ███ ████████ ██ ███ ██ █ \n ███ ████ ████ ███████ ██ ██ ██ ██ \n ███ ████████ ███████ ██ ██ ██ ██ \n █████ █████████ ██ █ █ █ \n ███████████████ ███ ██ ██ █ \n ██████ ██ ███ ██ █████ \n ████ ███ ██ █████████ \n █████ ████ ██████████ ███ \n ████ ████ █████████ \n ███████ \n ██ \n`);\n}\n\nexport function showBanner(): void {\n const version = getVersion();\n const title = chalk.bold.cyan('create-lft-app');\n const versionText = chalk.gray(`v${version}`);\n const description = chalk.white('Scaffolding para proyectos LFT');\n\n console.log(getKangarooArt());\n\n const banner = boxen(`${title} ${versionText}\\n${description}`, {\n padding: 1,\n margin: 1,\n borderStyle: 'round',\n borderColor: 'cyan',\n });\n\n console.log(banner);\n}\n\nexport function showSuccessBanner(projectName: string, urls: {\n github?: string;\n supabase?: string;\n jira?: string;\n}): void {\n const lines = [\n chalk.green.bold(`Proyecto \"${projectName}\" creado exitosamente!`),\n '',\n chalk.white('Directorio:') + ` ./${projectName}`,\n '',\n ];\n\n if (urls.github || urls.supabase || urls.jira) {\n lines.push(chalk.white('Enlaces:'));\n if (urls.github) {\n lines.push(` ${chalk.gray('GitHub:')} ${chalk.cyan(urls.github)}`);\n }\n if (urls.supabase) {\n lines.push(` ${chalk.gray('Supabase:')} ${chalk.cyan(urls.supabase)}`);\n }\n if (urls.jira) {\n lines.push(` ${chalk.gray('Jira:')} ${chalk.cyan(urls.jira)}`);\n }\n lines.push('');\n }\n\n lines.push(chalk.white('Siguiente pasos:'));\n lines.push(` ${chalk.cyan('cd')} ${projectName}`);\n lines.push(` ${chalk.cyan('npm run dev')}`);\n\n const banner = boxen(lines.join('\\n'), {\n padding: 1,\n margin: 1,\n borderStyle: 'round',\n borderColor: 'green',\n });\n\n console.log(banner);\n}\n"],"mappings":"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","names":["path","existsSync","anonKey","serviceKey","project","existsSync","existsSync","execa","execa","path","path","chalk","chalk","path","existsSync"]}
|