@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/src/index.js
CHANGED
|
@@ -1,170 +1,82 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import
|
|
5
|
-
import { confirm
|
|
6
|
-
|
|
7
|
-
// src/config/index.ts
|
|
8
|
-
import fs from "fs/promises";
|
|
9
|
-
import path from "path";
|
|
10
|
-
import os from "os";
|
|
11
|
-
import { createCipheriv, createDecipheriv, randomBytes, createHash } from "crypto";
|
|
12
|
-
import nodeMachineId from "node-machine-id";
|
|
13
|
-
import { input, password, select, confirm } from "@inquirer/prompts";
|
|
4
|
+
import path4 from "path";
|
|
5
|
+
import { confirm } from "@inquirer/prompts";
|
|
14
6
|
|
|
15
|
-
// src/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
success: (message) => {
|
|
22
|
-
console.log(chalk.green("\u2714"), message);
|
|
23
|
-
},
|
|
24
|
-
warning: (message) => {
|
|
25
|
-
console.log(chalk.yellow("\u26A0"), message);
|
|
26
|
-
},
|
|
27
|
-
error: (message) => {
|
|
28
|
-
console.log(chalk.red("\u2716"), message);
|
|
29
|
-
},
|
|
30
|
-
step: (step, total, message) => {
|
|
31
|
-
console.log(chalk.cyan(`[${step}/${total}]`), message);
|
|
32
|
-
},
|
|
33
|
-
newLine: () => {
|
|
34
|
-
console.log();
|
|
7
|
+
// src/config/static-config.ts
|
|
8
|
+
var STATIC_CONFIG = {
|
|
9
|
+
github: {
|
|
10
|
+
token: "__GITHUB_TOKEN__",
|
|
11
|
+
username: "__GITHUB_USERNAME__",
|
|
12
|
+
org: "__GITHUB_ORG__"
|
|
35
13
|
},
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
console.log(chalk.bold.white(message));
|
|
41
|
-
},
|
|
42
|
-
subtitle: (message) => {
|
|
43
|
-
console.log(chalk.gray(message));
|
|
44
|
-
},
|
|
45
|
-
link: (label, url) => {
|
|
46
|
-
console.log(` ${chalk.gray(label + ":")} ${chalk.cyan.underline(url)}`);
|
|
47
|
-
},
|
|
48
|
-
list: (items) => {
|
|
49
|
-
items.forEach((item) => {
|
|
50
|
-
console.log(chalk.gray(" \u2022"), item);
|
|
51
|
-
});
|
|
14
|
+
supabase: {
|
|
15
|
+
token: "__SUPABASE_TOKEN__",
|
|
16
|
+
orgId: "__SUPABASE_ORG_ID__",
|
|
17
|
+
region: "us-east-1"
|
|
52
18
|
},
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
console.log(` ${chalk.gray(paddedLabel)} ${value}`);
|
|
58
|
-
});
|
|
19
|
+
jira: {
|
|
20
|
+
email: "__JIRA_EMAIL__",
|
|
21
|
+
token: "__JIRA_TOKEN__",
|
|
22
|
+
domain: "__JIRA_DOMAIN__"
|
|
59
23
|
}
|
|
60
24
|
};
|
|
61
25
|
|
|
62
|
-
// src/ui/spinner.ts
|
|
63
|
-
import ora from "ora";
|
|
64
|
-
function createSpinner(text) {
|
|
65
|
-
return ora({
|
|
66
|
-
text,
|
|
67
|
-
spinner: "dots"
|
|
68
|
-
});
|
|
69
|
-
}
|
|
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;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
26
|
// src/config/index.ts
|
|
83
|
-
|
|
84
|
-
var CONFIG_FILE = path.join(os.homedir(), ".lftrc");
|
|
85
|
-
var ALGORITHM = "aes-256-gcm";
|
|
86
|
-
function getEncryptionKey() {
|
|
87
|
-
const machineId = machineIdSync();
|
|
88
|
-
return createHash("sha256").update(machineId + "lft-secret").digest();
|
|
89
|
-
}
|
|
90
|
-
function decryptConfig(encrypted) {
|
|
91
|
-
const { iv, tag, data } = JSON.parse(encrypted);
|
|
92
|
-
const key = getEncryptionKey();
|
|
93
|
-
const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(iv, "hex"));
|
|
94
|
-
decipher.setAuthTag(Buffer.from(tag, "hex"));
|
|
95
|
-
let decrypted = decipher.update(data, "hex", "utf8");
|
|
96
|
-
decrypted += decipher.final("utf8");
|
|
97
|
-
return JSON.parse(decrypted);
|
|
98
|
-
}
|
|
99
|
-
var ENV_VARS = {
|
|
100
|
-
GITHUB_TOKEN: "LFT_GITHUB_TOKEN",
|
|
101
|
-
GITHUB_USERNAME: "LFT_GITHUB_USERNAME",
|
|
102
|
-
GITHUB_ORG: "LFT_GITHUB_ORG",
|
|
103
|
-
SUPABASE_TOKEN: "LFT_SUPABASE_TOKEN",
|
|
104
|
-
SUPABASE_ORG_ID: "LFT_SUPABASE_ORG_ID",
|
|
105
|
-
SUPABASE_REGION: "LFT_SUPABASE_REGION",
|
|
106
|
-
JIRA_EMAIL: "LFT_JIRA_EMAIL",
|
|
107
|
-
JIRA_TOKEN: "LFT_JIRA_TOKEN",
|
|
108
|
-
JIRA_DOMAIN: "LFT_JIRA_DOMAIN"
|
|
109
|
-
};
|
|
110
|
-
function loadConfigFromEnv() {
|
|
111
|
-
const githubToken = process.env[ENV_VARS.GITHUB_TOKEN];
|
|
112
|
-
const githubUsername = process.env[ENV_VARS.GITHUB_USERNAME];
|
|
113
|
-
const supabaseToken = process.env[ENV_VARS.SUPABASE_TOKEN];
|
|
114
|
-
const supabaseOrgId = process.env[ENV_VARS.SUPABASE_ORG_ID];
|
|
115
|
-
const jiraEmail = process.env[ENV_VARS.JIRA_EMAIL];
|
|
116
|
-
const jiraToken = process.env[ENV_VARS.JIRA_TOKEN];
|
|
117
|
-
const jiraDomain = process.env[ENV_VARS.JIRA_DOMAIN];
|
|
118
|
-
if (!githubToken || !supabaseToken || !jiraToken) {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
27
|
+
async function loadConfig() {
|
|
121
28
|
return {
|
|
122
29
|
version: "1.0.0",
|
|
123
30
|
credentials: {
|
|
124
31
|
github: {
|
|
125
|
-
token:
|
|
126
|
-
username:
|
|
32
|
+
token: STATIC_CONFIG.github.token,
|
|
33
|
+
username: STATIC_CONFIG.github.username
|
|
127
34
|
},
|
|
128
35
|
supabase: {
|
|
129
|
-
accessToken:
|
|
130
|
-
organizationId:
|
|
36
|
+
accessToken: STATIC_CONFIG.supabase.token,
|
|
37
|
+
organizationId: STATIC_CONFIG.supabase.orgId
|
|
131
38
|
},
|
|
132
39
|
jira: {
|
|
133
|
-
email:
|
|
134
|
-
apiToken:
|
|
135
|
-
domain:
|
|
40
|
+
email: STATIC_CONFIG.jira.email,
|
|
41
|
+
apiToken: STATIC_CONFIG.jira.token,
|
|
42
|
+
domain: STATIC_CONFIG.jira.domain
|
|
136
43
|
}
|
|
137
44
|
},
|
|
138
45
|
defaults: {
|
|
139
|
-
githubOrg:
|
|
140
|
-
supabaseRegion:
|
|
46
|
+
githubOrg: STATIC_CONFIG.github.org || void 0,
|
|
47
|
+
supabaseRegion: STATIC_CONFIG.supabase.region,
|
|
141
48
|
jiraProjectType: "software"
|
|
142
49
|
}
|
|
143
50
|
};
|
|
144
51
|
}
|
|
145
|
-
function hasEnvConfig() {
|
|
146
|
-
return !!(process.env[ENV_VARS.GITHUB_TOKEN] && process.env[ENV_VARS.SUPABASE_TOKEN] && process.env[ENV_VARS.JIRA_TOKEN]);
|
|
147
|
-
}
|
|
148
52
|
async function hasConfig() {
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
await fs.access(CONFIG_FILE);
|
|
152
|
-
return true;
|
|
153
|
-
} catch {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
53
|
+
return STATIC_CONFIG.github.token !== "__GITHUB_TOKEN__" && STATIC_CONFIG.supabase.token !== "__SUPABASE_TOKEN__" && STATIC_CONFIG.jira.token !== "__JIRA_TOKEN__";
|
|
156
54
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
55
|
+
|
|
56
|
+
// src/services/github.ts
|
|
57
|
+
import { Octokit } from "octokit";
|
|
58
|
+
|
|
59
|
+
// src/ui/spinner.ts
|
|
60
|
+
import ora from "ora";
|
|
61
|
+
function createSpinner(text) {
|
|
62
|
+
return ora({
|
|
63
|
+
text,
|
|
64
|
+
spinner: "dots"
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async function withSpinner(text, fn, successText) {
|
|
68
|
+
const spinner = createSpinner(text).start();
|
|
69
|
+
try {
|
|
70
|
+
const result = await fn();
|
|
71
|
+
spinner.succeed(successText || text);
|
|
72
|
+
return result;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
spinner.fail();
|
|
75
|
+
throw error;
|
|
161
76
|
}
|
|
162
|
-
const encrypted = await fs.readFile(CONFIG_FILE, "utf8");
|
|
163
|
-
return decryptConfig(encrypted);
|
|
164
77
|
}
|
|
165
78
|
|
|
166
79
|
// src/services/github.ts
|
|
167
|
-
import { Octokit } from "octokit";
|
|
168
80
|
async function createGitHubRepo(projectName, config) {
|
|
169
81
|
const octokit = new Octokit({ auth: config.credentials.github.token });
|
|
170
82
|
const org = config.defaults.githubOrg;
|
|
@@ -270,16 +182,16 @@ async function createSupabaseProject(projectName, config) {
|
|
|
270
182
|
}
|
|
271
183
|
function generateSecurePassword() {
|
|
272
184
|
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
|
|
273
|
-
let
|
|
185
|
+
let password = "";
|
|
274
186
|
for (let i = 0; i < 32; i++) {
|
|
275
|
-
|
|
187
|
+
password += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
276
188
|
}
|
|
277
|
-
return
|
|
189
|
+
return password;
|
|
278
190
|
}
|
|
279
191
|
|
|
280
192
|
// src/utils/validation.ts
|
|
281
|
-
import
|
|
282
|
-
import
|
|
193
|
+
import fs from "fs";
|
|
194
|
+
import path from "path";
|
|
283
195
|
function validateProjectName(name) {
|
|
284
196
|
if (!name || name.trim() === "") {
|
|
285
197
|
return { valid: false, error: "El nombre del proyecto no puede estar vac\xEDo" };
|
|
@@ -303,8 +215,8 @@ function validateProjectName(name) {
|
|
|
303
215
|
if (name.length > 50) {
|
|
304
216
|
return { valid: false, error: "El nombre no puede tener m\xE1s de 50 caracteres" };
|
|
305
217
|
}
|
|
306
|
-
const projectPath =
|
|
307
|
-
if (
|
|
218
|
+
const projectPath = path.resolve(process.cwd(), name);
|
|
219
|
+
if (fs.existsSync(projectPath)) {
|
|
308
220
|
return { valid: false, error: `El directorio "${name}" ya existe` };
|
|
309
221
|
}
|
|
310
222
|
return { valid: true };
|
|
@@ -408,59 +320,59 @@ async function scaffoldNextJs(projectName, projectPath) {
|
|
|
408
320
|
|
|
409
321
|
// src/steps/copy-template.ts
|
|
410
322
|
import { cp, mkdir, readFile, writeFile } from "fs/promises";
|
|
411
|
-
import
|
|
323
|
+
import path2 from "path";
|
|
412
324
|
import { fileURLToPath } from "url";
|
|
413
325
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
414
|
-
var __dirname2 =
|
|
326
|
+
var __dirname2 = path2.dirname(__filename2);
|
|
415
327
|
async function copyTemplate(projectPath) {
|
|
416
328
|
await withSpinner(
|
|
417
329
|
"Copiando template LFT...",
|
|
418
330
|
async () => {
|
|
419
|
-
const templatesDir =
|
|
420
|
-
const srcDir =
|
|
331
|
+
const templatesDir = path2.join(__dirname2, "..", "..", "templates");
|
|
332
|
+
const srcDir = path2.join(projectPath, "src");
|
|
421
333
|
await cp(
|
|
422
|
-
|
|
423
|
-
|
|
334
|
+
path2.join(templatesDir, "components", "ui"),
|
|
335
|
+
path2.join(srcDir, "components", "ui"),
|
|
424
336
|
{ recursive: true }
|
|
425
337
|
);
|
|
426
338
|
await cp(
|
|
427
|
-
|
|
428
|
-
|
|
339
|
+
path2.join(templatesDir, "components", "layout"),
|
|
340
|
+
path2.join(srcDir, "components", "layout"),
|
|
429
341
|
{ recursive: true }
|
|
430
342
|
);
|
|
431
343
|
await cp(
|
|
432
|
-
|
|
433
|
-
|
|
344
|
+
path2.join(templatesDir, "components", "dashboard"),
|
|
345
|
+
path2.join(srcDir, "components", "dashboard"),
|
|
434
346
|
{ recursive: true }
|
|
435
347
|
);
|
|
436
|
-
await mkdir(
|
|
348
|
+
await mkdir(path2.join(srcDir, "lib"), { recursive: true });
|
|
437
349
|
await cp(
|
|
438
|
-
|
|
439
|
-
|
|
350
|
+
path2.join(templatesDir, "lib", "utils.ts"),
|
|
351
|
+
path2.join(srcDir, "lib", "utils.ts")
|
|
440
352
|
);
|
|
441
|
-
await mkdir(
|
|
353
|
+
await mkdir(path2.join(srcDir, "hooks"), { recursive: true });
|
|
442
354
|
await cp(
|
|
443
|
-
|
|
444
|
-
|
|
355
|
+
path2.join(templatesDir, "hooks"),
|
|
356
|
+
path2.join(srcDir, "hooks"),
|
|
445
357
|
{ recursive: true }
|
|
446
358
|
);
|
|
447
359
|
await cp(
|
|
448
|
-
|
|
449
|
-
|
|
360
|
+
path2.join(templatesDir, "app", "layout.tsx"),
|
|
361
|
+
path2.join(srcDir, "app", "layout.tsx")
|
|
450
362
|
);
|
|
451
363
|
await cp(
|
|
452
|
-
|
|
453
|
-
|
|
364
|
+
path2.join(templatesDir, "app", "page.tsx"),
|
|
365
|
+
path2.join(srcDir, "app", "page.tsx")
|
|
454
366
|
);
|
|
455
|
-
await mkdir(
|
|
367
|
+
await mkdir(path2.join(srcDir, "app", "dashboard"), { recursive: true });
|
|
456
368
|
await cp(
|
|
457
|
-
|
|
458
|
-
|
|
369
|
+
path2.join(templatesDir, "app", "dashboard", "page.tsx"),
|
|
370
|
+
path2.join(srcDir, "app", "dashboard", "page.tsx")
|
|
459
371
|
);
|
|
460
|
-
await mkdir(
|
|
372
|
+
await mkdir(path2.join(srcDir, "app", "auth", "login"), { recursive: true });
|
|
461
373
|
await cp(
|
|
462
|
-
|
|
463
|
-
|
|
374
|
+
path2.join(templatesDir, "app", "auth", "login", "page.tsx"),
|
|
375
|
+
path2.join(srcDir, "app", "auth", "login", "page.tsx")
|
|
464
376
|
);
|
|
465
377
|
await mergeGlobalStyles(projectPath, templatesDir);
|
|
466
378
|
},
|
|
@@ -468,8 +380,8 @@ async function copyTemplate(projectPath) {
|
|
|
468
380
|
);
|
|
469
381
|
}
|
|
470
382
|
async function mergeGlobalStyles(projectPath, templatesDir) {
|
|
471
|
-
const templateCssPath =
|
|
472
|
-
const projectCssPath =
|
|
383
|
+
const templateCssPath = path2.join(templatesDir, "app", "globals.css");
|
|
384
|
+
const projectCssPath = path2.join(projectPath, "src", "app", "globals.css");
|
|
473
385
|
try {
|
|
474
386
|
const templateCss = await readFile(templateCssPath, "utf-8");
|
|
475
387
|
const existingCss = await readFile(projectCssPath, "utf-8");
|
|
@@ -546,7 +458,7 @@ async function installDependencies(projectPath) {
|
|
|
546
458
|
|
|
547
459
|
// src/steps/create-env.ts
|
|
548
460
|
import { writeFile as writeFile2, readFile as readFile2, appendFile } from "fs/promises";
|
|
549
|
-
import
|
|
461
|
+
import path3 from "path";
|
|
550
462
|
async function createEnvFile(projectPath, supabaseKeys) {
|
|
551
463
|
await withSpinner(
|
|
552
464
|
"Creando archivo .env.local...",
|
|
@@ -557,10 +469,10 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=${supabaseKeys.anonKey}
|
|
|
557
469
|
SUPABASE_SERVICE_ROLE_KEY=${supabaseKeys.serviceKey}
|
|
558
470
|
`;
|
|
559
471
|
await writeFile2(
|
|
560
|
-
|
|
472
|
+
path3.join(projectPath, ".env.local"),
|
|
561
473
|
envContent
|
|
562
474
|
);
|
|
563
|
-
const gitignorePath =
|
|
475
|
+
const gitignorePath = path3.join(projectPath, ".gitignore");
|
|
564
476
|
try {
|
|
565
477
|
const gitignore = await readFile2(gitignorePath, "utf-8");
|
|
566
478
|
if (!gitignore.includes(".env.local")) {
|
|
@@ -604,6 +516,53 @@ async function setupGit(projectPath, remoteUrl) {
|
|
|
604
516
|
);
|
|
605
517
|
}
|
|
606
518
|
|
|
519
|
+
// src/ui/logger.ts
|
|
520
|
+
import chalk from "chalk";
|
|
521
|
+
var logger = {
|
|
522
|
+
info: (message) => {
|
|
523
|
+
console.log(chalk.blue("\u2139"), message);
|
|
524
|
+
},
|
|
525
|
+
success: (message) => {
|
|
526
|
+
console.log(chalk.green("\u2714"), message);
|
|
527
|
+
},
|
|
528
|
+
warning: (message) => {
|
|
529
|
+
console.log(chalk.yellow("\u26A0"), message);
|
|
530
|
+
},
|
|
531
|
+
error: (message) => {
|
|
532
|
+
console.log(chalk.red("\u2716"), message);
|
|
533
|
+
},
|
|
534
|
+
step: (step, total, message) => {
|
|
535
|
+
console.log(chalk.cyan(`[${step}/${total}]`), message);
|
|
536
|
+
},
|
|
537
|
+
newLine: () => {
|
|
538
|
+
console.log();
|
|
539
|
+
},
|
|
540
|
+
divider: () => {
|
|
541
|
+
console.log(chalk.gray("\u2500".repeat(50)));
|
|
542
|
+
},
|
|
543
|
+
title: (message) => {
|
|
544
|
+
console.log(chalk.bold.white(message));
|
|
545
|
+
},
|
|
546
|
+
subtitle: (message) => {
|
|
547
|
+
console.log(chalk.gray(message));
|
|
548
|
+
},
|
|
549
|
+
link: (label, url) => {
|
|
550
|
+
console.log(` ${chalk.gray(label + ":")} ${chalk.cyan.underline(url)}`);
|
|
551
|
+
},
|
|
552
|
+
list: (items) => {
|
|
553
|
+
items.forEach((item) => {
|
|
554
|
+
console.log(chalk.gray(" \u2022"), item);
|
|
555
|
+
});
|
|
556
|
+
},
|
|
557
|
+
table: (rows) => {
|
|
558
|
+
const maxLabelLength = Math.max(...rows.map((r) => r.label.length));
|
|
559
|
+
rows.forEach(({ label, value }) => {
|
|
560
|
+
const paddedLabel = label.padEnd(maxLabelLength);
|
|
561
|
+
console.log(` ${chalk.gray(paddedLabel)} ${value}`);
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
|
|
607
566
|
// src/ui/banner.ts
|
|
608
567
|
import boxen from "boxen";
|
|
609
568
|
import chalk2 from "chalk";
|
|
@@ -645,7 +604,7 @@ async function createProject(projectName, options = {}) {
|
|
|
645
604
|
if (!validation.valid) {
|
|
646
605
|
throw new Error(validation.error);
|
|
647
606
|
}
|
|
648
|
-
const projectPath =
|
|
607
|
+
const projectPath = path4.resolve(process.cwd(), projectName);
|
|
649
608
|
if (!await hasConfig()) {
|
|
650
609
|
logger.warning('No se encontr\xF3 configuraci\xF3n. Ejecuta "create-lft-app config" primero.');
|
|
651
610
|
throw new Error("Configuraci\xF3n no encontrada");
|
|
@@ -668,7 +627,7 @@ async function createProject(projectName, options = {}) {
|
|
|
668
627
|
logger.table(resources);
|
|
669
628
|
logger.newLine();
|
|
670
629
|
if (!options.autoConfirm) {
|
|
671
|
-
const shouldContinue = await
|
|
630
|
+
const shouldContinue = await confirm({
|
|
672
631
|
message: "\xBFContinuar con la creaci\xF3n?",
|
|
673
632
|
default: true
|
|
674
633
|
});
|