fwinit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # fwinit
2
+
3
+ CLI para crear proyectos desde templates. Instalás una vez y scaffoldás proyectos con un comando.
4
+
5
+ ## Instalación
6
+
7
+ ```bash
8
+ npm install -g fwinit
9
+ # o
10
+ bun add -g fwinit
11
+ ```
12
+
13
+ ## Uso
14
+
15
+ ### Modo directo
16
+
17
+ ```bash
18
+ fwinit fastify mi-api
19
+ fwinit aspnet mi-app
20
+ fwinit react-native mi-mobile
21
+ ```
22
+
23
+ ### Modo interactivo
24
+
25
+ ```bash
26
+ fwinit
27
+ ```
28
+
29
+ Te pregunta qué template usar y el nombre del proyecto.
30
+
31
+ ### Ver templates disponibles
32
+
33
+ ```bash
34
+ fwinit list
35
+ ```
36
+
37
+ ### Help
38
+
39
+ ```bash
40
+ fwinit --help
41
+ ```
42
+
43
+ ## Templates disponibles
44
+
45
+ | Template | Descripción | Runtime |
46
+ |----------|-------------|---------|
47
+ | `aspnet` | API REST con ASP.NET Core, Clean Architecture y C# | dotnet |
48
+ | `express` | API REST con Express, Prisma, TypeScript y Bun | bun |
49
+ | `fastify` | API REST con Fastify, Prisma, TypeScript y Bun | bun |
50
+ | `nodejs` | API con Node.js puro, Prisma, TypeScript y tsx | node |
51
+ | `react-native` | App móvil con React Native, Expo y TypeScript | node |
52
+
53
+ ## Qué hace
54
+
55
+ 1. Descarga el ZIP del repositorio de templates desde GitHub
56
+ 2. Extrae el template seleccionado
57
+ 3. Lo copia al directorio del proyecto
58
+ 4. Limpia los lock files (cada proyecto instala los suyos)
59
+ 5. Te muestra los próximos pasos (install y dev)
60
+
61
+ ## Desarrollo
62
+
63
+ ```bash
64
+ bun install
65
+ bun run build # build a dist/
66
+ bun run dev # watch mode
67
+ bun run start # ejecutar desde dist/
68
+ bun run typecheck # type check
69
+ ```
70
+
71
+ ### Probar localmente
72
+
73
+ ```bash
74
+ node dist/index.js list
75
+ node dist/index.js fastify mi-api
76
+ ```
77
+
78
+ ### Link global para desarrollo
79
+
80
+ ```bash
81
+ bun link
82
+ # ahora `fwinit` está disponible en todo el sistema
83
+ ```
84
+
85
+ ## Licencia
86
+
87
+ MIT
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import ora from "ora";
6
+ import chalk from "chalk";
7
+
8
+ // src/templates.ts
9
+ var TEMPLATES = [
10
+ {
11
+ name: "ASP.NET",
12
+ alias: ["aspnet", "dotnet", "csharp"],
13
+ description: "API REST con ASP.NET Core, Clean Architecture y C#",
14
+ folder: "ASPNET"
15
+ },
16
+ {
17
+ name: "Express",
18
+ alias: ["express"],
19
+ description: "API REST con Express, Prisma, TypeScript y Bun",
20
+ folder: "EXPRESS"
21
+ },
22
+ {
23
+ name: "Fastify",
24
+ alias: ["fastify"],
25
+ description: "API REST con Fastify, Prisma, TypeScript y Bun",
26
+ folder: "FASTIFY"
27
+ },
28
+ {
29
+ name: "Node.js Vanilla",
30
+ alias: ["nodejs", "vanilla", "node"],
31
+ description: "API con Node.js puro, Prisma, TypeScript y tsx",
32
+ folder: "NODEJS-VANILLA"
33
+ },
34
+ {
35
+ name: "React Native",
36
+ alias: ["react-native", "rn", "expo"],
37
+ description: "App m\xF3vil con React Native, Expo y TypeScript",
38
+ folder: "REACT-NATIVE"
39
+ }
40
+ ];
41
+ function findTemplate(input) {
42
+ const normalized = input.toLowerCase().trim();
43
+ return TEMPLATES.find(
44
+ (t) => t.folder.toLowerCase() === normalized || t.alias.includes(normalized)
45
+ );
46
+ }
47
+
48
+ // src/downloader.ts
49
+ import AdmZip from "adm-zip";
50
+ import { mkdir, rm, cp, readdir } from "fs/promises";
51
+ import { join } from "path";
52
+ import { tmpdir } from "os";
53
+ import { randomBytes } from "crypto";
54
+ var GITHUB_REPO = "orlandotellez/fwinit";
55
+ var GITHUB_BRANCH = "main";
56
+ async function downloadAndExtract(templateFolder) {
57
+ const url = `https://github.com/${GITHUB_REPO}/archive/refs/heads/${GITHUB_BRANCH}.zip`;
58
+ const response = await fetch(url);
59
+ if (!response.ok) {
60
+ throw new Error(
61
+ `Failed to download template: ${response.statusText}`
62
+ );
63
+ }
64
+ const buffer = Buffer.from(await response.arrayBuffer());
65
+ const zip = new AdmZip(buffer);
66
+ const tempId = randomBytes(8).toString("hex");
67
+ const tempDir = join(tmpdir(), `fwinit-${tempId}`);
68
+ await mkdir(tempDir, { recursive: true });
69
+ zip.extractAllTo(tempDir, true);
70
+ const repoRoot = join(tempDir, `fwinit-${GITHUB_BRANCH}`);
71
+ const templatePath = join(repoRoot, "templates", templateFolder);
72
+ try {
73
+ await readdir(templatePath);
74
+ } catch {
75
+ await rm(tempDir, { recursive: true, force: true });
76
+ throw new Error(
77
+ `Template "${templateFolder}" not found in repository`
78
+ );
79
+ }
80
+ return { tempDir, templatePath };
81
+ }
82
+ async function copyTemplate(source, destination) {
83
+ await cp(source, destination, { recursive: true });
84
+ await cleanTemplate(destination);
85
+ }
86
+ async function cleanTemplate(dir) {
87
+ const lockFiles = [
88
+ "bun.lock",
89
+ "pnpm-lock.yaml",
90
+ "package-lock.json",
91
+ "yarn.lock"
92
+ ];
93
+ for (const file of lockFiles) {
94
+ const path = join(dir, file);
95
+ try {
96
+ await rm(path, { force: true });
97
+ } catch {
98
+ }
99
+ }
100
+ }
101
+ async function cleanup(tempDir) {
102
+ await rm(tempDir, { recursive: true, force: true });
103
+ }
104
+
105
+ // src/prompts.ts
106
+ import inquirer from "inquirer";
107
+ async function selectTemplate() {
108
+ const { template } = await inquirer.prompt([
109
+ {
110
+ type: "list",
111
+ name: "template",
112
+ message: "What project do you want to create?",
113
+ choices: TEMPLATES.map((t) => ({
114
+ name: `${t.name} \u2014 ${t.description}`,
115
+ value: t
116
+ }))
117
+ }
118
+ ]);
119
+ return template;
120
+ }
121
+ async function askProjectName() {
122
+ const { name } = await inquirer.prompt([
123
+ {
124
+ type: "input",
125
+ name: "name",
126
+ message: "Project name:",
127
+ validate: (input) => {
128
+ if (!input.trim()) return "Name cannot be empty";
129
+ if (!/^[a-zA-Z0-9_-]+$/.test(input)) {
130
+ return "Only letters, numbers, hyphens and underscores";
131
+ }
132
+ return true;
133
+ }
134
+ }
135
+ ]);
136
+ return name.trim();
137
+ }
138
+
139
+ // src/utils.ts
140
+ import { existsSync } from "fs";
141
+ import { resolve } from "path";
142
+ function getProjectPath(name) {
143
+ return resolve(process.cwd(), name);
144
+ }
145
+ function projectExists(name) {
146
+ return existsSync(getProjectPath(name));
147
+ }
148
+ function printSuccess(projectName, templateName, postInit) {
149
+ const installCmd = postInit?.install || "npm install";
150
+ const devCmd = postInit?.dev || "npm run dev";
151
+ console.log(`
152
+ \x1B[32m\u2714\x1B[0m Template downloaded
153
+ \x1B[32m\u2714\x1B[0m Project created in ./${projectName}
154
+
155
+ \x1B[1mNext steps:\x1B[0m
156
+
157
+ \x1B[36mcd ${projectName}\x1B[0m
158
+ \x1B[36m${installCmd}\x1B[0m
159
+ \x1B[36m${devCmd}\x1B[0m
160
+ `);
161
+ }
162
+
163
+ // src/index.ts
164
+ var program = new Command();
165
+ program.name("fwinit").description("CLI to create projects from templates").version("1.0.0");
166
+ program.command("list").description("Show available templates").action(() => {
167
+ console.log(
168
+ chalk.bold("\nAvailable templates:\n")
169
+ );
170
+ TEMPLATES.forEach((t) => {
171
+ console.log(
172
+ ` ${chalk.cyan(t.folder.toLowerCase())} \u2014 ${t.description}`
173
+ );
174
+ });
175
+ console.log();
176
+ });
177
+ program.argument("[template]", "Template to use").argument("[project-name]", "Project name").action(
178
+ async (templateArg, projectNameArg) => {
179
+ try {
180
+ let template = templateArg ? findTemplate(templateArg) : void 0;
181
+ let projectName = projectNameArg;
182
+ if (templateArg && !template) {
183
+ console.error(
184
+ chalk.red(
185
+ `
186
+ \u2716 Template "${templateArg}" not found.
187
+ `
188
+ )
189
+ );
190
+ console.log("Available templates:");
191
+ TEMPLATES.forEach(
192
+ (t) => console.log(` - ${t.folder.toLowerCase()}`)
193
+ );
194
+ process.exit(1);
195
+ }
196
+ if (!template) {
197
+ template = await selectTemplate();
198
+ }
199
+ if (!projectName) {
200
+ projectName = await askProjectName();
201
+ }
202
+ if (projectExists(projectName)) {
203
+ console.error(
204
+ chalk.red(
205
+ `
206
+ \u2716 Directory "${projectName}" already exists.`
207
+ )
208
+ );
209
+ process.exit(1);
210
+ }
211
+ const spinner = ora(
212
+ `Downloading ${template.name} template...`
213
+ ).start();
214
+ const { tempDir, templatePath } = await downloadAndExtract(template.folder);
215
+ spinner.text = "Creating project...";
216
+ await copyTemplate(
217
+ templatePath,
218
+ getProjectPath(projectName)
219
+ );
220
+ await cleanup(tempDir);
221
+ spinner.succeed("Template downloaded");
222
+ let postInit;
223
+ try {
224
+ const { readFile } = await import("fs/promises");
225
+ const { join: join2 } = await import("path");
226
+ const tj = JSON.parse(
227
+ await readFile(
228
+ join2(getProjectPath(projectName), "template.json"),
229
+ "utf-8"
230
+ )
231
+ );
232
+ postInit = tj.postInit;
233
+ } catch {
234
+ }
235
+ printSuccess(projectName, template.name, postInit);
236
+ } catch (error) {
237
+ console.error(
238
+ chalk.red(
239
+ `
240
+ \u2716 Error: ${error.message}`
241
+ )
242
+ );
243
+ process.exit(1);
244
+ }
245
+ }
246
+ );
247
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "fwinit",
3
+ "version": "1.0.0",
4
+ "description": "CLI para crear proyectos desde templates",
5
+ "type": "module",
6
+ "bin": {
7
+ "fwinit": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsup src/index.ts --format esm --dts",
14
+ "dev": "tsup src/index.ts --format esm --watch",
15
+ "start": "node dist/index.js",
16
+ "typecheck": "tsc --noEmit",
17
+ "prepublishOnly": "chmod +x dist/index.js"
18
+ },
19
+ "dependencies": {
20
+ "adm-zip": "^0.5.16",
21
+ "chalk": "^5.4.1",
22
+ "commander": "^13.1.0",
23
+ "inquirer": "^12.4.2",
24
+ "ora": "^8.2.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/adm-zip": "^0.5.7",
28
+ "@types/node": "^22.13.10",
29
+ "tsup": "^8.4.0",
30
+ "typescript": "^5.8.2"
31
+ },
32
+ "keywords": [
33
+ "cli",
34
+ "templates",
35
+ "scaffolding",
36
+ "framework",
37
+ "boilerplate"
38
+ ],
39
+ "author": "Orlando Téllez",
40
+ "license": "MIT"
41
+ }