@create-lft-app/cli 1.0.2 → 1.0.3
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 +138 -400
- package/dist/bin/cli.js.map +1 -1
- package/dist/src/index.js +135 -176
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -3
package/dist/bin/cli.js
CHANGED
|
@@ -1,383 +1,85 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// bin/cli.ts
|
|
4
|
-
import "dotenv/config";
|
|
5
4
|
import { program } from "commander";
|
|
6
5
|
|
|
7
6
|
// src/index.ts
|
|
8
|
-
import
|
|
9
|
-
import { confirm
|
|
10
|
-
|
|
11
|
-
// src/config/index.ts
|
|
12
|
-
import fs from "fs/promises";
|
|
13
|
-
import path from "path";
|
|
14
|
-
import os from "os";
|
|
15
|
-
import { createCipheriv, createDecipheriv, randomBytes, createHash } from "crypto";
|
|
16
|
-
import nodeMachineId from "node-machine-id";
|
|
17
|
-
import { input, password, select, confirm } from "@inquirer/prompts";
|
|
7
|
+
import path4 from "path";
|
|
8
|
+
import { confirm } from "@inquirer/prompts";
|
|
18
9
|
|
|
19
|
-
// src/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
success: (message) => {
|
|
26
|
-
console.log(chalk.green("\u2714"), message);
|
|
27
|
-
},
|
|
28
|
-
warning: (message) => {
|
|
29
|
-
console.log(chalk.yellow("\u26A0"), message);
|
|
30
|
-
},
|
|
31
|
-
error: (message) => {
|
|
32
|
-
console.log(chalk.red("\u2716"), message);
|
|
33
|
-
},
|
|
34
|
-
step: (step, total, message) => {
|
|
35
|
-
console.log(chalk.cyan(`[${step}/${total}]`), message);
|
|
36
|
-
},
|
|
37
|
-
newLine: () => {
|
|
38
|
-
console.log();
|
|
39
|
-
},
|
|
40
|
-
divider: () => {
|
|
41
|
-
console.log(chalk.gray("\u2500".repeat(50)));
|
|
42
|
-
},
|
|
43
|
-
title: (message) => {
|
|
44
|
-
console.log(chalk.bold.white(message));
|
|
45
|
-
},
|
|
46
|
-
subtitle: (message) => {
|
|
47
|
-
console.log(chalk.gray(message));
|
|
10
|
+
// src/config/static-config.ts
|
|
11
|
+
var STATIC_CONFIG = {
|
|
12
|
+
github: {
|
|
13
|
+
token: "__GITHUB_TOKEN__",
|
|
14
|
+
username: "__GITHUB_USERNAME__",
|
|
15
|
+
org: "__GITHUB_ORG__"
|
|
48
16
|
},
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
items.forEach((item) => {
|
|
54
|
-
console.log(chalk.gray(" \u2022"), item);
|
|
55
|
-
});
|
|
17
|
+
supabase: {
|
|
18
|
+
token: "__SUPABASE_TOKEN__",
|
|
19
|
+
orgId: "__SUPABASE_ORG_ID__",
|
|
20
|
+
region: "us-east-1"
|
|
56
21
|
},
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
console.log(` ${chalk.gray(paddedLabel)} ${value}`);
|
|
62
|
-
});
|
|
22
|
+
jira: {
|
|
23
|
+
email: "__JIRA_EMAIL__",
|
|
24
|
+
token: "__JIRA_TOKEN__",
|
|
25
|
+
domain: "__JIRA_DOMAIN__"
|
|
63
26
|
}
|
|
64
27
|
};
|
|
65
28
|
|
|
66
|
-
// src/ui/spinner.ts
|
|
67
|
-
import ora from "ora";
|
|
68
|
-
function createSpinner(text) {
|
|
69
|
-
return ora({
|
|
70
|
-
text,
|
|
71
|
-
spinner: "dots"
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
async function withSpinner(text, fn, successText) {
|
|
75
|
-
const spinner = createSpinner(text).start();
|
|
76
|
-
try {
|
|
77
|
-
const result = await fn();
|
|
78
|
-
spinner.succeed(successText || text);
|
|
79
|
-
return result;
|
|
80
|
-
} catch (error) {
|
|
81
|
-
spinner.fail();
|
|
82
|
-
throw error;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
29
|
// src/config/index.ts
|
|
87
|
-
|
|
88
|
-
var CONFIG_FILE = path.join(os.homedir(), ".lftrc");
|
|
89
|
-
var ALGORITHM = "aes-256-gcm";
|
|
90
|
-
function getEncryptionKey() {
|
|
91
|
-
const machineId = machineIdSync();
|
|
92
|
-
return createHash("sha256").update(machineId + "lft-secret").digest();
|
|
93
|
-
}
|
|
94
|
-
function encryptConfig(config) {
|
|
95
|
-
const key = getEncryptionKey();
|
|
96
|
-
const iv = randomBytes(16);
|
|
97
|
-
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
98
|
-
let encrypted = cipher.update(JSON.stringify(config), "utf8", "hex");
|
|
99
|
-
encrypted += cipher.final("hex");
|
|
100
|
-
const authTag = cipher.getAuthTag();
|
|
101
|
-
return JSON.stringify({
|
|
102
|
-
iv: iv.toString("hex"),
|
|
103
|
-
tag: authTag.toString("hex"),
|
|
104
|
-
data: encrypted
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
function decryptConfig(encrypted) {
|
|
108
|
-
const { iv, tag, data } = JSON.parse(encrypted);
|
|
109
|
-
const key = getEncryptionKey();
|
|
110
|
-
const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(iv, "hex"));
|
|
111
|
-
decipher.setAuthTag(Buffer.from(tag, "hex"));
|
|
112
|
-
let decrypted = decipher.update(data, "hex", "utf8");
|
|
113
|
-
decrypted += decipher.final("utf8");
|
|
114
|
-
return JSON.parse(decrypted);
|
|
115
|
-
}
|
|
116
|
-
var ENV_VARS = {
|
|
117
|
-
GITHUB_TOKEN: "LFT_GITHUB_TOKEN",
|
|
118
|
-
GITHUB_USERNAME: "LFT_GITHUB_USERNAME",
|
|
119
|
-
GITHUB_ORG: "LFT_GITHUB_ORG",
|
|
120
|
-
SUPABASE_TOKEN: "LFT_SUPABASE_TOKEN",
|
|
121
|
-
SUPABASE_ORG_ID: "LFT_SUPABASE_ORG_ID",
|
|
122
|
-
SUPABASE_REGION: "LFT_SUPABASE_REGION",
|
|
123
|
-
JIRA_EMAIL: "LFT_JIRA_EMAIL",
|
|
124
|
-
JIRA_TOKEN: "LFT_JIRA_TOKEN",
|
|
125
|
-
JIRA_DOMAIN: "LFT_JIRA_DOMAIN"
|
|
126
|
-
};
|
|
127
|
-
function loadConfigFromEnv() {
|
|
128
|
-
const githubToken = process.env[ENV_VARS.GITHUB_TOKEN];
|
|
129
|
-
const githubUsername = process.env[ENV_VARS.GITHUB_USERNAME];
|
|
130
|
-
const supabaseToken = process.env[ENV_VARS.SUPABASE_TOKEN];
|
|
131
|
-
const supabaseOrgId = process.env[ENV_VARS.SUPABASE_ORG_ID];
|
|
132
|
-
const jiraEmail = process.env[ENV_VARS.JIRA_EMAIL];
|
|
133
|
-
const jiraToken = process.env[ENV_VARS.JIRA_TOKEN];
|
|
134
|
-
const jiraDomain = process.env[ENV_VARS.JIRA_DOMAIN];
|
|
135
|
-
if (!githubToken || !supabaseToken || !jiraToken) {
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
30
|
+
async function loadConfig() {
|
|
138
31
|
return {
|
|
139
32
|
version: "1.0.0",
|
|
140
33
|
credentials: {
|
|
141
34
|
github: {
|
|
142
|
-
token:
|
|
143
|
-
username:
|
|
35
|
+
token: STATIC_CONFIG.github.token,
|
|
36
|
+
username: STATIC_CONFIG.github.username
|
|
144
37
|
},
|
|
145
38
|
supabase: {
|
|
146
|
-
accessToken:
|
|
147
|
-
organizationId:
|
|
39
|
+
accessToken: STATIC_CONFIG.supabase.token,
|
|
40
|
+
organizationId: STATIC_CONFIG.supabase.orgId
|
|
148
41
|
},
|
|
149
42
|
jira: {
|
|
150
|
-
email:
|
|
151
|
-
apiToken:
|
|
152
|
-
domain:
|
|
43
|
+
email: STATIC_CONFIG.jira.email,
|
|
44
|
+
apiToken: STATIC_CONFIG.jira.token,
|
|
45
|
+
domain: STATIC_CONFIG.jira.domain
|
|
153
46
|
}
|
|
154
47
|
},
|
|
155
48
|
defaults: {
|
|
156
|
-
githubOrg:
|
|
157
|
-
supabaseRegion:
|
|
49
|
+
githubOrg: STATIC_CONFIG.github.org || void 0,
|
|
50
|
+
supabaseRegion: STATIC_CONFIG.supabase.region,
|
|
158
51
|
jiraProjectType: "software"
|
|
159
52
|
}
|
|
160
53
|
};
|
|
161
54
|
}
|
|
162
|
-
function hasEnvConfig() {
|
|
163
|
-
return !!(process.env[ENV_VARS.GITHUB_TOKEN] && process.env[ENV_VARS.SUPABASE_TOKEN] && process.env[ENV_VARS.JIRA_TOKEN]);
|
|
164
|
-
}
|
|
165
55
|
async function hasConfig() {
|
|
166
|
-
|
|
167
|
-
try {
|
|
168
|
-
await fs.access(CONFIG_FILE);
|
|
169
|
-
return true;
|
|
170
|
-
} catch {
|
|
171
|
-
return false;
|
|
172
|
-
}
|
|
56
|
+
return STATIC_CONFIG.github.token !== "__GITHUB_TOKEN__" && STATIC_CONFIG.supabase.token !== "__SUPABASE_TOKEN__" && STATIC_CONFIG.jira.token !== "__JIRA_TOKEN__";
|
|
173
57
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
await fs.writeFile(CONFIG_FILE, encrypted, "utf8");
|
|
185
|
-
}
|
|
186
|
-
async function showConfig() {
|
|
187
|
-
if (!await hasConfig()) {
|
|
188
|
-
logger.warning("No hay configuraci\xF3n guardada");
|
|
189
|
-
logger.info('Ejecuta "create-lft-app config" para configurar');
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
const config = await loadConfig();
|
|
193
|
-
logger.newLine();
|
|
194
|
-
logger.title("Configuraci\xF3n actual:");
|
|
195
|
-
logger.newLine();
|
|
196
|
-
logger.subtitle("GitHub:");
|
|
197
|
-
logger.table([
|
|
198
|
-
{ label: "Usuario", value: config.credentials.github.username },
|
|
199
|
-
{ label: "Token", value: "***" + config.credentials.github.token.slice(-4) },
|
|
200
|
-
{ label: "Org por defecto", value: config.defaults.githubOrg || "(ninguna)" }
|
|
201
|
-
]);
|
|
202
|
-
logger.newLine();
|
|
203
|
-
logger.subtitle("Supabase:");
|
|
204
|
-
logger.table([
|
|
205
|
-
{ label: "Org ID", value: config.credentials.supabase.organizationId },
|
|
206
|
-
{ label: "Token", value: "***" + config.credentials.supabase.accessToken.slice(-4) },
|
|
207
|
-
{ label: "Regi\xF3n", value: config.defaults.supabaseRegion }
|
|
208
|
-
]);
|
|
209
|
-
logger.newLine();
|
|
210
|
-
logger.subtitle("Jira:");
|
|
211
|
-
logger.table([
|
|
212
|
-
{ label: "Email", value: config.credentials.jira.email },
|
|
213
|
-
{ label: "Dominio", value: config.credentials.jira.domain },
|
|
214
|
-
{ label: "Token", value: "***" + config.credentials.jira.apiToken.slice(-4) }
|
|
215
|
-
]);
|
|
216
|
-
}
|
|
217
|
-
async function resetConfig() {
|
|
218
|
-
if (!await hasConfig()) {
|
|
219
|
-
logger.info("No hay configuraci\xF3n para resetear");
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
const shouldReset = await confirm({
|
|
223
|
-
message: "\xBFEst\xE1s seguro de que quieres eliminar la configuraci\xF3n?",
|
|
224
|
-
default: false
|
|
58
|
+
|
|
59
|
+
// src/services/github.ts
|
|
60
|
+
import { Octokit } from "octokit";
|
|
61
|
+
|
|
62
|
+
// src/ui/spinner.ts
|
|
63
|
+
import ora from "ora";
|
|
64
|
+
function createSpinner(text) {
|
|
65
|
+
return ora({
|
|
66
|
+
text,
|
|
67
|
+
spinner: "dots"
|
|
225
68
|
});
|
|
226
|
-
if (shouldReset) {
|
|
227
|
-
await fs.unlink(CONFIG_FILE);
|
|
228
|
-
logger.success("Configuraci\xF3n eliminada");
|
|
229
|
-
}
|
|
230
69
|
}
|
|
231
|
-
async function
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
github: { token: "", username: "" },
|
|
241
|
-
supabase: { accessToken: "", organizationId: "" },
|
|
242
|
-
jira: { email: "", apiToken: "", domain: "" }
|
|
243
|
-
},
|
|
244
|
-
defaults: {
|
|
245
|
-
supabaseRegion: "us-east-1",
|
|
246
|
-
jiraProjectType: "software"
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
const configureAll = !options.onlyGithub && !options.onlySupabase && !options.onlyJira;
|
|
251
|
-
if (configureAll || options.onlyGithub) {
|
|
252
|
-
logger.newLine();
|
|
253
|
-
logger.title("Configuraci\xF3n de GitHub");
|
|
254
|
-
logger.subtitle("Necesitas un Personal Access Token con permisos: repo, read:org");
|
|
255
|
-
logger.link("Crear token", "https://github.com/settings/tokens/new");
|
|
256
|
-
logger.newLine();
|
|
257
|
-
const githubToken = await password({
|
|
258
|
-
message: "GitHub Personal Access Token:",
|
|
259
|
-
mask: "*"
|
|
260
|
-
});
|
|
261
|
-
const isValid = await withSpinner(
|
|
262
|
-
"Validando token de GitHub...",
|
|
263
|
-
async () => {
|
|
264
|
-
const response = await fetch("https://api.github.com/user", {
|
|
265
|
-
headers: { Authorization: `Bearer ${githubToken}` }
|
|
266
|
-
});
|
|
267
|
-
if (!response.ok) return null;
|
|
268
|
-
return response.json();
|
|
269
|
-
}
|
|
270
|
-
);
|
|
271
|
-
if (!isValid) {
|
|
272
|
-
throw new Error("Token de GitHub inv\xE1lido");
|
|
273
|
-
}
|
|
274
|
-
config.credentials.github.token = githubToken;
|
|
275
|
-
config.credentials.github.username = isValid.login;
|
|
276
|
-
logger.success(`Conectado como: ${isValid.login}`);
|
|
277
|
-
const useOrg = await confirm({
|
|
278
|
-
message: "\xBFQuieres usar una organizaci\xF3n por defecto?",
|
|
279
|
-
default: false
|
|
280
|
-
});
|
|
281
|
-
if (useOrg) {
|
|
282
|
-
config.defaults.githubOrg = await input({
|
|
283
|
-
message: "Nombre de la organizaci\xF3n:"
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
if (configureAll || options.onlySupabase) {
|
|
288
|
-
logger.newLine();
|
|
289
|
-
logger.title("Configuraci\xF3n de Supabase");
|
|
290
|
-
logger.subtitle("Necesitas un Access Token de la Management API");
|
|
291
|
-
logger.link("Crear token", "https://supabase.com/dashboard/account/tokens");
|
|
292
|
-
logger.newLine();
|
|
293
|
-
const supabaseToken = await password({
|
|
294
|
-
message: "Supabase Access Token:",
|
|
295
|
-
mask: "*"
|
|
296
|
-
});
|
|
297
|
-
const orgs = await withSpinner(
|
|
298
|
-
"Obteniendo organizaciones de Supabase...",
|
|
299
|
-
async () => {
|
|
300
|
-
const response = await fetch("https://api.supabase.com/v1/organizations", {
|
|
301
|
-
headers: { Authorization: `Bearer ${supabaseToken}` }
|
|
302
|
-
});
|
|
303
|
-
if (!response.ok) return null;
|
|
304
|
-
return response.json();
|
|
305
|
-
}
|
|
306
|
-
);
|
|
307
|
-
if (!orgs || orgs.length === 0) {
|
|
308
|
-
throw new Error("Token de Supabase inv\xE1lido o no tienes organizaciones");
|
|
309
|
-
}
|
|
310
|
-
config.credentials.supabase.accessToken = supabaseToken;
|
|
311
|
-
if (orgs.length === 1) {
|
|
312
|
-
config.credentials.supabase.organizationId = orgs[0].id;
|
|
313
|
-
logger.success(`Usando organizaci\xF3n: ${orgs[0].name}`);
|
|
314
|
-
} else {
|
|
315
|
-
const selectedOrg = await select({
|
|
316
|
-
message: "Selecciona la organizaci\xF3n:",
|
|
317
|
-
choices: orgs.map((org) => ({ name: org.name, value: org.id }))
|
|
318
|
-
});
|
|
319
|
-
config.credentials.supabase.organizationId = selectedOrg;
|
|
320
|
-
}
|
|
321
|
-
config.defaults.supabaseRegion = await select({
|
|
322
|
-
message: "Regi\xF3n por defecto:",
|
|
323
|
-
choices: [
|
|
324
|
-
{ name: "US East (Virginia)", value: "us-east-1" },
|
|
325
|
-
{ name: "US West (Oregon)", value: "us-west-1" },
|
|
326
|
-
{ name: "EU West (Ireland)", value: "eu-west-1" },
|
|
327
|
-
{ name: "AP Southeast (Singapore)", value: "ap-southeast-1" },
|
|
328
|
-
{ name: "AP Northeast (Tokyo)", value: "ap-northeast-1" },
|
|
329
|
-
{ name: "SA East (S\xE3o Paulo)", value: "sa-east-1" }
|
|
330
|
-
],
|
|
331
|
-
default: "us-east-1"
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
if (configureAll || options.onlyJira) {
|
|
335
|
-
logger.newLine();
|
|
336
|
-
logger.title("Configuraci\xF3n de Jira");
|
|
337
|
-
logger.subtitle("Necesitas un API Token de Atlassian");
|
|
338
|
-
logger.link("Crear token", "https://id.atlassian.com/manage-profile/security/api-tokens");
|
|
339
|
-
logger.newLine();
|
|
340
|
-
const jiraEmail = await input({
|
|
341
|
-
message: "Email de Atlassian:"
|
|
342
|
-
});
|
|
343
|
-
const jiraToken = await password({
|
|
344
|
-
message: "Jira API Token:",
|
|
345
|
-
mask: "*"
|
|
346
|
-
});
|
|
347
|
-
const jiraDomain = await input({
|
|
348
|
-
message: "Dominio de Jira (ej: empresa.atlassian.net):",
|
|
349
|
-
validate: (value) => {
|
|
350
|
-
if (!value.includes(".atlassian.net")) {
|
|
351
|
-
return "El dominio debe terminar en .atlassian.net";
|
|
352
|
-
}
|
|
353
|
-
return true;
|
|
354
|
-
}
|
|
355
|
-
});
|
|
356
|
-
const isValid = await withSpinner(
|
|
357
|
-
"Validando credenciales de Jira...",
|
|
358
|
-
async () => {
|
|
359
|
-
const auth = Buffer.from(`${jiraEmail}:${jiraToken}`).toString("base64");
|
|
360
|
-
const response = await fetch(`https://${jiraDomain}/rest/api/3/myself`, {
|
|
361
|
-
headers: { Authorization: `Basic ${auth}` }
|
|
362
|
-
});
|
|
363
|
-
return response.ok;
|
|
364
|
-
}
|
|
365
|
-
);
|
|
366
|
-
if (!isValid) {
|
|
367
|
-
throw new Error("Credenciales de Jira inv\xE1lidas");
|
|
368
|
-
}
|
|
369
|
-
config.credentials.jira.email = jiraEmail;
|
|
370
|
-
config.credentials.jira.apiToken = jiraToken;
|
|
371
|
-
config.credentials.jira.domain = jiraDomain;
|
|
372
|
-
logger.success("Credenciales de Jira v\xE1lidas");
|
|
70
|
+
async function withSpinner(text, fn, successText) {
|
|
71
|
+
const spinner = createSpinner(text).start();
|
|
72
|
+
try {
|
|
73
|
+
const result = await fn();
|
|
74
|
+
spinner.succeed(successText || text);
|
|
75
|
+
return result;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
spinner.fail();
|
|
78
|
+
throw error;
|
|
373
79
|
}
|
|
374
|
-
await saveConfig(config);
|
|
375
|
-
logger.newLine();
|
|
376
|
-
logger.success("Configuraci\xF3n guardada en ~/.lftrc");
|
|
377
80
|
}
|
|
378
81
|
|
|
379
82
|
// src/services/github.ts
|
|
380
|
-
import { Octokit } from "octokit";
|
|
381
83
|
async function createGitHubRepo(projectName, config) {
|
|
382
84
|
const octokit = new Octokit({ auth: config.credentials.github.token });
|
|
383
85
|
const org = config.defaults.githubOrg;
|
|
@@ -483,16 +185,16 @@ async function createSupabaseProject(projectName, config) {
|
|
|
483
185
|
}
|
|
484
186
|
function generateSecurePassword() {
|
|
485
187
|
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
|
|
486
|
-
let
|
|
188
|
+
let password = "";
|
|
487
189
|
for (let i = 0; i < 32; i++) {
|
|
488
|
-
|
|
190
|
+
password += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
489
191
|
}
|
|
490
|
-
return
|
|
192
|
+
return password;
|
|
491
193
|
}
|
|
492
194
|
|
|
493
195
|
// src/utils/validation.ts
|
|
494
|
-
import
|
|
495
|
-
import
|
|
196
|
+
import fs from "fs";
|
|
197
|
+
import path from "path";
|
|
496
198
|
function validateProjectName(name) {
|
|
497
199
|
if (!name || name.trim() === "") {
|
|
498
200
|
return { valid: false, error: "El nombre del proyecto no puede estar vac\xEDo" };
|
|
@@ -516,8 +218,8 @@ function validateProjectName(name) {
|
|
|
516
218
|
if (name.length > 50) {
|
|
517
219
|
return { valid: false, error: "El nombre no puede tener m\xE1s de 50 caracteres" };
|
|
518
220
|
}
|
|
519
|
-
const projectPath =
|
|
520
|
-
if (
|
|
221
|
+
const projectPath = path.resolve(process.cwd(), name);
|
|
222
|
+
if (fs.existsSync(projectPath)) {
|
|
521
223
|
return { valid: false, error: `El directorio "${name}" ya existe` };
|
|
522
224
|
}
|
|
523
225
|
return { valid: true };
|
|
@@ -621,59 +323,59 @@ async function scaffoldNextJs(projectName, projectPath) {
|
|
|
621
323
|
|
|
622
324
|
// src/steps/copy-template.ts
|
|
623
325
|
import { cp, mkdir, readFile, writeFile } from "fs/promises";
|
|
624
|
-
import
|
|
326
|
+
import path2 from "path";
|
|
625
327
|
import { fileURLToPath } from "url";
|
|
626
328
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
627
|
-
var __dirname2 =
|
|
329
|
+
var __dirname2 = path2.dirname(__filename2);
|
|
628
330
|
async function copyTemplate(projectPath) {
|
|
629
331
|
await withSpinner(
|
|
630
332
|
"Copiando template LFT...",
|
|
631
333
|
async () => {
|
|
632
|
-
const templatesDir =
|
|
633
|
-
const srcDir =
|
|
334
|
+
const templatesDir = path2.join(__dirname2, "..", "..", "templates");
|
|
335
|
+
const srcDir = path2.join(projectPath, "src");
|
|
634
336
|
await cp(
|
|
635
|
-
|
|
636
|
-
|
|
337
|
+
path2.join(templatesDir, "components", "ui"),
|
|
338
|
+
path2.join(srcDir, "components", "ui"),
|
|
637
339
|
{ recursive: true }
|
|
638
340
|
);
|
|
639
341
|
await cp(
|
|
640
|
-
|
|
641
|
-
|
|
342
|
+
path2.join(templatesDir, "components", "layout"),
|
|
343
|
+
path2.join(srcDir, "components", "layout"),
|
|
642
344
|
{ recursive: true }
|
|
643
345
|
);
|
|
644
346
|
await cp(
|
|
645
|
-
|
|
646
|
-
|
|
347
|
+
path2.join(templatesDir, "components", "dashboard"),
|
|
348
|
+
path2.join(srcDir, "components", "dashboard"),
|
|
647
349
|
{ recursive: true }
|
|
648
350
|
);
|
|
649
|
-
await mkdir(
|
|
351
|
+
await mkdir(path2.join(srcDir, "lib"), { recursive: true });
|
|
650
352
|
await cp(
|
|
651
|
-
|
|
652
|
-
|
|
353
|
+
path2.join(templatesDir, "lib", "utils.ts"),
|
|
354
|
+
path2.join(srcDir, "lib", "utils.ts")
|
|
653
355
|
);
|
|
654
|
-
await mkdir(
|
|
356
|
+
await mkdir(path2.join(srcDir, "hooks"), { recursive: true });
|
|
655
357
|
await cp(
|
|
656
|
-
|
|
657
|
-
|
|
358
|
+
path2.join(templatesDir, "hooks"),
|
|
359
|
+
path2.join(srcDir, "hooks"),
|
|
658
360
|
{ recursive: true }
|
|
659
361
|
);
|
|
660
362
|
await cp(
|
|
661
|
-
|
|
662
|
-
|
|
363
|
+
path2.join(templatesDir, "app", "layout.tsx"),
|
|
364
|
+
path2.join(srcDir, "app", "layout.tsx")
|
|
663
365
|
);
|
|
664
366
|
await cp(
|
|
665
|
-
|
|
666
|
-
|
|
367
|
+
path2.join(templatesDir, "app", "page.tsx"),
|
|
368
|
+
path2.join(srcDir, "app", "page.tsx")
|
|
667
369
|
);
|
|
668
|
-
await mkdir(
|
|
370
|
+
await mkdir(path2.join(srcDir, "app", "dashboard"), { recursive: true });
|
|
669
371
|
await cp(
|
|
670
|
-
|
|
671
|
-
|
|
372
|
+
path2.join(templatesDir, "app", "dashboard", "page.tsx"),
|
|
373
|
+
path2.join(srcDir, "app", "dashboard", "page.tsx")
|
|
672
374
|
);
|
|
673
|
-
await mkdir(
|
|
375
|
+
await mkdir(path2.join(srcDir, "app", "auth", "login"), { recursive: true });
|
|
674
376
|
await cp(
|
|
675
|
-
|
|
676
|
-
|
|
377
|
+
path2.join(templatesDir, "app", "auth", "login", "page.tsx"),
|
|
378
|
+
path2.join(srcDir, "app", "auth", "login", "page.tsx")
|
|
677
379
|
);
|
|
678
380
|
await mergeGlobalStyles(projectPath, templatesDir);
|
|
679
381
|
},
|
|
@@ -681,8 +383,8 @@ async function copyTemplate(projectPath) {
|
|
|
681
383
|
);
|
|
682
384
|
}
|
|
683
385
|
async function mergeGlobalStyles(projectPath, templatesDir) {
|
|
684
|
-
const templateCssPath =
|
|
685
|
-
const projectCssPath =
|
|
386
|
+
const templateCssPath = path2.join(templatesDir, "app", "globals.css");
|
|
387
|
+
const projectCssPath = path2.join(projectPath, "src", "app", "globals.css");
|
|
686
388
|
try {
|
|
687
389
|
const templateCss = await readFile(templateCssPath, "utf-8");
|
|
688
390
|
const existingCss = await readFile(projectCssPath, "utf-8");
|
|
@@ -759,7 +461,7 @@ async function installDependencies(projectPath) {
|
|
|
759
461
|
|
|
760
462
|
// src/steps/create-env.ts
|
|
761
463
|
import { writeFile as writeFile2, readFile as readFile2, appendFile } from "fs/promises";
|
|
762
|
-
import
|
|
464
|
+
import path3 from "path";
|
|
763
465
|
async function createEnvFile(projectPath, supabaseKeys) {
|
|
764
466
|
await withSpinner(
|
|
765
467
|
"Creando archivo .env.local...",
|
|
@@ -770,10 +472,10 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=${supabaseKeys.anonKey}
|
|
|
770
472
|
SUPABASE_SERVICE_ROLE_KEY=${supabaseKeys.serviceKey}
|
|
771
473
|
`;
|
|
772
474
|
await writeFile2(
|
|
773
|
-
|
|
475
|
+
path3.join(projectPath, ".env.local"),
|
|
774
476
|
envContent
|
|
775
477
|
);
|
|
776
|
-
const gitignorePath =
|
|
478
|
+
const gitignorePath = path3.join(projectPath, ".gitignore");
|
|
777
479
|
try {
|
|
778
480
|
const gitignore = await readFile2(gitignorePath, "utf-8");
|
|
779
481
|
if (!gitignore.includes(".env.local")) {
|
|
@@ -817,6 +519,53 @@ async function setupGit(projectPath, remoteUrl) {
|
|
|
817
519
|
);
|
|
818
520
|
}
|
|
819
521
|
|
|
522
|
+
// src/ui/logger.ts
|
|
523
|
+
import chalk from "chalk";
|
|
524
|
+
var logger = {
|
|
525
|
+
info: (message) => {
|
|
526
|
+
console.log(chalk.blue("\u2139"), message);
|
|
527
|
+
},
|
|
528
|
+
success: (message) => {
|
|
529
|
+
console.log(chalk.green("\u2714"), message);
|
|
530
|
+
},
|
|
531
|
+
warning: (message) => {
|
|
532
|
+
console.log(chalk.yellow("\u26A0"), message);
|
|
533
|
+
},
|
|
534
|
+
error: (message) => {
|
|
535
|
+
console.log(chalk.red("\u2716"), message);
|
|
536
|
+
},
|
|
537
|
+
step: (step, total, message) => {
|
|
538
|
+
console.log(chalk.cyan(`[${step}/${total}]`), message);
|
|
539
|
+
},
|
|
540
|
+
newLine: () => {
|
|
541
|
+
console.log();
|
|
542
|
+
},
|
|
543
|
+
divider: () => {
|
|
544
|
+
console.log(chalk.gray("\u2500".repeat(50)));
|
|
545
|
+
},
|
|
546
|
+
title: (message) => {
|
|
547
|
+
console.log(chalk.bold.white(message));
|
|
548
|
+
},
|
|
549
|
+
subtitle: (message) => {
|
|
550
|
+
console.log(chalk.gray(message));
|
|
551
|
+
},
|
|
552
|
+
link: (label, url) => {
|
|
553
|
+
console.log(` ${chalk.gray(label + ":")} ${chalk.cyan.underline(url)}`);
|
|
554
|
+
},
|
|
555
|
+
list: (items) => {
|
|
556
|
+
items.forEach((item) => {
|
|
557
|
+
console.log(chalk.gray(" \u2022"), item);
|
|
558
|
+
});
|
|
559
|
+
},
|
|
560
|
+
table: (rows) => {
|
|
561
|
+
const maxLabelLength = Math.max(...rows.map((r) => r.label.length));
|
|
562
|
+
rows.forEach(({ label, value }) => {
|
|
563
|
+
const paddedLabel = label.padEnd(maxLabelLength);
|
|
564
|
+
console.log(` ${chalk.gray(paddedLabel)} ${value}`);
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
|
|
820
569
|
// src/ui/banner.ts
|
|
821
570
|
import boxen from "boxen";
|
|
822
571
|
import chalk2 from "chalk";
|
|
@@ -871,7 +620,7 @@ async function createProject(projectName, options = {}) {
|
|
|
871
620
|
if (!validation.valid) {
|
|
872
621
|
throw new Error(validation.error);
|
|
873
622
|
}
|
|
874
|
-
const projectPath =
|
|
623
|
+
const projectPath = path4.resolve(process.cwd(), projectName);
|
|
875
624
|
if (!await hasConfig()) {
|
|
876
625
|
logger.warning('No se encontr\xF3 configuraci\xF3n. Ejecuta "create-lft-app config" primero.');
|
|
877
626
|
throw new Error("Configuraci\xF3n no encontrada");
|
|
@@ -894,7 +643,7 @@ async function createProject(projectName, options = {}) {
|
|
|
894
643
|
logger.table(resources);
|
|
895
644
|
logger.newLine();
|
|
896
645
|
if (!options.autoConfirm) {
|
|
897
|
-
const shouldContinue = await
|
|
646
|
+
const shouldContinue = await confirm({
|
|
898
647
|
message: "\xBFContinuar con la creaci\xF3n?",
|
|
899
648
|
default: true
|
|
900
649
|
});
|
|
@@ -959,6 +708,11 @@ program.argument("[project-name]", "Nombre del proyecto a crear").option("--skip
|
|
|
959
708
|
logger.info("Uso: create-lft-app <nombre-proyecto>");
|
|
960
709
|
process.exit(1);
|
|
961
710
|
}
|
|
711
|
+
if (!await hasConfig()) {
|
|
712
|
+
logger.error("Las credenciales no est\xE1n configuradas en el paquete");
|
|
713
|
+
logger.info("Contacta al administrador del CLI");
|
|
714
|
+
process.exit(1);
|
|
715
|
+
}
|
|
962
716
|
try {
|
|
963
717
|
await createProject(projectName, {
|
|
964
718
|
skipGithub: options.skipGithub,
|
|
@@ -974,21 +728,5 @@ program.argument("[project-name]", "Nombre del proyecto a crear").option("--skip
|
|
|
974
728
|
process.exit(1);
|
|
975
729
|
}
|
|
976
730
|
});
|
|
977
|
-
program.command("config").description("Configurar credenciales de APIs").option("--show", "Mostrar configuraci\xF3n actual").option("--reset", "Resetear configuraci\xF3n").option("--github", "Configurar solo GitHub").option("--supabase", "Configurar solo Supabase").option("--jira", "Configurar solo Jira").action(async (options) => {
|
|
978
|
-
showBanner();
|
|
979
|
-
if (options.show) {
|
|
980
|
-
await showConfig();
|
|
981
|
-
return;
|
|
982
|
-
}
|
|
983
|
-
if (options.reset) {
|
|
984
|
-
await resetConfig();
|
|
985
|
-
return;
|
|
986
|
-
}
|
|
987
|
-
await configureCredentials({
|
|
988
|
-
onlyGithub: options.github,
|
|
989
|
-
onlySupabase: options.supabase,
|
|
990
|
-
onlyJira: options.jira
|
|
991
|
-
});
|
|
992
|
-
});
|
|
993
731
|
program.parse();
|
|
994
732
|
//# sourceMappingURL=cli.js.map
|