zugzbot 1.0.35 → 1.0.36
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/.opencode/agents/sdd-coder.md +1 -1
- package/.opencode/agents/sdd-spec-writer.md +2 -2
- package/.opencode/rules/sdd-global.md +13 -0
- package/.opencode/skills/shadcn-templates/SKILL.md +8 -2
- package/.opencode/templates/nextjs-shadcn/src/components/blocks/.gitkeep +1 -0
- package/.opencode/templates/nextjs-shadcn/src/components/layout/.gitkeep +1 -0
- package/.opencode/templates/nextjs-shadcn/src/components/ui/.gitkeep +1 -0
- package/.opencode/tools/sdd_bootstrap.ts +33 -20
- package/package.json +1 -1
|
@@ -88,7 +88,7 @@ Eres el Programador de Código (sdd-coder) del arnés SDD. Tu trabajo es codific
|
|
|
88
88
|
</coding_cheatsheet>
|
|
89
89
|
|
|
90
90
|
<design_standards>
|
|
91
|
-
- **Alineación con DESIGN.md**: Lee obligatoriamente `.openspec/design-assets/<brandId>/DESIGN.md` y asimila los layouts de los
|
|
91
|
+
- **Alineación con DESIGN.md e Instanciación de Bloques**: Lee obligatoriamente `.openspec/design-assets/<brandId>/DESIGN.md` y asimila los layouts para recrear diseños premium con grids, sidebars y headers. Quedan prohibidos los MVPs de página flotante. Es **obligatorio** que si la vista requiere un dashboard, panel lateral, login o registro, instales y utilices los bloques prehechos oficiales de Shadcn (`dashboard-01`, `sidebar-01` al `16`, `login-01` al `05`, `signup-01` al `05`, etc.) usando `npx shadcn@latest add <block-name> --yes` de forma agrupada. Distribuye y compón estos bloques enlazándolos con el backend en lugar de inventar la UI desde cero.
|
|
92
92
|
- **Self-audit pre-transición (BLOQUEANTE)**: Antes de entregar, ejecuta este bloque en una sola corrida de terminal. Si el linter o el tipado TypeScript fallan, debes corregirlos antes de transicionar (el test suite completo queda delegado al Tester en F3 para ahorrar tus turnos):
|
|
93
93
|
```bash
|
|
94
94
|
HITS=$(grep -rE '#[0-9a-fA-F]{6}\b' src/ --include='*.tsx' --include='*.ts' 2>/dev/null | grep -v 'globals.css' | grep -v 'tailwind.config' | wc -l | tr -d ' ')
|
|
@@ -31,7 +31,7 @@ Eres el Diseñador de Contratos (sdd-spec-writer) del flujo SDD. Tu único traba
|
|
|
31
31
|
El JSON generado en `contract.json` debe cumplir estrictamente con `.opencode/contract-schema.json`. Asegúrate de definir detalladamente:
|
|
32
32
|
- `contractName` & `description`: Nombre y alcance claro del spec.
|
|
33
33
|
- `settings`: Configuración clave como `verificationMode: "visual" | "console"`, lenguaje y persistencia.
|
|
34
|
-
- `files_affected`: Array obligatorio que define de forma explícita las rutas exactas de los archivos que serán modificados o creados (ej. `["src/components/layout/AppLayout.tsx", "src/components/blocks/SumadoraPanel.tsx", "src/app/page.tsx"]`). Esto es CRÍTICO para permitir la política de Zero-Search y la verificación dirigida de compilación, linter y testing.
|
|
34
|
+
- `files_affected`: Array obligatorio que define de forma explícita las rutas exactas de los archivos que serán modificados o creados (ej. `["src/components/layout/AppLayout.tsx", "src/components/blocks/SumadoraPanel.tsx", "src/app/page.tsx"]`). Si el proyecto vive en un subdirectorio (ej. `next-app` o `frontend`), las rutas **deben** ir obligatoriamente con el prefijo de dicho subdirectorio (ej. `["next-app/src/app/page.tsx"]`). Esto es CRÍTICO para permitir la política de Zero-Search y la verificación dirigida de compilación, linter y testing.
|
|
35
35
|
- `stack`: El framework base, bases de datos y librerías clave.
|
|
36
36
|
- `frontend` / `backend` / `database`: Componentes locales, props, state y endpoints de API. (Usa aliases locales como `@/components/ui/button` para components de Shadcn).
|
|
37
37
|
- `stateFlow`: Owner de estado centralizado, props hacia abajo y transiciones prohibidas (`forbidden[]`).
|
|
@@ -54,7 +54,7 @@ Extrae los tokens canónicos (paleta de colores, escala de bordes, tipografía,
|
|
|
54
54
|
|
|
55
55
|
<design_standards>
|
|
56
56
|
- **Alineación con DESIGN.md**: Debes leer obligatoriamente `.openspec/design-assets/<brandId>/DESIGN.md` para extraer los tokens de diseño. Basándote además en los ejemplos HTML interactivos (`preview.html`, `preview-dark.html`) correspondientes de la marca, define en el contrato los layouts exactos (sidebars, grids, headers) a implementar.
|
|
57
|
-
- **Diseño Premium**: El contrato debe instruir el uso
|
|
57
|
+
- **Diseño Premium y Bloques Shadcn**: El contrato debe instruir el uso de bloques prehechos oficiales de Shadcn (`dashboard-01`, `sidebar-01` al `16`, `login-01` al `05`, `signup-01` al `05`, etc.) mediante el skill `shadcn-templates` (Sección 3.1) alineado a la guía del `DESIGN.md`. Quedan prohibidos los MVPs de pantalla única flotante. El spec-writer debe usar proactivamente las herramientas MCP (`shadcn_search_items_in_registries`, `shadcn_list_items_in_registries`) para identificar qué bloques de tipo `registry:block` o `registry:ui` se adecuan a la necesidad y declararlos en `frontend.components` y `files_affected`.
|
|
58
58
|
- **Escenarios de Test**:
|
|
59
59
|
- Si `verificationMode === "console"`, genera únicamente escenarios de tipo `unit` o `integration` (entre 3 y 5 escenarios en total). No generes escenarios de tipo `visual` ni tests basados en browser.
|
|
60
60
|
- Si `verificationMode === "visual"`, incluye los escenarios visuales necesarios con selectores CSS reales.
|
|
@@ -21,3 +21,16 @@ Cuando el estado del sistema tiene `loopMode === true` (activado mediante el com
|
|
|
21
21
|
- **Intercepción de Barrera**: Si algún agente intenta llamar a la herramienta `question` en este modo, el sistema abortará la llamada mecánicamente a través del plugin enforcer.
|
|
22
22
|
- **Specs Incrementales**: Si la petición es grande, el orquestador debe dividirla secuencialmente en contratos incrementales. Completa un spec por completo (F0->F4) y arranca el siguiente de inmediato de forma autónoma hasta finalizar todo el plan.
|
|
23
23
|
- **Rollback de Emergencia**: Si una misma fase falla de forma continua (más de 2 rollbacks), desactiva `loopMode` usando `sdd_set_phase` y solicita asistencia humana en el chat.
|
|
24
|
+
|
|
25
|
+
## 3. Uso Mandatorio de Bloques Prehechos de Shadcn UI (dashboard, sidebar, login, etc.)
|
|
26
|
+
Para optimizar el tiempo de desarrollo, garantizar una experiencia visual premium y evitar la invención de layouts o formularios comunes desde cero:
|
|
27
|
+
- **Prioridad de Bloques**: Al diseñar cualquier interfaz de usuario (Layouts, Dashboards, Paneles de Control, Vistas de Configuración, Páginas de Autenticación, Tablas de Datos, Gráficos), los agentes **deben** buscar y utilizar de forma prioritaria los bloques prehechos del registro oficial `@shadcn` (como `dashboard-01`, `sidebar-01` al `16`, `login-01` al `05`, `signup-01` al `05`).
|
|
28
|
+
- **Búsqueda Proactiva**: El Spec-Writer **debe** consultar el registro utilizando las herramientas del MCP `shadcn` (`shadcn_search_items_in_registries`, `shadcn_list_items_in_registries`) al redactar el contrato (F1) para identificar qué bloques se ajustan a la necesidad, y declararlos en `frontend.components` y `files_affected` de `contract.json`.
|
|
29
|
+
- **Implementación por Distribución y Composición**: El Coder (F2) **debe** instalar estos bloques usando `npx shadcn@latest add <block-name>` (ej: `dashboard-01`, `login-03`) en su proyecto, integrando y distribuyendo los elementos con el estado y lógica del backend, sin rediseñar o codificar layouts que ya existen de forma oficial.
|
|
30
|
+
|
|
31
|
+
## 4. Soporte para Subdirectorios, Monorepos (targetDir) y Estándar Corporativo `src/`
|
|
32
|
+
Si el proyecto o aplicación principal vive en un subdirectorio (ej. `next-app`, `frontend`, `backend`), se deben seguir estas directivas estrictas para mantener la consistencia y el "estándar corporativo con carpeta `src/`":
|
|
33
|
+
- **Estructura del Proyecto**: Toda la estructura de código fuente, páginas, componentes, estilos, pruebas y configuraciones debe vivir agrupada dentro de la subcarpeta del proyecto y bajo la carpeta `src/` obligatoriamente (ej. `next-app/src/app/...`, `next-app/src/components/...`, `next-app/tsconfig.json`, `next-app/package.json`).
|
|
34
|
+
- **Rutas de Archivos en el Contrato**: El Spec-Writer (F1) **debe** escribir las rutas del campo `files_affected` del contrato con el prefijo del subdirectorio correspondiente (ej. `["next-app/src/app/page.tsx", "next-app/src/components/blocks/Navbar.tsx"]`).
|
|
35
|
+
- **Ejecución de Comandos**: El Coder (F2), Tester (F3) y Deployer (F4) **deben** ejecutar siempre sus comandos de terminal (`npm install`, `npm run lint`, `npx tsc`, `npx vitest`, `docker compose`) utilizando la subcarpeta como directorio de trabajo (`workdir` en las llamadas a herramientas, o moviéndose a ella).
|
|
36
|
+
- **Consistencia de Configuración**: Está estrictamente prohibido esparcir o duplicar archivos de configuración (como `tsconfig.json`, `eslint.config.mjs`, `vitest.config.ts`, `components.json`) en la raíz del espacio de trabajo si hay un subdirectorio activo. Todo debe quedar encapsulado dentro del subdirectorio del proyecto.
|
|
@@ -72,8 +72,14 @@ npx shadcn@latest add <component-name>
|
|
|
72
72
|
|
|
73
73
|
Al diseñar o codificar páginas con Shadcn UI, sigue estas reglas estrictas:
|
|
74
74
|
|
|
75
|
-
### 3.1 Componer, no reinventar
|
|
76
|
-
Utiliza las plantillas y bloques existentes de Shadcn
|
|
75
|
+
### 3.1 Componer, no reinventar (MANDATORIO)
|
|
76
|
+
Utiliza las plantillas y bloques existentes de Shadcn como el estándar absoluto de tu interfaz. Queda **estrictamente prohibido** escribir layouts, páginas de autenticación o cuadros de mando desde cero si ya existe un bloque oficial que resuelva el caso de uso:
|
|
77
|
+
- **Dashboards**: Utiliza `dashboard-01` como base para cualquier panel de control interactivo.
|
|
78
|
+
- **Sidebars / Barras de Navegación**: Utiliza `sidebar-01` al `16` para paneles laterales funcionales, colapsables, dinámicos o anidados. No programes componentes de navegación complejos a mano.
|
|
79
|
+
- **Login / Registro**: Utiliza `login-01` al `05` y `signup-01` al `05` para flujos de login y registro de usuarios.
|
|
80
|
+
- **Gráficos y Métricas**: Busca y añade bloques de gráficos oficiales de Recharts (`chart-area-*`, `chart-bar-*`, `chart-pie-*`, `chart-radar-*`) para dar vida a la visualización de datos.
|
|
81
|
+
|
|
82
|
+
Busca layouts de dashboards o formularios complejos en el registro mediante el MCP `shadcn` antes de escribir cualquier código personalizado. Una vez agregado el bloque, distribuye y adapta sus elementos con el estado y la lógica de tu aplicación.
|
|
77
83
|
|
|
78
84
|
### 3.2 Form Layouts (FieldGroup + Field)
|
|
79
85
|
Para estructurar campos de formularios, no uses `div`s arbitrarios con clases de espaciado complejas. Usa las composiciones semánticas oficiales de Shadcn:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Carpeta base para bloques complejos y layouts de Shadcn UI
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Carpeta base para layouts generales de la aplicacion
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Carpeta base para componentes primitivos de Shadcn UI
|
|
@@ -145,12 +145,15 @@ export const bootstrap_status = tool({
|
|
|
145
145
|
export const bootstrap_nextjs_shadcn = tool({
|
|
146
146
|
description: "Inicializa un proyecto Next.js 16 + Shadcn UI + Tailwind v4 + Vitest a partir de la plantilla canónica en .opencode/templates/nextjs-shadcn/. Es IDEMPOTENTE: si el proyecto ya está inicializado y no se pasa force=true, no toca nada. Copia archivo-por-archivo (sin pisar los existentes), mergea package.json, y opcionalmente instala dependencias y shadcn components.",
|
|
147
147
|
args: {
|
|
148
|
+
targetDir: tool.schema.string().default(".").describe("Subdirectorio donde se inicializará el proyecto Next.js (ej: 'next-app' o '.')."),
|
|
148
149
|
components: tool.schema.array(tool.schema.string()).default([]).describe("Lista de shadcn components a instalar (ej: ['button','input','table']). Vacío = no instalar shadcn components."),
|
|
149
150
|
install: tool.schema.boolean().default(true).describe("Si true, ejecuta npm/pnpm/yarn install después de copiar. Si false, solo copia archivos."),
|
|
150
151
|
force: tool.schema.boolean().default(false).describe("Si true, sobrescribe archivos existentes. Si false, los salta (mergea package.json)."),
|
|
151
152
|
},
|
|
152
153
|
async execute(args, context) {
|
|
153
154
|
const root = getRoot(context)
|
|
155
|
+
const targetDir = args.targetDir || "."
|
|
156
|
+
const targetPath = targetDir === "." ? root : path.resolve(root, targetDir)
|
|
154
157
|
const start = Date.now()
|
|
155
158
|
const templateDir = path.resolve(root, ".opencode/templates/nextjs-shadcn")
|
|
156
159
|
|
|
@@ -172,9 +175,9 @@ export const bootstrap_nextjs_shadcn = tool({
|
|
|
172
175
|
}
|
|
173
176
|
|
|
174
177
|
const isInitialized =
|
|
175
|
-
fs.existsSync(path.resolve(
|
|
176
|
-
fs.existsSync(path.resolve(
|
|
177
|
-
fs.existsSync(path.resolve(
|
|
178
|
+
fs.existsSync(path.resolve(targetPath, "src/app/page.tsx")) &&
|
|
179
|
+
fs.existsSync(path.resolve(targetPath, "package.json")) &&
|
|
180
|
+
fs.existsSync(path.resolve(targetPath, "src/lib/utils.ts"))
|
|
178
181
|
|
|
179
182
|
if (isInitialized && !args.force) {
|
|
180
183
|
return JSON.stringify({
|
|
@@ -189,7 +192,7 @@ export const bootstrap_nextjs_shadcn = tool({
|
|
|
189
192
|
const filesSkipped: string[] = []
|
|
190
193
|
const copyFileSafe = (rel: string) => {
|
|
191
194
|
const src = path.join(templateDir, rel)
|
|
192
|
-
const dst = path.resolve(
|
|
195
|
+
const dst = path.resolve(targetPath, rel)
|
|
193
196
|
if (!fs.existsSync(src)) return
|
|
194
197
|
const dstExists = fs.existsSync(dst)
|
|
195
198
|
if (dstExists && !args.force) {
|
|
@@ -201,7 +204,7 @@ export const bootstrap_nextjs_shadcn = tool({
|
|
|
201
204
|
filesCopied.push(rel)
|
|
202
205
|
}
|
|
203
206
|
|
|
204
|
-
const userPkgPath = path.resolve(
|
|
207
|
+
const userPkgPath = path.resolve(targetPath, "package.json")
|
|
205
208
|
let mergedPkg: any = null
|
|
206
209
|
if (fs.existsSync(userPkgPath) && !args.force) {
|
|
207
210
|
try {
|
|
@@ -223,7 +226,7 @@ export const bootstrap_nextjs_shadcn = tool({
|
|
|
223
226
|
copyFileSafe("package.json")
|
|
224
227
|
}
|
|
225
228
|
|
|
226
|
-
const userNextConfig = path.resolve(
|
|
229
|
+
const userNextConfig = path.resolve(targetPath, "next.config.ts")
|
|
227
230
|
if (fs.existsSync(userNextConfig) && !args.force) {
|
|
228
231
|
try {
|
|
229
232
|
const content = fs.readFileSync(userNextConfig, "utf8")
|
|
@@ -255,17 +258,20 @@ export const bootstrap_nextjs_shadcn = tool({
|
|
|
255
258
|
"src/app/globals.css",
|
|
256
259
|
"src/lib/utils.ts",
|
|
257
260
|
"src/components/theme-provider.tsx",
|
|
261
|
+
"src/components/ui/.gitkeep",
|
|
262
|
+
"src/components/blocks/.gitkeep",
|
|
263
|
+
"src/components/layout/.gitkeep",
|
|
258
264
|
"src/test/setup.ts",
|
|
259
265
|
"public/.gitkeep",
|
|
260
266
|
]
|
|
261
267
|
for (const f of standardFiles) copyFileSafe(f)
|
|
262
268
|
|
|
263
|
-
const pm = detectPackageManager(
|
|
269
|
+
const pm = detectPackageManager(targetPath)
|
|
264
270
|
|
|
265
271
|
let installDuration = 0
|
|
266
272
|
let installSkipped = false
|
|
267
273
|
let installError: string | null = null
|
|
268
|
-
const hasNodeModules = fs.existsSync(path.resolve(
|
|
274
|
+
const hasNodeModules = fs.existsSync(path.resolve(targetPath, "node_modules"))
|
|
269
275
|
if (args.install && (args.force || !hasNodeModules)) {
|
|
270
276
|
const installStart = Date.now()
|
|
271
277
|
const installCmd = pm === "pnpm"
|
|
@@ -275,7 +281,7 @@ export const bootstrap_nextjs_shadcn = tool({
|
|
|
275
281
|
: "npm install --prefer-offline"
|
|
276
282
|
try {
|
|
277
283
|
execSync(installCmd, {
|
|
278
|
-
cwd:
|
|
284
|
+
cwd: targetPath,
|
|
279
285
|
stdio: "ignore",
|
|
280
286
|
timeout: 300_000,
|
|
281
287
|
})
|
|
@@ -293,7 +299,7 @@ export const bootstrap_nextjs_shadcn = tool({
|
|
|
293
299
|
try {
|
|
294
300
|
const cmd = `npx shadcn@latest add ${args.components.join(" ")} --yes --overwrite`
|
|
295
301
|
execSync(cmd, {
|
|
296
|
-
cwd:
|
|
302
|
+
cwd: targetPath,
|
|
297
303
|
stdio: "ignore",
|
|
298
304
|
timeout: 180_000,
|
|
299
305
|
})
|
|
@@ -314,6 +320,7 @@ export const bootstrap_nextjs_shadcn = tool({
|
|
|
314
320
|
componentsInstalled,
|
|
315
321
|
installDuration,
|
|
316
322
|
totalDuration: Date.now() - start,
|
|
323
|
+
targetDir,
|
|
317
324
|
}
|
|
318
325
|
writeBootstrapStatus(root, bootstrapRecord)
|
|
319
326
|
|
|
@@ -348,12 +355,15 @@ export const bootstrap_nextjs_shadcn = tool({
|
|
|
348
355
|
export const bootstrap_fastapi = tool({
|
|
349
356
|
description: "Inicializa un proyecto FastAPI + Pydantic + Uvicorn + Pytest + Ruff a partir de la plantilla canónica en .opencode/templates/fastapi-sdd/. Es IDEMPOTENTE: si el proyecto ya está inicializado y no se pasa force=true, no toca nada. Copia archivo-por-archivo (sin pisar los existentes), opcionalmente instala dependencias con uv (fallback pip).",
|
|
350
357
|
args: {
|
|
358
|
+
targetDir: tool.schema.string().default(".").describe("Subdirectorio donde se inicializará el proyecto FastAPI (ej: 'backend' o '.')."),
|
|
351
359
|
extras: tool.schema.array(tool.schema.string()).default([]).describe("Lista de paquetes Python adicionales a instalar (ej: ['sqlalchemy','pydantic-settings','pytest-asyncio']). Vacío = no instalar extras extra."),
|
|
352
360
|
install: tool.schema.boolean().default(true).describe("Si true, ejecuta uv sync (o pip install -e '.[dev]') después de copiar. Si false, solo copia archivos."),
|
|
353
361
|
force: tool.schema.boolean().default(false).describe("Si true, sobrescribe archivos existentes. Si false, los salta."),
|
|
354
362
|
},
|
|
355
363
|
async execute(args, context) {
|
|
356
364
|
const root = getRoot(context)
|
|
365
|
+
const targetDir = args.targetDir || "."
|
|
366
|
+
const targetPath = targetDir === "." ? root : path.resolve(root, targetDir)
|
|
357
367
|
const start = Date.now()
|
|
358
368
|
const templateDir = path.resolve(root, ".opencode/templates/fastapi-sdd")
|
|
359
369
|
|
|
@@ -375,8 +385,8 @@ export const bootstrap_fastapi = tool({
|
|
|
375
385
|
}
|
|
376
386
|
|
|
377
387
|
const isInitialized =
|
|
378
|
-
fs.existsSync(path.resolve(
|
|
379
|
-
fs.existsSync(path.resolve(
|
|
388
|
+
fs.existsSync(path.resolve(targetPath, "src/main.py")) &&
|
|
389
|
+
fs.existsSync(path.resolve(targetPath, "pyproject.toml"))
|
|
380
390
|
|
|
381
391
|
if (isInitialized && !args.force) {
|
|
382
392
|
return JSON.stringify({
|
|
@@ -391,7 +401,7 @@ export const bootstrap_fastapi = tool({
|
|
|
391
401
|
const filesSkipped: string[] = []
|
|
392
402
|
const copyFileSafe = (rel: string) => {
|
|
393
403
|
const src = path.join(templateDir, rel)
|
|
394
|
-
const dst = path.resolve(
|
|
404
|
+
const dst = path.resolve(targetPath, rel)
|
|
395
405
|
if (!fs.existsSync(src)) return
|
|
396
406
|
const dstExists = fs.existsSync(dst)
|
|
397
407
|
if (dstExists && !args.force) {
|
|
@@ -403,7 +413,7 @@ export const bootstrap_fastapi = tool({
|
|
|
403
413
|
filesCopied.push(rel)
|
|
404
414
|
}
|
|
405
415
|
|
|
406
|
-
const userPyprojectPath = path.resolve(
|
|
416
|
+
const userPyprojectPath = path.resolve(targetPath, "pyproject.toml")
|
|
407
417
|
let mergedPyproject: string | null = null
|
|
408
418
|
if (fs.existsSync(userPyprojectPath) && !args.force) {
|
|
409
419
|
try {
|
|
@@ -443,13 +453,13 @@ export const bootstrap_fastapi = tool({
|
|
|
443
453
|
]
|
|
444
454
|
for (const f of standardFiles) copyFileSafe(f)
|
|
445
455
|
|
|
446
|
-
const pm = detectPythonPackageManager(
|
|
456
|
+
const pm = detectPythonPackageManager(targetPath)
|
|
447
457
|
|
|
448
458
|
let installDuration = 0
|
|
449
459
|
let installSkipped = false
|
|
450
460
|
let installError: string | null = null
|
|
451
|
-
const hasVenv = fs.existsSync(path.resolve(
|
|
452
|
-
const hasInstalled = fs.existsSync(path.resolve(
|
|
461
|
+
const hasVenv = fs.existsSync(path.resolve(targetPath, ".venv"))
|
|
462
|
+
const hasInstalled = fs.existsSync(path.resolve(targetPath, "uv.lock")) || hasVenv
|
|
453
463
|
if (args.install && (args.force || !hasInstalled)) {
|
|
454
464
|
const installStart = Date.now()
|
|
455
465
|
const installCmd = pm === "uv"
|
|
@@ -457,7 +467,7 @@ export const bootstrap_fastapi = tool({
|
|
|
457
467
|
: "pip install -e '.[dev]'"
|
|
458
468
|
try {
|
|
459
469
|
execSync(installCmd, {
|
|
460
|
-
cwd:
|
|
470
|
+
cwd: targetPath,
|
|
461
471
|
stdio: "ignore",
|
|
462
472
|
timeout: 300_000,
|
|
463
473
|
})
|
|
@@ -466,7 +476,9 @@ export const bootstrap_fastapi = tool({
|
|
|
466
476
|
installError = e.message?.slice(0, 500) || "unknown error"
|
|
467
477
|
}
|
|
468
478
|
} else if (hasInstalled && !args.force) {
|
|
469
|
-
|
|
479
|
+
try {
|
|
480
|
+
installSkipped = true
|
|
481
|
+
} catch (e) {}
|
|
470
482
|
}
|
|
471
483
|
|
|
472
484
|
const extrasInstalled: string[] = []
|
|
@@ -477,7 +489,7 @@ export const bootstrap_fastapi = tool({
|
|
|
477
489
|
: `pip install ${args.extras.join(" ")}`
|
|
478
490
|
try {
|
|
479
491
|
execSync(extrasCmd, {
|
|
480
|
-
cwd:
|
|
492
|
+
cwd: targetPath,
|
|
481
493
|
stdio: "ignore",
|
|
482
494
|
timeout: 180_000,
|
|
483
495
|
})
|
|
@@ -498,6 +510,7 @@ export const bootstrap_fastapi = tool({
|
|
|
498
510
|
extrasInstalled,
|
|
499
511
|
installDuration,
|
|
500
512
|
totalDuration: Date.now() - start,
|
|
513
|
+
targetDir,
|
|
501
514
|
}
|
|
502
515
|
writeBootstrapStatus(root, bootstrapRecord)
|
|
503
516
|
|