fwinit 1.4.2 → 1.4.4
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 +2 -2
- package/dist/index.js +113 -53
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ mi-proyecto/
|
|
|
66
66
|
├── backend/ # API (template elegido — trae su DB: Prisma o EF)
|
|
67
67
|
├── frontend/ # App (template frontend elegido)
|
|
68
68
|
├── .gitignore # .atl/ · odd (.opencode/ y .pi/ SÍ se versionan)
|
|
69
|
-
├── .opencode/ # Skills de opencode (create-specs
|
|
69
|
+
├── .opencode/ # Skills de opencode (create-specs, create-specs-from-code y biblioteca de diseño)
|
|
70
70
|
├── .pi/ # Mismo bundle de skills para Pi (badlogic)
|
|
71
71
|
├── README.md # Cómo correr backend y frontend
|
|
72
72
|
└── specs/ # La creás con /create-specs
|
|
@@ -123,7 +123,7 @@ El runtime `bun` de un template se porta automáticamente a `node` (npm/pnpm) si
|
|
|
123
123
|
2. Extrae y copia el template (o los dos, `backend/` + `frontend/`) al proyecto
|
|
124
124
|
3. Sustituye el nombre del proyecto (package.json, app.json de Expo, proyectos ASP.NET con "Example")
|
|
125
125
|
4. Si corresponde, porta el template de runtime bun a node (npm/pnpm)
|
|
126
|
-
5. Instala `.opencode/` y `.pi/` con
|
|
126
|
+
5. Instala `.opencode/` y `.pi/` con las skills `create-specs` y `create-specs-from-code`, sus comandos (`/create-specs` rutea entre ambos según el estado del repo) y la biblioteca de diseño (mismo bundle para opencode y Pi: elegí el agente que quieras)
|
|
127
127
|
6. Limpia los lock files (cada proyecto instala los suyos)
|
|
128
128
|
7. Si lo elegís (o pasás `--git`), inicializa un repositorio git en la raíz
|
|
129
129
|
8. Te muestra los próximos pasos (install y dev de cada carpeta)
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Command } from "commander";
|
|
|
5
5
|
import ora from "ora";
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import { readFileSync } from "fs";
|
|
8
|
-
import { mkdir as mkdir2, cp as cp2,
|
|
8
|
+
import { mkdir as mkdir2, cp as cp2, readFile as readFile2 } from "fs/promises";
|
|
9
9
|
import { join as join2, basename } from "path";
|
|
10
10
|
|
|
11
11
|
// src/templates.ts
|
|
@@ -228,6 +228,96 @@ function toPascalCase(str) {
|
|
|
228
228
|
return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
// src/agent-bundle.ts
|
|
232
|
+
var AGENT_BUNDLE = [
|
|
233
|
+
{
|
|
234
|
+
kind: "skill",
|
|
235
|
+
dir: "create-specs",
|
|
236
|
+
required: true,
|
|
237
|
+
// Los ejemplos de prompts viajan a <agent>/skills/create-specs/examples/
|
|
238
|
+
// para que la skill pueda mostrarlos dentro del proyecto generado.
|
|
239
|
+
extras: ["examples"]
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
// Skill espejo para proyectos que YA existen: misma familia, el comando
|
|
243
|
+
// /create-specs rutea entre las dos según el estado del repo. Comparte el
|
|
244
|
+
// Spec Tree Contract con create-specs, así que no duplica ejemplos: los
|
|
245
|
+
// referencia desde ahí.
|
|
246
|
+
kind: "skill",
|
|
247
|
+
dir: "create-specs-from-code",
|
|
248
|
+
required: true
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
// Biblioteca de estilos: skills/design/ con una subskill por estilo y sus
|
|
252
|
+
// commands (/design, /design-<estilo>) que leen specs/frontend/02-design.md.
|
|
253
|
+
kind: "library",
|
|
254
|
+
dir: "design",
|
|
255
|
+
required: false
|
|
256
|
+
}
|
|
257
|
+
];
|
|
258
|
+
async function exists(path) {
|
|
259
|
+
try {
|
|
260
|
+
const { access } = await import("fs/promises");
|
|
261
|
+
await access(path);
|
|
262
|
+
return true;
|
|
263
|
+
} catch {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async function listMarkdown(dir) {
|
|
268
|
+
try {
|
|
269
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
270
|
+
return (await readdir2(dir)).filter((f) => f.endsWith(".md"));
|
|
271
|
+
} catch {
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
async function planAgentBundle(repoRoot, bundle = AGENT_BUNDLE) {
|
|
276
|
+
const { join: join3 } = await import("path");
|
|
277
|
+
const installs = [];
|
|
278
|
+
const skillDirs = [];
|
|
279
|
+
const missingRequired = [];
|
|
280
|
+
for (const entry of bundle) {
|
|
281
|
+
const src = join3(repoRoot, "skills", entry.dir);
|
|
282
|
+
if (entry.kind === "library") {
|
|
283
|
+
if (!await exists(src)) {
|
|
284
|
+
if (entry.required) missingRequired.push(entry.dir);
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
installs.push({ from: src, to: `skills/${entry.dir}`, recursive: true });
|
|
288
|
+
const cmds = join3(src, "commands");
|
|
289
|
+
for (const file of await listMarkdown(cmds)) {
|
|
290
|
+
installs.push({ from: join3(cmds, file), to: `commands/${file}` });
|
|
291
|
+
}
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const skill = join3(src, "SKILL.md");
|
|
295
|
+
const command = join3(src, "command.md");
|
|
296
|
+
if (!await exists(skill)) {
|
|
297
|
+
if (entry.required) missingRequired.push(entry.dir);
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
skillDirs.push(`skills/${entry.dir}`);
|
|
301
|
+
installs.push({ from: skill, to: `skills/${entry.dir}/SKILL.md` });
|
|
302
|
+
if (await exists(command)) {
|
|
303
|
+
installs.push({ from: command, to: `commands/${entry.dir}.md` });
|
|
304
|
+
} else if (entry.required) {
|
|
305
|
+
missingRequired.push(`${entry.dir}/command.md`);
|
|
306
|
+
}
|
|
307
|
+
for (const extra of entry.extras ?? []) {
|
|
308
|
+
const from = join3(src, extra);
|
|
309
|
+
if (await exists(from)) {
|
|
310
|
+
installs.push({
|
|
311
|
+
from,
|
|
312
|
+
to: `skills/${entry.dir}/${extra}`,
|
|
313
|
+
recursive: true
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return { installs, skillDirs, commandDirs: ["commands"], missingRequired };
|
|
319
|
+
}
|
|
320
|
+
|
|
231
321
|
// src/prompts.ts
|
|
232
322
|
import inquirer from "inquirer";
|
|
233
323
|
async function selectScope() {
|
|
@@ -417,7 +507,7 @@ function adaptScriptToNode(script) {
|
|
|
417
507
|
return s;
|
|
418
508
|
}
|
|
419
509
|
async function adaptToNodeRuntime(projectPath) {
|
|
420
|
-
const { readFile: readFile3, writeFile: writeFile2, readdir:
|
|
510
|
+
const { readFile: readFile3, writeFile: writeFile2, readdir: readdir2 } = await import("fs/promises");
|
|
421
511
|
const { join: join3 } = await import("path");
|
|
422
512
|
const pkgPath = join3(projectPath, "package.json");
|
|
423
513
|
const pkg = JSON.parse(await readFile3(pkgPath, "utf-8"));
|
|
@@ -430,7 +520,7 @@ async function adaptToNodeRuntime(projectPath) {
|
|
|
430
520
|
delete pkg.devDependencies["bun-types"];
|
|
431
521
|
await writeFile2(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
432
522
|
async function walk(dir) {
|
|
433
|
-
const entries = await
|
|
523
|
+
const entries = await readdir2(dir, { withFileTypes: true });
|
|
434
524
|
for (const entry of entries) {
|
|
435
525
|
const full = join3(dir, entry.name);
|
|
436
526
|
if (entry.isDirectory()) {
|
|
@@ -459,11 +549,11 @@ async function adaptToNodeRuntime(projectPath) {
|
|
|
459
549
|
}
|
|
460
550
|
}
|
|
461
551
|
async function writeRootGitignore(projectPath) {
|
|
462
|
-
const { writeFile: writeFile2, access
|
|
552
|
+
const { writeFile: writeFile2, access } = await import("fs/promises");
|
|
463
553
|
const { join: join3 } = await import("path");
|
|
464
554
|
const gitignorePath = join3(projectPath, ".gitignore");
|
|
465
555
|
try {
|
|
466
|
-
await
|
|
556
|
+
await access(gitignorePath);
|
|
467
557
|
} catch {
|
|
468
558
|
await writeFile2(
|
|
469
559
|
gitignorePath,
|
|
@@ -542,59 +632,29 @@ function getCliVersion() {
|
|
|
542
632
|
}
|
|
543
633
|
var AGENT_DIRS = [".opencode", ".pi"];
|
|
544
634
|
async function installAgentSkills(repoRoot, projectPath) {
|
|
545
|
-
const
|
|
546
|
-
const command = join2(repoRoot, "skills", "create-specs", "command.md");
|
|
547
|
-
const designDir = join2(repoRoot, "skills", "design");
|
|
548
|
-
try {
|
|
549
|
-
await access(skill);
|
|
550
|
-
await access(command);
|
|
551
|
-
} catch {
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
635
|
+
const plan = await planAgentBundle(repoRoot);
|
|
554
636
|
for (const agentDir of AGENT_DIRS) {
|
|
555
|
-
const
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
let hasExamples = false;
|
|
559
|
-
try {
|
|
560
|
-
await access(examplesDir);
|
|
561
|
-
hasExamples = true;
|
|
562
|
-
} catch {
|
|
637
|
+
const root = join2(projectPath, agentDir);
|
|
638
|
+
for (const dir of plan.skillDirs) {
|
|
639
|
+
await mkdir2(join2(root, dir), { recursive: true });
|
|
563
640
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
await access(designDir);
|
|
567
|
-
hasDesign = true;
|
|
568
|
-
} catch {
|
|
641
|
+
for (const dir of plan.commandDirs) {
|
|
642
|
+
await mkdir2(join2(root, dir), { recursive: true });
|
|
569
643
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
await cp2(
|
|
575
|
-
examplesDir,
|
|
576
|
-
join2(skillsDir, "create-specs", "examples"),
|
|
577
|
-
{ recursive: true }
|
|
578
|
-
);
|
|
579
|
-
}
|
|
580
|
-
await cp2(command, join2(commandsDir, "create-specs.md"));
|
|
581
|
-
if (hasDesign) {
|
|
582
|
-
await cp2(designDir, join2(skillsDir, "design"), { recursive: true });
|
|
583
|
-
try {
|
|
584
|
-
const designCommandsDir = join2(designDir, "commands");
|
|
585
|
-
const commandFiles = (await readdir2(designCommandsDir)).filter(
|
|
586
|
-
(f) => f.endsWith(".md")
|
|
587
|
-
);
|
|
588
|
-
for (const file of commandFiles) {
|
|
589
|
-
await cp2(
|
|
590
|
-
join2(designCommandsDir, file),
|
|
591
|
-
join2(commandsDir, file)
|
|
592
|
-
);
|
|
593
|
-
}
|
|
594
|
-
} catch {
|
|
595
|
-
}
|
|
644
|
+
for (const item of plan.installs) {
|
|
645
|
+
await cp2(item.from, join2(root, item.to), {
|
|
646
|
+
recursive: item.recursive === true
|
|
647
|
+
});
|
|
596
648
|
}
|
|
597
649
|
}
|
|
650
|
+
if (plan.missingRequired.length > 0) {
|
|
651
|
+
console.log(
|
|
652
|
+
chalk.yellow(
|
|
653
|
+
` Skills: el bundle remoto no trae ${plan.missingRequired.join(", ")} \u2014 omitido
|
|
654
|
+
`
|
|
655
|
+
)
|
|
656
|
+
);
|
|
657
|
+
}
|
|
598
658
|
}
|
|
599
659
|
async function readTemplatePostInit(dir) {
|
|
600
660
|
try {
|