diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c95664c..5798d4c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,75 +7,23 @@ on: branches: [main, develop] jobs: - build: - name: Build & Install + # Job matrix para rodar múltiplos checks em paralelo + quality-checks: + name: ${{ matrix.check-name }} runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - - - name: Install dependencies - run: npm ci - - - name: Build project - run: npm run build - - test: - name: Test - needs: build - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" + strategy: + fail-fast: false # Continua mesmo se um falhar + matrix: + include: + - check-name: "Build" + check-command: "build" + - check-name: "Lint" + check-command: "lint" + - check-name: "Format Check" + check-command: "format:check" + - check-name: "Tests" + check-command: "test" - - name: Install dependencies - run: npm ci - - - name: Build project - run: npm run build - - - name: Run tests - run: npm test - - lint: - name: Lint - needs: build - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - - - name: Install dependencies - run: npm ci - - - name: Build project - run: npm run build - - - name: Run ESLint - run: npm run lint - - format_check: - name: Format Check - needs: build - runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 @@ -89,8 +37,10 @@ jobs: - name: Install dependencies run: npm ci + # Build é necessário antes de outros checks - name: Build project + if: matrix.check-command != 'build' run: npm run build - - name: Run Prettier Check - run: npm run format:check + - name: Run ${{ matrix.check-name }} + run: npm run ${{ matrix.check-command }} diff --git a/.gitignore b/.gitignore index 07497cff..d8069855 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,13 @@ -cat < .gitignore # Dependencies node_modules/ .pnp .pnp.js .yarn/install-state.gz -# Production +# Production & Build Artifacts build/ dist/ +out/ .out/ # Misc @@ -37,4 +37,11 @@ lerna-debug.log* # TypeScript *.tsbuildinfo -EOF \ No newline at end of file + +# Test Coverage +coverage/ +*.lcov +.nyc_output/ + +# VS Code Extension +*.vsix diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..7a742e74 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,22 @@ +# Build outputs +**/dist +**/out +**/build + +# Coverage +**/coverage + +# Dependencies +**/node_modules + +# Lock files +package-lock.json +pnpm-lock.yaml +yarn.lock + +# Generated files +*.log +*.tsbuildinfo + +# VS Code extension specific +*.vsix diff --git a/docs/es/ARCHITECTURE.md b/docs/es/ARCHITECTURE.md index dd044e7a..97a349ce 100644 --- a/docs/es/ARCHITECTURE.md +++ b/docs/es/ARCHITECTURE.md @@ -1,6 +1,6 @@ # Arquitectura de StackCode -StackCode es un kit de herramientas DevOps integral diseñado como un monorepo con múltiples paquetes interconectados. Este documento detalla la arquitectura del proyecto, principios de diseño e interacciones entre componentes. +StackCode es un kit de herramientas DevOps integral diseñado como un monorepo con múltiples paquetes interconectados. Este documento describe la arquitectura del proyecto, principios de diseño e interacciones entre componentes. ## 🏗️ Arquitectura de Alto Nivel @@ -8,13 +8,13 @@ StackCode sigue una **arquitectura de monorepo modular** con clara separación d ``` ┌─────────────────────────────────────────────────────────────┐ -│ Ecosistema StackCode │ +│ Ecosistema StackCode │ ├─────────────────────────────────────────────────────────────┤ │ Paquete CLI │ Extensión VS Code │ │ (@stackcode/cli) │ (stackcode-vscode) │ │ ┌─────────────────┐ │ ┌─────────────────────────────┐ │ -│ │ Capa Comandos │ │ │ Comandos de Extensión │ │ -│ │ ├─ init │ │ │ ├─ Provider Dashboard │ │ +│ │ Capa Comando │ │ │ Comandos de Extensión │ │ +│ │ ├─ init │ │ │ ├─ Proveedor Dashboard │ │ │ │ ├─ generate │ │ │ ├─ Monitores File/Git │ │ │ │ ├─ commit │ │ │ ├─ Gestor Notificaciones │ │ │ │ ├─ git │ │ │ └─ UI Webview │ │ @@ -32,12 +32,12 @@ StackCode sigue una **arquitectura de monorepo modular** con clara separación d │ (@stackcode/core) │ (@stackcode/i18n) │ │ ┌─────────────────┐ │ ┌─────────────────────────────┐ │ │ │ Lógica Negocio │ │ │ Internacionalización │ │ -│ │ ├─ Generadores │ │ │ ├─ Gestión Locales │ │ +│ │ ├─ Generadores │ │ │ ├─ Gestión Locales │ │ │ │ ├─ Validadores │ │ │ ├─ Sistema Traducción │ │ │ │ ├─ API GitHub │ │ │ └─ Detección Idioma │ │ │ │ ├─ Gest. Release│ │ └─────────────────────────────┘ │ │ │ ├─ Scaffolding │ │ │ -│ │ └─ Plantillas │ │ │ +│ │ └─ Templates │ │ │ │ └─────────────────┘ │ │ └─────────────────────────────────────────────────────────────┘ ``` @@ -46,13 +46,13 @@ StackCode sigue una **arquitectura de monorepo modular** con clara separación d ### 1. **@stackcode/cli** - Interfaz de Línea de Comandos -**Propósito:** Interfaz primaria del usuario para las funcionalidades de StackCode. +**Propósito:** Interfaz principal del usuario para funcionalidades de StackCode. **Componentes Principales:** - **Capa de Comandos:** Puntos de entrada para todas las operaciones CLI - **Manejadores de Comandos:** Implementaciones de comandos individuales -- **Prompts Interactivos:** Orientación al usuario y recolección de entrada +- **Prompts Interactivos:** Orientación y recolección de entrada del usuario - **Modo Educativo:** Sistema de aprendizaje contextual con explicaciones de mejores prácticas - **Manejo de Errores:** Reporte de errores consistente y recuperación @@ -61,272 +61,374 @@ StackCode sigue una **arquitectura de monorepo modular** con clara separación d ```typescript cli/ ├── src/ -│ ├── index.ts # Punto de entrada principal CLI +│ ├── index.ts # Punto de entrada principal de CLI │ ├── educational-mode.ts # Gestión del modo educativo │ ├── commands/ # Implementaciones de comandos │ │ ├── init.ts # Scaffolding de proyecto │ │ ├── generate.ts # Generación de archivos │ │ ├── commit.ts # Commits convencionales -│ │ ├── git.ts # Gestión de flujo Git +│ │ ├── git.ts # Gestión de workflow Git │ │ ├── release.ts # Gestión de versiones │ │ ├── validate.ts # Validación de commits │ │ ├── config.ts # Gestión de configuración -│ │ └── ui.ts # Prompts interactivos y feedback -│ └── types/ # Definiciones de tipos específicos CLI -└── test/ # Tests de comandos +│ │ └── ui.ts # Prompts interactivos y retroalimentación +│ └── types/ # Definiciones de tipos específicos de CLI +└── test/ # Pruebas de comandos ``` ### 2. **@stackcode/core** - Motor de Lógica de Negocio -**Propósito:** Contiene toda la lógica de negocio, utilidades y plantillas. +**Propósito:** Contiene toda la lógica de negocio, utilidades y templates. **Componentes Principales:** - **Generadores:** Lógica de generación de proyectos y archivos -- **Validadores:** Validación de mensajes de commit y proyectos -- **Integración GitHub:** Interacciones API y automatización +- **Validadores:** Validación de mensajes de commit y dependencias del sistema +- **Integración GitHub:** Interacciones con API y automatización - **Gestión de Releases:** Versionado semántico y generación de changelog -- **Sistema de Plantillas:** Plantillas de proyecto configurables +- **Sistema de Templates:** Templates de proyecto configurables +- **Validación de Dependencias:** Validación inteligente de herramientas del sistema **Arquitectura:** ```typescript core/ ├── src/ -│ ├── index.ts # Exportaciones core -│ ├── generators.ts # Generadores proyecto/archivo +│ ├── index.ts # Exportaciones del core +│ ├── generators.ts # Generadores de proyecto/archivo │ ├── validator.ts # Lógica de validación -│ ├── github.ts # Integración API GitHub +│ ├── github.ts # Integración con API GitHub │ ├── release.ts # Gestión de versiones │ ├── scaffold.ts # Scaffolding de proyecto │ ├── utils.ts # Utilidades compartidas -│ ├── types.ts # Definiciones de tipos core -│ └── templates/ # Plantillas de proyecto -│ ├── common/ # Archivos de plantilla compartidos -│ ├── node-js/ # Plantillas Node.js -│ ├── react/ # Plantillas React -│ ├── vue/ # Plantillas Vue.js -│ ├── python/ # Plantillas Python -│ ├── java/ # Java templates -│ ├── go/ # Go templates -│ ├── php/ # PHP templates -│ ├── gitignore/ # .gitignore templates -│ └── readme/ # README templates -└── test/ # Core logic tests +│ ├── types.ts # Definiciones de tipos del core +│ └── templates/ # Templates de proyecto +│ ├── common/ # Archivos de template compartidos +│ ├── node-js/ # Templates Node.js +│ ├── react/ # Templates React +│ ├── vue/ # Templates Vue.js +│ ├── python/ # Templates Python +│ ├── java/ # Templates Java +│ ├── go/ # Templates Go +│ ├── php/ # Templates PHP +│ ├── gitignore/ # Templates .gitignore +│ └── readme/ # Templates README +└── test/ # Pruebas de lógica del core ``` ### 3. **@stackcode/i18n** - Internacionalización -**Propósito:** Gestiona el soporte multiidioma en todos los paquetes. +**Propósito:** Gestiona soporte multi-idioma en todos los paquetes. -**Features:** +**Características:** -- Dynamic locale detection -- Translation loading and caching -- Language switching -- Fallback mechanisms +- Detección dinámica de locale +- Carga y caché de traducciones +- Cambio de idioma +- Mecanismos de fallback -**Architecture:** +**Arquitectura:** ```typescript i18n/ ├── src/ -│ ├── index.ts # i18n exports -│ └── locales/ # Translation files -│ ├── en.json # English translations -│ └── pt.json # Portuguese translations +│ ├── index.ts # Exportaciones i18n +│ └── locales/ # Archivos de traducción +│ ├── en.json # Traducciones en inglés +│ └── pt.json # Traducciones en portugués ``` -### 4. **stackcode-vscode** - Extensión de VS Code +### 4. **stackcode-vscode** - Extensión VS Code -**Propósito:** Integra la funcionalidad de StackCode directamente en VS Code. +**Propósito:** Integra funcionalidad de StackCode directamente en VS Code. -**Key Components:** +**Componentes Principales:** -- **Extension Commands:** VS Code command palette integration -- **File Monitors:** Real-time file change detection -- **Git Monitors:** Git state monitoring -- **Dashboard Provider:** Interactive project dashboard -- **Webview UI:** Rich user interface components -- **Notification System:** Proactive user guidance +- **Comandos de Extensión:** Integración con paleta de comandos de VS Code +- **Monitores de Archivo:** Detección de cambios en tiempo real +- **Monitores Git:** Monitoreo del estado de Git +- **Proveedor de Dashboard:** Dashboard interactivo del proyecto +- **UI Webview:** Componentes de interfaz rica +- **Sistema de Notificaciones:** Orientación proactiva al usuario -**Architecture:** +**Arquitectura:** ```typescript vscode-extension/ ├── src/ -│ ├── extension.ts # Extension entry point -│ ├── commands/ # VS Code commands -│ ├── config/ # Configuration management -│ ├── monitors/ # File and Git monitoring -│ ├── notifications/ # Notification system -│ ├── providers/ # VS Code providers -│ ├── services/ # Extension services -│ └── webview-ui/ # React-based UI -└── test/ # Extension tests +│ ├── extension.ts # Punto de entrada de la extensión +│ ├── commands/ # Comandos de VS Code +│ ├── config/ # Gestión de configuración +│ ├── monitors/ # Monitoreo de archivos y Git +│ ├── notifications/ # Sistema de notificaciones +│ ├── providers/ # Proveedores de VS Code +│ ├── services/ # Servicios de la extensión +│ └── webview-ui/ # UI basada en React +└── test/ # Pruebas de la extensión ``` -## 🔄 Data Flow and Interactions +## 🔄 Flujo de Datos e Interacciones -### Command Execution Flow +### Flujo de Ejecución de Comandos -1. **User Input:** CLI command or VS Code action -2. **Command Parsing:** Yargs (CLI) or VS Code API -3. **Core Logic:** Business logic execution in `@stackcode/core` -4. **i18n Processing:** Localized messages via `@stackcode/i18n` -5. **Output:** Results displayed to user +1. **Entrada del Usuario:** Comando CLI o acción en VS Code +2. **Análisis de Comandos:** Yargs (CLI) o API de VS Code +3. **Lógica del Core:** Ejecución de lógica de negocio en `@stackcode/core` +4. **Procesamiento i18n:** Mensajes localizados vía `@stackcode/i18n` +5. **Salida:** Resultados mostrados al usuario -### Cross-Package Dependencies +### Dependencias Entre Paquetes ```mermaid graph TD - A[CLI Package] --> C[Core Package] - A --> D[i18n Package] - B[VS Code Extension] --> C + A[Paquete CLI] --> C[Paquete Core] + A --> D[Paquete i18n] + B[Extensión VS Code] --> C B --> D C --> D ``` -## 🎯 Design Principles +## 🎯 Principios de Diseño + +### 1. **Separación de Responsabilidades** + +- **Paquete CLI:** Interfaz de usuario y manejo de comandos +- **Paquete Core:** Lógica de negocio y utilidades +- **Paquete i18n:** Preocupaciones de internacionalización +- **Extensión VS Code:** Integración con IDE + +### 2. **Inversión de Dependencias** + +- Módulos de alto nivel no dependen de módulos de bajo nivel +- Ambos dependen de abstracciones (interfaces) +- Dependencias externas son inyectadas, no hardcodeadas + +### 3. **Responsabilidad Única** + +- Cada paquete tiene un propósito claramente definido +- Funciones y clases tienen responsabilidades únicas y bien definidas +- Templates son modulares y componibles + +### 4. **Principio Abierto/Cerrado** + +- Sistema es abierto para extensión (nuevos templates, comandos) +- Cerrado para modificación (lógica del core permanece estable) + +## 🔍 Arquitectura de Validación del Sistema -### 1. **Separation of Concerns** +### Sistema de Validación de Dependencias -- **CLI Package:** User interface and command handling -- **Core Package:** Business logic and utilities -- **i18n Package:** Internationalization concerns -- **VS Code Extension:** IDE integration +StackCode implementa un sistema integral de validación de dependencias para asegurar inicialización fluida de proyectos en diferentes stacks de tecnología. -### 2. **Dependency Inversion** +#### Componentes Principales -- Higher-level modules don't depend on lower-level modules -- Both depend on abstractions (interfaces) -- External dependencies are injected, not hardcoded +**1. Detección de Disponibilidad de Comandos (`isCommandAvailable`)** + +```typescript +// Detección de comando multiplataforma +const isAvailable = await isCommandAvailable("go"); +// Usa 'which' (Unix) o 'where' (Windows) +``` + +**2. Mapeo de Dependencias de Stack (`getStackDependencies`)** + +```typescript +const stackMap = { + go: ["go"], + php: ["composer", "php"], + java: ["mvn", "java"], + python: ["pip", "python"], + react: ["npm"], + vue: ["npm"], +}; +``` + +**3. Validación Integral (`validateStackDependencies`)** + +```typescript +const result = await validateStackDependencies("go"); +// Retorna: { isValid, missingDependencies, availableDependencies } +``` -### 3. **Single Responsibility** +#### Flujo de Validación -- Each package has a clearly defined purpose -- Functions and classes have single, well-defined responsibilities -- Templates are modular and composable +```mermaid +graph TD + A[Usuario ejecuta 'stc init'] --> B[Seleccionar Stack de Tecnología] + B --> C[Validar Dependencias del Stack] + C --> D{¿Todas las Dependencias Disponibles?} + D -->|Sí| E[✅ Proceder con Instalación] + D -->|No| F[⚠️ Mostrar Dependencias Faltantes] + F --> G[Mostrar Instrucciones de Instalación] + G --> H{¿Usuario Elige Continuar?} + H -->|Sí| I[🚧 Crear Solo Estructura del Proyecto] + H -->|No| J[❌ Cancelar Operación] + E --> K[🎉 Completar Configuración del Proyecto] + I --> L[⚠️ Instalación Manual de Dependencias Requerida] +``` -### 4. **Open/Closed Principle** +#### Estrategia de Manejo de Errores -- System is open for extension (new templates, commands) -- Closed for modification (core logic remains stable) +- **Degradación Elegante:** Creación de proyecto exitosa incluso sin dependencias +- **Mensajes Informativos:** Instrucciones claras de instalación con URLs oficiales +- **Elección del Usuario:** Opción de proceder o cancelar cuando faltan dependencias +- **Soporte i18n:** Mensajes de error localizados en múltiples idiomas -## 🛠️ Technology Stack +## 🛠️ Stack de Tecnología -### Core Technologies +### Tecnologías Principales -- **TypeScript:** Type safety and modern JavaScript features -- **Node.js:** Runtime environment -- **ESM:** Modern module system +- **TypeScript:** Seguridad de tipos y características modernas de JavaScript +- **Node.js:** Entorno de ejecución +- **ESM:** Sistema de módulos moderno -### CLI-Specific +### Específicas de CLI -- **Yargs:** Command-line argument parsing -- **Inquirer:** Interactive command-line prompts +- **Yargs:** Análisis de argumentos de línea de comandos +- **Inquirer:** Prompts interactivos de línea de comandos -### VS Code Extension-Specific +### Específicas de Extensión VS Code -- **VS Code API:** Extension development framework -- **React:** Webview UI components -- **Vite:** Build tool for webview assets +- **API de VS Code:** Framework de desarrollo de extensiones +- **React:** Componentes UI para webview +- **Vite:** Herramienta de build para assets webview -### Development Tools +### Herramientas de Desarrollo -- **Vitest/Jest:** Testing frameworks -- **ESLint:** Code linting -- **Prettier:** Code formatting -- **GitHub Actions:** CI/CD pipeline +- **Vitest/Jest:** Frameworks de pruebas +- **ESLint:** Linting de código +- **Prettier:** Formateo de código +- **GitHub Actions:** Pipeline CI/CD -## 📁 File Organization Strategy +## 📁 Estrategia de Organización de Archivos -### Monorepo Structure +### Estructura de Monorepo ``` StackCode/ -├── packages/ # All packages -│ ├── cli/ # CLI package -│ ├── core/ # Core business logic -│ ├── i18n/ # Internationalization -│ └── vscode-extension/ # VS Code extension -├── docs/ # Project documentation -├── scripts/ # Build and utility scripts -└── types/ # Shared type definitions +├── packages/ # Todos los paquetes +│ ├── cli/ # Paquete CLI +│ ├── core/ # Lógica de negocio principal +│ ├── i18n/ # Internacionalización +│ └── vscode-extension/ # Extensión VS Code +├── docs/ # Documentación del proyecto +├── scripts/ # Scripts de build y utilidades +└── types/ # Definiciones de tipos compartidos ``` -### Package Structure Conventions +### Convenciones de Estructura de Paquetes + +Cada paquete sigue patrones consistentes: + +- `src/` - Código fuente +- `test/` - Archivos de prueba +- `dist/` - Salida compilada +- `package.json` - Configuración del paquete +- `tsconfig.json` - Configuración TypeScript +- `README.md` - Documentación del paquete +- `CHANGELOG.md` - Historial de versiones + +## 🔧 Sistema de Build -Each package follows consistent patterns: +### Compilación TypeScript -- `src/` - Source code -- `test/` - Test files -- `dist/` - Compiled output -- `package.json` - Package configuration -- `tsconfig.json` - TypeScript configuration -- `README.md` - Package documentation -- `CHANGELOG.md` - Version history +- **Build de Monorepo:** `tsc --build` para dependencias entre paquetes +- **Copia de Assets:** Templates y locales copiados a `dist/` +- **Permisos de Ejecutable:** Punto de entrada CLI marcado como ejecutable -## 🔧 Build System +### Build de Extensión VS Code -### TypeScript Compilation +- **Compilación de Extensión:** TypeScript a JavaScript +- **Build de Webview:** Vite para componentes React +- **Generación de Paquete:** Creación de archivo `.vsix` -- **Monorepo Build:** `tsc --build` for cross-package dependencies -- **Asset Copying:** Templates and locales copied to `dist/` -- **Executable Permissions:** CLI entry point marked as executable +## 🧪 Estrategia de Pruebas -### VS Code Extension Build +### Pruebas Unitarias -- **Extension Compilation:** TypeScript to JavaScript -- **Webview Build:** Vite for React components -- **Package Generation:** `.vsix` file creation +- **Lógica del Core:** Pruebas integrales para lógica de negocio +- **Comandos CLI:** Ejecución de comandos y manejo de errores +- **Validadores:** Validación de entrada y casos de error -## 🧪 Testing Strategy +### Pruebas de Integración -### Unit Testing +- **Entre Paquetes:** Asegurar que los paquetes funcionen juntos +- **Generación de Templates:** Verificar corrección de la salida +- **Integración GitHub:** Pruebas de interacción con API -- **Core Logic:** Comprehensive tests for business logic -- **CLI Commands:** Command execution and error handling -- **Validators:** Input validation and error cases +## ⚙️ Sistema de Validación de Dependencias -### Integration Testing +### Resumen de la Arquitectura -- **Cross-Package:** Ensure packages work together -- **Template Generation:** Verify output correctness -- **GitHub Integration:** API interaction testing +StackCode incluye un sistema inteligente de validación de dependencias que previene crashes y proporciona orientación útil cuando faltan herramientas requeridas. -## 🚀 Deployment and Distribution +### Componentes -### NPM Packages +#### 1. **Detección de Comandos (`isCommandAvailable`)** -- **@stackcode/cli:** Published to NPM for global installation -- **@stackcode/core:** Internal package, not published separately -- **@stackcode/i18n:** Internal package, not published separately +```typescript +// Verifica si un comando existe en el PATH del sistema +const isGoAvailable = await isCommandAvailable("go"); +``` + +#### 2. **Mapeo de Stacks (`getStackDependencies`)** + +```typescript +// Mapea cada stack a sus herramientas requeridas +const goDeps = getStackDependencies("go"); // Retorna: ['go'] +const phpDeps = getStackDependencies("php"); // Retorna: ['composer', 'php'] +``` + +#### 3. **Motor de Validación (`validateStackDependencies`)** + +```typescript +// Validación integral con resultados detallados +const result = await validateStackDependencies("go"); +// Retorna: { isValid: boolean, missingDependencies: string[], availableDependencies: string[] } +``` + +### Flujo de Validación + +1. **Verificación Pre-Instalación:** Antes de intentar instalación de dependencias +2. **Notificación del Usuario:** Advertencias claras sobre herramientas faltantes +3. **Orientación de Instalación:** Enlaces directos para descargar dependencias faltantes +4. **Degradación Elegante:** Opción de continuar sin herramientas +5. **Manejo de Errores:** Falla controlada en lugar de crashes + +### Dependencias de Stacks Soportados + +| Stack | Herramientas Requeridas | Estado de Validación | +| ------------------------------------ | ----------------------- | -------------------- | +| `go` | `go` | ✅ | +| `php` | `composer`, `php` | ✅ | +| `java` | `mvn`, `java` | ✅ | +| `python` | `pip`, `python` | ✅ | +| `node-js`, `node-ts`, `react`, `vue` | `npm` | ✅ | ## 🎓 Arquitectura del Modo Educativo -### Resumen General +### Resumen -El Modo Educativo es una característica transversal que mejora la experiencia del usuario proporcionando explicaciones contextuales y orientación sobre mejores prácticas a través de todo el kit de herramientas StackCode. +El Modo Educativo es una característica transversal que mejora la experiencia del usuario proporcionando explicaciones contextuales y orientación sobre mejores prácticas en todo el kit de herramientas StackCode. ### Implementación ```typescript // Flujo del Modo Educativo ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ -│ Comando Usuario │ -> │ Detección Modo │ -> │ Mostrar Mensaj. │ -│ --educate o │ │ Config Global + │ │ Mejores Práctic.│ +│ Comando Usuario │ -> │ Detección Modo │ -> │ Mostrar Mensajes│ +│ --educate o │ │ Config Global + │ │ Mejores Práctica│ │ config global │ │ Flag Comando │ │ & Explicaciones │ └─────────────────┘ └─────────────────┘ └─────────────────┘ ``` ### Componentes Principales -- **`educational-mode.ts`:** Lógica central del modo educativo +- **`educational-mode.ts`:** Lógica principal del modo educativo - `initEducationalMode()`: Detecta configuración y flags de comando - `showEducationalMessage()`: Muestra consejos contextuales - `showBestPractice()`: Muestra explicaciones de mejores prácticas - - `showSecurityTip()`: Resalta consideraciones de seguridad + - `showSecurityTip()`: Destaca consideraciones de seguridad - **Integración de Configuración:** - Configuración global: `stackcode config set educate true/false` @@ -334,40 +436,48 @@ El Modo Educativo es una característica transversal que mejora la experiencia d - Configuración interactiva vía `stackcode config` - **Sistema de Mensajes:** - - Explicaciones internacionalizadas (ES/PT/EN) - - Mensajes de respaldo para confiabilidad + - Explicaciones internacionalizadas (PT/EN) + - Mensajes de fallback para confiabilidad - Contenido contextual basado en el comando -### Cobertura del Contenido Educativo +### Cobertura de Contenido Educativo - **Inicialización de Proyectos:** Explica decisiones de scaffolding y dependencias - **Generación de Archivos:** Describe propósito de .gitignore, README, etc. - **Flujos Git:** Explica commits convencionales y beneficios del control de versiones -- **Prácticas de Seguridad:** Resalta importancia de .gitignore para secretos +- **Prácticas de Seguridad:** Destaca importancia de .gitignore para secretos - **Beneficios de Automatización:** Muestra valor de Husky, CI/CD y automatización de releases -### VS Code Extension +## 🚀 Despliegue y Distribución + +### Paquetes NPM + +- **@stackcode/cli:** Publicado en NPM para instalación global +- **@stackcode/core:** Paquete interno, no publicado separadamente +- **@stackcode/i18n:** Paquete interno, no publicado separadamente + +### Extensión VS Code -- **Marketplace:** Published to VS Code Marketplace -- **VSIX:** Direct installation package available +- **Marketplace:** Publicado en VS Code Marketplace +- **VSIX:** Paquete de instalación directa disponible -## 🔮 Extensibility Points +## 🔮 Puntos de Extensibilidad -### Template System +### Sistema de Templates -- **Custom Templates:** Easy addition of new project types -- **Template Composition:** Combining multiple template sources -- **Dynamic Configuration:** Runtime template customization +- **Templates Personalizados:** Adición fácil de nuevos tipos de proyecto +- **Composición de Templates:** Combinando múltiples fuentes de template +- **Configuración Dinámica:** Personalización de template en tiempo de ejecución -### Command System +### Sistema de Comandos -- **Plugin Architecture:** Soporte futuro para comandos personalizados -- **Middleware Support:** Hooks pre/post comando -- **Configuration Extension:** Reglas personalizadas de validación y generación +- **Arquitectura de Plugin:** Soporte futuro para comandos personalizados +- **Soporte de Middleware:** Hooks pre/post comando +- **Extensión de Configuración:** Reglas personalizadas de validación y generación ## 📚 Recursos Adicionales - **[Guía de Contribución](../CONTRIBUTING.md):** Flujo de trabajo de desarrollo y estándares - **[Documentación de Stacks](STACKS.md):** Stacks de tecnología soportados - **[Guía de Auto-hospedaje](SELF_HOSTING_GUIDE.md):** Opciones de despliegue -- **[Directorio ADR](adr/):** Registros de decisión arquitectural +- **[Directorio ADR](adr/):** Registros de decisión arquitecturaltectura de StackCode diff --git a/docs/pt-BR/ARCHITECTURE.md b/docs/pt-BR/ARCHITECTURE.md index faf4bf2c..b56c08ab 100644 --- a/docs/pt-BR/ARCHITECTURE.md +++ b/docs/pt-BR/ARCHITECTURE.md @@ -8,7 +8,7 @@ O StackCode segue uma **arquitetura de monorepo modular** com clara separação ``` ┌─────────────────────────────────────────────────────────────┐ -│ Ecossistema StackCode │ +│ Ecossistema StackCode │ ├─────────────────────────────────────────────────────────────┤ │ Pacote CLI │ Extensão VS Code │ │ (@stackcode/cli) │ (stackcode-vscode) │ @@ -50,602 +50,360 @@ O StackCode segue uma **arquitetura de monorepo modular** com clara separação **Componentes Principais:** -- **Comandos**: Implementações dos comandos CLI (`init`, `generate`, `commit`, etc.) -- **Manipuladores de Argumentos**: Parsing e validação de argumentos usando Yargs -- **Modo Educacional**: Sistema de aprendizado contextual com explicações de melhores práticas -- **Utilitários**: Funções auxiliares específicas da CLI +- **Camada de Comandos:** Pontos de entrada para todas as operações CLI +- **Manipuladores de Comandos:** Implementações de comandos individuais +- **Prompts Interativos:** Orientação e coleta de entrada do usuário +- **Modo Educacional:** Sistema de aprendizado contextual com explicações de melhores práticas +- **Tratamento de Erros:** Relatório de erros consistente e recuperação -**Responsabilidades:** +**Arquitetura:** -- Parsing de argumentos de linha de comando -- Interação com o usuário via terminal -- Fornecimento de explicações educacionais contextuais -- Orquestração de chamadas para o pacote core -- Tratamento de erros e apresentação de resultados +```typescript +cli/ +├── src/ +│ ├── index.ts # Ponto de entrada principal da CLI +│ ├── educational-mode.ts # Gerenciamento do modo educacional +│ ├── commands/ # Implementações de comandos +│ │ ├── init.ts # Scaffolding de projeto +│ │ ├── generate.ts # Geração de arquivos +│ │ ├── commit.ts # Commits convencionais +│ │ ├── git.ts # Gerenciamento de workflow Git +│ │ ├── release.ts # Gerenciamento de versão +│ │ ├── validate.ts # Validação de commits +│ │ ├── config.ts # Gerenciamento de configuração +│ │ └── ui.ts # Prompts interativos e feedback +│ └── types/ # Definições de tipos específicos da CLI +└── test/ # Testes de comandos +``` -### 2. **@stackcode/core** - Lógica de Negócio Central +### 2. **@stackcode/core** - Motor de Lógica de Negócio -**Propósito:** Implementa toda a lógica de negócio principal e funcionalidades centrais. +**Propósito:** Contém toda a lógica de negócio, utilitários e templates. **Componentes Principais:** -- **Geradores**: Criação de projetos a partir de templates -- **Validadores**: Validação de código e configurações -- **Integração GitHub**: API e funcionalidades de integração -- **Gerenciamento de Release**: Automação de release de versões -- **Sistema de Templates**: Mecanismo de scaffolding e templates -- **Utilitários**: Funções compartilhadas entre pacotes +- **Geradores:** Lógica de geração de projetos e arquivos +- **Validadores:** Validação de mensagens de commit e dependências do sistema +- **Integração GitHub:** Interações com API e automação +- **Gerenciamento de Release:** Versionamento semântico e geração de changelog +- **Sistema de Templates:** Templates de projeto configuráveis +- **Validação de Dependências:** Validação inteligente de ferramentas do sistema -**Responsabilidades:** +**Arquitetura:** -- Processamento de templates e scaffolding -- Integração com APIs externas (GitHub) -- Validação de estruturas de projeto -- Geração automática de documentação -- Gerenciamento de dependências e configurações +```typescript +core/ +├── src/ +│ ├── index.ts # Exportações do core +│ ├── generators.ts # Geradores de projeto/arquivo +│ ├── validator.ts # Lógica de validação +│ ├── github.ts # Integração com API GitHub +│ ├── release.ts # Gerenciamento de versão +│ ├── scaffold.ts # Scaffolding de projeto +│ ├── utils.ts # Utilitários compartilhados +│ ├── types.ts # Definições de tipos do core +│ └── templates/ # Templates de projeto +│ ├── common/ # Arquivos de template compartilhados +│ ├── node-js/ # Templates Node.js +│ ├── react/ # Templates React +│ ├── vue/ # Templates Vue.js +│ ├── python/ # Templates Python +│ ├── java/ # Templates Java +│ ├── go/ # Templates Go +│ ├── php/ # Templates PHP +│ ├── gitignore/ # Templates .gitignore +│ └── readme/ # Templates README +└── test/ # Testes de lógica do core +``` ### 3. **@stackcode/i18n** - Internacionalização -**Propósito:** Fornece suporte multi-idioma para toda a suite StackCode. +**Propósito:** Gerencia suporte multi-idioma em todos os pacotes. -**Componentes Principais:** +**Funcionalidades:** -- **Gerenciador de Locales**: Carregamento e gerenciamento de traduções -- **Sistema de Tradução**: API de tradução e fallbacks -- **Detecção de Idioma**: Detecção automática do idioma preferido do usuário +- Detecção dinâmica de locale +- Carregamento e cache de traduções +- Troca de idioma +- Mecanismos de fallback -**Responsabilidades:** +**Arquitetura:** -- Carregamento de arquivos de tradução -- Fornecimento de strings localizadas -- Gerenciamento de idiomas suportados -- Fallback para idioma padrão quando necessário +```typescript +i18n/ +├── src/ +│ ├── index.ts # Exportações i18n +│ └── locales/ # Arquivos de tradução +│ ├── en.json # Traduções em inglês +│ └── pt.json # Traduções em português +``` ### 4. **stackcode-vscode** - Extensão VS Code -**Propósito:** Integração nativa com VS Code para experiência de desenvolvimento aprimorada. +**Propósito:** Integra funcionalidade do StackCode diretamente no VS Code. **Componentes Principais:** -- **Comandos da Extensão**: Comandos VS Code que utilizam funcionalidades do StackCode -- **Providers**: Providers de dashboard, tree view e webview -- **Monitores**: Monitoramento de arquivos e mudanças do Git -- **Gerenciador de Notificações**: Sistema de notificações proativas -- **UI Webview**: Interface de usuário rica baseada na web +- **Comandos da Extensão:** Integração com paleta de comandos do VS Code +- **Monitores de Arquivo:** Detecção de mudanças em tempo real +- **Monitores Git:** Monitoramento do estado do Git +- **Provider de Dashboard:** Dashboard interativo do projeto +- **UI Webview:** Componentes de interface rica +- **Sistema de Notificações:** Orientação proativa ao usuário -**Responsabilidades:** +**Arquitetura:** -- Integração com comandos VS Code -- Monitoramento de atividade do workspace -- Fornecimento de UI rica via webviews -- Notificações contextuais e proativas +```typescript +vscode-extension/ +├── src/ +│ ├── extension.ts # Ponto de entrada da extensão +│ ├── commands/ # Comandos do VS Code +│ ├── config/ # Gerenciamento de configuração +│ ├── monitors/ # Monitoramento de arquivos e Git +│ ├── notifications/ # Sistema de notificações +│ ├── providers/ # Providers do VS Code +│ ├── services/ # Serviços da extensão +│ └── webview-ui/ # UI baseada em React +└── test/ # Testes da extensão +``` ## 🔄 Fluxo de Dados e Interações -### Fluxo Típico de Comando CLI +### Fluxo de Execução de Comandos -``` -Usuário → CLI Input → Yargs Parser → Command Handler → @stackcode/core → Execução → Resultado -``` - -### Fluxo da Extensão VS Code - -``` -Ação do Usuário → Comando VS Code → Extension Handler → @stackcode/core → Atualização UI → Feedback Visual -``` +1. **Entrada do Usuário:** Comando CLI ou ação no VS Code +2. **Análise de Comandos:** Yargs (CLI) ou API do VS Code +3. **Lógica do Core:** Execução da lógica de negócio em `@stackcode/core` +4. **Processamento i18n:** Mensagens localizadas via `@stackcode/i18n` +5. **Saída:** Resultados exibidos ao usuário -### Fluxo de Internacionalização +### Dependências Entre Pacotes -``` -Solicitação de String → i18n Manager → Carregamento de Locale → String Traduzida → Interface do Usuário +```mermaid +graph TD + A[Pacote CLI] --> C[Pacote Core] + A --> D[Pacote i18n] + B[Extensão VS Code] --> C + B --> D + C --> D ``` ## 🎯 Princípios de Design ### 1. **Separação de Responsabilidades** -Cada pacote tem responsabilidades claramente definidas: - -- **CLI**: Interação com usuário e parsing de comandos -- **Core**: Lógica de negócio e processamento -- **i18n**: Localização e traduções -- **VSCode**: Integração com editor e UI rica - -### 2. **Reutilização de Código** - -- Funcionalidades centrais ficam no pacote `@stackcode/core` -- Interfaces (CLI e VS Code) consomem a mesma lógica de negócio -- Sistema de templates compartilhado entre todos os pontos de entrada - -### 3. **Extensibilidade** - -- Sistema de templates permite adição fácil de novos stacks -- Arquitetura de plugins para extensões futuras -- APIs bem definidas entre pacotes - -### 4. **Type Safety** - -- TypeScript em todo o projeto -- Tipos compartilhados entre pacotes -- Validação de runtime com schemas quando necessário - -### 5. **Testabilidade** - -- Unidades testáveis pequenas e focadas -- Mocking de dependências externas -- Testes de integração entre pacotes - -## 🔧 Tecnologias e Dependências - -### Tecnologias Core - -- **TypeScript**: Linguagem principal para type safety -- **Node.js**: Runtime para CLI e extensão -- **ESM**: Módulos ES para estrutura moderna -- **Yargs**: Biblioteca CLI para parsing de argumentos - -### Ferramentas de Build - -- **TypeScript Compiler**: Transpilação de TypeScript -- **ESBuild**: Bundling rápido para a extensão VS Code -- **npm workspaces**: Gerenciamento de monorepo - -### Testes e Qualidade - -- **Jest**: Framework de testes principal -- **ESLint**: Linting de código -- **Prettier**: Formatação de código - -### Integrações Externas - -- **GitHub API**: Para funcionalidades de repositório -- **VS Code API**: Para integração com editor -- **npm registry**: Para publicação de pacotes - -## 📁 Organização de Código - -### Estrutura de Diretórios - -``` -packages/ -├── cli/ # Interface linha de comando -│ ├── src/ -│ │ ├── commands/ # Implementações de comandos -│ │ ├── types/ # Tipos específicos da CLI -│ │ └── index.ts # Ponto de entrada -│ └── test/ # Testes da CLI -├── core/ # Lógica de negócio principal -│ ├── src/ -│ │ ├── templates/ # Templates de projeto -│ │ ├── generators.ts # Lógica de geração -│ │ ├── validator.ts # Sistema de validação -│ │ └── types.ts # Tipos compartilhados -│ └── test/ # Testes do core -├── i18n/ # Sistema de internacionalização -│ ├── src/ -│ │ ├── locales/ # Arquivos de tradução -│ │ └── index.ts # API de i18n -│ └── test/ # Testes de i18n -└── vscode-extension/ # Extensão VS Code - ├── src/ - │ ├── commands/ # Comandos da extensão - │ ├── providers/ # Providers VS Code - │ ├── webview-ui/ # Interface webview - │ └── extension.ts # Ponto de entrada - └── test/ # Testes da extensão -``` - -### Convenções de Nomenclatura - -- **Arquivos**: camelCase para arquivos TypeScript -- **Classes**: PascalCase para classes e interfaces -- **Constantes**: UPPER_SNAKE_CASE para constantes -- **Funções**: camelCase para funções e métodos - -## 🔒 Considerações de Segurança - -### Validação de Input - -- Todas as entradas do usuário são validadas e sanitizadas -- Uso de esquemas de validação onde apropriado -- Prevenção de path traversal em operações de arquivo - -### Gerenciamento de Dependências - -- Auditoria regular de dependências para vulnerabilidades -- Pinning de versões de dependências críticas -- Uso de dependências mínimas necessárias - -### Tratamento de Dados Sensíveis - -- Tokens e credenciais nunca são logados -- Uso de variáveis de ambiente para dados sensíveis -- Criptografia para dados persistidos quando necessário - -## 🚀 Estratégia de Release - -### Versionamento - -- **Semantic Versioning**: Major.Minor.Patch -- **Versões Synchronized**: Todos os pacotes mantêm versão sincronizada -- **Changelog**: Changelog detalhado para cada release - -### Pipeline de Release - -1. **Desenvolvimento**: Feature branches com PRs -2. **Testes**: CI/CD automatizado com testes completos -3. **Staging**: Release candidates para testes -4. **Produção**: Release para npm registry -5. **Documentação**: Atualização de docs e guias - -### Compatibilidade - -- **Breaking Changes**: Apenas em major versions -- **Deprecations**: Avisos em minor versions antes de remoção -- **Migrations**: Guias de migração para breaking changes - -## 🔄 Fluxos de Desenvolvimento - -### Adicionando Novo Stack de Tecnologia - -1. Criar template em `packages/core/src/templates/novo-stack/` -2. Adicionar tipo em `packages/core/src/types.ts` -3. Implementar gerador em `packages/core/src/generators.ts` -4. Adicionar testes em `packages/core/test/` -5. Atualizar documentação - -### Adicionando Novo Comando CLI - -1. Criar handler em `packages/cli/src/commands/` -2. Registrar comando em `packages/cli/src/index.ts` -3. Implementar lógica em `packages/core/src/` -4. Adicionar testes para CLI e core -5. Atualizar documentação de comandos - -### Adicionando Funcionalidade VS Code - -1. Implementar comando em `packages/vscode-extension/src/commands/` -2. Registrar em `package.json` da extensão -3. Adicionar UI necessária em webview -4. Implementar testes da extensão -5. Testar integração completa - -## 📊 Métricas e Monitoramento - -### Métricas de Qualidade +- **Pacote CLI:** Interface do usuário e manipulação de comandos +- **Pacote Core:** Lógica de negócio e utilitários +- **Pacote i18n:** Preocupações de internacionalização +- **Extensão VS Code:** Integração com IDE -- **Cobertura de Testes**: >80% para todos os pacotes -- **Type Coverage**: >95% TypeScript coverage -- **Linting**: Zero issues ESLint/Prettier +### 2. **Inversão de Dependências** -### Métricas de Performance +- Módulos de alto nível não dependem de módulos de baixo nível +- Ambos dependem de abstrações (interfaces) +- Dependências externas são injetadas, não codificadas -- **Bundle Size**: Monitorar tamanho da extensão VS Code -- **Startup Time**: Tempo de inicialização da CLI -- **Memory Usage**: Uso de memória durante geração de projetos +### 3. **Responsabilidade Única** -### Métricas de Uso +- Cada pacote tem um propósito claramente definido +- Funções e classes têm responsabilidades únicas e bem definidas +- Templates são modulares e combináveis -- **Downloads**: Estatísticas npm registry -- **Comando Usage**: Telemetria anônima de comandos populares -- **Error Rates**: Monitoramento de erros em produção +### 4. **Princípio Aberto/Fechado** -## 🔮 Evolução da Arquitetura +- Sistema é aberto para extensão (novos templates, comandos) +- Fechado para modificação (lógica do core permanece estável) -### Próximas Iterações +## 🔍 Arquitetura de Validação do Sistema -- **Plugin System**: Sistema de plugins para extensibilidade -- **Cloud Integration**: Integração com serviços cloud -- **AI Templates**: Templates gerados por IA -- **Real-time Collaboration**: Funcionalidades colaborativas +### Sistema de Validação de Dependências -### Considerações de Escalabilidade +O StackCode implementa um sistema abrangente de validação de dependências para garantir inicialização suave de projetos em diferentes stacks de tecnologia. -- **Micro-frontends**: Possível divisão da extensão VS Code -- **Service Architecture**: Migração para arquitetura de serviços -- **Caching**: Sistema de cache para templates e metadados +#### Componentes Principais ---- - -_Para mais informações sobre desenvolvimento e contribuição, veja o [Guia de Contribuição](CONTRIBUTING.md)._ - -- **Command Layer:** Entry points for all CLI operations -- **Command Handlers:** Individual command implementations -- **Interactive Prompts:** User guidance and input collection -- **Error Handling:** Consistent error reporting and recovery - -**Architecture:** +**1. Detecção de Disponibilidade de Comandos (`isCommandAvailable`)** ```typescript -cli/ -├── src/ -│ ├── index.ts # Main CLI entry point -│ ├── commands/ # Command implementations -│ │ ├── init.ts # Project scaffolding -│ │ ├── generate.ts # File generation -│ │ ├── commit.ts # Conventional commits -│ │ ├── git.ts # Git workflow management -│ │ ├── release.ts # Version management -│ │ ├── validate.ts # Commit validation -│ │ └── config.ts # Configuration management -│ └── types/ # CLI-specific type definitions -└── test/ # Command tests +// Detecção de comando multiplataforma +const isAvailable = await isCommandAvailable("go"); +// Usa 'which' (Unix) ou 'where' (Windows) ``` -### 2. **@stackcode/core** - Motor de Lógica de Negócio - -**Propósito:** Contém toda a lógica de negócio, utilitários e templates. - -**Key Components:** - -- **Generators:** Project and file generation logic -- **Validators:** Commit message and project validation -- **GitHub Integration:** API interactions and automation -- **Release Management:** Semantic versioning and changelog generation -- **Template System:** Configurable project templates - -**Architecture:** +**2. Mapeamento de Dependências de Stack (`getStackDependencies`)** ```typescript -core/ -├── src/ -│ ├── index.ts # Core exports -│ ├── generators.ts # Project/file generators -│ ├── validator.ts # Validation logic -│ ├── github.ts # GitHub API integration -│ ├── release.ts # Version management -│ ├── scaffold.ts # Project scaffolding -│ ├── utils.ts # Shared utilities -│ ├── types.ts # Core type definitions -│ └── templates/ # Project templates -│ ├── common/ # Shared template files -│ ├── node-js/ # Node.js templates -│ ├── react/ # React templates -│ ├── vue/ # Vue.js templates -│ ├── python/ # Python templates -│ ├── java/ # Java templates -│ ├── go/ # Go templates -│ ├── php/ # PHP templates -│ ├── gitignore/ # .gitignore templates -│ └── readme/ # README templates -└── test/ # Core logic tests +const stackMap = { + go: ["go"], + php: ["composer", "php"], + java: ["mvn", "java"], + python: ["pip", "python"], + react: ["npm"], + vue: ["npm"], +}; ``` -### 3. **@stackcode/i18n** - Internacionalização - -**Propósito:** Gerencia suporte multilíngue em todos os pacotes. - -**Features:** - -- Dynamic locale detection -- Translation loading and caching -- Language switching -- Fallback mechanisms - -**Architecture:** +**3. Validação Abrangente (`validateStackDependencies`)** ```typescript -i18n/ -├── src/ -│ ├── index.ts # i18n exports -│ └── locales/ # Translation files -│ ├── en.json # English translations -│ └── pt.json # Portuguese translations +const result = await validateStackDependencies("go"); +// Retorna: { isValid, missingDependencies, availableDependencies } ``` -### 4. **stackcode-vscode** - VS Code Extension - -**Propósito:** Integra a funcionalidade do StackCode diretamente no VS Code. - -**Key Components:** - -- **Extension Commands:** VS Code command palette integration -- **File Monitors:** Real-time file change detection -- **Git Monitors:** Git state monitoring -- **Dashboard Provider:** Interactive project dashboard -- **Webview UI:** Rich user interface components -- **Notification System:** Proactive user guidance - -**Architecture:** - -```typescript -vscode-extension/ -├── src/ -│ ├── extension.ts # Extension entry point -│ ├── commands/ # VS Code commands -│ ├── config/ # Configuration management -│ ├── monitors/ # File and Git monitoring -│ ├── notifications/ # Notification system -│ ├── providers/ # VS Code providers -│ ├── services/ # Extension services -│ └── webview-ui/ # React-based UI -└── test/ # Extension tests -``` - -## 🔄 Data Flow and Interactions - -### Command Execution Flow - -1. **User Input:** CLI command or VS Code action -2. **Command Parsing:** Yargs (CLI) or VS Code API -3. **Core Logic:** Business logic execution in `@stackcode/core` -4. **i18n Processing:** Localized messages via `@stackcode/i18n` -5. **Output:** Results displayed to user - -### Cross-Package Dependencies +#### Fluxo de Validação ```mermaid graph TD - A[CLI Package] --> C[Core Package] - A --> D[i18n Package] - B[VS Code Extension] --> C - B --> D - C --> D + A[Usuário executa 'stc init'] --> B[Selecionar Stack de Tecnologia] + B --> C[Validar Dependências do Stack] + C --> D{Todas as Dependências Disponíveis?} + D -->|Sim| E[✅ Prosseguir com Instalação] + D -->|Não| F[⚠️ Mostrar Dependências Faltando] + F --> G[Exibir Instruções de Instalação] + G --> H{Usuário Escolhe Continuar?} + H -->|Sim| I[🚧 Criar Apenas Estrutura do Projeto] + H -->|Não| J[❌ Cancelar Operação] + E --> K[🎉 Completar Configuração do Projeto] + I --> L[⚠️ Instalação Manual de Dependências Necessária] ``` -## 🎯 Design Principles - -### 1. **Separation of Concerns** - -- **CLI Package:** User interface and command handling -- **Core Package:** Business logic and utilities -- **i18n Package:** Internationalization concerns -- **VS Code Extension:** IDE integration - -### 2. **Dependency Inversion** - -- Higher-level modules don't depend on lower-level modules -- Both depend on abstractions (interfaces) -- External dependencies are injected, not hardcoded +#### Estratégia de Tratamento de Erros -### 3. **Single Responsibility** +- **Degradação Graciosa:** Criação de projeto é bem-sucedida mesmo sem dependências +- **Mensagens Informativas:** Instruções claras de instalação com URLs oficiais +- **Escolha do Usuário:** Opção de prosseguir ou cancelar quando dependências estão faltando +- **Suporte i18n:** Mensagens de erro localizadas em múltiplos idiomas -- Each package has a clearly defined purpose -- Functions and classes have single, well-defined responsibilities -- Templates are modular and composable +## 🛠️ Stack de Tecnologia -### 4. **Open/Closed Principle** +### Tecnologias Principais -- System is open for extension (new templates, commands) -- Closed for modification (core logic remains stable) +- **TypeScript:** Segurança de tipos e recursos modernos do JavaScript +- **Node.js:** Ambiente de execução +- **ESM:** Sistema de módulos moderno -## 🛠️ Technology Stack +### Específicas da CLI -### Core Technologies +- **Yargs:** Análise de argumentos de linha de comando +- **Inquirer:** Prompts interativos de linha de comando -- **TypeScript:** Type safety and modern JavaScript features -- **Node.js:** Runtime environment -- **ESM:** Modern module system +### Específicas da Extensão VS Code -### CLI-Specific +- **API do VS Code:** Framework de desenvolvimento de extensões +- **React:** Componentes UI para webview +- **Vite:** Ferramenta de build para assets webview -- **Yargs:** Command-line argument parsing -- **Inquirer:** Interactive command-line prompts +### Ferramentas de Desenvolvimento -### VS Code Extension-Specific +- **Vitest/Jest:** Frameworks de teste +- **ESLint:** Linting de código +- **Prettier:** Formatação de código +- **GitHub Actions:** Pipeline CI/CD -- **VS Code API:** Extension development framework -- **React:** Webview UI components -- **Vite:** Build tool for webview assets +## 📁 Estratégia de Organização de Arquivos -### Development Tools - -- **Vitest/Jest:** Testing frameworks -- **ESLint:** Code linting -- **Prettier:** Code formatting -- **GitHub Actions:** CI/CD pipeline - -## 📁 File Organization Strategy - -### Monorepo Structure +### Estrutura de Monorepo ``` StackCode/ -├── packages/ # All packages -│ ├── cli/ # CLI package -│ ├── core/ # Core business logic -│ ├── i18n/ # Internationalization -│ └── vscode-extension/ # VS Code extension -├── docs/ # Project documentation -├── scripts/ # Build and utility scripts -└── types/ # Shared type definitions +├── packages/ # Todos os pacotes +│ ├── cli/ # Pacote CLI +│ ├── core/ # Lógica de negócio principal +│ ├── i18n/ # Internacionalização +│ └── vscode-extension/ # Extensão VS Code +├── docs/ # Documentação do projeto +├── scripts/ # Scripts de build e utilitários +└── types/ # Definições de tipos compartilhados ``` -### Package Structure Conventions +### Convenções de Estrutura de Pacotes -Each package follows consistent patterns: +Cada pacote segue padrões consistentes: -- `src/` - Source code -- `test/` - Test files -- `dist/` - Compiled output -- `package.json` - Package configuration -- `tsconfig.json` - TypeScript configuration -- `README.md` - Package documentation -- `CHANGELOG.md` - Version history +- `src/` - Código fonte +- `test/` - Arquivos de teste +- `dist/` - Saída compilada +- `package.json` - Configuração do pacote +- `tsconfig.json` - Configuração TypeScript +- `README.md` - Documentação do pacote +- `CHANGELOG.md` - Histórico de versões -## 🔧 Build System +## 🔧 Sistema de Build -### TypeScript Compilation +### Compilação TypeScript -- **Monorepo Build:** `tsc --build` for cross-package dependencies -- **Asset Copying:** Templates and locales copied to `dist/` -- **Executable Permissions:** CLI entry point marked as executable +- **Build de Monorepo:** `tsc --build` para dependências entre pacotes +- **Cópia de Assets:** Templates e locales copiados para `dist/` +- **Permissões de Executável:** Ponto de entrada CLI marcado como executável -### VS Code Extension Build +### Build da Extensão VS Code -- **Extension Compilation:** TypeScript to JavaScript -- **Webview Build:** Vite for React components -- **Package Generation:** `.vsix` file creation +- **Compilação da Extensão:** TypeScript para JavaScript +- **Build de Webview:** Vite para componentes React +- **Geração de Pacote:** Criação de arquivo `.vsix` -## 🧪 Testing Strategy +## 🧪 Estratégia de Testes -### Unit Testing +### Testes Unitários -- **Core Logic:** Comprehensive tests for business logic -- **CLI Commands:** Command execution and error handling -- **Validators:** Input validation and error cases +- **Lógica do Core:** Testes abrangentes para lógica de negócio +- **Comandos CLI:** Execução de comandos e tratamento de erros +- **Validadores:** Validação de entrada e casos de erro -### Integration Testing +### Testes de Integração -- **Cross-Package:** Ensure packages work together -- **Template Generation:** Verify output correctness -- **GitHub Integration:** API interaction testing +- **Entre Pacotes:** Garantir que pacotes funcionem juntos +- **Geração de Templates:** Verificar correção da saída +- **Integração GitHub:** Teste de interação com API -## 🎓 Arquitetura do Modo Educacional +## ⚙️ Sistema de Validação de Dependências -### Visão Geral +### Visão Geral da Arquitetura -O Modo Educacional é uma funcionalidade transversal que melhora a experiência do usuário fornecendo explicações contextuais e orientações sobre melhores práticas em todo o kit de ferramentas StackCode. +O StackCode inclui um sistema inteligente de validação de dependências que previne crashes e fornece orientação útil quando ferramentas necessárias estão faltando. -### Implementação +### Componentes + +#### 1. **Detecção de Comandos (`isCommandAvailable`)** ```typescript -// Fluxo do Modo Educacional -┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ -│ Comando Usuário │ -> │ Detecção Modo │ -> │ Mostrar Mensag. │ -│ --educate ou │ │ Config Global + │ │ Melhores Prátic.│ -│ config global │ │ Flag Comando │ │ & Explicações │ -└─────────────────┘ └─────────────────┘ └─────────────────┘ +// Verifica se um comando existe no PATH do sistema +const isGoAvailable = await isCommandAvailable("go"); ``` -### Componentes Principais - -- **`educational-mode.ts`:** Lógica central do modo educacional - - `initEducationalMode()`: Detecta configuração e flags de comando - - `showEducationalMessage()`: Exibe dicas contextuais - - `showBestPractice()`: Mostra explicações de melhores práticas - - `showSecurityTip()`: Destaca considerações de segurança +#### 2. **Mapeamento de Stacks (`getStackDependencies`)** -- **Integração de Configuração:** - - Configuração global: `stackcode config set educate true/false` - - Flag por comando: `--educate` em qualquer comando - - Configuração interativa via `stackcode config` - -- **Sistema de Mensagens:** - - Explicações internacionalizadas (PT/EN/ES) - - Mensagens de fallback para confiabilidade - - Conteúdo contextual baseado no comando +```typescript +// Mapeia cada stack para suas ferramentas necessárias +const goDeps = getStackDependencies("go"); // Retorna: ['go'] +const phpDeps = getStackDependencies("php"); // Retorna: ['composer', 'php'] +``` -### Cobertura do Conteúdo Educacional +#### 3. **Motor de Validação (`validateStackDependencies`)** -- **Inicialização de Projetos:** Explica decisões de scaffolding e dependências -- **Geração de Arquivos:** Descreve propósito do .gitignore, README, etc. -- **Fluxos Git:** Explica commits convencionais e benefícios do controle de versão -- **Práticas de Segurança:** Destaca importância do .gitignore para segredos -- **Benefícios da Automação:** Mostra valor do Husky, CI/CD e automação de releases - -## 🚀 Deployment and Distribution +```typescript +// Validação abrangente com resultados detalhados +const result = await validateStackDependencies("go"); +// Retorna: { isValid: boolean, missingDependencies: string[], availableDependencies: string[] } +``` -### NPM Packages +### Fluxo de Validação -- **@stackcode/cli:** Published to NPM for global installation -- **@stackcode/core:** Internal package, not published separately -- **@stackcode/i18n:** Internal package, not published separately +1. **Verificação Pré-Instalação:** Antes de tentar instalação de dependências +2. **Notificação do Usuário:** Avisos claros sobre ferramentas faltando +3. **Orientação de Instalação:** Links diretos para baixar dependências faltando +4. **Degradação Graciosa:** Opção de continuar sem ferramentas +5. **Tratamento de Erros:** Falha controlada ao invés de crashes -### VS Code Extension +### Dependências de Stacks Suportados -- **Marketplace:** Published to VS Code Marketplace -- **VSIX:** Direct installation package available +| Stack | Ferramentas Necessárias | Status da Validação | +| ------------------------------------ | ----------------------- | ------------------- | +| `go` | `go` | ✅ | +| `php` | `composer`, `php` | ✅ | +| `java` | `mvn`, `java` | ✅ | +| `python` | `pip`, `python` | ✅ | +| `node-js`, `node-ts`, `react`, `vue` | `npm` | ✅ | ## 🎓 Arquitetura do Modo Educacional @@ -690,19 +448,32 @@ O Modo Educacional é um recurso transversal que aprimora a experiência do usu - **Práticas de Segurança:** Destaca importância do .gitignore para segredos - **Benefícios da Automação:** Mostra valor do Husky, CI/CD e automação de releases -## 🔮 Extensibility Points +## 🚀 Implantação e Distribuição + +### Pacotes NPM + +- **@stackcode/cli:** Publicado no NPM para instalação global +- **@stackcode/core:** Pacote interno, não publicado separadamente +- **@stackcode/i18n:** Pacote interno, não publicado separadamente + +### Extensão VS Code + +- **Marketplace:** Publicado no VS Code Marketplace +- **VSIX:** Pacote de instalação direta disponível + +## 🔮 Pontos de Extensibilidade -### Template System +### Sistema de Templates -- **Custom Templates:** Easy addition of new project types -- **Template Composition:** Combining multiple template sources -- **Dynamic Configuration:** Runtime template customization +- **Templates Personalizados:** Adição fácil de novos tipos de projeto +- **Composição de Templates:** Combinando múltiplas fontes de template +- **Configuração Dinâmica:** Personalização de template em tempo de execução -### Command System +### Sistema de Comandos -- **Plugin Architecture:** Suporte futuro para comandos personalizados -- **Middleware Support:** Hooks pré/pós comando -- **Configuration Extension:** Regras personalizadas de validação e geração +- **Arquitetura de Plugin:** Suporte futuro para comandos personalizados +- **Suporte a Middleware:** Hooks pré/pós comando +- **Extensão de Configuração:** Regras personalizadas de validação e geração ## 📚 Recursos Adicionais diff --git a/package-lock.json b/package-lock.json index 87aaac09..dacd5250 100644 --- a/package-lock.json +++ b/package-lock.json @@ -676,6 +676,30 @@ } } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@esbuild/linux-x64": { "version": "0.25.10", "cpu": [ @@ -944,6 +968,109 @@ "url": "https://opencollective.com/express" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "dev": true, @@ -1963,6 +2090,17 @@ "@octokit/openapi-types": "^26.0.0" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.35", "dev": true, @@ -2070,6 +2208,10 @@ "resolved": "packages/core", "link": true }, + "node_modules/@stackcode/github-auth": { + "resolved": "packages/github-auth", + "link": true + }, "node_modules/@stackcode/i18n": { "resolved": "packages/i18n", "link": true @@ -2142,6 +2284,34 @@ "tailwindcss": "4.1.13" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "dev": true, @@ -2250,6 +2420,17 @@ "@types/node": "*" } }, + "node_modules/@types/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/minimatch": "^5.1.2", + "@types/node": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "dev": true, @@ -2302,6 +2483,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mocha": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.5.2", "devOptional": true, @@ -2720,159 +2915,408 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vscode/vsce": { - "version": "2.32.0", + "node_modules/@vscode/test-electron": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.5.2.tgz", + "integrity": "sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==", "dev": true, "license": "MIT", "dependencies": { - "@azure/identity": "^4.1.0", - "@vscode/vsce-sign": "^2.0.0", - "azure-devops-node-api": "^12.5.0", - "chalk": "^2.4.2", - "cheerio": "^1.0.0-rc.9", - "cockatiel": "^3.1.2", - "commander": "^6.2.1", - "form-data": "^4.0.0", - "glob": "^7.0.6", - "hosted-git-info": "^4.0.2", - "jsonc-parser": "^3.2.0", - "leven": "^3.1.0", - "markdown-it": "^12.3.2", - "mime": "^1.3.4", - "minimatch": "^3.0.3", - "parse-semver": "^1.1.1", - "read": "^1.0.7", - "semver": "^7.5.2", - "tmp": "^0.2.1", - "typed-rest-client": "^1.8.4", - "url-join": "^4.0.1", - "xml2js": "^0.5.0", - "yauzl": "^2.3.1", - "yazl": "^2.2.2" - }, - "bin": { - "vsce": "vsce" + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "jszip": "^3.10.1", + "ora": "^8.1.0", + "semver": "^7.6.2" }, "engines": { - "node": ">= 16" - }, - "optionalDependencies": { - "keytar": "^7.7.0" - } - }, - "node_modules/@vscode/vsce-sign": { - "version": "2.0.7", - "dev": true, - "hasInstallScript": true, - "license": "SEE LICENSE IN LICENSE.txt", - "optionalDependencies": { - "@vscode/vsce-sign-alpine-arm64": "2.0.6", - "@vscode/vsce-sign-alpine-x64": "2.0.6", - "@vscode/vsce-sign-darwin-arm64": "2.0.6", - "@vscode/vsce-sign-darwin-x64": "2.0.6", - "@vscode/vsce-sign-linux-arm": "2.0.6", - "@vscode/vsce-sign-linux-arm64": "2.0.6", - "@vscode/vsce-sign-linux-x64": "2.0.6", - "@vscode/vsce-sign-win32-arm64": "2.0.6", - "@vscode/vsce-sign-win32-x64": "2.0.6" + "node": ">=16" } }, - "node_modules/@vscode/vsce-sign-linux-x64": { - "version": "2.0.6", - "cpu": [ - "x64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.txt", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@vscode/vsce/node_modules/brace-expansion": { - "version": "1.1.12", + "node_modules/@vscode/test-electron/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@vscode/vsce/node_modules/minimatch": { - "version": "3.1.2", + "node_modules/@vscode/test-electron/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "MIT", "engines": { - "node": "*" + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/acorn": { - "version": "8.15.0", + "node_modules/@vscode/test-electron/node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "restore-cursor": "^5.0.0" }, "engines": { - "node": ">=0.4.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", + "node_modules/@vscode/test-electron/node_modules/emoji-regex": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz", + "integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==", "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/add-stream": { - "version": "1.0.0", "license": "MIT" }, - "node_modules/agent-base": { - "version": "7.1.4", + "node_modules/@vscode/test-electron/node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 14" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ajv": { - "version": "6.12.6", + "node_modules/@vscode/test-electron/node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "dev": true, "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "engines": { + "node": ">=18" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", + "node_modules/@vscode/test-electron/node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, "license": "MIT", "dependencies": { - "type-fest": "^0.21.3" + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" + "node_modules/@vscode/test-electron/node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vscode/test-electron/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@vscode/vsce": { + "version": "2.32.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/identity": "^4.1.0", + "@vscode/vsce-sign": "^2.0.0", + "azure-devops-node-api": "^12.5.0", + "chalk": "^2.4.2", + "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", + "commander": "^6.2.1", + "form-data": "^4.0.0", + "glob": "^7.0.6", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "leven": "^3.1.0", + "markdown-it": "^12.3.2", + "mime": "^1.3.4", + "minimatch": "^3.0.3", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "semver": "^7.5.2", + "tmp": "^0.2.1", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.5.0", + "yauzl": "^2.3.1", + "yazl": "^2.2.2" + }, + "bin": { + "vsce": "vsce" + }, + "engines": { + "node": ">= 16" + }, + "optionalDependencies": { + "keytar": "^7.7.0" + } + }, + "node_modules/@vscode/vsce-sign": { + "version": "2.0.7", + "dev": true, + "hasInstallScript": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optionalDependencies": { + "@vscode/vsce-sign-alpine-arm64": "2.0.6", + "@vscode/vsce-sign-alpine-x64": "2.0.6", + "@vscode/vsce-sign-darwin-arm64": "2.0.6", + "@vscode/vsce-sign-darwin-x64": "2.0.6", + "@vscode/vsce-sign-linux-arm": "2.0.6", + "@vscode/vsce-sign-linux-arm64": "2.0.6", + "@vscode/vsce-sign-linux-x64": "2.0.6", + "@vscode/vsce-sign-win32-arm64": "2.0.6", + "@vscode/vsce-sign-win32-x64": "2.0.6" + } + }, + "node_modules/@vscode/vsce-sign-linux-x64": { + "version": "2.0.6", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@vscode/vsce/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/add-stream": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/agent-base": { + "version": "7.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2905,6 +3349,13 @@ "normalize-path": "^2.0.0" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "dev": true, @@ -3365,6 +3816,17 @@ "node": ">=0.10.0" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, "node_modules/bl": { "version": "4.1.0", "license": "MIT", @@ -3400,6 +3862,13 @@ "node": ">=0.10.0" } }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, "node_modules/browserslist": { "version": "4.26.2", "dev": true, @@ -3724,6 +4193,26 @@ "fsevents": "^1.0.0" } }, + "node_modules/chokidar/node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "Upgrade to fsevents v2 to mitigate potential security issues", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, "node_modules/chokidar/node_modules/is-extglob": { "version": "1.0.0", "dev": true, @@ -4279,6 +4768,13 @@ "node": ">=8" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "dev": true, @@ -4292,29 +4788,6 @@ "node": ">= 8" } }, - "node_modules/crypto-random-string": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "type-fest": "^1.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/crypto-random-string/node_modules/type-fest": { - "version": "1.4.0", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/css-select": { "version": "5.2.2", "dev": true, @@ -4410,6 +4883,19 @@ } } }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/decode-uri-component": { "version": "0.2.2", "dev": true, @@ -4596,6 +5082,16 @@ "node": ">=8" } }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "dev": true, @@ -4686,6 +5182,13 @@ "dev": true, "license": "MIT" }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "dev": true, @@ -5513,6 +6016,14 @@ "node": ">=16.0.0" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/filename-regex": { "version": "2.0.1", "dev": true, @@ -5556,6 +6067,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, "node_modules/flat-cache": { "version": "4.0.1", "dev": true, @@ -5606,6 +6127,36 @@ "node": ">=0.10.0" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.4", "dev": true, @@ -5655,6 +6206,21 @@ "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "dev": true, @@ -5705,6 +6271,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "dev": true, @@ -6181,6 +6760,16 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, "node_modules/hosted-git-info": { "version": "4.1.0", "dev": true, @@ -6311,6 +6900,13 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true, + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "dev": true, @@ -6822,6 +7418,16 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-object": { "version": "2.0.4", "dev": true, @@ -7131,6 +7737,22 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest": { "version": "29.7.0", "dev": true, @@ -8755,30 +9377,76 @@ "node": ">=6" } }, - "node_modules/jsonc-parser": { - "version": "3.3.1", + "node_modules/jsonc-parser": { + "version": "3.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, "license": "MIT" }, - "node_modules/jsonwebtoken": { - "version": "9.0.2", + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" + "safe-buffer": "~5.1.0" } }, "node_modules/jwa": { @@ -8858,6 +9526,16 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lightningcss": { "version": "1.30.1", "dev": true, @@ -9330,6 +10008,19 @@ "node": ">=6" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mimic-response": { "version": "3.1.0", "dev": true, @@ -9402,25 +10093,356 @@ "is-plain-object": "^2.0.4" }, "engines": { - "node": ">=0.10.0" + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/mocha": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/mocha/node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mocha/node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/mocha/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/mocha/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/mocha/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mocha/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mocha/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/mkdirp": { - "version": "0.5.6", + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "license": "MIT", "dependencies": { - "minimist": "^1.2.6" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">=10" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", + "node_modules/mocha/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, - "license": "MIT", - "optional": true + "license": "ISC", + "engines": { + "node": ">=10" + } }, "node_modules/ms": { "version": "2.1.3", @@ -9431,6 +10453,14 @@ "version": "0.0.8", "license": "ISC" }, + "node_modules/nan": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz", + "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/nanoid": { "version": "3.3.11", "dev": true, @@ -10019,6 +11049,20 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "dev": true, @@ -10178,6 +11222,30 @@ "dev": true, "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/path-type": { "version": "3.0.0", "dev": true, @@ -10549,6 +11617,16 @@ "node": ">=0.10.0" } }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/rc": { "version": "1.2.8", "dev": true, @@ -11308,6 +12386,16 @@ "node": ">=10" } }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "dev": true, @@ -11376,6 +12464,13 @@ "node": ">=0.10.0" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "dev": true, @@ -11806,6 +12901,19 @@ "dev": true, "license": "MIT" }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "dev": true, @@ -11849,6 +12957,22 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.padend": { "version": "3.1.6", "dev": true, @@ -11929,6 +13053,20 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "dev": true, @@ -12311,6 +13449,60 @@ } } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/tslib": { "version": "2.8.1", "license": "0BSD" @@ -12550,19 +13742,6 @@ "node": ">=0.10.0" } }, - "node_modules/unique-string": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "crypto-random-string": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/universal-user-agent": { "version": "7.0.3", "license": "ISC" @@ -12686,6 +13865,13 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "dev": true, @@ -13079,6 +14265,13 @@ "version": "1.0.0", "license": "MIT" }, + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/wrap-ansi": { "version": "6.2.0", "license": "MIT", @@ -13091,6 +14284,61 @@ "node": ">=8" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", "license": "MIT", @@ -13213,6 +14461,35 @@ "node": ">=12" } }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yauzl": { "version": "2.10.0", "dev": true, @@ -13230,6 +14507,16 @@ "buffer-crc32": "~0.2.3" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "dev": true, @@ -13246,7 +14533,9 @@ "version": "2.0.0", "license": "MIT", "dependencies": { + "@octokit/rest": "^22.0.0", "@stackcode/core": "^1.0.4", + "@stackcode/github-auth": "^1.0.0", "@stackcode/i18n": "^1.0.4", "chalk": "^5.3.0", "configstore": "^7.0.0", @@ -13272,6 +14561,7 @@ }, "packages/cli/node_modules/@stackcode/i18n/node_modules/configstore": { "version": "6.0.0", + "extraneous": true, "license": "BSD-2-Clause", "dependencies": { "dot-prop": "^6.0.1", @@ -13297,29 +14587,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "packages/cli/node_modules/dot-prop": { - "version": "6.0.1", - "license": "MIT", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "packages/cli/node_modules/write-file-atomic": { - "version": "3.0.3", - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, "packages/core": { "name": "@stackcode/core", "version": "1.0.4", @@ -13337,6 +14604,25 @@ "vitest": "^3.2.4" } }, + "packages/github-auth": { + "name": "@stackcode/github-auth", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@types/node": "^24.2.0", + "typescript": "^5.8.3", + "vitest": "^3.2.4" + }, + "peerDependencies": { + "@octokit/rest": "^22.0.0", + "vscode": "^1.85.0" + }, + "peerDependenciesMeta": { + "vscode": { + "optional": true + } + } + }, "packages/i18n": { "name": "@stackcode/i18n", "version": "1.0.4", @@ -13361,27 +14647,35 @@ "version": "2.0.0", "license": "ISC", "dependencies": { + "@octokit/rest": "^22.0.0", "@stackcode/core": "^1.0.4", + "@stackcode/github-auth": "^1.0.0", "@stackcode/i18n": "^1.0.4" }, "devDependencies": { "@tailwindcss/postcss": "^4.1.11", + "@types/glob": "^8.1.0", "@types/jest": "^29.5.14", + "@types/mocha": "^10.0.6", "@types/node": "^16.18.126", "@types/react": "^19.1.9", "@types/react-dom": "^19.1.7", "@types/vscode": "^1.85.0", "@vitejs/plugin-react": "^5.0.0", + "@vscode/test-electron": "^2.3.9", "@vscode/vsce": "^2.19.0", "autoprefixer": "^10.4.21", + "glob": "^10.3.10", "jest": "^29.5.0", "lucide-react": "^0.539.0", + "mocha": "^10.3.0", "npm-run-all": "^4.1.5", "postcss": "^8.5.6", "react": "^19.1.1", "react-dom": "^19.1.1", "tailwindcss": "^4.1.11", "ts-jest": "^29.4.1", + "ts-node": "^10.9.2", "typescript": "^4.9.5", "vite": "^7.1.1" }, @@ -13394,6 +14688,27 @@ "dev": true, "license": "MIT" }, + "packages/vscode-extension/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "packages/vscode-extension/node_modules/typescript": { "version": "4.9.5", "dev": true, diff --git a/packages/cli/dist/commands/commit.js b/packages/cli/dist/commands/commit.js index 4c9545a5..c95aa986 100644 --- a/packages/cli/dist/commands/commit.js +++ b/packages/cli/dist/commands/commit.js @@ -1,4 +1,4 @@ -import { runCommand, getCommandOutput, getErrorMessage } from "@stackcode/core"; +import { getCommandOutput, getErrorMessage, runCommitWorkflow, } from "@stackcode/core"; import { t } from "@stackcode/i18n"; import * as ui from "./ui.js"; import { initEducationalMode, showBestPractice } from "../educational-mode.js"; @@ -16,24 +16,27 @@ export const getCommitCommand = () => ({ return; } const answers = await ui.promptForCommitAnswers(); - let commitMessage = `${answers.type}`; - if (answers.scope) { - commitMessage += `(${answers.scope.trim()})`; - } - commitMessage += `: ${answers.shortDescription.trim()}`; - if (answers.longDescription) { - commitMessage += `\n\n${answers.longDescription.replace(/\|/g, "\n")}`; + const result = await runCommitWorkflow({ + cwd: process.cwd(), + type: answers.type, + scope: answers.scope, + shortDescription: answers.shortDescription, + longDescription: answers.longDescription, + breakingChanges: answers.breakingChanges, + affectedIssues: answers.affectedIssues, + }); + if (result.status === "committed") { + ui.log.success(t("commit.success")); } - if (answers.breakingChanges) { - commitMessage += `\n\nBREAKING CHANGE: ${answers.breakingChanges.trim()}`; + else if (result.reason === "no-staged-changes") { + ui.log.warning(t("commit.error_no_changes_staged")); } - if (answers.affectedIssues) { - commitMessage += `\n\n${answers.affectedIssues.trim()}`; + else { + ui.log.error(t("common.unexpected_error")); + if (result.error) { + ui.log.gray(result.error); + } } - await runCommand("git", ["commit", "-m", commitMessage], { - cwd: process.cwd(), - }); - ui.log.success(t("commit.success")); } catch (error) { ui.log.error(t("common.unexpected_error")); diff --git a/packages/cli/dist/commands/generate.js b/packages/cli/dist/commands/generate.js index cc6a6848..0a697262 100644 --- a/packages/cli/dist/commands/generate.js +++ b/packages/cli/dist/commands/generate.js @@ -1,36 +1,31 @@ import fs from "fs/promises"; import path from "path"; -import { generateGitignoreContent, generateReadmeContent, } from "@stackcode/core"; +import { runGenerateWorkflow, } from "@stackcode/core"; import { t } from "@stackcode/i18n"; import * as ui from "./ui.js"; +import { showEducationalMessage } from "../educational-mode.js"; async function getProjectStack() { const configPath = path.join(process.cwd(), ".stackcoderc.json"); try { const content = await fs.readFile(configPath, "utf-8"); const config = JSON.parse(content); - return config.stack || "node-ts"; - } - catch { - return "node-ts"; - } -} -async function handleFileGeneration(options) { - const filePath = path.join(process.cwd(), options.fileName); - try { - await fs.access(filePath); - const overwrite = await ui.promptForConfirmation(t(options.overwriteMsgKey), false); - if (!overwrite) { - ui.log.warning(t("common.operation_cancelled")); - return; + if (typeof config.stack === "string" && config.stack.length > 0) { + return config.stack; } } catch { - // Intentionally ignored + // Intentionally ignored - fallback below } - const content = await options.contentPromise; - await fs.writeFile(filePath, content); - ui.log.success(t(options.successMsgKey)); + return "node-ts"; } +const successMessageKey = { + readme: "generate.success.readme", + gitignore: "generate.success.gitignore", +}; +const overwriteMessageKey = { + readme: "generate.prompt.readme_overwrite", + gitignore: "generate.prompt.gitignore_overwrite", +}; export const getGenerateCommand = () => ({ command: "generate [filetype]", describe: t("generate.command_description"), @@ -41,46 +36,68 @@ export const getGenerateCommand = () => ({ }), handler: async (argv) => { const filetype = argv.filetype; + const requestedFiles = new Set(); + const mapFiletype = (value) => { + if (value === "readme") + return "readme"; + if (value === "gitignore") + return "gitignore"; + return null; + }; if (filetype) { - if (filetype === "readme") { - await handleFileGeneration({ - fileName: "README.md", - overwriteMsgKey: "generate.prompt.readme_overwrite", - successMsgKey: "generate.success.readme", - contentPromise: generateReadmeContent(), - }); + const mapped = mapFiletype(filetype); + if (mapped) { + requestedFiles.add(mapped); } - if (filetype === "gitignore") { - const stack = await getProjectStack(); - await handleFileGeneration({ - fileName: ".gitignore", - overwriteMsgKey: "generate.prompt.gitignore_overwrite", - successMsgKey: "generate.success.gitignore", - contentPromise: generateGitignoreContent([stack]), - }); + } + else { + const filesToGenerate = await ui.promptForFilesToGenerate(); + if (!filesToGenerate || filesToGenerate.length === 0) { + ui.log.warning(t("common.operation_cancelled")); + return; } - return; + filesToGenerate.forEach((value) => { + const mapped = mapFiletype(value); + if (mapped) { + requestedFiles.add(mapped); + } + }); } - const filesToGenerate = await ui.promptForFilesToGenerate(); - if (!filesToGenerate || filesToGenerate.length === 0) { + if (requestedFiles.size === 0) { ui.log.warning(t("common.operation_cancelled")); return; } - if (filesToGenerate.includes("readme")) { - await handleFileGeneration({ - fileName: "README.md", - overwriteMsgKey: "generate.prompt.readme_overwrite", - successMsgKey: "generate.success.readme", - contentPromise: generateReadmeContent(), - }); - } - if (filesToGenerate.includes("gitignore")) { + const projectPath = process.cwd(); + let gitignoreTechnologies; + if (requestedFiles.has("gitignore")) { const stack = await getProjectStack(); - await handleFileGeneration({ - fileName: ".gitignore", - overwriteMsgKey: "generate.prompt.gitignore_overwrite", - successMsgKey: "generate.success.gitignore", - contentPromise: generateGitignoreContent([stack]), + gitignoreTechnologies = stack ? [stack] : undefined; + } + const workflowOptions = { + projectPath, + files: Array.from(requestedFiles), + gitignoreTechnologies, + }; + const workflowHooks = { + onEducationalMessage: async (messageKey) => { + showEducationalMessage(messageKey); + }, + shouldOverwriteFile: async ({ fileType }) => ui.promptForConfirmation(t(overwriteMessageKey[fileType]), false), + }; + const result = await runGenerateWorkflow(workflowOptions, workflowHooks); + const successfulFiles = result.files.filter((file) => file.status === "created" || file.status === "overwritten"); + const declinedFiles = result.files.filter((file) => file.reason === "overwrite-declined"); + successfulFiles.forEach((file) => { + ui.log.success(t(successMessageKey[file.fileType])); + }); + declinedFiles.forEach(() => { + const message = t("common.operation_cancelled"); + ui.log.warning(message); + }); + if (result.warnings.length > 0) { + result.warnings.forEach((warning) => { + const translated = t(warning); + ui.log.warning(translated); }); } }, diff --git a/packages/cli/dist/commands/git_sub/finish.js b/packages/cli/dist/commands/git_sub/finish.js index 36c6dfbc..00b1e2ae 100644 --- a/packages/cli/dist/commands/git_sub/finish.js +++ b/packages/cli/dist/commands/git_sub/finish.js @@ -1,29 +1,38 @@ import chalk from "chalk"; -import { runCommand, getCommandOutput, getErrorMessage } from "@stackcode/core"; +import { getErrorMessage, runGitFinishWorkflow } from "@stackcode/core"; import { t } from "@stackcode/i18n"; import open from "open"; -function getRepoPathFromUrl(url) { - const match = url.match(/github\.com[/:]([\w-]+\/[\w-.]+)/); - return match ? match[1].replace(".git", "") : null; -} export const finishHandler = async () => { try { - const currentBranch = await getCommandOutput("git", ["branch", "--show-current"], { cwd: process.cwd() }); - if (!currentBranch) { - console.error(chalk.red(t("git.error_not_git_repo"))); + const result = await runGitFinishWorkflow({ cwd: process.cwd() }, { + onProgress: (progress) => { + if (progress.step === "pushing" && progress.message) { + console.log(chalk.blue(t("git.info_pushing_branch", { + branchName: progress.message, + }))); + } + if (progress.step === "computingPrUrl") { + console.log(chalk.blue(t("git.info_opening_browser"))); + } + }, + }); + if (result.status !== "pushed" || !result.branch) { + if (result.error === "not-on-branch") { + console.error(chalk.red(t("git.error_not_git_repo"))); + } + else { + console.error(chalk.red(t("common.unexpected_error"))); + if (result.error) { + console.error(chalk.gray(result.error)); + } + } return; } - console.log(chalk.blue(t("git.info_pushing_branch", { branchName: currentBranch }))); - await runCommand("git", ["push", "--set-upstream", "origin", currentBranch], { cwd: process.cwd() }); - console.log(chalk.blue(t("git.info_opening_browser"))); - const remoteUrl = await getCommandOutput("git", ["remote", "get-url", "origin"], { cwd: process.cwd() }); - const repoPath = getRepoPathFromUrl(remoteUrl); - if (!repoPath) { + if (!result.prUrl) { console.error(chalk.red(t("git.error_parsing_remote"))); return; } - const prUrl = `https://github.com/${repoPath}/pull/new/${currentBranch}`; - await open(prUrl); + await open(result.prUrl); console.log(chalk.green(t("git.success_pr_ready"))); } catch (error) { diff --git a/packages/cli/dist/commands/git_sub/start.js b/packages/cli/dist/commands/git_sub/start.js index 99924ff1..c7941baf 100644 --- a/packages/cli/dist/commands/git_sub/start.js +++ b/packages/cli/dist/commands/git_sub/start.js @@ -1,5 +1,5 @@ import chalk from "chalk"; -import { runCommand, getErrorMessage } from "@stackcode/core"; +import { getErrorMessage, runGitStartWorkflow } from "@stackcode/core"; import { t } from "@stackcode/i18n"; import inquirer from "inquirer"; /** @@ -10,14 +10,19 @@ export async function createBranch(branchName, branchType) { const fullBranchName = `${branchType}/${branchName}`; try { console.log(chalk.blue(t("git.info_creating_branch", { branchName: fullBranchName }))); - await runCommand("git", ["checkout", "develop"], { cwd: process.cwd() }); - await runCommand("git", ["pull", "origin", "develop"], { + const result = await runGitStartWorkflow({ cwd: process.cwd(), + branchName, + branchType, }); - await runCommand("git", ["checkout", "-b", fullBranchName], { - cwd: process.cwd(), - }); - console.log(chalk.green(t("git.success_branch_created", { branchName: fullBranchName }))); + if (result.status === "created") { + console.log(chalk.green(t("git.success_branch_created", { + branchName: result.fullBranchName ?? fullBranchName, + }))); + } + else { + throw new Error(result.error ?? "Failed to create branch"); + } } catch (error) { console.error(chalk.red(t("git.error_branch_exists", { branchName: fullBranchName }))); diff --git a/packages/cli/dist/commands/init.js b/packages/cli/dist/commands/init.js index ba988ed8..078a2e55 100644 --- a/packages/cli/dist/commands/init.js +++ b/packages/cli/dist/commands/init.js @@ -1,6 +1,6 @@ import fs from "fs/promises"; import path from "path"; -import { scaffoldProject, setupHusky, generateReadmeContent, generateGitignoreContent, runCommand, validateStackDependencies, saveStackCodeConfig, } from "@stackcode/core"; +import { runInitWorkflow, } from "@stackcode/core"; import { t } from "@stackcode/i18n"; import * as ui from "./ui.js"; import { initEducationalMode, showEducationalMessage, } from "../educational-mode.js"; @@ -14,7 +14,6 @@ export const getInitCommand = () => ({ describe: t("init.command_description"), builder: {}, handler: async (argv) => { - // Initialize educational mode based on config and flag initEducationalMode(argv.educate || false); ui.log.step(t("init.welcome")); ui.log.divider(); @@ -33,112 +32,81 @@ export const getInitCommand = () => ({ } ui.log.divider(); ui.log.success(t("init.setup_start")); - const replacements = { + const workflowOptions = { + projectPath, projectName: answers.projectName, description: answers.description, authorName: answers.authorName, - }; - const projectOptions = { - projectPath, stack: answers.stack, features: answers.features, - replacements, + commitValidation: answers.commitValidation, }; - ui.log.info(` ${t("init.step.scaffold")}`); - showEducationalMessage("educational.scaffold_explanation"); - await scaffoldProject(projectOptions); - if (answers.features.includes("husky") && - answers.commitValidation !== undefined) { - const config = { - defaultAuthor: answers.authorName, - defaultLicense: "MIT", // Default license, could be prompted in future - features: { commitValidation: answers.commitValidation }, - }; - await saveStackCodeConfig(projectPath, config); - } - ui.log.info(` ${t("init.step.readme")}`); - showEducationalMessage("educational.readme_explanation"); - const readmeContent = await generateReadmeContent(); - await fs.writeFile(path.join(projectPath, "README.md"), readmeContent); - ui.log.info(` ${t("init.step.gitignore")}`); - showEducationalMessage("educational.gitignore_explanation"); - const gitignoreContent = await generateGitignoreContent([answers.stack]); - await fs.writeFile(path.join(projectPath, ".gitignore"), gitignoreContent); - if (answers.features.includes("husky")) { - ui.log.info(` ${t("init.step.husky")}`); - showEducationalMessage("educational.husky_explanation"); - await setupHusky(projectPath); - } - ui.log.info(` ${t("init.step.git")}`); - showEducationalMessage("educational.git_init_explanation"); - await runCommand("git", ["init"], { cwd: projectPath }); - ui.log.info(` ${t("init.step.validate_deps")}`); - showEducationalMessage("educational.dependency_validation_explanation"); - const dependencyValidation = await validateStackDependencies(answers.stack); - if (!dependencyValidation.isValid) { - ui.log.warning(t("init.dependencies.missing", { stack: answers.stack })); - dependencyValidation.missingDependencies.forEach((dep) => { - ui.log.raw(t("init.dependencies.missing_detail", { command: dep })); - }); - ui.log.raw("\n" + t("init.dependencies.install_instructions")); - dependencyValidation.missingDependencies.forEach((dep) => { - const installKey = `init.dependencies.install_${dep}`; - try { - ui.log.raw(t(installKey)); - } - catch { - ui.log.raw(` - ${dep}: Check the official documentation for installation instructions`); + const workflowHooks = { + onProgress: async ({ step }) => { + switch (step) { + case "scaffold": + ui.log.info(` ${t("init.step.scaffold")}`); + break; + case "generateReadme": + ui.log.info(` ${t("init.step.readme")}`); + break; + case "generateGitignore": + ui.log.info(` ${t("init.step.gitignore")}`); + break; + case "setupHusky": + ui.log.info(` ${t("init.step.husky")}`); + break; + case "initializeGit": + ui.log.info(` ${t("init.step.git")}`); + break; + case "validateDependencies": + ui.log.info(` ${t("init.step.validate_deps")}`); + break; + case "installDependencies": + ui.log.info(` ${t("init.step.deps")}`); + break; + case "saveConfig": + case "completed": + break; } - }); - ui.log.warning("\n" + t("init.dependencies.optional_skip")); - const shouldContinue = await ui.promptForConfirmation(t("init.dependencies.prompt_continue"), false); - if (!shouldContinue) { - ui.log.info(t("common.operation_cancelled")); - return; - } + }, + onEducationalMessage: async (messageKey) => { + showEducationalMessage(messageKey); + }, + onMissingDependencies: async ({ stack, missingDependencies, }) => { + ui.log.warning(t("init.dependencies.missing", { stack })); + missingDependencies.forEach((dependency) => { + ui.log.raw(t("init.dependencies.missing_detail", { command: dependency })); + }); + ui.log.raw(`\n${t("init.dependencies.install_instructions")}`); + missingDependencies.forEach((dependency) => { + const installKey = `init.dependencies.install_${dependency}`; + try { + ui.log.raw(t(installKey)); + } + catch { + ui.log.raw(` - ${dependency}: Check the official documentation for installation instructions`); + } + }); + ui.log.warning(`\n${t("init.dependencies.optional_skip")}`); + }, + confirmContinueAfterMissingDependencies: async () => ui.promptForConfirmation(t("init.dependencies.prompt_continue"), false), + }; + const result = await runInitWorkflow(workflowOptions, workflowHooks); + if (result.status === "cancelled") { + ui.log.info(t("common.operation_cancelled")); + return; } - else { + if (result.dependencyValidation.isValid) { ui.log.success(` ${t("init.dependencies.all_available")}`); } - ui.log.info(` ${t("init.step.deps")}`); - try { - if (answers.stack === "python") { - await runCommand("pip", ["install", "-e", "."], { cwd: projectPath }); - } - else if (answers.stack === "java") { - await runCommand("mvn", ["install"], { cwd: projectPath }); - } - else if (answers.stack === "go") { - await runCommand("go", ["mod", "tidy"], { cwd: projectPath }); - } - else if (answers.stack === "php") { - await runCommand("composer", ["install"], { cwd: projectPath }); - } - else { - await runCommand("npm", ["install"], { cwd: projectPath }); - } - } - catch (error) { - ui.log.error(`\n${t("init.error.deps_install_failed", { - error: error instanceof Error ? error.message : String(error), - })}`); + if (!result.dependenciesInstalled && result.installCommand) { + const installCommandString = `${result.installCommand.command} ${result.installCommand.args.join(" ")}`.trim(); + const warningMessage = result.warnings.at(-1) ?? "Unknown error"; + ui.log.error(`\n${t("init.error.deps_install_failed", { error: warningMessage })}`); ui.log.warning(t("init.error.deps_install_manual")); ui.log.info(t("init.error.suggested_command")); - if (answers.stack === "python") { - ui.log.raw(" pip install -e ."); - } - else if (answers.stack === "java") { - ui.log.raw(" mvn install"); - } - else if (answers.stack === "go") { - ui.log.raw(" go mod tidy"); - } - else if (answers.stack === "php") { - ui.log.raw(" composer install"); - } - else { - ui.log.raw(" npm install"); - } + ui.log.raw(` ${installCommandString}`); } ui.log.divider(); ui.log.success(t("init.success.ready")); diff --git a/packages/cli/dist/commands/release.js b/packages/cli/dist/commands/release.js index 488308c0..452379fd 100644 --- a/packages/cli/dist/commands/release.js +++ b/packages/cli/dist/commands/release.js @@ -1,103 +1,104 @@ -import path from "path"; -import fs from "fs/promises"; -import semver from "semver"; -import Configstore from "configstore"; +/** + * @fileoverview Release command for CLI. + * Handles version bumping, changelog generation, and GitHub release creation. + */ import { t } from "@stackcode/i18n"; import * as ui from "./ui.js"; -import { detectVersioningStrategy, findChangedPackages, determinePackageBumps, updatePackageVersion, updateAllVersions, generateChangelog, getRecommendedBump, performReleaseCommit, createGitHubRelease, getCommandOutput, getErrorMessage, } from "@stackcode/core"; -const config = new Configstore("@stackcode/cli", { github_token: "" }); -async function handleGitHubReleaseCreation(tagName, releaseNotes) { +import { runReleaseWorkflow, createGitHubRelease, getCommandOutput, getErrorMessage, } from "@stackcode/core"; +import { createCLIAuthFacade, getCurrentRepository, } from "../services/githubAuth.js"; +/** + * Handles GitHub release creation after a successful version release. + * + * @param params - Release parameters including tag name and notes + * @param authManager - CLI authentication facade for token management + */ +async function handleGitHubReleaseCreation(params, authManager) { const shouldCreateRelease = await ui.promptToCreateGitHubRelease(); if (!shouldCreateRelease) return; - let token = config.get("github_token"); + const token = await resolveGitHubToken(authManager); if (!token) { - token = await ui.promptForToken(); - if (await ui.promptToSaveToken()) { - config.set("github_token", token); - } + ui.log.warning(t("github.auth.not_authenticated")); + return; } try { - const remoteUrl = await getCommandOutput("git", ["remote", "get-url", "origin"], { cwd: process.cwd() }); - const match = remoteUrl.match(/github\.com[/:]([\w-]+\/[\w-.]+)/); - if (!match) - throw new Error("Could not parse GitHub owner/repo from remote URL."); - const [owner, repo] = match[1].replace(".git", "").split("/"); - await createGitHubRelease({ owner, repo, tagName, releaseNotes, token }); + const repository = params.githubInfo ?? + getCurrentRepository({ cwd: params.cwd }) ?? + (await fallbackResolveRepository(params.cwd)); + if (!repository) { + throw new Error("Could not detect GitHub repository"); + } + const { owner, repo } = repository; + await createGitHubRelease({ + owner, + repo, + tagName: params.tagName, + releaseNotes: params.releaseNotes, + token, + }); } catch (error) { ui.log.error(`\n${t("common.error_generic")}`); const errorMessage = getErrorMessage(error); ui.log.gray(errorMessage); if (errorMessage.toLowerCase().includes("bad credentials")) { - config.delete("github_token"); + await authManager.removeToken(); ui.log.warning("Your saved GitHub token was invalid and has been cleared."); } } } -async function handleLockedRelease(monorepoInfo) { - const bumpType = await getRecommendedBump(monorepoInfo.rootDir); - const currentVersion = monorepoInfo.rootVersion || "0.0.0"; - const newVersion = semver.inc(currentVersion, bumpType); - if (!newVersion) { - ui.log.error(t("release.error_calculating_version")); - return; +/** + * Resolves a valid GitHub token for API calls. + * Checks stored token first, then prompts for new one if needed. + * + * @param authManager - CLI authentication facade + * @returns Valid GitHub token or null if unavailable + */ +async function resolveGitHubToken(authManager) { + const storedToken = await authManager.getToken(); + if (storedToken) { + const isValid = await authManager.validateToken(storedToken); + if (isValid) { + return storedToken; + } + await authManager.removeToken(); + ui.log.warning(t("github.auth.token_invalid")); } - const confirm = await ui.promptForLockedRelease(currentVersion, newVersion); - if (!confirm) { - ui.log.warning(t("common.operation_cancelled")); - return; + const token = (await ui.promptForToken()).trim(); + if (!token) { + return null; } - ui.log.step(t("release.step_updating_versions")); - await updateAllVersions(monorepoInfo, newVersion); - ui.log.step(t("release.step_generating_changelog")); - const changelog = await generateChangelog(monorepoInfo); - const changelogPath = path.join(monorepoInfo.rootDir, "CHANGELOG.md"); - const existing = await fs.readFile(changelogPath, "utf-8").catch(() => ""); - await fs.writeFile(changelogPath, `${changelog}\n${existing}`); - ui.log.success(`\n${t("release.success_ready_to_commit")}`); - ui.log.warning(` ${t("release.next_steps_commit")}`); - await handleGitHubReleaseCreation(`v${newVersion}`, changelog); -} -async function handleIndependentRelease(monorepoInfo) { - const changedPackages = await findChangedPackages(monorepoInfo.packages, monorepoInfo.rootDir); - if (changedPackages.length === 0) { - ui.log.success(t("release.independent_mode_no_changes")); - return; + const isValid = await authManager.validateToken(token); + if (!isValid) { + ui.log.error(`❌ ${t("github.auth.token_invalid_error")}`); + return null; } - const packagesToUpdate = await determinePackageBumps(changedPackages); - if (packagesToUpdate.length === 0) { - ui.log.warning(t("release.independent_mode_no_bumps")); - return; + const shouldPersist = await ui.promptToSaveToken(); + if (shouldPersist) { + await authManager.saveToken(token); } - ui.displayIndependentReleasePlan(packagesToUpdate); - const confirm = await ui.promptForIndependentRelease(); - if (!confirm) { - ui.log.warning(t("common.operation_cancelled")); - return; + return token; +} +/** + * Falls back to parsing git remote URL to determine GitHub repository. + * + * @param cwd - Current working directory + * @returns GitHub repository info or null if not found + */ +async function fallbackResolveRepository(cwd) { + try { + const remoteUrl = (await getCommandOutput("git", ["remote", "get-url", "origin"], { cwd })).trim(); + const match = remoteUrl.match(/github\.com[/:]([\w-]+\/[\w-.]+)/); + if (!match) { + return null; + } + const [owner, repoWithSuffix] = match[1].split("/"); + const repo = repoWithSuffix.replace(/\.git$/, ""); + return { owner, repo, remoteUrl }; } - const allChangelogs = []; - for (const pkgInfo of packagesToUpdate) { - await updatePackageVersion(pkgInfo); - const changelogContent = await generateChangelog(monorepoInfo, pkgInfo); - const changelogPath = path.join(pkgInfo.pkg.path, "CHANGELOG.md"); - const existing = await fs.readFile(changelogPath, "utf-8").catch(() => ""); - await fs.writeFile(changelogPath, `${changelogContent}\n${existing}`); - allChangelogs.push({ - header: `### 🎉 Release for ${pkgInfo.pkg.name}@${pkgInfo.newVersion}`, - content: changelogContent, - }); + catch { + return null; } - await performReleaseCommit(packagesToUpdate, monorepoInfo.rootDir); - ui.log.success(`\n${t("release.independent_success")}`); - const combinedNotes = allChangelogs - .map((c) => `${c.header}\n\n${c.content}`) - .join("\n\n"); - const primaryPackage = packagesToUpdate.find((p) => p.pkg.name === "@stackcode/cli") || - packagesToUpdate[0]; - const tagName = `${primaryPackage.pkg.name.split("/")[1] || primaryPackage.pkg.name}@${primaryPackage.newVersion}`; - await handleGitHubReleaseCreation(tagName, combinedNotes); - ui.log.warning(` ${t("release.next_steps_push")}`); } export const getReleaseCommand = () => ({ command: "release", @@ -105,17 +106,35 @@ export const getReleaseCommand = () => ({ builder: {}, handler: async () => { try { + const cwd = process.cwd(); + const authManager = createCLIAuthFacade(); ui.log.step(t("release.start")); - const monorepoInfo = await detectVersioningStrategy(process.cwd()); - if (monorepoInfo.strategy === "unknown") { - throw new Error(t("release.error_structure")); + const releaseHooks = { + onProgress: async (progress) => { + await handleProgress(progress); + }, + confirmLockedRelease: ({ currentVersion, newVersion }) => ui.promptForLockedRelease(currentVersion, newVersion), + displayIndependentPlan: (plan) => { + ui.displayIndependentReleasePlan(plan); + }, + confirmIndependentRelease: () => ui.promptForIndependentRelease(), + }; + const result = await runReleaseWorkflow({ cwd }, releaseHooks); + if (result.strategy !== "unknown") { + ui.log.info(t("release.detected_strategy", { strategy: result.strategy })); } - ui.log.info(t("release.detected_strategy", { strategy: monorepoInfo.strategy })); - if (monorepoInfo.strategy === "locked") { - await handleLockedRelease(monorepoInfo); + if (result.status === "cancelled") { + await handleCancelledRelease(result); + return; } - else if (monorepoInfo.strategy === "independent") { - await handleIndependentRelease(monorepoInfo); + await handlePreparedRelease(result); + if (result.tagName && result.releaseNotes) { + await handleGitHubReleaseCreation({ + tagName: result.tagName, + releaseNotes: result.releaseNotes, + cwd, + githubInfo: result.github, + }, authManager); } } catch (error) { @@ -125,4 +144,51 @@ export const getReleaseCommand = () => ({ } }, }); +async function handleProgress(progress) { + const messages = { + lockedUpdatingVersions: () => ui.log.step(t("release.step_updating_versions")), + lockedGeneratingChangelog: () => ui.log.step(t("release.step_generating_changelog")), + independentFindingChanges: () => ui.log.info(t("release.independent_mode_start")), + independentUpdatingPackages: () => ui.log.step(t("release.step_updating_version")), + independentCommitting: () => ui.log.step(t("release.step_committing_and_tagging")), + }; + const handler = messages[progress.step]; + if (handler) + handler(); +} +async function handleCancelledRelease(result) { + switch (result.reason) { + case "invalid-structure": + ui.log.error(t("release.error_structure")); + process.exit(1); + break; + case "no-changes": + ui.log.success(t("release.independent_mode_no_changes")); + break; + case "no-bumps": + ui.log.warning(t("release.independent_mode_no_bumps")); + break; + case "cancelled-by-user": + ui.log.warning(t("common.operation_cancelled")); + break; + case "error": + default: + ui.log.error(`\n${t("common.error_generic")}`); + if (result.error) { + ui.log.gray(result.error); + } + process.exit(1); + } +} +async function handlePreparedRelease(result) { + if (result.strategy === "locked") { + ui.log.success(`\n${t("release.success_ready_to_commit")}`); + ui.log.warning(` ${t("release.next_steps_commit")}`); + return; + } + if (result.strategy === "independent") { + ui.log.success(`\n${t("release.independent_success")}`); + ui.log.warning(` ${t("release.next_steps_push")}`); + } +} //# sourceMappingURL=release.js.map \ No newline at end of file diff --git a/packages/cli/dist/commands/validate.js b/packages/cli/dist/commands/validate.js index a8724d89..3949a6f4 100644 --- a/packages/cli/dist/commands/validate.js +++ b/packages/cli/dist/commands/validate.js @@ -1,5 +1,5 @@ import { t } from "@stackcode/i18n"; -import { validateCommitMessage } from "@stackcode/core"; +import { runValidateWorkflow } from "@stackcode/core"; import * as ui from "./ui.js"; import { initEducationalMode, showBestPractice } from "../educational-mode.js"; export const getValidateCommand = () => ({ @@ -12,10 +12,11 @@ export const getValidateCommand = () => ({ demandOption: true, }); }, - handler: (argv) => { + handler: async (argv) => { initEducationalMode(argv.educate || false); const message = argv.message; - if (validateCommitMessage(message)) { + const result = await runValidateWorkflow({ message }); + if (result.isValid) { ui.log.success(`✔ ${t("validate.success")}`); showBestPractice("educational.commit_validation_explanation"); } diff --git a/packages/cli/package.json b/packages/cli/package.json index def068f0..dce994d1 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -34,6 +34,8 @@ "url": "https://github.com/YagoBorba/StackCode/issues" }, "dependencies": { + "@octokit/rest": "^22.0.0", + "@stackcode/github-auth": "^1.0.0", "@stackcode/core": "^1.0.4", "@stackcode/i18n": "^1.0.4", "chalk": "^5.3.0", diff --git a/packages/cli/src/commands/commit.ts b/packages/cli/src/commands/commit.ts index 6b44bc76..4208c631 100644 --- a/packages/cli/src/commands/commit.ts +++ b/packages/cli/src/commands/commit.ts @@ -1,5 +1,9 @@ import type { CommandModule, ArgumentsCamelCase } from "yargs"; -import { runCommand, getCommandOutput, getErrorMessage } from "@stackcode/core"; +import { + getCommandOutput, + getErrorMessage, + runCommitWorkflow, +} from "@stackcode/core"; import { t } from "@stackcode/i18n"; import * as ui from "./ui.js"; import { initEducationalMode, showBestPractice } from "../educational-mode.js"; @@ -28,28 +32,26 @@ export const getCommitCommand = (): CommandModule => ({ const answers = await ui.promptForCommitAnswers(); - let commitMessage = `${answers.type}`; - if (answers.scope) { - commitMessage += `(${answers.scope.trim()})`; - } - commitMessage += `: ${answers.shortDescription.trim()}`; - - if (answers.longDescription) { - commitMessage += `\n\n${answers.longDescription.replace(/\|/g, "\n")}`; - } - - if (answers.breakingChanges) { - commitMessage += `\n\nBREAKING CHANGE: ${answers.breakingChanges.trim()}`; - } - - if (answers.affectedIssues) { - commitMessage += `\n\n${answers.affectedIssues.trim()}`; - } - - await runCommand("git", ["commit", "-m", commitMessage], { + const result = await runCommitWorkflow({ cwd: process.cwd(), + type: answers.type, + scope: answers.scope, + shortDescription: answers.shortDescription, + longDescription: answers.longDescription, + breakingChanges: answers.breakingChanges, + affectedIssues: answers.affectedIssues, }); - ui.log.success(t("commit.success")); + + if (result.status === "committed") { + ui.log.success(t("commit.success")); + } else if (result.reason === "no-staged-changes") { + ui.log.warning(t("commit.error_no_changes_staged")); + } else { + ui.log.error(t("common.unexpected_error")); + if (result.error) { + ui.log.gray(result.error); + } + } } catch (error: unknown) { ui.log.error(t("common.unexpected_error")); ui.log.gray(getErrorMessage(error)); diff --git a/packages/cli/src/commands/generate.ts b/packages/cli/src/commands/generate.ts index 62e90b17..c5360197 100644 --- a/packages/cli/src/commands/generate.ts +++ b/packages/cli/src/commands/generate.ts @@ -2,49 +2,44 @@ import type { CommandModule } from "yargs"; import fs from "fs/promises"; import path from "path"; import { - generateGitignoreContent, - generateReadmeContent, + type GenerateFileType, + type GenerateWorkflowHooks, + type GenerateWorkflowOptions, + type GenerateWorkflowResult, + type StackCodeConfig, + runGenerateWorkflow, } from "@stackcode/core"; import { t } from "@stackcode/i18n"; import * as ui from "./ui.js"; +import { showEducationalMessage } from "../educational-mode.js"; async function getProjectStack(): Promise { const configPath = path.join(process.cwd(), ".stackcoderc.json"); try { const content = await fs.readFile(configPath, "utf-8"); - const config = JSON.parse(content); - return (config as { stack: string }).stack || "node-ts"; - } catch { - return "node-ts"; - } -} - -async function handleFileGeneration(options: { - fileName: string; - overwriteMsgKey: string; - successMsgKey: string; - contentPromise: Promise; -}) { - const filePath = path.join(process.cwd(), options.fileName); - try { - await fs.access(filePath); - const overwrite = await ui.promptForConfirmation( - t(options.overwriteMsgKey), - false, - ); - if (!overwrite) { - ui.log.warning(t("common.operation_cancelled")); - return; + const config = JSON.parse(content) as StackCodeConfig & { + stack?: string; + }; + if (typeof config.stack === "string" && config.stack.length > 0) { + return config.stack; } } catch { - // Intentionally ignored + // Intentionally ignored - fallback below } - const content = await options.contentPromise; - await fs.writeFile(filePath, content); - ui.log.success(t(options.successMsgKey)); + return "node-ts"; } +const successMessageKey: Record = { + readme: "generate.success.readme", + gitignore: "generate.success.gitignore", +}; + +const overwriteMessageKey: Record = { + readme: "generate.prompt.readme_overwrite", + gitignore: "generate.prompt.gitignore_overwrite", +}; + export const getGenerateCommand = (): CommandModule => ({ command: "generate [filetype]", describe: t("generate.command_description"), @@ -56,50 +51,89 @@ export const getGenerateCommand = (): CommandModule => ({ }), handler: async (argv) => { const filetype = argv.filetype as string | undefined; + const requestedFiles = new Set(); + + const mapFiletype = ( + value: string | undefined, + ): GenerateFileType | null => { + if (value === "readme") return "readme"; + if (value === "gitignore") return "gitignore"; + return null; + }; if (filetype) { - if (filetype === "readme") { - await handleFileGeneration({ - fileName: "README.md", - overwriteMsgKey: "generate.prompt.readme_overwrite", - successMsgKey: "generate.success.readme", - contentPromise: generateReadmeContent(), - }); + const mapped = mapFiletype(filetype); + if (mapped) { + requestedFiles.add(mapped); } - if (filetype === "gitignore") { - const stack = await getProjectStack(); - await handleFileGeneration({ - fileName: ".gitignore", - overwriteMsgKey: "generate.prompt.gitignore_overwrite", - successMsgKey: "generate.success.gitignore", - contentPromise: generateGitignoreContent([stack]), - }); + } else { + const filesToGenerate = await ui.promptForFilesToGenerate(); + + if (!filesToGenerate || filesToGenerate.length === 0) { + ui.log.warning(t("common.operation_cancelled")); + return; } - return; - } - const filesToGenerate = await ui.promptForFilesToGenerate(); + filesToGenerate.forEach((value) => { + const mapped = mapFiletype(value); + if (mapped) { + requestedFiles.add(mapped); + } + }); + } - if (!filesToGenerate || filesToGenerate.length === 0) { + if (requestedFiles.size === 0) { ui.log.warning(t("common.operation_cancelled")); return; } - if (filesToGenerate.includes("readme")) { - await handleFileGeneration({ - fileName: "README.md", - overwriteMsgKey: "generate.prompt.readme_overwrite", - successMsgKey: "generate.success.readme", - contentPromise: generateReadmeContent(), - }); - } - if (filesToGenerate.includes("gitignore")) { + const projectPath = process.cwd(); + + let gitignoreTechnologies: string[] | undefined; + if (requestedFiles.has("gitignore")) { const stack = await getProjectStack(); - await handleFileGeneration({ - fileName: ".gitignore", - overwriteMsgKey: "generate.prompt.gitignore_overwrite", - successMsgKey: "generate.success.gitignore", - contentPromise: generateGitignoreContent([stack]), + gitignoreTechnologies = stack ? [stack] : undefined; + } + + const workflowOptions: GenerateWorkflowOptions = { + projectPath, + files: Array.from(requestedFiles), + gitignoreTechnologies, + }; + + const workflowHooks: GenerateWorkflowHooks = { + onEducationalMessage: async (messageKey: string) => { + showEducationalMessage(messageKey); + }, + shouldOverwriteFile: async ({ fileType }) => + ui.promptForConfirmation(t(overwriteMessageKey[fileType]), false), + }; + + const result: GenerateWorkflowResult = await runGenerateWorkflow( + workflowOptions, + workflowHooks, + ); + + const successfulFiles = result.files.filter( + (file) => file.status === "created" || file.status === "overwritten", + ); + const declinedFiles = result.files.filter( + (file) => file.reason === "overwrite-declined", + ); + + successfulFiles.forEach((file) => { + ui.log.success(t(successMessageKey[file.fileType])); + }); + + declinedFiles.forEach(() => { + const message = t("common.operation_cancelled"); + ui.log.warning(message); + }); + + if (result.warnings.length > 0) { + result.warnings.forEach((warning) => { + const translated = t(warning); + ui.log.warning(translated); }); } }, diff --git a/packages/cli/src/commands/git_sub/finish.ts b/packages/cli/src/commands/git_sub/finish.ts index 16a242bc..bd5dfcc5 100644 --- a/packages/cli/src/commands/git_sub/finish.ts +++ b/packages/cli/src/commands/git_sub/finish.ts @@ -1,51 +1,48 @@ import type { CommandModule } from "yargs"; import chalk from "chalk"; -import { runCommand, getCommandOutput, getErrorMessage } from "@stackcode/core"; +import { getErrorMessage, runGitFinishWorkflow } from "@stackcode/core"; import { t } from "@stackcode/i18n"; import open from "open"; -function getRepoPathFromUrl(url: string): string | null { - const match = url.match(/github\.com[/:]([\w-]+\/[\w-.]+)/); - return match ? match[1].replace(".git", "") : null; -} - export const finishHandler = async () => { try { - const currentBranch = await getCommandOutput( - "git", - ["branch", "--show-current"], + const result = await runGitFinishWorkflow( { cwd: process.cwd() }, + { + onProgress: (progress) => { + if (progress.step === "pushing" && progress.message) { + console.log( + chalk.blue( + t("git.info_pushing_branch", { + branchName: progress.message, + }), + ), + ); + } + if (progress.step === "computingPrUrl") { + console.log(chalk.blue(t("git.info_opening_browser"))); + } + }, + }, ); - if (!currentBranch) { - console.error(chalk.red(t("git.error_not_git_repo"))); + + if (result.status !== "pushed" || !result.branch) { + if (result.error === "not-on-branch") { + console.error(chalk.red(t("git.error_not_git_repo"))); + } else { + console.error(chalk.red(t("common.unexpected_error"))); + if (result.error) { + console.error(chalk.gray(result.error)); + } + } return; } - console.log( - chalk.blue(t("git.info_pushing_branch", { branchName: currentBranch })), - ); - await runCommand( - "git", - ["push", "--set-upstream", "origin", currentBranch], - { cwd: process.cwd() }, - ); - - console.log(chalk.blue(t("git.info_opening_browser"))); - const remoteUrl = await getCommandOutput( - "git", - ["remote", "get-url", "origin"], - { cwd: process.cwd() }, - ); - const repoPath = getRepoPathFromUrl(remoteUrl); - - if (!repoPath) { + if (!result.prUrl) { console.error(chalk.red(t("git.error_parsing_remote"))); return; } - - const prUrl = `https://github.com/${repoPath}/pull/new/${currentBranch}`; - - await open(prUrl); + await open(result.prUrl); console.log(chalk.green(t("git.success_pr_ready"))); } catch (error: unknown) { diff --git a/packages/cli/src/commands/git_sub/start.ts b/packages/cli/src/commands/git_sub/start.ts index 3ed37de9..49919b93 100644 --- a/packages/cli/src/commands/git_sub/start.ts +++ b/packages/cli/src/commands/git_sub/start.ts @@ -1,6 +1,6 @@ import type { CommandModule, ArgumentsCamelCase } from "yargs"; import chalk from "chalk"; -import { runCommand, getErrorMessage } from "@stackcode/core"; +import { getErrorMessage, runGitStartWorkflow } from "@stackcode/core"; import { t } from "@stackcode/i18n"; import inquirer from "inquirer"; @@ -18,18 +18,24 @@ export async function createBranch(branchName: string, branchType: string) { console.log( chalk.blue(t("git.info_creating_branch", { branchName: fullBranchName })), ); - await runCommand("git", ["checkout", "develop"], { cwd: process.cwd() }); - await runCommand("git", ["pull", "origin", "develop"], { - cwd: process.cwd(), - }); - await runCommand("git", ["checkout", "-b", fullBranchName], { + + const result = await runGitStartWorkflow({ cwd: process.cwd(), + branchName, + branchType, }); - console.log( - chalk.green( - t("git.success_branch_created", { branchName: fullBranchName }), - ), - ); + + if (result.status === "created") { + console.log( + chalk.green( + t("git.success_branch_created", { + branchName: result.fullBranchName ?? fullBranchName, + }), + ), + ); + } else { + throw new Error(result.error ?? "Failed to create branch"); + } } catch (error: unknown) { console.error( chalk.red(t("git.error_branch_exists", { branchName: fullBranchName })), diff --git a/packages/cli/src/commands/github.ts b/packages/cli/src/commands/github.ts index 261f754c..2789c1d5 100644 --- a/packages/cli/src/commands/github.ts +++ b/packages/cli/src/commands/github.ts @@ -1,11 +1,17 @@ +/** + * @fileoverview GitHub integration commands for CLI. + * Provides authentication management and issue listing functionality. + */ + import { CommandModule } from "yargs"; -import { getErrorMessage } from "@stackcode/core"; -import { fetchRepositoryIssues } from "@stackcode/core"; -import { Octokit } from "@octokit/rest"; +import { fetchRepositoryIssues, getErrorMessage } from "@stackcode/core"; import { t, initI18n } from "@stackcode/i18n"; -import fs from "fs"; -import os from "os"; -import path from "path"; +import { + createCLIAuthFacade, + getCurrentRepository, + type AuthenticatedOctokit, + type CLIAuthFacade, +} from "../services/githubAuth.js"; interface AuthArgs { token?: string; @@ -23,104 +29,9 @@ interface IssuesArgs { } /** - * Gerenciamento de token de autenticação GitHub no CLI - */ -class CLIAuthManager { - private tokenPath: string; - - constructor() { - const configDir = path.join(os.homedir(), ".stackcode"); - if (!fs.existsSync(configDir)) { - fs.mkdirSync(configDir, { recursive: true }); - } - this.tokenPath = path.join(configDir, "github_token"); - } - - saveToken(token: string): void { - fs.writeFileSync(this.tokenPath, token, { mode: 0o600 }); - } - - getToken(): string | null { - try { - if (fs.existsSync(this.tokenPath)) { - return fs.readFileSync(this.tokenPath, "utf-8").trim(); - } - } catch (error) { - console.error(t("github.auth.error_reading_token"), error); - } - return null; - } - - removeToken(): void { - try { - if (fs.existsSync(this.tokenPath)) { - fs.unlinkSync(this.tokenPath); - } - } catch (error) { - console.error(t("github.auth.error_removing_token"), error); - } - } - - async validateToken(token: string): Promise { - try { - const octokit = new Octokit({ auth: token }); - await octokit.users.getAuthenticated(); - return true; - } catch { - return false; - } - } -} - -/** - * Detecta repositório GitHub atual baseado no git remote - */ -function getCurrentRepository(): { owner: string; repo: string } | null { - try { - const cwd = process.cwd(); - console.log(`🔍 Detectando repositório em: ${cwd}`); - - const remoteUrl = fs - .readFileSync(".git/config", "utf8") - .split("\n") - .find((line: string) => line.includes("url = ")) - ?.split("url = ")[1] - ?.trim(); - - if (!remoteUrl) { - console.log(`❌ Não foi possível encontrar URL remota no .git/config`); - return null; - } - - console.log(`🔗 URL remota encontrada: ${remoteUrl}`); - - const patterns = [ - /^https:\/\/github\.com\/([^/]+)\/([^/]+)(?:\.git)?$/, - /^git@github\.com:([^/]+)\/([^/]+)(?:\.git)?$/, - /^ssh:\/\/git@github\.com\/([^/]+)\/([^/]+)(?:\.git)?$/, - ]; - - for (const pattern of patterns) { - const match = remoteUrl.match(pattern); - if (match) { - const result = { owner: match[1], repo: match[2] }; - console.log(`✅ Repositório detectado: ${result.owner}/${result.repo}`); - return result; - } - } - - console.log(`❌ URL não corresponde aos padrões GitHub conhecidos`); - return null; - } catch (error) { - console.log( - `❌ Erro ao detectar repositório: ${error instanceof Error ? error.message : String(error)}`, - ); - return null; - } -} - -/** - * Comando para autenticação GitHub + * Creates the GitHub authentication command module. + * + * @returns Command module for managing GitHub authentication */ function getAuthCommand(): CommandModule, AuthArgs> { return { @@ -155,17 +66,17 @@ function getAuthCommand(): CommandModule, AuthArgs> { async handler(args: AuthArgs) { await initI18n(); - const authManager = new CLIAuthManager(); + const authManager = createCLIAuthFacade(); try { if (args.logout) { - authManager.removeToken(); + await authManager.removeToken(); console.log(`✅ ${t("github.auth.authentication_removed")}`); return; } if (args.status) { - const token = authManager.getToken(); + const token = await authManager.getToken(); if (!token) { console.log(`❌ ${t("github.auth.not_authenticated")}`); console.log(t("github.auth.run_login")); @@ -189,7 +100,7 @@ function getAuthCommand(): CommandModule, AuthArgs> { process.exit(1); } - authManager.saveToken(args.token); + await authManager.saveToken(args.token); console.log(`✅ ${t("github.auth.token_saved")}`); return; } @@ -221,7 +132,9 @@ function getAuthCommand(): CommandModule, AuthArgs> { } /** - * Comando para listar issues + * Creates the GitHub issues listing command module. + * + * @returns Command module for listing and filtering repository issues */ function getIssuesCommand(): CommandModule< Record, @@ -273,7 +186,7 @@ function getIssuesCommand(): CommandModule< async handler(args: IssuesArgs) { await initI18n(); - const authManager = new CLIAuthManager(); + const authManager = createCLIAuthFacade(); try { if ( @@ -287,7 +200,7 @@ function getIssuesCommand(): CommandModule< return; } - const token = authManager.getToken(); + const token = await authManager.getToken(); if (!token) { console.error(`❌ ${t("github.auth.not_authenticated")}`); console.error(t("github.auth.run_login")); @@ -303,7 +216,7 @@ function getIssuesCommand(): CommandModule< } [owner, repo] = parts; } else { - const currentRepo = getCurrentRepository(); + const currentRepo = getCurrentRepository({ verbose: true }); if (!currentRepo) { console.error(`❌ ${t("github.issues.no_repository_detected")}`); console.error(t("github.issues.run_from_git_repo")); @@ -314,7 +227,19 @@ function getIssuesCommand(): CommandModule< console.log(`📋 ${t("github.issues.fetching")} ${owner}/${repo}...`); - const octokit = new Octokit({ auth: token }); + let octokit: AuthenticatedOctokit; + try { + octokit = await authManager.getClient(); + } catch (error) { + console.error(`❌ ${t("github.auth.token_invalid")}`); + await authManager.removeToken(); + console.error(t("github.auth.run_login")); + if (error instanceof Error) { + console.error(error.message); + } + process.exit(1); + return; + } const issues = await fetchRepositoryIssues(octokit, { owner, repo, @@ -385,18 +310,18 @@ export function getGitHubCommand(): CommandModule { * Menu interativo para issues do GitHub */ async function showInteractiveIssuesMenu( - authManager: CLIAuthManager, + authManager: CLIAuthFacade, ): Promise { const inquirer = await import("inquirer"); - const token = authManager.getToken(); + const token = await authManager.getToken(); if (!token) { console.error(`❌ ${t("github.auth.not_authenticated")}`); console.error(t("github.auth.run_login")); process.exit(1); } - const currentRepo = getCurrentRepository(); + const currentRepo = getCurrentRepository({ verbose: true }); const choices = [ { @@ -468,7 +393,7 @@ async function showInteractiveIssuesMenu( * Handle repositório específico */ async function handleSpecificRepository( - authManager: CLIAuthManager, + authManager: CLIAuthFacade, ): Promise { const inquirer = await import("inquirer"); @@ -498,7 +423,7 @@ async function handleSpecificRepository( * Handle filtro por labels */ async function handleLabelFilter( - authManager: CLIAuthManager, + authManager: CLIAuthFacade, repository: { owner: string; repo: string }, ): Promise { const inquirer = await import("inquirer"); @@ -521,7 +446,7 @@ async function handleLabelFilter( * Busca e exibe issues com opções de paginação */ async function fetchAndDisplayIssues( - authManager: CLIAuthManager, + authManager: CLIAuthFacade, repository: { owner: string; repo: string }, options: { state?: string; @@ -535,8 +460,25 @@ async function fetchAndDisplayIssues( ); try { - const token = authManager.getToken()!; - const octokit = new Octokit({ auth: token }); + const token = await authManager.getToken(); + if (!token) { + console.error(`❌ ${t("github.auth.not_authenticated")}`); + console.error(t("github.auth.run_login")); + return; + } + + let octokit: AuthenticatedOctokit; + try { + octokit = await authManager.getClient(); + } catch (clientError) { + console.error(`❌ ${t("github.auth.token_invalid")}`); + if (clientError instanceof Error) { + console.error(clientError.message); + } + await authManager.removeToken(); + console.error(t("github.auth.run_login")); + return; + } const issues = await fetchRepositoryIssues(octokit, { owner: repository.owner, @@ -604,4 +546,4 @@ async function fetchAndDisplayIssues( } } -export { CLIAuthManager, getCurrentRepository, fetchRepositoryIssues }; +export { createCLIAuthFacade, getCurrentRepository, fetchRepositoryIssues }; diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index a3158889..d389ee50 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -2,15 +2,12 @@ import type { CommandModule, ArgumentsCamelCase } from "yargs"; import fs from "fs/promises"; import path from "path"; import { - scaffoldProject, - setupHusky, - generateReadmeContent, - generateGitignoreContent, - runCommand, - validateStackDependencies, - saveStackCodeConfig, - type ProjectOptions, - type StackCodeConfig, + runInitWorkflow, + type InitWorkflowHooks, + type InitWorkflowOptions, + type InitWorkflowResult, + type InitWorkflowProgress, + type InitWorkflowDependencyDecision, } from "@stackcode/core"; import { t } from "@stackcode/i18n"; import * as ui from "./ui.js"; @@ -33,7 +30,6 @@ export const getInitCommand = (): CommandModule => ({ describe: t("init.command_description"), builder: {}, handler: async (argv: ArgumentsCamelCase) => { - // Initialize educational mode based on config and flag initEducationalMode(argv.educate || false); ui.log.step(t("init.welcome")); ui.log.divider(); @@ -55,124 +51,99 @@ export const getInitCommand = (): CommandModule => ({ ui.log.divider(); ui.log.success(t("init.setup_start")); - const replacements = { + const workflowOptions: InitWorkflowOptions = { + projectPath, projectName: answers.projectName, description: answers.description, authorName: answers.authorName, - }; - - const projectOptions: ProjectOptions = { - projectPath, stack: answers.stack, features: answers.features, - replacements, + commitValidation: answers.commitValidation, }; - ui.log.info(` ${t("init.step.scaffold")}`); - showEducationalMessage("educational.scaffold_explanation"); - await scaffoldProject(projectOptions); - - if ( - answers.features.includes("husky") && - answers.commitValidation !== undefined - ) { - const config: StackCodeConfig = { - defaultAuthor: answers.authorName, - defaultLicense: "MIT", // Default license, could be prompted in future - features: { commitValidation: answers.commitValidation }, - }; - await saveStackCodeConfig(projectPath, config); - } - - ui.log.info(` ${t("init.step.readme")}`); - showEducationalMessage("educational.readme_explanation"); - const readmeContent = await generateReadmeContent(); - await fs.writeFile(path.join(projectPath, "README.md"), readmeContent); - - ui.log.info(` ${t("init.step.gitignore")}`); - showEducationalMessage("educational.gitignore_explanation"); - const gitignoreContent = await generateGitignoreContent([answers.stack]); - await fs.writeFile(path.join(projectPath, ".gitignore"), gitignoreContent); - - if (answers.features.includes("husky")) { - ui.log.info(` ${t("init.step.husky")}`); - showEducationalMessage("educational.husky_explanation"); - await setupHusky(projectPath); - } - - ui.log.info(` ${t("init.step.git")}`); - showEducationalMessage("educational.git_init_explanation"); - await runCommand("git", ["init"], { cwd: projectPath }); - - ui.log.info(` ${t("init.step.validate_deps")}`); - showEducationalMessage("educational.dependency_validation_explanation"); - const dependencyValidation = await validateStackDependencies(answers.stack); - - if (!dependencyValidation.isValid) { - ui.log.warning(t("init.dependencies.missing", { stack: answers.stack })); - dependencyValidation.missingDependencies.forEach((dep) => { - ui.log.raw(t("init.dependencies.missing_detail", { command: dep })); - }); - - ui.log.raw("\n" + t("init.dependencies.install_instructions")); - dependencyValidation.missingDependencies.forEach((dep) => { - const installKey = `init.dependencies.install_${dep}`; - try { - ui.log.raw(t(installKey)); - } catch { + const workflowHooks: InitWorkflowHooks = { + onProgress: async ({ step }: InitWorkflowProgress) => { + switch (step) { + case "scaffold": + ui.log.info(` ${t("init.step.scaffold")}`); + break; + case "generateReadme": + ui.log.info(` ${t("init.step.readme")}`); + break; + case "generateGitignore": + ui.log.info(` ${t("init.step.gitignore")}`); + break; + case "setupHusky": + ui.log.info(` ${t("init.step.husky")}`); + break; + case "initializeGit": + ui.log.info(` ${t("init.step.git")}`); + break; + case "validateDependencies": + ui.log.info(` ${t("init.step.validate_deps")}`); + break; + case "installDependencies": + ui.log.info(` ${t("init.step.deps")}`); + break; + case "saveConfig": + case "completed": + break; + } + }, + onEducationalMessage: async (messageKey: string) => { + showEducationalMessage(messageKey); + }, + onMissingDependencies: async ({ + stack, + missingDependencies, + }: InitWorkflowDependencyDecision) => { + ui.log.warning(t("init.dependencies.missing", { stack })); + missingDependencies.forEach((dependency: string) => { ui.log.raw( - ` - ${dep}: Check the official documentation for installation instructions`, + t("init.dependencies.missing_detail", { command: dependency }), ); - } - }); + }); + ui.log.raw(`\n${t("init.dependencies.install_instructions")}`); + missingDependencies.forEach((dependency: string) => { + const installKey = `init.dependencies.install_${dependency}`; + try { + ui.log.raw(t(installKey)); + } catch { + ui.log.raw( + ` - ${dependency}: Check the official documentation for installation instructions`, + ); + } + }); + ui.log.warning(`\n${t("init.dependencies.optional_skip")}`); + }, + confirmContinueAfterMissingDependencies: async () => + ui.promptForConfirmation(t("init.dependencies.prompt_continue"), false), + }; - ui.log.warning("\n" + t("init.dependencies.optional_skip")); - const shouldContinue = await ui.promptForConfirmation( - t("init.dependencies.prompt_continue"), - false, - ); + const result: InitWorkflowResult = await runInitWorkflow( + workflowOptions, + workflowHooks, + ); - if (!shouldContinue) { - ui.log.info(t("common.operation_cancelled")); - return; - } - } else { + if (result.status === "cancelled") { + ui.log.info(t("common.operation_cancelled")); + return; + } + + if (result.dependencyValidation.isValid) { ui.log.success(` ${t("init.dependencies.all_available")}`); } - ui.log.info(` ${t("init.step.deps")}`); - try { - if (answers.stack === "python") { - await runCommand("pip", ["install", "-e", "."], { cwd: projectPath }); - } else if (answers.stack === "java") { - await runCommand("mvn", ["install"], { cwd: projectPath }); - } else if (answers.stack === "go") { - await runCommand("go", ["mod", "tidy"], { cwd: projectPath }); - } else if (answers.stack === "php") { - await runCommand("composer", ["install"], { cwd: projectPath }); - } else { - await runCommand("npm", ["install"], { cwd: projectPath }); - } - } catch (error) { + if (!result.dependenciesInstalled && result.installCommand) { + const installCommandString = + `${result.installCommand.command} ${result.installCommand.args.join(" ")}`.trim(); + const warningMessage = result.warnings.at(-1) ?? "Unknown error"; ui.log.error( - `\n${t("init.error.deps_install_failed", { - error: error instanceof Error ? error.message : String(error), - })}`, + `\n${t("init.error.deps_install_failed", { error: warningMessage })}`, ); ui.log.warning(t("init.error.deps_install_manual")); ui.log.info(t("init.error.suggested_command")); - - if (answers.stack === "python") { - ui.log.raw(" pip install -e ."); - } else if (answers.stack === "java") { - ui.log.raw(" mvn install"); - } else if (answers.stack === "go") { - ui.log.raw(" go mod tidy"); - } else if (answers.stack === "php") { - ui.log.raw(" composer install"); - } else { - ui.log.raw(" npm install"); - } + ui.log.raw(` ${installCommandString}`); } ui.log.divider(); diff --git a/packages/cli/src/commands/release.ts b/packages/cli/src/commands/release.ts index 4fb79cfc..e58fc8fb 100644 --- a/packages/cli/src/commands/release.ts +++ b/packages/cli/src/commands/release.ts @@ -1,68 +1,78 @@ +/** + * @fileoverview Release command for CLI. + * Handles version bumping, changelog generation, and GitHub release creation. + */ + import type { CommandModule } from "yargs"; -import path from "path"; -import fs from "fs/promises"; -import semver from "semver"; -import Configstore from "configstore"; import { t } from "@stackcode/i18n"; import * as ui from "./ui.js"; import { - MonorepoInfo, - detectVersioningStrategy, - findChangedPackages, - determinePackageBumps, - updatePackageVersion, - updateAllVersions, - generateChangelog, - getRecommendedBump, - performReleaseCommit, + runReleaseWorkflow, + type ReleaseWorkflowResult, + type ReleaseWorkflowHooks, + type ReleaseWorkflowStep, + type ReleaseWorkflowProgress, + type ReleaseWorkflowGitHubInfo, createGitHubRelease, getCommandOutput, getErrorMessage, } from "@stackcode/core"; +import { + createCLIAuthFacade, + getCurrentRepository, + type CLIAuthFacade, +} from "../services/githubAuth.js"; -const config = new Configstore("@stackcode/cli", { github_token: "" }); - +/** + * Handles GitHub release creation after a successful version release. + * + * @param params - Release parameters including tag name and notes + * @param authManager - CLI authentication facade for token management + */ async function handleGitHubReleaseCreation( - tagName: string, - releaseNotes: string, + params: { + tagName: string; + releaseNotes: string; + cwd: string; + githubInfo?: ReleaseWorkflowGitHubInfo; + }, + authManager: CLIAuthFacade, ) { const shouldCreateRelease = await ui.promptToCreateGitHubRelease(); if (!shouldCreateRelease) return; - let token = config.get("github_token"); + const token = await resolveGitHubToken(authManager); if (!token) { - token = await ui.promptForToken(); - if (await ui.promptToSaveToken()) { - config.set("github_token", token); - } + ui.log.warning(t("github.auth.not_authenticated")); + return; } try { - const remoteUrl = await getCommandOutput( - "git", - ["remote", "get-url", "origin"], - { cwd: process.cwd() }, - ); - const match = remoteUrl.match(/github\.com[/:]([\w-]+\/[\w-.]+)/); - if (!match) - throw new Error("Could not parse GitHub owner/repo from remote URL."); + const repository = + params.githubInfo ?? + getCurrentRepository({ cwd: params.cwd }) ?? + (await fallbackResolveRepository(params.cwd)); - const [owner, repo] = match[1].replace(".git", "").split("/"); - - if (typeof token !== "string" || !token) { - throw new Error( - "Invalid GitHub token. Please run 'stackcode config' to set it.", - ); + if (!repository) { + throw new Error("Could not detect GitHub repository"); } - await createGitHubRelease({ owner, repo, tagName, releaseNotes, token }); + const { owner, repo } = repository; + + await createGitHubRelease({ + owner, + repo, + tagName: params.tagName, + releaseNotes: params.releaseNotes, + token, + }); } catch (error: unknown) { ui.log.error(`\n${t("common.error_generic")}`); const errorMessage = getErrorMessage(error); ui.log.gray(errorMessage); if (errorMessage.toLowerCase().includes("bad credentials")) { - config.delete("github_token"); + await authManager.removeToken(); ui.log.warning( "Your saved GitHub token was invalid and has been cleared.", ); @@ -70,85 +80,68 @@ async function handleGitHubReleaseCreation( } } -async function handleLockedRelease(monorepoInfo: MonorepoInfo) { - const bumpType = await getRecommendedBump(monorepoInfo.rootDir); - const currentVersion = monorepoInfo.rootVersion || "0.0.0"; - const newVersion = semver.inc(currentVersion, bumpType as semver.ReleaseType); - if (!newVersion) { - ui.log.error(t("release.error_calculating_version")); - return; +/** + * Resolves a valid GitHub token for API calls. + * Checks stored token first, then prompts for new one if needed. + * + * @param authManager - CLI authentication facade + * @returns Valid GitHub token or null if unavailable + */ +async function resolveGitHubToken( + authManager: CLIAuthFacade, +): Promise { + const storedToken = await authManager.getToken(); + if (storedToken) { + const isValid = await authManager.validateToken(storedToken); + if (isValid) { + return storedToken; + } + await authManager.removeToken(); + ui.log.warning(t("github.auth.token_invalid")); } - const confirm = await ui.promptForLockedRelease(currentVersion, newVersion); - if (!confirm) { - ui.log.warning(t("common.operation_cancelled")); - return; + const token = (await ui.promptForToken()).trim(); + if (!token) { + return null; } - ui.log.step(t("release.step_updating_versions")); - await updateAllVersions(monorepoInfo, newVersion); - - ui.log.step(t("release.step_generating_changelog")); - const changelog = await generateChangelog(monorepoInfo); - const changelogPath = path.join(monorepoInfo.rootDir, "CHANGELOG.md"); - const existing = await fs.readFile(changelogPath, "utf-8").catch(() => ""); - await fs.writeFile(changelogPath, `${changelog}\n${existing}`); - - ui.log.success(`\n${t("release.success_ready_to_commit")}`); - ui.log.warning(` ${t("release.next_steps_commit")}`); - await handleGitHubReleaseCreation(`v${newVersion}`, changelog); -} - -async function handleIndependentRelease(monorepoInfo: MonorepoInfo) { - const changedPackages = await findChangedPackages( - monorepoInfo.packages, - monorepoInfo.rootDir, - ); - if (changedPackages.length === 0) { - ui.log.success(t("release.independent_mode_no_changes")); - return; + const isValid = await authManager.validateToken(token); + if (!isValid) { + ui.log.error(`❌ ${t("github.auth.token_invalid_error")}`); + return null; } - const packagesToUpdate = await determinePackageBumps(changedPackages); - if (packagesToUpdate.length === 0) { - ui.log.warning(t("release.independent_mode_no_bumps")); - return; + const shouldPersist = await ui.promptToSaveToken(); + if (shouldPersist) { + await authManager.saveToken(token); } - ui.displayIndependentReleasePlan(packagesToUpdate); - - const confirm = await ui.promptForIndependentRelease(); - if (!confirm) { - ui.log.warning(t("common.operation_cancelled")); - return; - } + return token; +} - const allChangelogs: { header: string; content: string }[] = []; - for (const pkgInfo of packagesToUpdate) { - await updatePackageVersion(pkgInfo); - const changelogContent = await generateChangelog(monorepoInfo, pkgInfo); - const changelogPath = path.join(pkgInfo.pkg.path, "CHANGELOG.md"); - const existing = await fs.readFile(changelogPath, "utf-8").catch(() => ""); - await fs.writeFile(changelogPath, `${changelogContent}\n${existing}`); - allChangelogs.push({ - header: `### 🎉 Release for ${pkgInfo.pkg.name}@${pkgInfo.newVersion}`, - content: changelogContent, - }); +/** + * Falls back to parsing git remote URL to determine GitHub repository. + * + * @param cwd - Current working directory + * @returns GitHub repository info or null if not found + */ +async function fallbackResolveRepository( + cwd: string, +): Promise { + try { + const remoteUrl = ( + await getCommandOutput("git", ["remote", "get-url", "origin"], { cwd }) + ).trim(); + const match = remoteUrl.match(/github\.com[/:]([\w-]+\/[\w-.]+)/); + if (!match) { + return null; + } + const [owner, repoWithSuffix] = match[1].split("/"); + const repo = repoWithSuffix.replace(/\.git$/, ""); + return { owner, repo, remoteUrl }; + } catch { + return null; } - - await performReleaseCommit(packagesToUpdate, monorepoInfo.rootDir); - ui.log.success(`\n${t("release.independent_success")}`); - - const combinedNotes = allChangelogs - .map((c) => `${c.header}\n\n${c.content}`) - .join("\n\n"); - const primaryPackage = - packagesToUpdate.find((p) => p.pkg.name === "@stackcode/cli") || - packagesToUpdate[0]; - const tagName = `${primaryPackage.pkg.name.split("/")[1] || primaryPackage.pkg.name}@${primaryPackage.newVersion}`; - await handleGitHubReleaseCreation(tagName, combinedNotes); - - ui.log.warning(` ${t("release.next_steps_push")}`); } export const getReleaseCommand = (): CommandModule => ({ @@ -157,21 +150,47 @@ export const getReleaseCommand = (): CommandModule => ({ builder: {}, handler: async () => { try { + const cwd = process.cwd(); + const authManager = createCLIAuthFacade(); ui.log.step(t("release.start")); - const monorepoInfo = await detectVersioningStrategy(process.cwd()); - if (monorepoInfo.strategy === "unknown") { - throw new Error(t("release.error_structure")); + const releaseHooks: ReleaseWorkflowHooks = { + onProgress: async (progress: ReleaseWorkflowProgress) => { + await handleProgress(progress); + }, + confirmLockedRelease: ({ currentVersion, newVersion }) => + ui.promptForLockedRelease(currentVersion, newVersion), + displayIndependentPlan: (plan) => { + ui.displayIndependentReleasePlan(plan); + }, + confirmIndependentRelease: () => ui.promptForIndependentRelease(), + }; + + const result = await runReleaseWorkflow({ cwd }, releaseHooks); + + if (result.strategy !== "unknown") { + ui.log.info( + t("release.detected_strategy", { strategy: result.strategy }), + ); } - ui.log.info( - t("release.detected_strategy", { strategy: monorepoInfo.strategy }), - ); + if (result.status === "cancelled") { + await handleCancelledRelease(result); + return; + } - if (monorepoInfo.strategy === "locked") { - await handleLockedRelease(monorepoInfo); - } else if (monorepoInfo.strategy === "independent") { - await handleIndependentRelease(monorepoInfo); + await handlePreparedRelease(result); + + if (result.tagName && result.releaseNotes) { + await handleGitHubReleaseCreation( + { + tagName: result.tagName, + releaseNotes: result.releaseNotes, + cwd, + githubInfo: result.github, + }, + authManager, + ); } } catch (error: unknown) { ui.log.error(`\n${t("common.error_unexpected")}`); @@ -180,3 +199,59 @@ export const getReleaseCommand = (): CommandModule => ({ } }, }); + +async function handleProgress(progress: ReleaseWorkflowProgress) { + const messages: Partial void>> = { + lockedUpdatingVersions: () => + ui.log.step(t("release.step_updating_versions")), + lockedGeneratingChangelog: () => + ui.log.step(t("release.step_generating_changelog")), + independentFindingChanges: () => + ui.log.info(t("release.independent_mode_start")), + independentUpdatingPackages: () => + ui.log.step(t("release.step_updating_version")), + independentCommitting: () => + ui.log.step(t("release.step_committing_and_tagging")), + }; + + const handler = messages[progress.step]; + if (handler) handler(); +} + +async function handleCancelledRelease(result: ReleaseWorkflowResult) { + switch (result.reason) { + case "invalid-structure": + ui.log.error(t("release.error_structure")); + process.exit(1); + break; + case "no-changes": + ui.log.success(t("release.independent_mode_no_changes")); + break; + case "no-bumps": + ui.log.warning(t("release.independent_mode_no_bumps")); + break; + case "cancelled-by-user": + ui.log.warning(t("common.operation_cancelled")); + break; + case "error": + default: + ui.log.error(`\n${t("common.error_generic")}`); + if (result.error) { + ui.log.gray(result.error); + } + process.exit(1); + } +} + +async function handlePreparedRelease(result: ReleaseWorkflowResult) { + if (result.strategy === "locked") { + ui.log.success(`\n${t("release.success_ready_to_commit")}`); + ui.log.warning(` ${t("release.next_steps_commit")}`); + return; + } + + if (result.strategy === "independent") { + ui.log.success(`\n${t("release.independent_success")}`); + ui.log.warning(` ${t("release.next_steps_push")}`); + } +} diff --git a/packages/cli/src/commands/ui.ts b/packages/cli/src/commands/ui.ts index a62d8a11..7865f933 100644 --- a/packages/cli/src/commands/ui.ts +++ b/packages/cli/src/commands/ui.ts @@ -2,9 +2,12 @@ import chalk from "chalk"; import inquirer from "inquirer"; import { t } from "@stackcode/i18n"; import { type PackageBumpInfo } from "@stackcode/core"; -import { CLIAuthManager } from "./github.js"; -import { getCurrentRepository, fetchRepositoryIssues } from "./github.js"; -import { Octokit } from "@octokit/rest"; +import { + createCLIAuthFacade, + getCurrentRepository, + type CLIAuthFacade, +} from "../services/githubAuth.js"; +import { fetchRepositoryIssues } from "@stackcode/core"; export const log = { info: (message: string) => console.log(chalk.blue(message)), @@ -340,8 +343,8 @@ export async function promptForCommitAnswers(): Promise { }, ]); - const authManager = new CLIAuthManager(); - const hasGitHubAuth = authManager.getToken() !== null; + const authManager = createCLIAuthFacade(); + const hasGitHubAuth = (await authManager.getToken()) !== null; const currentRepo = hasGitHubAuth ? getCurrentRepository() : null; let affectedIssues = ""; @@ -392,7 +395,7 @@ export async function promptForCommitAnswers(): Promise { * Prompt para seleção de issues do GitHub */ async function promptForGitHubIssues( - authManager: CLIAuthManager, + authManager: CLIAuthFacade, repository: { owner: string; repo: string }, ): Promise { try { @@ -400,8 +403,25 @@ async function promptForGitHubIssues( `📋 ${t("github.issues.fetching")} ${repository.owner}/${repository.repo}...`, ); - const token = authManager.getToken()!; - const octokit = new Octokit({ auth: token }); + const token = await authManager.getToken(); + if (!token) { + log.warning(`❌ ${t("github.auth.not_authenticated")}`); + log.warning(t("github.auth.run_login")); + return []; + } + + let octokit; + try { + octokit = await authManager.getClient(); + } catch (error) { + log.error(`❌ ${t("github.auth.token_invalid")}`); + if (error instanceof Error) { + log.gray(error.message); + } + await authManager.removeToken(); + log.warning(t("github.auth.run_login")); + return []; + } const issues = await fetchRepositoryIssues(octokit, { owner: repository.owner, diff --git a/packages/cli/src/commands/validate.ts b/packages/cli/src/commands/validate.ts index b998a5f7..d997b164 100644 --- a/packages/cli/src/commands/validate.ts +++ b/packages/cli/src/commands/validate.ts @@ -1,6 +1,6 @@ import { CommandModule, ArgumentsCamelCase } from "yargs"; import { t } from "@stackcode/i18n"; -import { validateCommitMessage } from "@stackcode/core"; +import { runValidateWorkflow } from "@stackcode/core"; import * as ui from "./ui.js"; import { initEducationalMode, showBestPractice } from "../educational-mode.js"; @@ -19,11 +19,12 @@ export const getValidateCommand = (): CommandModule => ({ demandOption: true, }); }, - handler: (argv: ArgumentsCamelCase) => { + handler: async (argv: ArgumentsCamelCase) => { initEducationalMode(argv.educate || false); const message = argv.message as string; - if (validateCommitMessage(message)) { + const result = await runValidateWorkflow({ message }); + if (result.isValid) { ui.log.success(`✔ ${t("validate.success")}`); showBestPractice("educational.commit_validation_explanation"); } else { diff --git a/packages/cli/src/services/githubAuth.ts b/packages/cli/src/services/githubAuth.ts new file mode 100644 index 00000000..ac797a44 --- /dev/null +++ b/packages/cli/src/services/githubAuth.ts @@ -0,0 +1,159 @@ +import fs from "node:fs"; +import path from "node:path"; +import { + createCLIAuthProvider, + createFileTokenStorage, + createGitHubAuth, + DEFAULT_GITHUB_TOKEN_FILE, + DEFAULT_STACKCODE_DIRECTORY, +} from "@stackcode/github-auth"; + +export interface RepositoryInfo { + owner: string; + repo: string; +} + +interface RepositoryDetectionOptions { + cwd?: string; + verbose?: boolean; +} + +const tokenFilePath = path.join( + DEFAULT_STACKCODE_DIRECTORY, + DEFAULT_GITHUB_TOKEN_FILE, +); + +const tokenStorage = createFileTokenStorage({ filePath: tokenFilePath }); + +export const githubAuth = createGitHubAuth({ + provider: createCLIAuthProvider({ storage: tokenStorage }), +}); + +export type AuthenticatedOctokit = Awaited< + ReturnType +>; + +/** + * Retrieves a persisted GitHub token when available. + */ +export async function getStoredToken(): Promise { + return githubAuth.getStoredToken(); +} + +/** + * Saves a GitHub personal access token securely. + */ +export async function saveToken(token: string): Promise { + await githubAuth.saveToken(token); +} + +/** + * Removes any stored GitHub token. + */ +export async function removeToken(): Promise { + await githubAuth.removeToken(); +} + +/** + * Validates a candidate token by performing a lightweight authenticated request. + */ +export async function validateToken(token: string): Promise { + return githubAuth.validateToken(token); +} + +export interface CLIAuthFacade { + getToken(): Promise; + saveToken(token: string): Promise; + removeToken(): Promise; + validateToken(token: string): Promise; + getClient(): Promise; +} + +export function createCLIAuthFacade(): CLIAuthFacade { + return { + getToken: () => githubAuth.getStoredToken(), + saveToken: (token: string) => githubAuth.saveToken(token), + removeToken: () => githubAuth.removeToken(), + validateToken: (token: string) => githubAuth.validateToken(token), + getClient: () => githubAuth.getAuthenticatedClient(), + }; +} + +/** + * Attempts to detect the GitHub repository associated with the provided working directory. + * Returns null when the remote cannot be inferred. + */ +export function getCurrentRepository( + options: RepositoryDetectionOptions = {}, +): RepositoryInfo | null { + const cwd = options.cwd ?? process.cwd(); + const verbose = options.verbose ?? false; + + try { + const configPath = path.join(cwd, ".git", "config"); + if (verbose) { + console.log(`🔍 Detecting repository in: ${cwd}`); + } + + if (!fs.existsSync(configPath)) { + if (verbose) { + console.log( + "❌ .git/config not found – are you inside a Git repository?", + ); + } + return null; + } + + const configContent = fs.readFileSync(configPath, "utf8"); + const remoteLine = configContent + .split("\n") + .find((line: string) => line.includes("url = ")); + + if (!remoteLine) { + if (verbose) { + console.log("❌ Could not find a remote URL in .git/config"); + } + return null; + } + + const remoteUrl = remoteLine.split("url = ")[1]?.trim(); + if (verbose && remoteUrl) { + console.log(`🔗 Remote URL detected: ${remoteUrl}`); + } + + if (!remoteUrl) { + return null; + } + + const patterns = [ + /^https:\/\/github\.com\/([^/]+)\/([^/]+)(?:\.git)?$/, + /^git@github\.com:([^/]+)\/([^/]+)(?:\.git)?$/, + /^ssh:\/\/git@github\.com\/([^/]+)\/([^/]+)(?:\.git)?$/, + ]; + + for (const pattern of patterns) { + const match = remoteUrl.match(pattern); + if (match) { + const info: RepositoryInfo = { owner: match[1], repo: match[2] }; + if (verbose) { + console.log(`✅ Repository detected: ${info.owner}/${info.repo}`); + } + return info; + } + } + + if (verbose) { + console.log("❌ Remote URL did not match known GitHub patterns"); + } + return null; + } catch (error) { + if (verbose) { + console.log( + `❌ Failed to detect repository: ${ + error instanceof Error ? error.message : String(error) + }`, + ); + } + return null; + } +} diff --git a/packages/cli/test/commands/commit.test.ts b/packages/cli/test/commands/commit.test.ts index b1006ac1..1f74babd 100644 --- a/packages/cli/test/commands/commit.test.ts +++ b/packages/cli/test/commands/commit.test.ts @@ -3,7 +3,13 @@ import { getCommitCommand } from "../../src/commands/commit"; import * as core from "@stackcode/core"; import * as ui from "../../src/commands/ui"; -vi.mock("@stackcode/core"); +vi.mock("@stackcode/core", () => ({ + getCommandOutput: vi.fn(), + runCommitWorkflow: vi.fn(), + getErrorMessage: vi.fn((error: unknown) => + error instanceof Error ? error.message : String(error ?? "error"), + ), +})); vi.mock("../../src/commands/ui"); describe("Commit Command Handler", () => { @@ -33,14 +39,18 @@ describe("Commit Command Handler", () => { affectedIssues: "", }); - const runCommandMock = vi.mocked(core.runCommand); + vi.mocked(core.runCommitWorkflow).mockResolvedValue({ + status: "committed", + message: "feat(api): add new login endpoint", + }); await handler({ _: [], $0: "stc" }); - expect(runCommandMock).toHaveBeenCalledOnce(); - expect(runCommandMock).toHaveBeenCalledWith( - "git", - ["commit", "-m", "feat(api): add new login endpoint"], - expect.anything(), + expect(core.runCommitWorkflow).toHaveBeenCalledWith( + expect.objectContaining({ + type: "feat", + scope: "api", + shortDescription: "add new login endpoint", + }), ); }); @@ -60,24 +70,24 @@ describe("Commit Command Handler", () => { affectedIssues: "closes #42", }); - const runCommandMock = vi.mocked(core.runCommand); + vi.mocked(core.runCommitWorkflow).mockResolvedValue({ + status: "committed", + message: "", + }); await handler({ _: [], $0: "stc" }); - const expectedMessage = `refactor(auth): use JWT service for authentication - -Implement new JWT service for better security. -Separate concerns. - -BREAKING CHANGE: The token format has changed and now requires a new validation method. - -closes #42`; - - expect(runCommandMock).toHaveBeenCalledOnce(); - expect(runCommandMock).toHaveBeenCalledWith( - "git", - ["commit", "-m", expectedMessage], - { cwd: process.cwd() }, + expect(core.runCommitWorkflow).toHaveBeenCalledWith( + expect.objectContaining({ + type: "refactor", + scope: "auth", + shortDescription: "use JWT service for authentication", + longDescription: + "Implement new JWT service for better security.|Separate concerns.", + breakingChanges: + "The token format has changed and now requires a new validation method.", + affectedIssues: "closes #42", + }), ); }); }); diff --git a/packages/cli/test/commands/generate.test.ts b/packages/cli/test/commands/generate.test.ts index 52e5ef67..15c6518c 100644 --- a/packages/cli/test/commands/generate.test.ts +++ b/packages/cli/test/commands/generate.test.ts @@ -1,15 +1,18 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import inquirer from "inquirer"; import fs from "fs/promises"; +import path from "path"; import { - generateGitignoreContent, - generateReadmeContent, + runGenerateWorkflow, + type GenerateWorkflowResult, + type GenerateWorkflowOptions, + type GenerateWorkflowHooks, } from "@stackcode/core"; import { getGenerateCommand } from "../../src/commands/generate"; +import * as ui from "../../src/commands/ui.js"; vi.mock("@stackcode/core", () => ({ - generateReadmeContent: vi.fn(), - generateGitignoreContent: vi.fn(), + runGenerateWorkflow: vi.fn(), })); vi.mock("inquirer"); @@ -17,12 +20,14 @@ vi.mock("inquirer"); vi.mock("fs/promises"); vi.mock("@stackcode/i18n", () => ({ t: (key: string) => key })); +vi.mock("../../src/educational-mode.js", () => ({ + showEducationalMessage: vi.fn(), +})); const mockedInquirer = vi.mocked(inquirer); const mockedFs = vi.mocked(fs); const mockedCore = { - generateReadmeContent: vi.mocked(generateReadmeContent), - generateGitignoreContent: vi.mocked(generateGitignoreContent), + runGenerateWorkflow: vi.mocked(runGenerateWorkflow), }; describe("Generate Command", () => { @@ -31,34 +36,58 @@ describe("Generate Command", () => { beforeEach(() => { vi.clearAllMocks(); vi.spyOn(console, "log").mockImplementation(() => {}); + mockedCore.runGenerateWorkflow.mockResolvedValue({ + status: "completed", + files: [], + warnings: [], + }); + mockedFs.readFile.mockResolvedValue('{ "stack": "node-ts" }'); }); describe("Non-Interactive Mode", () => { it("should generate a README.md when specified as an argument", async () => { - // Arrange - const mockContent = "# Mocked README"; - mockedCore.generateReadmeContent.mockResolvedValue(mockContent); - mockedFs.access.mockRejectedValue(new Error("File not found")); + const workflowResult: GenerateWorkflowResult = { + status: "completed", + files: [ + { + fileType: "readme", + filePath: path.join(process.cwd(), "README.md"), + status: "created", + }, + ], + warnings: [], + }; + mockedCore.runGenerateWorkflow.mockResolvedValueOnce(workflowResult); const argv = { filetype: "readme", _: [], $0: "stc" }; // Act await handler(argv); // Assert - expect(mockedCore.generateReadmeContent).toHaveBeenCalledOnce(); - expect(mockedFs.writeFile).toHaveBeenCalledOnce(); - expect(mockedFs.writeFile).toHaveBeenCalledWith( - expect.stringContaining("README.md"), - mockContent, + expect(mockedCore.runGenerateWorkflow).toHaveBeenCalledWith( + expect.objectContaining({ + files: ["readme"], + }), + expect.any(Object), + ); + expect(console.log).toHaveBeenCalledWith( + expect.stringContaining("generate.success.readme"), ); - expect(console.log).toHaveBeenCalledWith("generate.success.readme"); }); it("should generate a .gitignore when specified as an argument", async () => { - // Arrange - const mockContent = "node_modules/"; - mockedCore.generateGitignoreContent.mockResolvedValue(mockContent); - mockedFs.access.mockRejectedValue(new Error("File not found")); + const workflowResult: GenerateWorkflowResult = { + status: "completed", + files: [ + { + fileType: "gitignore", + filePath: path.join(process.cwd(), ".gitignore"), + status: "created", + }, + ], + warnings: [], + }; + mockedCore.runGenerateWorkflow.mockResolvedValueOnce(workflowResult); mockedFs.readFile.mockResolvedValue('{ "stack": "node-ts" }'); const argv = { filetype: "gitignore", _: [], $0: "stc" }; @@ -66,14 +95,16 @@ describe("Generate Command", () => { await handler(argv); // Assert - expect(mockedCore.generateGitignoreContent).toHaveBeenCalledWith([ - "node-ts", - ]); - expect(mockedFs.writeFile).toHaveBeenCalledWith( - expect.stringContaining(".gitignore"), - mockContent, + expect(mockedCore.runGenerateWorkflow).toHaveBeenCalledWith( + expect.objectContaining({ + files: ["gitignore"], + gitignoreTechnologies: ["node-ts"], + }), + expect.any(Object), + ); + expect(console.log).toHaveBeenCalledWith( + expect.stringContaining("generate.success.gitignore"), ); - expect(console.log).toHaveBeenCalledWith("generate.success.gitignore"); }); }); @@ -83,23 +114,34 @@ describe("Generate Command", () => { mockedInquirer.prompt.mockResolvedValue({ filesToGenerate: ["readme", "gitignore"], }); - mockedCore.generateReadmeContent.mockResolvedValue("# README"); - mockedCore.generateGitignoreContent.mockResolvedValue("node_modules"); - mockedFs.access.mockRejectedValue(new Error("File not found")); + const workflowResult: GenerateWorkflowResult = { + status: "completed", + files: [ + { + fileType: "readme", + filePath: path.join(process.cwd(), "README.md"), + status: "created", + }, + { + fileType: "gitignore", + filePath: path.join(process.cwd(), ".gitignore"), + status: "created", + }, + ], + warnings: [], + }; + mockedCore.runGenerateWorkflow.mockResolvedValueOnce(workflowResult); const argv = { _: [], $0: "stc" }; // Act await handler(argv); // Assert - expect(mockedFs.writeFile).toHaveBeenCalledTimes(2); - expect(mockedFs.writeFile).toHaveBeenCalledWith( - expect.stringContaining("README.md"), - "# README", - ); - expect(mockedFs.writeFile).toHaveBeenCalledWith( - expect.stringContaining(".gitignore"), - "node_modules", + expect(mockedCore.runGenerateWorkflow).toHaveBeenCalledWith( + expect.objectContaining({ + files: expect.arrayContaining(["readme", "gitignore"]), + }), + expect.any(Object), ); }); @@ -112,40 +154,77 @@ describe("Generate Command", () => { await handler(argv); // Assert - expect(mockedFs.writeFile).not.toHaveBeenCalled(); - expect(console.log).toHaveBeenCalledWith("common.operation_cancelled"); + expect(mockedCore.runGenerateWorkflow).not.toHaveBeenCalled(); + expect(console.log).toHaveBeenCalledWith( + expect.stringContaining("common.operation_cancelled"), + ); }); }); describe("File Overwriting Logic", () => { it("should prompt for overwrite if file exists and proceed if confirmed", async () => { - // Arrange - mockedFs.access.mockResolvedValue(undefined); - mockedInquirer.prompt.mockResolvedValue({ confirm: true }); - mockedCore.generateReadmeContent.mockResolvedValue("# Overwritten"); + mockedCore.runGenerateWorkflow.mockImplementationOnce( + async ( + options: GenerateWorkflowOptions, + hooks?: GenerateWorkflowHooks, + ) => { + const promptSpy = vi.spyOn(ui, "promptForConfirmation"); + mockedInquirer.prompt.mockResolvedValueOnce({ confirm: true }); + const decision = await hooks?.shouldOverwriteFile?.({ + fileType: "readme", + filePath: path.join(process.cwd(), "README.md"), + }); + expect(promptSpy).toHaveBeenCalled(); + expect(decision).toBe(true); + return { + status: "completed", + files: [ + { + fileType: "readme", + filePath: path.join(process.cwd(), "README.md"), + status: "overwritten", + }, + ], + warnings: [], + }; + }, + ); const argv = { filetype: "readme", _: [], $0: "stc" }; // Act await handler(argv); // Assert - expect(mockedInquirer.prompt).toHaveBeenCalledOnce(); - expect(mockedFs.writeFile).toHaveBeenCalledOnce(); + expect(mockedCore.runGenerateWorkflow).toHaveBeenCalled(); + expect(console.log).toHaveBeenCalledWith( + expect.stringContaining("generate.success.readme"), + ); }); it("should cancel if file exists and user denies overwrite", async () => { - // Arrange - mockedFs.access.mockResolvedValue(undefined); - mockedInquirer.prompt.mockResolvedValue({ confirm: false }); + const workflowResult: GenerateWorkflowResult = { + status: "cancelled", + files: [ + { + fileType: "readme", + filePath: path.join(process.cwd(), "README.md"), + status: "skipped", + reason: "overwrite-declined", + }, + ], + warnings: [], + }; + mockedCore.runGenerateWorkflow.mockResolvedValueOnce(workflowResult); const argv = { filetype: "readme", _: [], $0: "stc" }; // Act await handler(argv); // Assert - expect(mockedInquirer.prompt).toHaveBeenCalledOnce(); - expect(mockedFs.writeFile).not.toHaveBeenCalled(); - expect(console.log).toHaveBeenCalledWith("common.operation_cancelled"); + expect(mockedCore.runGenerateWorkflow).toHaveBeenCalled(); + expect(console.log).toHaveBeenCalledWith( + expect.stringContaining("common.operation_cancelled"), + ); }); }); }); diff --git a/packages/cli/test/commands/init.test.ts b/packages/cli/test/commands/init.test.ts index 70fe535a..1f084c37 100644 --- a/packages/cli/test/commands/init.test.ts +++ b/packages/cli/test/commands/init.test.ts @@ -1,41 +1,26 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; +import path from "path"; import inquirer from "inquirer"; import fs from "fs/promises"; -import { - scaffoldProject, - setupHusky, - generateReadmeContent, - generateGitignoreContent, - runCommand, - validateStackDependencies, - saveStackCodeConfig, -} from "@stackcode/core"; +import { runInitWorkflow, type InitWorkflowResult } from "@stackcode/core"; import { getInitCommand } from "../../src/commands/init"; vi.mock("@stackcode/core", () => ({ - scaffoldProject: vi.fn(), - setupHusky: vi.fn(), - generateReadmeContent: vi.fn(), - generateGitignoreContent: vi.fn(), - runCommand: vi.fn(), - validateStackDependencies: vi.fn(), - saveStackCodeConfig: vi.fn(), + runInitWorkflow: vi.fn(), })); vi.mock("inquirer"); vi.mock("fs/promises"); vi.mock("@stackcode/i18n", () => ({ t: (key: string) => key })); +vi.mock("../../src/educational-mode.js", () => ({ + initEducationalMode: vi.fn(), + showEducationalMessage: vi.fn(), +})); const mockedInquirer = vi.mocked(inquirer); const mockedFs = vi.mocked(fs); const mockedCore = { - scaffoldProject: vi.mocked(scaffoldProject), - setupHusky: vi.mocked(setupHusky), - generateReadmeContent: vi.mocked(generateReadmeContent), - generateGitignoreContent: vi.mocked(generateGitignoreContent), - runCommand: vi.mocked(runCommand), - validateStackDependencies: vi.mocked(validateStackDependencies), - saveStackCodeConfig: vi.mocked(saveStackCodeConfig), + runInitWorkflow: vi.mocked(runInitWorkflow), }; describe("Init Command", () => { @@ -57,58 +42,46 @@ describe("Init Command", () => { }; mockedInquirer.prompt.mockResolvedValue(mockAnswers); mockedFs.access.mockRejectedValue(new Error("not found")); - mockedCore.generateReadmeContent.mockResolvedValue("# Test Project"); - mockedCore.generateGitignoreContent.mockResolvedValue("node_modules"); - mockedCore.validateStackDependencies.mockResolvedValue({ - isValid: true, - missingDependencies: [], - availableDependencies: ["npm"], - }); + const workflowResult: InitWorkflowResult = { + status: "completed", + projectPath: path.join(process.cwd(), mockAnswers.projectName), + dependencyValidation: { + isValid: true, + missingDependencies: [], + availableDependencies: ["npm"], + }, + dependenciesInstalled: true, + installCommand: { command: "npm", args: ["install"] }, + warnings: [], + }; + mockedCore.runInitWorkflow.mockResolvedValue(workflowResult); // Act await handler({ _: [], $0: "stc" }); // Assert const projectPath = expect.stringContaining(mockAnswers.projectName); - expect(mockedCore.scaffoldProject).toHaveBeenCalledWith({ - projectPath: projectPath, - stack: mockAnswers.stack, - features: mockAnswers.features, - replacements: { + expect(mockedCore.runInitWorkflow).toHaveBeenCalledWith( + { + projectPath, projectName: mockAnswers.projectName, description: mockAnswers.description, authorName: mockAnswers.authorName, + stack: mockAnswers.stack, + features: mockAnswers.features, + commitValidation: mockAnswers.commitValidation, }, - }); - - expect(mockedCore.saveStackCodeConfig).toHaveBeenCalledWith( - projectPath, expect.objectContaining({ - defaultAuthor: mockAnswers.authorName, - defaultLicense: "MIT", - features: { commitValidation: true }, + onProgress: expect.any(Function), + onEducationalMessage: expect.any(Function), + onMissingDependencies: expect.any(Function), + confirmContinueAfterMissingDependencies: expect.any(Function), }), ); - expect(mockedFs.writeFile).toHaveBeenCalledWith( - expect.stringContaining("README.md"), - "# Test Project", - ); - expect(mockedFs.writeFile).toHaveBeenCalledWith( - expect.stringContaining(".gitignore"), - "node_modules", + expect(console.log).toHaveBeenCalledWith( + expect.stringContaining("init.success.ready"), ); - - expect(mockedCore.setupHusky).toHaveBeenCalledWith(projectPath); - - expect(mockedCore.runCommand).toHaveBeenCalledWith("git", ["init"], { - cwd: projectPath, - }); - expect(mockedCore.runCommand).toHaveBeenCalledWith("npm", ["install"], { - cwd: projectPath, - }); - - expect(console.log).toHaveBeenCalledWith("init.success.ready"); }); it("should cancel the operation if user denies overwrite", async () => { @@ -124,7 +97,9 @@ describe("Init Command", () => { // Assert expect(mockedInquirer.prompt).toHaveBeenCalledTimes(2); - expect(mockedCore.scaffoldProject).not.toHaveBeenCalled(); - expect(console.log).toHaveBeenCalledWith("common.operation_cancelled"); + expect(mockedCore.runInitWorkflow).not.toHaveBeenCalled(); + expect(console.log).toHaveBeenCalledWith( + expect.stringContaining("common.operation_cancelled"), + ); }); }); diff --git a/packages/cli/test/commands/release.test.ts b/packages/cli/test/commands/release.test.ts index 1b1a4167..5975168e 100644 --- a/packages/cli/test/commands/release.test.ts +++ b/packages/cli/test/commands/release.test.ts @@ -1,14 +1,72 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { getReleaseCommand } from "../../src/commands/release"; import * as core from "@stackcode/core"; -import inquirer from "inquirer"; -import fs from "fs/promises"; -import Configstore from "configstore"; +import * as ui from "../../src/commands/ui.js"; -vi.mock("@stackcode/core"); -vi.mock("inquirer"); -vi.mock("fs/promises"); -vi.mock("configstore"); +type ViMock = ReturnType; + +interface MockReleaseResult { + status: "prepared" | "cancelled"; + strategy: "locked" | "independent" | "unknown"; + newVersion?: string; + releaseNotes?: string; + tagName?: string; + github?: { owner: string; repo: string; remoteUrl: string }; + reason?: + | "invalid-structure" + | "cancelled-by-user" + | "no-changes" + | "no-bumps" + | "error"; +} + +const authManagerInstance = vi.hoisted(() => ({ + getToken: vi.fn(), + saveToken: vi.fn(), + removeToken: vi.fn(), + validateToken: vi.fn(), +})); + +vi.mock("@stackcode/core", () => { + const runReleaseWorkflow = vi.fn(); + const createGitHubRelease = vi.fn(); + return { + runReleaseWorkflow, + createGitHubRelease, + getCommandOutput: vi.fn(), + getErrorMessage: vi.fn((error: unknown) => + error instanceof Error ? error.message : String(error), + ), + }; +}); + +vi.mock("../../src/services/githubAuth.js", () => ({ + createCLIAuthFacade: vi.fn(() => authManagerInstance), + getCurrentRepository: vi.fn(), +})); + +vi.mock("../../src/commands/ui.js", () => ({ + log: { + info: vi.fn(), + success: vi.fn(), + warning: vi.fn(), + error: vi.fn(), + step: vi.fn(), + gray: vi.fn(), + }, + promptToCreateGitHubRelease: vi.fn().mockResolvedValue(false), + promptForToken: vi.fn(), + promptToSaveToken: vi.fn(), + promptForLockedRelease: vi.fn().mockResolvedValue(true), + promptForIndependentRelease: vi.fn().mockResolvedValue(true), + displayIndependentReleasePlan: vi.fn(), +})); + +const coreModule = core as Record; +const runReleaseWorkflowMock = coreModule["runReleaseWorkflow"] as ViMock; +const createGitHubReleaseMock = coreModule["createGitHubRelease"] as ViMock; +const getErrorMessageMock = coreModule["getErrorMessage"] as ViMock; +const getCommandOutputMock = coreModule["getCommandOutput"] as ViMock; describe("Release Command Handler", () => { const { handler } = getReleaseCommand(); @@ -18,76 +76,104 @@ describe("Release Command Handler", () => { vi.spyOn(console, "log").mockImplementation(() => {}); vi.spyOn(console, "error").mockImplementation(() => {}); vi.spyOn(console, "table").mockImplementation(() => {}); + + authManagerInstance.getToken.mockReset(); + authManagerInstance.getToken.mockResolvedValue(null); + authManagerInstance.saveToken.mockReset(); + authManagerInstance.saveToken.mockResolvedValue(undefined); + authManagerInstance.removeToken.mockReset(); + authManagerInstance.removeToken.mockResolvedValue(undefined); + authManagerInstance.validateToken.mockReset(); + authManagerInstance.validateToken.mockResolvedValue(false); + runReleaseWorkflowMock.mockReset(); + createGitHubReleaseMock.mockReset(); + getErrorMessageMock.mockReset(); + getCommandOutputMock.mockReset(); }); afterEach(() => { vi.restoreAllMocks(); }); - it('should call the correct core functions for a "locked" release', async () => { - // Arrange - vi.mocked(core.detectVersioningStrategy).mockResolvedValue({ + it("handles a locked release prepared by the workflow", async () => { + runReleaseWorkflowMock.mockResolvedValue({ + status: "prepared", strategy: "locked", - rootDir: "/fake", - packages: [], - }); - vi.mocked(inquirer.prompt).mockResolvedValue({ - confirm: true, - createRelease: true, - }); - vi.mocked(core.getRecommendedBump).mockResolvedValue("patch"); - vi.mocked(core.getErrorMessage).mockImplementation((error: any) => - error instanceof Error ? error.message : String(error), - ); - vi.mocked(core.getCommandOutput).mockResolvedValue( - "git@github.com:owner/repo.git", - ); - vi.mocked(fs.readFile).mockResolvedValue(""); - vi.mocked(fs.writeFile).mockResolvedValue(); - const config = new Configstore("stackcode"); - vi.mocked(config.get).mockReturnValue("gh_token"); + newVersion: "1.1.0", + releaseNotes: "Notes", + tagName: "v1.1.0", + } as MockReleaseResult); - // Act // @ts-expect-error - Testing with empty options object await handler({}); - // Assert - expect(core.updateAllVersions).toHaveBeenCalledOnce(); - expect(fs.writeFile).toHaveBeenCalledOnce(); - expect(core.findChangedPackages).not.toHaveBeenCalled(); + expect(runReleaseWorkflowMock).toHaveBeenCalled(); + expect(ui.log.success).toHaveBeenCalledWith( + expect.stringContaining("release.success_ready_to_commit"), + ); + expect(createGitHubReleaseMock).not.toHaveBeenCalled(); }); - it('should call the correct core functions for an "independent" release', async () => { - // Arrange - vi.mocked(core.detectVersioningStrategy).mockResolvedValue({ + it("logs when there are no changes in independent strategy", async () => { + runReleaseWorkflowMock.mockResolvedValue({ + status: "cancelled", strategy: "independent", - rootDir: "/fake", - packages: [{ name: "pkg1", path: "/fake/pkg1" }], - }); - vi.mocked(inquirer.prompt).mockResolvedValue({ - confirmRelease: true, - createRelease: true, - }); - vi.mocked(core.findChangedPackages).mockResolvedValue([ - { name: "pkg1", path: "/fake/pkg1" }, - ]); - vi.mocked(core.determinePackageBumps).mockResolvedValue([ - { - pkg: { name: "pkg1", path: "/fake/pkg1" }, - bumpType: "patch", - newVersion: "1.0.1", - }, - ]); - vi.mocked(core.getCommandOutput).mockResolvedValue( - "git@github.com:owner/repo.git", + reason: "no-changes", + } as MockReleaseResult); + + // @ts-expect-error - Testing with empty options object + await handler({}); + + expect(ui.log.success).toHaveBeenCalledWith( + expect.stringContaining("release.independent_mode_no_changes"), ); + }); + + it("creates a GitHub release when requested", async () => { + runReleaseWorkflowMock.mockResolvedValue({ + status: "prepared", + strategy: "locked", + newVersion: "1.2.0", + releaseNotes: "Notes", + tagName: "v1.2.0", + github: { owner: "owner", repo: "repo", remoteUrl: "remote" }, + } as MockReleaseResult); + + vi.mocked(ui.promptToCreateGitHubRelease).mockResolvedValue(true); + authManagerInstance.getToken.mockResolvedValue("gh_token"); + authManagerInstance.validateToken.mockResolvedValue(true); - // Act // @ts-expect-error - Testing with empty options object await handler({}); - // Assert - expect(core.findChangedPackages).toHaveBeenCalledOnce(); - expect(core.updateAllVersions).not.toHaveBeenCalled(); + expect(createGitHubReleaseMock).toHaveBeenCalledWith( + expect.objectContaining({ + owner: "owner", + repo: "repo", + tagName: "v1.2.0", + }), + ); + }); + + it("exits when structure is invalid", async () => { + runReleaseWorkflowMock.mockResolvedValue({ + status: "cancelled", + strategy: "unknown", + reason: "invalid-structure", + } as MockReleaseResult); + + const exitSpy = vi.spyOn(process, "exit").mockImplementation((() => { + throw new Error("exit"); + }) as unknown as typeof process.exit); + + await expect(async () => { + // @ts-expect-error - Testing with empty options object + await handler({}); + }).rejects.toThrow("exit"); + + expect(ui.log.error).toHaveBeenCalledWith( + expect.stringContaining("structure"), + ); + exitSpy.mockRestore(); }); }); diff --git a/packages/cli/test/commands/validate.test.ts b/packages/cli/test/commands/validate.test.ts index 5241d642..7f11b05d 100644 --- a/packages/cli/test/commands/validate.test.ts +++ b/packages/cli/test/commands/validate.test.ts @@ -1,14 +1,14 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { validateCommitMessage } from "@stackcode/core"; +import { runValidateWorkflow } from "@stackcode/core"; import { getValidateCommand } from "../../src/commands/validate"; vi.mock("@stackcode/core", () => ({ - validateCommitMessage: vi.fn(), + runValidateWorkflow: vi.fn(), })); vi.mock("@stackcode/i18n", () => ({ t: (key: string) => key })); const mockedCore = { - validateCommitMessage: vi.mocked(validateCommitMessage), + runValidateWorkflow: vi.mocked(runValidateWorkflow), }; describe("Validate Command", () => { @@ -23,32 +23,36 @@ describe("Validate Command", () => { vi.spyOn(console, "error").mockImplementation(() => {}); }); - it("should log a success message for a valid commit message", () => { + it("should log a success message for a valid commit message", async () => { // Arrange const argv = { message: "feat: add new feature", _: [], $0: "stc" }; - mockedCore.validateCommitMessage.mockReturnValue(true); + mockedCore.runValidateWorkflow.mockResolvedValue({ isValid: true }); // Act - handler(argv); + await handler(argv as any); // Assert - expect(mockedCore.validateCommitMessage).toHaveBeenCalledWith(argv.message); + expect(mockedCore.runValidateWorkflow).toHaveBeenCalledWith({ + message: argv.message, + }); expect(console.log).toHaveBeenCalledWith( expect.stringContaining("validate.success"), ); expect(mockProcessExit).not.toHaveBeenCalled(); }); - it("should log an error and exit with code 1 for an invalid commit message", () => { + it("should log an error and exit with code 1 for an invalid commit message", async () => { // Arrange const argv = { message: "invalid message", _: [], $0: "stc" }; - mockedCore.validateCommitMessage.mockReturnValue(false); + mockedCore.runValidateWorkflow.mockResolvedValue({ isValid: false }); // Act - handler(argv); + await handler(argv as any); // Assert - expect(mockedCore.validateCommitMessage).toHaveBeenCalledWith(argv.message); + expect(mockedCore.runValidateWorkflow).toHaveBeenCalledWith({ + message: argv.message, + }); expect(console.error).toHaveBeenCalledWith( expect.stringContaining("validate.error_invalid"), ); diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 1c1620b0..32b492de 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -6,5 +6,9 @@ }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"], - "references": [{ "path": "../core" }, { "path": "../i18n" }] + "references": [ + { "path": "../core" }, + { "path": "../i18n" }, + { "path": "../github-auth" } + ] } diff --git a/packages/core/dist/github.d.ts b/packages/core/dist/github.d.ts index 84984b1f..f6b4ca72 100644 --- a/packages/core/dist/github.d.ts +++ b/packages/core/dist/github.d.ts @@ -35,11 +35,17 @@ export interface FetchIssuesOptions { per_page?: number; } /** - * Busca issues de um repositório GitHub + * Fetches issues from a GitHub repository. * - * @param octokit - Cliente Octokit autenticado - * @param options - Opções de busca - * @returns Promise com array de issues formatadas + * @param octokit - Authenticated Octokit client + * @param options - Fetch options for filtering and pagination + * @returns Promise resolving to an array of formatted issues */ export declare function fetchRepositoryIssues(octokit: Octokit, options: FetchIssuesOptions): Promise; +/** + * Creates a GitHub release for a repository. + * + * @param options - Release options including repository info, tag, and notes + * @returns Promise that resolves when release is created + */ export declare function createGitHubRelease(options: GitHubReleaseOptions): Promise; diff --git a/packages/core/dist/github.js b/packages/core/dist/github.js index c5827df2..70a8f500 100644 --- a/packages/core/dist/github.js +++ b/packages/core/dist/github.js @@ -1,10 +1,10 @@ import { Octokit } from "@octokit/rest"; /** - * Busca issues de um repositório GitHub + * Fetches issues from a GitHub repository. * - * @param octokit - Cliente Octokit autenticado - * @param options - Opções de busca - * @returns Promise com array de issues formatadas + * @param octokit - Authenticated Octokit client + * @param options - Fetch options for filtering and pagination + * @returns Promise resolving to an array of formatted issues */ export async function fetchRepositoryIssues(octokit, options) { const { owner, repo, state = "open", assignee, labels, sort = "updated", direction = "desc", per_page = 30, } = options; @@ -20,7 +20,6 @@ export async function fetchRepositoryIssues(octokit, options) { direction, per_page, }); - // Filtrar apenas issues (não pull requests) const issues = response.data.filter((issue) => !issue.pull_request); console.log(`[Core] Found ${issues.length} issues`); return issues.map((issue) => ({ @@ -53,6 +52,12 @@ export async function fetchRepositoryIssues(octokit, options) { throw new Error(`Failed to fetch repository issues: ${error instanceof Error ? error.message : "Unknown error"}`); } } +/** + * Creates a GitHub release for a repository. + * + * @param options - Release options including repository info, tag, and notes + * @returns Promise that resolves when release is created + */ export async function createGitHubRelease(options) { const { owner, repo, tagName, releaseNotes, token } = options; const octokit = new Octokit({ auth: token }); diff --git a/packages/core/dist/index.d.ts b/packages/core/dist/index.d.ts deleted file mode 100644 index 1396dc51..00000000 --- a/packages/core/dist/index.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @fileoverview Main entry point for the @stackcode/core package. - * It exports all the public-facing functions and types. - */ -export { runCommand, getCommandOutput, getErrorMessage, isCommandAvailable, getStackDependencies, validateStackDependencies, loadStackCodeConfig, saveStackCodeConfig, } from "./utils.js"; -export { generateGitignoreContent, generateReadmeContent, } from "./generators.js"; -export { scaffoldProject, setupHusky } from "./scaffold.js"; -export { validateCommitMessage } from "./validator.js"; -export * from "./github.js"; -export * from "./types.js"; -export { detectVersioningStrategy, getRecommendedBump, updateAllVersions, generateChangelog, findChangedPackages, determinePackageBumps, updatePackageVersion, performReleaseCommit, } from "./release.js"; diff --git a/packages/core/dist/index.js b/packages/core/dist/index.js deleted file mode 100644 index 5a437563..00000000 --- a/packages/core/dist/index.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @fileoverview Main entry point for the @stackcode/core package. - * It exports all the public-facing functions and types. - */ -export { runCommand, getCommandOutput, getErrorMessage, isCommandAvailable, getStackDependencies, validateStackDependencies, loadStackCodeConfig, saveStackCodeConfig, } from "./utils.js"; -export { generateGitignoreContent, generateReadmeContent, } from "./generators.js"; -export { scaffoldProject, setupHusky } from "./scaffold.js"; -export { validateCommitMessage } from "./validator.js"; -export * from "./github.js"; -export * from "./types.js"; -export { detectVersioningStrategy, getRecommendedBump, updateAllVersions, generateChangelog, findChangedPackages, determinePackageBumps, updatePackageVersion, performReleaseCommit, } from "./release.js"; -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/core/dist/release.d.ts b/packages/core/dist/release.d.ts index 331636cf..09c08b90 100644 --- a/packages/core/dist/release.d.ts +++ b/packages/core/dist/release.d.ts @@ -1,9 +1,66 @@ +/** + * @fileoverview Release management utilities for versioning and changelog generation. + * Supports both monorepo (independent/locked) and single-package strategies. + */ import { PackageInfo, MonorepoInfo, PackageBumpInfo } from "./types.js"; +/** + * Detects the versioning strategy of a project (monorepo or single package). + * + * @param startPath - Starting directory to analyze + * @returns Monorepo information including strategy, packages, and versions + */ export declare function detectVersioningStrategy(startPath: string): Promise; +/** + * Finds packages that have changes since their last git tag. + * + * @param allPackages - Array of all packages to check + * @param projectRoot - Root directory of the project + * @returns Array of packages that have been modified + */ export declare function findChangedPackages(allPackages: PackageInfo[], projectRoot: string): Promise; +/** + * Gets the recommended version bump type based on conventional commits. + * + * @param projectRoot - Root directory of the project + * @returns Recommended bump type ('major', 'minor', or 'patch') + */ export declare function getRecommendedBump(projectRoot: string): Promise; +/** + * Determines version bumps for each changed package based on conventional commits. + * + * @param changedPackages - Array of packages that have changes + * @param projectRoot - Root directory of the project + * @returns Array of package bump information + */ export declare function determinePackageBumps(changedPackages: PackageInfo[]): Promise; +/** + * Generates a changelog based on conventional commits. + * + * @param monorepoInfo - Monorepo information + * @param pkgInfo - Optional package bump info for package-specific changelog + * @returns Promise resolving to the generated changelog content + */ export declare function generateChangelog(monorepoInfo: MonorepoInfo, pkgInfo?: PackageBumpInfo): Promise; +/** + * Updates the version field in a package's package.json file. + * + * @param pkgInfo - Package bump information containing the new version + * @returns Promise that resolves when the file is updated + */ export declare function updatePackageVersion(pkgInfo: PackageBumpInfo): Promise; +/** + * Updates all package versions to a single version (locked strategy). + * + * @param monorepoInfo - Monorepo information + * @param newVersion - New version to apply to all packages + * @returns Promise that resolves when all versions are updated + */ export declare function updateAllVersions(monorepoInfo: MonorepoInfo, newVersion: string): Promise; +/** + * Commits release changes and creates git tags for released packages. + * + * @param packages - Array of package bump information + * @param projectRoot - Root directory of the project + * @returns Promise that resolves when commit and tags are created + */ export declare function performReleaseCommit(packages: PackageBumpInfo[], projectRoot: string): Promise; diff --git a/packages/core/dist/release.js b/packages/core/dist/release.js index 385d9f6f..f21331a1 100644 --- a/packages/core/dist/release.js +++ b/packages/core/dist/release.js @@ -1,9 +1,19 @@ +/** + * @fileoverview Release management utilities for versioning and changelog generation. + * Supports both monorepo (independent/locked) and single-package strategies. + */ import fs from "fs/promises"; import path from "path"; import semver from "semver"; import { Bumper } from "conventional-recommended-bump"; import conventionalChangelog from "conventional-changelog-core"; import { getCommandOutput, runCommand } from "./utils.js"; +/** + * Safely reads and parses a package.json file. + * + * @param filePath - Path to the package.json file + * @returns Parsed package.json object or null if reading fails + */ async function _safeReadJson(filePath) { try { const content = await fs.readFile(filePath, "utf-8"); @@ -13,6 +23,13 @@ async function _safeReadJson(filePath) { return null; } } +/** + * Finds all package paths in a monorepo workspace. + * + * @param rootDir - Root directory of the monorepo + * @param rootPackageJson - Parsed root package.json + * @returns Array of package directory paths + */ async function _findPackagePaths(rootDir, rootPackageJson) { const packagePaths = []; let workspaces; @@ -53,6 +70,13 @@ async function _findPackagePaths(rootDir, rootPackageJson) { } return packagePaths; } +/** + * Retrieves the latest git tags for packages in a monorepo. + * + * @param packageNames - Array of package names to find tags for + * @param projectRoot - Root directory of the project + * @returns Map of package names to their latest tags + */ async function _getLatestTags(packageNames, projectRoot) { const tagMap = new Map(); try { @@ -75,6 +99,12 @@ async function _getLatestTags(packageNames, projectRoot) { } return tagMap; } +/** + * Detects the versioning strategy of a project (monorepo or single package). + * + * @param startPath - Starting directory to analyze + * @returns Monorepo information including strategy, packages, and versions + */ export async function detectVersioningStrategy(startPath) { const rootDir = startPath; const rootPackageJsonPath = path.join(rootDir, "package.json"); @@ -96,6 +126,13 @@ export async function detectVersioningStrategy(startPath) { } return { strategy: "locked", rootDir, rootVersion, packages }; } +/** + * Finds packages that have changes since their last git tag. + * + * @param allPackages - Array of all packages to check + * @param projectRoot - Root directory of the project + * @returns Array of packages that have been modified + */ export async function findChangedPackages(allPackages, projectRoot) { const packageNames = allPackages.map((p) => p.name); const latestTags = await _getLatestTags(packageNames, projectRoot); @@ -115,11 +152,24 @@ export async function findChangedPackages(allPackages, projectRoot) { return !latestTags.has(pkg.name); }); } +/** + * Gets the recommended version bump type based on conventional commits. + * + * @param projectRoot - Root directory of the project + * @returns Recommended bump type ('major', 'minor', or 'patch') + */ export async function getRecommendedBump(projectRoot) { const bumper = new Bumper(projectRoot).loadPreset("angular"); const recommendation = await bumper.bump(); return recommendation?.releaseType || "patch"; } +/** + * Determines version bumps for each changed package based on conventional commits. + * + * @param changedPackages - Array of packages that have changes + * @param projectRoot - Root directory of the project + * @returns Array of package bump information + */ export async function determinePackageBumps(changedPackages) { const bumpInfoPromises = changedPackages.map(async (pkg) => { const bumpType = await getRecommendedBump(pkg.path); @@ -129,6 +179,13 @@ export async function determinePackageBumps(changedPackages) { const results = await Promise.all(bumpInfoPromises); return results.filter((info) => info !== null); } +/** + * Generates a changelog based on conventional commits. + * + * @param monorepoInfo - Monorepo information + * @param pkgInfo - Optional package bump info for package-specific changelog + * @returns Promise resolving to the generated changelog content + */ export function generateChangelog(monorepoInfo, pkgInfo) { return new Promise((resolve, reject) => { let changelogContent = ""; @@ -144,6 +201,12 @@ export function generateChangelog(monorepoInfo, pkgInfo) { stream.on("error", reject); }); } +/** + * Updates the version field in a package's package.json file. + * + * @param pkgInfo - Package bump information containing the new version + * @returns Promise that resolves when the file is updated + */ export async function updatePackageVersion(pkgInfo) { const pkgJsonPath = path.join(pkgInfo.pkg.path, "package.json"); const pkgJson = await _safeReadJson(pkgJsonPath); @@ -152,6 +215,13 @@ export async function updatePackageVersion(pkgInfo) { await fs.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n"); } } +/** + * Updates all package versions to a single version (locked strategy). + * + * @param monorepoInfo - Monorepo information + * @param newVersion - New version to apply to all packages + * @returns Promise that resolves when all versions are updated + */ export async function updateAllVersions(monorepoInfo, newVersion) { const allPaths = [ path.join(monorepoInfo.rootDir, "package.json"), @@ -165,6 +235,13 @@ export async function updateAllVersions(monorepoInfo, newVersion) { } })); } +/** + * Commits release changes and creates git tags for released packages. + * + * @param packages - Array of package bump information + * @param projectRoot - Root directory of the project + * @returns Promise that resolves when commit and tags are created + */ export async function performReleaseCommit(packages, projectRoot) { const filesToAdd = []; let commitMessage = "chore(release): release\n\n"; diff --git a/packages/core/dist/types.d.ts b/packages/core/dist/types.d.ts index e7f958ad..6962cf11 100644 --- a/packages/core/dist/types.d.ts +++ b/packages/core/dist/types.d.ts @@ -26,6 +26,7 @@ export interface StackCodeConfig { defaultAuthor?: string; defaultLicense?: string; defaultDescription?: string; + stack?: SupportedStack | string; features?: { commitValidation?: boolean; husky?: boolean; diff --git a/packages/core/dist/utils.js b/packages/core/dist/utils.js index dfada6fd..06e42112 100644 --- a/packages/core/dist/utils.js +++ b/packages/core/dist/utils.js @@ -156,6 +156,7 @@ export async function loadStackCodeConfig(projectPath) { } catch { return { + stack: undefined, features: { commitValidation: false, husky: false, diff --git a/packages/core/src/github.ts b/packages/core/src/github.ts index 13d64dbb..01e4f38a 100644 --- a/packages/core/src/github.ts +++ b/packages/core/src/github.ts @@ -38,11 +38,11 @@ export interface FetchIssuesOptions { } /** - * Busca issues de um repositório GitHub + * Fetches issues from a GitHub repository. * - * @param octokit - Cliente Octokit autenticado - * @param options - Opções de busca - * @returns Promise com array de issues formatadas + * @param octokit - Authenticated Octokit client + * @param options - Fetch options for filtering and pagination + * @returns Promise resolving to an array of formatted issues */ export async function fetchRepositoryIssues( octokit: Octokit, @@ -73,7 +73,6 @@ export async function fetchRepositoryIssues( per_page, }); - // Filtrar apenas issues (não pull requests) const issues = response.data.filter((issue) => !issue.pull_request); console.log(`[Core] Found ${issues.length} issues`); @@ -115,6 +114,12 @@ export async function fetchRepositoryIssues( } } +/** + * Creates a GitHub release for a repository. + * + * @param options - Release options including repository info, tag, and notes + * @returns Promise that resolves when release is created + */ export async function createGitHubRelease( options: GitHubReleaseOptions, ): Promise { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 199adab8..000e6e87 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -23,6 +23,22 @@ export { validateCommitMessage } from "./validator.js"; export * from "./github.js"; export * from "./types.js"; +export { + runIssuesWorkflow, + clearIssuesCache, + clearExpiredIssuesCache, + clearRepositoryCache, + getIssuesCacheSize, + getIssuesCacheStats, + type IssuesWorkflowRepository, + type IssuesWorkflowOptions, + type IssuesWorkflowResult, + type IssuesWorkflowStep, + type IssuesWorkflowProgress, + type IssuesWorkflowHooks, + type IssuesCacheStats, +} from "./workflows/issues.js"; + export { detectVersioningStrategy, getRecommendedBump, @@ -33,3 +49,74 @@ export { updatePackageVersion, performReleaseCommit, } from "./release.js"; + +export { + runInitWorkflow, + type InitFeature, + type InitWorkflowStep, + type InitWorkflowOptions, + type InitWorkflowProgress, + type InitWorkflowDependencyDecision, + type InitWorkflowHooks, + type InitWorkflowResult, +} from "./workflows/init.js"; + +export { + runGenerateWorkflow, + type GenerateFileType, + type GenerateWorkflowStep, + type GenerateWorkflowOptions, + type GenerateWorkflowProgress, + type GenerateWorkflowHooks, + type GenerateWorkflowResult, + type GenerateWorkflowFileResult, + type GenerateWorkflowFileStatus, + type GenerateWorkflowFileSkipReason, +} from "./workflows/generate.js"; + +export { + runValidateWorkflow, + type ValidateWorkflowOptions, + type ValidateWorkflowProgress, + type ValidateWorkflowStep, + type ValidateWorkflowHooks, + type ValidateWorkflowResult, + runProjectValidateWorkflow, + type ProjectValidateOptions, + type ProjectValidateProgress, + type ProjectValidateStep, + type ProjectValidateIssue, + type ProjectValidateResult, + type ProjectValidateSeverity, +} from "./workflows/validate.js"; + +export { + runCommitWorkflow, + type CommitWorkflowOptions, + type CommitWorkflowProgress, + type CommitWorkflowStep, + type CommitWorkflowHooks, + type CommitWorkflowResult, + runGitStartWorkflow, + type GitStartWorkflowOptions, + type GitStartWorkflowProgress, + type GitStartWorkflowStep, + type GitStartWorkflowHooks, + type GitStartWorkflowResult, + runGitFinishWorkflow, + type GitFinishWorkflowOptions, + type GitFinishWorkflowProgress, + type GitFinishWorkflowStep, + type GitFinishWorkflowHooks, + type GitFinishWorkflowResult, +} from "./workflows/git.js"; + +export { + runReleaseWorkflow, + type ReleaseWorkflowOptions, + type ReleaseWorkflowHooks, + type ReleaseWorkflowProgress, + type ReleaseWorkflowStep, + type ReleaseWorkflowResult, + type ReleaseWorkflowGitHubInfo, +} from "./workflows/release.js"; diff --git a/packages/core/src/release.ts b/packages/core/src/release.ts index ff31e787..4f71d01d 100644 --- a/packages/core/src/release.ts +++ b/packages/core/src/release.ts @@ -1,3 +1,8 @@ +/** + * @fileoverview Release management utilities for versioning and changelog generation. + * Supports both monorepo (independent/locked) and single-package strategies. + */ + import fs from "fs/promises"; import path from "path"; import semver from "semver"; @@ -13,6 +18,12 @@ interface PackageJson { [key: string]: unknown; } +/** + * Safely reads and parses a package.json file. + * + * @param filePath - Path to the package.json file + * @returns Parsed package.json object or null if reading fails + */ async function _safeReadJson(filePath: string): Promise { try { const content = await fs.readFile(filePath, "utf-8"); @@ -22,6 +33,13 @@ async function _safeReadJson(filePath: string): Promise { } } +/** + * Finds all package paths in a monorepo workspace. + * + * @param rootDir - Root directory of the monorepo + * @param rootPackageJson - Parsed root package.json + * @returns Array of package directory paths + */ async function _findPackagePaths( rootDir: string, rootPackageJson: PackageJson, @@ -63,6 +81,13 @@ async function _findPackagePaths( return packagePaths; } +/** + * Retrieves the latest git tags for packages in a monorepo. + * + * @param packageNames - Array of package names to find tags for + * @param projectRoot - Root directory of the project + * @returns Map of package names to their latest tags + */ async function _getLatestTags( packageNames: string[], projectRoot: string, @@ -89,6 +114,12 @@ async function _getLatestTags( return tagMap; } +/** + * Detects the versioning strategy of a project (monorepo or single package). + * + * @param startPath - Starting directory to analyze + * @returns Monorepo information including strategy, packages, and versions + */ export async function detectVersioningStrategy( startPath: string, ): Promise { @@ -123,6 +154,13 @@ export async function detectVersioningStrategy( return { strategy: "locked", rootDir, rootVersion, packages }; } +/** + * Finds packages that have changes since their last git tag. + * + * @param allPackages - Array of all packages to check + * @param projectRoot - Root directory of the project + * @returns Array of packages that have been modified + */ export async function findChangedPackages( allPackages: PackageInfo[], projectRoot: string, @@ -152,12 +190,25 @@ export async function findChangedPackages( }); } +/** + * Gets the recommended version bump type based on conventional commits. + * + * @param projectRoot - Root directory of the project + * @returns Recommended bump type ('major', 'minor', or 'patch') + */ export async function getRecommendedBump(projectRoot: string): Promise { const bumper = new Bumper(projectRoot).loadPreset("angular"); const recommendation = await bumper.bump(); return (recommendation as { releaseType?: string })?.releaseType || "patch"; } +/** + * Determines version bumps for each changed package based on conventional commits. + * + * @param changedPackages - Array of packages that have changes + * @param projectRoot - Root directory of the project + * @returns Array of package bump information + */ export async function determinePackageBumps( changedPackages: PackageInfo[], ): Promise { @@ -173,6 +224,13 @@ export async function determinePackageBumps( return results.filter((info): info is PackageBumpInfo => info !== null); } +/** + * Generates a changelog based on conventional commits. + * + * @param monorepoInfo - Monorepo information + * @param pkgInfo - Optional package bump info for package-specific changelog + * @returns Promise resolving to the generated changelog content + */ export function generateChangelog( monorepoInfo: MonorepoInfo, pkgInfo?: PackageBumpInfo, @@ -195,6 +253,12 @@ export function generateChangelog( }); } +/** + * Updates the version field in a package's package.json file. + * + * @param pkgInfo - Package bump information containing the new version + * @returns Promise that resolves when the file is updated + */ export async function updatePackageVersion( pkgInfo: PackageBumpInfo, ): Promise { @@ -206,6 +270,13 @@ export async function updatePackageVersion( } } +/** + * Updates all package versions to a single version (locked strategy). + * + * @param monorepoInfo - Monorepo information + * @param newVersion - New version to apply to all packages + * @returns Promise that resolves when all versions are updated + */ export async function updateAllVersions( monorepoInfo: MonorepoInfo, newVersion: string, @@ -225,6 +296,13 @@ export async function updateAllVersions( ); } +/** + * Commits release changes and creates git tags for released packages. + * + * @param packages - Array of package bump information + * @param projectRoot - Root directory of the project + * @returns Promise that resolves when commit and tags are created + */ export async function performReleaseCommit( packages: PackageBumpInfo[], projectRoot: string, diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 7ec3ef6a..bea69a4d 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -31,6 +31,7 @@ export interface StackCodeConfig { defaultAuthor?: string; defaultLicense?: string; defaultDescription?: string; + stack?: SupportedStack | string; features?: { commitValidation?: boolean; husky?: boolean; diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index e1299a5d..bd9214b9 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -196,6 +196,7 @@ export async function loadStackCodeConfig( return JSON.parse(configContent) as StackCodeConfig; } catch { return { + stack: undefined, features: { commitValidation: false, husky: false, diff --git a/packages/core/src/workflows.ts b/packages/core/src/workflows.ts new file mode 100644 index 00000000..02388705 --- /dev/null +++ b/packages/core/src/workflows.ts @@ -0,0 +1,98 @@ +/** + * @deprecated This file is maintained for backward compatibility. + * Please import from '@stackcode/core/workflows' instead. + * + * This module will be removed in a future major version. + * + * Migration guide: + * - Import from './workflows/index' or './workflows/' + * - All types and functions are re-exported from the new location + */ + +// Re-export all workflow modules +export { + runInitWorkflow, + type InitFeature, + type InitWorkflowStep, + type InitWorkflowOptions, + type InitWorkflowProgress, + type InitWorkflowDependencyDecision, + type InitWorkflowHooks, + type InitWorkflowResult, +} from "./workflows/init.js"; + +export { + runGenerateWorkflow, + type GenerateFileType, + type GenerateWorkflowStep, + type GenerateWorkflowOptions, + type GenerateWorkflowProgress, + type GenerateWorkflowHooks, + type GenerateWorkflowResult, + type GenerateWorkflowFileResult, + type GenerateWorkflowFileStatus, + type GenerateWorkflowFileSkipReason, +} from "./workflows/generate.js"; + +export { + runValidateWorkflow, + type ValidateWorkflowOptions, + type ValidateWorkflowProgress, + type ValidateWorkflowStep, + type ValidateWorkflowHooks, + type ValidateWorkflowResult, + runProjectValidateWorkflow, + type ProjectValidateOptions, + type ProjectValidateProgress, + type ProjectValidateStep, + type ProjectValidateIssue, + type ProjectValidateResult, + type ProjectValidateSeverity, +} from "./workflows/validate.js"; + +export { + runCommitWorkflow, + type CommitWorkflowOptions, + type CommitWorkflowProgress, + type CommitWorkflowStep, + type CommitWorkflowHooks, + type CommitWorkflowResult, + runGitStartWorkflow, + type GitStartWorkflowOptions, + type GitStartWorkflowProgress, + type GitStartWorkflowStep, + type GitStartWorkflowHooks, + type GitStartWorkflowResult, + runGitFinishWorkflow, + type GitFinishWorkflowOptions, + type GitFinishWorkflowProgress, + type GitFinishWorkflowStep, + type GitFinishWorkflowHooks, + type GitFinishWorkflowResult, +} from "./workflows/git.js"; + +export { + runReleaseWorkflow, + type ReleaseWorkflowOptions, + type ReleaseWorkflowHooks, + type ReleaseWorkflowProgress, + type ReleaseWorkflowStep, + type ReleaseWorkflowResult, + type ReleaseWorkflowGitHubInfo, +} from "./workflows/release.js"; + +export { + runIssuesWorkflow, + clearIssuesCache, + clearExpiredIssuesCache, + clearRepositoryCache, + getIssuesCacheSize, + getIssuesCacheStats, + type IssuesWorkflowRepository, + type IssuesWorkflowOptions, + type IssuesWorkflowResult, + type IssuesWorkflowStep, + type IssuesWorkflowProgress, + type IssuesWorkflowHooks, + type IssuesCacheStats, +} from "./workflows/issues.js"; diff --git a/packages/core/src/workflows/generate.ts b/packages/core/src/workflows/generate.ts new file mode 100644 index 00000000..56d0c9fc --- /dev/null +++ b/packages/core/src/workflows/generate.ts @@ -0,0 +1,202 @@ +import fs from "fs/promises"; +import path from "path"; +import { + generateGitignoreContent, + generateReadmeContent, +} from "../generators.js"; +import { loadStackCodeConfig } from "../utils.js"; + +export type GenerateFileType = "readme" | "gitignore"; + +export type GenerateWorkflowStep = + | "checkingFile" + | "generatingContent" + | "writingFile" + | "completed"; + +export interface GenerateWorkflowProgress { + step: GenerateWorkflowStep; + fileType?: GenerateFileType; + filePath?: string; +} + +export interface GenerateWorkflowOptions { + projectPath: string; + files: GenerateFileType[]; + gitignoreTechnologies?: string[]; +} + +export interface GenerateWorkflowHooks { + onProgress?(progress: GenerateWorkflowProgress): Promise | void; + onEducationalMessage?(messageKey: string): Promise | void; + shouldOverwriteFile?(details: { + fileType: GenerateFileType; + filePath: string; + }): Promise | boolean; + resolveGitignoreTechnologies?(details: { + projectPath: string; + }): Promise | string[] | undefined; +} + +export type GenerateWorkflowFileStatus = "created" | "overwritten" | "skipped"; + +export type GenerateWorkflowFileSkipReason = "overwrite-declined" | "error"; + +export interface GenerateWorkflowFileResult { + fileType: GenerateFileType; + filePath: string; + status: GenerateWorkflowFileStatus; + reason?: GenerateWorkflowFileSkipReason; + error?: string; +} + +export interface GenerateWorkflowResult { + status: "completed" | "cancelled"; + files: GenerateWorkflowFileResult[]; + warnings: string[]; +} + +/** + * Generates project configuration files (README.md, .gitignore). + * + * Checks for existing files, prompts for overwrite confirmation, generates content + * based on project stack/technologies, and writes files to the project directory. + * + * @param options - Target files and project configuration + * @param hooks - UI callbacks for progress and user confirmations + * @returns Result object with generated file statuses and warnings + */ +export async function runGenerateWorkflow( + options: GenerateWorkflowOptions, + hooks: GenerateWorkflowHooks = {}, +): Promise { + const reportProgress = async ( + step: GenerateWorkflowStep, + fileType?: GenerateFileType, + filePath?: string, + ): Promise => { + if (hooks.onProgress) { + await hooks.onProgress({ step, fileType, filePath }); + } + }; + + const sendEducationalMessage = async (messageKey: string): Promise => { + if (hooks.onEducationalMessage) { + await hooks.onEducationalMessage(messageKey); + } + }; + + const requestedFiles = Array.from(new Set(options.files)); + const warnings: string[] = []; + const results: GenerateWorkflowFileResult[] = []; + + if (requestedFiles.length === 0) { + return { status: "cancelled", files: results, warnings }; + } + + for (const fileType of requestedFiles) { + const filePath = path.join( + options.projectPath, + fileType === "readme" ? "README.md" : ".gitignore", + ); + + await reportProgress("checkingFile", fileType, filePath); + + let fileExists = false; + try { + await fs.access(filePath); + fileExists = true; + } catch { + fileExists = false; + } + + if (fileExists) { + const shouldOverwrite = hooks.shouldOverwriteFile + ? await hooks.shouldOverwriteFile({ fileType, filePath }) + : false; + + if (!shouldOverwrite) { + results.push({ + fileType, + filePath, + status: "skipped", + reason: "overwrite-declined", + }); + continue; + } + } + + await reportProgress("generatingContent", fileType, filePath); + + try { + let content: string; + + if (fileType === "readme") { + await sendEducationalMessage("educational.readme_explanation"); + content = await generateReadmeContent(); + } else { + await sendEducationalMessage("educational.gitignore_explanation"); + + let technologies = options.gitignoreTechnologies; + + if (!technologies || technologies.length === 0) { + const resolved = hooks.resolveGitignoreTechnologies + ? await hooks.resolveGitignoreTechnologies({ + projectPath: options.projectPath, + }) + : undefined; + if (resolved && resolved.length > 0) { + technologies = resolved; + } + } + + if (!technologies || technologies.length === 0) { + const config = await loadStackCodeConfig(options.projectPath); + const inferredStack = (config as { stack?: string }).stack; + if (inferredStack) { + technologies = [inferredStack]; + } + } + + if (!technologies || technologies.length === 0) { + warnings.push("generate.warning.gitignore_default"); + technologies = ["node-ts"]; + } + + content = await generateGitignoreContent(technologies); + } + + await reportProgress("writingFile", fileType, filePath); + await fs.writeFile(filePath, content); + + results.push({ + fileType, + filePath, + status: fileExists ? "overwritten" : "created", + }); + } catch (error) { + const message = + error instanceof Error ? error.message : String(error ?? "error"); + warnings.push(message); + results.push({ + fileType, + filePath, + status: "skipped", + reason: "error", + error: message, + }); + } + } + + await reportProgress("completed"); + + const hasSuccessfulFile = results.some( + (result) => result.status === "created" || result.status === "overwritten", + ); + + return { + status: hasSuccessfulFile ? "completed" : "cancelled", + files: results, + warnings, + }; +} diff --git a/packages/core/src/workflows/git.ts b/packages/core/src/workflows/git.ts new file mode 100644 index 00000000..907d1d88 --- /dev/null +++ b/packages/core/src/workflows/git.ts @@ -0,0 +1,242 @@ +import { runCommand, getCommandOutput } from "../utils.js"; + +export type CommitWorkflowStep = + | "checkingStaged" + | "buildingMessage" + | "committing" + | "completed"; + +export interface CommitWorkflowProgress { + step: CommitWorkflowStep; + message?: string; +} + +/** + * Configuration options for the commit workflow. + */ +export interface CommitWorkflowOptions { + cwd: string; + type: string; + scope?: string; + shortDescription: string; + longDescription?: string; + breakingChanges?: string; + affectedIssues?: string; +} + +/** + * Hooks for progress reporting during the commit workflow. + */ +export interface CommitWorkflowHooks { + onProgress?(progress: CommitWorkflowProgress): Promise | void; +} + +/** + * Result of the commit workflow execution. + */ +export interface CommitWorkflowResult { + status: "committed" | "cancelled"; + reason?: "no-staged-changes" | "error"; + message?: string; + error?: string; +} + +/** + * Executes the commit workflow with the provided options. + * Creates a conventional commit with the given type, scope, description, body, and footers. + * + * @param options - Configuration options for the commit workflow + * @param hooks - Optional hooks for progress reporting + * @returns Promise resolving to the workflow result + */ +export async function runCommitWorkflow( + options: CommitWorkflowOptions, + hooks: CommitWorkflowHooks = {}, +): Promise { + const report = async (p: CommitWorkflowProgress) => + hooks.onProgress ? hooks.onProgress(p) : undefined; + + try { + await report({ step: "checkingStaged" }); + const status = await getCommandOutput("git", ["status", "--porcelain"], { + cwd: options.cwd, + }); + if (!status) { + return { status: "cancelled", reason: "no-staged-changes" }; + } + + await report({ step: "buildingMessage" }); + let msg = `${options.type}`; + if (options.scope) msg += `(${options.scope.trim()})`; + msg += `: ${options.shortDescription.trim()}`; + + if (options.longDescription) { + const body = options.longDescription.replace(/\|/g, "\n"); + msg += `\n\n${body}`; + } + if (options.breakingChanges) { + msg += `\n\nBREAKING CHANGE: ${options.breakingChanges.trim()}`; + } + if (options.affectedIssues) { + msg += `\n\n${options.affectedIssues.trim()}`; + } + + await report({ step: "committing", message: msg }); + await runCommand("git", ["commit", "-m", msg], { cwd: options.cwd }); + + await report({ step: "completed" }); + return { status: "committed", message: msg }; + } catch (e) { + const err = e instanceof Error ? e.message : String(e ?? "error"); + return { status: "cancelled", reason: "error", error: err }; + } +} + +export type GitStartWorkflowStep = + | "switchingBase" + | "pullingBase" + | "creatingBranch" + | "completed"; + +export interface GitStartWorkflowProgress { + step: GitStartWorkflowStep; + message?: string; +} + +/** + * Configuration options for starting a Git workflow. + */ +export interface GitStartWorkflowOptions { + cwd: string; + branchName: string; + branchType: string; + baseBranch?: string; +} + +/** + * Hooks for progress reporting during the Git start workflow. + */ +export interface GitStartWorkflowHooks { + onProgress?(progress: GitStartWorkflowProgress): Promise | void; +} + +/** + * Result of the Git start workflow execution. + */ +export interface GitStartWorkflowResult { + status: "created" | "cancelled"; + fullBranchName?: string; + error?: string; +} + +/** + * Starts a new Git branch workflow (feature, bugfix, hotfix, release). + * Creates and switches to a new branch with the specified name and type. + * + * @param options - Configuration options for starting the Git workflow + * @param hooks - Optional hooks for progress reporting + * @returns Promise resolving to the workflow result + */ +export async function runGitStartWorkflow( + options: GitStartWorkflowOptions, + hooks: GitStartWorkflowHooks = {}, +): Promise { + const report = async (p: GitStartWorkflowProgress) => + hooks.onProgress ? hooks.onProgress(p) : undefined; + + const base = options.baseBranch || "develop"; + const full = `${options.branchType}/${options.branchName}`; + try { + await report({ step: "switchingBase", message: base }); + await runCommand("git", ["checkout", base], { cwd: options.cwd }); + await report({ step: "pullingBase", message: base }); + await runCommand("git", ["pull", "origin", base], { cwd: options.cwd }); + await report({ step: "creatingBranch", message: full }); + await runCommand("git", ["checkout", "-b", full], { cwd: options.cwd }); + await report({ step: "completed" }); + return { status: "created", fullBranchName: full }; + } catch (e) { + const err = e instanceof Error ? e.message : String(e ?? "error"); + return { status: "cancelled", error: err }; + } +} + +export type GitFinishWorkflowStep = "pushing" | "computingPrUrl" | "completed"; + +export interface GitFinishWorkflowProgress { + step: GitFinishWorkflowStep; + message?: string; +} + +/** + * Configuration options for finishing a Git workflow. + */ +export interface GitFinishWorkflowOptions { + cwd: string; +} + +/** + * Hooks for progress reporting during the Git finish workflow. + */ +export interface GitFinishWorkflowHooks { + onProgress?(progress: GitFinishWorkflowProgress): Promise | void; +} + +/** + * Result of the Git finish workflow execution. + */ +export interface GitFinishWorkflowResult { + status: "pushed" | "cancelled"; + branch?: string; + prUrl?: string; + error?: string; +} + +/** + * Finishes a Git branch workflow by pushing the branch and computing a PR URL. + * Typically used for feature, bugfix, or hotfix branches. + * + * @param options - Configuration options for finishing the Git workflow + * @param hooks - Optional hooks for progress reporting + * @returns Promise resolving to the workflow result + */ +export async function runGitFinishWorkflow( + options: GitFinishWorkflowOptions, + hooks: GitFinishWorkflowHooks = {}, +): Promise { + const report = async (p: GitFinishWorkflowProgress) => + hooks.onProgress ? hooks.onProgress(p) : undefined; + + try { + const currentBranch = await getCommandOutput( + "git", + ["branch", "--show-current"], + { cwd: options.cwd }, + ); + if (!currentBranch) { + return { status: "cancelled", error: "not-on-branch" }; + } + await report({ step: "pushing", message: currentBranch }); + await runCommand( + "git", + ["push", "--set-upstream", "origin", currentBranch], + { cwd: options.cwd }, + ); + await report({ step: "computingPrUrl" }); + const remoteUrl = await getCommandOutput( + "git", + ["remote", "get-url", "origin"], + { cwd: options.cwd }, + ); + const match = remoteUrl.match(/github\.com[/:]([\w-]+\/[\w-.]+)/); + const repoPath = match ? match[1].replace(".git", "") : null; + const prUrl = repoPath + ? `https://github.com/${repoPath}/pull/new/${currentBranch}` + : undefined; + await report({ step: "completed" }); + return { status: "pushed", branch: currentBranch, prUrl }; + } catch (e) { + const err = e instanceof Error ? e.message : String(e ?? "error"); + return { status: "cancelled", error: err }; + } +} diff --git a/packages/core/src/workflows/index.ts b/packages/core/src/workflows/index.ts new file mode 100644 index 00000000..befe3a56 --- /dev/null +++ b/packages/core/src/workflows/index.ts @@ -0,0 +1,20 @@ +/** + * Workflows Module - Orchestration Layer + * + * This module provides high-level workflow orchestration for various StackCode operations. + * Each workflow coordinates multiple core operations and provides progress hooks for UI integration. + * + * Workflows are organized by domain: + * - init: Project initialization and scaffolding + * - generate: File generation (README, .gitignore) + * - validate: Commit message and project validation + * - git: Git operations (commit, branch management) + * - release: Version management and changelog generation + */ + +export * from "./init.js"; +export * from "./generate.js"; +export * from "./validate.js"; +export * from "./git.js"; +export * from "./release.js"; +export * from "./issues.js"; diff --git a/packages/core/src/workflows/init.ts b/packages/core/src/workflows/init.ts new file mode 100644 index 00000000..76f440a1 --- /dev/null +++ b/packages/core/src/workflows/init.ts @@ -0,0 +1,241 @@ +import fs from "fs/promises"; +import path from "path"; +import { scaffoldProject, setupHusky } from "../scaffold.js"; +import { + generateGitignoreContent, + generateReadmeContent, +} from "../generators.js"; +import { + runCommand, + saveStackCodeConfig, + validateStackDependencies, +} from "../utils.js"; +import type { + ProjectOptions, + StackCodeConfig, + SupportedStack, +} from "../types.js"; + +export type InitFeature = "docker" | "husky"; + +export type InitWorkflowStep = + | "scaffold" + | "saveConfig" + | "generateReadme" + | "generateGitignore" + | "setupHusky" + | "initializeGit" + | "validateDependencies" + | "installDependencies" + | "completed"; + +export interface InitWorkflowOptions { + projectPath: string; + projectName: string; + description: string; + authorName: string; + stack: SupportedStack; + features: InitFeature[]; + commitValidation?: boolean; +} + +export interface InitWorkflowProgress { + step: InitWorkflowStep; + message?: string; + data?: Record; +} + +export interface InitWorkflowDependencyDecision { + stack: SupportedStack; + missingDependencies: string[]; +} + +export interface InitWorkflowHooks { + onProgress?(progress: InitWorkflowProgress): Promise | void; + onEducationalMessage?(messageKey: string): Promise | void; + onMissingDependencies?( + details: InitWorkflowDependencyDecision, + ): Promise | void; + confirmContinueAfterMissingDependencies?( + decision: InitWorkflowDependencyDecision, + ): Promise | boolean; +} + +export interface InitWorkflowResult { + status: "completed" | "cancelled"; + projectPath: string; + dependencyValidation: Awaited>; + dependenciesInstalled: boolean; + installCommand?: { + command: string; + args: string[]; + }; + warnings: string[]; +} + +interface InstallCommand { + command: string; + args: string[]; +} + +const STACK_INSTALL_COMMANDS: Record = { + "node-js": { command: "npm", args: ["install"] }, + "node-ts": { command: "npm", args: ["install"] }, + react: { command: "npm", args: ["install"] }, + vue: { command: "npm", args: ["install"] }, + angular: { command: "npm", args: ["install"] }, + svelte: { command: "npm", args: ["install"] }, + python: { command: "pip", args: ["install", "-e", "."] }, + java: { command: "mvn", args: ["install"] }, + go: { command: "go", args: ["mod", "tidy"] }, + php: { command: "composer", args: ["install"] }, +}; + +/** + * Orchestrates the complete project initialization workflow. + * + * Creates project structure, generates configuration files, sets up version control, + * validates stack dependencies, and optionally installs project dependencies. + * + * @param options - Project configuration including path, stack, and features + * @param hooks - UI callbacks for progress reporting and user confirmations + * @returns Result object with status, warnings, and dependency information + */ +export async function runInitWorkflow( + options: InitWorkflowOptions, + hooks: InitWorkflowHooks = {}, +): Promise { + const reportProgress = async ( + step: InitWorkflowStep, + data?: Record, + ): Promise => { + if (hooks.onProgress) { + await hooks.onProgress({ step, data }); + } + }; + + const sendEducationalMessage = async (messageKey: string): Promise => { + if (hooks.onEducationalMessage) { + await hooks.onEducationalMessage(messageKey); + } + }; + + const projectOptions: ProjectOptions = { + projectPath: options.projectPath, + stack: options.stack, + features: options.features, + replacements: { + projectName: options.projectName, + description: options.description, + authorName: options.authorName, + }, + }; + + await reportProgress("scaffold"); + await sendEducationalMessage("educational.scaffold_explanation"); + await scaffoldProject(projectOptions); + + if ( + options.features.includes("husky") && + typeof options.commitValidation !== "undefined" + ) { + await reportProgress("saveConfig"); + const config: StackCodeConfig = { + defaultAuthor: options.authorName, + defaultLicense: "MIT", + defaultDescription: options.description, + stack: options.stack, + features: { + commitValidation: options.commitValidation, + husky: options.features.includes("husky"), + docker: options.features.includes("docker"), + }, + }; + await saveStackCodeConfig(options.projectPath, config); + } + + await reportProgress("generateReadme"); + await sendEducationalMessage("educational.readme_explanation"); + const readmeContent = await generateReadmeContent(); + await fs.writeFile( + path.join(options.projectPath, "README.md"), + readmeContent, + ); + + await reportProgress("generateGitignore"); + await sendEducationalMessage("educational.gitignore_explanation"); + const gitignoreContent = await generateGitignoreContent([options.stack]); + await fs.writeFile( + path.join(options.projectPath, ".gitignore"), + gitignoreContent, + ); + + if (options.features.includes("husky")) { + await reportProgress("setupHusky"); + await sendEducationalMessage("educational.husky_explanation"); + await setupHusky(options.projectPath); + } + + await reportProgress("initializeGit"); + await sendEducationalMessage("educational.git_init_explanation"); + await runCommand("git", ["init"], { cwd: options.projectPath }); + + await reportProgress("validateDependencies"); + await sendEducationalMessage("educational.dependency_validation_explanation"); + const dependencyValidation = await validateStackDependencies(options.stack); + + let shouldContinue = true; + const warnings: string[] = []; + let dependenciesInstalled = false; + + if (!dependencyValidation.isValid) { + const decision: InitWorkflowDependencyDecision = { + stack: options.stack, + missingDependencies: dependencyValidation.missingDependencies, + }; + + if (hooks.onMissingDependencies) { + await hooks.onMissingDependencies(decision); + } + + if (hooks.confirmContinueAfterMissingDependencies) { + shouldContinue = + await hooks.confirmContinueAfterMissingDependencies(decision); + } + + if (!shouldContinue) { + return { + status: "cancelled", + projectPath: options.projectPath, + dependencyValidation, + dependenciesInstalled: false, + warnings, + }; + } + } + + await reportProgress("installDependencies"); + const installCommand = STACK_INSTALL_COMMANDS[options.stack]; + + try { + await runCommand(installCommand.command, installCommand.args, { + cwd: options.projectPath, + }); + dependenciesInstalled = true; + } catch (error) { + warnings.push( + error instanceof Error ? error.message : String(error ?? "Unknown error"), + ); + } + + await reportProgress("completed"); + + return { + status: "completed", + projectPath: options.projectPath, + dependencyValidation, + dependenciesInstalled, + installCommand, + warnings, + }; +} diff --git a/packages/core/src/workflows/issues.ts b/packages/core/src/workflows/issues.ts new file mode 100644 index 00000000..e21abf24 --- /dev/null +++ b/packages/core/src/workflows/issues.ts @@ -0,0 +1,308 @@ +import type { Octokit } from "@octokit/rest"; +import { + fetchRepositoryIssues, + type GitHubIssue, + type FetchIssuesOptions, +} from "../github.js"; + +/** + * Repository information required for issues workflow + */ +export interface IssuesWorkflowRepository { + owner: string; + repo: string; + fullName?: string; +} + +/** + * Options for running the issues workflow + */ +export interface IssuesWorkflowOptions { + /** Authenticated Octokit client */ + client: Octokit; + /** Repository to fetch issues from */ + repository: IssuesWorkflowRepository; + /** Optional fetch options (state, assignee, labels, etc.) */ + fetchOptions?: Partial; + /** Whether to enable caching (default: true) */ + enableCache?: boolean; + /** Cache TTL in milliseconds (default: 5 minutes) */ + cacheTTL?: number; +} + +/** + * Result of the issues workflow + */ +export interface IssuesWorkflowResult { + status: "success" | "error"; + issues: GitHubIssue[]; + cached: boolean; + error?: string; + timestamp: string; +} + +/** + * Progress step types for issues workflow + */ +export type IssuesWorkflowStep = "fetching" | "caching" | "completed" | "error"; + +/** + * Progress information for issues workflow + */ +export interface IssuesWorkflowProgress { + step: IssuesWorkflowStep; + message?: string; +} + +/** + * Hooks for issues workflow callbacks + */ +export interface IssuesWorkflowHooks { + onProgress?(progress: IssuesWorkflowProgress): Promise | void; +} + +/** + * Global cache for issues + * Key format: "owner/repo:optionsJson" + */ +const issuesCache = new Map< + string, + { issues: GitHubIssue[]; timestamp: number } +>(); + +/** + * Default cache TTL: 5 minutes + */ +const DEFAULT_CACHE_TTL = 5 * 60 * 1000; + +/** + * Runs the issues workflow to fetch GitHub issues for a repository. + * + * This is the centralized business logic for fetching issues that can be used + * by both CLI and VS Code extension, ensuring consistent behavior across interfaces. + * + * @param options - Workflow options including client, repository, and fetch options + * @param hooks - Optional callbacks for progress reporting + * @returns Promise with workflow result containing issues and metadata + * + * @example + * ```typescript + * const result = await runIssuesWorkflow({ + * client: authenticatedOctokit, + * repository: { owner: "user", repo: "project" }, + * fetchOptions: { state: "open", assignee: "username" } + * }); + * + * if (result.status === "success") { + * console.log(`Found ${result.issues.length} issues`); + * } + * ``` + */ +export async function runIssuesWorkflow( + options: IssuesWorkflowOptions, + hooks?: IssuesWorkflowHooks, +): Promise { + const { + client, + repository, + fetchOptions = {}, + enableCache = true, + cacheTTL = DEFAULT_CACHE_TTL, + } = options; + + const timestamp = new Date().toISOString(); + + try { + // Report fetching progress + await hooks?.onProgress?.({ + step: "fetching", + message: "Fetching issues from GitHub...", + }); + + // Generate cache key + const cacheKey = generateCacheKey(repository, fetchOptions); + + // Check cache if enabled + if (enableCache) { + const cached = issuesCache.get(cacheKey); + if (cached && Date.now() - cached.timestamp < cacheTTL) { + console.log( + `[Core] Returning cached issues for ${repository.owner}/${repository.repo}`, + ); + await hooks?.onProgress?.({ + step: "completed", + message: "Returned cached issues", + }); + + return { + status: "success", + issues: cached.issues, + cached: true, + timestamp, + }; + } + } + + // Build full fetch options + const fullFetchOptions: FetchIssuesOptions = { + owner: repository.owner, + repo: repository.repo, + state: "open", + sort: "updated", + direction: "desc", + per_page: 30, + ...fetchOptions, + }; + + // Fetch issues from GitHub + console.log( + `[Core] Fetching issues for ${repository.owner}/${repository.repo}...`, + ); + const issues = await fetchRepositoryIssues(client, fullFetchOptions); + + // Cache the results if enabled + if (enableCache) { + await hooks?.onProgress?.({ + step: "caching", + message: "Caching results...", + }); + issuesCache.set(cacheKey, { + issues, + timestamp: Date.now(), + }); + } + + await hooks?.onProgress?.({ + step: "completed", + message: `Found ${issues.length} issues`, + }); + + return { + status: "success", + issues, + cached: false, + timestamp, + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : "Unknown error"; + console.error( + `[Core] Failed to fetch issues for ${repository.owner}/${repository.repo}:`, + error, + ); + + await hooks?.onProgress?.({ step: "error", message: errorMessage }); + + return { + status: "error", + issues: [], + cached: false, + error: errorMessage, + timestamp, + }; + } +} + +/** + * Clears all cached issues + */ +export function clearIssuesCache(): void { + issuesCache.clear(); + console.log("[Core] Issues cache cleared"); +} + +/** + * Clears expired cache entries + * + * @param cacheTTL - Time to live in milliseconds (default: 5 minutes) + */ +export function clearExpiredIssuesCache(cacheTTL = DEFAULT_CACHE_TTL): void { + const now = Date.now(); + let clearedCount = 0; + + for (const [key, cache] of issuesCache.entries()) { + if (now - cache.timestamp >= cacheTTL) { + issuesCache.delete(key); + clearedCount++; + } + } + + if (clearedCount > 0) { + console.log(`[Core] Cleared ${clearedCount} expired cache entries`); + } +} + +/** + * Clears cache for a specific repository + * + * @param repository - Repository to clear cache for + */ +export function clearRepositoryCache( + repository: IssuesWorkflowRepository, +): void { + const fullName = + repository.fullName || `${repository.owner}/${repository.repo}`; + let clearedCount = 0; + + const keysToDelete = Array.from(issuesCache.keys()).filter((key) => + key.startsWith(fullName), + ); + + keysToDelete.forEach((key) => { + issuesCache.delete(key); + clearedCount++; + }); + + if (clearedCount > 0) { + console.log(`[Core] Cleared ${clearedCount} cache entries for ${fullName}`); + } +} + +/** + * Generates a unique cache key for a repository and fetch options + */ +function generateCacheKey( + repository: IssuesWorkflowRepository, + fetchOptions?: Partial, +): string { + const fullName = + repository.fullName || `${repository.owner}/${repository.repo}`; + const optionsStr = JSON.stringify(fetchOptions || {}); + return `${fullName}:${optionsStr}`; +} + +/** + * Gets the current cache size + */ +export function getIssuesCacheSize(): number { + return issuesCache.size; +} + +/** + * Gets cache statistics + */ +export interface IssuesCacheStats { + size: number; + entries: Array<{ + key: string; + issuesCount: number; + age: number; + }>; +} + +/** + * Gets cache statistics for debugging/monitoring + */ +export function getIssuesCacheStats(): IssuesCacheStats { + const now = Date.now(); + const entries = Array.from(issuesCache.entries()).map(([key, value]) => ({ + key, + issuesCount: value.issues.length, + age: now - value.timestamp, + })); + + return { + size: issuesCache.size, + entries, + }; +} diff --git a/packages/core/src/workflows/release.ts b/packages/core/src/workflows/release.ts new file mode 100644 index 00000000..4b818b7e --- /dev/null +++ b/packages/core/src/workflows/release.ts @@ -0,0 +1,298 @@ +import path from "path"; +import semver from "semver"; +import { + detectVersioningStrategy, + findChangedPackages, + determinePackageBumps, + getRecommendedBump, + updateAllVersions, + updatePackageVersion, + generateChangelog, + performReleaseCommit, +} from "../release.js"; +import { getCommandOutput } from "../utils.js"; +import type { VersioningStrategy, PackageBumpInfo } from "../types.js"; +import fs from "fs/promises"; + +export type ReleaseWorkflowStep = + | "detectingStrategy" + | "lockedRecommendedBump" + | "lockedUpdatingVersions" + | "lockedGeneratingChangelog" + | "independentFindingChanges" + | "independentDeterminingBumps" + | "independentPreparingPlan" + | "independentUpdatingPackages" + | "independentCommitting" + | "completed"; + +export interface ReleaseWorkflowProgress { + step: ReleaseWorkflowStep; + message?: string; +} + +export interface ReleaseWorkflowOptions { + cwd: string; + changelogFilename?: string; + gitRemote?: string; + autoCommitIndependent?: boolean; +} + +export interface ReleaseWorkflowHooks { + onProgress?(progress: ReleaseWorkflowProgress): Promise | void; + confirmLockedRelease?(details: { + currentVersion: string; + newVersion: string; + }): Promise | boolean; + displayIndependentPlan?(plan: PackageBumpInfo[]): Promise | void; + confirmIndependentRelease?( + plan: PackageBumpInfo[], + ): Promise | boolean; +} + +export interface ReleaseWorkflowGitHubInfo { + owner: string; + repo: string; + remoteUrl: string; +} + +export interface ReleaseWorkflowResult { + status: "prepared" | "cancelled"; + strategy: VersioningStrategy; + reason?: + | "invalid-structure" + | "cancelled-by-user" + | "no-changes" + | "no-bumps" + | "error"; + error?: string; + newVersion?: string; + packages?: PackageBumpInfo[]; + releaseNotes?: string; + tagName?: string; + github?: ReleaseWorkflowGitHubInfo; + warnings?: string[]; +} + +async function resolveGitHubInfo( + cwd: string, + remote: string, +): Promise { + try { + const remoteUrl = await getCommandOutput( + "git", + ["remote", "get-url", remote], + { cwd }, + ); + const match = remoteUrl.match(/github\.com[/:]([\w-]+\/[\w-.]+)/); + if (!match) { + return undefined; + } + const [owner, repoWithSuffix] = match[1].split("/"); + const repo = repoWithSuffix.replace(/\.git$/, ""); + return { owner, repo, remoteUrl }; + } catch { + return undefined; + } +} + +/** + * Orchestrates the complete release workflow for monorepos. + * + * Supports both locked (synchronized) and independent versioning strategies. + * For locked releases, bumps all packages to the same version. For independent + * releases, analyzes changed packages and determines individual version bumps. + * Generates changelogs, updates version files, and prepares release metadata. + * + * @param options - Release configuration including working directory and options + * @param hooks - UI callbacks for confirmations, progress, and plan display + * @returns Result with release metadata, version bumps, and GitHub information + */ +export async function runReleaseWorkflow( + options: ReleaseWorkflowOptions, + hooks: ReleaseWorkflowHooks = {}, +): Promise { + const report = async (progress: ReleaseWorkflowProgress) => { + if (hooks.onProgress) { + await hooks.onProgress(progress); + } + }; + + const changelogFilename = options.changelogFilename ?? "CHANGELOG.md"; + const gitRemote = options.gitRemote ?? "origin"; + const autoCommitIndependent = + typeof options.autoCommitIndependent === "boolean" + ? options.autoCommitIndependent + : true; + + let currentStrategy: VersioningStrategy = "unknown"; + + try { + await report({ step: "detectingStrategy" }); + const monorepoInfo = await detectVersioningStrategy(options.cwd); + currentStrategy = monorepoInfo.strategy; + + if (monorepoInfo.strategy === "unknown") { + return { + status: "cancelled", + strategy: monorepoInfo.strategy, + reason: "invalid-structure", + }; + } + + if (monorepoInfo.strategy === "locked") { + await report({ step: "lockedRecommendedBump" }); + const bumpType = await getRecommendedBump(monorepoInfo.rootDir); + const currentVersion = monorepoInfo.rootVersion ?? "0.0.0"; + const nextVersion = semver.inc( + currentVersion, + bumpType as semver.ReleaseType, + ); + if (!nextVersion) { + return { + status: "cancelled", + strategy: "locked", + reason: "error", + error: "Unable to calculate next version", + }; + } + + let shouldProceed = true; + if (hooks.confirmLockedRelease) { + shouldProceed = await hooks.confirmLockedRelease({ + currentVersion, + newVersion: nextVersion, + }); + } + + if (!shouldProceed) { + return { + status: "cancelled", + strategy: "locked", + reason: "cancelled-by-user", + newVersion: nextVersion, + }; + } + + await report({ step: "lockedUpdatingVersions" }); + await updateAllVersions(monorepoInfo, nextVersion); + + await report({ step: "lockedGeneratingChangelog" }); + const changelog = await generateChangelog(monorepoInfo); + const changelogPath = path.join(options.cwd, changelogFilename); + const existing = await fs + .readFile(changelogPath, "utf-8") + .catch(() => ""); + await fs.writeFile( + changelogPath, + existing ? `${changelog}\n${existing}` : `${changelog}\n`, + ); + + const github = await resolveGitHubInfo(options.cwd, gitRemote); + + await report({ step: "completed" }); + return { + status: "prepared", + strategy: "locked", + newVersion: nextVersion, + releaseNotes: changelog, + tagName: `v${nextVersion}`, + github, + }; + } + + await report({ step: "independentFindingChanges" }); + const changedPackages = await findChangedPackages( + monorepoInfo.packages, + monorepoInfo.rootDir, + ); + + if (changedPackages.length === 0) { + return { + status: "cancelled", + strategy: "independent", + reason: "no-changes", + }; + } + + await report({ step: "independentDeterminingBumps" }); + const packagesToUpdate = await determinePackageBumps(changedPackages); + + if (packagesToUpdate.length === 0) { + return { + status: "cancelled", + strategy: "independent", + reason: "no-bumps", + }; + } + + await report({ step: "independentPreparingPlan" }); + if (hooks.displayIndependentPlan) { + await hooks.displayIndependentPlan(packagesToUpdate); + } + + let shouldContinue = true; + if (hooks.confirmIndependentRelease) { + shouldContinue = await hooks.confirmIndependentRelease(packagesToUpdate); + } + + if (!shouldContinue) { + return { + status: "cancelled", + strategy: "independent", + reason: "cancelled-by-user", + packages: packagesToUpdate, + }; + } + + await report({ step: "independentUpdatingPackages" }); + const combinedNotes: string[] = []; + for (const pkgInfo of packagesToUpdate) { + await updatePackageVersion(pkgInfo); + const changelogContent = await generateChangelog(monorepoInfo, pkgInfo); + const changelogPath = path.join(pkgInfo.pkg.path, "CHANGELOG.md"); + const existing = await fs + .readFile(changelogPath, "utf-8") + .catch(() => ""); + await fs.writeFile( + changelogPath, + existing ? `${changelogContent}\n${existing}` : `${changelogContent}\n`, + ); + combinedNotes.push( + `### 🎉 Release for ${pkgInfo.pkg.name}@${pkgInfo.newVersion}\n\n${changelogContent}`, + ); + } + + if (autoCommitIndependent) { + await report({ step: "independentCommitting" }); + await performReleaseCommit(packagesToUpdate, monorepoInfo.rootDir); + } + + const releaseNotes = combinedNotes.join("\n\n"); + const primaryPackage = + packagesToUpdate.find((p) => p.pkg.name === "@stackcode/cli") || + packagesToUpdate[0]; + const shortName = + primaryPackage.pkg.name.split("/")[1] || primaryPackage.pkg.name; + const tagName = `${shortName}@${primaryPackage.newVersion}`; + const github = await resolveGitHubInfo(options.cwd, gitRemote); + + await report({ step: "completed" }); + return { + status: "prepared", + strategy: "independent", + packages: packagesToUpdate, + releaseNotes, + tagName, + github, + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { + status: "cancelled", + strategy: currentStrategy, + reason: "error", + error: message, + }; + } +} diff --git a/packages/core/src/workflows/validate.ts b/packages/core/src/workflows/validate.ts new file mode 100644 index 00000000..f427cdf2 --- /dev/null +++ b/packages/core/src/workflows/validate.ts @@ -0,0 +1,290 @@ +import fs from "fs/promises"; +import path from "path"; +import { validateCommitMessage } from "../validator.js"; +import { loadStackCodeConfig } from "../utils.js"; + +export type ValidateWorkflowStep = "validating" | "completed"; + +export interface ValidateWorkflowProgress { + step: ValidateWorkflowStep; +} + +export interface ValidateWorkflowOptions { + message: string; +} + +export interface ValidateWorkflowHooks { + onProgress?(progress: ValidateWorkflowProgress): Promise | void; +} + +export interface ValidateWorkflowResult { + isValid: boolean; +} + +/** + * Validates a commit message against Conventional Commits specification. + * + * @param options - Contains the commit message to validate + * @param hooks - Optional progress callback for UI updates + * @returns Validation result indicating spec compliance + */ +export async function runValidateWorkflow( + options: ValidateWorkflowOptions, + hooks: ValidateWorkflowHooks = {}, +): Promise { + if (hooks.onProgress) { + await hooks.onProgress({ step: "validating" }); + } + + const isValid = validateCommitMessage(options.message); + + if (hooks.onProgress) { + await hooks.onProgress({ step: "completed" }); + } + + return { isValid }; +} + +export type ProjectValidateSeverity = "info" | "warning" | "error"; + +export type ProjectValidateStep = + | "loadingConfig" + | "checkingFiles" + | "completed"; + +export interface ProjectValidateProgress { + step: ProjectValidateStep; + message?: string; +} + +export interface ProjectValidateIssue { + id: string; + messageKey: string; + severity: ProjectValidateSeverity; + filePath?: string; +} + +export interface ProjectValidateOptions { + projectPath: string; +} + +export interface ProjectValidateHooks { + onProgress?(progress: ProjectValidateProgress): Promise | void; + onEducationalMessage?(messageKey: string): Promise | void; +} + +export interface ProjectValidateResult { + status: "valid" | "invalid"; + issues: ProjectValidateIssue[]; +} + +async function fileExists(filePath: string): Promise { + try { + await fs.access(filePath); + return true; + } catch { + return false; + } +} + +/** + * Validates project structure and configuration integrity. + * + * Checks for essential files (package.json, .stackcoderc), validates configuration + * format, and reports issues with appropriate severity levels. + * + * @param options - Project path to validate + * @param hooks - UI callbacks for progress and educational messages + * @returns Validation result with discovered issues + */ +export async function runProjectValidateWorkflow( + options: ProjectValidateOptions, + hooks: ProjectValidateHooks = {}, +): Promise { + const report = async ( + step: ProjectValidateStep, + message?: string, + ): Promise => { + if (hooks.onProgress) await hooks.onProgress({ step, message }); + }; + + const issues: ProjectValidateIssue[] = []; + + await report("loadingConfig", "loading-config"); + const config = await loadStackCodeConfig(options.projectPath); + + await report("checkingFiles", "core-files"); + const readmePath = path.join(options.projectPath, "README.md"); + if (!(await fileExists(readmePath))) { + issues.push({ + id: "missing-readme", + messageKey: "validate.project.missing_readme", + severity: "warning", + filePath: readmePath, + }); + } + + const giPath = path.join(options.projectPath, ".gitignore"); + if (!(await fileExists(giPath))) { + issues.push({ + id: "missing-gitignore", + messageKey: "validate.project.missing_gitignore", + severity: "warning", + filePath: giPath, + }); + } + + const gitPath = path.join(options.projectPath, ".git"); + if (!(await fileExists(gitPath))) { + issues.push({ + id: "missing-git-init", + messageKey: "validate.project.git_not_initialized", + severity: "warning", + filePath: gitPath, + }); + } + + const nodeStacks = new Set([ + "node-js", + "node-ts", + "react", + "vue", + "angular", + "svelte", + ]); + if (config.stack && nodeStacks.has(config.stack as string)) { + const pkgPath = path.join(options.projectPath, "package.json"); + if (!(await fileExists(pkgPath))) { + issues.push({ + id: "missing-package-json", + messageKey: "validate.project.missing_package_json", + severity: "error", + filePath: pkgPath, + }); + } + const lockPathNpm = path.join(options.projectPath, "package-lock.json"); + const lockPathYarn = path.join(options.projectPath, "yarn.lock"); + const hasLock = + (await fileExists(lockPathNpm)) || (await fileExists(lockPathYarn)); + if (!hasLock) { + issues.push({ + id: "missing-lockfile", + messageKey: "validate.project.missing_lockfile", + severity: "warning", + }); + } + + if ( + config.stack === "node-ts" || + config.stack === "react" || + config.stack === "angular" || + config.stack === "vue" || + config.stack === "svelte" + ) { + const tsconfigPath = path.join(options.projectPath, "tsconfig.json"); + if (!(await fileExists(tsconfigPath))) { + issues.push({ + id: "missing-tsconfig", + messageKey: "validate.project.missing_tsconfig", + severity: "warning", + filePath: tsconfigPath, + }); + } + } + } + + if (config.stack === "python") { + const pyproject = path.join(options.projectPath, "pyproject.toml"); + const reqs = path.join(options.projectPath, "requirements.txt"); + const hasPyproject = await fileExists(pyproject); + const hasReqs = await fileExists(reqs); + if (!hasPyproject && !hasReqs) { + issues.push({ + id: "missing-pyproject-or-requirements", + messageKey: "validate.project.missing_pyproject_or_requirements", + severity: "error", + }); + } + } + + if (config.stack === "java") { + const pom = path.join(options.projectPath, "pom.xml"); + const gradle = path.join(options.projectPath, "build.gradle"); + const gradleKts = path.join(options.projectPath, "build.gradle.kts"); + const hasBuild = + (await fileExists(pom)) || + (await fileExists(gradle)) || + (await fileExists(gradleKts)); + if (!hasBuild) { + issues.push({ + id: "missing-java-build-file", + messageKey: "validate.project.missing_java_build_file", + severity: "error", + }); + } + } + + if (config.stack === "go") { + const goMod = path.join(options.projectPath, "go.mod"); + if (!(await fileExists(goMod))) { + issues.push({ + id: "missing-go-mod", + messageKey: "validate.project.missing_go_mod", + severity: "error", + filePath: goMod, + }); + } + } + + if (config.stack === "php") { + const composer = path.join(options.projectPath, "composer.json"); + const composerLock = path.join(options.projectPath, "composer.lock"); + if (!(await fileExists(composer))) { + issues.push({ + id: "missing-composer-json", + messageKey: "validate.project.missing_composer_json", + severity: "error", + filePath: composer, + }); + } + if (!(await fileExists(composerLock))) { + issues.push({ + id: "missing-composer-lock", + messageKey: "validate.project.missing_composer_lock", + severity: "warning", + filePath: composerLock, + }); + } + } + + if (config.features?.husky) { + const huskyDir = path.join(options.projectPath, ".husky"); + const hasHusky = await fileExists(huskyDir); + if (!hasHusky) { + issues.push({ + id: "missing-husky", + messageKey: "validate.project.missing_husky", + severity: "warning", + filePath: huskyDir, + }); + } else if (config.features?.commitValidation) { + const commitHook = path.join(huskyDir, "commit-msg"); + if (!(await fileExists(commitHook))) { + issues.push({ + id: "missing-commit-msg-hook", + messageKey: "validate.project.missing_commit_msg_hook", + severity: "warning", + filePath: commitHook, + }); + } + } + } + + await report("completed", "completed"); + + const hasError = issues.some((i) => i.severity === "error"); + return { + status: hasError ? "invalid" : "valid", + issues, + }; +} diff --git a/packages/core/test/workflows.test.ts b/packages/core/test/workflows.test.ts new file mode 100644 index 00000000..aa541609 --- /dev/null +++ b/packages/core/test/workflows.test.ts @@ -0,0 +1,619 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import fs from "fs/promises"; +import path from "path"; +import { + runInitWorkflow, + type InitWorkflowHooks, + type InitWorkflowOptions, +} from "../src/workflows/init.js"; +import { + runGenerateWorkflow, + type GenerateWorkflowHooks, + type GenerateWorkflowOptions, +} from "../src/workflows/generate.js"; +import { + runValidateWorkflow, + type ValidateWorkflowHooks, + type ValidateWorkflowOptions, +} from "../src/workflows/validate.js"; +import { + runCommitWorkflow, + runGitStartWorkflow, + runGitFinishWorkflow, +} from "../src/workflows/git.js"; +import { + runReleaseWorkflow, + type ReleaseWorkflowHooks, +} from "../src/workflows/release.js"; +import * as scaffoldModule from "../src/scaffold.js"; +import * as utilsModule from "../src/utils.js"; +import * as generatorsModule from "../src/generators.js"; +import * as releaseModule from "../src/release.js"; + +vi.mock("fs/promises"); +vi.mock("path", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + join: (...segments: string[]) => segments.join("/"), + }; +}); +vi.mock("../src/scaffold.js"); +vi.mock("../src/utils.js"); +vi.mock("../src/generators.js"); +vi.mock("../src/release.js"); + +const mockedFs = fs as unknown as { + writeFile: ReturnType; + access: ReturnType; + readFile: ReturnType; +}; + +describe("runInitWorkflow", () => { + const baseOptions: InitWorkflowOptions = { + projectPath: "/tmp/demo", + projectName: "demo", + description: "Demo project", + authorName: "Jane Doe", + stack: "node-ts", + features: ["docker", "husky"], + commitValidation: true, + }; + + const hooks: InitWorkflowHooks = { + onProgress: vi.fn(), + onEducationalMessage: vi.fn(), + onMissingDependencies: vi.fn(), + confirmContinueAfterMissingDependencies: vi.fn().mockResolvedValue(true), + }; + + beforeEach(() => { + vi.resetAllMocks(); + ( + scaffoldModule.scaffoldProject as ReturnType + ).mockResolvedValue(undefined); + (scaffoldModule.setupHusky as ReturnType).mockResolvedValue( + undefined, + ); + ( + generatorsModule.generateReadmeContent as ReturnType + ).mockResolvedValue("# Demo"); + ( + generatorsModule.generateGitignoreContent as ReturnType + ).mockResolvedValue("node_modules"); + (utilsModule.runCommand as ReturnType).mockResolvedValue( + undefined, + ); + ( + utilsModule.saveStackCodeConfig as ReturnType + ).mockResolvedValue(undefined); + ( + utilsModule.loadStackCodeConfig as ReturnType + ).mockResolvedValue({ + stack: "node-ts", + features: { + commitValidation: false, + husky: false, + docker: false, + }, + }); + ( + utilsModule.validateStackDependencies as ReturnType + ).mockResolvedValue({ + isValid: true, + missingDependencies: [], + availableDependencies: ["npm"], + }); + (mockedFs.writeFile as ReturnType).mockResolvedValue( + undefined, + ); + (mockedFs.access as ReturnType).mockRejectedValue( + new Error("not found"), + ); + (mockedFs.readFile as ReturnType).mockRejectedValue( + new Error("not found"), + ); + }); + + it("completes successfully when dependencies install", async () => { + const result = await runInitWorkflow(baseOptions, hooks); + + expect(result.status).toBe("completed"); + expect(result.dependenciesInstalled).toBe(true); + expect(result.dependencyValidation.isValid).toBe(true); + expect(scaffoldModule.scaffoldProject).toHaveBeenCalledWith( + expect.objectContaining({ projectPath: baseOptions.projectPath }), + ); + expect(utilsModule.runCommand).toHaveBeenCalledWith("npm", ["install"], { + cwd: baseOptions.projectPath, + }); + expect(mockedFs.writeFile).toHaveBeenCalledWith( + path.join(baseOptions.projectPath, "README.md"), + "# Demo", + ); + expect(mockedFs.writeFile).toHaveBeenCalledWith( + path.join(baseOptions.projectPath, ".gitignore"), + "node_modules", + ); + expect(utilsModule.saveStackCodeConfig).toHaveBeenCalledWith( + baseOptions.projectPath, + expect.objectContaining({ stack: baseOptions.stack }), + ); + }); + + it("returns cancellation when user declines after missing dependencies", async () => { + ( + utilsModule.validateStackDependencies as ReturnType + ).mockResolvedValue({ + isValid: false, + missingDependencies: ["npm"], + availableDependencies: [], + }); + ( + hooks.confirmContinueAfterMissingDependencies as ReturnType + ).mockResolvedValue(false); + + const result = await runInitWorkflow(baseOptions, hooks); + + expect(result.status).toBe("cancelled"); + expect(result.dependenciesInstalled).toBe(false); + expect(utilsModule.runCommand).not.toHaveBeenCalledWith( + "npm", + ["install"], + { + cwd: baseOptions.projectPath, + }, + ); + }); + + it("collects warnings when dependency installation fails", async () => { + (utilsModule.runCommand as ReturnType) + .mockResolvedValueOnce(undefined) // git init + .mockRejectedValueOnce(new Error("install failed")); // install command + + const result = await runInitWorkflow(baseOptions, hooks); + + expect(result.status).toBe("completed"); + expect(result.dependenciesInstalled).toBe(false); + expect(result.warnings).toContain("install failed"); + }); +}); + +describe("runGenerateWorkflow", () => { + const baseOptions: GenerateWorkflowOptions = { + projectPath: "/tmp/demo", + files: ["readme"], + }; + + const hooks: GenerateWorkflowHooks = { + onProgress: vi.fn(), + onEducationalMessage: vi.fn(), + shouldOverwriteFile: vi.fn(), + resolveGitignoreTechnologies: vi.fn(), + }; + + beforeEach(() => { + vi.resetAllMocks(); + (mockedFs.writeFile as ReturnType).mockResolvedValue( + undefined, + ); + (mockedFs.access as ReturnType).mockRejectedValue( + new Error("not found"), + ); + ( + generatorsModule.generateReadmeContent as ReturnType + ).mockResolvedValue("# Demo README"); + ( + generatorsModule.generateGitignoreContent as ReturnType + ).mockResolvedValue("node_modules"); + (hooks.shouldOverwriteFile as ReturnType).mockResolvedValue( + true, + ); + ( + hooks.resolveGitignoreTechnologies as ReturnType + ).mockResolvedValue(["node-ts"]); + }); + + it("generates a README file when not present", async () => { + const result = await runGenerateWorkflow(baseOptions, hooks); + + expect(result.status).toBe("completed"); + expect(result.files).toHaveLength(1); + expect(result.files[0]).toMatchObject({ + fileType: "readme", + status: "created", + }); + expect(mockedFs.writeFile).toHaveBeenCalledWith( + path.join(baseOptions.projectPath, "README.md"), + "# Demo README", + ); + expect(hooks.shouldOverwriteFile).not.toHaveBeenCalled(); + }); + + it("skips file generation when overwrite is declined", async () => { + (mockedFs.access as ReturnType).mockResolvedValue(undefined); + ( + hooks.shouldOverwriteFile as ReturnType + ).mockResolvedValueOnce(false); + + const result = await runGenerateWorkflow(baseOptions, hooks); + + expect(result.status).toBe("cancelled"); + expect(result.files[0]).toMatchObject({ + fileType: "readme", + status: "skipped", + reason: "overwrite-declined", + }); + expect(mockedFs.writeFile).not.toHaveBeenCalled(); + }); + + it("uses provided technologies to generate .gitignore", async () => { + const options: GenerateWorkflowOptions = { + ...baseOptions, + files: ["gitignore"], + gitignoreTechnologies: ["react"], + }; + + const result = await runGenerateWorkflow(options, hooks); + + expect(result.status).toBe("completed"); + expect(generatorsModule.generateGitignoreContent).toHaveBeenCalledWith([ + "react", + ]); + expect(mockedFs.writeFile).toHaveBeenCalledWith( + path.join(baseOptions.projectPath, ".gitignore"), + "node_modules", + ); + }); + + it("infers gitignore technologies from project configuration when none provided", async () => { + ( + utilsModule.loadStackCodeConfig as ReturnType + ).mockResolvedValue({ + stack: "vue", + features: {}, + }); + ( + hooks.resolveGitignoreTechnologies as ReturnType + ).mockResolvedValue(undefined); + + const options: GenerateWorkflowOptions = { + ...baseOptions, + files: ["gitignore"], + }; + + const result = await runGenerateWorkflow(options, hooks); + + expect(result.status).toBe("completed"); + expect(generatorsModule.generateGitignoreContent).toHaveBeenCalledWith([ + "vue", + ]); + }); +}); + +describe("runValidateWorkflow", () => { + it("returns isValid=true for a valid commit message and reports progress", async () => { + const hooks: ValidateWorkflowHooks = { onProgress: vi.fn() }; + const options: ValidateWorkflowOptions = { message: "feat: add feature" }; + + const result = await runValidateWorkflow(options, hooks); + + expect(result.isValid).toBe(true); + expect(hooks.onProgress).toHaveBeenCalledWith({ step: "validating" }); + expect(hooks.onProgress).toHaveBeenCalledWith({ step: "completed" }); + }); + + it("returns isValid=false for an invalid commit message", async () => { + const hooks: ValidateWorkflowHooks = { onProgress: vi.fn() }; + const options: ValidateWorkflowOptions = { message: "invalid" }; + + const result = await runValidateWorkflow(options, hooks); + + expect(result.isValid).toBe(false); + }); +}); + +describe("runCommitWorkflow", () => { + const baseOptions = { + cwd: "/repo", + type: "feat", + scope: "api", + shortDescription: "add endpoint", + longDescription: "Add endpoint.|Handle errors.", + breakingChanges: "Changed response format", + affectedIssues: "closes #123", + }; + + beforeEach(() => { + vi.resetAllMocks(); + ( + utilsModule.getCommandOutput as ReturnType + ).mockResolvedValue("M file.ts"); + (utilsModule.runCommand as ReturnType).mockResolvedValue( + undefined, + ); + }); + + it("commits successfully when staged changes exist", async () => { + const result = await runCommitWorkflow(baseOptions); + + expect(result.status).toBe("committed"); + expect(result.message).toBeDefined(); + const message = result.message!; + expect(message).toContain("feat(api): add endpoint"); + expect(message).toContain("Add endpoint.\nHandle errors."); + expect(message).toContain("BREAKING CHANGE: Changed response format"); + expect(message).toContain("closes #123"); + expect(utilsModule.runCommand).toHaveBeenCalledWith( + "git", + ["commit", "-m", message], + { cwd: baseOptions.cwd }, + ); + }); + + it("returns cancelled when there are no staged changes", async () => { + ( + utilsModule.getCommandOutput as ReturnType + ).mockResolvedValue(""); + + const result = await runCommitWorkflow(baseOptions); + + expect(result.status).toBe("cancelled"); + expect(result.reason).toBe("no-staged-changes"); + expect(utilsModule.runCommand).not.toHaveBeenCalled(); + }); + + it("returns error when git commit fails", async () => { + (utilsModule.runCommand as ReturnType).mockRejectedValue( + new Error("commit failed"), + ); + + const result = await runCommitWorkflow(baseOptions); + + expect(result.status).toBe("cancelled"); + expect(result.reason).toBe("error"); + expect(result.error).toContain("commit failed"); + }); +}); + +describe("runGitStartWorkflow", () => { + const options = { + cwd: "/repo", + branchName: "awesome", + branchType: "feature", + }; + + beforeEach(() => { + vi.resetAllMocks(); + (utilsModule.runCommand as ReturnType).mockResolvedValue( + undefined, + ); + }); + + it("creates a feature branch from develop", async () => { + const result = await runGitStartWorkflow(options); + + expect(result.status).toBe("created"); + expect(result.fullBranchName).toBe("feature/awesome"); + expect(utilsModule.runCommand).toHaveBeenNthCalledWith( + 1, + "git", + ["checkout", "develop"], + { cwd: options.cwd }, + ); + expect(utilsModule.runCommand).toHaveBeenNthCalledWith( + 2, + "git", + ["pull", "origin", "develop"], + { cwd: options.cwd }, + ); + expect(utilsModule.runCommand).toHaveBeenNthCalledWith( + 3, + "git", + ["checkout", "-b", "feature/awesome"], + { cwd: options.cwd }, + ); + }); + + it("returns cancelled when git checkout fails", async () => { + (utilsModule.runCommand as ReturnType).mockRejectedValue( + new Error("checkout failed"), + ); + + const result = await runGitStartWorkflow(options); + + expect(result.status).toBe("cancelled"); + expect(result.error).toContain("checkout failed"); + }); +}); + +describe("runGitFinishWorkflow", () => { + const options = { cwd: "/repo" }; + + beforeEach(() => { + vi.resetAllMocks(); + (utilsModule.runCommand as ReturnType).mockResolvedValue( + undefined, + ); + }); + + it("pushes current branch and returns PR URL", async () => { + (utilsModule.getCommandOutput as ReturnType) + .mockResolvedValueOnce("feature/awesome") + .mockResolvedValueOnce("git@github.com:acme/project.git"); + + const result = await runGitFinishWorkflow(options); + + expect(result.status).toBe("pushed"); + expect(result.branch).toBe("feature/awesome"); + expect(result.prUrl).toBe( + "https://github.com/acme/project/pull/new/feature/awesome", + ); + expect(utilsModule.runCommand).toHaveBeenCalledWith( + "git", + ["push", "--set-upstream", "origin", "feature/awesome"], + { cwd: options.cwd }, + ); + }); + + it("returns cancelled when not on a branch", async () => { + ( + utilsModule.getCommandOutput as ReturnType + ).mockResolvedValueOnce(""); + + const result = await runGitFinishWorkflow(options); + + expect(result.status).toBe("cancelled"); + expect(result.error).toBe("not-on-branch"); + }); +}); + +describe("runReleaseWorkflow", () => { + const cwd = "/repo"; + + beforeEach(() => { + vi.resetAllMocks(); + (mockedFs.writeFile as ReturnType).mockResolvedValue( + undefined, + ); + (mockedFs.readFile as ReturnType).mockRejectedValue( + new Error("not found"), + ); + ( + utilsModule.getCommandOutput as ReturnType + ).mockResolvedValue("git@github.com:org/repo.git"); + }); + + it("prepares a locked release when confirmed", async () => { + vi.mocked(releaseModule.detectVersioningStrategy).mockResolvedValue({ + strategy: "locked", + rootDir: cwd, + rootVersion: "1.2.3", + packages: [], + }); + vi.mocked(releaseModule.getRecommendedBump).mockResolvedValue("patch"); + vi.mocked(releaseModule.updateAllVersions).mockResolvedValue(); + vi.mocked(releaseModule.generateChangelog).mockResolvedValue("# Changelog"); + (mockedFs.readFile as ReturnType).mockResolvedValueOnce(""); + + const confirmLockedRelease = vi.fn().mockResolvedValue(true); + const hooks: ReleaseWorkflowHooks = { + confirmLockedRelease: async (details) => { + expect(details.currentVersion).toBe("1.2.3"); + expect(details.newVersion).toBe("1.2.4"); + return confirmLockedRelease(); + }, + }; + + const result = await runReleaseWorkflow({ cwd }, hooks); + + expect(result.status).toBe("prepared"); + expect(result.strategy).toBe("locked"); + expect(result.newVersion).toBe("1.2.4"); + expect(result.tagName).toBe("v1.2.4"); + expect(result.releaseNotes).toBe("# Changelog"); + expect(result.github).toEqual( + expect.objectContaining({ owner: "org", repo: "repo" }), + ); + expect(releaseModule.updateAllVersions).toHaveBeenCalledWith( + expect.objectContaining({ rootDir: cwd }), + "1.2.4", + ); + expect(mockedFs.writeFile).toHaveBeenCalledWith( + `${cwd}/CHANGELOG.md`, + "# Changelog\n", + ); + }); + + it("cancels locked release when user declines", async () => { + vi.mocked(releaseModule.detectVersioningStrategy).mockResolvedValue({ + strategy: "locked", + rootDir: cwd, + rootVersion: "1.0.0", + packages: [], + }); + vi.mocked(releaseModule.getRecommendedBump).mockResolvedValue("minor"); + + const result = await runReleaseWorkflow( + { cwd }, + { + confirmLockedRelease: () => Promise.resolve(false), + }, + ); + + expect(result.status).toBe("cancelled"); + expect(result.reason).toBe("cancelled-by-user"); + expect(releaseModule.updateAllVersions).not.toHaveBeenCalled(); + }); + + it("prepares an independent release with combined notes", async () => { + vi.mocked(releaseModule.detectVersioningStrategy).mockResolvedValue({ + strategy: "independent", + rootDir: cwd, + packages: [ + { name: "pkg1", version: "1.0.0", path: `${cwd}/packages/pkg1` }, + ], + }); + vi.mocked(releaseModule.findChangedPackages).mockResolvedValue([ + { name: "pkg1", version: "1.0.0", path: `${cwd}/packages/pkg1` }, + ]); + vi.mocked(releaseModule.determinePackageBumps).mockResolvedValue([ + { + pkg: { name: "pkg1", version: "1.0.0", path: `${cwd}/packages/pkg1` }, + bumpType: "patch", + newVersion: "1.0.1", + }, + ]); + vi.mocked(releaseModule.updatePackageVersion).mockResolvedValue(); + vi.mocked(releaseModule.performReleaseCommit).mockResolvedValue(); + vi.mocked(releaseModule.generateChangelog).mockImplementation( + async (_mono, pkgInfo) => + pkgInfo ? `Changelog for ${pkgInfo.pkg.name}` : "", + ); + (mockedFs.readFile as ReturnType).mockResolvedValue(""); + + const displayPlan = vi.fn(); + const confirmPlan = vi.fn().mockResolvedValue(true); + + const result = await runReleaseWorkflow( + { cwd }, + { + displayIndependentPlan: displayPlan, + confirmIndependentRelease: () => confirmPlan(), + }, + ); + + expect(displayPlan).toHaveBeenCalled(); + expect(confirmPlan).toHaveBeenCalled(); + expect(result.status).toBe("prepared"); + expect(result.strategy).toBe("independent"); + expect(result.packages).toHaveLength(1); + expect(result.releaseNotes).toContain("Changelog for pkg1"); + expect(result.tagName).toBe("pkg1@1.0.1"); + expect(releaseModule.performReleaseCommit).toHaveBeenCalledWith( + expect.arrayContaining([ + expect.objectContaining({ newVersion: "1.0.1" }), + ]), + cwd, + ); + expect(mockedFs.writeFile).toHaveBeenCalledWith( + `${cwd}/packages/pkg1/CHANGELOG.md`, + "Changelog for pkg1\n", + ); + }); + + it("returns no changes when independent strategy has nothing to release", async () => { + vi.mocked(releaseModule.detectVersioningStrategy).mockResolvedValue({ + strategy: "independent", + rootDir: cwd, + packages: [], + }); + vi.mocked(releaseModule.findChangedPackages).mockResolvedValue([]); + + const result = await runReleaseWorkflow({ cwd }); + + expect(result.status).toBe("cancelled"); + expect(result.reason).toBe("no-changes"); + expect(releaseModule.determinePackageBumps).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/github-auth/README.md b/packages/github-auth/README.md new file mode 100644 index 00000000..9044daf4 --- /dev/null +++ b/packages/github-auth/README.md @@ -0,0 +1,39 @@ +# @stackcode/github-auth + +Shared GitHub authentication utilities for the StackCode CLI and VS Code extension. + +## Features + +- Unified API that exposes `login`, `logout`, `getSession`, `getAuthenticatedClient`, and validation helpers +- Pluggable providers for CLI (Personal Access Token) and VS Code (OAuth via `vscode.authentication`) +- Secure token storage abstractions with filesystem and secret storage implementations +- Optional cross-environment token sharing so one login can power multiple clients + +## Usage + +```ts +import { + createGitHubAuth, + createCLIAuthProvider, + createFileTokenStorage, +} from "@stackcode/github-auth"; + +const auth = createGitHubAuth({ + provider: createCLIAuthProvider({ + storage: createFileTokenStorage(), + }), +}); + +const session = await auth.login({ token: "ghp_xxx", persist: true }); +const client = await auth.getAuthenticatedClient(); +``` + +## Scripts + +- `npm run build` – emit compiled JavaScript and type declarations +- `npm run test` – execute unit tests with Vitest +- `npm run clean` – remove build artifacts + +## License + +MIT © StackCode diff --git a/packages/github-auth/package.json b/packages/github-auth/package.json new file mode 100644 index 00000000..343ac6fe --- /dev/null +++ b/packages/github-auth/package.json @@ -0,0 +1,45 @@ +{ + "name": "@stackcode/github-auth", + "version": "1.0.0", + "description": "Shared GitHub authentication utilities for StackCode clients", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc -p ./tsconfig.json", + "clean": "rm -rf dist tsconfig.tsbuildinfo", + "test": "vitest run" + }, + "keywords": [ + "github", + "authentication", + "octokit", + "stackcode" + ], + "author": "Yago Borba", + "license": "MIT", + "dependencies": {}, + "peerDependencies": { + "vscode": "^1.85.0", + "@octokit/rest": "^22.0.0" + }, + "peerDependenciesMeta": { + "vscode": { + "optional": true + } + }, + "devDependencies": { + "@types/node": "^24.2.0", + "typescript": "^5.8.3", + "vitest": "^3.2.4" + } +} diff --git a/packages/github-auth/src/index.ts b/packages/github-auth/src/index.ts new file mode 100644 index 00000000..c6e91b9d --- /dev/null +++ b/packages/github-auth/src/index.ts @@ -0,0 +1,119 @@ +import type { + GitHubAuthContext, + GitHubAuthInstance, + GitHubAuthOptions, + GitHubAuthProvider, +} from "./types.js"; + +export type { + GitHubAuthContext, + GitHubAuthInstance, + GitHubAuthLoginOptions, + GitHubAuthOptions, + GitHubAuthProvider, + GitHubAuthSession, + GitHubAuthenticatedUser, + TokenStorage, +} from "./types.js"; +export { + createCLIAuthProvider, + type CLIAuthProviderOptions, +} from "./providers/cliProvider.js"; +export { + createVSCodeAuthProvider, + type VSCodeAuthProviderOptions, +} from "./providers/vscodeProvider.js"; +export { + createFileTokenStorage, + DEFAULT_GITHUB_TOKEN_FILE, + DEFAULT_STACKCODE_DIRECTORY, + type FileTokenStorageOptions, +} from "./storage/index.js"; + +/** + * Factory that builds the public GitHub authentication facade used throughout + * StackCode clients. + */ +export function createGitHubAuth( + options: GitHubAuthOptions, +): GitHubAuthInstance { + const provider = options.provider; + let cachedContext: GitHubAuthContext | null = null; + + const ensureOperation = ( + method: K, + ): NonNullable => { + const operation = provider[method]; + if (typeof operation !== "function") { + throw new Error(`Provider does not implement '${String(method)}'.`); + } + return operation.bind(provider) as NonNullable; + }; + + const login = async (...args: Parameters) => { + const context = await provider.login(...args); + cachedContext = context; + return context; + }; + + const logout = async () => { + await provider.logout(); + cachedContext = null; + }; + + const getSession = async (requestOptions?: { + forceRefresh?: boolean; + }): Promise => { + if (!requestOptions?.forceRefresh && cachedContext) { + return cachedContext; + } + + const context = await provider.getSession(); + cachedContext = context; + return context; + }; + + const getAuthenticatedClient = async () => { + const context = await getSession(); + if (!context) { + throw new Error("GitHub user is not authenticated. Call login() first."); + } + return context.client; + }; + + const getStoredToken = () => { + if (provider.getStoredToken) { + return provider.getStoredToken(); + } + return Promise.resolve(null); + }; + + const saveToken = async (token: string) => { + const operation = ensureOperation("saveToken"); + cachedContext = null; + await operation(token); + }; + + const removeToken = async () => { + const operation = ensureOperation("removeToken"); + cachedContext = null; + await operation(); + }; + + const validateToken = async (token: string) => { + const operation = ensureOperation("validateToken"); + return operation(token) as Promise; + }; + + return { + login, + logout, + getSession, + getAuthenticatedClient, + isAuthenticated: () => cachedContext !== null, + getStoredToken, + saveToken, + removeToken, + validateToken, + }; +} diff --git a/packages/github-auth/src/providers/cliProvider.ts b/packages/github-auth/src/providers/cliProvider.ts new file mode 100644 index 00000000..b798a043 --- /dev/null +++ b/packages/github-auth/src/providers/cliProvider.ts @@ -0,0 +1,163 @@ +import { Octokit as OctokitClient } from "@octokit/rest"; +import type { Octokit } from "@octokit/rest"; +import type { + GitHubAuthContext, + GitHubAuthLoginOptions, + GitHubAuthProvider, + GitHubAuthSession, + GitHubAuthenticatedUser, + TokenStorage, +} from "../types.js"; + +/** + * Additional configuration accepted by {@link createCLIAuthProvider}. + */ +export interface CLIAuthProviderOptions { + /** Storage implementation responsible for persisting the token. */ + storage: TokenStorage; + /** + * Factory used to instantiate Octokit clients. Exposed for testing so the HTTP + * layer can be mocked easily. + */ + octokitFactory?: (token: string) => Octokit; + /** Optional provider identifier added to the exported session. */ + providerId?: string; + /** Optional fixed scopes associated with the stored token. */ + scopes?: readonly string[]; +} + +/** Default provider identifier for the CLI environment. */ +const DEFAULT_PROVIDER_ID = "cli"; + +/** + * Creates a GitHub auth provider backed by personal access tokens stored in the + * local filesystem for the StackCode CLI. + */ +export function createCLIAuthProvider( + options: CLIAuthProviderOptions, +): GitHubAuthProvider { + const octokitFactory: (token: string) => Octokit = + options.octokitFactory ?? + ((token: string) => new OctokitClient({ auth: token })); + + let cachedContext: GitHubAuthContext | null = null; + + /** + * Materializes an authenticated context by calling GitHub's `getAuthenticated` + * endpoint. The result is cached to avoid redundant network calls. + */ + const resolveContext = async ( + token: string, + cache = true, + ): Promise => { + if (cache && cachedContext && cachedContext.session.accessToken === token) { + return cachedContext; + } + + const client = octokitFactory(token); + const userResponse = await client.users.getAuthenticated(); + const payload = userResponse.data as unknown as GitHubAuthenticatedUser; + + const session: GitHubAuthSession = { + accessToken: token, + providerId: options.providerId ?? DEFAULT_PROVIDER_ID, + scopes: options.scopes, + account: { + username: payload.login, + displayName: payload.name ?? undefined, + email: payload.email ?? null, + id: String(payload.id), + }, + }; + + const context: GitHubAuthContext = { session, client }; + if (cache) { + cachedContext = context; + } + return context; + }; + + const readToken = async (): Promise => { + if (cachedContext) { + return cachedContext.session.accessToken; + } + return options.storage.read(); + }; + + return { + /** @inheritdoc */ + async login( + loginOptions?: GitHubAuthLoginOptions, + ): Promise { + const providedToken = loginOptions?.token; + + const token = + providedToken ?? + (await readToken()) ?? + (() => { + throw new Error("GitHub token not found. Provide a token to login."); + })(); + + const context = await resolveContext(token); + + const shouldPersist = + loginOptions?.persist ?? Boolean(providedToken && providedToken !== ""); + if (shouldPersist) { + await options.storage.write(token); + } + + return context; + }, + + /** @inheritdoc */ + async logout(): Promise { + cachedContext = null; + await options.storage.clear(); + }, + + /** @inheritdoc */ + async getSession(): Promise { + const token = await readToken(); + if (!token) { + cachedContext = null; + return null; + } + + try { + return await resolveContext(token); + } catch { + await options.storage.clear(); + cachedContext = null; + return null; + } + }, + + /** @inheritdoc */ + async getStoredToken(): Promise { + return readToken(); + }, + + /** @inheritdoc */ + async saveToken(token: string): Promise { + cachedContext = null; + await options.storage.write(token); + }, + + /** @inheritdoc */ + async removeToken(): Promise { + cachedContext = null; + await options.storage.clear(); + }, + + /** @inheritdoc */ + async validateToken(token: string): Promise { + try { + await resolveContext(token, false); + return true; + } catch { + cachedContext = null; + return false; + } + }, + }; +} diff --git a/packages/github-auth/src/providers/vscodeProvider.ts b/packages/github-auth/src/providers/vscodeProvider.ts new file mode 100644 index 00000000..fa98ef12 --- /dev/null +++ b/packages/github-auth/src/providers/vscodeProvider.ts @@ -0,0 +1,221 @@ +import { Octokit as OctokitClient } from "@octokit/rest"; +import type { Octokit } from "@octokit/rest"; +import type { + GitHubAuthContext, + GitHubAuthProvider, + GitHubAuthSession, + GitHubAuthenticatedUser, + TokenStorage, +} from "../types.js"; +import type { ExtensionContext, AuthenticationSession } from "vscode"; + +type VSCodeModule = typeof import("vscode"); + +/** + * Configuration accepted by {@link createVSCodeAuthProvider}. + */ +export interface VSCodeAuthProviderOptions { + /** Reference to the VS Code API module. */ + vscode: VSCodeModule; + /** Extension context obtained from the activation function. */ + context: ExtensionContext; + /** OAuth scopes requested during the login flow. */ + scopes?: readonly string[]; + /** Identifier stored alongside the session metadata. */ + providerId?: string; + /** Secret storage key used to persist the token. */ + secretKey?: string; + /** Optional shared storage to enable cross-environment reuse. */ + sharedStorage?: TokenStorage; + /** When true, tokens are mirrored to {@link sharedStorage}. */ + shareTokens?: boolean; + /** Custom Octokit factory primarily for testing. */ + octokitFactory?: (token: string) => Octokit; +} + +const DEFAULT_PROVIDER_ID = "vscode"; +const DEFAULT_SCOPES = ["repo", "user:email"] as const; +const DEFAULT_SECRET_KEY = "stackcode.github.token"; + +/** + * Creates a VS Code oriented GitHub auth provider that relies on the native + * authentication API and secret storage services. + */ +export function createVSCodeAuthProvider( + options: VSCodeAuthProviderOptions, +): GitHubAuthProvider { + const providerId = options.providerId ?? DEFAULT_PROVIDER_ID; + const scopes = options.scopes ?? DEFAULT_SCOPES; + const secretKey = options.secretKey ?? DEFAULT_SECRET_KEY; + const shareTokens = options.shareTokens ?? true; + + const octokitFactory: (token: string) => Octokit = + options.octokitFactory ?? + ((token: string) => new OctokitClient({ auth: token })); + + let cachedContext: GitHubAuthContext | null = null; + + const storeToken = async (token: string): Promise => { + await options.context.secrets.store(secretKey, token); + if (shareTokens && options.sharedStorage) { + await options.sharedStorage.write(token); + } + }; + + const clearToken = async (): Promise => { + await options.context.secrets.delete(secretKey); + if (shareTokens && options.sharedStorage) { + await options.sharedStorage.clear(); + } + }; + + const readTokenFromSecret = async (): Promise => { + const stored = await options.context.secrets.get(secretKey); + if (stored) { + return stored; + } + if (shareTokens && options.sharedStorage) { + return options.sharedStorage.read(); + } + return null; + }; + + const buildContext = async ( + token: string, + sessionInfo?: AuthenticationSession, + cache = true, + ): Promise => { + if (cache && cachedContext && cachedContext.session.accessToken === token) { + return cachedContext; + } + + const client = octokitFactory(token); + let userPayload: GitHubAuthenticatedUser | null = null; + + try { + const { data } = await client.users.getAuthenticated(); + userPayload = data as unknown as GitHubAuthenticatedUser; + } catch (error) { + if (!sessionInfo) { + throw error; + } + } + + const resolvedAccount = sessionInfo?.account; + + const session: GitHubAuthSession = { + accessToken: token, + providerId, + scopes: sessionInfo?.scopes ?? scopes, + account: { + username: resolvedAccount?.label ?? userPayload?.login, + displayName: userPayload?.name ?? resolvedAccount?.label, + email: userPayload?.email ?? null, + id: userPayload ? String(userPayload.id) : resolvedAccount?.id, + }, + metadata: sessionInfo ? { sessionId: sessionInfo.id } : undefined, + }; + + const context: GitHubAuthContext = { session, client }; + if (cache) { + cachedContext = context; + } + return context; + }; + + const getVSCodeSession = async ( + createIfNone: boolean, + ): Promise => { + try { + const session = await options.vscode.authentication.getSession( + "github", + scopes, + { createIfNone }, + ); + return session ?? null; + } catch (error) { + console.warn( + "[StackCode] Failed to retrieve VS Code GitHub session", + error, + ); + return null; + } + }; + + return { + /** @inheritdoc */ + async login(): Promise { + const session = await getVSCodeSession(true); + if (!session) { + throw new Error("GitHub authentication was cancelled or unavailable."); + } + + const context = await buildContext(session.accessToken, session); + await storeToken(session.accessToken); + return context; + }, + + /** @inheritdoc */ + async logout(): Promise { + cachedContext = null; + await clearToken(); + }, + + /** @inheritdoc */ + async getSession(): Promise { + if (cachedContext && !cachedContext.session.accessToken) { + cachedContext = null; + } + + if (cachedContext) { + return cachedContext; + } + + const session = await getVSCodeSession(false); + if (session) { + return buildContext(session.accessToken, session); + } + + const token = await readTokenFromSecret(); + if (!token) { + cachedContext = null; + return null; + } + + try { + return await buildContext(token, undefined, true); + } catch { + await clearToken(); + cachedContext = null; + return null; + } + }, + + /** @inheritdoc */ + async getStoredToken(): Promise { + return readTokenFromSecret(); + }, + + /** @inheritdoc */ + async saveToken(token: string): Promise { + cachedContext = null; + await storeToken(token); + }, + + /** @inheritdoc */ + async removeToken(): Promise { + cachedContext = null; + await clearToken(); + }, + + /** @inheritdoc */ + async validateToken(token: string): Promise { + try { + await buildContext(token, undefined, false); + return true; + } catch { + return false; + } + }, + }; +} diff --git a/packages/github-auth/src/storage/fileStorage.ts b/packages/github-auth/src/storage/fileStorage.ts new file mode 100644 index 00000000..bc7f045b --- /dev/null +++ b/packages/github-auth/src/storage/fileStorage.ts @@ -0,0 +1,77 @@ +import { promises as fs } from "node:fs"; +import path from "node:path"; +import os from "node:os"; +import type { TokenStorage } from "../types.js"; + +/** Default directory used to persist StackCode configuration data. */ +export const DEFAULT_STACKCODE_DIRECTORY = path.join( + os.homedir(), + ".stackcode", +); + +/** Default filename used to store the GitHub token. */ +export const DEFAULT_GITHUB_TOKEN_FILE = "github_token"; + +/** + * Options accepted by {@link createFileTokenStorage}. + */ +export interface FileTokenStorageOptions { + /** Custom absolute path to the token file. */ + filePath?: string; + /** UNIX permission mask applied to the stored token. */ + mode?: number; +} + +/** + * Ensures the parent directory exists before writing tokens to disk. + */ +async function ensureDirectoryExists(filePath: string): Promise { + const directory = path.dirname(filePath); + await fs.mkdir(directory, { recursive: true, mode: 0o700 }); +} + +/** + * Creates a {@link TokenStorage} implementation backed by the local filesystem + * with secure default permissions suitable for personal access tokens. + */ +export function createFileTokenStorage( + options: FileTokenStorageOptions = {}, +): TokenStorage { + const filePath = + options.filePath ?? + path.join(DEFAULT_STACKCODE_DIRECTORY, DEFAULT_GITHUB_TOKEN_FILE); + const mode = options.mode ?? 0o600; + + return { + /** @inheritdoc */ + async read(): Promise { + try { + await fs.access(filePath); + } catch { + return null; + } + + const raw = await fs.readFile(filePath, "utf8"); + return raw.trim() || null; + }, + + /** @inheritdoc */ + async write(token: string): Promise { + await ensureDirectoryExists(filePath); + await fs.writeFile(filePath, `${token}\n`, { mode }); + await fs.chmod(filePath, mode); + }, + + /** @inheritdoc */ + async clear(): Promise { + try { + await fs.unlink(filePath); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") { + return; + } + throw error; + } + }, + }; +} diff --git a/packages/github-auth/src/storage/index.ts b/packages/github-auth/src/storage/index.ts new file mode 100644 index 00000000..5e7e535d --- /dev/null +++ b/packages/github-auth/src/storage/index.ts @@ -0,0 +1,6 @@ +export { + DEFAULT_GITHUB_TOKEN_FILE, + DEFAULT_STACKCODE_DIRECTORY, + type FileTokenStorageOptions, + createFileTokenStorage, +} from "./fileStorage.js"; diff --git a/packages/github-auth/src/types.ts b/packages/github-auth/src/types.ts new file mode 100644 index 00000000..0d9bf532 --- /dev/null +++ b/packages/github-auth/src/types.ts @@ -0,0 +1,131 @@ +import type { Octokit } from "@octokit/rest"; + +/** + * Represents a standardized GitHub authentication session shared across environments. + */ +export interface GitHubAuthSession { + /** Raw access token returned by GitHub. */ + accessToken: string; + /** Identifier of the provider that issued the session (e.g. `cli`, `vscode`). */ + providerId: string; + /** Optional list of scopes that were granted to the token. */ + scopes?: readonly string[]; + /** Optional metadata describing the authenticated account. */ + account?: { + /** Friendly label or username shown to the user. */ + username?: string; + /** Display name when available. */ + displayName?: string; + /** Primary e-mail if exposed by the API. */ + email?: string | null; + /** Unique account identifier returned by GitHub. */ + id?: string; + }; + /** Arbitrary provider-specific data. */ + metadata?: Record; +} + +/** + * Aggregates the current GitHub session with an authenticated Octokit instance. + */ +export interface GitHubAuthContext { + /** Authentication session metadata. */ + session: GitHubAuthSession; + /** Authenticated Octokit client ready for GitHub calls. */ + client: Octokit; +} + +/** + * Optional settings that fine tune the login flow for different providers. + */ +export interface GitHubAuthLoginOptions { + /** + * Raw token value provided by the caller. Providers may ignore this field when + * they fully control the OAuth flow (e.g. VS Code). + */ + token?: string; + /** When true, providers should persist the token using their configured storage. */ + persist?: boolean; + /** Force-refreshes the session even if a cached value exists. */ + forceRefresh?: boolean; + /** Signals that an interactive flow is allowed (e.g. device code, OAuth UI). */ + interactive?: boolean; +} + +/** + * Defines the minimal contract any GitHub auth provider must fulfill so the + * unified API can orchestrate authentication consistently across environments. + */ +export interface GitHubAuthProvider { + /** Performs the environment-specific login flow and returns the new context. */ + login(options?: GitHubAuthLoginOptions): Promise; + /** Clears tokens, sessions, and cached state. */ + logout(): Promise; + /** Attempts to restore a cached session (without user interaction whenever possible). */ + getSession(): Promise; + /** Optional hook for retrieving the persisted raw token, when applicable. */ + getStoredToken?(): Promise; + /** Optional hook for persisting a token without triggering a login flow. */ + saveToken?(token: string): Promise; + /** Optional hook for removing persisted tokens without a full logout. */ + removeToken?(): Promise; + /** Optional hook used to validate arbitrary tokens. */ + validateToken?(token: string): Promise; +} + +/** + * Storage abstraction that makes token persistence pluggable and testable. + */ +export interface TokenStorage { + /** Reads a token from the underlying storage or returns null when empty. */ + read(): Promise; + /** Writes or overwrites the stored token. */ + write(token: string): Promise; + /** Deletes the stored token and related metadata. */ + clear(): Promise; +} + +/** + * Configuration used when instantiating the GitHub auth facade. + */ +export interface GitHubAuthOptions { + /** Provider responsible for executing the concrete login/logout logic. */ + provider: GitHubAuthProvider; +} + +/** + * Public facade consumed by StackCode packages. + */ +export interface GitHubAuthInstance { + /** Runs the login flow and caches the resulting session and client. */ + login(options?: GitHubAuthLoginOptions): Promise; + /** Clears cached state and invokes the provider logout logic. */ + logout(): Promise; + /** Returns the cached session or attempts to restore one from the provider. */ + getSession(options?: { + forceRefresh?: boolean; + }): Promise; + /** Returns an authenticated Octokit client, ensuring the user is logged in. */ + getAuthenticatedClient(): Promise; + /** Helper flag to quickly check whether a valid session is cached. */ + isAuthenticated(): boolean; + /** Retrieves the raw persisted token when available. */ + getStoredToken(): Promise; + /** Persists the provided token without triggering additional validations. */ + saveToken(token: string): Promise; + /** Removes any persisted tokens from storage. */ + removeToken(): Promise; + /** Validates an arbitrary token against the provider's rules. */ + validateToken(token: string): Promise; +} + +/** + * Common metadata returned by the GitHub REST API when requesting the + * authenticated user. Declared separately to simplify stubbing in tests. + */ +export interface GitHubAuthenticatedUser { + id: number; + login: string; + name?: string | null; + email?: string | null; +} diff --git a/packages/github-auth/test/cliProvider.test.d.ts b/packages/github-auth/test/cliProvider.test.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/packages/github-auth/test/cliProvider.test.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/github-auth/test/cliProvider.test.js b/packages/github-auth/test/cliProvider.test.js new file mode 100644 index 00000000..675cbd11 --- /dev/null +++ b/packages/github-auth/test/cliProvider.test.js @@ -0,0 +1,76 @@ +import { describe, it, expect, beforeEach, vi } from "vitest"; +import { createGitHubAuth } from "../src/index.js"; +import { createCLIAuthProvider } from "../src/providers/cliProvider.js"; +const createMemoryStorage = () => { + let value = null; + return { + async read() { + return value; + }, + async write(token) { + value = token; + }, + async clear() { + value = null; + }, + }; +}; +describe("CLI auth provider", () => { + const userPayload = { + id: 123, + login: "stackcoder", + name: "Stack Coder", + email: "stackcoder@example.com", + }; + const createStubOctokit = () => ({ + users: { + getAuthenticated: vi.fn().mockResolvedValue({ data: userPayload }), + }, + }); + let storage; + beforeEach(() => { + storage = createMemoryStorage(); + }); + it("persists tokens when requested during login", async () => { + const auth = createGitHubAuth({ + provider: createCLIAuthProvider({ + storage, + octokitFactory: () => createStubOctokit(), + }), + }); + await auth.login({ token: "token123", persist: true }); + expect(await storage.read()).toBe("token123"); + expect(auth.isAuthenticated()).toBe(true); + const client = await auth.getAuthenticatedClient(); + const session = await auth.getSession(); + expect(session?.session.account?.username).toBe("stackcoder"); + expect((session?.client).users.getAuthenticated).toBeDefined(); + expect(typeof client).toBe("object"); + }); + it("validates tokens and clears cache on failure", async () => { + const provider = createCLIAuthProvider({ + storage, + octokitFactory: () => ({ + users: { + getAuthenticated: vi.fn().mockRejectedValue(new Error("bad token")), + }, + }), + }); + const auth = createGitHubAuth({ provider }); + expect(await auth.validateToken("invalid")).toBe(false); + expect(await auth.getStoredToken()).toBeNull(); + }); + it("saves and removes tokens via helper methods", async () => { + const auth = createGitHubAuth({ + provider: createCLIAuthProvider({ + storage, + octokitFactory: () => createStubOctokit(), + }), + }); + await auth.saveToken("temp-token"); + expect(await storage.read()).toBe("temp-token"); + await auth.removeToken(); + expect(await storage.read()).toBeNull(); + }); +}); +//# sourceMappingURL=cliProvider.test.js.map diff --git a/packages/github-auth/test/cliProvider.test.js.map b/packages/github-auth/test/cliProvider.test.js.map new file mode 100644 index 00000000..7ad0e5ca --- /dev/null +++ b/packages/github-auth/test/cliProvider.test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"cliProvider.test.js","sourceRoot":"","sources":["cliProvider.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/github-auth/test/cliProvider.test.ts b/packages/github-auth/test/cliProvider.test.ts new file mode 100644 index 00000000..7b4692c7 --- /dev/null +++ b/packages/github-auth/test/cliProvider.test.ts @@ -0,0 +1,101 @@ +import { describe, it, expect, beforeEach, vi } from "vitest"; +import { createGitHubAuth } from "../src/index.js"; +import { createCLIAuthProvider } from "../src/providers/cliProvider.js"; +import type { TokenStorage } from "../src/types.js"; +import type { Octokit } from "@octokit/rest"; + +const createMemoryStorage = (): TokenStorage => { + let value: string | null = null; + return { + async read() { + return value; + }, + async write(token: string) { + value = token; + }, + async clear() { + value = null; + }, + }; +}; + +describe("CLI auth provider", () => { + const userPayload = { + id: 123, + login: "stackcoder", + name: "Stack Coder", + email: "stackcoder@example.com", + }; + + const createStubOctokit = (): Octokit => + ({ + users: { + getAuthenticated: vi.fn().mockResolvedValue({ data: userPayload }), + }, + }) as unknown as Octokit; + + let storage: TokenStorage; + + beforeEach(() => { + storage = createMemoryStorage(); + }); + + it("persists tokens when requested during login", async () => { + const auth = createGitHubAuth({ + provider: createCLIAuthProvider({ + storage, + octokitFactory: () => createStubOctokit(), + }), + }); + + await auth.login({ token: "token123", persist: true }); + + expect(await storage.read()).toBe("token123"); + expect(auth.isAuthenticated()).toBe(true); + + const client = await auth.getAuthenticatedClient(); + const session = await auth.getSession(); + + expect(session?.session.account?.username).toBe("stackcoder"); + expect( + ( + session?.client as unknown as { + users: { getAuthenticated: () => unknown }; + } + ).users.getAuthenticated, + ).toBeDefined(); + expect(typeof client).toBe("object"); + }); + + it("validates tokens and clears cache on failure", async () => { + const provider = createCLIAuthProvider({ + storage, + octokitFactory: () => + ({ + users: { + getAuthenticated: vi.fn().mockRejectedValue(new Error("bad token")), + }, + }) as unknown as Octokit, + }); + + const auth = createGitHubAuth({ provider }); + + expect(await auth.validateToken("invalid")).toBe(false); + expect(await auth.getStoredToken()).toBeNull(); + }); + + it("saves and removes tokens via helper methods", async () => { + const auth = createGitHubAuth({ + provider: createCLIAuthProvider({ + storage, + octokitFactory: () => createStubOctokit(), + }), + }); + + await auth.saveToken("temp-token"); + expect(await storage.read()).toBe("temp-token"); + + await auth.removeToken(); + expect(await storage.read()).toBeNull(); + }); +}); diff --git a/packages/github-auth/tsconfig.json b/packages/github-auth/tsconfig.json new file mode 100644 index 00000000..951eb825 --- /dev/null +++ b/packages/github-auth/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["dist", "node_modules"] +} diff --git a/packages/i18n/dist/locales/en.json b/packages/i18n/dist/locales/en.json index 0d323d2c..75b7963a 100644 --- a/packages/i18n/dist/locales/en.json +++ b/packages/i18n/dist/locales/en.json @@ -4,6 +4,8 @@ "error_generic": "✖ An error occurred.", "yes": "Yes", "no": "No", + "continue": "Continue", + "cancel": "Cancel", "error_demand_command": "You need to specify at least one command.", "educate_flag_description": "Enable educational mode with detailed explanations" }, @@ -68,6 +70,8 @@ "login_github": "Login to GitHub", "error_loading_issues": "Error loading issues", "loading_issues": "Loading issues...", + "fetching_issues": "Fetching issues from GitHub...", + "please_wait": "Please wait while we load the issues", "view_on_github": "View on GitHub", "created_by": "Created by", "updated": "Updated", @@ -87,6 +91,19 @@ "breaking_changes": "Are there any BREAKING CHANGES? (optional)", "affected_issues": "Does this change affect any open issues? (e.g. 'closes #123') (optional)" }, + "output_channel_title": "Commit message", + "placeholder": { + "issue_reference": "closes #123" + }, + "issues": { + "reference_entry": "closes #{issueNumber}" + }, + "progress": { + "checking_staged": "Checking staged changes...", + "building_message": "Building commit message...", + "committing": "Running git commit...", + "completed": "Commit completed successfully." + }, "types": { "feat": "feat: A new feature", "fix": "fix: A bug fix", @@ -200,7 +217,8 @@ "error": { "deps_install_failed": "⚠️ Dependency installation failed: {error}", "deps_install_manual": "The project was created successfully, but you'll need to install dependencies manually.", - "suggested_command": "Suggested command:" + "suggested_command": "Suggested command:", + "deps_install_unknown": "Unknown error" }, "success": { "ready": "✅ Success! Your project is ready.", @@ -212,6 +230,13 @@ }, "release": { "command_description": "Helps with versioning and releasing the project.", + "workflow_completed": "Release workflow completed successfully.", + "step_detecting_strategy": "Detecting release strategy...", + "step_calculating_bump": "Calculating recommended version bump...", + "step_determining_bumps": "Determining package version bumps...", + "independent_mode_preparing_plan": "Preparing independent release plan...", + "step_completed": "Release completed successfully.", + "independent_plan_entry": "{package} -> {currentVersion} → {newVersion} ({bumpType})", "start": "🚀 Starting release process...", "error_structure": "✖ Could not determine project structure. Are you in the root of a monorepo with a `packages` directory?", "detected_strategy": "ℹ️ Detected versioning strategy: {strategy}", @@ -249,7 +274,22 @@ "command_description": "Validates if a string is a conventional commit message.", "option_message_description": "The commit message string to validate.", "success": "✔ Valid: This is a valid conventional commit message.", - "error_invalid": "✖ Invalid: This is not a valid conventional commit message." + "error_invalid": "✖ Invalid: This is not a valid conventional commit message.", + "project": { + "missing_readme": "README.md is missing.", + "missing_gitignore": ".gitignore is missing.", + "git_not_initialized": "Git repository not initialized.", + "missing_package_json": "package.json is missing for a Node-based stack.", + "missing_lockfile": "No lockfile found (package-lock.json or yarn.lock).", + "missing_husky": "Husky directory (.husky) not found.", + "missing_commit_msg_hook": "Husky commit-msg hook not found.", + "missing_tsconfig": "tsconfig.json is missing for a TypeScript-based project.", + "missing_pyproject_or_requirements": "Neither pyproject.toml nor requirements.txt found for Python project.", + "missing_java_build_file": "No Java build file found (pom.xml, build.gradle, or build.gradle.kts).", + "missing_go_mod": "go.mod is missing for Go project.", + "missing_composer_json": "composer.json is missing for PHP project.", + "missing_composer_lock": "composer.lock not found. Consider committing a lockfile for reproducible installs." + } }, "yargs": { "Commands:": "Commands:", @@ -286,32 +326,32 @@ "open_project_config_description": "Edit .stackcoderc.json file", "create_project_config": "Create Project Config", "create_project_config_description": "Create a new .stackcoderc.json file", + "stackcoderc_exists_overwrite": ".stackcoderc.json already exists. Overwrite?", + "overwrite": "Overwrite", "what_would_you_like_configure": "What would you like to configure?", "stackcoderc_not_found": ".stackcoderc.json file not found. Use \"Create Project Config\" to create one.", "project_configuration_initialized": "Project configuration initialized!", - "failed_open_configuration": "Failed to open configuration:" + "failed_open_configuration": "Failed to open configuration:", + "failed_create_config": "Failed to create project configuration: {error}" + }, + "release": { + "are_you_sure_create_release": "Are you sure you want to create a release?", + "create_release": "Create Release", + "creating_release": "Creating release..." }, "commit": { "commit_dialog_opened": "Commit dialog opened in terminal!", "failed_open_commit_dialog": "Failed to open commit dialog:" }, - "release": { - "release_process_started": "Release process started! Check terminal for progress.", - "failed_create_release": "Failed to create release:", - "are_you_sure_create_release": "Are you sure you want to create a new release? This will tag the current commit and publish the release.", - "create_release": "Create Release", - "creating_release": "Creating release", - "preparing_release": "Preparing release...", - "creating_release_message": "Creating release...", - "release_created": "Release created!" - }, "validate": { "failed_validate_project": "Failed to validate project:", "validating_project_structure": "Validating project structure", "running_validation": "Running validation...", "checking_project_structure": "Checking project structure...", "validation_completed": "Validation completed!", - "project_validation_completed": "Project validation completed! Check terminal for results." + "project_validation_completed": "Project validation completed! Check terminal for results.", + "issues_summary": "Found {count} issue(s):", + "enter_commit_message": "Enter a commit message to validate" }, "init": { "enter_project_name": "Enter project name", @@ -332,8 +372,21 @@ "initializing_project": "Initializing project {projectName}", "setting_up_structure": "Setting up project structure...", "running_stackcode_cli": "Running StackCode CLI...", + "step": { + "save_config": "Saving StackCode configuration..." + }, "open_project": "Open Project", "later": "Later", + "features": { + "docker": { + "label": "Docker support", + "description": "Adds Docker configuration to the project" + }, + "husky": { + "label": "Husky commit hooks", + "description": "Installs Husky to enforce commit conventions" + } + }, "stacks": { "node_ts": "Node.js with TypeScript", "react": "React application", diff --git a/packages/i18n/dist/locales/pt.json b/packages/i18n/dist/locales/pt.json index 64171e40..3b280e11 100644 --- a/packages/i18n/dist/locales/pt.json +++ b/packages/i18n/dist/locales/pt.json @@ -4,6 +4,7 @@ "error_generic": "✖ Ocorreu um erro.", "yes": "Sim", "no": "Não", + "continue": "Continuar", "error_demand_command": "Você precisa especificar pelo menos um comando.", "educate_flag_description": "Ativar modo educacional com explicações detalhadas", "back": "Voltar", @@ -87,6 +88,8 @@ "login_github": "Entrar no GitHub", "error_loading_issues": "Erro ao carregar issues", "loading_issues": "Carregando issues...", + "fetching_issues": "Buscando issues do GitHub...", + "please_wait": "Por favor, aguarde enquanto carregamos as issues", "view_on_github": "Ver no GitHub", "created_by": "Criado por", "updated": "Atualizado", @@ -109,6 +112,19 @@ "select_issues_to_link": "Selecione as issues que este commit resolve", "select_at_least_one_issue": "Selecione pelo menos uma issue ou cancele" }, + "output_channel_title": "Mensagem de commit", + "placeholder": { + "issue_reference": "fecha #123" + }, + "issues": { + "reference_entry": "fecha #{issueNumber}" + }, + "progress": { + "checking_staged": "Verificando alterações preparadas...", + "building_message": "Construindo mensagem de commit...", + "committing": "Executando git commit...", + "completed": "Commit concluído com sucesso." + }, "types": { "feat": "feat: Uma nova funcionalidade", "fix": "fix: Uma correção de bug", @@ -222,7 +238,8 @@ "error": { "deps_install_failed": "⚠️ Falha na instalação de dependências: {error}", "deps_install_manual": "O projeto foi criado com sucesso, mas você precisará instalar as dependências manualmente.", - "suggested_command": "Comando sugerido:" + "suggested_command": "Comando sugerido:", + "deps_install_unknown": "Erro desconhecido" }, "success": { "ready": "✅ Sucesso! Seu projeto está pronto.", @@ -234,6 +251,13 @@ }, "release": { "command_description": "Auxilia com versionamento e release do projeto.", + "workflow_completed": "Fluxo de release concluído com sucesso.", + "step_detecting_strategy": "Detectando estratégia de versionamento...", + "step_calculating_bump": "Calculando incremento de versão recomendado...", + "step_determining_bumps": "Determinando incrementos de versão por pacote...", + "independent_mode_preparing_plan": "Preparando plano de release independente...", + "step_completed": "Release concluído com sucesso.", + "independent_plan_entry": "{package} -> {currentVersion} → {newVersion} ({bumpType})", "start": "🚀 Iniciando processo de release...", "error_structure": "✖ Não foi possível determinar a estrutura do projeto. Você está na raiz de um monorepo com um diretório `packages`?", "detected_strategy": "ℹ️ Estratégia de versionamento detectada: {strategy}", @@ -271,7 +295,22 @@ "command_description": "Valida se uma string é uma mensagem de commit convencional.", "option_message_description": "A mensagem de commit a ser validada.", "success": "✔ Válido: Esta é uma mensagem de commit convencional válida.", - "error_invalid": "✖ Inválido: Esta não é uma mensagem de commit convencional válida." + "error_invalid": "✖ Inválido: Esta não é uma mensagem de commit convencional válida.", + "project": { + "missing_readme": "README.md não encontrado.", + "missing_gitignore": ".gitignore não encontrado.", + "git_not_initialized": "Repositório Git não inicializado.", + "missing_package_json": "package.json ausente para uma stack baseada em Node.", + "missing_lockfile": "Nenhum lockfile encontrado (package-lock.json ou yarn.lock).", + "missing_husky": "Diretório do Husky (.husky) não encontrado.", + "missing_commit_msg_hook": "Hook commit-msg do Husky não encontrado.", + "missing_tsconfig": "tsconfig.json ausente para projeto baseado em TypeScript.", + "missing_pyproject_or_requirements": "Nem pyproject.toml nem requirements.txt foram encontrados para projeto Python.", + "missing_java_build_file": "Nenhum arquivo de build Java encontrado (pom.xml, build.gradle ou build.gradle.kts).", + "missing_go_mod": "go.mod ausente para projeto Go.", + "missing_composer_json": "composer.json ausente para projeto PHP.", + "missing_composer_lock": "composer.lock não encontrado. Considere versionar um lockfile para instalações reproduzíveis." + } }, "yargs": { "Commands:": "Comandos:", @@ -308,24 +347,22 @@ "open_project_config_description": "Editar arquivo .stackcoderc.json", "create_project_config": "Criar Configuração do Projeto", "create_project_config_description": "Criar um novo arquivo .stackcoderc.json", + "stackcoderc_exists_overwrite": ".stackcoderc.json já existe. Deseja sobrescrever?", + "overwrite": "Sobrescrever", "what_would_you_like_configure": "O que você gostaria de configurar?", "stackcoderc_not_found": "Arquivo .stackcoderc.json não encontrado. Use \"Criar Configuração do Projeto\" para criar um.", "project_configuration_initialized": "Configuração do projeto inicializada!", - "failed_open_configuration": "Falha ao abrir configuração:" + "failed_open_configuration": "Falha ao abrir configuração:", + "failed_create_config": "Falha ao criar configuração do projeto: {error}" }, "commit": { "commit_dialog_opened": "Diálogo de commit aberto no terminal!", "failed_open_commit_dialog": "Falha ao abrir diálogo de commit:" }, "release": { - "release_process_started": "Processo de release iniciado! Verifique o terminal para progresso.", - "failed_create_release": "Falha ao criar release:", - "are_you_sure_create_release": "Tem certeza de que deseja criar um novo release? Isso irá marcar o commit atual e publicar o release.", - "create_release": "Criar Release", - "creating_release": "Criando release", - "preparing_release": "Preparando release...", - "creating_release_message": "Criando release...", - "release_created": "Release criado!" + "are_you_sure_create_release": "Tem certeza de que deseja criar um release?", + "create_release": "Criar release", + "creating_release": "Criando release..." }, "validate": { "failed_validate_project": "Falha ao validar projeto:", @@ -333,7 +370,9 @@ "running_validation": "Executando validação...", "checking_project_structure": "Verificando estrutura do projeto...", "validation_completed": "Validação concluída!", - "project_validation_completed": "Validação do projeto concluída! Verifique o terminal para resultados." + "project_validation_completed": "Validação do projeto concluída! Verifique o terminal para resultados.", + "issues_summary": "Encontrado(s) {count} problema(s):", + "enter_commit_message": "Digite uma mensagem de commit para validar" }, "init": { "enter_project_name": "Digite o nome do projeto", @@ -354,8 +393,21 @@ "initializing_project": "Inicializando projeto {projectName}", "setting_up_structure": "Configurando estrutura do projeto...", "running_stackcode_cli": "Executando StackCode CLI...", + "step": { + "save_config": "Salvando configuração do StackCode..." + }, "open_project": "Abrir Projeto", "later": "Mais tarde", + "features": { + "docker": { + "label": "Suporte Docker", + "description": "Adiciona configuração Docker ao projeto" + }, + "husky": { + "label": "Hooks Husky", + "description": "Instala o Husky para reforçar convenções de commit" + } + }, "stacks": { "node_ts": "Node.js com TypeScript", "react": "Aplicação React", diff --git a/packages/i18n/src/locales/en.json b/packages/i18n/src/locales/en.json index 0d323d2c..75b7963a 100644 --- a/packages/i18n/src/locales/en.json +++ b/packages/i18n/src/locales/en.json @@ -4,6 +4,8 @@ "error_generic": "✖ An error occurred.", "yes": "Yes", "no": "No", + "continue": "Continue", + "cancel": "Cancel", "error_demand_command": "You need to specify at least one command.", "educate_flag_description": "Enable educational mode with detailed explanations" }, @@ -68,6 +70,8 @@ "login_github": "Login to GitHub", "error_loading_issues": "Error loading issues", "loading_issues": "Loading issues...", + "fetching_issues": "Fetching issues from GitHub...", + "please_wait": "Please wait while we load the issues", "view_on_github": "View on GitHub", "created_by": "Created by", "updated": "Updated", @@ -87,6 +91,19 @@ "breaking_changes": "Are there any BREAKING CHANGES? (optional)", "affected_issues": "Does this change affect any open issues? (e.g. 'closes #123') (optional)" }, + "output_channel_title": "Commit message", + "placeholder": { + "issue_reference": "closes #123" + }, + "issues": { + "reference_entry": "closes #{issueNumber}" + }, + "progress": { + "checking_staged": "Checking staged changes...", + "building_message": "Building commit message...", + "committing": "Running git commit...", + "completed": "Commit completed successfully." + }, "types": { "feat": "feat: A new feature", "fix": "fix: A bug fix", @@ -200,7 +217,8 @@ "error": { "deps_install_failed": "⚠️ Dependency installation failed: {error}", "deps_install_manual": "The project was created successfully, but you'll need to install dependencies manually.", - "suggested_command": "Suggested command:" + "suggested_command": "Suggested command:", + "deps_install_unknown": "Unknown error" }, "success": { "ready": "✅ Success! Your project is ready.", @@ -212,6 +230,13 @@ }, "release": { "command_description": "Helps with versioning and releasing the project.", + "workflow_completed": "Release workflow completed successfully.", + "step_detecting_strategy": "Detecting release strategy...", + "step_calculating_bump": "Calculating recommended version bump...", + "step_determining_bumps": "Determining package version bumps...", + "independent_mode_preparing_plan": "Preparing independent release plan...", + "step_completed": "Release completed successfully.", + "independent_plan_entry": "{package} -> {currentVersion} → {newVersion} ({bumpType})", "start": "🚀 Starting release process...", "error_structure": "✖ Could not determine project structure. Are you in the root of a monorepo with a `packages` directory?", "detected_strategy": "ℹ️ Detected versioning strategy: {strategy}", @@ -249,7 +274,22 @@ "command_description": "Validates if a string is a conventional commit message.", "option_message_description": "The commit message string to validate.", "success": "✔ Valid: This is a valid conventional commit message.", - "error_invalid": "✖ Invalid: This is not a valid conventional commit message." + "error_invalid": "✖ Invalid: This is not a valid conventional commit message.", + "project": { + "missing_readme": "README.md is missing.", + "missing_gitignore": ".gitignore is missing.", + "git_not_initialized": "Git repository not initialized.", + "missing_package_json": "package.json is missing for a Node-based stack.", + "missing_lockfile": "No lockfile found (package-lock.json or yarn.lock).", + "missing_husky": "Husky directory (.husky) not found.", + "missing_commit_msg_hook": "Husky commit-msg hook not found.", + "missing_tsconfig": "tsconfig.json is missing for a TypeScript-based project.", + "missing_pyproject_or_requirements": "Neither pyproject.toml nor requirements.txt found for Python project.", + "missing_java_build_file": "No Java build file found (pom.xml, build.gradle, or build.gradle.kts).", + "missing_go_mod": "go.mod is missing for Go project.", + "missing_composer_json": "composer.json is missing for PHP project.", + "missing_composer_lock": "composer.lock not found. Consider committing a lockfile for reproducible installs." + } }, "yargs": { "Commands:": "Commands:", @@ -286,32 +326,32 @@ "open_project_config_description": "Edit .stackcoderc.json file", "create_project_config": "Create Project Config", "create_project_config_description": "Create a new .stackcoderc.json file", + "stackcoderc_exists_overwrite": ".stackcoderc.json already exists. Overwrite?", + "overwrite": "Overwrite", "what_would_you_like_configure": "What would you like to configure?", "stackcoderc_not_found": ".stackcoderc.json file not found. Use \"Create Project Config\" to create one.", "project_configuration_initialized": "Project configuration initialized!", - "failed_open_configuration": "Failed to open configuration:" + "failed_open_configuration": "Failed to open configuration:", + "failed_create_config": "Failed to create project configuration: {error}" + }, + "release": { + "are_you_sure_create_release": "Are you sure you want to create a release?", + "create_release": "Create Release", + "creating_release": "Creating release..." }, "commit": { "commit_dialog_opened": "Commit dialog opened in terminal!", "failed_open_commit_dialog": "Failed to open commit dialog:" }, - "release": { - "release_process_started": "Release process started! Check terminal for progress.", - "failed_create_release": "Failed to create release:", - "are_you_sure_create_release": "Are you sure you want to create a new release? This will tag the current commit and publish the release.", - "create_release": "Create Release", - "creating_release": "Creating release", - "preparing_release": "Preparing release...", - "creating_release_message": "Creating release...", - "release_created": "Release created!" - }, "validate": { "failed_validate_project": "Failed to validate project:", "validating_project_structure": "Validating project structure", "running_validation": "Running validation...", "checking_project_structure": "Checking project structure...", "validation_completed": "Validation completed!", - "project_validation_completed": "Project validation completed! Check terminal for results." + "project_validation_completed": "Project validation completed! Check terminal for results.", + "issues_summary": "Found {count} issue(s):", + "enter_commit_message": "Enter a commit message to validate" }, "init": { "enter_project_name": "Enter project name", @@ -332,8 +372,21 @@ "initializing_project": "Initializing project {projectName}", "setting_up_structure": "Setting up project structure...", "running_stackcode_cli": "Running StackCode CLI...", + "step": { + "save_config": "Saving StackCode configuration..." + }, "open_project": "Open Project", "later": "Later", + "features": { + "docker": { + "label": "Docker support", + "description": "Adds Docker configuration to the project" + }, + "husky": { + "label": "Husky commit hooks", + "description": "Installs Husky to enforce commit conventions" + } + }, "stacks": { "node_ts": "Node.js with TypeScript", "react": "React application", diff --git a/packages/i18n/src/locales/es.json b/packages/i18n/src/locales/es.json index 37952fe9..07b9d5ca 100644 --- a/packages/i18n/src/locales/es.json +++ b/packages/i18n/src/locales/es.json @@ -4,6 +4,8 @@ "error_generic": "✖ Ocurrió un error.", "yes": "Sí", "no": "No", + "continue": "Continuar", + "cancel": "Cancelar", "error_demand_command": "Necesitas especificar al menos un comando.", "educate_flag_description": "Habilitar modo educativo con explicaciones detalladas" }, @@ -68,6 +70,8 @@ "login_github": "Iniciar sesión en GitHub", "error_loading_issues": "Error cargando issues", "loading_issues": "Cargando issues...", + "fetching_issues": "Obteniendo issues de GitHub...", + "please_wait": "Por favor espere mientras cargamos los issues", "view_on_github": "Ver en GitHub", "created_by": "Creado por", "updated": "Actualizado", @@ -87,6 +91,19 @@ "breaking_changes": "¿Hay algún BREAKING CHANGE? (opcional)", "affected_issues": "¿Este cambio afecta algún issue abierto? (ej. 'closes #123') (opcional)" }, + "output_channel_title": "Mensaje de commit", + "placeholder": { + "issue_reference": "cierra #123" + }, + "issues": { + "reference_entry": "cierra #{issueNumber}" + }, + "progress": { + "checking_staged": "Comprobando cambios preparados...", + "building_message": "Construyendo mensaje de commit...", + "committing": "Ejecutando git commit...", + "completed": "Commit completado con éxito." + }, "types": { "feat": "feat: Una nueva funcionalidad", "fix": "fix: Una corrección de bug", @@ -212,6 +229,13 @@ }, "release": { "command_description": "Ayuda con el versionado y lanzamiento del proyecto.", + "workflow_completed": "Flujo de publicación completado con éxito.", + "step_detecting_strategy": "Detectando estrategia de versión...", + "step_calculating_bump": "Calculando incremento de versión recomendado...", + "step_determining_bumps": "Determinando incrementos de versión por paquete...", + "independent_mode_preparing_plan": "Preparando plan de lanzamiento independiente...", + "step_completed": "Publicación completada con éxito.", + "independent_plan_entry": "{package} -> {currentVersion} → {newVersion} ({bumpType})", "start": "🚀 Iniciando proceso de release...", "error_structure": "✖ No se pudo determinar la estructura del proyecto. ¿Estás en la raíz de un monorepo con directorio `packages`?", "detected_strategy": "ℹ️ Estrategia de versionado detectada: {strategy}", @@ -298,24 +322,22 @@ "open_project_config_description": "Editar archivo .stackcoderc.json", "create_project_config": "Crear Configuración del Proyecto", "create_project_config_description": "Crear un nuevo archivo .stackcoderc.json", + "stackcoderc_exists_overwrite": ".stackcoderc.json ya existe. ¿Sobrescribir?", + "overwrite": "Sobrescribir", "what_would_you_like_configure": "¿Qué te gustaría configurar?", "stackcoderc_not_found": "Archivo .stackcoderc.json no encontrado. Usa \"Crear Configuración del Proyecto\" para crear uno.", "project_configuration_initialized": "¡Configuración del proyecto inicializada!", - "failed_open_configuration": "Falló abrir configuración:" + "failed_open_configuration": "Falló abrir configuración:", + "failed_create_config": "Error al crear la configuración del proyecto: {error}" }, "commit": { "commit_dialog_opened": "¡Diálogo de commit abierto en terminal!", "failed_open_commit_dialog": "Falló abrir diálogo de commit:" }, "release": { - "release_process_started": "¡Proceso de release iniciado! Verifica el terminal para el progreso.", - "failed_create_release": "Falló crear release:", - "are_you_sure_create_release": "¿Estás seguro de que quieres crear un nuevo release? Esto etiquetará el commit actual y publicará el release.", - "create_release": "Crear Release", - "creating_release": "Creando release", - "preparing_release": "Preparando release...", - "creating_release_message": "Creando release...", - "release_created": "¡Release creado!" + "are_you_sure_create_release": "¿Estás seguro de que quieres crear un lanzamiento?", + "create_release": "Crear lanzamiento", + "creating_release": "Creando lanzamiento..." }, "validate": { "failed_validate_project": "Falló validar proyecto:", diff --git a/packages/i18n/src/locales/pt.json b/packages/i18n/src/locales/pt.json index 64171e40..3b280e11 100644 --- a/packages/i18n/src/locales/pt.json +++ b/packages/i18n/src/locales/pt.json @@ -4,6 +4,7 @@ "error_generic": "✖ Ocorreu um erro.", "yes": "Sim", "no": "Não", + "continue": "Continuar", "error_demand_command": "Você precisa especificar pelo menos um comando.", "educate_flag_description": "Ativar modo educacional com explicações detalhadas", "back": "Voltar", @@ -87,6 +88,8 @@ "login_github": "Entrar no GitHub", "error_loading_issues": "Erro ao carregar issues", "loading_issues": "Carregando issues...", + "fetching_issues": "Buscando issues do GitHub...", + "please_wait": "Por favor, aguarde enquanto carregamos as issues", "view_on_github": "Ver no GitHub", "created_by": "Criado por", "updated": "Atualizado", @@ -109,6 +112,19 @@ "select_issues_to_link": "Selecione as issues que este commit resolve", "select_at_least_one_issue": "Selecione pelo menos uma issue ou cancele" }, + "output_channel_title": "Mensagem de commit", + "placeholder": { + "issue_reference": "fecha #123" + }, + "issues": { + "reference_entry": "fecha #{issueNumber}" + }, + "progress": { + "checking_staged": "Verificando alterações preparadas...", + "building_message": "Construindo mensagem de commit...", + "committing": "Executando git commit...", + "completed": "Commit concluído com sucesso." + }, "types": { "feat": "feat: Uma nova funcionalidade", "fix": "fix: Uma correção de bug", @@ -222,7 +238,8 @@ "error": { "deps_install_failed": "⚠️ Falha na instalação de dependências: {error}", "deps_install_manual": "O projeto foi criado com sucesso, mas você precisará instalar as dependências manualmente.", - "suggested_command": "Comando sugerido:" + "suggested_command": "Comando sugerido:", + "deps_install_unknown": "Erro desconhecido" }, "success": { "ready": "✅ Sucesso! Seu projeto está pronto.", @@ -234,6 +251,13 @@ }, "release": { "command_description": "Auxilia com versionamento e release do projeto.", + "workflow_completed": "Fluxo de release concluído com sucesso.", + "step_detecting_strategy": "Detectando estratégia de versionamento...", + "step_calculating_bump": "Calculando incremento de versão recomendado...", + "step_determining_bumps": "Determinando incrementos de versão por pacote...", + "independent_mode_preparing_plan": "Preparando plano de release independente...", + "step_completed": "Release concluído com sucesso.", + "independent_plan_entry": "{package} -> {currentVersion} → {newVersion} ({bumpType})", "start": "🚀 Iniciando processo de release...", "error_structure": "✖ Não foi possível determinar a estrutura do projeto. Você está na raiz de um monorepo com um diretório `packages`?", "detected_strategy": "ℹ️ Estratégia de versionamento detectada: {strategy}", @@ -271,7 +295,22 @@ "command_description": "Valida se uma string é uma mensagem de commit convencional.", "option_message_description": "A mensagem de commit a ser validada.", "success": "✔ Válido: Esta é uma mensagem de commit convencional válida.", - "error_invalid": "✖ Inválido: Esta não é uma mensagem de commit convencional válida." + "error_invalid": "✖ Inválido: Esta não é uma mensagem de commit convencional válida.", + "project": { + "missing_readme": "README.md não encontrado.", + "missing_gitignore": ".gitignore não encontrado.", + "git_not_initialized": "Repositório Git não inicializado.", + "missing_package_json": "package.json ausente para uma stack baseada em Node.", + "missing_lockfile": "Nenhum lockfile encontrado (package-lock.json ou yarn.lock).", + "missing_husky": "Diretório do Husky (.husky) não encontrado.", + "missing_commit_msg_hook": "Hook commit-msg do Husky não encontrado.", + "missing_tsconfig": "tsconfig.json ausente para projeto baseado em TypeScript.", + "missing_pyproject_or_requirements": "Nem pyproject.toml nem requirements.txt foram encontrados para projeto Python.", + "missing_java_build_file": "Nenhum arquivo de build Java encontrado (pom.xml, build.gradle ou build.gradle.kts).", + "missing_go_mod": "go.mod ausente para projeto Go.", + "missing_composer_json": "composer.json ausente para projeto PHP.", + "missing_composer_lock": "composer.lock não encontrado. Considere versionar um lockfile para instalações reproduzíveis." + } }, "yargs": { "Commands:": "Comandos:", @@ -308,24 +347,22 @@ "open_project_config_description": "Editar arquivo .stackcoderc.json", "create_project_config": "Criar Configuração do Projeto", "create_project_config_description": "Criar um novo arquivo .stackcoderc.json", + "stackcoderc_exists_overwrite": ".stackcoderc.json já existe. Deseja sobrescrever?", + "overwrite": "Sobrescrever", "what_would_you_like_configure": "O que você gostaria de configurar?", "stackcoderc_not_found": "Arquivo .stackcoderc.json não encontrado. Use \"Criar Configuração do Projeto\" para criar um.", "project_configuration_initialized": "Configuração do projeto inicializada!", - "failed_open_configuration": "Falha ao abrir configuração:" + "failed_open_configuration": "Falha ao abrir configuração:", + "failed_create_config": "Falha ao criar configuração do projeto: {error}" }, "commit": { "commit_dialog_opened": "Diálogo de commit aberto no terminal!", "failed_open_commit_dialog": "Falha ao abrir diálogo de commit:" }, "release": { - "release_process_started": "Processo de release iniciado! Verifique o terminal para progresso.", - "failed_create_release": "Falha ao criar release:", - "are_you_sure_create_release": "Tem certeza de que deseja criar um novo release? Isso irá marcar o commit atual e publicar o release.", - "create_release": "Criar Release", - "creating_release": "Criando release", - "preparing_release": "Preparando release...", - "creating_release_message": "Criando release...", - "release_created": "Release criado!" + "are_you_sure_create_release": "Tem certeza de que deseja criar um release?", + "create_release": "Criar release", + "creating_release": "Criando release..." }, "validate": { "failed_validate_project": "Falha ao validar projeto:", @@ -333,7 +370,9 @@ "running_validation": "Executando validação...", "checking_project_structure": "Verificando estrutura do projeto...", "validation_completed": "Validação concluída!", - "project_validation_completed": "Validação do projeto concluída! Verifique o terminal para resultados." + "project_validation_completed": "Validação do projeto concluída! Verifique o terminal para resultados.", + "issues_summary": "Encontrado(s) {count} problema(s):", + "enter_commit_message": "Digite uma mensagem de commit para validar" }, "init": { "enter_project_name": "Digite o nome do projeto", @@ -354,8 +393,21 @@ "initializing_project": "Inicializando projeto {projectName}", "setting_up_structure": "Configurando estrutura do projeto...", "running_stackcode_cli": "Executando StackCode CLI...", + "step": { + "save_config": "Salvando configuração do StackCode..." + }, "open_project": "Abrir Projeto", "later": "Mais tarde", + "features": { + "docker": { + "label": "Suporte Docker", + "description": "Adiciona configuração Docker ao projeto" + }, + "husky": { + "label": "Hooks Husky", + "description": "Instala o Husky para reforçar convenções de commit" + } + }, "stacks": { "node_ts": "Node.js com TypeScript", "react": "Aplicação React", diff --git a/packages/vscode-extension/IMPLEMENTATION.md b/packages/vscode-extension/IMPLEMENTATION.md deleted file mode 100644 index 5cbf15ef..00000000 --- a/packages/vscode-extension/IMPLEMENTATION.md +++ /dev/null @@ -1,202 +0,0 @@ -# StackCode VS Code Extension - Implementation Summary - -## 🚀 Complete Implementation Overview - -A extensão VS Code do StackCode foi completamente reestruturada para ser uma versão completa da CLI, não apenas notificações. Agora oferece todas as funcionalidades do CLI com uma interface visual integrada. - -## 📦 Estrutura Implementada - -### Core Architecture - -``` -src/ -├── extension.ts # Ponto de entrada principal -├── config/ -│ └── ConfigurationManager.ts # Gerenciamento de configurações -├── commands/ # Todos os comandos da CLI -│ ├── BaseCommand.ts # Classe base para comandos -│ ├── InitCommand.ts # Inicialização de projetos -│ ├── GenerateCommand.ts # Geração de arquivos -│ ├── GitCommand.ts # Operações Git/Gitflow -│ ├── CommitCommand.ts # Commits convencionais -│ ├── ValidateCommand.ts # Validação de projetos -│ ├── ReleaseCommand.ts # Gerenciamento de releases -│ └── ConfigCommand.ts # Configurações -├── monitors/ # Sistema de monitoramento -│ ├── GitMonitor.ts # Monitor de Git/branches -│ └── FileMonitor.ts # Monitor de arquivos -├── notifications/ -│ └── ProactiveNotificationManager.ts # Notificações proativas -├── providers/ # Provedores de interface -│ ├── DashboardProvider.ts # Dashboard visual -│ └── ProjectViewProvider.ts # Visão de projeto -└── types.ts # Definições de tipos -``` - -## ✨ Funcionalidades Implementadas - -### 1. Integração Completa da CLI - -- **Inicialização**: `stackcode.init` - Scaffolding completo de projetos -- **Geração**: `stackcode.generate.*` - README, .gitignore, etc. -- **Git Workflow**: `stackcode.git.*` - Start/finish branches com Gitflow -- **Commits**: `stackcode.commit` - Builder de mensagens convencionais -- **Validação**: `stackcode.validate` - Auditoria de estrutura do projeto -- **Releases**: `stackcode.release` - Gerenciamento de versões -- **Configuração**: `stackcode.config` - Configurações de projeto - -### 2. Sistema de Notificações Proativas - -- **Monitoramento de Branch**: Alertas quando trabalhando em main/develop -- **Validação de Commits**: Verificação de formato convencional -- **Estrutura de Projeto**: Sugestões para arquivos ausentes -- **Configurável**: Todas as notificações podem ser desabilitadas - -### 3. Interface Visual Avançada - -- **Dashboard Interativo**: Painel com acesso rápido a funcionalidades -- **Project View**: Visão hierárquica do projeto no Explorer -- **Context Menus**: Integração com menus de contexto do VS Code -- **Command Palette**: Todos os comandos disponíveis via Ctrl+Shift+P - -### 4. Configuração Abrangente - -```json -{ - "stackcode.notifications.enabled": true, - "stackcode.notifications.branchCheck": true, - "stackcode.notifications.commitCheck": true, - "stackcode.autoGenerate.readme": false, - "stackcode.autoGenerate.gitignore": true, - "stackcode.git.defaultBranchType": "feature", - "stackcode.dashboard.autoOpen": false -} -``` - -## 🎯 Diferencial da Implementação - -### Antes (Apenas Notificações) - -- Notificações básicas de branch -- Validação simples de commits -- Comandos limitados - -### Agora (CLI Completa) - -- **Todas as funcionalidades da CLI** disponíveis no VS Code -- **Interface visual** com dashboard e project view -- **Integração nativa** com Git e sistema de arquivos do VS Code -- **Experiência unificada** entre CLI e extensão -- **Configuração granular** para personalização -- **Arquitetura extensível** para futuras funcionalidades - -## 🔧 Comandos Disponíveis - -### Project Management - -- `StackCode: Initialize New Project` - Setup completo com scaffolding -- `StackCode: Generate README.md` - Geração de documentação -- `StackCode: Generate .gitignore` - Geração baseada em stack -- `StackCode: Validate Project Structure` - Auditoria completa - -### Git Workflow - -- `StackCode: Start New Feature Branch` - Gitflow branch creation -- `StackCode: Finish Current Branch` - Merge e cleanup -- `StackCode: Create Conventional Commit` - Builder interativo - -### Configuration & Management - -- `StackCode: Create Release` - Versionamento automático -- `StackCode: Open Configuration` - Gerenciamento de configs -- `StackCode: Open StackCode Dashboard` - Interface visual - -## 🚀 Roadmap para Próximas Iterações - -### Fase 1: Funcionalidade Base ✅ - -- [x] Integração completa da CLI -- [x] Sistema de notificações proativas -- [x] Interface visual básica -- [x] Configuração abrangente - -### Fase 2: Melhorias de Interface (Próxima) - -- [ ] Templates visuais para geração de arquivos -- [ ] Wizard interativo para inicialização -- [ ] Preview de arquivos antes da geração -- [ ] Integração com Git Graph - -### Fase 3: Recursos Avançados - -- [ ] Integração com GitHub/GitLab -- [ ] Templates customizáveis -- [ ] Workflows de equipe -- [ ] Analytics de desenvolvimento - -### Fase 4: Inteligência Artificial - -- [ ] Sugestões baseadas em IA -- [ ] Geração automática de documentação -- [ ] Otimizações de workflow personalizadas - -## 💡 Inovações Técnicas - -### 1. Arquitetura Modular - -- Comandos independentes e testáveis -- Sistema de providers para UI -- Monitoramento reativo de estado - -### 2. Integração Nativa - -- Uso da API do VS Code Git -- Integração com sistema de arquivos -- Aproveitamento de recursos nativos - -### 3. Experiência Unificada - -- Mesma funcionalidade CLI e extensão -- Configuração compartilhada -- Comandos mapeados 1:1 - -## 🔄 Fluxo de Desenvolvimento Seguindo GitFlow - -### Branch Strategy - -``` -develop (main) ← feature/vscode-proactive-notifications -``` - -### Commit Convention - -``` -feat(vscode): implement complete CLI integration with proactive notifications - -BREAKING CHANGE: Extension now provides complete CLI functionality -``` - -## 📈 Métricas de Sucesso - -### Implementação Atual - -- **23 arquivos** criados/modificados -- **2249 linhas** de código adicionadas -- **Cobertura completa** de funcionalidades CLI -- **Arquitetura escalável** para futuras features - -### Objetivos Alcançados - -- ✅ Extensão não é mais apenas notificações -- ✅ Funcionalidade completa da CLI disponível -- ✅ Interface visual moderna e intuitiva -- ✅ Sistema de configuração robusto -- ✅ Experiência de desenvolvimento aprimorada - -## 🎉 Conclusão - -A extensão VS Code do StackCode agora oferece uma experiência completa de desenvolvimento, integrando todas as funcionalidades da CLI em uma interface visual moderna. O projeto está preparado para crescer e se adaptar às necessidades futuras dos desenvolvedores, mantendo sempre o foco em qualidade de código e melhores práticas. - ---- - -**Próximos passos**: Merge para develop, testes de integração e preparação para release. diff --git a/packages/vscode-extension/jest.config.json b/packages/vscode-extension/jest.config.json index 104e04e8..200ac0b8 100644 --- a/packages/vscode-extension/jest.config.json +++ b/packages/vscode-extension/jest.config.json @@ -4,12 +4,20 @@ "rootDir": ".", "testMatch": ["/src/test/**/*.test.ts"], "testPathIgnorePatterns": ["/node_modules/", "/out/", "../../"], - "collectCoverageFrom": ["src/**/*.ts", "!src/test/**"], + "collectCoverageFrom": [ + "src/**/*.ts", + "!src/test/**", + "!src/webview-ui/**", + "!src/**/*.d.ts" + ], "moduleFileExtensions": ["ts", "js", "json"], "transform": { "^.+\\.ts$": "ts-jest" }, + "transformIgnorePatterns": ["node_modules/(?!(@stackcode)/)"], "moduleNameMapper": { - "^vscode$": "/src/test/__mocks__/vscode.ts" + "^vscode$": "/src/test/__mocks__/vscode.ts", + "^@stackcode/core$": "/src/test/__mocks__/core-workflows.ts", + "^@stackcode/i18n$": "/src/test/__mocks__/i18n.ts" } } diff --git a/packages/vscode-extension/out/commands/AuthCommand.js b/packages/vscode-extension/out/commands/AuthCommand.js index 1efa7180..be26b0ad 100644 --- a/packages/vscode-extension/out/commands/AuthCommand.js +++ b/packages/vscode-extension/out/commands/AuthCommand.js @@ -1,143 +1,109 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if ( - !desc || - ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) - ) { - desc = { - enumerable: true, - get: function () { - return m[k]; - }, - }; - } - Object.defineProperty(o, k2, desc); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) - __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; - }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.AuthCommand = void 0; const vscode = __importStar(require("vscode")); const BaseCommand_1 = require("./BaseCommand"); /** - * AuthCommand - Gerencia comandos de autenticação GitHub + * AuthCommand - Manages GitHub authentication commands * - * Comandos disponíveis: + * Available commands: * - stackcode.auth.login: Inicia processo de login - * - stackcode.auth.logout: Remove autenticação + * - stackcode.auth.logout: Removes authentication */ class AuthCommand extends BaseCommand_1.BaseCommand { - constructor(authService) { - super(); - this._authService = authService; - } - /** - * Implementação do método abstrato - mostra status da autenticação - */ - async execute() { - await this.showStatus(); - } - /** - * Executa comando de login - */ - async executeLogin() { - try { - if (this._authService.isAuthenticated) { - const userInfo = this._authService.userInfo; - const result = await vscode.window.showInformationMessage( - `Already logged in as ${userInfo?.username}. Would you like to logout and login again?`, - "Yes, re-login", - "Cancel", - ); - if (result === "Yes, re-login") { - await this._authService.logout(); - await this._authService.login(); + constructor(authService) { + super(); + this._authService = authService; + } + /** + * Abstract method implementation - shows authentication status + */ + async execute() { + await this.showStatus(); + } + /** + * Executa comando de login + */ + async executeLogin() { + try { + if (this._authService.isAuthenticated) { + const userInfo = this._authService.userInfo; + const result = await vscode.window.showInformationMessage(`Already logged in as ${userInfo?.username}. Would you like to logout and login again?`, "Yes, re-login", "Cancel"); + if (result === "Yes, re-login") { + await this._authService.logout(); + await this._authService.login(); + } + return; + } + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: "Authenticating with GitHub...", + cancellable: false, + }, async () => { + await this._authService.login(); + }); + } + catch (error) { + console.error("[AuthCommand] Login failed:", error); } - return; - } - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: "Authenticating with GitHub...", - cancellable: false, - }, - async () => { - await this._authService.login(); - }, - ); - } catch (error) { - console.error("[AuthCommand] Login failed:", error); } - } - /** - * Executa comando de logout - */ - async executeLogout() { - try { - if (!this._authService.isAuthenticated) { - vscode.window.showInformationMessage("You are not logged in to GitHub"); - return; - } - const userInfo = this._authService.userInfo; - const result = await vscode.window.showWarningMessage( - `Are you sure you want to logout from GitHub (${userInfo?.username})?`, - "Yes, logout", - "Cancel", - ); - if (result === "Yes, logout") { - await this._authService.logout(); - } - } catch (error) { - console.error("[AuthCommand] Logout failed:", error); + /** + * Executa comando de logout + */ + async executeLogout() { + try { + if (!this._authService.isAuthenticated) { + vscode.window.showInformationMessage("You are not logged in to GitHub"); + return; + } + const userInfo = this._authService.userInfo; + const result = await vscode.window.showWarningMessage(`Are you sure you want to logout from GitHub (${userInfo?.username})?`, "Yes, logout", "Cancel"); + if (result === "Yes, logout") { + await this._authService.logout(); + } + } + catch (error) { + console.error("[AuthCommand] Logout failed:", error); + } } - } - /** - * Mostra status atual da autenticação - */ - async showStatus() { - if (this._authService.isAuthenticated) { - const userInfo = this._authService.userInfo; - vscode.window.showInformationMessage( - `✅ Authenticated with GitHub as ${userInfo?.username}`, - ); - } else { - const result = await vscode.window.showInformationMessage( - "❌ Not authenticated with GitHub", - "Login now", - ); - if (result === "Login now") { - await this.executeLogin(); - } + /** + * Shows current authentication status + */ + async showStatus() { + if (this._authService.isAuthenticated) { + const userInfo = this._authService.userInfo; + vscode.window.showInformationMessage(`✅ Authenticated with GitHub as ${userInfo?.username}`); + } + else { + const result = await vscode.window.showInformationMessage("❌ Not authenticated with GitHub", "Login now"); + if (result === "Login now") { + await this.executeLogin(); + } + } } - } } exports.AuthCommand = AuthCommand; -//# sourceMappingURL=AuthCommand.js.map +//# sourceMappingURL=AuthCommand.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/commands/BaseCommand.js b/packages/vscode-extension/out/commands/BaseCommand.js index 26326af0..f076573e 100644 --- a/packages/vscode-extension/out/commands/BaseCommand.js +++ b/packages/vscode-extension/out/commands/BaseCommand.js @@ -1,84 +1,58 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if ( - !desc || - ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) - ) { - desc = { - enumerable: true, - get: function () { - return m[k]; - }, - }; - } - Object.defineProperty(o, k2, desc); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) - __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; - }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseCommand = void 0; const vscode = __importStar(require("vscode")); class BaseCommand { - async showError(message) { - vscode.window.showErrorMessage(`StackCode: ${message}`); - } - async showWarning(message) { - vscode.window.showWarningMessage(`StackCode: ${message}`); - } - async showInfo(message) { - vscode.window.showInformationMessage(`StackCode: ${message}`); - } - async showSuccess(message) { - vscode.window.showInformationMessage(`✅ ${message}`); - } - getCurrentWorkspaceFolder() { - return vscode.workspace.workspaceFolders?.[0]; - } - async runTerminalCommand(command, cwd) { - const terminal = vscode.window.createTerminal({ - name: "StackCode", - cwd: cwd || this.getCurrentWorkspaceFolder()?.uri.fsPath, - }); - terminal.sendText(command); - terminal.show(); - } - async confirmAction(message, confirmText = "Yes") { - const result = await vscode.window.showWarningMessage( - message, - { modal: true }, - confirmText, - "Cancel", - ); - return result === confirmText; - } + async showError(message) { + vscode.window.showErrorMessage(`StackCode: ${message}`); + } + async showWarning(message) { + vscode.window.showWarningMessage(`StackCode: ${message}`); + } + async showInfo(message) { + vscode.window.showInformationMessage(`StackCode: ${message}`); + } + async showSuccess(message) { + vscode.window.showInformationMessage(`✅ ${message}`); + } + getCurrentWorkspaceFolder() { + return vscode.workspace.workspaceFolders?.[0]; + } + async runTerminalCommand(command, cwd) { + const terminal = vscode.window.createTerminal({ + name: "StackCode", + cwd: cwd || this.getCurrentWorkspaceFolder()?.uri.fsPath, + }); + terminal.sendText(command); + terminal.show(); + } + async confirmAction(message, confirmText = "Yes", cancelText = "Cancel") { + const result = await vscode.window.showWarningMessage(message, { modal: true }, confirmText, cancelText); + return result === confirmText; + } } exports.BaseCommand = BaseCommand; -//# sourceMappingURL=BaseCommand.js.map +//# sourceMappingURL=BaseCommand.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/commands/BaseCommand.js.map b/packages/vscode-extension/out/commands/BaseCommand.js.map index 6b4c6573..7b0c6fe8 100644 --- a/packages/vscode-extension/out/commands/BaseCommand.js.map +++ b/packages/vscode-extension/out/commands/BaseCommand.js.map @@ -1 +1 @@ -{"version":3,"file":"BaseCommand.js","sourceRoot":"","sources":["../../src/commands/BaseCommand.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AAEjC,MAAsB,WAAW;IAGrB,KAAK,CAAC,SAAS,CAAC,OAAe;QACvC,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,cAAc,OAAO,EAAE,CAAC,CAAC;IAC1D,CAAC;IAES,KAAK,CAAC,WAAW,CAAC,OAAe;QACzC,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,cAAc,OAAO,EAAE,CAAC,CAAC;IAC5D,CAAC;IAES,KAAK,CAAC,QAAQ,CAAC,OAAe;QACtC,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,cAAc,OAAO,EAAE,CAAC,CAAC;IAChE,CAAC;IAES,KAAK,CAAC,WAAW,CAAC,OAAe;QACzC,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC;IACvD,CAAC;IAES,yBAAyB;QACjC,OAAO,MAAM,CAAC,SAAS,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC;IAES,KAAK,CAAC,kBAAkB,CAChC,OAAe,EACf,GAAY;QAEZ,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC;YAC5C,IAAI,EAAE,WAAW;YACjB,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,yBAAyB,EAAE,EAAE,GAAG,CAAC,MAAM;SACzD,CAAC,CAAC;QACH,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC3B,QAAQ,CAAC,IAAI,EAAE,CAAC;IAClB,CAAC;IAES,KAAK,CAAC,aAAa,CAC3B,OAAe,EACf,cAAsB,KAAK;QAE3B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,kBAAkB,CACnD,OAAO,EACP,EAAE,KAAK,EAAE,IAAI,EAAE,EACf,WAAW,EACX,QAAQ,CACT,CAAC;QACF,OAAO,MAAM,KAAK,WAAW,CAAC;IAChC,CAAC;CACF;AA/CD,kCA+CC"} \ No newline at end of file +{"version":3,"file":"BaseCommand.js","sourceRoot":"","sources":["../../src/commands/BaseCommand.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AAEjC,MAAsB,WAAW;IAGrB,KAAK,CAAC,SAAS,CAAC,OAAe;QACvC,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,cAAc,OAAO,EAAE,CAAC,CAAC;IAC1D,CAAC;IAES,KAAK,CAAC,WAAW,CAAC,OAAe;QACzC,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,cAAc,OAAO,EAAE,CAAC,CAAC;IAC5D,CAAC;IAES,KAAK,CAAC,QAAQ,CAAC,OAAe;QACtC,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,cAAc,OAAO,EAAE,CAAC,CAAC;IAChE,CAAC;IAES,KAAK,CAAC,WAAW,CAAC,OAAe;QACzC,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC;IACvD,CAAC;IAES,yBAAyB;QACjC,OAAO,MAAM,CAAC,SAAS,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC;IAES,KAAK,CAAC,kBAAkB,CAChC,OAAe,EACf,GAAY;QAEZ,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC;YAC5C,IAAI,EAAE,WAAW;YACjB,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,yBAAyB,EAAE,EAAE,GAAG,CAAC,MAAM;SACzD,CAAC,CAAC;QACH,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC3B,QAAQ,CAAC,IAAI,EAAE,CAAC;IAClB,CAAC;IAES,KAAK,CAAC,aAAa,CAC3B,OAAe,EACf,cAAsB,KAAK,EAC3B,aAAqB,QAAQ;QAE7B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,kBAAkB,CACnD,OAAO,EACP,EAAE,KAAK,EAAE,IAAI,EAAE,EACf,WAAW,EACX,UAAU,CACX,CAAC;QACF,OAAO,MAAM,KAAK,WAAW,CAAC;IAChC,CAAC;CACF;AAhDD,kCAgDC"} \ No newline at end of file diff --git a/packages/vscode-extension/out/commands/CommitCommand.js b/packages/vscode-extension/out/commands/CommitCommand.js index 160b0616..2c98f9bc 100644 --- a/packages/vscode-extension/out/commands/CommitCommand.js +++ b/packages/vscode-extension/out/commands/CommitCommand.js @@ -1,27 +1,276 @@ "use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.CommitCommand = void 0; -const BaseCommand_1 = require("./BaseCommand"); +const vscode = __importStar(require("vscode")); +const core_1 = require("@stackcode/core"); const i18n_1 = require("@stackcode/i18n"); +const BaseCommand_1 = require("./BaseCommand"); +/** + * Handles Conventional Commit workflow in VS Code. + * Prompts for commit details, links GitHub issues, and integrates progress feedback. + */ class CommitCommand extends BaseCommand_1.BaseCommand { - async execute() { - try { - const workspaceFolder = this.getCurrentWorkspaceFolder(); - if (!workspaceFolder) { - this.showError((0, i18n_1.t)("vscode.common.no_workspace_folder")); - return; - } - const command = `npx @stackcode/cli commit`; - await this.runTerminalCommand(command, workspaceFolder.uri.fsPath); - this.showSuccess((0, i18n_1.t)("vscode.commit.commit_dialog_opened")); - } catch (error) { - this.showError( - (0, i18n_1.t)("vscode.commit.failed_open_commit_dialog", { - error: String(error), - }), - ); - } - } + constructor(authService, gitMonitor, progressManager) { + super(); + this.authService = authService; + this.gitMonitor = gitMonitor; + this.progressManager = progressManager; + } + async execute() { + try { + const workspaceFolder = this.getCurrentWorkspaceFolder(); + if (!workspaceFolder) { + await this.showError((0, i18n_1.t)("vscode.common.no_workspace_folder")); + return; + } + const commitType = await this.selectCommitType(); + if (!commitType) { + return; + } + const scope = await vscode.window.showInputBox({ + prompt: this.translate("commit.prompt.scope", "Scope (optional)"), + placeHolder: this.translate("commit.prompt.scope", "Scope (optional)"), + }); + const shortDescription = await this.promptRequiredText(this.translate("commit.prompt.short_description", "Write a short, imperative description of the change"), this.translate("commit.prompt.short_description", "Write a short, imperative description of the change")); + if (!shortDescription) { + return; + } + const longDescription = await vscode.window.showInputBox({ + prompt: this.translate("commit.prompt.long_description", "Provide a longer description (optional)"), + placeHolder: this.translate("commit.prompt.long_description", "Provide a longer description (optional)"), + value: "", + }); + const breakingChanges = await vscode.window.showInputBox({ + prompt: this.translate("commit.prompt.breaking_changes", "Describe BREAKING CHANGES (optional)"), + placeHolder: this.translate("commit.prompt.breaking_changes", "Describe BREAKING CHANGES (optional)"), + }); + const issueReferences = await this.resolveIssueReferences(); + this.progressManager.startWorkflow("commit"); + const result = await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: this.translate("commit.command_description", "Prepare a conventional commit"), + cancellable: false, + }, async (progress) => { + this.progressManager.setVSCodeProgressReporter(progress); + return (0, core_1.runCommitWorkflow)({ + cwd: workspaceFolder.uri.fsPath, + type: commitType, + scope: scope || undefined, + shortDescription, + longDescription: longDescription || undefined, + breakingChanges: breakingChanges || undefined, + affectedIssues: issueReferences || undefined, + }, { + onProgress: (workflowProgress) => { + this.reportCommitProgress(workflowProgress.step, progress); + this.progressManager.reportProgress("commit", workflowProgress.step, workflowProgress.message); + }, + }); + }); + this.progressManager.clearVSCodeProgressReporter(); + if (result.status === "committed") { + this.progressManager.completeWorkflow("commit", "Commit created successfully"); + this.appendCommitMessage(result.message ?? shortDescription); + await this.showSuccess((0, i18n_1.t)("commit.success")); + return; + } + this.progressManager.failWorkflow("commit", result.error || "Commit workflow cancelled"); + if (result.reason === "no-staged-changes") { + await this.showWarning((0, i18n_1.t)("commit.error_no_changes_staged")); + return; + } + const errorMessage = result.error ?? + this.translate("common.error_generic", "An error occurred."); + await this.showError(errorMessage); + } + catch (error) { + await this.showError(`${this.translate("common.error_generic", "An error occurred.")} ${error instanceof Error ? error.message : String(error)}`); + } + } + /** + * Displays commit message output for user reference. + * @param message - The final commit message created by the workflow. + */ + appendCommitMessage(message) { + const channel = this.ensureOutputChannel(); + channel.appendLine("―".repeat(60)); + channel.appendLine(`${new Date().toISOString()} - ${this.translate("commit.output_channel_title", "Commit message")}`); + channel.appendLine(message); + channel.show(true); + } + /** + * Prompts the user to select the Conventional Commit type. + */ + async selectCommitType() { + const items = [ + { label: this.translate("commit.types.feat", "feat"), value: "feat" }, + { label: this.translate("commit.types.fix", "fix"), value: "fix" }, + { label: this.translate("commit.types.docs", "docs"), value: "docs" }, + { + label: this.translate("commit.types.style", "style"), + value: "style", + }, + { + label: this.translate("commit.types.refactor", "refactor"), + value: "refactor", + }, + { label: this.translate("commit.types.perf", "perf"), value: "perf" }, + { label: this.translate("commit.types.test", "test"), value: "test" }, + { + label: this.translate("commit.types.chore", "chore"), + value: "chore", + }, + { + label: this.translate("commit.types.revert", "revert"), + value: "revert", + }, + ]; + const selection = await vscode.window.showQuickPick(items, { + placeHolder: this.translate("commit.prompt.select_type", "Select the type of change"), + }); + return selection?.value; + } + /** + * Prompts the user for required text input, handling validation. + * @param prompt - Prompt message to display. + * @param placeHolder - Placeholder text for the input box. + */ + async promptRequiredText(prompt, placeHolder) { + return vscode.window.showInputBox({ + prompt, + placeHolder, + validateInput: (value) => value && value.trim().length > 0 + ? undefined + : this.translate("common.error_generic", "This field is required."), + }); + } + /** + * Resolves GitHub issues references, asking the user if they wish to link issues. + * Now uses runIssuesWorkflow from @stackcode/core for centralized logic. + */ + async resolveIssueReferences() { + try { + if (!this.authService.isAuthenticated) { + return this.promptManualIssueReference(); + } + const repository = await this.gitMonitor.getCurrentGitHubRepository(); + if (!repository) { + return this.promptManualIssueReference(); + } + const client = await this.authService.getAuthenticatedClient(); + const result = await (0, core_1.runIssuesWorkflow)({ + client, + repository: { + owner: repository.owner, + repo: repository.repo, + fullName: repository.fullName, + }, + enableCache: true, + }); + if (result.status === "error" || !result.issues.length) { + return this.promptManualIssueReference(); + } + const selections = await vscode.window.showQuickPick(result.issues.map((issue) => this.mapIssueToQuickPick(issue)), { + canPickMany: true, + placeHolder: this.translate("commit.prompt.affected_issues", "Select issues to reference"), + }); + if (!selections || selections.length === 0) { + return this.promptManualIssueReference(); + } + return selections + .map((item) => this.translate("commit.issues.reference_entry", "closes #{issueNumber}", { + issueNumber: item.issue.number, + })) + .join(", "); + } + catch (error) { + const reason = error instanceof Error ? error.message : String(error); + await this.showWarning(`${this.translate("github.issues.error_fetching", "Failed to fetch issues:")} ${reason}`); + return this.promptManualIssueReference(); + } + } + /** + * Prompts the user for manual issue references when GitHub integration is unavailable. + */ + async promptManualIssueReference() { + const manualValue = await vscode.window.showInputBox({ + prompt: this.translate("commit.prompt.affected_issues", "Does this change affect any open issues?"), + placeHolder: this.translate("commit.placeholder.issue_reference", "closes #123"), + }); + return manualValue?.trim() ? manualValue.trim() : undefined; + } + /** + * Maps a GitHub issue to a VS Code quick pick item. + */ + mapIssueToQuickPick(issue) { + return { + label: `#${issue.number} ${issue.title}`, + description: issue.user?.login ?? "", + issue, + }; + } + /** + * Updates progress reporting messages according to the workflow step. + */ + reportCommitProgress(step, progress) { + const messages = { + checkingStaged: this.translate("commit.progress.checking_staged", "Checking staged changes..."), + buildingMessage: this.translate("commit.progress.building_message", "Building commit message..."), + committing: this.translate("commit.progress.committing", "Running git commit..."), + completed: this.translate("commit.progress.completed", "Commit completed successfully."), + }; + const message = messages[step]; + if (message) { + progress.report({ message }); + this.ensureOutputChannel().appendLine(message); + } + } + /** + * Lazily creates and returns the output channel used for commit logs. + */ + ensureOutputChannel() { + if (!this.outputChannel) { + this.outputChannel = + vscode.window.createOutputChannel("StackCode Commit"); + } + return this.outputChannel; + } + /** + * Safely translates a key using i18n with a fallback string. + */ + translate(key, fallback, variables) { + try { + return variables ? (0, i18n_1.t)(key, variables) : (0, i18n_1.t)(key); + } + catch { + if (!variables) + return fallback; + return Object.entries(variables).reduce((acc, [varKey, value]) => acc.replace(`{${varKey}}`, String(value)), fallback); + } + } } exports.CommitCommand = CommitCommand; -//# sourceMappingURL=CommitCommand.js.map +//# sourceMappingURL=CommitCommand.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/commands/CommitCommand.js.map b/packages/vscode-extension/out/commands/CommitCommand.js.map index b5fa9dc3..7cdbacdd 100644 --- a/packages/vscode-extension/out/commands/CommitCommand.js.map +++ b/packages/vscode-extension/out/commands/CommitCommand.js.map @@ -1 +1 @@ -{"version":3,"file":"CommitCommand.js","sourceRoot":"","sources":["../../src/commands/CommitCommand.ts"],"names":[],"mappings":";;;AAAA,+CAA4C;AAC5C,0CAAoC;AAEpC,MAAa,aAAc,SAAQ,yBAAW;IAC5C,KAAK,CAAC,OAAO;QACX,IAAI;YACF,MAAM,eAAe,GAAG,IAAI,CAAC,yBAAyB,EAAE,CAAC;YACzD,IAAI,CAAC,eAAe,EAAE;gBACpB,IAAI,CAAC,SAAS,CAAC,IAAA,QAAC,EAAC,mCAAmC,CAAC,CAAC,CAAC;gBACvD,OAAO;aACR;YAED,MAAM,OAAO,GAAG,2BAA2B,CAAC;YAE5C,MAAM,IAAI,CAAC,kBAAkB,CAAC,OAAO,EAAE,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAEnE,IAAI,CAAC,WAAW,CAAC,IAAA,QAAC,EAAC,oCAAoC,CAAC,CAAC,CAAC;SAC3D;QAAC,OAAO,KAAK,EAAE;YACd,IAAI,CAAC,SAAS,CACZ,IAAA,QAAC,EAAC,yCAAyC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CACvE,CAAC;SACH;IACH,CAAC;CACF;AApBD,sCAoBC"} \ No newline at end of file +{"version":3,"file":"CommitCommand.js","sourceRoot":"","sources":["../../src/commands/CommitCommand.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/vscode-extension/out/commands/ConfigCommand.js b/packages/vscode-extension/out/commands/ConfigCommand.js index 16d80e26..96e8b243 100644 --- a/packages/vscode-extension/out/commands/ConfigCommand.js +++ b/packages/vscode-extension/out/commands/ConfigCommand.js @@ -1,128 +1,111 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if ( - !desc || - ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) - ) { - desc = { - enumerable: true, - get: function () { - return m[k]; - }, - }; - } - Object.defineProperty(o, k2, desc); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) - __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; - }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConfigCommand = void 0; const vscode = __importStar(require("vscode")); const BaseCommand_1 = require("./BaseCommand"); const i18n_1 = require("@stackcode/i18n"); +const core_1 = require("@stackcode/core"); class ConfigCommand extends BaseCommand_1.BaseCommand { - async execute() { - try { - const workspaceFolder = this.getCurrentWorkspaceFolder(); - if (!workspaceFolder) { - this.showError((0, i18n_1.t)("vscode.common.no_workspace_folder")); - return; - } - const action = await vscode.window.showQuickPick( - [ - { - label: (0, i18n_1.t)("vscode.config.open_stackcode_settings"), - description: (0, i18n_1.t)( - "vscode.config.open_stackcode_settings_description", - ), - }, - { - label: (0, i18n_1.t)("vscode.config.open_project_config"), - description: (0, i18n_1.t)( - "vscode.config.open_project_config_description", - ), - }, - { - label: (0, i18n_1.t)("vscode.config.create_project_config"), - description: (0, i18n_1.t)( - "vscode.config.create_project_config_description", - ), - }, - ], - { - placeHolder: (0, i18n_1.t)( - "vscode.config.what_would_you_like_configure", - ), - }, - ); - if (!action) { - return; - } - if ( - action.label === (0, i18n_1.t)("vscode.config.open_stackcode_settings") - ) { - vscode.commands.executeCommand( - "workbench.action.openSettings", - "stackcode", - ); - } else if ( - action.label === (0, i18n_1.t)("vscode.config.open_project_config") - ) { - const configPath = vscode.Uri.joinPath( - workspaceFolder.uri, - ".stackcoderc.json", - ); + async execute() { try { - const document = await vscode.workspace.openTextDocument(configPath); - await vscode.window.showTextDocument(document); - } catch { - this.showError((0, i18n_1.t)("vscode.config.stackcoderc_not_found")); + const workspaceFolder = this.getCurrentWorkspaceFolder(); + if (!workspaceFolder) { + this.showError((0, i18n_1.t)("vscode.common.no_workspace_folder")); + return; + } + const action = await vscode.window.showQuickPick([ + { + label: (0, i18n_1.t)("vscode.config.open_stackcode_settings"), + description: (0, i18n_1.t)("vscode.config.open_stackcode_settings_description"), + }, + { + label: (0, i18n_1.t)("vscode.config.open_project_config"), + description: (0, i18n_1.t)("vscode.config.open_project_config_description"), + }, + { + label: (0, i18n_1.t)("vscode.config.create_project_config"), + description: (0, i18n_1.t)("vscode.config.create_project_config_description"), + }, + ], { + placeHolder: (0, i18n_1.t)("vscode.config.what_would_you_like_configure"), + }); + if (!action) { + return; + } + if (action.label === (0, i18n_1.t)("vscode.config.open_stackcode_settings")) { + vscode.commands.executeCommand("workbench.action.openSettings", "stackcode"); + } + else if (action.label === (0, i18n_1.t)("vscode.config.open_project_config")) { + const configPath = vscode.Uri.joinPath(workspaceFolder.uri, ".stackcoderc.json"); + try { + const document = await vscode.workspace.openTextDocument(configPath); + await vscode.window.showTextDocument(document); + } + catch { + this.showError((0, i18n_1.t)("vscode.config.stackcoderc_not_found")); + } + } + else if (action.label === (0, i18n_1.t)("vscode.config.create_project_config")) { + await this.createProjectConfig(workspaceFolder); + } + } + catch (error) { + this.showError((0, i18n_1.t)("vscode.config.failed_open_configuration", { error: String(error) })); + } + } + async createProjectConfig(workspaceFolder) { + try { + const configUri = vscode.Uri.joinPath(workspaceFolder.uri, ".stackcoderc.json"); + try { + await vscode.workspace.fs.stat(configUri); + const overwrite = await this.confirmAction((0, i18n_1.t)("vscode.config.stackcoderc_exists_overwrite"), (0, i18n_1.t)("vscode.config.overwrite"), (0, i18n_1.t)("common.cancel")); + if (!overwrite) { + return; + } + } + catch (error) { + console.warn("Failed to read existing config:", error); + } + const defaultConfig = { + stack: undefined, + features: { + commitValidation: false, + husky: false, + docker: false, + }, + }; + await (0, core_1.saveStackCodeConfig)(workspaceFolder.uri.fsPath, defaultConfig); + const document = await vscode.workspace.openTextDocument(configUri); + await vscode.window.showTextDocument(document); + await this.showSuccess((0, i18n_1.t)("vscode.config.project_configuration_initialized")); + } + catch (error) { + await this.showError((0, i18n_1.t)("vscode.config.failed_create_config", { error: String(error) })); } - } else if ( - action.label === (0, i18n_1.t)("vscode.config.create_project_config") - ) { - const command = `npx @stackcode/cli config init`; - await this.runTerminalCommand(command, workspaceFolder.uri.fsPath); - this.showSuccess( - (0, i18n_1.t)("vscode.config.project_configuration_initialized"), - ); - } - } catch (error) { - this.showError( - (0, i18n_1.t)("vscode.config.failed_open_configuration", { - error: String(error), - }), - ); } - } } exports.ConfigCommand = ConfigCommand; -//# sourceMappingURL=ConfigCommand.js.map +//# sourceMappingURL=ConfigCommand.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/commands/ConfigCommand.js.map b/packages/vscode-extension/out/commands/ConfigCommand.js.map index be220858..e17c74fe 100644 --- a/packages/vscode-extension/out/commands/ConfigCommand.js.map +++ b/packages/vscode-extension/out/commands/ConfigCommand.js.map @@ -1 +1 @@ -{"version":3,"file":"ConfigCommand.js","sourceRoot":"","sources":["../../src/commands/ConfigCommand.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AACjC,+CAA4C;AAC5C,0CAAoC;AAEpC,MAAa,aAAc,SAAQ,yBAAW;IAC5C,KAAK,CAAC,OAAO;QACX,IAAI;YACF,MAAM,eAAe,GAAG,IAAI,CAAC,yBAAyB,EAAE,CAAC;YACzD,IAAI,CAAC,eAAe,EAAE;gBACpB,IAAI,CAAC,SAAS,CAAC,IAAA,QAAC,EAAC,mCAAmC,CAAC,CAAC,CAAC;gBACvD,OAAO;aACR;YAED,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,aAAa,CAC9C;gBACE;oBACE,KAAK,EAAE,IAAA,QAAC,EAAC,uCAAuC,CAAC;oBACjD,WAAW,EAAE,IAAA,QAAC,EAAC,mDAAmD,CAAC;iBACpE;gBACD;oBACE,KAAK,EAAE,IAAA,QAAC,EAAC,mCAAmC,CAAC;oBAC7C,WAAW,EAAE,IAAA,QAAC,EAAC,+CAA+C,CAAC;iBAChE;gBACD;oBACE,KAAK,EAAE,IAAA,QAAC,EAAC,qCAAqC,CAAC;oBAC/C,WAAW,EAAE,IAAA,QAAC,EAAC,iDAAiD,CAAC;iBAClE;aACF,EACD;gBACE,WAAW,EAAE,IAAA,QAAC,EAAC,6CAA6C,CAAC;aAC9D,CACF,CAAC;YAEF,IAAI,CAAC,MAAM,EAAE;gBACX,OAAO;aACR;YAED,IAAI,MAAM,CAAC,KAAK,KAAK,IAAA,QAAC,EAAC,uCAAuC,CAAC,EAAE;gBAC/D,MAAM,CAAC,QAAQ,CAAC,cAAc,CAC5B,+BAA+B,EAC/B,WAAW,CACZ,CAAC;aACH;iBAAM,IAAI,MAAM,CAAC,KAAK,KAAK,IAAA,QAAC,EAAC,mCAAmC,CAAC,EAAE;gBAClE,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CACpC,eAAe,CAAC,GAAG,EACnB,mBAAmB,CACpB,CAAC;gBACF,IAAI;oBACF,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;oBACrE,MAAM,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;iBAChD;gBAAC,MAAM;oBACN,IAAI,CAAC,SAAS,CAAC,IAAA,QAAC,EAAC,qCAAqC,CAAC,CAAC,CAAC;iBAC1D;aACF;iBAAM,IAAI,MAAM,CAAC,KAAK,KAAK,IAAA,QAAC,EAAC,qCAAqC,CAAC,EAAE;gBACpE,MAAM,OAAO,GAAG,gCAAgC,CAAC;gBACjD,MAAM,IAAI,CAAC,kBAAkB,CAAC,OAAO,EAAE,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBACnE,IAAI,CAAC,WAAW,CAAC,IAAA,QAAC,EAAC,iDAAiD,CAAC,CAAC,CAAC;aACxE;SACF;QAAC,OAAO,KAAK,EAAE;YACd,IAAI,CAAC,SAAS,CACZ,IAAA,QAAC,EAAC,yCAAyC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CACvE,CAAC;SACH;IACH,CAAC;CACF;AA5DD,sCA4DC"} \ No newline at end of file +{"version":3,"file":"ConfigCommand.js","sourceRoot":"","sources":["../../src/commands/ConfigCommand.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/vscode-extension/out/commands/GenerateCommand.js b/packages/vscode-extension/out/commands/GenerateCommand.js index 7a23d73f..d95e3df5 100644 --- a/packages/vscode-extension/out/commands/GenerateCommand.js +++ b/packages/vscode-extension/out/commands/GenerateCommand.js @@ -1,266 +1,225 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if ( - !desc || - ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) - ) { - desc = { - enumerable: true, - get: function () { - return m[k]; - }, - }; - } - Object.defineProperty(o, k2, desc); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) - __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; - }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.GenerateCommand = void 0; const vscode = __importStar(require("vscode")); const BaseCommand_1 = require("./BaseCommand"); const i18n_1 = require("@stackcode/i18n"); const path = __importStar(require("path")); +const core_1 = require("@stackcode/core"); +/** + * Command to generate project files like README.md and .gitignore. + * Provides options to generate individual files or both at once. + */ class GenerateCommand extends BaseCommand_1.BaseCommand { - async execute() { - const option = await vscode.window.showQuickPick( - [ - { - label: "README.md", - description: (0, i18n_1.t)("vscode.generate.readme_description"), - }, - { - label: ".gitignore", - description: (0, i18n_1.t)("vscode.generate.gitignore_description"), - }, - { - label: (0, i18n_1.t)("vscode.generate.both"), - description: (0, i18n_1.t)("vscode.generate.both_description"), - }, - ], - { - placeHolder: (0, i18n_1.t)( - "vscode.generate.what_would_you_like_generate", - ), - }, - ); - if (!option) { - return; + /** + * Executes the file generation workflow with user selection. + */ + async execute() { + const option = await vscode.window.showQuickPick([ + { + label: "README.md", + description: (0, i18n_1.t)("vscode.generate.readme_description"), + }, + { + label: ".gitignore", + description: (0, i18n_1.t)("vscode.generate.gitignore_description"), + }, + { + label: (0, i18n_1.t)("vscode.generate.both"), + description: (0, i18n_1.t)("vscode.generate.both_description"), + }, + ], { + placeHolder: (0, i18n_1.t)("vscode.generate.what_would_you_like_generate"), + }); + if (!option) { + return; + } + if (option.label === "README.md") { + await this.generateReadme(); + } + else if (option.label === ".gitignore") { + await this.generateGitignore(); + } + else if (option.label === (0, i18n_1.t)("vscode.generate.both")) { + await this.generateReadme(); + await this.generateGitignore(); + } + } + async ensureWorkspaceFolder() { + const workspaceFolder = this.getCurrentWorkspaceFolder(); + if (!workspaceFolder) { + await this.showError((0, i18n_1.t)("vscode.common.no_workspace_folder")); + return undefined; + } + return workspaceFolder; + } + async promptGitignoreTechnologies() { + const selections = await vscode.window.showQuickPick([ + { label: "node-ts", description: (0, i18n_1.t)("vscode.init.stacks.node_ts") }, + { label: "react", description: (0, i18n_1.t)("vscode.init.stacks.react") }, + { label: "vue", description: (0, i18n_1.t)("vscode.init.stacks.vue") }, + { label: "angular", description: (0, i18n_1.t)("vscode.init.stacks.angular") }, + { label: "python", description: (0, i18n_1.t)("vscode.init.stacks.python") }, + { label: "java", description: (0, i18n_1.t)("vscode.init.stacks.java") }, + { label: "go", description: (0, i18n_1.t)("vscode.init.stacks.go") }, + { label: "php", description: (0, i18n_1.t)("vscode.init.stacks.php") }, + ], { + placeHolder: (0, i18n_1.t)("vscode.generate.select_project_type_gitignore"), + canPickMany: true, + }); + if (!selections || selections.length === 0) + return undefined; + return selections.map((s) => s.label); + } + stepMessage(step) { + switch (step) { + case "checkingFile": + return (0, i18n_1.t)("vscode.generate.setting_up_readme"); + case "generatingContent": + return (0, i18n_1.t)("vscode.generate.running_generator"); + case "writingFile": + return (0, i18n_1.t)("vscode.generate.readme_created"); + default: + return undefined; + } + } + createWorkflowHooks(progress) { + return { + onProgress: async ({ step }) => { + const message = this.stepMessage(step); + if (message) + progress.report({ message }); + }, + onEducationalMessage: async (messageKey) => { + progress.report({ message: (0, i18n_1.t)(messageKey) }); + }, + shouldOverwriteFile: async ({ fileType, }) => { + const confirmLabel = (0, i18n_1.t)("vscode.generate.overwrite"); + const message = fileType === "readme" + ? (0, i18n_1.t)("vscode.generate.readme_exists_overwrite") + : (0, i18n_1.t)("vscode.generate.gitignore_exists_overwrite"); + const choice = await vscode.window.showWarningMessage(message, { modal: true }, confirmLabel); + return choice === confirmLabel; + }, + }; + } + async runWorkflowWithProgress(workspaceFolder, options) { + return vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: (0, i18n_1.t)("vscode.generate.running_generator"), + cancellable: false, + }, async (progress) => { + const hooks = this.createWorkflowHooks(progress); + return (0, core_1.runGenerateWorkflow)(options, hooks); + }); + } + async handleWorkflowOutcome(workspaceFolder, result, fileTypes) { + const created = result.files.filter((f) => f.status === "created" || f.status === "overwritten"); + for (const f of created) { + if (f.fileType === "readme") { + await this.showSuccess((0, i18n_1.t)("vscode.generate.readme_has_been_generated")); + } + else if (f.fileType === "gitignore") { + await this.showSuccess((0, i18n_1.t)("vscode.generate.gitignore_has_been_generated")); + } + } + for (const ft of fileTypes) { + const filePath = path.join(workspaceFolder.uri.fsPath, ft === "readme" ? "README.md" : ".gitignore"); + const openPromptKey = ft === "readme" + ? "vscode.generate.would_you_like_open_readme" + : "vscode.generate.would_you_like_open_gitignore"; + const openLabel = (0, i18n_1.t)("vscode.generate.open_file"); + const choice = await vscode.window.showInformationMessage((0, i18n_1.t)(openPromptKey), openLabel); + if (choice === openLabel) { + const document = await vscode.workspace.openTextDocument(filePath); + await vscode.window.showTextDocument(document); + } + } + if (result.warnings.length > 0) { + for (const w of result.warnings) { + await this.showWarning((0, i18n_1.t)(w)); + } + } } - if (option.label === "README.md") { - await this.generateReadme(); - } else if (option.label === ".gitignore") { - await this.generateGitignore(); - } else if (option.label === (0, i18n_1.t)("vscode.generate.both")) { - await this.generateReadme(); - await this.generateGitignore(); + async generateReadme() { + const workspaceFolder = await this.ensureWorkspaceFolder(); + if (!workspaceFolder) { + return; + } + try { + const result = await this.runWorkflowWithProgress(workspaceFolder, { + projectPath: workspaceFolder.uri.fsPath, + files: ["readme"], + }); + await this.handleWorkflowOutcome(workspaceFolder, result, ["readme"]); + } + catch (error) { + await this.showError((0, i18n_1.t)("vscode.generate.failed_generate_readme", { error: String(error) })); + } } - } - async generateReadme() { - try { - const workspaceFolder = this.getCurrentWorkspaceFolder(); - if (!workspaceFolder) { - this.showError((0, i18n_1.t)("vscode.common.no_workspace_folder")); - return; - } - const readmePath = path.join(workspaceFolder.uri.fsPath, "README.md"); - try { - await vscode.workspace.fs.stat(vscode.Uri.file(readmePath)); - const overwrite = await this.confirmAction( - (0, i18n_1.t)("vscode.generate.readme_exists_overwrite"), - (0, i18n_1.t)("vscode.generate.overwrite"), - ); - if (!overwrite) { - return; + async generateGitignore() { + const workspaceFolder = await this.ensureWorkspaceFolder(); + if (!workspaceFolder) { + return; + } + const technologies = await this.promptGitignoreTechnologies(); + if (!technologies) { + return; + } + try { + const result = await this.runWorkflowWithProgress(workspaceFolder, { + projectPath: workspaceFolder.uri.fsPath, + files: ["gitignore"], + gitignoreTechnologies: technologies, + }); + await this.handleWorkflowOutcome(workspaceFolder, result, ["gitignore"]); + } + catch (error) { + await this.showError((0, i18n_1.t)("vscode.generate.failed_generate_gitignore", { + error: String(error), + })); } - } catch { - // File doesn't exist, which is fine - } - vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: (0, i18n_1.t)("vscode.generate.generating_readme"), - cancellable: false, - }, - async (progress) => { - progress.report({ - increment: 0, - message: (0, i18n_1.t)("vscode.generate.setting_up_readme"), - }); - const command = `npx @stackcode/cli generate readme`; - progress.report({ - increment: 50, - message: (0, i18n_1.t)("vscode.generate.running_generator"), - }); - await this.runTerminalCommand(command, workspaceFolder.uri.fsPath); - progress.report({ - increment: 100, - message: (0, i18n_1.t)("vscode.generate.readme_created"), - }); - }, - ); - this.showSuccess( - (0, i18n_1.t)("vscode.generate.readme_has_been_generated"), - ); - const openFile = await vscode.window.showInformationMessage( - (0, i18n_1.t)("vscode.generate.would_you_like_open_readme"), - (0, i18n_1.t)("vscode.generate.open_file"), - ); - if (openFile === (0, i18n_1.t)("vscode.generate.open_file")) { - const document = await vscode.workspace.openTextDocument(readmePath); - await vscode.window.showTextDocument(document); - } - } catch (error) { - this.showError( - (0, i18n_1.t)("vscode.generate.failed_generate_readme", { - error: String(error), - }), - ); } - } - async generateGitignore() { - try { - const workspaceFolder = this.getCurrentWorkspaceFolder(); - if (!workspaceFolder) { - this.showError((0, i18n_1.t)("vscode.common.no_workspace_folder")); - return; - } - const gitignorePath = path.join(workspaceFolder.uri.fsPath, ".gitignore"); - try { - await vscode.workspace.fs.stat(vscode.Uri.file(gitignorePath)); - const overwrite = await this.confirmAction( - (0, i18n_1.t)("vscode.generate.gitignore_exists_overwrite"), - (0, i18n_1.t)("vscode.generate.overwrite"), - ); - if (!overwrite) { - return; + async generateFiles(fileTypes, gitignoreTechnologies) { + const workspaceFolder = await this.ensureWorkspaceFolder(); + if (!workspaceFolder) { + return; + } + try { + const result = await this.runWorkflowWithProgress(workspaceFolder, { + projectPath: workspaceFolder.uri.fsPath, + files: fileTypes, + gitignoreTechnologies, + }); + await this.handleWorkflowOutcome(workspaceFolder, result, fileTypes); + } + catch (error) { + await this.showError((0, i18n_1.t)("vscode.generate.failed_generate_readme", { error: String(error) })); } - } catch { - // File doesn't exist, which is fine - } - const projectType = await vscode.window.showQuickPick( - [ - { - label: "node-ts", - description: (0, i18n_1.t)("vscode.init.stacks.node_ts"), - }, - { - label: "react", - description: (0, i18n_1.t)("vscode.init.stacks.react"), - }, - { - label: "vue", - description: (0, i18n_1.t)("vscode.init.stacks.vue"), - }, - { - label: "angular", - description: (0, i18n_1.t)("vscode.init.stacks.angular"), - }, - { - label: "python", - description: (0, i18n_1.t)("vscode.init.stacks.python"), - }, - { - label: "java", - description: (0, i18n_1.t)("vscode.init.stacks.java"), - }, - { label: "go", description: (0, i18n_1.t)("vscode.init.stacks.go") }, - { - label: "php", - description: (0, i18n_1.t)("vscode.init.stacks.php"), - }, - { - label: "flutter", - description: (0, i18n_1.t)("vscode.generate.stacks.flutter"), - }, - { - label: "swift", - description: (0, i18n_1.t)("vscode.generate.stacks.swift"), - }, - { - label: "android", - description: (0, i18n_1.t)("vscode.generate.stacks.android"), - }, - ], - { - placeHolder: (0, i18n_1.t)( - "vscode.generate.select_project_type_gitignore", - ), - }, - ); - if (!projectType) { - return; - } - vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: (0, i18n_1.t)("vscode.generate.generating_gitignore"), - cancellable: false, - }, - async (progress) => { - progress.report({ - increment: 0, - message: (0, i18n_1.t)("vscode.generate.setting_up_gitignore"), - }); - const command = `npx @stackcode/cli generate gitignore --type="${projectType.label}"`; - progress.report({ - increment: 50, - message: (0, i18n_1.t)("vscode.generate.running_generator"), - }); - await this.runTerminalCommand(command, workspaceFolder.uri.fsPath); - progress.report({ - increment: 100, - message: (0, i18n_1.t)("vscode.generate.gitignore_created"), - }); - }, - ); - this.showSuccess( - (0, i18n_1.t)("vscode.generate.gitignore_has_been_generated"), - ); - const openFile = await vscode.window.showInformationMessage( - (0, i18n_1.t)("vscode.generate.would_you_like_open_gitignore"), - (0, i18n_1.t)("vscode.generate.open_file"), - ); - if (openFile === (0, i18n_1.t)("vscode.generate.open_file")) { - const document = await vscode.workspace.openTextDocument(gitignorePath); - await vscode.window.showTextDocument(document); - } - } catch (error) { - this.showError( - (0, i18n_1.t)("vscode.generate.failed_generate_gitignore", { - error: String(error), - }), - ); } - } } exports.GenerateCommand = GenerateCommand; -//# sourceMappingURL=GenerateCommand.js.map +//# sourceMappingURL=GenerateCommand.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/commands/GenerateCommand.js.map b/packages/vscode-extension/out/commands/GenerateCommand.js.map index b2d1f29d..e7097867 100644 --- a/packages/vscode-extension/out/commands/GenerateCommand.js.map +++ b/packages/vscode-extension/out/commands/GenerateCommand.js.map @@ -1 +1 @@ -{"version":3,"file":"GenerateCommand.js","sourceRoot":"","sources":["../../src/commands/GenerateCommand.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"GenerateCommand.js","sourceRoot":"","sources":["../../src/commands/GenerateCommand.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/vscode-extension/out/commands/GitCommand.js b/packages/vscode-extension/out/commands/GitCommand.js index 3b997719..cc469224 100644 --- a/packages/vscode-extension/out/commands/GitCommand.js +++ b/packages/vscode-extension/out/commands/GitCommand.js @@ -1,232 +1,213 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if ( - !desc || - ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) - ) { - desc = { - enumerable: true, - get: function () { - return m[k]; - }, - }; - } - Object.defineProperty(o, k2, desc); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) - __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; - }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.GitCommand = void 0; const vscode = __importStar(require("vscode")); const BaseCommand_1 = require("./BaseCommand"); const i18n_1 = require("@stackcode/i18n"); +const core_1 = require("@stackcode/core"); class GitCommand extends BaseCommand_1.BaseCommand { - async execute() { - const action = await vscode.window.showQuickPick( - [ - { - label: "start", - description: (0, i18n_1.t)("vscode.git.start_description"), - }, - { - label: "finish", - description: (0, i18n_1.t)("vscode.git.finish_description"), - }, - ], - { - placeHolder: (0, i18n_1.t)("vscode.git.select_git_action"), - }, - ); - if (!action) { - return; - } - if (action.label === "start") { - await this.startBranch(); - } else if (action.label === "finish") { - await this.finishBranch(); + async execute() { + const action = await vscode.window.showQuickPick([ + { label: "start", description: (0, i18n_1.t)("vscode.git.start_description") }, + { label: "finish", description: (0, i18n_1.t)("vscode.git.finish_description") }, + ], { + placeHolder: (0, i18n_1.t)("vscode.git.select_git_action"), + }); + if (!action) { + return; + } + if (action.label === "start") { + await this.startBranch(); + } + else if (action.label === "finish") { + await this.finishBranch(); + } } - } - async startBranch() { - try { - const branchName = await vscode.window.showInputBox({ - prompt: (0, i18n_1.t)("vscode.git.enter_branch_name"), - placeHolder: (0, i18n_1.t)("vscode.git.new_feature"), - validateInput: (value) => { - if (!value) { - return (0, i18n_1.t)("vscode.git.branch_name_required"); - } - if (!/^[a-zA-Z0-9/_-]+$/.test(value)) { - return (0, i18n_1.t)("vscode.git.branch_name_invalid"); - } - return null; - }, - }); - if (!branchName) { - return; - } - const branchType = await vscode.window.showQuickPick( - [ - { - label: "feature", - description: (0, i18n_1.t)("vscode.git.feature_description"), - }, - { - label: "bugfix", - description: (0, i18n_1.t)("vscode.git.bugfix_description"), - }, - { - label: "hotfix", - description: (0, i18n_1.t)("vscode.git.hotfix_description"), - }, - { - label: "chore", - description: (0, i18n_1.t)("vscode.git.chore_description"), - }, - ], - { - placeHolder: (0, i18n_1.t)("vscode.git.select_branch_type"), - }, - ); - if (!branchType) { - return; - } - const workspaceFolder = this.getCurrentWorkspaceFolder(); - if (!workspaceFolder) { - this.showError((0, i18n_1.t)("vscode.common.no_workspace_folder")); - return; - } - vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: (0, i18n_1.t)("vscode.git.creating_branch", { - branchName: `${branchType.label}/${branchName}`, - }), - cancellable: false, - }, - async (progress) => { - progress.report({ - increment: 0, - message: (0, i18n_1.t)("vscode.git.switching_to_develop"), - }); - const command = `npx @stackcode/cli git start ${branchName} --type=${branchType.label}`; - progress.report({ - increment: 50, - message: (0, i18n_1.t)("vscode.git.creating_new_branch"), - }); - await this.runTerminalCommand(command, workspaceFolder.uri.fsPath); - progress.report({ - increment: 100, - message: (0, i18n_1.t)("vscode.git.branch_created_successfully"), - }); - }, - ); - this.showSuccess( - (0, i18n_1.t)("vscode.git.new_branch_created", { - branchName: `${branchType.label}/${branchName}`, - }), - ); - } catch (error) { - this.showError( - (0, i18n_1.t)("vscode.git.failed_create_branch", { - error: String(error), - }), - ); + async startBranch() { + try { + const branchName = await vscode.window.showInputBox({ + prompt: (0, i18n_1.t)("vscode.git.enter_branch_name"), + placeHolder: (0, i18n_1.t)("vscode.git.new_feature"), + validateInput: (value) => { + if (!value) { + return (0, i18n_1.t)("vscode.git.branch_name_required"); + } + if (!/^[a-zA-Z0-9/_-]+$/.test(value)) { + return (0, i18n_1.t)("vscode.git.branch_name_invalid"); + } + return null; + }, + }); + if (!branchName) { + return; + } + const branchType = await vscode.window.showQuickPick([ + { + label: "feature", + description: (0, i18n_1.t)("vscode.git.feature_description"), + }, + { label: "bugfix", description: (0, i18n_1.t)("vscode.git.bugfix_description") }, + { label: "hotfix", description: (0, i18n_1.t)("vscode.git.hotfix_description") }, + { label: "chore", description: (0, i18n_1.t)("vscode.git.chore_description") }, + ], { + placeHolder: (0, i18n_1.t)("vscode.git.select_branch_type"), + }); + if (!branchType) { + return; + } + const workspaceFolder = this.getCurrentWorkspaceFolder(); + if (!workspaceFolder) { + this.showError((0, i18n_1.t)("vscode.common.no_workspace_folder")); + return; + } + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: (0, i18n_1.t)("vscode.git.creating_branch", { + branchName: `${branchType.label}/${branchName}`, + }), + cancellable: false, + }, async (progress) => { + const result = await (0, core_1.runGitStartWorkflow)({ + cwd: workspaceFolder.uri.fsPath, + branchName, + branchType: branchType.label, + }, { + onProgress: (step) => { + switch (step.step) { + case "switchingBase": + progress.report({ + increment: 10, + message: (0, i18n_1.t)("vscode.git.switching_to_develop"), + }); + break; + case "pullingBase": + progress.report({ + increment: 50, + message: (0, i18n_1.t)("vscode.git.pulling_latest_changes"), + }); + break; + case "creatingBranch": + progress.report({ + increment: 80, + message: (0, i18n_1.t)("vscode.git.creating_new_branch"), + }); + break; + case "completed": + progress.report({ + increment: 100, + message: (0, i18n_1.t)("vscode.git.branch_created_successfully"), + }); + break; + } + }, + }); + if (result.status !== "created") { + throw new Error(result.error ?? (0, i18n_1.t)("vscode.common.unknown_error")); + } + }); + this.showSuccess((0, i18n_1.t)("vscode.git.new_branch_created", { + branchName: `${branchType.label}/${branchName}`, + })); + } + catch (error) { + this.showError((0, i18n_1.t)("vscode.git.failed_create_branch", { error: String(error) })); + } } - } - async finishBranch() { - try { - const workspaceFolder = this.getCurrentWorkspaceFolder(); - if (!workspaceFolder) { - this.showError((0, i18n_1.t)("vscode.common.no_workspace_folder")); - return; - } - const gitExtension = vscode.extensions.getExtension("vscode.git"); - let currentBranch = "current branch"; - if (gitExtension && gitExtension.isActive) { + async finishBranch() { try { - const git = gitExtension.exports; - const api = git.getAPI(1); - const repo = api.repositories[0]; - if (repo && repo.state.HEAD) { - currentBranch = repo.state.HEAD.name || "current branch"; - } - } catch { - // Fallback to generic message + const workspaceFolder = this.getCurrentWorkspaceFolder(); + if (!workspaceFolder) { + this.showError((0, i18n_1.t)("vscode.common.no_workspace_folder")); + return; + } + const gitExtension = vscode.extensions.getExtension("vscode.git"); + let currentBranch = "current branch"; + if (gitExtension && gitExtension.isActive) { + try { + const git = gitExtension.exports; + const api = git.getAPI(1); + const repo = api.repositories[0]; + if (repo && repo.state.HEAD) { + currentBranch = repo.state.HEAD.name || "current branch"; + } + } + catch (error) { + console.warn("Git API unavailable:", error); + } + } + const confirm = await this.confirmAction((0, i18n_1.t)("vscode.git.are_you_sure_finish_branch", { currentBranch }), (0, i18n_1.t)("vscode.git.finish_branch")); + if (!confirm) { + return; + } + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: (0, i18n_1.t)("vscode.git.finishing_branch", { + branchName: currentBranch, + }), + cancellable: false, + }, async (progress) => { + const result = await (0, core_1.runGitFinishWorkflow)({ cwd: workspaceFolder.uri.fsPath }, { + onProgress: (step) => { + switch (step.step) { + case "pushing": + progress.report({ + increment: 30, + message: (0, i18n_1.t)("vscode.git.pushing_branch"), + }); + break; + case "computingPrUrl": + progress.report({ + increment: 70, + message: (0, i18n_1.t)("vscode.git.opening_pr"), + }); + break; + case "completed": + progress.report({ + increment: 100, + message: (0, i18n_1.t)("vscode.git.branch_finished_successfully"), + }); + break; + } + }, + }); + if (result.status !== "pushed" || !result.prUrl || !result.branch) { + const errorMessage = result.error === "not-on-branch" + ? (0, i18n_1.t)("vscode.git.branch_name_required") + : (result.error ?? (0, i18n_1.t)("vscode.common.unknown_error")); + throw new Error(errorMessage); + } + await vscode.env.openExternal(vscode.Uri.parse(result.prUrl)); + currentBranch = result.branch; + }); + this.showSuccess((0, i18n_1.t)("vscode.git.branch_has_been_finished", { currentBranch })); + } + catch (error) { + this.showError((0, i18n_1.t)("vscode.git.failed_finish_branch", { error: String(error) })); } - } - const confirm = await this.confirmAction( - (0, i18n_1.t)("vscode.git.are_you_sure_finish_branch", { - currentBranch, - }), - (0, i18n_1.t)("vscode.git.finish_branch"), - ); - if (!confirm) { - return; - } - vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: (0, i18n_1.t)("vscode.git.finishing_branch", { - branchName: currentBranch, - }), - cancellable: false, - }, - async (progress) => { - progress.report({ - increment: 0, - message: (0, i18n_1.t)("vscode.git.pushing_branch"), - }); - const command = `npx @stackcode/cli git finish`; - progress.report({ - increment: 50, - message: (0, i18n_1.t)("vscode.git.opening_pr"), - }); - await this.runTerminalCommand(command, workspaceFolder.uri.fsPath); - progress.report({ - increment: 100, - message: (0, i18n_1.t)("vscode.git.branch_finished_successfully"), - }); - }, - ); - this.showSuccess( - (0, i18n_1.t)("vscode.git.branch_has_been_finished", { currentBranch }), - ); - } catch (error) { - this.showError( - (0, i18n_1.t)("vscode.git.failed_finish_branch", { - error: String(error), - }), - ); } - } } exports.GitCommand = GitCommand; -//# sourceMappingURL=GitCommand.js.map +//# sourceMappingURL=GitCommand.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/commands/GitCommand.js.map b/packages/vscode-extension/out/commands/GitCommand.js.map index c7e861ab..11bdf088 100644 --- a/packages/vscode-extension/out/commands/GitCommand.js.map +++ b/packages/vscode-extension/out/commands/GitCommand.js.map @@ -1 +1 @@ -{"version":3,"file":"GitCommand.js","sourceRoot":"","sources":["../../src/commands/GitCommand.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AACjC,+CAA4C;AAE5C,0CAAoC;AAEpC,MAAa,UAAW,SAAQ,yBAAW;IACzC,KAAK,CAAC,OAAO;QACX,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,aAAa,CAC9C;YACE,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,8BAA8B,CAAC,EAAE;YAClE,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,+BAA+B,CAAC,EAAE;SACrE,EACD;YACE,WAAW,EAAE,IAAA,QAAC,EAAC,8BAA8B,CAAC;SAC/C,CACF,CAAC;QAEF,IAAI,CAAC,MAAM,EAAE;YACX,OAAO;SACR;QAED,IAAI,MAAM,CAAC,KAAK,KAAK,OAAO,EAAE;YAC5B,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;SAC1B;aAAM,IAAI,MAAM,CAAC,KAAK,KAAK,QAAQ,EAAE;YACpC,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;SAC3B;IACH,CAAC;IAED,KAAK,CAAC,WAAW;QACf,IAAI;YACF,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;gBAClD,MAAM,EAAE,IAAA,QAAC,EAAC,8BAA8B,CAAC;gBACzC,WAAW,EAAE,IAAA,QAAC,EAAC,wBAAwB,CAAC;gBACxC,aAAa,EAAE,CAAC,KAAa,EAAE,EAAE;oBAC/B,IAAI,CAAC,KAAK,EAAE;wBACV,OAAO,IAAA,QAAC,EAAC,iCAAiC,CAAC,CAAC;qBAC7C;oBACD,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;wBACpC,OAAO,IAAA,QAAC,EAAC,gCAAgC,CAAC,CAAC;qBAC5C;oBACD,OAAO,IAAI,CAAC;gBACd,CAAC;aACF,CAAC,CAAC;YAEH,IAAI,CAAC,UAAU,EAAE;gBACf,OAAO;aACR;YAED,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,aAAa,CAClD;gBACE;oBACE,KAAK,EAAE,SAAS;oBAChB,WAAW,EAAE,IAAA,QAAC,EAAC,gCAAgC,CAAC;iBACjD;gBACD,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,+BAA+B,CAAC,EAAE;gBACpE,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,+BAA+B,CAAC,EAAE;gBACpE,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,8BAA8B,CAAC,EAAE;aACnE,EACD;gBACE,WAAW,EAAE,IAAA,QAAC,EAAC,+BAA+B,CAAC;aAChD,CACF,CAAC;YAEF,IAAI,CAAC,UAAU,EAAE;gBACf,OAAO;aACR;YAED,MAAM,eAAe,GAAG,IAAI,CAAC,yBAAyB,EAAE,CAAC;YACzD,IAAI,CAAC,eAAe,EAAE;gBACpB,IAAI,CAAC,SAAS,CAAC,IAAA,QAAC,EAAC,mCAAmC,CAAC,CAAC,CAAC;gBACvD,OAAO;aACR;YAED,MAAM,CAAC,MAAM,CAAC,YAAY,CACxB;gBACE,QAAQ,EAAE,MAAM,CAAC,gBAAgB,CAAC,YAAY;gBAC9C,KAAK,EAAE,IAAA,QAAC,EAAC,4BAA4B,EAAE;oBACrC,UAAU,EAAE,GAAG,UAAU,CAAC,KAAK,IAAI,UAAU,EAAE;iBAChD,CAAC;gBACF,WAAW,EAAE,KAAK;aACnB,EACD,KAAK,EAAE,QAA0B,EAAE,EAAE;gBACnC,QAAQ,CAAC,MAAM,CAAC;oBACd,SAAS,EAAE,CAAC;oBACZ,OAAO,EAAE,IAAA,QAAC,EAAC,iCAAiC,CAAC;iBAC9C,CAAC,CAAC;gBAEH,MAAM,OAAO,GAAG,gCAAgC,UAAU,WAAW,UAAU,CAAC,KAAK,EAAE,CAAC;gBAExF,QAAQ,CAAC,MAAM,CAAC;oBACd,SAAS,EAAE,EAAE;oBACb,OAAO,EAAE,IAAA,QAAC,EAAC,gCAAgC,CAAC;iBAC7C,CAAC,CAAC;gBAEH,MAAM,IAAI,CAAC,kBAAkB,CAAC,OAAO,EAAE,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAEnE,QAAQ,CAAC,MAAM,CAAC;oBACd,SAAS,EAAE,GAAG;oBACd,OAAO,EAAE,IAAA,QAAC,EAAC,wCAAwC,CAAC;iBACrD,CAAC,CAAC;YACL,CAAC,CACF,CAAC;YAEF,IAAI,CAAC,WAAW,CACd,IAAA,QAAC,EAAC,+BAA+B,EAAE;gBACjC,UAAU,EAAE,GAAG,UAAU,CAAC,KAAK,IAAI,UAAU,EAAE;aAChD,CAAC,CACH,CAAC;SACH;QAAC,OAAO,KAAK,EAAE;YACd,IAAI,CAAC,SAAS,CACZ,IAAA,QAAC,EAAC,iCAAiC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAC/D,CAAC;SACH;IACH,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,IAAI;YACF,MAAM,eAAe,GAAG,IAAI,CAAC,yBAAyB,EAAE,CAAC;YACzD,IAAI,CAAC,eAAe,EAAE;gBACpB,IAAI,CAAC,SAAS,CAAC,IAAA,QAAC,EAAC,mCAAmC,CAAC,CAAC,CAAC;gBACvD,OAAO;aACR;YAED,MAAM,YAAY,GAAG,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;YAClE,IAAI,aAAa,GAAG,gBAAgB,CAAC;YAErC,IAAI,YAAY,IAAI,YAAY,CAAC,QAAQ,EAAE;gBACzC,IAAI;oBACF,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC;oBACjC,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;oBAC1B,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;oBACjC,IAAI,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;wBAC3B,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,gBAAgB,CAAC;qBAC1D;iBACF;gBAAC,MAAM;oBACN,8BAA8B;iBAC/B;aACF;YAED,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,aAAa,CACtC,IAAA,QAAC,EAAC,uCAAuC,EAAE,EAAE,aAAa,EAAE,CAAC,EAC7D,IAAA,QAAC,EAAC,0BAA0B,CAAC,CAC9B,CAAC;YAEF,IAAI,CAAC,OAAO,EAAE;gBACZ,OAAO;aACR;YAED,MAAM,CAAC,MAAM,CAAC,YAAY,CACxB;gBACE,QAAQ,EAAE,MAAM,CAAC,gBAAgB,CAAC,YAAY;gBAC9C,KAAK,EAAE,IAAA,QAAC,EAAC,6BAA6B,EAAE;oBACtC,UAAU,EAAE,aAAa;iBAC1B,CAAC;gBACF,WAAW,EAAE,KAAK;aACnB,EACD,KAAK,EAAE,QAA0B,EAAE,EAAE;gBACnC,QAAQ,CAAC,MAAM,CAAC;oBACd,SAAS,EAAE,CAAC;oBACZ,OAAO,EAAE,IAAA,QAAC,EAAC,2BAA2B,CAAC;iBACxC,CAAC,CAAC;gBAEH,MAAM,OAAO,GAAG,+BAA+B,CAAC;gBAEhD,QAAQ,CAAC,MAAM,CAAC;oBACd,SAAS,EAAE,EAAE;oBACb,OAAO,EAAE,IAAA,QAAC,EAAC,uBAAuB,CAAC;iBACpC,CAAC,CAAC;gBAEH,MAAM,IAAI,CAAC,kBAAkB,CAAC,OAAO,EAAE,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAEnE,QAAQ,CAAC,MAAM,CAAC;oBACd,SAAS,EAAE,GAAG;oBACd,OAAO,EAAE,IAAA,QAAC,EAAC,yCAAyC,CAAC;iBACtD,CAAC,CAAC;YACL,CAAC,CACF,CAAC;YAEF,IAAI,CAAC,WAAW,CACd,IAAA,QAAC,EAAC,qCAAqC,EAAE,EAAE,aAAa,EAAE,CAAC,CAC5D,CAAC;SACH;QAAC,OAAO,KAAK,EAAE;YACd,IAAI,CAAC,SAAS,CACZ,IAAA,QAAC,EAAC,iCAAiC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAC/D,CAAC;SACH;IACH,CAAC;CACF;AAtLD,gCAsLC"} \ No newline at end of file +{"version":3,"file":"GitCommand.js","sourceRoot":"","sources":["../../src/commands/GitCommand.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/vscode-extension/out/commands/InitCommand.js b/packages/vscode-extension/out/commands/InitCommand.js index 4266d6ec..6ab9c609 100644 --- a/packages/vscode-extension/out/commands/InitCommand.js +++ b/packages/vscode-extension/out/commands/InitCommand.js @@ -1,207 +1,307 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if ( - !desc || - ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) - ) { - desc = { - enumerable: true, - get: function () { - return m[k]; - }, - }; - } - Object.defineProperty(o, k2, desc); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) - __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; - }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.InitCommand = void 0; const vscode = __importStar(require("vscode")); const BaseCommand_1 = require("./BaseCommand"); const i18n_1 = require("@stackcode/i18n"); +const core_1 = require("@stackcode/core"); const path = __importStar(require("path")); +/** + * Command to initialize a new project through VS Code interface. + * Provides interactive project setup with stack selection and configuration. + */ class InitCommand extends BaseCommand_1.BaseCommand { - async execute() { - try { - const projectName = await vscode.window.showInputBox({ - prompt: (0, i18n_1.t)("vscode.init.enter_project_name"), - placeHolder: (0, i18n_1.t)("vscode.init.my_awesome_project"), - validateInput: (value) => { - if (!value) { - return (0, i18n_1.t)("vscode.init.project_name_required"); - } - if (!/^[a-zA-Z0-9-_]+$/.test(value)) { - return (0, i18n_1.t)("vscode.init.project_name_invalid"); - } - return null; - }, - }); - if (!projectName) { - return; - } - const description = await vscode.window.showInputBox({ - prompt: (0, i18n_1.t)("vscode.init.enter_project_description"), - placeHolder: (0, i18n_1.t)("vscode.init.brief_description"), - }); - const authorName = await vscode.window.showInputBox({ - prompt: (0, i18n_1.t)("vscode.init.enter_author_name"), - placeHolder: (0, i18n_1.t)("vscode.init.your_name"), - value: await this.getGitUserName(), - }); - const stack = await vscode.window.showQuickPick( - [ - { - label: "node-ts", - description: (0, i18n_1.t)("vscode.init.stacks.node_ts"), - }, - { - label: "react", - description: (0, i18n_1.t)("vscode.init.stacks.react"), - }, - { - label: "vue", - description: (0, i18n_1.t)("vscode.init.stacks.vue"), - }, - { - label: "angular", - description: (0, i18n_1.t)("vscode.init.stacks.angular"), - }, - { - label: "python", - description: (0, i18n_1.t)("vscode.init.stacks.python"), - }, - { - label: "java", - description: (0, i18n_1.t)("vscode.init.stacks.java"), - }, - { label: "go", description: (0, i18n_1.t)("vscode.init.stacks.go") }, - { - label: "php", - description: (0, i18n_1.t)("vscode.init.stacks.php"), - }, - ], - { - placeHolder: (0, i18n_1.t)("vscode.init.select_project_stack"), - }, - ); - if (!stack) { - return; - } - const workspaceFolder = this.getCurrentWorkspaceFolder(); - let projectPath; - if (workspaceFolder) { - projectPath = path.join(workspaceFolder.uri.fsPath, projectName); - } else { - const folderUris = await vscode.window.showOpenDialog({ - canSelectFolders: true, - canSelectFiles: false, - canSelectMany: false, - openLabel: (0, i18n_1.t)("vscode.init.select_project_location"), - }); - if (!folderUris || folderUris.length === 0) { - return; + /** + * Executes the project initialization workflow with user prompts. + */ + async execute() { + try { + const projectName = await vscode.window.showInputBox({ + prompt: (0, i18n_1.t)("vscode.init.enter_project_name"), + placeHolder: (0, i18n_1.t)("vscode.init.my_awesome_project"), + validateInput: (value) => { + if (!value) { + return (0, i18n_1.t)("vscode.init.project_name_required"); + } + if (!/^[a-zA-Z0-9-_]+$/.test(value)) { + return (0, i18n_1.t)("vscode.init.project_name_invalid"); + } + return null; + }, + }); + if (!projectName) { + return; + } + const descriptionInput = await vscode.window.showInputBox({ + prompt: (0, i18n_1.t)("vscode.init.enter_project_description"), + placeHolder: (0, i18n_1.t)("vscode.init.brief_description"), + }); + const description = descriptionInput ?? ""; + const authorInput = await vscode.window.showInputBox({ + prompt: (0, i18n_1.t)("vscode.init.enter_author_name"), + placeHolder: (0, i18n_1.t)("vscode.init.your_name"), + value: await this.getGitUserName(), + }); + const authorName = authorInput ?? ""; + const stack = await vscode.window.showQuickPick([ + { label: "node-ts", description: (0, i18n_1.t)("vscode.init.stacks.node_ts") }, + { label: "react", description: (0, i18n_1.t)("vscode.init.stacks.react") }, + { label: "vue", description: (0, i18n_1.t)("vscode.init.stacks.vue") }, + { label: "angular", description: (0, i18n_1.t)("vscode.init.stacks.angular") }, + { label: "python", description: (0, i18n_1.t)("vscode.init.stacks.python") }, + { label: "java", description: (0, i18n_1.t)("vscode.init.stacks.java") }, + { label: "go", description: (0, i18n_1.t)("vscode.init.stacks.go") }, + { label: "php", description: (0, i18n_1.t)("vscode.init.stacks.php") }, + ], { + placeHolder: (0, i18n_1.t)("vscode.init.select_project_stack"), + }); + if (!stack) { + return; + } + const featureItems = [ + { + label: this.safeTranslate("vscode.init.features.docker.label", "Docker support"), + description: this.safeTranslate("vscode.init.features.docker.description", "Adds Docker configuration to the project"), + picked: true, + value: "docker", + }, + { + label: this.safeTranslate("vscode.init.features.husky.label", "Husky commit hooks"), + description: this.safeTranslate("vscode.init.features.husky.description", "Installs Husky to enforce commit conventions"), + picked: true, + value: "husky", + }, + ]; + const selectedFeatures = await vscode.window.showQuickPick(featureItems, { + canPickMany: true, + placeHolder: this.safeTranslate("init.prompt.features", "Select optional features"), + }); + if (typeof selectedFeatures === "undefined") { + return; + } + const features = (selectedFeatures.length > 0 + ? selectedFeatures + : featureItems.filter((item) => item.picked)).map((item) => item.value); + let commitValidation; + if (features.includes("husky")) { + const validationChoice = await vscode.window.showQuickPick([ + { + label: this.safeTranslate("common.yes", "Yes"), + value: true, + }, + { + label: this.safeTranslate("common.no", "No"), + value: false, + }, + ], { + placeHolder: this.safeTranslate("init.prompt.commit_validation", "Enable commit validation hooks?"), + }); + if (!validationChoice) { + return; + } + commitValidation = validationChoice.value; + } + const workspaceFolder = this.getCurrentWorkspaceFolder(); + let projectPath; + if (workspaceFolder) { + projectPath = path.join(workspaceFolder.uri.fsPath, projectName); + } + else { + const folderUris = await vscode.window.showOpenDialog({ + canSelectFolders: true, + canSelectFiles: false, + canSelectMany: false, + openLabel: (0, i18n_1.t)("vscode.init.select_project_location"), + }); + if (!folderUris || folderUris.length === 0) { + return; + } + projectPath = path.join(folderUris[0].fsPath, projectName); + } + try { + await vscode.workspace.fs.stat(vscode.Uri.file(projectPath)); + const overwrite = await this.confirmAction((0, i18n_1.t)("vscode.init.directory_exists_overwrite", { projectName }), (0, i18n_1.t)("vscode.init.overwrite")); + if (!overwrite) { + return; + } + } + catch (error) { + console.warn("Directory check failed:", error); + } + const workflowOptions = { + projectPath, + projectName, + description, + authorName, + stack: stack.label, + features, + commitValidation, + }; + const initializingTitle = this.safeTranslate("vscode.init.initializing_project", `Initializing project ${projectName}`, { projectName }); + const workflowResult = await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: initializingTitle, + cancellable: false, + }, async (progress) => { + progress.report({ + message: this.safeTranslate("vscode.init.setting_up_structure", "Setting up project structure..."), + }); + const hooks = this.createWorkflowHooks(progress); + return (0, core_1.runInitWorkflow)(workflowOptions, hooks); + }); + if (!workflowResult) { + return; + } + if (workflowResult.status === "cancelled") { + await vscode.window.showWarningMessage(this.safeTranslate("common.operation_cancelled", "Operation cancelled.")); + return; + } + if (!workflowResult.dependenciesInstalled && + workflowResult.installCommand) { + const installCommandString = `${workflowResult.installCommand.command} ${workflowResult.installCommand.args.join(" ")}`.trim(); + const lastWarning = workflowResult.warnings.at(-1) ?? + this.safeTranslate("init.error.deps_install_unknown", "Unknown error"); + const failureMessage = this.safeTranslate("init.error.deps_install_failed", `Failed to install dependencies: ${lastWarning}`, { error: lastWarning }); + const manualMessage = this.safeTranslate("init.error.deps_install_manual", "Please run the install command manually:"); + await vscode.window.showWarningMessage(`${failureMessage}\n${manualMessage}\n${installCommandString}`); + } + const openProjectLabel = this.safeTranslate("vscode.init.open_project", "Open Project"); + const laterLabel = this.safeTranslate("vscode.init.later", "Later"); + const successMessage = this.safeTranslate("vscode.init.project_created_successfully", `Project ${projectName} created successfully!`, { projectName }); + const openProject = await vscode.window.showInformationMessage(successMessage, openProjectLabel, laterLabel); + if (openProject === openProjectLabel) { + const uri = vscode.Uri.file(projectPath); + await vscode.commands.executeCommand("vscode.openFolder", uri, true); + } + } + catch (error) { + this.showError((0, i18n_1.t)("vscode.init.failed_initialize_project", { error: String(error) })); + } + } + /** + * Creates workflow hooks wired to VS Code progress feedback and dialogs. + * @param progress - The VS Code progress reporter. + * @returns Configured hooks for the init workflow. + */ + createWorkflowHooks(progress) { + const stepMessage = (step) => { + switch (step) { + case "scaffold": + return this.safeTranslate("init.step.scaffold", "Scaffolding project..."); + case "saveConfig": + return this.safeTranslate("vscode.init.step.save_config", "Saving StackCode configuration..."); + case "generateReadme": + return this.safeTranslate("init.step.readme", "Generating README.md..."); + case "generateGitignore": + return this.safeTranslate("init.step.gitignore", "Creating .gitignore..."); + case "setupHusky": + return this.safeTranslate("init.step.husky", "Configuring Husky hooks..."); + case "initializeGit": + return this.safeTranslate("init.step.git", "Initializing Git repository..."); + case "validateDependencies": + return this.safeTranslate("init.step.validate_deps", "Validating local dependencies..."); + case "installDependencies": + return this.safeTranslate("init.step.deps", "Installing project dependencies..."); + case "completed": + return this.safeTranslate("vscode.init.project_initialized_successfully", "Project initialized successfully!"); + default: + return undefined; + } + }; + return { + onProgress: async ({ step }) => { + const message = stepMessage(step); + if (message) { + progress.report({ message }); + } + }, + onEducationalMessage: async (messageKey) => { + const message = this.safeTranslate(messageKey, messageKey); + progress.report({ message }); + }, + onMissingDependencies: async (details) => { + await vscode.window.showWarningMessage(this.formatMissingDependenciesMessage(details)); + }, + confirmContinueAfterMissingDependencies: async () => { + const continueLabel = this.safeTranslate("common.continue", "Continue"); + const cancelLabel = this.safeTranslate("common.cancel", "Cancel"); + const choice = await vscode.window.showWarningMessage(this.safeTranslate("init.dependencies.prompt_continue", "Continue even if some dependencies are missing?"), { modal: true }, continueLabel, cancelLabel); + return choice === continueLabel; + }, + }; + } + /** + * Formats a readable warning message listing missing dependencies. + * @param details - The dependency validation outcome. + * @returns Formatted multi-line warning string. + */ + formatMissingDependenciesMessage(details) { + const header = this.safeTranslate("init.dependencies.missing", `Missing dependencies for ${details.stack}`, { stack: details.stack }); + const missingLines = details.missingDependencies.map((dependency) => this.safeTranslate("init.dependencies.missing_detail", ` - ${dependency}`, { command: dependency })); + const instructionHeader = this.safeTranslate("init.dependencies.install_instructions", "Install the following dependencies:"); + const installLines = details.missingDependencies.map((dependency) => this.safeTranslate(`init.dependencies.install_${dependency}`, ` - ${dependency}`)); + const optionalWarning = this.safeTranslate("init.dependencies.optional_skip", "You can skip for now, but remember to install them later."); + return [ + header, + ...missingLines, + "", + instructionHeader, + ...installLines, + "", + optionalWarning, + ] + .filter((line) => line.length > 0) + .join("\n"); + } + /** + * Attempts to translate a key, falling back to a default string when missing. + * @param key - Translation key to resolve. + * @param fallback - Fallback string when key is missing. + * @param variables - Optional translation variables. + * @returns Resolved translation or fallback. + */ + safeTranslate(key, fallback, variables) { + try { + return variables ? (0, i18n_1.t)(key, variables) : (0, i18n_1.t)(key); } - projectPath = path.join(folderUris[0].fsPath, projectName); - } - try { - await vscode.workspace.fs.stat(vscode.Uri.file(projectPath)); - const overwrite = await this.confirmAction( - (0, i18n_1.t)("vscode.init.directory_exists_overwrite", { - projectName, - }), - (0, i18n_1.t)("vscode.init.overwrite"), - ); - if (!overwrite) { - return; + catch { + return fallback; } - } catch { - // Directory doesn't exist, which is fine - } - vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: (0, i18n_1.t)("vscode.init.initializing_project", { - projectName, - }), - cancellable: false, - }, - async (progress) => { - progress.report({ - increment: 0, - message: (0, i18n_1.t)("vscode.init.setting_up_structure"), - }); - const command = `npx @stackcode/cli init --name="${projectName}" --description="${description}" --author="${authorName}" --stack="${stack.label}" --path="${projectPath}"`; - progress.report({ - increment: 50, - message: (0, i18n_1.t)("vscode.init.running_stackcode_cli"), - }); - await this.runTerminalCommand(command); - progress.report({ - increment: 100, - message: (0, i18n_1.t)( - "vscode.init.project_initialized_successfully", - ), - }); - }, - ); - const openProject = await vscode.window.showInformationMessage( - (0, i18n_1.t)("vscode.init.project_created_successfully", { - projectName, - }), - (0, i18n_1.t)("vscode.init.open_project"), - (0, i18n_1.t)("vscode.init.later"), - ); - if (openProject === (0, i18n_1.t)("vscode.init.open_project")) { - const uri = vscode.Uri.file(projectPath); - await vscode.commands.executeCommand("vscode.openFolder", uri, true); - } - } catch (error) { - this.showError( - (0, i18n_1.t)("vscode.init.failed_initialize_project", { - error: String(error), - }), - ); } - } - async getGitUserName() { - try { - const terminal = vscode.window.createTerminal({ name: "temp" }); - terminal.sendText("git config user.name"); - terminal.dispose(); - return ""; - } catch { - return ""; + async getGitUserName() { + try { + const terminal = vscode.window.createTerminal({ name: "temp" }); + terminal.sendText("git config user.name"); + terminal.dispose(); + return ""; + } + catch { + return ""; + } } - } } exports.InitCommand = InitCommand; -//# sourceMappingURL=InitCommand.js.map +//# sourceMappingURL=InitCommand.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/commands/InitCommand.js.map b/packages/vscode-extension/out/commands/InitCommand.js.map index 56a88a1e..4aff9a00 100644 --- a/packages/vscode-extension/out/commands/InitCommand.js.map +++ b/packages/vscode-extension/out/commands/InitCommand.js.map @@ -1 +1 @@ -{"version":3,"file":"InitCommand.js","sourceRoot":"","sources":["../../src/commands/InitCommand.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AACjC,+CAA4C;AAE5C,0CAAoC;AACpC,2CAA6B;AAE7B,MAAa,WAAY,SAAQ,yBAAW;IAC1C,KAAK,CAAC,OAAO;QACX,IAAI;YACF,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;gBACnD,MAAM,EAAE,IAAA,QAAC,EAAC,gCAAgC,CAAC;gBAC3C,WAAW,EAAE,IAAA,QAAC,EAAC,gCAAgC,CAAC;gBAChD,aAAa,EAAE,CAAC,KAAa,EAAE,EAAE;oBAC/B,IAAI,CAAC,KAAK,EAAE;wBACV,OAAO,IAAA,QAAC,EAAC,mCAAmC,CAAC,CAAC;qBAC/C;oBACD,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;wBACnC,OAAO,IAAA,QAAC,EAAC,kCAAkC,CAAC,CAAC;qBAC9C;oBACD,OAAO,IAAI,CAAC;gBACd,CAAC;aACF,CAAC,CAAC;YAEH,IAAI,CAAC,WAAW,EAAE;gBAChB,OAAO;aACR;YAED,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;gBACnD,MAAM,EAAE,IAAA,QAAC,EAAC,uCAAuC,CAAC;gBAClD,WAAW,EAAE,IAAA,QAAC,EAAC,+BAA+B,CAAC;aAChD,CAAC,CAAC;YAEH,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;gBAClD,MAAM,EAAE,IAAA,QAAC,EAAC,+BAA+B,CAAC;gBAC1C,WAAW,EAAE,IAAA,QAAC,EAAC,uBAAuB,CAAC;gBACvC,KAAK,EAAE,MAAM,IAAI,CAAC,cAAc,EAAE;aACnC,CAAC,CAAC;YAEH,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,aAAa,CAC7C;gBACE,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,4BAA4B,CAAC,EAAE;gBAClE,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,0BAA0B,CAAC,EAAE;gBAC9D,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,wBAAwB,CAAC,EAAE;gBAC1D,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,4BAA4B,CAAC,EAAE;gBAClE,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,2BAA2B,CAAC,EAAE;gBAChE,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,yBAAyB,CAAC,EAAE;gBAC5D,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,uBAAuB,CAAC,EAAE;gBACxD,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,wBAAwB,CAAC,EAAE;aAC3D,EACD;gBACE,WAAW,EAAE,IAAA,QAAC,EAAC,kCAAkC,CAAC;aACnD,CACF,CAAC;YAEF,IAAI,CAAC,KAAK,EAAE;gBACV,OAAO;aACR;YAED,MAAM,eAAe,GAAG,IAAI,CAAC,yBAAyB,EAAE,CAAC;YACzD,IAAI,WAAmB,CAAC;YAExB,IAAI,eAAe,EAAE;gBACnB,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;aAClE;iBAAM;gBACL,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC;oBACpD,gBAAgB,EAAE,IAAI;oBACtB,cAAc,EAAE,KAAK;oBACrB,aAAa,EAAE,KAAK;oBACpB,SAAS,EAAE,IAAA,QAAC,EAAC,qCAAqC,CAAC;iBACpD,CAAC,CAAC;gBAEH,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE;oBAC1C,OAAO;iBACR;gBAED,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;aAC5D;YAED,IAAI;gBACF,MAAM,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;gBAC7D,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,aAAa,CACxC,IAAA,QAAC,EAAC,wCAAwC,EAAE,EAAE,WAAW,EAAE,CAAC,EAC5D,IAAA,QAAC,EAAC,uBAAuB,CAAC,CAC3B,CAAC;gBACF,IAAI,CAAC,SAAS,EAAE;oBACd,OAAO;iBACR;aACF;YAAC,MAAM;gBACN,yCAAyC;aAC1C;YAED,MAAM,CAAC,MAAM,CAAC,YAAY,CACxB;gBACE,QAAQ,EAAE,MAAM,CAAC,gBAAgB,CAAC,YAAY;gBAC9C,KAAK,EAAE,IAAA,QAAC,EAAC,kCAAkC,EAAE,EAAE,WAAW,EAAE,CAAC;gBAC7D,WAAW,EAAE,KAAK;aACnB,EACD,KAAK,EAAE,QAA0B,EAAE,EAAE;gBACnC,QAAQ,CAAC,MAAM,CAAC;oBACd,SAAS,EAAE,CAAC;oBACZ,OAAO,EAAE,IAAA,QAAC,EAAC,kCAAkC,CAAC;iBAC/C,CAAC,CAAC;gBAEH,MAAM,OAAO,GAAG,mCAAmC,WAAW,oBAAoB,WAAW,eAAe,UAAU,cAAc,KAAK,CAAC,KAAK,aAAa,WAAW,GAAG,CAAC;gBAE3K,QAAQ,CAAC,MAAM,CAAC;oBACd,SAAS,EAAE,EAAE;oBACb,OAAO,EAAE,IAAA,QAAC,EAAC,mCAAmC,CAAC;iBAChD,CAAC,CAAC;gBAEH,MAAM,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;gBAEvC,QAAQ,CAAC,MAAM,CAAC;oBACd,SAAS,EAAE,GAAG;oBACd,OAAO,EAAE,IAAA,QAAC,EAAC,8CAA8C,CAAC;iBAC3D,CAAC,CAAC;YACL,CAAC,CACF,CAAC;YAEF,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAC5D,IAAA,QAAC,EAAC,0CAA0C,EAAE,EAAE,WAAW,EAAE,CAAC,EAC9D,IAAA,QAAC,EAAC,0BAA0B,CAAC,EAC7B,IAAA,QAAC,EAAC,mBAAmB,CAAC,CACvB,CAAC;YAEF,IAAI,WAAW,KAAK,IAAA,QAAC,EAAC,0BAA0B,CAAC,EAAE;gBACjD,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;gBACzC,MAAM,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,mBAAmB,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;aACtE;SACF;QAAC,OAAO,KAAK,EAAE;YACd,IAAI,CAAC,SAAS,CACZ,IAAA,QAAC,EAAC,uCAAuC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CACrE,CAAC;SACH;IACH,CAAC;IAEO,KAAK,CAAC,cAAc;QAC1B,IAAI;YACF,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YAChE,QAAQ,CAAC,QAAQ,CAAC,sBAAsB,CAAC,CAAC;YAC1C,QAAQ,CAAC,OAAO,EAAE,CAAC;YACnB,OAAO,EAAE,CAAC;SACX;QAAC,MAAM;YACN,OAAO,EAAE,CAAC;SACX;IACH,CAAC;CACF;AA5ID,kCA4IC"} \ No newline at end of file +{"version":3,"file":"InitCommand.js","sourceRoot":"","sources":["../../src/commands/InitCommand.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AACjC,+CAA4C;AAC5C,0CAAoC;AACpC,0CASyB;AACzB,2CAA6B;AAE7B;;;GAGG;AACH,MAAa,WAAY,SAAQ,yBAAW;IAC1C;;OAEG;IACH,KAAK,CAAC,OAAO;QACX,IAAI;YACF,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;gBACnD,MAAM,EAAE,IAAA,QAAC,EAAC,gCAAgC,CAAC;gBAC3C,WAAW,EAAE,IAAA,QAAC,EAAC,gCAAgC,CAAC;gBAChD,aAAa,EAAE,CAAC,KAAa,EAAE,EAAE;oBAC/B,IAAI,CAAC,KAAK,EAAE;wBACV,OAAO,IAAA,QAAC,EAAC,mCAAmC,CAAC,CAAC;qBAC/C;oBACD,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;wBACnC,OAAO,IAAA,QAAC,EAAC,kCAAkC,CAAC,CAAC;qBAC9C;oBACD,OAAO,IAAI,CAAC;gBACd,CAAC;aACF,CAAC,CAAC;YAEH,IAAI,CAAC,WAAW,EAAE;gBAChB,OAAO;aACR;YAED,MAAM,gBAAgB,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;gBACxD,MAAM,EAAE,IAAA,QAAC,EAAC,uCAAuC,CAAC;gBAClD,WAAW,EAAE,IAAA,QAAC,EAAC,+BAA+B,CAAC;aAChD,CAAC,CAAC;YACH,MAAM,WAAW,GAAG,gBAAgB,IAAI,EAAE,CAAC;YAE3C,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;gBACnD,MAAM,EAAE,IAAA,QAAC,EAAC,+BAA+B,CAAC;gBAC1C,WAAW,EAAE,IAAA,QAAC,EAAC,uBAAuB,CAAC;gBACvC,KAAK,EAAE,MAAM,IAAI,CAAC,cAAc,EAAE;aACnC,CAAC,CAAC;YACH,MAAM,UAAU,GAAG,WAAW,IAAI,EAAE,CAAC;YAErC,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,aAAa,CAC7C;gBACE,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,4BAA4B,CAAC,EAAE;gBAClE,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,0BAA0B,CAAC,EAAE;gBAC9D,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,wBAAwB,CAAC,EAAE;gBAC1D,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,4BAA4B,CAAC,EAAE;gBAClE,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,2BAA2B,CAAC,EAAE;gBAChE,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,yBAAyB,CAAC,EAAE;gBAC5D,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,uBAAuB,CAAC,EAAE;gBACxD,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,IAAA,QAAC,EAAC,wBAAwB,CAAC,EAAE;aAC3D,EACD;gBACE,WAAW,EAAE,IAAA,QAAC,EAAC,kCAAkC,CAAC;aACnD,CACF,CAAC;YAEF,IAAI,CAAC,KAAK,EAAE;gBACV,OAAO;aACR;YAGD,MAAM,YAAY,GAA2B;gBAC3C;oBACE,KAAK,EAAE,IAAI,CAAC,aAAa,CACvB,mCAAmC,EACnC,gBAAgB,CACjB;oBACD,WAAW,EAAE,IAAI,CAAC,aAAa,CAC7B,yCAAyC,EACzC,0CAA0C,CAC3C;oBACD,MAAM,EAAE,IAAI;oBACZ,KAAK,EAAE,QAAQ;iBAChB;gBACD;oBACE,KAAK,EAAE,IAAI,CAAC,aAAa,CACvB,kCAAkC,EAClC,oBAAoB,CACrB;oBACD,WAAW,EAAE,IAAI,CAAC,aAAa,CAC7B,wCAAwC,EACxC,8CAA8C,CAC/C;oBACD,MAAM,EAAE,IAAI;oBACZ,KAAK,EAAE,OAAO;iBACf;aACF,CAAC;YAEF,MAAM,gBAAgB,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,YAAY,EAAE;gBACvE,WAAW,EAAE,IAAI;gBACjB,WAAW,EAAE,IAAI,CAAC,aAAa,CAC7B,sBAAsB,EACtB,0BAA0B,CAC3B;aACF,CAAC,CAAC;YAEH,IAAI,OAAO,gBAAgB,KAAK,WAAW,EAAE;gBAC3C,OAAO;aACR;YAED,MAAM,QAAQ,GAAG,CACf,gBAAgB,CAAC,MAAM,GAAG,CAAC;gBACzB,CAAC,CAAC,gBAAgB;gBAClB,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAC/C,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAoB,CAAC,CAAC;YAE3C,IAAI,gBAAqC,CAAC;YAC1C,IAAI,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE;gBAE9B,MAAM,gBAAgB,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,aAAa,CACxD;oBACE;wBACE,KAAK,EAAE,IAAI,CAAC,aAAa,CAAC,YAAY,EAAE,KAAK,CAAC;wBAC9C,KAAK,EAAE,IAAI;qBACZ;oBACD;wBACE,KAAK,EAAE,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC;wBAC5C,KAAK,EAAE,KAAK;qBACb;iBAC+B,EAClC;oBACE,WAAW,EAAE,IAAI,CAAC,aAAa,CAC7B,+BAA+B,EAC/B,iCAAiC,CAClC;iBACF,CACF,CAAC;gBAEF,IAAI,CAAC,gBAAgB,EAAE;oBACrB,OAAO;iBACR;gBAED,gBAAgB,GAAG,gBAAgB,CAAC,KAAK,CAAC;aAC3C;YAED,MAAM,eAAe,GAAG,IAAI,CAAC,yBAAyB,EAAE,CAAC;YACzD,IAAI,WAAmB,CAAC;YAExB,IAAI,eAAe,EAAE;gBACnB,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;aAClE;iBAAM;gBACL,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC;oBACpD,gBAAgB,EAAE,IAAI;oBACtB,cAAc,EAAE,KAAK;oBACrB,aAAa,EAAE,KAAK;oBACpB,SAAS,EAAE,IAAA,QAAC,EAAC,qCAAqC,CAAC;iBACpD,CAAC,CAAC;gBAEH,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE;oBAC1C,OAAO;iBACR;gBAED,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;aAC5D;YAED,IAAI;gBACF,MAAM,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;gBAC7D,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,aAAa,CACxC,IAAA,QAAC,EAAC,wCAAwC,EAAE,EAAE,WAAW,EAAE,CAAC,EAC5D,IAAA,QAAC,EAAC,uBAAuB,CAAC,CAC3B,CAAC;gBACF,IAAI,CAAC,SAAS,EAAE;oBACd,OAAO;iBACR;aACF;YAAC,OAAO,KAAK,EAAE;gBACd,OAAO,CAAC,IAAI,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;aAChD;YAED,MAAM,eAAe,GAAwB;gBAC3C,WAAW;gBACX,WAAW;gBACX,WAAW;gBACX,UAAU;gBACV,KAAK,EAAE,KAAK,CAAC,KAAqC;gBAClD,QAAQ;gBACR,gBAAgB;aACjB,CAAC;YAEF,MAAM,iBAAiB,GAAG,IAAI,CAAC,aAAa,CAC1C,kCAAkC,EAClC,wBAAwB,WAAW,EAAE,EACrC,EAAE,WAAW,EAAE,CAChB,CAAC;YAEF,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,YAAY,CAGrD;gBACE,QAAQ,EAAE,MAAM,CAAC,gBAAgB,CAAC,YAAY;gBAC9C,KAAK,EAAE,iBAAiB;gBACxB,WAAW,EAAE,KAAK;aACnB,EACD,KAAK,EACH,QAAmE,EACnE,EAAE;gBACF,QAAQ,CAAC,MAAM,CAAC;oBACd,OAAO,EAAE,IAAI,CAAC,aAAa,CACzB,kCAAkC,EAClC,iCAAiC,CAClC;iBACF,CAAC,CAAC;gBAEH,MAAM,KAAK,GAAG,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;gBACjD,OAAO,IAAA,sBAAe,EAAC,eAAe,EAAE,KAAK,CAAC,CAAC;YACjD,CAAC,CACF,CAAC;YAEF,IAAI,CAAC,cAAc,EAAE;gBACnB,OAAO;aACR;YAED,IAAI,cAAc,CAAC,MAAM,KAAK,WAAW,EAAE;gBACzC,MAAM,MAAM,CAAC,MAAM,CAAC,kBAAkB,CACpC,IAAI,CAAC,aAAa,CAChB,4BAA4B,EAC5B,sBAAsB,CACvB,CACF,CAAC;gBACF,OAAO;aACR;YAED,IACE,CAAC,cAAc,CAAC,qBAAqB;gBACrC,cAAc,CAAC,cAAc,EAC7B;gBACA,MAAM,oBAAoB,GACxB,GAAG,cAAc,CAAC,cAAc,CAAC,OAAO,IAAI,cAAc,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;gBACpG,MAAM,WAAW,GACf,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;oBAC9B,IAAI,CAAC,aAAa,CAChB,iCAAiC,EACjC,eAAe,CAChB,CAAC;gBACJ,MAAM,cAAc,GAAG,IAAI,CAAC,aAAa,CACvC,gCAAgC,EAChC,mCAAmC,WAAW,EAAE,EAChD,EAAE,KAAK,EAAE,WAAW,EAAE,CACvB,CAAC;gBACF,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,CACtC,gCAAgC,EAChC,0CAA0C,CAC3C,CAAC;gBACF,MAAM,MAAM,CAAC,MAAM,CAAC,kBAAkB,CACpC,GAAG,cAAc,KAAK,aAAa,KAAK,oBAAoB,EAAE,CAC/D,CAAC;aACH;YAED,MAAM,gBAAgB,GAAG,IAAI,CAAC,aAAa,CACzC,0BAA0B,EAC1B,cAAc,CACf,CAAC;YACF,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,CAAC,mBAAmB,EAAE,OAAO,CAAC,CAAC;YACpE,MAAM,cAAc,GAAG,IAAI,CAAC,aAAa,CACvC,0CAA0C,EAC1C,WAAW,WAAW,wBAAwB,EAC9C,EAAE,WAAW,EAAE,CAChB,CAAC;YAEF,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAC5D,cAAc,EACd,gBAAgB,EAChB,UAAU,CACX,CAAC;YAEF,IAAI,WAAW,KAAK,gBAAgB,EAAE;gBACpC,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;gBACzC,MAAM,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,mBAAmB,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;aACtE;SACF;QAAC,OAAO,KAAK,EAAE;YACd,IAAI,CAAC,SAAS,CACZ,IAAA,QAAC,EAAC,uCAAuC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CACrE,CAAC;SACH;IACH,CAAC;IAED;;;;OAIG;IACK,mBAAmB,CACzB,QAAmE;QAEnE,MAAM,WAAW,GAAG,CAAC,IAAsB,EAAsB,EAAE;YACjE,QAAQ,IAAI,EAAE;gBACZ,KAAK,UAAU;oBACb,OAAO,IAAI,CAAC,aAAa,CACvB,oBAAoB,EACpB,wBAAwB,CACzB,CAAC;gBACJ,KAAK,YAAY;oBACf,OAAO,IAAI,CAAC,aAAa,CACvB,8BAA8B,EAC9B,mCAAmC,CACpC,CAAC;gBACJ,KAAK,gBAAgB;oBACnB,OAAO,IAAI,CAAC,aAAa,CACvB,kBAAkB,EAClB,yBAAyB,CAC1B,CAAC;gBACJ,KAAK,mBAAmB;oBACtB,OAAO,IAAI,CAAC,aAAa,CACvB,qBAAqB,EACrB,wBAAwB,CACzB,CAAC;gBACJ,KAAK,YAAY;oBACf,OAAO,IAAI,CAAC,aAAa,CACvB,iBAAiB,EACjB,4BAA4B,CAC7B,CAAC;gBACJ,KAAK,eAAe;oBAClB,OAAO,IAAI,CAAC,aAAa,CACvB,eAAe,EACf,gCAAgC,CACjC,CAAC;gBACJ,KAAK,sBAAsB;oBACzB,OAAO,IAAI,CAAC,aAAa,CACvB,yBAAyB,EACzB,kCAAkC,CACnC,CAAC;gBACJ,KAAK,qBAAqB;oBACxB,OAAO,IAAI,CAAC,aAAa,CACvB,gBAAgB,EAChB,oCAAoC,CACrC,CAAC;gBACJ,KAAK,WAAW;oBACd,OAAO,IAAI,CAAC,aAAa,CACvB,8CAA8C,EAC9C,mCAAmC,CACpC,CAAC;gBACJ;oBACE,OAAO,SAAS,CAAC;aACpB;QACH,CAAC,CAAC;QAEF,OAAO;YACL,UAAU,EAAE,KAAK,EAAE,EAAE,IAAI,EAAwB,EAAE,EAAE;gBACnD,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;gBAClC,IAAI,OAAO,EAAE;oBACX,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;iBAC9B;YACH,CAAC;YACD,oBAAoB,EAAE,KAAK,EAAE,UAAkB,EAAE,EAAE;gBACjD,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;gBAC3D,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;YAC/B,CAAC;YACD,qBAAqB,EAAE,KAAK,EAC1B,OAAuC,EACvC,EAAE;gBACF,MAAM,MAAM,CAAC,MAAM,CAAC,kBAAkB,CACpC,IAAI,CAAC,gCAAgC,CAAC,OAAO,CAAC,CAC/C,CAAC;YACJ,CAAC;YACD,uCAAuC,EAAE,KAAK,IAAI,EAAE;gBAClD,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC,iBAAiB,EAAE,UAAU,CAAC,CAAC;gBACxE,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;gBAClE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,kBAAkB,CACnD,IAAI,CAAC,aAAa,CAChB,mCAAmC,EACnC,iDAAiD,CAClD,EACD,EAAE,KAAK,EAAE,IAAI,EAAE,EACf,aAAa,EACb,WAAW,CACZ,CAAC;gBACF,OAAO,MAAM,KAAK,aAAa,CAAC;YAClC,CAAC;SACF,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACK,gCAAgC,CACtC,OAAuC;QAEvC,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAC/B,2BAA2B,EAC3B,4BAA4B,OAAO,CAAC,KAAK,EAAE,EAC3C,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CACzB,CAAC;QACF,MAAM,YAAY,GAAG,OAAO,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,UAAkB,EAAE,EAAE,CAC1E,IAAI,CAAC,aAAa,CAChB,kCAAkC,EAClC,OAAO,UAAU,EAAE,EACnB,EAAE,OAAO,EAAE,UAAU,EAAE,CACxB,CACF,CAAC;QACF,MAAM,iBAAiB,GAAG,IAAI,CAAC,aAAa,CAC1C,wCAAwC,EACxC,qCAAqC,CACtC,CAAC;QACF,MAAM,YAAY,GAAG,OAAO,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,UAAkB,EAAE,EAAE,CAC1E,IAAI,CAAC,aAAa,CAChB,6BAA6B,UAAU,EAAE,EACzC,OAAO,UAAU,EAAE,CACpB,CACF,CAAC;QACF,MAAM,eAAe,GAAG,IAAI,CAAC,aAAa,CACxC,iCAAiC,EACjC,2DAA2D,CAC5D,CAAC;QAEF,OAAO;YACL,MAAM;YACN,GAAG,YAAY;YACf,EAAE;YACF,iBAAiB;YACjB,GAAG,YAAY;YACf,EAAE;YACF,eAAe;SAChB;aACE,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;aACjC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC;IAED;;;;;;OAMG;IACK,aAAa,CACnB,GAAW,EACX,QAAgB,EAChB,SAA2C;QAE3C,IAAI;YACF,OAAO,SAAS,CAAC,CAAC,CAAC,IAAA,QAAC,EAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,IAAA,QAAC,EAAC,GAAG,CAAC,CAAC;SAC/C;QAAC,MAAM;YACN,OAAO,QAAQ,CAAC;SACjB;IACH,CAAC;IAEO,KAAK,CAAC,cAAc;QAC1B,IAAI;YACF,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YAChE,QAAQ,CAAC,QAAQ,CAAC,sBAAsB,CAAC,CAAC;YAC1C,QAAQ,CAAC,OAAO,EAAE,CAAC;YACnB,OAAO,EAAE,CAAC;SACX;QAAC,MAAM;YACN,OAAO,EAAE,CAAC;SACX;IACH,CAAC;CACF;AA5bD,kCA4bC"} \ No newline at end of file diff --git a/packages/vscode-extension/out/commands/ReleaseCommand.js b/packages/vscode-extension/out/commands/ReleaseCommand.js index aa16e567..aa50f4d4 100644 --- a/packages/vscode-extension/out/commands/ReleaseCommand.js +++ b/packages/vscode-extension/out/commands/ReleaseCommand.js @@ -1,100 +1,256 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if ( - !desc || - ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) - ) { - desc = { - enumerable: true, - get: function () { - return m[k]; - }, - }; - } - Object.defineProperty(o, k2, desc); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) - __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; - }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.ReleaseCommand = void 0; const vscode = __importStar(require("vscode")); -const BaseCommand_1 = require("./BaseCommand"); +const core_1 = require("@stackcode/core"); const i18n_1 = require("@stackcode/i18n"); +const BaseCommand_1 = require("./BaseCommand"); +/** + * Handles monorepo release workflow in VS Code. + * Supports strategy detection, version management, and GitHub release creation. + */ class ReleaseCommand extends BaseCommand_1.BaseCommand { - async execute() { - try { - const workspaceFolder = this.getCurrentWorkspaceFolder(); - if (!workspaceFolder) { - this.showError((0, i18n_1.t)("vscode.common.no_workspace_folder")); - return; - } - const confirm = await this.confirmAction( - (0, i18n_1.t)("vscode.release.are_you_sure_create_release"), - (0, i18n_1.t)("vscode.release.create_release"), - ); - if (!confirm) { - return; - } - vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: (0, i18n_1.t)("vscode.release.creating_release"), - cancellable: false, - }, - async (progress) => { - progress.report({ - increment: 0, - message: (0, i18n_1.t)("vscode.release.preparing_release"), - }); - const command = `npx @stackcode/cli release`; - progress.report({ - increment: 50, - message: (0, i18n_1.t)("vscode.release.creating_release_message"), - }); - await this.runTerminalCommand(command, workspaceFolder.uri.fsPath); - progress.report({ - increment: 100, - message: (0, i18n_1.t)("vscode.release.release_created"), - }); - }, - ); - this.showSuccess((0, i18n_1.t)("vscode.release.release_process_started")); - } catch (error) { - this.showError( - (0, i18n_1.t)("vscode.release.failed_create_release", { - error: String(error), - }), - ); + constructor(authService, progressManager) { + super(); + this.authService = authService; + this.progressManager = progressManager; + } + async execute() { + try { + const workspaceFolder = this.getCurrentWorkspaceFolder(); + if (!workspaceFolder) { + await this.showError((0, i18n_1.t)("vscode.common.no_workspace_folder")); + return; + } + const shouldProceed = await this.confirmAction((0, i18n_1.t)("vscode.release.are_you_sure_create_release"), (0, i18n_1.t)("vscode.release.create_release"), (0, i18n_1.t)("common.cancel")); + if (!shouldProceed) { + return; + } + const cwd = workspaceFolder.uri.fsPath; + this.progressManager.startWorkflow("release"); + const result = await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: (0, i18n_1.t)("vscode.release.creating_release"), + cancellable: false, + }, async (progress) => { + this.progressManager.setVSCodeProgressReporter(progress); + const hooks = this.buildReleaseHooks(progress, cwd); + return (0, core_1.runReleaseWorkflow)({ cwd }, hooks); + }); + this.progressManager.clearVSCodeProgressReporter(); + if (result.status === "prepared") { + this.progressManager.completeWorkflow("release", "Release prepared successfully"); + } + else { + this.progressManager.failWorkflow("release", result.error || "Release workflow cancelled"); + } + await this.handleReleaseResult(result, cwd); + } + catch (error) { + await this.showError(`${(0, i18n_1.t)("common.error_generic")} ${error instanceof Error ? error.message : String(error)}`); + } + } + /** + * Builds workflow hooks translating progress events into VS Code feedback. + */ + buildReleaseHooks(progress, cwd) { + return { + onProgress: (workflowProgress) => { + this.reportReleaseProgress(workflowProgress, progress); + this.progressManager.reportProgress("release", workflowProgress.step, workflowProgress.message); + }, + confirmLockedRelease: ({ currentVersion, newVersion }) => this.confirmAction((0, i18n_1.t)("release.prompt_confirm_release", { + currentVersion, + newVersion, + }), (0, i18n_1.t)("common.continue"), (0, i18n_1.t)("common.cancel")), + displayIndependentPlan: (plan) => this.displayIndependentPlan(plan, cwd), + confirmIndependentRelease: () => this.confirmAction((0, i18n_1.t)("release.independent_prompt_confirm"), (0, i18n_1.t)("common.continue"), (0, i18n_1.t)("common.cancel")), + }; + } + /** + * Handles the final result returned by the release workflow. + */ + async handleReleaseResult(result, cwd) { + if (result.status === "cancelled") { + await this.handleCancelledRelease(result); + return; + } + const channel = this.ensureOutputChannel(); + channel.appendLine((0, i18n_1.t)("release.workflow_completed")); + if (result.strategy === "locked") { + await this.showSuccess((0, i18n_1.t)("release.success_ready_to_commit")); + await this.showInfo((0, i18n_1.t)("release.next_steps_commit")); + } + else if (result.strategy === "independent") { + await this.showSuccess((0, i18n_1.t)("release.independent_success")); + await this.showInfo((0, i18n_1.t)("release.next_steps_push")); + } + if (result.releaseNotes) { + channel.appendLine("―".repeat(60)); + channel.appendLine(result.releaseNotes); + channel.show(true); + } + if (result.tagName && result.releaseNotes) { + await this.promptForGitHubRelease({ + tagName: result.tagName, + releaseNotes: result.releaseNotes, + cwd, + githubInfo: result.github, + }); + } + } + /** + * Handles workflow cancellations by surfacing the appropriate message. + */ + async handleCancelledRelease(result) { + switch (result.reason) { + case "invalid-structure": + await this.showError((0, i18n_1.t)("release.error_structure")); + break; + case "no-changes": + await this.showSuccess((0, i18n_1.t)("release.independent_mode_no_changes")); + break; + case "no-bumps": + await this.showWarning((0, i18n_1.t)("release.independent_mode_no_bumps")); + break; + case "cancelled-by-user": + await this.showWarning((0, i18n_1.t)("common.operation_cancelled")); + break; + default: + await this.showError(result.error ?? (0, i18n_1.t)("common.error_generic")); + } + } + /** + * Reports release workflow progress to the notification UI and output channel. + */ + reportReleaseProgress(progress, notification) { + const messages = { + detectingStrategy: (0, i18n_1.t)("release.step_detecting_strategy"), + lockedRecommendedBump: (0, i18n_1.t)("release.step_calculating_bump"), + lockedUpdatingVersions: (0, i18n_1.t)("release.step_updating_versions"), + lockedGeneratingChangelog: (0, i18n_1.t)("release.step_generating_changelog"), + independentFindingChanges: (0, i18n_1.t)("release.independent_mode_start"), + independentDeterminingBumps: (0, i18n_1.t)("release.step_determining_bumps"), + independentPreparingPlan: (0, i18n_1.t)("release.independent_mode_preparing_plan"), + independentUpdatingPackages: (0, i18n_1.t)("release.step_updating_version"), + independentCommitting: (0, i18n_1.t)("release.step_committing_and_tagging"), + completed: (0, i18n_1.t)("release.step_completed"), + }; + const message = messages[progress.step]; + if (message) { + notification.report({ message }); + this.ensureOutputChannel().appendLine(message); + } + } + /** + * Displays the independent release plan in the output channel. + */ + async displayIndependentPlan(plan, cwd) { + const channel = this.ensureOutputChannel(); + channel.show(true); + channel.appendLine("―".repeat(60)); + channel.appendLine((0, i18n_1.t)("release.independent_mode_packages_to_update")); + plan.forEach((pkg) => { + channel.appendLine((0, i18n_1.t)("release.independent_plan_entry", { + package: pkg.pkg.name, + currentVersion: pkg.pkg.version ?? "?", + newVersion: pkg.newVersion, + bumpType: pkg.bumpType, + })); + }); + channel.appendLine(""); + channel.appendLine(`cwd: ${cwd}`); + } + /** + * Prompts the user to create a GitHub release using the authenticated session. + */ + async promptForGitHubRelease(params) { + const choice = await vscode.window.showInformationMessage((0, i18n_1.t)("release.prompt_create_github_release"), (0, i18n_1.t)("common.yes"), (0, i18n_1.t)("common.no")); + if (choice !== (0, i18n_1.t)("common.yes")) { + return; + } + try { + await this.ensureAuthenticated(); + const client = await this.authService.getAuthenticatedClient(); + const { owner, repo } = await this.resolveRepositoryInfo(params.cwd, params.githubInfo); + await client.repos.createRelease({ + owner, + repo, + tag_name: params.tagName, + name: `Release ${params.tagName}`, + body: params.releaseNotes, + prerelease: false, + }); + await this.showSuccess((0, i18n_1.t)("release.success_github_release_created")); + } + catch (error) { + await this.showError(`${(0, i18n_1.t)("common.error_generic")} ${error instanceof Error ? error.message : String(error)}`); + } + } + /** + * Ensures the user is authenticated with GitHub, prompting login when required. + */ + async ensureAuthenticated() { + if (this.authService.isAuthenticated) { + return; + } + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: (0, i18n_1.t)("github.auth.login"), + cancellable: false, + }, async () => { + await this.authService.login(); + }); + } + /** + * Resolves repository owner and name from workflow data or git remotes. + */ + async resolveRepositoryInfo(cwd, info) { + if (info?.owner && info?.repo) { + return { owner: info.owner, repo: info.repo }; + } + const remoteUrl = await (0, core_1.getCommandOutput)("git", ["remote", "get-url", "origin"], { + cwd, + }); + const match = remoteUrl.match(/github\.com[/:]([\w-]+)\/([\w-.]+)/); + if (!match) { + throw new Error((0, i18n_1.t)("git.error_parsing_remote")); + } + return { owner: match[1], repo: match[2].replace(/\.git$/, "") }; + } + /** + * Lazily creates the release output channel. + */ + ensureOutputChannel() { + if (!this.outputChannel) { + this.outputChannel = + vscode.window.createOutputChannel("StackCode Release"); + } + return this.outputChannel; } - } } exports.ReleaseCommand = ReleaseCommand; -//# sourceMappingURL=ReleaseCommand.js.map +//# sourceMappingURL=ReleaseCommand.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/commands/ReleaseCommand.js.map b/packages/vscode-extension/out/commands/ReleaseCommand.js.map index 9c4032d8..060c86f9 100644 --- a/packages/vscode-extension/out/commands/ReleaseCommand.js.map +++ b/packages/vscode-extension/out/commands/ReleaseCommand.js.map @@ -1 +1 @@ -{"version":3,"file":"ReleaseCommand.js","sourceRoot":"","sources":["../../src/commands/ReleaseCommand.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AACjC,+CAA4C;AAE5C,0CAAoC;AAEpC,MAAa,cAAe,SAAQ,yBAAW;IAC7C,KAAK,CAAC,OAAO;QACX,IAAI;YACF,MAAM,eAAe,GAAG,IAAI,CAAC,yBAAyB,EAAE,CAAC;YACzD,IAAI,CAAC,eAAe,EAAE;gBACpB,IAAI,CAAC,SAAS,CAAC,IAAA,QAAC,EAAC,mCAAmC,CAAC,CAAC,CAAC;gBACvD,OAAO;aACR;YAED,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,aAAa,CACtC,IAAA,QAAC,EAAC,4CAA4C,CAAC,EAC/C,IAAA,QAAC,EAAC,+BAA+B,CAAC,CACnC,CAAC;YAEF,IAAI,CAAC,OAAO,EAAE;gBACZ,OAAO;aACR;YAED,MAAM,CAAC,MAAM,CAAC,YAAY,CACxB;gBACE,QAAQ,EAAE,MAAM,CAAC,gBAAgB,CAAC,YAAY;gBAC9C,KAAK,EAAE,IAAA,QAAC,EAAC,iCAAiC,CAAC;gBAC3C,WAAW,EAAE,KAAK;aACnB,EACD,KAAK,EAAE,QAA0B,EAAE,EAAE;gBACnC,QAAQ,CAAC,MAAM,CAAC;oBACd,SAAS,EAAE,CAAC;oBACZ,OAAO,EAAE,IAAA,QAAC,EAAC,kCAAkC,CAAC;iBAC/C,CAAC,CAAC;gBAEH,MAAM,OAAO,GAAG,4BAA4B,CAAC;gBAE7C,QAAQ,CAAC,MAAM,CAAC;oBACd,SAAS,EAAE,EAAE;oBACb,OAAO,EAAE,IAAA,QAAC,EAAC,yCAAyC,CAAC;iBACtD,CAAC,CAAC;gBAEH,MAAM,IAAI,CAAC,kBAAkB,CAAC,OAAO,EAAE,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAEnE,QAAQ,CAAC,MAAM,CAAC;oBACd,SAAS,EAAE,GAAG;oBACd,OAAO,EAAE,IAAA,QAAC,EAAC,gCAAgC,CAAC;iBAC7C,CAAC,CAAC;YACL,CAAC,CACF,CAAC;YAEF,IAAI,CAAC,WAAW,CAAC,IAAA,QAAC,EAAC,wCAAwC,CAAC,CAAC,CAAC;SAC/D;QAAC,OAAO,KAAK,EAAE;YACd,IAAI,CAAC,SAAS,CACZ,IAAA,QAAC,EAAC,sCAAsC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CACpE,CAAC;SACH;IACH,CAAC;CACF;AArDD,wCAqDC"} \ No newline at end of file +{"version":3,"file":"ReleaseCommand.js","sourceRoot":"","sources":["../../src/commands/ReleaseCommand.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/vscode-extension/out/commands/TestGitHubDetectionCommand.js b/packages/vscode-extension/out/commands/TestGitHubDetectionCommand.js index 27678c8b..4f40cd81 100644 --- a/packages/vscode-extension/out/commands/TestGitHubDetectionCommand.js +++ b/packages/vscode-extension/out/commands/TestGitHubDetectionCommand.js @@ -1,48 +1,27 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if ( - !desc || - ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) - ) { - desc = { - enumerable: true, - get: function () { - return m[k]; - }, - }; - } - Object.defineProperty(o, k2, desc); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) - __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; - }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.TestGitHubDetectionCommand = void 0; const vscode = __importStar(require("vscode")); @@ -50,62 +29,50 @@ const GitMonitor_1 = require("../monitors/GitMonitor"); const ProactiveNotificationManager_1 = require("../notifications/ProactiveNotificationManager"); const ConfigurationManager_1 = require("../config/ConfigurationManager"); /** - * Comando de teste para verificar detecção de repositório GitHub + * Test command to verify GitHub repository detection */ class TestGitHubDetectionCommand { - constructor() { - const configManager = new ConfigurationManager_1.ConfigurationManager(); - const proactiveManager = - new ProactiveNotificationManager_1.ProactiveNotificationManager( - configManager, - ); - this.gitMonitor = new GitMonitor_1.GitMonitor( - proactiveManager, - configManager, - ); - } - async execute() { - try { - console.log( - "🧪 [TestCommand] Starting GitHub repository detection test...", - ); - const repository = await this.gitMonitor.getCurrentGitHubRepository(); - if (repository) { - const message = `✅ Repository Detected!\n\nOwner: ${repository.owner}\nRepo: ${repository.repo}\nFull Name: ${repository.fullName}\nRemote URL: ${repository.remoteUrl}`; - vscode.window - .showInformationMessage(message, "Copy Full Name") - .then((selection) => { - if (selection === "Copy Full Name") { - vscode.env.clipboard.writeText(repository.fullName); - vscode.window.showInformationMessage( - `Copied "${repository.fullName}" to clipboard!`, - ); + constructor() { + const configManager = new ConfigurationManager_1.ConfigurationManager(); + const proactiveManager = new ProactiveNotificationManager_1.ProactiveNotificationManager(configManager); + this.gitMonitor = new GitMonitor_1.GitMonitor(proactiveManager, configManager); + } + async execute() { + try { + console.log("🧪 [TestCommand] Starting GitHub repository detection test..."); + const repository = await this.gitMonitor.getCurrentGitHubRepository(); + if (repository) { + const message = `✅ Repository Detected!\n\nOwner: ${repository.owner}\nRepo: ${repository.repo}\nFull Name: ${repository.fullName}\nRemote URL: ${repository.remoteUrl}`; + vscode.window + .showInformationMessage(message, "Copy Full Name") + .then((selection) => { + if (selection === "Copy Full Name") { + vscode.env.clipboard.writeText(repository.fullName); + vscode.window.showInformationMessage(`Copied "${repository.fullName}" to clipboard!`); + } + }); + console.log("✅ [TestCommand] Repository detection successful:", repository); } - }); - console.log( - "✅ [TestCommand] Repository detection successful:", - repository, - ); - } else { - const message = - "❌ No GitHub repository detected\n\nPossible causes:\n• Not in a Git repository\n• No GitHub remote configured\n• Remote is not a GitHub URL"; - vscode.window - .showWarningMessage(message, "Show Debug Info") - .then((selection) => { - if (selection === "Show Debug Info") { - const workspaceFolders = vscode.workspace.workspaceFolders; - const debugInfo = `Debug Info:\n\nWorkspace Folders: ${workspaceFolders?.length || 0}\nFolders: ${workspaceFolders?.map((f) => f.uri.fsPath).join(", ") || "None"}`; - vscode.window.showInformationMessage(debugInfo); + else { + const message = "❌ No GitHub repository detected\n\nPossible causes:\n• Not in a Git repository\n• No GitHub remote configured\n• Remote is not a GitHub URL"; + vscode.window + .showWarningMessage(message, "Show Debug Info") + .then((selection) => { + if (selection === "Show Debug Info") { + const workspaceFolders = vscode.workspace.workspaceFolders; + const debugInfo = `Debug Info:\n\nWorkspace Folders: ${workspaceFolders?.length || 0}\nFolders: ${workspaceFolders?.map((f) => f.uri.fsPath).join(", ") || "None"}`; + vscode.window.showInformationMessage(debugInfo); + } + }); + console.warn("❌ [TestCommand] Repository detection failed"); } - }); - console.warn("❌ [TestCommand] Repository detection failed"); - } - } catch (error) { - const errorMessage = `❌ Error testing repository detection: ${error}`; - vscode.window.showErrorMessage(errorMessage); - console.error("❌ [TestCommand] Error:", error); + } + catch (error) { + const errorMessage = `❌ Error testing repository detection: ${error}`; + vscode.window.showErrorMessage(errorMessage); + console.error("❌ [TestCommand] Error:", error); + } } - } } exports.TestGitHubDetectionCommand = TestGitHubDetectionCommand; -//# sourceMappingURL=TestGitHubDetectionCommand.js.map +//# sourceMappingURL=TestGitHubDetectionCommand.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/commands/TestGitHubDetectionCommand.js.map b/packages/vscode-extension/out/commands/TestGitHubDetectionCommand.js.map index 0cf2f28c..ef179665 100644 --- a/packages/vscode-extension/out/commands/TestGitHubDetectionCommand.js.map +++ b/packages/vscode-extension/out/commands/TestGitHubDetectionCommand.js.map @@ -1 +1 @@ -{"version":3,"file":"TestGitHubDetectionCommand.js","sourceRoot":"","sources":["../../src/commands/TestGitHubDetectionCommand.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AACjC,uDAAoD;AACpD,gGAA6F;AAC7F,yEAAsE;AAEtE;;GAEG;AACH,MAAa,0BAA0B;IAGrC;QACE,MAAM,aAAa,GAAG,IAAI,2CAAoB,EAAE,CAAC;QACjD,MAAM,gBAAgB,GAAG,IAAI,2DAA4B,CAAC,aAAa,CAAC,CAAC;QACzE,IAAI,CAAC,UAAU,GAAG,IAAI,uBAAU,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;IACpE,CAAC;IAED,KAAK,CAAC,OAAO;QACX,IAAI;YACF,OAAO,CAAC,GAAG,CAAC,+DAA+D,CAAC,CAAC;YAE7E,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,0BAA0B,EAAE,CAAC;YAEtE,IAAI,UAAU,EAAE;gBACd,MAAM,OAAO,GAAG,oCAAoC,UAAU,CAAC,KAAK,WAAW,UAAU,CAAC,IAAI,gBAAgB,UAAU,CAAC,QAAQ,iBAAiB,UAAU,CAAC,SAAS,EAAE,CAAC;gBAEzK,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE;oBACjF,IAAI,SAAS,KAAK,gBAAgB,EAAE;wBAClC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;wBACpD,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,WAAW,UAAU,CAAC,QAAQ,iBAAiB,CAAC,CAAC;qBACvF;gBACH,CAAC,CAAC,CAAC;gBAEH,OAAO,CAAC,GAAG,CAAC,kDAAkD,EAAE,UAAU,CAAC,CAAC;aAC7E;iBAAM;gBACL,MAAM,OAAO,GAAG,6IAA6I,CAAC;gBAE9J,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE;oBAC9E,IAAI,SAAS,KAAK,iBAAiB,EAAE;wBACnC,MAAM,gBAAgB,GAAG,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC;wBAC3D,MAAM,SAAS,GAAG,qCAAqC,gBAAgB,EAAE,MAAM,IAAI,CAAC,cAAc,gBAAgB,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,EAAE,CAAC;wBAClK,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC;qBACjD;gBACH,CAAC,CAAC,CAAC;gBAEH,OAAO,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;aAC7D;SAEF;QAAC,OAAO,KAAK,EAAE;YACd,MAAM,YAAY,GAAG,yCAAyC,KAAK,EAAE,CAAC;YACtE,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC;YAC7C,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;SAChD;IACH,CAAC;CACF;AA9CD,gEA8CC"} \ No newline at end of file +{"version":3,"file":"TestGitHubDetectionCommand.js","sourceRoot":"","sources":["../../src/commands/TestGitHubDetectionCommand.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AACjC,uDAAoD;AACpD,gGAA6F;AAC7F,yEAAsE;AAEtE;;GAEG;AACH,MAAa,0BAA0B;IAGrC;QACE,MAAM,aAAa,GAAG,IAAI,2CAAoB,EAAE,CAAC;QACjD,MAAM,gBAAgB,GAAG,IAAI,2DAA4B,CAAC,aAAa,CAAC,CAAC;QACzE,IAAI,CAAC,UAAU,GAAG,IAAI,uBAAU,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;IACpE,CAAC;IAED,KAAK,CAAC,OAAO;QACX,IAAI;YACF,OAAO,CAAC,GAAG,CACT,+DAA+D,CAChE,CAAC;YAEF,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,0BAA0B,EAAE,CAAC;YAEtE,IAAI,UAAU,EAAE;gBACd,MAAM,OAAO,GAAG,oCAAoC,UAAU,CAAC,KAAK,WAAW,UAAU,CAAC,IAAI,gBAAgB,UAAU,CAAC,QAAQ,iBAAiB,UAAU,CAAC,SAAS,EAAE,CAAC;gBAEzK,MAAM,CAAC,MAAM;qBACV,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,CAAC;qBACjD,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE;oBAClB,IAAI,SAAS,KAAK,gBAAgB,EAAE;wBAClC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;wBACpD,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAClC,WAAW,UAAU,CAAC,QAAQ,iBAAiB,CAChD,CAAC;qBACH;gBACH,CAAC,CAAC,CAAC;gBAEL,OAAO,CAAC,GAAG,CACT,kDAAkD,EAClD,UAAU,CACX,CAAC;aACH;iBAAM;gBACL,MAAM,OAAO,GACX,6IAA6I,CAAC;gBAEhJ,MAAM,CAAC,MAAM;qBACV,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,CAAC;qBAC9C,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE;oBAClB,IAAI,SAAS,KAAK,iBAAiB,EAAE;wBACnC,MAAM,gBAAgB,GAAG,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC;wBAC3D,MAAM,SAAS,GAAG,qCAAqC,gBAAgB,EAAE,MAAM,IAAI,CAAC,cAAc,gBAAgB,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,EAAE,CAAC;wBACpK,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC;qBACjD;gBACH,CAAC,CAAC,CAAC;gBAEL,OAAO,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;aAC7D;SACF;QAAC,OAAO,KAAK,EAAE;YACd,MAAM,YAAY,GAAG,yCAAyC,KAAK,EAAE,CAAC;YACtE,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC;YAC7C,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;SAChD;IACH,CAAC;CACF;AAzDD,gEAyDC"} \ No newline at end of file diff --git a/packages/vscode-extension/out/commands/ValidateCommand.js b/packages/vscode-extension/out/commands/ValidateCommand.js index a0b94518..38739f68 100644 --- a/packages/vscode-extension/out/commands/ValidateCommand.js +++ b/packages/vscode-extension/out/commands/ValidateCommand.js @@ -1,97 +1,125 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if ( - !desc || - ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) - ) { - desc = { - enumerable: true, - get: function () { - return m[k]; - }, - }; - } - Object.defineProperty(o, k2, desc); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) - __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; - }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.ValidateCommand = void 0; const vscode = __importStar(require("vscode")); const BaseCommand_1 = require("./BaseCommand"); const i18n_1 = require("@stackcode/i18n"); +const core_1 = require("@stackcode/core"); class ValidateCommand extends BaseCommand_1.BaseCommand { - async execute() { - try { - const workspaceFolder = this.getCurrentWorkspaceFolder(); - if (!workspaceFolder) { - this.showError((0, i18n_1.t)("vscode.common.no_workspace_folder")); - return; - } - vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: (0, i18n_1.t)("vscode.validate.validating_project_structure"), - cancellable: false, - }, - async (progress) => { - progress.report({ - increment: 0, - message: (0, i18n_1.t)("vscode.validate.running_validation"), - }); - const command = `npx @stackcode/cli validate`; - progress.report({ - increment: 50, - message: (0, i18n_1.t)( - "vscode.validate.checking_project_structure", - ), - }); - await this.runTerminalCommand(command, workspaceFolder.uri.fsPath); - progress.report({ - increment: 100, - message: (0, i18n_1.t)("vscode.validate.validation_completed"), - }); - }, - ); - this.showSuccess( - (0, i18n_1.t)("vscode.validate.project_validation_completed"), - ); - } catch (error) { - this.showError( - (0, i18n_1.t)("vscode.validate.failed_validate_project", { - error: String(error), - }), - ); + async execute() { + try { + const workspaceFolder = this.getCurrentWorkspaceFolder(); + if (!workspaceFolder) { + this.showError((0, i18n_1.t)("vscode.common.no_workspace_folder")); + return; + } + let resultIssues = []; + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: (0, i18n_1.t)("vscode.validate.validating_project_structure"), + cancellable: false, + }, async (progress) => { + progress.report({ + increment: 0, + message: (0, i18n_1.t)("vscode.validate.running_validation"), + }); + const res = await (0, core_1.runProjectValidateWorkflow)({ projectPath: workspaceFolder.uri.fsPath }, { + onProgress: (p) => { + if (p.step === "checkingFiles") { + progress.report({ + increment: 50, + message: (0, i18n_1.t)("vscode.validate.checking_project_structure"), + }); + } + }, + }); + resultIssues = res.issues; + progress.report({ + increment: 100, + message: (0, i18n_1.t)("vscode.validate.validation_completed"), + }); + }); + if (!resultIssues.length) { + await this.showSuccess((0, i18n_1.t)("vscode.validate.project_validation_completed")); + return; + } + const summary = resultIssues + .map((i) => `• ${(0, i18n_1.t)(i.messageKey)}`) + .join("\n"); + await this.showWarning((0, i18n_1.t)("vscode.validate.issues_summary", { + count: String(resultIssues.length), + }) + + "\n" + + summary); + const missingFiles = []; + const hasMissingReadme = resultIssues.some((i) => i.id === "missing-readme"); + const hasMissingGitignore = resultIssues.some((i) => i.id === "missing-gitignore"); + if (hasMissingReadme) + missingFiles.push("README.md"); + if (hasMissingGitignore) + missingFiles.push(".gitignore"); + if (missingFiles.length > 0) { + const action = await vscode.window.showInformationMessage((0, i18n_1.t)("vscode.common.project_missing_files", { + missingFiles: missingFiles.join(", "), + }), (0, i18n_1.t)("vscode.common.generate_files"), (0, i18n_1.t)("vscode.common.not_now")); + if (action === (0, i18n_1.t)("vscode.common.generate_files")) { + if (hasMissingReadme) { + await vscode.commands.executeCommand("stackcode.generate.readme"); + } + if (hasMissingGitignore) { + await vscode.commands.executeCommand("stackcode.generate.gitignore"); + } + } + } + } + catch (error) { + this.showError((0, i18n_1.t)("vscode.validate.failed_validate_project", { error: String(error) })); + } + } + async validateCommitMessage() { + try { + const message = await vscode.window.showInputBox({ + prompt: (0, i18n_1.t)("vscode.validate.enter_commit_message"), + placeHolder: "feat: add new feature", + validateInput: (value) => !value ? (0, i18n_1.t)("ui.short_description_required") : null, + }); + if (!message) + return; + const { isValid } = await Promise.resolve().then(() => __importStar(require("@stackcode/core"))).then((m) => m.runValidateWorkflow({ message })); + if (isValid) { + await this.showSuccess((0, i18n_1.t)("validate.success")); + } + else { + await this.showWarning((0, i18n_1.t)("validate.error_invalid")); + } + } + catch (error) { + this.showError((0, i18n_1.t)("vscode.validate.failed_validate_project", { error: String(error) })); + } } - } } exports.ValidateCommand = ValidateCommand; -//# sourceMappingURL=ValidateCommand.js.map +//# sourceMappingURL=ValidateCommand.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/commands/ValidateCommand.js.map b/packages/vscode-extension/out/commands/ValidateCommand.js.map index 97b838f1..8cd0c81f 100644 --- a/packages/vscode-extension/out/commands/ValidateCommand.js.map +++ b/packages/vscode-extension/out/commands/ValidateCommand.js.map @@ -1 +1 @@ -{"version":3,"file":"ValidateCommand.js","sourceRoot":"","sources":["../../src/commands/ValidateCommand.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AACjC,+CAA4C;AAE5C,0CAAoC;AAEpC,MAAa,eAAgB,SAAQ,yBAAW;IAC9C,KAAK,CAAC,OAAO;QACX,IAAI;YACF,MAAM,eAAe,GAAG,IAAI,CAAC,yBAAyB,EAAE,CAAC;YACzD,IAAI,CAAC,eAAe,EAAE;gBACpB,IAAI,CAAC,SAAS,CAAC,IAAA,QAAC,EAAC,mCAAmC,CAAC,CAAC,CAAC;gBACvD,OAAO;aACR;YAED,MAAM,CAAC,MAAM,CAAC,YAAY,CACxB;gBACE,QAAQ,EAAE,MAAM,CAAC,gBAAgB,CAAC,YAAY;gBAC9C,KAAK,EAAE,IAAA,QAAC,EAAC,8CAA8C,CAAC;gBACxD,WAAW,EAAE,KAAK;aACnB,EACD,KAAK,EAAE,QAA0B,EAAE,EAAE;gBACnC,QAAQ,CAAC,MAAM,CAAC;oBACd,SAAS,EAAE,CAAC;oBACZ,OAAO,EAAE,IAAA,QAAC,EAAC,oCAAoC,CAAC;iBACjD,CAAC,CAAC;gBAEH,MAAM,OAAO,GAAG,6BAA6B,CAAC;gBAE9C,QAAQ,CAAC,MAAM,CAAC;oBACd,SAAS,EAAE,EAAE;oBACb,OAAO,EAAE,IAAA,QAAC,EAAC,4CAA4C,CAAC;iBACzD,CAAC,CAAC;gBAEH,MAAM,IAAI,CAAC,kBAAkB,CAAC,OAAO,EAAE,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAEnE,QAAQ,CAAC,MAAM,CAAC;oBACd,SAAS,EAAE,GAAG;oBACd,OAAO,EAAE,IAAA,QAAC,EAAC,sCAAsC,CAAC;iBACnD,CAAC,CAAC;YACL,CAAC,CACF,CAAC;YAEF,IAAI,CAAC,WAAW,CAAC,IAAA,QAAC,EAAC,8CAA8C,CAAC,CAAC,CAAC;SACrE;QAAC,OAAO,KAAK,EAAE;YACd,IAAI,CAAC,SAAS,CACZ,IAAA,QAAC,EAAC,yCAAyC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CACvE,CAAC;SACH;IACH,CAAC;CACF;AA5CD,0CA4CC"} \ No newline at end of file +{"version":3,"file":"ValidateCommand.js","sourceRoot":"","sources":["../../src/commands/ValidateCommand.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/vscode-extension/out/config/ConfigurationManager.js b/packages/vscode-extension/out/config/ConfigurationManager.js index d7144191..8f863c2a 100644 --- a/packages/vscode-extension/out/config/ConfigurationManager.js +++ b/packages/vscode-extension/out/config/ConfigurationManager.js @@ -1,89 +1,63 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if ( - !desc || - ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) - ) { - desc = { - enumerable: true, - get: function () { - return m[k]; - }, - }; - } - Object.defineProperty(o, k2, desc); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) - __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; - }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConfigurationManager = void 0; const vscode = __importStar(require("vscode")); class ConfigurationManager { - constructor() { - this.configuration = vscode.workspace.getConfiguration("stackcode"); - // Listen for configuration changes - vscode.workspace.onDidChangeConfiguration((event) => { - if (event.affectsConfiguration("stackcode")) { + constructor() { this.configuration = vscode.workspace.getConfiguration("stackcode"); - } - }); - } - get notificationsEnabled() { - return this.configuration.get("notifications.enabled", true); - } - get branchCheckEnabled() { - return this.configuration.get("notifications.branchCheck", true); - } - get commitCheckEnabled() { - return this.configuration.get("notifications.commitCheck", true); - } - get autoGenerateReadme() { - return this.configuration.get("autoGenerate.readme", false); - } - get autoGenerateGitignore() { - return this.configuration.get("autoGenerate.gitignore", true); - } - get defaultBranchType() { - return this.configuration.get("git.defaultBranchType", "feature"); - } - get dashboardAutoOpen() { - return this.configuration.get("dashboard.autoOpen", false); - } - async updateConfiguration(key, value) { - await this.configuration.update( - key, - value, - vscode.ConfigurationTarget.Workspace, - ); - } + vscode.workspace.onDidChangeConfiguration((event) => { + if (event.affectsConfiguration("stackcode")) { + this.configuration = vscode.workspace.getConfiguration("stackcode"); + } + }); + } + get notificationsEnabled() { + return this.configuration.get("notifications.enabled", true); + } + get branchCheckEnabled() { + return this.configuration.get("notifications.branchCheck", true); + } + get commitCheckEnabled() { + return this.configuration.get("notifications.commitCheck", true); + } + get autoGenerateReadme() { + return this.configuration.get("autoGenerate.readme", false); + } + get autoGenerateGitignore() { + return this.configuration.get("autoGenerate.gitignore", true); + } + get defaultBranchType() { + return this.configuration.get("git.defaultBranchType", "feature"); + } + get dashboardAutoOpen() { + return this.configuration.get("dashboard.autoOpen", false); + } + async updateConfiguration(key, value) { + await this.configuration.update(key, value, vscode.ConfigurationTarget.Workspace); + } } exports.ConfigurationManager = ConfigurationManager; -//# sourceMappingURL=ConfigurationManager.js.map +//# sourceMappingURL=ConfigurationManager.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/config/ConfigurationManager.js.map b/packages/vscode-extension/out/config/ConfigurationManager.js.map index 6bd5776c..a00e5858 100644 --- a/packages/vscode-extension/out/config/ConfigurationManager.js.map +++ b/packages/vscode-extension/out/config/ConfigurationManager.js.map @@ -1 +1 @@ -{"version":3,"file":"ConfigurationManager.js","sourceRoot":"","sources":["../../src/config/ConfigurationManager.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AAEjC,MAAa,oBAAoB;IAG/B;QACE,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;QAEpE,mCAAmC;QACnC,MAAM,CAAC,SAAS,CAAC,wBAAwB,CACvC,CAAC,KAAsC,EAAE,EAAE;YACzC,IAAI,KAAK,CAAC,oBAAoB,CAAC,WAAW,CAAC,EAAE;gBAC3C,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;aACrE;QACH,CAAC,CACF,CAAC;IACJ,CAAC;IAED,IAAI,oBAAoB;QACtB,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,uBAAuB,EAAE,IAAI,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,kBAAkB;QACpB,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,2BAA2B,EAAE,IAAI,CAAC,CAAC;IACnE,CAAC;IAED,IAAI,kBAAkB;QACpB,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,2BAA2B,EAAE,IAAI,CAAC,CAAC;IACnE,CAAC;IAED,IAAI,kBAAkB;QACpB,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;IAC9D,CAAC;IAED,IAAI,qBAAqB;QACvB,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,wBAAwB,EAAE,IAAI,CAAC,CAAC;IAChE,CAAC;IAED,IAAI,iBAAiB;QACnB,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,uBAAuB,EAAE,SAAS,CAAC,CAAC;IACpE,CAAC;IAED,IAAI,iBAAiB;QACnB,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,GAAW,EAAE,KAAc;QACnD,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,CAC7B,GAAG,EACH,KAAK,EACL,MAAM,CAAC,mBAAmB,CAAC,SAAS,CACrC,CAAC;IACJ,CAAC;CACF;AAnDD,oDAmDC"} \ No newline at end of file +{"version":3,"file":"ConfigurationManager.js","sourceRoot":"","sources":["../../src/config/ConfigurationManager.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AAEjC,MAAa,oBAAoB;IAG/B;QACE,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;QAEpE,MAAM,CAAC,SAAS,CAAC,wBAAwB,CACvC,CAAC,KAAsC,EAAE,EAAE;YACzC,IAAI,KAAK,CAAC,oBAAoB,CAAC,WAAW,CAAC,EAAE;gBAC3C,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;aACrE;QACH,CAAC,CACF,CAAC;IACJ,CAAC;IAED,IAAI,oBAAoB;QACtB,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,uBAAuB,EAAE,IAAI,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,kBAAkB;QACpB,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,2BAA2B,EAAE,IAAI,CAAC,CAAC;IACnE,CAAC;IAED,IAAI,kBAAkB;QACpB,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,2BAA2B,EAAE,IAAI,CAAC,CAAC;IACnE,CAAC;IAED,IAAI,kBAAkB;QACpB,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;IAC9D,CAAC;IAED,IAAI,qBAAqB;QACvB,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,wBAAwB,EAAE,IAAI,CAAC,CAAC;IAChE,CAAC;IAED,IAAI,iBAAiB;QACnB,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,uBAAuB,EAAE,SAAS,CAAC,CAAC;IACpE,CAAC;IAED,IAAI,iBAAiB;QACnB,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,GAAW,EAAE,KAAc;QACnD,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,CAC7B,GAAG,EACH,KAAK,EACL,MAAM,CAAC,mBAAmB,CAAC,SAAS,CACrC,CAAC;IACJ,CAAC;CACF;AAlDD,oDAkDC"} \ No newline at end of file diff --git a/packages/vscode-extension/out/extension.js b/packages/vscode-extension/out/extension.js index 9c776105..0de9951f 100644 --- a/packages/vscode-extension/out/extension.js +++ b/packages/vscode-extension/out/extension.js @@ -1,48 +1,27 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if ( - !desc || - ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) - ) { - desc = { - enumerable: true, - get: function () { - return m[k]; - }, - }; - } - Object.defineProperty(o, k2, desc); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) - __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; - }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.deactivate = exports.activate = void 0; const vscode = __importStar(require("vscode")); @@ -62,7 +41,7 @@ const TestGitHubDetectionCommand_1 = require("./commands/TestGitHubDetectionComm const DashboardProvider_1 = require("./providers/DashboardProvider"); const ProjectViewProvider_1 = require("./providers/ProjectViewProvider"); const GitHubAuthService_1 = require("./services/GitHubAuthService"); -const GitHubIssuesService_1 = require("./services/GitHubIssuesService"); +const ProgressManager_1 = require("./services/ProgressManager"); let proactiveManager; let gitMonitor; let fileMonitor; @@ -70,8 +49,7 @@ let configManager; let dashboardProvider; let projectViewProvider; let gitHubAuthService; -let gitHubIssuesService; -// Command instances +let progressManager; let initCommand; let generateCommand; let gitCommand; @@ -80,193 +58,83 @@ let validateCommand; let releaseCommand; let configCommand; let authCommand; +/** + * Activates the StackCode VS Code extension. + * Initializes all services, monitors, providers, and registers commands. + */ async function activate(context) { - console.log("🚀 [StackCode] Extension activation started!"); - console.log("🚀 [StackCode] Extension is now active!"); - console.log("🚀 [StackCode] Extension activation started..."); - console.log( - "🚀 [StackCode] Workspace folders:", - vscode.workspace.workspaceFolders?.length || 0, - ); - console.log("🚀 [StackCode] Extension path:", context.extensionPath); - // Initialize configuration manager - configManager = new ConfigurationManager_1.ConfigurationManager(); - // Initialize GitHub authentication service - gitHubAuthService = new GitHubAuthService_1.GitHubAuthService(context); - // Initialize notification manager - proactiveManager = - new ProactiveNotificationManager_1.ProactiveNotificationManager( - configManager, - ); - // Initialize monitors FIRST (dependencies for other services) - gitMonitor = new GitMonitor_1.GitMonitor(proactiveManager, configManager); - fileMonitor = new FileMonitor_1.FileMonitor(proactiveManager, configManager); - // Initialize GitHub issues service (depends on gitMonitor) - gitHubIssuesService = new GitHubIssuesService_1.GitHubIssuesService( - gitHubAuthService, - gitMonitor, - ); - // Initialize providers (after services are ready) - dashboardProvider = new DashboardProvider_1.DashboardProvider( - context, - gitHubIssuesService, - gitHubAuthService, - ); - projectViewProvider = new ProjectViewProvider_1.ProjectViewProvider( - context.workspaceState, - ); - // Initialize commands - initCommand = new InitCommand_1.InitCommand(); - generateCommand = new GenerateCommand_1.GenerateCommand(); - gitCommand = new GitCommand_1.GitCommand(); - commitCommand = new CommitCommand_1.CommitCommand(); - validateCommand = new ValidateCommand_1.ValidateCommand(); - releaseCommand = new ReleaseCommand_1.ReleaseCommand(); - configCommand = new ConfigCommand_1.ConfigCommand(); - authCommand = new AuthCommand_1.AuthCommand(gitHubAuthService); - // Register webview providers - context.subscriptions.push( - vscode.window.registerWebviewViewProvider( - "stackcode.dashboard", - dashboardProvider, - ), - vscode.window.registerTreeDataProvider( - "stackcode.projectView", - projectViewProvider, - ), - ); - // Register all commands - const commands = [ - // Core functionality commands - vscode.commands.registerCommand("stackcode.init", () => - initCommand.execute(), - ), - vscode.commands.registerCommand("stackcode.generate.readme", () => - generateCommand.generateReadme(), - ), - vscode.commands.registerCommand("stackcode.generate.gitignore", () => - generateCommand.generateGitignore(), - ), - vscode.commands.registerCommand("stackcode.git.start", () => - gitCommand.startBranch(), - ), - vscode.commands.registerCommand("stackcode.git.finish", () => - gitCommand.finishBranch(), - ), - vscode.commands.registerCommand("stackcode.commit", () => - commitCommand.execute(), - ), - vscode.commands.registerCommand("stackcode.validate", () => - validateCommand.execute(), - ), - vscode.commands.registerCommand("stackcode.release", () => - releaseCommand.execute(), - ), - vscode.commands.registerCommand("stackcode.config", () => - configCommand.execute(), - ), - vscode.commands.registerCommand("stackcode.dashboard", () => - dashboardProvider.show(), - ), - vscode.commands.registerCommand("stackcode.auth.login", () => { - console.log("🔐 [StackCode] AUTH LOGIN command executed!"); - vscode.window.showInformationMessage( - "🔐 StackCode: Executando login GitHub...", - ); - return authCommand.executeLogin(); - }), - vscode.commands.registerCommand("stackcode.auth.logout", () => { - console.log("🔓 [StackCode] AUTH LOGOUT command executed!"); - vscode.window.showInformationMessage( - "🔓 StackCode: Executando logout GitHub...", - ); - return authCommand.executeLogout(); - }), - // Test commands (development only) - vscode.commands.registerCommand("stackcode.test.github.detection", () => { - console.log("🧪 [StackCode] TEST GITHUB DETECTION command executed!"); - const testCommand = - new TestGitHubDetectionCommand_1.TestGitHubDetectionCommand(); - return testCommand.execute(); - }), - // Legacy commands for backward compatibility - vscode.commands.registerCommand("stackcode.createBranch", () => - gitCommand.startBranch(), - ), - vscode.commands.registerCommand("stackcode.formatCommitMessage", () => - commitCommand.execute(), - ), - vscode.commands.registerCommand("stackcode.checkBestPractices", () => - validateCommand.execute(), - ), - // Project view commands - vscode.commands.registerCommand("stackcode.projectView.refresh", () => - projectViewProvider.refresh(), - ), - // Webview commands - vscode.commands.registerCommand("webviewReady", () => { - console.log("[StackCode] Webview is ready!"); - // Pode enviar dados iniciais aqui se necessário - }), - vscode.commands.registerCommand("stackcode.webview.init", () => - initCommand.execute(), - ), - vscode.commands.registerCommand("stackcode.webview.generate.readme", () => - generateCommand.generateReadme(), - ), - vscode.commands.registerCommand( - "stackcode.webview.generate.gitignore", - () => generateCommand.generateGitignore(), - ), - vscode.commands.registerCommand("stackcode.webview.git.start", () => - gitCommand.startBranch(), - ), - vscode.commands.registerCommand("stackcode.webview.commit", () => - commitCommand.execute(), - ), - vscode.commands.registerCommand("stackcode.webview.validate", () => - validateCommand.execute(), - ), - ]; - // Add all to context subscriptions for cleanup - context.subscriptions.push( - ...commands, - gitMonitor, - fileMonitor, - proactiveManager, - dashboardProvider, - gitHubAuthService, - ); - console.log("📋 [StackCode] Commands registered:", commands.length); - console.log("🔐 [StackCode] Auth commands should be available now"); - console.log( - "🎯 [StackCode] Available commands: stackcode.auth.login, stackcode.auth.logout, stackcode.dashboard", - ); - // Initialize GitHub authentication - await gitHubAuthService.initializeFromStorage(); - // Start monitoring - gitMonitor.startMonitoring(); - fileMonitor.startMonitoring(); - // Auto-open dashboard if configured - if (configManager.dashboardAutoOpen) { - setTimeout(() => { - dashboardProvider.show(); - }, 1000); - } - // Show welcome message - proactiveManager.showWelcomeMessage(); + console.log("🚀 [StackCode] Extension activating..."); + configManager = new ConfigurationManager_1.ConfigurationManager(); + gitHubAuthService = new GitHubAuthService_1.GitHubAuthService(context); + progressManager = new ProgressManager_1.ProgressManager(); + proactiveManager = new ProactiveNotificationManager_1.ProactiveNotificationManager(configManager); + gitMonitor = new GitMonitor_1.GitMonitor(proactiveManager, configManager); + fileMonitor = new FileMonitor_1.FileMonitor(proactiveManager, configManager); + dashboardProvider = new DashboardProvider_1.DashboardProvider(context, gitHubAuthService, gitMonitor, progressManager); + projectViewProvider = new ProjectViewProvider_1.ProjectViewProvider(context.workspaceState); + initCommand = new InitCommand_1.InitCommand(); + generateCommand = new GenerateCommand_1.GenerateCommand(); + gitCommand = new GitCommand_1.GitCommand(); + commitCommand = new CommitCommand_1.CommitCommand(gitHubAuthService, gitMonitor, progressManager); + validateCommand = new ValidateCommand_1.ValidateCommand(); + releaseCommand = new ReleaseCommand_1.ReleaseCommand(gitHubAuthService, progressManager); + configCommand = new ConfigCommand_1.ConfigCommand(); + authCommand = new AuthCommand_1.AuthCommand(gitHubAuthService); + context.subscriptions.push(vscode.window.registerWebviewViewProvider("stackcode.dashboard", dashboardProvider), vscode.window.registerTreeDataProvider("stackcode.projectView", projectViewProvider)); + const commands = [ + vscode.commands.registerCommand("stackcode.init", () => initCommand.execute()), + vscode.commands.registerCommand("stackcode.validate.commit", () => validateCommand.validateCommitMessage()), + vscode.commands.registerCommand("stackcode.generate.readme", () => generateCommand.generateReadme()), + vscode.commands.registerCommand("stackcode.generate.gitignore", () => generateCommand.generateGitignore()), + vscode.commands.registerCommand("stackcode.git.start", () => gitCommand.startBranch()), + vscode.commands.registerCommand("stackcode.git.finish", () => gitCommand.finishBranch()), + vscode.commands.registerCommand("stackcode.commit", () => commitCommand.execute()), + vscode.commands.registerCommand("stackcode.validate", () => validateCommand.execute()), + vscode.commands.registerCommand("stackcode.release", () => releaseCommand.execute()), + vscode.commands.registerCommand("stackcode.config", () => configCommand.execute()), + vscode.commands.registerCommand("stackcode.dashboard", () => dashboardProvider.show()), + vscode.commands.registerCommand("stackcode.auth.login", () => authCommand.executeLogin()), + vscode.commands.registerCommand("stackcode.auth.logout", () => authCommand.executeLogout()), + vscode.commands.registerCommand("stackcode.test.github.detection", () => new TestGitHubDetectionCommand_1.TestGitHubDetectionCommand().execute()), + vscode.commands.registerCommand("stackcode.createBranch", () => gitCommand.startBranch()), + vscode.commands.registerCommand("stackcode.formatCommitMessage", () => commitCommand.execute()), + vscode.commands.registerCommand("stackcode.checkBestPractices", () => validateCommand.execute()), + vscode.commands.registerCommand("stackcode.projectView.refresh", () => projectViewProvider.refresh()), + vscode.commands.registerCommand("webviewReady", () => { }), + vscode.commands.registerCommand("stackcode.webview.init", () => initCommand.execute()), + vscode.commands.registerCommand("stackcode.webview.generate.readme", () => generateCommand.generateReadme()), + vscode.commands.registerCommand("stackcode.webview.generate.gitignore", () => generateCommand.generateGitignore()), + vscode.commands.registerCommand("stackcode.webview.git.start", () => gitCommand.startBranch()), + vscode.commands.registerCommand("stackcode.webview.commit", () => commitCommand.execute()), + vscode.commands.registerCommand("stackcode.webview.validate", () => validateCommand.execute()), + ]; + context.subscriptions.push(...commands, gitMonitor, fileMonitor, proactiveManager, dashboardProvider, gitHubAuthService, progressManager); + await gitHubAuthService.initializeFromStorage(); + gitMonitor.startMonitoring(); + fileMonitor.startMonitoring(); + if (configManager.dashboardAutoOpen) { + setTimeout(() => dashboardProvider.show(), 1000); + } + proactiveManager.showWelcomeMessage(); + console.log("✅ [StackCode] Extension activated successfully"); } exports.activate = activate; +/** + * Deactivates the extension and cleans up resources. + */ function deactivate() { - if (gitMonitor) { - gitMonitor.dispose(); - } - if (fileMonitor) { - fileMonitor.dispose(); - } - if (proactiveManager) { - proactiveManager.dispose(); - } + if (progressManager) { + progressManager.dispose(); + } + if (gitMonitor) { + gitMonitor.dispose(); + } + if (fileMonitor) { + fileMonitor.dispose(); + } + if (proactiveManager) { + proactiveManager.dispose(); + } } exports.deactivate = deactivate; -//# sourceMappingURL=extension.js.map +//# sourceMappingURL=extension.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/extension.js.map b/packages/vscode-extension/out/extension.js.map index d6773d6c..ddb914c8 100644 --- a/packages/vscode-extension/out/extension.js.map +++ b/packages/vscode-extension/out/extension.js.map @@ -1 +1 @@ -{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/vscode-extension/out/monitors/FileMonitor.js b/packages/vscode-extension/out/monitors/FileMonitor.js index 8efbdfbd..8df5a3cb 100644 --- a/packages/vscode-extension/out/monitors/FileMonitor.js +++ b/packages/vscode-extension/out/monitors/FileMonitor.js @@ -1,174 +1,133 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if ( - !desc || - ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) - ) { - desc = { - enumerable: true, - get: function () { - return m[k]; - }, - }; - } - Object.defineProperty(o, k2, desc); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) - __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; - }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.FileMonitor = void 0; const vscode = __importStar(require("vscode")); const i18n_1 = require("@stackcode/i18n"); class FileMonitor { - constructor(proactiveManager, configManager) { - this.disposables = []; - this.processedFiles = new Set(); - this.proactiveManager = proactiveManager; - this.configManager = configManager; - } - startMonitoring() { - this.disposables.push( - vscode.workspace.onDidCreateFiles((event) => { - for (const file of event.files) { - this.handleFileCreation(file); - } - }), - ); - this.disposables.push( - vscode.workspace.onDidChangeTextDocument((event) => { - this.handleFileChange(event); - }), - ); - this.disposables.push( - vscode.window.onDidChangeActiveTextEditor((editor) => { - if (editor) { - this.handleFileOpen(editor.document.uri); - } - }), - ); - } - async handleFileCreation(fileUri) { - if (!this.configManager.notificationsEnabled) { - return; - } - const fileName = fileUri.path.split("/").pop() || ""; - const fileKey = `${fileUri.toString()}-created`; - if (this.processedFiles.has(fileKey)) { - return; + constructor(proactiveManager, configManager) { + this.disposables = []; + this.processedFiles = new Set(); + this.proactiveManager = proactiveManager; + this.configManager = configManager; } - this.processedFiles.add(fileKey); - if (["README.md", ".gitignore"].includes(fileName)) { - await this.proactiveManager.showFileCreationSuggestion(fileName); + startMonitoring() { + this.disposables.push(vscode.workspace.onDidCreateFiles((event) => { + for (const file of event.files) { + this.handleFileCreation(file); + } + })); + this.disposables.push(vscode.workspace.onDidChangeTextDocument((event) => { + this.handleFileChange(event); + })); + this.disposables.push(vscode.window.onDidChangeActiveTextEditor((editor) => { + if (editor) { + this.handleFileOpen(editor.document.uri); + } + })); } - } - async handleFileChange(event) { - const document = event.document; - if (!document.fileName.includes("COMMIT_EDITMSG")) { - return; - } - const content = document.getText(); - if (content.trim()) { - await this.proactiveManager.showCommitMessageWarning(content); - } - } - async handleFileOpen(fileUri) { - const fileName = fileUri.path.split("/").pop() || ""; - const fileKey = `${fileUri.toString()}-opened`; - if (this.processedFiles.has(fileKey)) { - return; - } - this.processedFiles.add(fileKey); - if ( - fileName.endsWith(".js") || - fileName.endsWith(".ts") || - fileName.endsWith(".json") - ) { - await this.checkProjectStructure(); + async handleFileCreation(fileUri) { + if (!this.configManager.notificationsEnabled) { + return; + } + const fileName = fileUri.path.split("/").pop() || ""; + const fileKey = `${fileUri.toString()}-created`; + if (this.processedFiles.has(fileKey)) { + return; + } + this.processedFiles.add(fileKey); + if (["README.md", ".gitignore"].includes(fileName)) { + await this.proactiveManager.showFileCreationSuggestion(fileName); + } } - } - async checkProjectStructure() { - if (!this.configManager.notificationsEnabled) { - return; + async handleFileChange(event) { + const document = event.document; + if (!document.fileName.includes("COMMIT_EDITMSG")) { + return; + } + const content = document.getText(); + if (content.trim()) { + await this.proactiveManager.showCommitMessageWarning(content); + } } - const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; - if (!workspaceFolder) { - return; + async handleFileOpen(fileUri) { + const fileName = fileUri.path.split("/").pop() || ""; + const fileKey = `${fileUri.toString()}-opened`; + if (this.processedFiles.has(fileKey)) { + return; + } + this.processedFiles.add(fileKey); + if (fileName.endsWith(".js") || + fileName.endsWith(".ts") || + fileName.endsWith(".json")) { + await this.checkProjectStructure(); + } } - try { - const files = await vscode.workspace.fs.readDirectory( - workspaceFolder.uri, - ); - const fileNames = files.map(([name]) => name); - const missingFiles = []; - if (!fileNames.includes("README.md")) { - missingFiles.push("README.md"); - } - if (!fileNames.includes(".gitignore")) { - missingFiles.push(".gitignore"); - } - if (missingFiles.length > 0 && Math.random() < 0.3) { - const message = (0, i18n_1.t)("vscode.common.project_missing_files", { - missingFiles: missingFiles.join(", "), - }); - const action = await vscode.window.showInformationMessage( - message, - (0, i18n_1.t)("vscode.common.generate_files"), - (0, i18n_1.t)("vscode.common.not_now"), - (0, i18n_1.t)("vscode.common.dont_show_again"), - ); - if (action === (0, i18n_1.t)("vscode.common.generate_files")) { - vscode.window.showInformationMessage( - (0, i18n_1.t)("vscode.common.file_generation_available_soon"), - ); - } else if (action === (0, i18n_1.t)("vscode.common.dont_show_again")) { - await this.configManager.updateConfiguration( - "notifications.enabled", - false, - ); + async checkProjectStructure() { + if (!this.configManager.notificationsEnabled) { + return; } - } - } catch (error) { - const outputChannel = vscode.window.createOutputChannel("StackCode"); - outputChannel.appendLine( - (0, i18n_1.t)("vscode.common.error_checking_project_structure", { - error: String(error), - }), - ); + const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; + if (!workspaceFolder) { + return; + } + try { + const files = await vscode.workspace.fs.readDirectory(workspaceFolder.uri); + const fileNames = files.map(([name]) => name); + const missingFiles = []; + if (!fileNames.includes("README.md")) { + missingFiles.push("README.md"); + } + if (!fileNames.includes(".gitignore")) { + missingFiles.push(".gitignore"); + } + if (missingFiles.length > 0 && Math.random() < 0.3) { + const message = (0, i18n_1.t)("vscode.common.project_missing_files", { + missingFiles: missingFiles.join(", "), + }); + const action = await vscode.window.showInformationMessage(message, (0, i18n_1.t)("vscode.common.generate_files"), (0, i18n_1.t)("vscode.common.not_now"), (0, i18n_1.t)("vscode.common.dont_show_again")); + if (action === (0, i18n_1.t)("vscode.common.generate_files")) { + vscode.window.showInformationMessage((0, i18n_1.t)("vscode.common.file_generation_available_soon")); + } + else if (action === (0, i18n_1.t)("vscode.common.dont_show_again")) { + await this.configManager.updateConfiguration("notifications.enabled", false); + } + } + } + catch (error) { + const outputChannel = vscode.window.createOutputChannel("StackCode"); + outputChannel.appendLine((0, i18n_1.t)("vscode.common.error_checking_project_structure", { + error: String(error), + })); + } + } + dispose() { + this.disposables.forEach((d) => d.dispose()); + this.disposables = []; + this.processedFiles.clear(); } - } - dispose() { - this.disposables.forEach((d) => d.dispose()); - this.disposables = []; - this.processedFiles.clear(); - } } exports.FileMonitor = FileMonitor; -//# sourceMappingURL=FileMonitor.js.map +//# sourceMappingURL=FileMonitor.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/monitors/GitMonitor.js b/packages/vscode-extension/out/monitors/GitMonitor.js index bb8059f8..0881541e 100644 --- a/packages/vscode-extension/out/monitors/GitMonitor.js +++ b/packages/vscode-extension/out/monitors/GitMonitor.js @@ -1,381 +1,326 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if ( - !desc || - ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) - ) { - desc = { - enumerable: true, - get: function () { - return m[k]; - }, - }; - } - Object.defineProperty(o, k2, desc); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) - __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; - }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.GitMonitor = void 0; const vscode = __importStar(require("vscode")); const fs = __importStar(require("fs")); const path = __importStar(require("path")); +/** + * Monitors Git repository changes and provides Git-related functionality. + * Tracks branch changes, detects GitHub repositories, and provides Git workflow helpers. + */ class GitMonitor { - constructor(proactiveManager, configManager) { - this.disposables = []; - this.proactiveManager = proactiveManager; - this.configManager = configManager; - } - startMonitoring() { - const gitExtension = vscode.extensions.getExtension("vscode.git"); - if (gitExtension) { - if (gitExtension.isActive) { - this.setupGitMonitoring(); - } else { - gitExtension.activate().then(() => { - this.setupGitMonitoring(); - }); - } + constructor(proactiveManager, configManager) { + this.disposables = []; + this.proactiveManager = proactiveManager; + this.configManager = configManager; } - this.disposables.push( - vscode.workspace.onDidChangeWorkspaceFolders(() => { - this.checkCurrentBranch(); - }), - ); - setTimeout(() => { - this.checkCurrentBranch(); - }, 2000); - } - setupGitMonitoring() { - try { - const git = vscode.extensions.getExtension("vscode.git")?.exports; - if (git) { - const gitAPI = git.getAPI(1); - this.disposables.push( - gitAPI.onDidChangeState(() => { + /** + * Starts monitoring Git repository changes and workspace events. + */ + startMonitoring() { + const gitExtension = vscode.extensions.getExtension("vscode.git"); + if (gitExtension) { + if (gitExtension.isActive) { + this.setupGitMonitoring(); + } + else { + gitExtension.activate().then(() => { + this.setupGitMonitoring(); + }); + } + } + this.disposables.push(vscode.workspace.onDidChangeWorkspaceFolders(() => { this.checkCurrentBranch(); - }), - ); - this.disposables.push( - gitAPI.onDidOpenRepository(() => { + })); + setTimeout(() => { this.checkCurrentBranch(); - }), - ); - } - } catch (error) { - console.log("Failed to setup git monitoring:", error); - } - } - async checkCurrentBranch() { - try { - const git = vscode.extensions.getExtension("vscode.git")?.exports; - if (git) { - const gitAPI = git.getAPI(1); - const repo = gitAPI.repositories[0]; - if (repo && repo.state.HEAD) { - const currentBranch = repo.state.HEAD.name; - if (currentBranch && currentBranch !== this.lastBranch) { - this.lastBranch = currentBranch; - await this.proactiveManager.showBranchWarning(currentBranch); - } - } - } - } catch (error) { - console.log("Error checking current branch:", error); + }, 2000); } - } - async showCreateBranchDialog() { - const branchName = await vscode.window.showInputBox({ - prompt: "Enter the name for the new branch", - placeHolder: "feature/new-feature", - validateInput: (value) => { - if (!value) { - return "Branch name is required"; + setupGitMonitoring() { + try { + const git = vscode.extensions.getExtension("vscode.git")?.exports; + if (git) { + const gitAPI = git.getAPI(1); + this.disposables.push(gitAPI.onDidChangeState(() => { + this.checkCurrentBranch(); + })); + this.disposables.push(gitAPI.onDidOpenRepository(() => { + this.checkCurrentBranch(); + })); + } } - if (!/^[a-zA-Z0-9/_-]+$/.test(value)) { - return "Branch name can only contain letters, numbers, hyphens, underscores and slashes"; + catch (error) { + console.log("Failed to setup git monitoring:", error); } - return null; - }, - }); - if (branchName) { - const branchType = await vscode.window.showQuickPick( - [ - { label: "feature", description: "A new feature branch" }, - { label: "bugfix", description: "A bug fix branch" }, - { label: "hotfix", description: "A hotfix branch" }, - { label: "release", description: "A release branch" }, - ], - { - placeHolder: "Select branch type", - }, - ); - if (branchType) { - const fullBranchName = branchName.includes("/") - ? branchName - : `${branchType.label}/${branchName}`; + } + async checkCurrentBranch() { try { - const terminal = vscode.window.createTerminal("StackCode Git"); - terminal.sendText(`git checkout -b ${fullBranchName}`); - terminal.show(); - vscode.window.showInformationMessage( - `✅ Created and switched to branch: ${fullBranchName}`, - ); - } catch (error) { - vscode.window.showErrorMessage(`Failed to create branch: ${error}`); + const git = vscode.extensions.getExtension("vscode.git")?.exports; + if (git) { + const gitAPI = git.getAPI(1); + const repo = gitAPI.repositories[0]; + if (repo && repo.state.HEAD) { + const currentBranch = repo.state.HEAD.name; + if (currentBranch && currentBranch !== this.lastBranch) { + this.lastBranch = currentBranch; + await this.proactiveManager.showBranchWarning(currentBranch); + } + } + } + } + catch (error) { + console.log("Error checking current branch:", error); } - } } - } - async showCommitMessageDialog() { - const commitType = await vscode.window.showQuickPick( - [ - { label: "feat", description: "A new feature" }, - { label: "fix", description: "A bug fix" }, - { label: "docs", description: "Documentation changes" }, - { label: "style", description: "Code style changes (formatting, etc)" }, - { label: "refactor", description: "Code refactoring" }, - { label: "perf", description: "Performance improvements" }, - { label: "test", description: "Adding or updating tests" }, - { label: "chore", description: "Maintenance tasks" }, - { label: "build", description: "Build system changes" }, - { label: "ci", description: "CI/CD changes" }, - ], - { - placeHolder: "Select commit type", - }, - ); - if (!commitType) { - return; + async showCreateBranchDialog() { + const branchName = await vscode.window.showInputBox({ + prompt: "Enter the name for the new branch", + placeHolder: "feature/new-feature", + validateInput: (value) => { + if (!value) { + return "Branch name is required"; + } + if (!/^[a-zA-Z0-9/_-]+$/.test(value)) { + return "Branch name can only contain letters, numbers, hyphens, underscores and slashes"; + } + return null; + }, + }); + if (branchName) { + const branchType = await vscode.window.showQuickPick([ + { label: "feature", description: "A new feature branch" }, + { label: "bugfix", description: "A bug fix branch" }, + { label: "hotfix", description: "A hotfix branch" }, + { label: "release", description: "A release branch" }, + ], { + placeHolder: "Select branch type", + }); + if (branchType) { + const fullBranchName = branchName.includes("/") + ? branchName + : `${branchType.label}/${branchName}`; + try { + const terminal = vscode.window.createTerminal("StackCode Git"); + terminal.sendText(`git checkout -b ${fullBranchName}`); + terminal.show(); + vscode.window.showInformationMessage(`✅ Created and switched to branch: ${fullBranchName}`); + } + catch (error) { + vscode.window.showErrorMessage(`Failed to create branch: ${error}`); + } + } + } } - const scope = await vscode.window.showInputBox({ - prompt: "Enter scope (optional)", - placeHolder: "auth, api, ui, etc.", - }); - const description = await vscode.window.showInputBox({ - prompt: "Enter commit description", - placeHolder: "add user authentication", - validateInput: (value) => { - if (!value) { - return "Description is required"; + async showCommitMessageDialog() { + const commitType = await vscode.window.showQuickPick([ + { label: "feat", description: "A new feature" }, + { label: "fix", description: "A bug fix" }, + { label: "docs", description: "Documentation changes" }, + { label: "style", description: "Code style changes (formatting, etc)" }, + { label: "refactor", description: "Code refactoring" }, + { label: "perf", description: "Performance improvements" }, + { label: "test", description: "Adding or updating tests" }, + { label: "chore", description: "Maintenance tasks" }, + { label: "build", description: "Build system changes" }, + { label: "ci", description: "CI/CD changes" }, + ], { + placeHolder: "Select commit type", + }); + if (!commitType) { + return; + } + const scope = await vscode.window.showInputBox({ + prompt: "Enter scope (optional)", + placeHolder: "auth, api, ui, etc.", + }); + const description = await vscode.window.showInputBox({ + prompt: "Enter commit description", + placeHolder: "add user authentication", + validateInput: (value) => { + if (!value) { + return "Description is required"; + } + if (value.length > 50) { + return "Description should be 50 characters or less"; + } + return null; + }, + }); + if (!description) { + return; } - if (value.length > 50) { - return "Description should be 50 characters or less"; + let commitMessage = commitType.label; + if (scope) { + commitMessage += `(${scope})`; } - return null; - }, - }); - if (!description) { - return; - } - let commitMessage = commitType.label; - if (scope) { - commitMessage += `(${scope})`; + commitMessage += `: ${description}`; + await vscode.env.clipboard.writeText(commitMessage); + vscode.window + .showInformationMessage(`📋 Commit message copied to clipboard: ${commitMessage}`, "Open Git Panel") + .then((action) => { + if (action === "Open Git Panel") { + vscode.commands.executeCommand("workbench.view.scm"); + } + }); } - commitMessage += `: ${description}`; - await vscode.env.clipboard.writeText(commitMessage); - vscode.window - .showInformationMessage( - `📋 Commit message copied to clipboard: ${commitMessage}`, - "Open Git Panel", - ) - .then((action) => { - if (action === "Open Git Panel") { - vscode.commands.executeCommand("workbench.view.scm"); + /** + * Detects current GitHub repository using multiple strategies + */ + async getCurrentGitHubRepository() { + try { + console.log("🔍 [GitMonitor] Starting repository detection..."); + const fromConfigFile = await this.getRepositoryFromGitConfig(); + if (fromConfigFile) { + console.log(`✅ [GitMonitor] Repository detected via .git/config: ${fromConfigFile.fullName}`); + return fromConfigFile; + } + const fromGitAPI = await this.getRepositoryFromGitAPI(); + if (fromGitAPI) { + console.log(`✅ [GitMonitor] Repository detected via Git API: ${fromGitAPI.fullName}`); + return fromGitAPI; + } + console.warn("❌ [GitMonitor] No GitHub repository detected with any strategy"); + return null; + } + catch (error) { + console.error("❌ [GitMonitor] Failed to get current GitHub repository:", error); + return null; } - }); - } - /** - * Detecta o repositório GitHub atual usando múltiplas estratégias - */ - async getCurrentGitHubRepository() { - try { - console.log("🔍 [GitMonitor] Starting repository detection..."); - const fromConfigFile = await this.getRepositoryFromGitConfig(); - if (fromConfigFile) { - console.log( - `✅ [GitMonitor] Repository detected via .git/config: ${fromConfigFile.fullName}`, - ); - return fromConfigFile; - } - const fromGitAPI = await this.getRepositoryFromGitAPI(); - if (fromGitAPI) { - console.log( - `✅ [GitMonitor] Repository detected via Git API: ${fromGitAPI.fullName}`, - ); - return fromGitAPI; - } - console.warn( - "❌ [GitMonitor] No GitHub repository detected with any strategy", - ); - return null; - } catch (error) { - console.error( - "❌ [GitMonitor] Failed to get current GitHub repository:", - error, - ); - return null; } - } - /** - * Estratégia 1: Lê repositório diretamente do .git/config - */ - async getRepositoryFromGitConfig() { - try { - const workspaceFolders = vscode.workspace.workspaceFolders; - // Lista de caminhos para tentar - const pathsToTry = []; - if (workspaceFolders && workspaceFolders.length > 0) { - // Adicionar workspace folders configurados - workspaceFolders.forEach((folder) => { - pathsToTry.push(folder.uri.fsPath); - }); - } - // Adicionar caminhos alternativos comuns em dev containers - pathsToTry.push( - "/workspaces/StackCode", - process.cwd(), - path.join(process.cwd(), ".."), - path.join(process.cwd(), "..", ".."), - ); - console.log( - `🔍 [GitMonitor] Trying ${pathsToTry.length} possible paths:`, - pathsToTry, - ); - for (const folderPath of pathsToTry) { - const gitConfigPath = path.join(folderPath, ".git", "config"); - console.log(`🔍 [GitMonitor] Checking git config at: ${gitConfigPath}`); - if (fs.existsSync(gitConfigPath)) { - const configContent = fs.readFileSync(gitConfigPath, "utf8"); - console.log(`📄 [GitMonitor] Found .git/config at: ${folderPath}`); - // Procurar pela URL do remote origin - const originMatch = configContent.match( - /\[remote "origin"\]\s*\n\s*url\s*=\s*(.+)/, - ); - if (originMatch) { - const remoteUrl = originMatch[1].trim(); - console.log(`🔗 [GitMonitor] Found remote origin: ${remoteUrl}`); + /** + * Strategy 1: Reads repository directly from .git/config + */ + async getRepositoryFromGitConfig() { + try { + const workspaceFolders = vscode.workspace.workspaceFolders; + const pathsToTry = []; + if (workspaceFolders && workspaceFolders.length > 0) { + workspaceFolders.forEach((folder) => { + pathsToTry.push(folder.uri.fsPath); + }); + } + pathsToTry.push("/workspaces/StackCode", process.cwd(), path.join(process.cwd(), ".."), path.join(process.cwd(), "..", "..")); + console.log(`🔍 [GitMonitor] Trying ${pathsToTry.length} possible paths:`, pathsToTry); + for (const folderPath of pathsToTry) { + const gitConfigPath = path.join(folderPath, ".git", "config"); + console.log(`🔍 [GitMonitor] Checking git config at: ${gitConfigPath}`); + if (fs.existsSync(gitConfigPath)) { + const configContent = fs.readFileSync(gitConfigPath, "utf8"); + console.log(`📄 [GitMonitor] Found .git/config at: ${folderPath}`); + const originMatch = configContent.match(/\[remote "origin"\]\s*\n\s*url\s*=\s*(.+)/); + if (originMatch) { + const remoteUrl = originMatch[1].trim(); + console.log(`🔗 [GitMonitor] Found remote origin: ${remoteUrl}`); + const githubRepo = this.parseGitHubUrl(remoteUrl); + if (githubRepo) { + return githubRepo; + } + } + } + } + console.log("❌ [GitMonitor] No .git/config found in any path"); + return null; + } + catch (error) { + console.error("❌ [GitMonitor] Error reading .git/config:", error); + return null; + } + } /** + * Strategy 2: Via Git Extension API (original method as fallback) + */ + async getRepositoryFromGitAPI() { + try { + const git = vscode.extensions.getExtension("vscode.git")?.exports; + if (!git) { + console.warn("⚠️ [GitMonitor] Git extension not available"); + return null; + } + const gitAPI = git.getAPI(1); + if (!gitAPI || gitAPI.repositories.length === 0) { + console.warn("⚠️ [GitMonitor] No git repositories found via API"); + return null; + } + const repository = gitAPI.repositories[0]; + const remotes = repository.state.remotes; + const originRemote = remotes.find((remote) => remote.name === "origin"); + if (!originRemote) { + console.warn("⚠️ [GitMonitor] No origin remote found via API"); + return null; + } + const remoteUrl = originRemote.fetchUrl || originRemote.pushUrl; + if (!remoteUrl) { + console.warn("⚠️ [GitMonitor] No remote URL found via API"); + return null; + } const githubRepo = this.parseGitHubUrl(remoteUrl); - if (githubRepo) { - return githubRepo; + if (!githubRepo) { + console.warn("⚠️ [GitMonitor] Remote is not a GitHub repository:", remoteUrl); + return null; } - } + return githubRepo; + } + catch (error) { + console.error("❌ [GitMonitor] Failed to get repository via Git API:", error); + return null; } - } - console.log("❌ [GitMonitor] No .git/config found in any path"); - return null; - } catch (error) { - console.error("❌ [GitMonitor] Error reading .git/config:", error); - return null; - } - } /** - * Estratégia 2: Via Git Extension API (método original como fallback) - */ - async getRepositoryFromGitAPI() { - try { - const git = vscode.extensions.getExtension("vscode.git")?.exports; - if (!git) { - console.warn("⚠️ [GitMonitor] Git extension not available"); - return null; - } - const gitAPI = git.getAPI(1); - if (!gitAPI || gitAPI.repositories.length === 0) { - console.warn("⚠️ [GitMonitor] No git repositories found via API"); - return null; - } - const repository = gitAPI.repositories[0]; - const remotes = repository.state.remotes; - const originRemote = remotes.find((remote) => remote.name === "origin"); - if (!originRemote) { - console.warn("⚠️ [GitMonitor] No origin remote found via API"); - return null; - } - const remoteUrl = originRemote.fetchUrl || originRemote.pushUrl; - if (!remoteUrl) { - console.warn("⚠️ [GitMonitor] No remote URL found via API"); - return null; - } - const githubRepo = this.parseGitHubUrl(remoteUrl); - if (!githubRepo) { - console.warn( - "⚠️ [GitMonitor] Remote is not a GitHub repository:", - remoteUrl, - ); - return null; - } - return githubRepo; - } catch (error) { - console.error( - "❌ [GitMonitor] Failed to get repository via Git API:", - error, - ); - return null; } - } - /** - * Parse URLs do GitHub em diferentes formatos - */ - parseGitHubUrl(url) { - try { - const cleanUrl = url.replace(/\.git$/, ""); - const patterns = [ - // HTTPS: https://github.com/owner/repo - /^https:\/\/github\.com\/([^/]+)\/([^/]+)$/, - // SSH: git@github.com:owner/repo - /^git@github\.com:([^/]+)\/([^/]+)$/, - // SSH alternative: ssh://git@github.com/owner/repo - /^ssh:\/\/git@github\.com\/([^/]+)\/([^/]+)$/, - ]; - for (const pattern of patterns) { - const match = cleanUrl.match(pattern); - if (match) { - const [, owner, repo] = match; - return { - owner, - repo, - fullName: `${owner}/${repo}`, - remoteUrl: url, - }; + /** + * Parse URLs do GitHub em diferentes formatos + */ + parseGitHubUrl(url) { + try { + const cleanUrl = url.replace(/\.git$/, ""); + const patterns = [ + /^https:\/\/github\.com\/([^/]+)\/([^/]+)$/, + /^git@github\.com:([^/]+)\/([^/]+)$/, + /^ssh:\/\/git@github\.com\/([^/]+)\/([^/]+)$/, + ]; + for (const pattern of patterns) { + const match = cleanUrl.match(pattern); + if (match) { + const [, owner, repo] = match; + return { + owner, + repo, + fullName: `${owner}/${repo}`, + remoteUrl: url, + }; + } + } + return null; } - } - return null; - } catch (error) { - console.error("[GitMonitor] Failed to parse GitHub URL:", error); - return null; + catch (error) { + console.error("[GitMonitor] Failed to parse GitHub URL:", error); + return null; + } + } + dispose() { + this.disposables.forEach((d) => d.dispose()); + this.disposables = []; } - } - dispose() { - this.disposables.forEach((d) => d.dispose()); - this.disposables = []; - } } exports.GitMonitor = GitMonitor; -//# sourceMappingURL=GitMonitor.js.map +//# sourceMappingURL=GitMonitor.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/monitors/GitMonitor.js.map b/packages/vscode-extension/out/monitors/GitMonitor.js.map index 630987a1..470e0046 100644 --- a/packages/vscode-extension/out/monitors/GitMonitor.js.map +++ b/packages/vscode-extension/out/monitors/GitMonitor.js.map @@ -1 +1 @@ -{"version":3,"file":"GitMonitor.js","sourceRoot":"","sources":["../../src/monitors/GitMonitor.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"GitMonitor.js","sourceRoot":"","sources":["../../src/monitors/GitMonitor.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/vscode-extension/out/notifications/ProactiveNotificationManager.js b/packages/vscode-extension/out/notifications/ProactiveNotificationManager.js index 4a6d94f3..55b55688 100644 --- a/packages/vscode-extension/out/notifications/ProactiveNotificationManager.js +++ b/packages/vscode-extension/out/notifications/ProactiveNotificationManager.js @@ -1,268 +1,201 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if ( - !desc || - ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) - ) { - desc = { - enumerable: true, - get: function () { - return m[k]; - }, - }; - } - Object.defineProperty(o, k2, desc); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) - __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; - }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProactiveNotificationManager = void 0; const vscode = __importStar(require("vscode")); class ProactiveNotificationManager { - constructor(configManager) { - this.notificationQueue = []; - this.configManager = configManager; - } - async showWelcomeMessage() { - if (!this.configManager.notificationsEnabled) { - return; + constructor(configManager) { + this.notificationQueue = []; + this.configManager = configManager; } - const action = await vscode.window.showInformationMessage( - "🚀 StackCode is now active! Get proactive suggestions to improve your development workflow.", - "Learn More", - "Settings", - ); - if (action === "Learn More") { - vscode.env.openExternal( - vscode.Uri.parse("https://github.com/YagoBorba/StackCode"), - ); - } else if (action === "Settings") { - vscode.commands.executeCommand( - "workbench.action.openSettings", - "stackcode", - ); - } - } - async showBranchWarning(currentBranch) { - if (!this.configManager.branchCheckEnabled) { - return; + async showWelcomeMessage() { + if (!this.configManager.notificationsEnabled) { + return; + } + const action = await vscode.window.showInformationMessage("🚀 StackCode is now active! Get proactive suggestions to improve your development workflow.", "Learn More", "Settings"); + if (action === "Learn More") { + vscode.env.openExternal(vscode.Uri.parse("https://github.com/YagoBorba/StackCode")); + } + else if (action === "Settings") { + vscode.commands.executeCommand("workbench.action.openSettings", "stackcode"); + } } - const isMainBranch = ["main", "master", "develop"].includes(currentBranch); - if (isMainBranch) { - const action = await vscode.window.showWarningMessage( - `⚠️ You are working on the ${currentBranch} branch. Would you like to create a new feature branch?`, - "Create Branch", - "Continue", - "Don't Show Again", - ); - if (action === "Create Branch") { - vscode.commands.executeCommand("stackcode.createBranch"); - } else if (action === "Don't Show Again") { - await this.configManager.updateConfiguration( - "notifications.branchCheck", - false, - ); - } + async showBranchWarning(currentBranch) { + if (!this.configManager.branchCheckEnabled) { + return; + } + const isMainBranch = ["main", "master", "develop"].includes(currentBranch); + if (isMainBranch) { + const action = await vscode.window.showWarningMessage(`⚠️ You are working on the ${currentBranch} branch. Would you like to create a new feature branch?`, "Create Branch", "Continue", "Don't Show Again"); + if (action === "Create Branch") { + vscode.commands.executeCommand("stackcode.createBranch"); + } + else if (action === "Don't Show Again") { + await this.configManager.updateConfiguration("notifications.branchCheck", false); + } + } } - } - async showCommitMessageWarning(message) { - if (!this.configManager.commitCheckEnabled) { - return; + async showCommitMessageWarning(message) { + if (!this.configManager.commitCheckEnabled) { + return; + } + const isConventional = this.isConventionalCommit(message); + if (!isConventional) { + const action = await vscode.window.showWarningMessage("💬 We detected you are trying to commit without a conventional message. Would you like help formatting it?", "Format Message", "Continue", "Learn More"); + if (action === "Format Message") { + vscode.commands.executeCommand("stackcode.formatCommitMessage"); + } + else if (action === "Learn More") { + vscode.env.openExternal(vscode.Uri.parse("https://conventionalcommits.org/")); + } + } } - const isConventional = this.isConventionalCommit(message); - if (!isConventional) { - const action = await vscode.window.showWarningMessage( - "💬 We detected you are trying to commit without a conventional message. Would you like help formatting it?", - "Format Message", - "Continue", - "Learn More", - ); - if (action === "Format Message") { - vscode.commands.executeCommand("stackcode.formatCommitMessage"); - } else if (action === "Learn More") { - vscode.env.openExternal( - vscode.Uri.parse("https://conventionalcommits.org/"), - ); - } + async showFileCreationSuggestion(fileName) { + if (!this.configManager.notificationsEnabled) { + return; + } + if (fileName === "README.md") { + const action = await vscode.window.showInformationMessage("📝 Would you like to generate a comprehensive README.md using StackCode templates?", "Generate README", "Not Now"); + if (action === "Generate README") { + // TODO: Implement README generation + vscode.window.showInformationMessage("README generation will be available soon!"); + } + } + else if (fileName === ".gitignore") { + const action = await vscode.window.showInformationMessage("🚫 Would you like to generate a .gitignore file based on your project type?", "Generate .gitignore", "Not Now"); + if (action === "Generate .gitignore") { + // TODO: Implement .gitignore generation + vscode.window.showInformationMessage(".gitignore generation will be available soon!"); + } + } } - } - async showFileCreationSuggestion(fileName) { - if (!this.configManager.notificationsEnabled) { - return; + async runFullBestPracticesCheck() { + const issues = []; + try { + const gitExtension = vscode.extensions.getExtension("vscode.git")?.exports; + if (gitExtension) { + const repo = gitExtension.getAPI(1).repositories[0]; + if (repo && + ["main", "master", "develop"].includes(repo.state.HEAD?.name || "")) { + issues.push("Working on main/develop branch"); + } + } + } + catch (error) { + console.log("Git extension error:", error); + } + const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; + if (workspaceFolder) { + const files = await vscode.workspace.fs.readDirectory(workspaceFolder.uri); + const fileNames = files.map(([name]) => name); + if (!fileNames.includes("README.md")) { + issues.push("Missing README.md file"); + } + if (!fileNames.includes(".gitignore")) { + issues.push("Missing .gitignore file"); + } + } + if (issues.length === 0) { + vscode.window.showInformationMessage("✅ All best practices checks passed!"); + } + else { + const message = `Found ${issues.length} potential improvements:\n${issues.map((issue) => `• ${issue}`).join("\n")}`; + vscode.window.showWarningMessage(message, "Fix Issues"); + } } - if (fileName === "README.md") { - const action = await vscode.window.showInformationMessage( - "📝 Would you like to generate a comprehensive README.md using StackCode templates?", - "Generate README", - "Not Now", - ); - if (action === "Generate README") { - // TODO: Implement README generation - vscode.window.showInformationMessage( - "README generation will be available soon!", - ); - } - } else if (fileName === ".gitignore") { - const action = await vscode.window.showInformationMessage( - "🚫 Would you like to generate a .gitignore file based on your project type?", - "Generate .gitignore", - "Not Now", - ); - if (action === "Generate .gitignore") { - // TODO: Implement .gitignore generation - vscode.window.showInformationMessage( - ".gitignore generation will be available soon!", - ); - } + isConventionalCommit(message) { + const conventionalPattern = /^(feat|fix|docs|style|refactor|perf|test|chore|build|ci)(\(.+\))?: .+/; + return conventionalPattern.test(message); } - } - async runFullBestPracticesCheck() { - const issues = []; - // Check if working on main branch - try { - const gitExtension = - vscode.extensions.getExtension("vscode.git")?.exports; - if (gitExtension) { - const repo = gitExtension.getAPI(1).repositories[0]; - if ( - repo && - ["main", "master", "develop"].includes(repo.state.HEAD?.name || "") - ) { - issues.push("Working on main/develop branch"); + async handleApplyFix(message) { + if (message.includes("README")) { + await vscode.commands.executeCommand("stackcode.generate.readme"); + } + else if (message.includes("gitignore")) { + await vscode.commands.executeCommand("stackcode.generate.gitignore"); + } + else if (message.includes("commit")) { + await vscode.commands.executeCommand("stackcode.commit"); + } + else { + await vscode.commands.executeCommand("stackcode.validate"); } - } - } catch (error) { - // Git extension not available or error accessing it - console.log("Git extension error:", error); } - // Check for missing files - const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; - if (workspaceFolder) { - const files = await vscode.workspace.fs.readDirectory( - workspaceFolder.uri, - ); - const fileNames = files.map(([name]) => name); - if (!fileNames.includes("README.md")) { - issues.push("Missing README.md file"); - } - if (!fileNames.includes(".gitignore")) { - issues.push("Missing .gitignore file"); - } + async handleLearnMore(message) { + const learnMoreUrls = { + "conventional commits": "https://conventionalcommits.org/", + gitflow: "https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow", + readme: "https://www.makeareadme.com/", + "git best practices": "https://sethrobertson.github.io/GitBestPractices/", + }; + const topic = Object.keys(learnMoreUrls).find((key) => message.toLowerCase().includes(key)) || "git best practices"; + await vscode.env.openExternal(vscode.Uri.parse(learnMoreUrls[topic])); } - if (issues.length === 0) { - vscode.window.showInformationMessage( - "✅ All best practices checks passed!", - ); - } else { - const message = `Found ${issues.length} potential improvements:\n${issues.map((issue) => `• ${issue}`).join("\n")}`; - vscode.window.showWarningMessage(message, "Fix Issues"); + async handleStartWorkflow(workflowType) { + switch (workflowType) { + case "feature": + await vscode.commands.executeCommand("stackcode.git.feature.start"); + break; + case "hotfix": + await vscode.commands.executeCommand("stackcode.git.hotfix.start"); + break; + case "release": + await vscode.commands.executeCommand("stackcode.release"); + break; + default: + await vscode.commands.executeCommand("stackcode.git.feature.start"); + } } - } - isConventionalCommit(message) { - const conventionalPattern = - /^(feat|fix|docs|style|refactor|perf|test|chore|build|ci)(\(.+\))?: .+/; - return conventionalPattern.test(message); - } - async handleApplyFix(message) { - // Enhanced fix handling with specific actions - if (message.includes("README")) { - await vscode.commands.executeCommand("stackcode.generate.readme"); - } else if (message.includes("gitignore")) { - await vscode.commands.executeCommand("stackcode.generate.gitignore"); - } else if (message.includes("commit")) { - await vscode.commands.executeCommand("stackcode.commit"); - } else { - await vscode.commands.executeCommand("stackcode.validate"); + async handleConfigureWorkflow() { + await vscode.commands.executeCommand("stackcode.config"); } - } - async handleLearnMore(message) { - const learnMoreUrls = { - "conventional commits": "https://conventionalcommits.org/", - gitflow: - "https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow", - readme: "https://www.makeareadme.com/", - "git best practices": "https://sethrobertson.github.io/GitBestPractices/", - }; - const topic = - Object.keys(learnMoreUrls).find((key) => - message.toLowerCase().includes(key), - ) || "git best practices"; - await vscode.env.openExternal(vscode.Uri.parse(learnMoreUrls[topic])); - } - async handleStartWorkflow(workflowType) { - switch (workflowType) { - case "feature": - await vscode.commands.executeCommand("stackcode.git.feature.start"); - break; - case "hotfix": - await vscode.commands.executeCommand("stackcode.git.hotfix.start"); - break; - case "release": - await vscode.commands.executeCommand("stackcode.release"); - break; - default: - await vscode.commands.executeCommand("stackcode.git.feature.start"); + async handleFixProjectIssue(issue) { + if (issue.includes("README")) { + await vscode.commands.executeCommand("stackcode.generate.readme"); + } + else if (issue.includes("gitignore")) { + await vscode.commands.executeCommand("stackcode.generate.gitignore"); + } + else { + await vscode.commands.executeCommand("stackcode.validate"); + } } - } - async handleConfigureWorkflow() { - await vscode.commands.executeCommand("stackcode.config"); - } - async handleFixProjectIssue(issue) { - if (issue.includes("README")) { - await vscode.commands.executeCommand("stackcode.generate.readme"); - } else if (issue.includes("gitignore")) { - await vscode.commands.executeCommand("stackcode.generate.gitignore"); - } else { - await vscode.commands.executeCommand("stackcode.validate"); + async handleShowDetails(issue) { + const outputChannel = vscode.window.createOutputChannel("StackCode Details"); + outputChannel.appendLine(`=== Project Issue Details ===`); + outputChannel.appendLine(`Issue: ${issue}`); + outputChannel.appendLine(`Timestamp: ${new Date().toISOString()}`); + outputChannel.appendLine(`Workspace: ${vscode.workspace.name || "Unknown"}`); + outputChannel.appendLine(""); + outputChannel.appendLine("Suggested Actions:"); + outputChannel.appendLine("1. Run project validation"); + outputChannel.appendLine("2. Check project structure"); + outputChannel.appendLine("3. Review best practices"); + outputChannel.show(); } - } - async handleShowDetails(issue) { - const outputChannel = - vscode.window.createOutputChannel("StackCode Details"); - outputChannel.appendLine(`=== Project Issue Details ===`); - outputChannel.appendLine(`Issue: ${issue}`); - outputChannel.appendLine(`Timestamp: ${new Date().toISOString()}`); - outputChannel.appendLine( - `Workspace: ${vscode.workspace.name || "Unknown"}`, - ); - outputChannel.appendLine(""); - outputChannel.appendLine("Suggested Actions:"); - outputChannel.appendLine("1. Run project validation"); - outputChannel.appendLine("2. Check project structure"); - outputChannel.appendLine("3. Review best practices"); - outputChannel.show(); - } - dispose() { - // Cleanup if needed - } + dispose() { } } exports.ProactiveNotificationManager = ProactiveNotificationManager; -//# sourceMappingURL=ProactiveNotificationManager.js.map +//# sourceMappingURL=ProactiveNotificationManager.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/notifications/ProactiveNotificationManager.js.map b/packages/vscode-extension/out/notifications/ProactiveNotificationManager.js.map index cb7165d7..7816a0c4 100644 --- a/packages/vscode-extension/out/notifications/ProactiveNotificationManager.js.map +++ b/packages/vscode-extension/out/notifications/ProactiveNotificationManager.js.map @@ -1 +1 @@ -{"version":3,"file":"ProactiveNotificationManager.js","sourceRoot":"","sources":["../../src/notifications/ProactiveNotificationManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"ProactiveNotificationManager.js","sourceRoot":"","sources":["../../src/notifications/ProactiveNotificationManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/vscode-extension/out/providers/DashboardProvider.js b/packages/vscode-extension/out/providers/DashboardProvider.js index 81919099..a8ea0b9b 100644 --- a/packages/vscode-extension/out/providers/DashboardProvider.js +++ b/packages/vscode-extension/out/providers/DashboardProvider.js @@ -1,273 +1,237 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if ( - !desc || - ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) - ) { - desc = { - enumerable: true, - get: function () { - return m[k]; - }, - }; - } - Object.defineProperty(o, k2, desc); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) - __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; - }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.DashboardProvider = void 0; const vscode = __importStar(require("vscode")); const path = __importStar(require("path")); const fs = __importStar(require("fs")); +const core_1 = require("@stackcode/core"); +/** + * Provides the StackCode dashboard webview interface. + * Manages project statistics, GitHub issues, and integrates with core workflows. + * Implements WebviewProgressListener to receive and display progress updates. + */ class DashboardProvider { - constructor(context, issuesService, authService) { - this._disposables = []; - this._extensionUri = context.extensionUri; - this._issuesService = issuesService; - this._authService = authService; - } - resolveWebviewView( - webviewView, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - context, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - token, - ) { - this._view = webviewView; - webviewView.webview.options = { - enableScripts: true, - localResourceRoots: [vscode.Uri.joinPath(this._extensionUri, "dist")], - }; - webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); - webviewView.webview.onDidReceiveMessage( - async (data) => { - console.log(`[StackCode] Received command from webview: ${data.type}`); - try { - // Tratar comandos específicos do webview - switch (data.type) { - case "webviewReady": - console.log( - "[StackCode] Webview reported ready, sending initial data", - ); - this.updateProjectStats(); - // Buscar issues automaticamente se autenticado - if (this._authService?.isAuthenticated) { - await this.updateIssues(); - } - return; - case "refreshStats": - this.updateProjectStats(); - return; - case "fetchIssues": - await this.updateIssues(); - return; - case "refreshIssues": - await this.updateIssues(true); - return; - default: - // Executar comando normal do VS Code - await vscode.commands.executeCommand(data.type, data.payload); - } - } catch (error) { - console.error( - `[StackCode] Error executing command ${data.type}:`, - error, - ); - this.sendMessage({ - type: "commandError", - payload: { - command: data.type, - error: error instanceof Error ? error.message : "Unknown error", - }, - }); + constructor(context, authService, gitMonitor, progressManager) { + this._disposables = []; + this._extensionUri = context.extensionUri; + this._authService = authService; + this._gitMonitor = gitMonitor; + this._progressManager = progressManager; + if (this._progressManager) { + this._progressManager.registerWebviewProvider(this); } - }, - undefined, - this._disposables, - ); - // WebviewView doesn't have onDidBecomeVisible, so we'll update stats immediately - this.updateProjectStats(); - } - sendMessage(message) { - if (this._view) { - this._view.webview.postMessage(message); } - } - show() { - if (this._view) { - this._view.show?.(true); - } else { - // If view is not created yet, trigger the creation by executing the show command - vscode.commands.executeCommand("workbench.view.extension.stackcode"); + resolveWebviewView(webviewView) { + this._view = webviewView; + webviewView.webview.options = { + enableScripts: true, + localResourceRoots: [vscode.Uri.joinPath(this._extensionUri, "dist")], + }; + webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); + webviewView.webview.onDidReceiveMessage(async (data) => { + try { + switch (data.type) { + case "webviewReady": + this.updateProjectStats(); + if (this._authService?.isAuthenticated) { + await this.updateIssues(); + } + return; + case "refreshStats": + this.updateProjectStats(); + return; + case "fetchIssues": + await this.updateIssues(); + return; + case "refreshIssues": + await this.updateIssues(true); + return; + default: + await vscode.commands.executeCommand(data.type, data.payload); + } + } + catch (error) { + this.sendMessage({ + type: "commandError", + payload: { + command: data.type, + error: error instanceof Error ? error.message : "Unknown error", + }, + }); + } + }, undefined, this._disposables); + this.updateProjectStats(); } - } - async updateIssues(forceRefresh = false) { - try { - if (!this._issuesService || !this._authService) { - console.warn("[DashboardProvider] Issues service not available"); - return; - } - // Verificar se está autenticado - if (!this._authService.isAuthenticated) { - this.sendMessage({ - type: "updateIssues", - payload: { - issues: [], - error: "Not authenticated with GitHub", - needsAuth: true, - }, - }); - return; - } - console.log("[DashboardProvider] Fetching GitHub issues..."); - const issues = forceRefresh - ? await this._issuesService.refreshIssues() - : await this._issuesService.fetchCurrentRepositoryIssues(); - this.sendMessage({ - type: "updateIssues", - payload: { - issues, - timestamp: new Date().toISOString(), - }, - }); - console.log( - `[DashboardProvider] Sent ${issues.length} issues to webview`, - ); - } catch (error) { - console.error("[DashboardProvider] Failed to fetch issues:", error); - this.sendMessage({ - type: "updateIssues", - payload: { - issues: [], - error: - error instanceof Error ? error.message : "Failed to fetch issues", - needsAuth: - error instanceof Error && - error.message.includes("not authenticated"), - }, - }); + sendMessage(message) { + if (this._view) { + this._view.webview.postMessage(message); + } } - } - async updateProjectStats() { - if (!this._view) { - console.log("[StackCode] No view available for stats update"); - return; + show() { + if (this._view) { + this._view.show?.(true); + } + else { + vscode.commands.executeCommand("workbench.view.extension.stackcode"); + } } - const workspaceFolders = vscode.workspace.workspaceFolders; - console.log( - "[StackCode] Workspace folders:", - workspaceFolders?.length || 0, - ); - console.log("[StackCode] Workspace name:", vscode.workspace.name); - console.log( - "[StackCode] Workspace file:", - vscode.workspace.workspaceFile?.toString(), - ); - if (!workspaceFolders || workspaceFolders.length === 0) { - console.log( - "[StackCode] No workspace folders found, using alternative detection", - ); - // Fallback: usar informações do contexto da extensão - const extensionWorkspace = path.dirname( - path.dirname(path.dirname(this._extensionUri.fsPath)), - ); - console.log("[StackCode] Extension workspace path:", extensionWorkspace); - this.sendMessage({ - type: "updateStats", - payload: { - files: 0, - workspaceName: "StackCode (Debug)", - workspacePath: extensionWorkspace, - mode: "development", - }, - }); - return; + async updateIssues(forceRefresh = false) { + try { + if (!this._authService || !this._gitMonitor) { + return; + } + if (!this._authService.isAuthenticated) { + this.sendMessage({ + type: "updateIssues", + payload: { + issues: [], + error: "Not authenticated with GitHub", + needsAuth: true, + }, + }); + return; + } + const repository = await this._gitMonitor.getCurrentGitHubRepository(); + if (!repository) { + this.sendMessage({ + type: "updateIssues", + payload: { + issues: [], + error: "No GitHub repository detected", + needsAuth: false, + }, + }); + return; + } + if (forceRefresh) { + (0, core_1.clearRepositoryCache)({ + owner: repository.owner, + repo: repository.repo, + fullName: repository.fullName, + }); + } + const client = await this._authService.getAuthenticatedClient(); + if (this._progressManager) { + this._progressManager.startWorkflow("issues"); + } + const result = await (0, core_1.runIssuesWorkflow)({ + client, + repository: { + owner: repository.owner, + repo: repository.repo, + fullName: repository.fullName, + }, + enableCache: !forceRefresh, + }, { + onProgress: this._progressManager + ? this._progressManager.createProgressHook("issues") + : undefined, + }); + if (result.status === "error") { + if (this._progressManager) { + this._progressManager.failWorkflow("issues", result.error || "Failed to fetch issues"); + } + throw new Error(result.error || "Failed to fetch issues"); + } + if (this._progressManager) { + this._progressManager.completeWorkflow("issues", `Fetched ${result.issues.length} issues`); + } + this.sendMessage({ + type: "updateIssues", + payload: { + issues: result.issues, + timestamp: result.timestamp, + }, + }); + } + catch (error) { + if (this._progressManager) { + this._progressManager.failWorkflow("issues", error instanceof Error ? error.message : "Failed to fetch issues"); + } + this.sendMessage({ + type: "updateIssues", + payload: { + issues: [], + error: error instanceof Error ? error.message : "Failed to fetch issues", + needsAuth: error instanceof Error && + error.message.includes("not authenticated"), + }, + }); + } } - try { - const files = await vscode.workspace.findFiles( - "**/*", - "**/node_modules/**", - 1000, - ); - console.log("[StackCode] Found files:", files.length); - this.sendMessage({ - type: "updateStats", - payload: { - files: files.length, - workspaceName: workspaceFolders[0].name, - workspacePath: workspaceFolders[0].uri.fsPath, - mode: "production", - }, - }); - } catch (e) { - console.error("[StackCode] Error fetching project stats:", e); - this.sendMessage({ - type: "updateStats", - payload: { files: 0, error: "Failed to scan files" }, - }); + async updateProjectStats() { + if (!this._view) { + return; + } + const workspaceFolders = vscode.workspace.workspaceFolders; + if (!workspaceFolders || workspaceFolders.length === 0) { + const extensionWorkspace = path.dirname(path.dirname(path.dirname(this._extensionUri.fsPath))); + this.sendMessage({ + type: "updateStats", + payload: { + files: 0, + workspaceName: "StackCode (Debug)", + workspacePath: extensionWorkspace, + mode: "development", + }, + }); + return; + } + try { + const files = await vscode.workspace.findFiles("**/*", "**/node_modules/**", 1000); + this.sendMessage({ + type: "updateStats", + payload: { + files: files.length, + workspaceName: workspaceFolders[0].name, + workspacePath: workspaceFolders[0].uri.fsPath, + mode: "production", + }, + }); + } + catch { + this.sendMessage({ + type: "updateStats", + payload: { files: 0, error: "Failed to scan files" }, + }); + } } - } - _getHtmlForWebview(webview) { - const nonce = getNonce(); - const buildPath = vscode.Uri.joinPath( - this._extensionUri, - "dist", - "webview-ui", - ); - // Lê o manifest.json do Vite - const manifestPath = path.join(buildPath.fsPath, ".vite", "manifest.json"); - console.log("[StackCode] Build path:", buildPath.fsPath); - console.log("[StackCode] Manifest path:", manifestPath); - console.log("[StackCode] Manifest exists:", fs.existsSync(manifestPath)); - try { - const manifestContent = fs.readFileSync(manifestPath, "utf-8"); - const manifest = JSON.parse(manifestContent); - console.log("[StackCode] Manifest content:", manifest); - // Pega os arquivos do manifest do Vite - const indexEntry = manifest["index.html"]; - const scriptFile = indexEntry.file; - const cssFiles = indexEntry.css || []; - const scriptUri = webview.asWebviewUri( - vscode.Uri.joinPath(buildPath, scriptFile), - ); - const cssUris = cssFiles.map((cssFile) => - webview.asWebviewUri(vscode.Uri.joinPath(buildPath, cssFile)), - ); - console.log("[StackCode] Script URI:", scriptUri.toString()); - console.log( - "[StackCode] CSS URIs:", - cssUris.map((uri) => uri.toString()), - ); - return ` + _getHtmlForWebview(webview) { + const nonce = getNonce(); + const buildPath = vscode.Uri.joinPath(this._extensionUri, "dist", "webview-ui"); + const manifestPath = path.join(buildPath.fsPath, ".vite", "manifest.json"); + try { + const manifestContent = fs.readFileSync(manifestPath, "utf-8"); + const manifest = JSON.parse(manifestContent); + const indexEntry = manifest["index.html"]; + const scriptFile = indexEntry.file; + const cssFiles = indexEntry.css || []; + const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(buildPath, scriptFile)); + const cssUris = cssFiles.map((cssFile) => webview.asWebviewUri(vscode.Uri.joinPath(buildPath, cssFile))); + return ` @@ -282,34 +246,11 @@ class DashboardProvider {
- `; - } catch (error) { - console.error("[StackCode] Error reading manifest:", error); - // Fallback melhorado para desenvolvimento - return ` + } + catch (error) { + return ` @@ -351,38 +292,42 @@ class DashboardProvider {
Development Mode

🏗️ StackCode Dashboard

- Build Required: O webview-ui precisa ser compilado primeiro. + Build Required: The webview-ui needs to be compiled first.

- Execute: npm run build:ui + Run: npm run build:ui

- Erro: ${error instanceof Error ? error.message : "Manifest não encontrado"} + Error: ${error instanceof Error ? error.message : "Manifest not found"}
-

Status da extensão: ✅ Ativa

-

Workspace: ${vscode.workspace.workspaceFolders?.[0]?.name || "Nenhum"}

+

Extension Status: ✅ Active

+

Workspace: ${vscode.workspace.workspaceFolders?.[0]?.name || "None"}

`; + } } - } - // CORREÇÃO: Adicionando o método dispose para conformidade. - dispose() { - while (this._disposables.length) { - const x = this._disposables.pop(); - if (x) { - x.dispose(); - } + dispose() { + if (this._progressManager) { + this._progressManager.unregisterWebviewProvider(this); + } + while (this._disposables.length) { + const x = this._disposables.pop(); + if (x) { + x.dispose(); + } + } } - } } exports.DashboardProvider = DashboardProvider; DashboardProvider.viewType = "stackcode.dashboard"; +/** + * Generates a cryptographically random nonce for CSP. + */ function getNonce() { - let text = ""; - const possible = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - for (let i = 0; i < 32; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; + let text = ""; + const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + for (let i = 0; i < 32; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; } -//# sourceMappingURL=DashboardProvider.js.map +//# sourceMappingURL=DashboardProvider.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/providers/DashboardProvider.js.map b/packages/vscode-extension/out/providers/DashboardProvider.js.map index b4df8cf6..ac4ad3fa 100644 --- a/packages/vscode-extension/out/providers/DashboardProvider.js.map +++ b/packages/vscode-extension/out/providers/DashboardProvider.js.map @@ -1 +1 @@ -{"version":3,"file":"DashboardProvider.js","sourceRoot":"","sources":["../../src/providers/DashboardProvider.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"DashboardProvider.js","sourceRoot":"","sources":["../../src/providers/DashboardProvider.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/vscode-extension/out/providers/ProjectViewProvider.js b/packages/vscode-extension/out/providers/ProjectViewProvider.js index 73617dff..79d6e9e7 100644 --- a/packages/vscode-extension/out/providers/ProjectViewProvider.js +++ b/packages/vscode-extension/out/providers/ProjectViewProvider.js @@ -1,331 +1,297 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if ( - !desc || - ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) - ) { - desc = { - enumerable: true, - get: function () { - return m[k]; - }, - }; - } - Object.defineProperty(o, k2, desc); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) - __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; - }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProjectViewProvider = void 0; const vscode = __importStar(require("vscode")); class ProjectViewProvider { - constructor(workspaceState) { - this.workspaceState = workspaceState; - this._onDidChangeTreeData = new vscode.EventEmitter(); - this.onDidChangeTreeData = this._onDidChangeTreeData.event; - } - refresh() { - this._onDidChangeTreeData.fire(); - } - getTreeItem(element) { - const item = new vscode.TreeItem(element.label, element.collapsibleState); - // Enhanced icons and styling - const iconMap = { - "StackCode Project": "rocket", - "Quick Actions": "zap", - "Initialize Project": "folder-opened", - "Generate README": "book", - "Generate .gitignore": "git-branch", - "Validate Project": "check", - "Git Workflow": "git-commit", - "Start Feature": "git-branch", - "Create Hotfix": "flame", - "Make Release": "package", - "Commit Changes": "git-commit", - "Project Tools": "tools", - Configuration: "gear", - "Show Dashboard": "dashboard", - "View Project Stats": "graph", - "Help & Documentation": "question", - }; - // Set icons with theme support - item.iconPath = new vscode.ThemeIcon( - iconMap[element.label] || "circle-filled", - ); - // Add commands for interactive items - if (element.command) { - // Handle both string and Command object types - if (typeof element.command === "string") { - item.command = { - command: element.command, - title: element.label, - }; - } else { - item.command = element.command; - } - // Add hover descriptions - const tooltips = { - "Initialize Project": "Create a new project with StackCode scaffolding", - "Generate README": "Generate a comprehensive README.md file", - "Generate .gitignore": "Generate .gitignore based on project type", - "Validate Project": "Check project structure and best practices", - "Start Feature": "Begin a new feature using GitFlow", - "Create Hotfix": "Create a hotfix branch for urgent fixes", - "Make Release": "Create a new release with automated versioning", - "Commit Changes": "Make a conventional commit with validation", - Configuration: "Configure StackCode settings", - "Show Dashboard": "Open the interactive StackCode dashboard", - "View Project Stats": "See detailed project statistics", - "Help & Documentation": "Access StackCode documentation", - }; - item.tooltip = tooltips[element.label] || element.label; + constructor(workspaceState) { + this.workspaceState = workspaceState; + this._onDidChangeTreeData = new vscode.EventEmitter(); + this.onDidChangeTreeData = this._onDidChangeTreeData.event; } - // Style for different types - if (element.children && element.children.length > 0) { - item.contextValue = "stackcode-category"; - } else if (element.command) { - item.contextValue = "stackcode-action"; + refresh() { + this._onDidChangeTreeData.fire(); } - return item; - } - getChildren(element) { - if (!element) { - // Root level - show main categories with enhanced structure - return Promise.resolve([ - { - label: "StackCode Project", - icon: "rocket", - collapsibleState: vscode.TreeItemCollapsibleState.Expanded, - children: [ - { - label: "Quick Actions", - icon: "zap", - collapsibleState: vscode.TreeItemCollapsibleState.Expanded, - children: [ - { - label: "Initialize Project", - icon: "folder-opened", - collapsibleState: vscode.TreeItemCollapsibleState.None, - command: { - command: "stackcode.init", - title: "Initialize Project", - }, - }, - { - label: "Generate README", - icon: "book", - collapsibleState: vscode.TreeItemCollapsibleState.None, - command: { - command: "stackcode.generate.readme", - title: "Generate README", - }, - }, - { - label: "Generate .gitignore", - icon: "git-branch", - collapsibleState: vscode.TreeItemCollapsibleState.None, - command: { - command: "stackcode.generate.gitignore", - title: "Generate .gitignore", - }, - }, - { - label: "Validate Project", - icon: "check", - collapsibleState: vscode.TreeItemCollapsibleState.None, - command: { - command: "stackcode.validate", - title: "Validate Project", - }, - }, - ], - }, - { - label: "Git Workflow", - icon: "git-commit", - collapsibleState: vscode.TreeItemCollapsibleState.Expanded, - children: [ - { - label: "Start Feature", - icon: "git-branch", - collapsibleState: vscode.TreeItemCollapsibleState.None, - command: { - command: "stackcode.git.feature.start", - title: "Start Feature", - }, - }, - { - label: "Create Hotfix", - icon: "flame", - collapsibleState: vscode.TreeItemCollapsibleState.None, - command: { - command: "stackcode.git.hotfix.start", - title: "Create Hotfix", - }, - }, - { - label: "Make Release", - icon: "package", - collapsibleState: vscode.TreeItemCollapsibleState.None, - command: { - command: "stackcode.release", - title: "Make Release", - }, - }, - { - label: "Commit Changes", - icon: "git-commit", - collapsibleState: vscode.TreeItemCollapsibleState.None, - command: { - command: "stackcode.commit", - title: "Commit Changes", - }, - }, - ], - }, - { - label: "Project Tools", - icon: "tools", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - children: [ - { - label: "Configuration", - icon: "gear", - collapsibleState: vscode.TreeItemCollapsibleState.None, - command: { - command: "stackcode.config", - title: "Configuration", - }, - }, - { - label: "Show Dashboard", - icon: "dashboard", - collapsibleState: vscode.TreeItemCollapsibleState.None, - command: { - command: "stackcode.dashboard.show", - title: "Show Dashboard", - }, - }, + getTreeItem(element) { + const item = new vscode.TreeItem(element.label, element.collapsibleState); + const iconMap = { + "StackCode Project": "rocket", + "Quick Actions": "zap", + "Initialize Project": "folder-opened", + "Generate README": "book", + "Generate .gitignore": "git-branch", + "Validate Project": "check", + "Git Workflow": "git-commit", + "Start Feature": "git-branch", + "Create Hotfix": "flame", + "Make Release": "package", + "Commit Changes": "git-commit", + "Project Tools": "tools", + Configuration: "gear", + "Show Dashboard": "dashboard", + "View Project Stats": "graph", + "Help & Documentation": "question", + }; + item.iconPath = new vscode.ThemeIcon(iconMap[element.label] || "circle-filled"); + if (element.command) { + if (typeof element.command === "string") { + item.command = { + command: element.command, + title: element.label, + }; + } + else { + item.command = element.command; + } + const tooltips = { + "Initialize Project": "Create a new project with StackCode scaffolding", + "Generate README": "Generate a comprehensive README.md file", + "Generate .gitignore": "Generate .gitignore based on project type", + "Validate Project": "Check project structure and best practices", + "Start Feature": "Begin a new feature using GitFlow", + "Create Hotfix": "Create a hotfix branch for urgent fixes", + "Make Release": "Create a new release with automated versioning", + "Commit Changes": "Make a conventional commit with validation", + Configuration: "Configure StackCode settings", + "Show Dashboard": "Open the interactive StackCode dashboard", + "View Project Stats": "See detailed project statistics", + "Help & Documentation": "Access StackCode documentation", + }; + item.tooltip = tooltips[element.label] || element.label; + } + if (element.children && element.children.length > 0) { + item.contextValue = "stackcode-category"; + } + else if (element.command) { + item.contextValue = "stackcode-action"; + } + return item; + } + getChildren(element) { + if (!element) { + return Promise.resolve([ { - label: "View Project Stats", - icon: "graph", - collapsibleState: vscode.TreeItemCollapsibleState.None, - command: { - command: "stackcode.stats.show", - title: "View Project Stats", - }, + label: "StackCode Project", + icon: "rocket", + collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + children: [ + { + label: "Quick Actions", + icon: "zap", + collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + children: [ + { + label: "Initialize Project", + icon: "folder-opened", + collapsibleState: vscode.TreeItemCollapsibleState.None, + command: { + command: "stackcode.init", + title: "Initialize Project", + }, + }, + { + label: "Generate README", + icon: "book", + collapsibleState: vscode.TreeItemCollapsibleState.None, + command: { + command: "stackcode.generate.readme", + title: "Generate README", + }, + }, + { + label: "Generate .gitignore", + icon: "git-branch", + collapsibleState: vscode.TreeItemCollapsibleState.None, + command: { + command: "stackcode.generate.gitignore", + title: "Generate .gitignore", + }, + }, + { + label: "Validate Project", + icon: "check", + collapsibleState: vscode.TreeItemCollapsibleState.None, + command: { + command: "stackcode.validate", + title: "Validate Project", + }, + }, + ], + }, + { + label: "Git Workflow", + icon: "git-commit", + collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + children: [ + { + label: "Start Feature", + icon: "git-branch", + collapsibleState: vscode.TreeItemCollapsibleState.None, + command: { + command: "stackcode.git.feature.start", + title: "Start Feature", + }, + }, + { + label: "Create Hotfix", + icon: "flame", + collapsibleState: vscode.TreeItemCollapsibleState.None, + command: { + command: "stackcode.git.hotfix.start", + title: "Create Hotfix", + }, + }, + { + label: "Make Release", + icon: "package", + collapsibleState: vscode.TreeItemCollapsibleState.None, + command: { + command: "stackcode.release", + title: "Make Release", + }, + }, + { + label: "Commit Changes", + icon: "git-commit", + collapsibleState: vscode.TreeItemCollapsibleState.None, + command: { + command: "stackcode.commit", + title: "Commit Changes", + }, + }, + ], + }, + { + label: "Project Tools", + icon: "tools", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + children: [ + { + label: "Configuration", + icon: "gear", + collapsibleState: vscode.TreeItemCollapsibleState.None, + command: { + command: "stackcode.config", + title: "Configuration", + }, + }, + { + label: "Show Dashboard", + icon: "dashboard", + collapsibleState: vscode.TreeItemCollapsibleState.None, + command: { + command: "stackcode.dashboard.show", + title: "Show Dashboard", + }, + }, + { + label: "View Project Stats", + icon: "graph", + collapsibleState: vscode.TreeItemCollapsibleState.None, + command: { + command: "stackcode.stats.show", + title: "View Project Stats", + }, + }, + { + label: "Help & Documentation", + icon: "question", + collapsibleState: vscode.TreeItemCollapsibleState.None, + command: { + command: "stackcode.help", + title: "Help & Documentation", + }, + }, + ], + }, + ], }, + ]); + } + else { + return Promise.resolve(element.children || []); + } + } + async getProjectInfo() { + const items = []; + const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; + if (!workspaceFolder) { + return [ { - label: "Help & Documentation", - icon: "question", - collapsibleState: vscode.TreeItemCollapsibleState.None, - command: { - command: "stackcode.help", - title: "Help & Documentation", - }, + label: "No workspace", + description: "Open a folder to see project info", + icon: "folder-opened", }, - ], - }, - ], - }, - ]); - } else { - return Promise.resolve(element.children || []); - } - } - async getProjectInfo() { - const items = []; - const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; - if (!workspaceFolder) { - return [ - { - label: "No workspace", - description: "Open a folder to see project info", - icon: "folder-opened", - }, - ]; - } - // Project name - items.push({ - label: workspaceFolder.name, - description: "Project root", - icon: "folder", - }); - // Git status - try { - const gitExtension = vscode.extensions.getExtension("vscode.git"); - if (gitExtension && gitExtension.isActive) { - const git = gitExtension.exports; - const api = git.getAPI(1); - const repo = api.repositories[0]; - if (repo && repo.state.HEAD) { - items.push({ - label: `Branch: ${repo.state.HEAD.name}`, - description: `${repo.state.workingTreeChanges.length} changes`, - icon: "git-branch", - }); + ]; + } + items.push({ + label: workspaceFolder.name, + description: "Project root", + icon: "folder", + }); + try { + const gitExtension = vscode.extensions.getExtension("vscode.git"); + if (gitExtension && gitExtension.isActive) { + const git = gitExtension.exports; + const api = git.getAPI(1); + const repo = api.repositories[0]; + if (repo && repo.state.HEAD) { + items.push({ + label: `Branch: ${repo.state.HEAD.name}`, + description: `${repo.state.workingTreeChanges.length} changes`, + icon: "git-branch", + }); + } + } } - } - } catch { - // Git not available + catch (error) { + console.warn("Git info unavailable:", error); + } + items.push({ + label: "Initialize Project", + description: "Set up StackCode project", + icon: "play", + command: "stackcode.init", + }, { + label: "Generate Files", + description: "Create README, .gitignore, etc.", + icon: "file-add", + command: "stackcode.generate.readme", + }, { + label: "Start Branch", + description: "Create new feature branch", + icon: "git-branch", + command: "stackcode.git.start", + }, { + label: "Create Commit", + description: "Make conventional commit", + icon: "git-commit", + command: "stackcode.commit", + }); + return items; } - // Quick actions - items.push( - { - label: "Initialize Project", - description: "Set up StackCode project", - icon: "play", - command: "stackcode.init", - }, - { - label: "Generate Files", - description: "Create README, .gitignore, etc.", - icon: "file-add", - command: "stackcode.generate.readme", - }, - { - label: "Start Branch", - description: "Create new feature branch", - icon: "git-branch", - command: "stackcode.git.start", - }, - { - label: "Create Commit", - description: "Make conventional commit", - icon: "git-commit", - command: "stackcode.commit", - }, - ); - return items; - } } exports.ProjectViewProvider = ProjectViewProvider; -//# sourceMappingURL=ProjectViewProvider.js.map +//# sourceMappingURL=ProjectViewProvider.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/providers/ProjectViewProvider.js.map b/packages/vscode-extension/out/providers/ProjectViewProvider.js.map index 9f1ce9e1..f51f6c8d 100644 --- a/packages/vscode-extension/out/providers/ProjectViewProvider.js.map +++ b/packages/vscode-extension/out/providers/ProjectViewProvider.js.map @@ -1 +1 @@ -{"version":3,"file":"ProjectViewProvider.js","sourceRoot":"","sources":["../../src/providers/ProjectViewProvider.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"ProjectViewProvider.js","sourceRoot":"","sources":["../../src/providers/ProjectViewProvider.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/vscode-extension/out/services/GitHubAuthService.js b/packages/vscode-extension/out/services/GitHubAuthService.js index 457f835a..c5f41844 100644 --- a/packages/vscode-extension/out/services/GitHubAuthService.js +++ b/packages/vscode-extension/out/services/GitHubAuthService.js @@ -1,209 +1,124 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if ( - !desc || - ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) - ) { - desc = { - enumerable: true, - get: function () { - return m[k]; - }, - }; - } - Object.defineProperty(o, k2, desc); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) - __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; - }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.GitHubAuthService = void 0; const vscode = __importStar(require("vscode")); -const rest_1 = require("@octokit/rest"); -/** - * GitHubAuthService - Gerencia autenticação OAuth2 com GitHub - * - * Responsabilidades: - * 1. Implementar fluxo OAuth2 usando VS Code native authentication - * 2. Armazenar token seguramente usando SecretStorage - * 3. Fornecer cliente Octokit autenticado - * 4. Gerenciar login/logout - */ +const github_auth_1 = require("@stackcode/github-auth"); class GitHubAuthService { - constructor(context) { - this._octokit = null; - this._session = null; - this._context = context; - } - /** - * Verifica se o usuário está autenticado - */ - get isAuthenticated() { - return this._session !== null && this._octokit !== null; - } - /** - * Retorna informações do usuário autenticado - */ - get userInfo() { - if (!this._session) return null; - return { - username: this._session.account.label, - email: this._session.account.id, - }; - } - /** - * Obtém cliente Octokit autenticado - * Throws se não estiver autenticado - */ - getAuthenticatedClient() { - if (!this._octokit) { - throw new Error("User not authenticated. Please login first."); - } - return this._octokit; - } - /** - * Inicia processo de login OAuth2 - */ - async login() { - try { - // Usar VS Code native authentication - this._session = await vscode.authentication.getSession( - GitHubAuthService.GITHUB_PROVIDER_ID, - GitHubAuthService.SCOPES, - { createIfNone: true }, - ); - if (this._session) { - // Criar cliente Octokit com token - this._octokit = new rest_1.Octokit({ - auth: this._session.accessToken, + constructor(context) { + this.context = context; + this.cachedContext = null; + const sharedStorage = (0, github_auth_1.createFileTokenStorage)(); + this.auth = (0, github_auth_1.createGitHubAuth)({ + provider: (0, github_auth_1.createVSCodeAuthProvider)({ + vscode, + context, + scopes: GitHubAuthService.SCOPES, + sharedStorage, + shareTokens: true, + }), }); - // Armazenar token seguramente - await this._context.secrets.store( - GitHubAuthService.TOKEN_KEY, - this._session.accessToken, - ); - // Verificar se o token funciona - await this._validateToken(); - vscode.window.showInformationMessage( - `✅ Successfully logged in to GitHub as ${this._session.account.label}`, - ); - console.log("[StackCode] GitHub authentication successful"); - } - } catch (error) { - console.error("[StackCode] GitHub authentication failed:", error); - vscode.window.showErrorMessage( - `Failed to authenticate with GitHub: ${error instanceof Error ? error.message : "Unknown error"}`, - ); - throw error; } - } - /** - * Remove autenticação e limpa dados - */ - async logout() { - try { - // Remover token do storage seguro - await this._context.secrets.delete(GitHubAuthService.TOKEN_KEY); - // Limpar sessão do VS Code - if (this._session) { - // Note: VS Code handles session cleanup automatically - this._session = null; - } - // Limpar cliente - this._octokit = null; - vscode.window.showInformationMessage( - "✅ Successfully logged out from GitHub", - ); - console.log("[StackCode] GitHub logout successful"); - } catch (error) { - console.error("[StackCode] GitHub logout failed:", error); - vscode.window.showErrorMessage( - `Failed to logout from GitHub: ${error instanceof Error ? error.message : "Unknown error"}`, - ); + get isAuthenticated() { + return this.auth.isAuthenticated(); } - } - /** - * Tenta restaurar sessão existente na inicialização - */ - async initializeFromStorage() { - try { - // Tentar recuperar sessão existente (sem criar nova) - const session = await vscode.authentication.getSession( - GitHubAuthService.GITHUB_PROVIDER_ID, - GitHubAuthService.SCOPES, - { createIfNone: false }, - ); - if (session) { - this._session = session; - this._octokit = new rest_1.Octokit({ - auth: session.accessToken, - }); - // Validar token - await this._validateToken(); - console.log("[StackCode] GitHub session restored successfully"); - } - } catch (error) { - console.warn("[StackCode] Failed to restore GitHub session:", error); - // Se não conseguir restaurar, limpar dados corrompidos - await this._context.secrets.delete(GitHubAuthService.TOKEN_KEY); - this._session = null; - this._octokit = null; + get userInfo() { + const account = this.cachedContext?.session.account; + if (!account) { + return null; + } + return { + username: account.username ?? account.displayName, + email: account.email ?? undefined, + }; } - } - /** - * Valida se o token atual ainda é válido - */ - async _validateToken() { - if (!this._octokit) { - throw new Error("No Octokit client available"); + async getAuthenticatedClient() { + const context = await this.ensureSession(); + return context.client; } - try { - // Fazer uma chamada simples para validar o token - await this._octokit.users.getAuthenticated(); - } catch (error) { - console.error("[StackCode] Token validation failed:", error); - // Token inválido, limpar tudo - await this.logout(); - throw new Error("GitHub token is invalid or expired"); + async login() { + try { + const context = await this.auth.login({ interactive: true }); + this.cachedContext = context; + const label = context.session.account?.username ?? + context.session.account?.displayName ?? + context.session.account?.id ?? + "GitHub"; + vscode.window.showInformationMessage(`✅ Successfully logged in to GitHub as ${label}`); + console.log("[StackCode] GitHub authentication successful"); + } + catch (error) { + console.error("[StackCode] GitHub authentication failed:", error); + vscode.window.showErrorMessage(`Failed to authenticate with GitHub: ${error instanceof Error ? error.message : "Unknown error"}`); + throw error; + } + } + async logout() { + try { + await this.auth.logout(); + this.cachedContext = null; + vscode.window.showInformationMessage("✅ Successfully logged out from GitHub"); + console.log("[StackCode] GitHub logout successful"); + } + catch (error) { + console.error("[StackCode] GitHub logout failed:", error); + vscode.window.showErrorMessage(`Failed to logout from GitHub: ${error instanceof Error ? error.message : "Unknown error"}`); + } + } + async initializeFromStorage() { + try { + const context = await this.auth.getSession({ forceRefresh: true }); + if (context) { + this.cachedContext = context; + console.log("[StackCode] GitHub session restored successfully"); + } + else { + this.cachedContext = null; + } + } + catch (error) { + console.warn("[StackCode] Failed to restore GitHub session:", error); + this.cachedContext = null; + await this.auth.removeToken(); + } + } + dispose() { + this.cachedContext = null; + } + async ensureSession() { + if (this.cachedContext) { + return this.cachedContext; + } + const context = await this.auth.getSession({ forceRefresh: false }); + if (!context) { + throw new Error("User not authenticated. Please login first."); + } + this.cachedContext = context; + return context; } - } - /** - * Limpa recursos ao desativar extensão - */ - dispose() { - this._session = null; - this._octokit = null; - } } exports.GitHubAuthService = GitHubAuthService; -GitHubAuthService.GITHUB_PROVIDER_ID = "github"; -GitHubAuthService.TOKEN_KEY = "stackcode.github.token"; GitHubAuthService.SCOPES = ["repo", "user:email"]; -//# sourceMappingURL=GitHubAuthService.js.map +//# sourceMappingURL=GitHubAuthService.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/services/GitHubAuthService.js.map b/packages/vscode-extension/out/services/GitHubAuthService.js.map index 005dd6c1..478f645e 100644 --- a/packages/vscode-extension/out/services/GitHubAuthService.js.map +++ b/packages/vscode-extension/out/services/GitHubAuthService.js.map @@ -1 +1 @@ -{"version":3,"file":"GitHubAuthService.js","sourceRoot":"","sources":["../../src/services/GitHubAuthService.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"GitHubAuthService.js","sourceRoot":"","sources":["../../src/services/GitHubAuthService.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AAEjC,wDAKgC;AAEhC,MAAa,iBAAiB;IAM5B,YAA6B,OAAgC;QAAhC,YAAO,GAAP,OAAO,CAAyB;QAFrD,kBAAa,GAA6B,IAAI,CAAC;QAGrD,MAAM,aAAa,GAAG,IAAA,oCAAsB,GAAE,CAAC;QAC/C,IAAI,CAAC,IAAI,GAAG,IAAA,8BAAgB,EAAC;YAC3B,QAAQ,EAAE,IAAA,sCAAwB,EAAC;gBACjC,MAAM;gBACN,OAAO;gBACP,MAAM,EAAE,iBAAiB,CAAC,MAAM;gBAChC,aAAa;gBACb,WAAW,EAAE,IAAI;aAClB,CAAC;SACH,CAAC,CAAC;IACL,CAAC;IAED,IAAW,eAAe;QACxB,OAAO,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;IACrC,CAAC;IAED,IAAW,QAAQ;QACjB,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC;QACpD,IAAI,CAAC,OAAO,EAAE;YACZ,OAAO,IAAI,CAAC;SACb;QAED,OAAO;YACL,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,WAAW;YACjD,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,SAAS;SAClC,CAAC;IACJ,CAAC;IAEM,KAAK,CAAC,sBAAsB;QACjC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC3C,OAAO,OAAO,CAAC,MAAM,CAAC;IACxB,CAAC;IAEM,KAAK,CAAC,KAAK;QAChB,IAAI;YACF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;YAC7D,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC;YAE7B,MAAM,KAAK,GACT,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ;gBACjC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,WAAW;gBACpC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE;gBAC3B,QAAQ,CAAC;YAEX,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAClC,yCAAyC,KAAK,EAAE,CACjD,CAAC;YACF,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;SAC7D;QAAC,OAAO,KAAK,EAAE;YACd,OAAO,CAAC,KAAK,CAAC,2CAA2C,EAAE,KAAK,CAAC,CAAC;YAClE,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAC5B,uCACE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAC3C,EAAE,CACH,CAAC;YACF,MAAM,KAAK,CAAC;SACb;IACH,CAAC;IAEM,KAAK,CAAC,MAAM;QACjB,IAAI;YACF,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACzB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;YAE1B,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAClC,uCAAuC,CACxC,CAAC;YACF,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;SACrD;QAAC,OAAO,KAAK,EAAE;YACd,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE,KAAK,CAAC,CAAC;YAC1D,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAC5B,iCACE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAC3C,EAAE,CACH,CAAC;SACH;IACH,CAAC;IAEM,KAAK,CAAC,qBAAqB;QAChC,IAAI;YACF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;YACnE,IAAI,OAAO,EAAE;gBACX,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC;gBAC7B,OAAO,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC;aACjE;iBAAM;gBACL,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;aAC3B;SACF;QAAC,OAAO,KAAK,EAAE;YACd,OAAO,CAAC,IAAI,CAAC,+CAA+C,EAAE,KAAK,CAAC,CAAC;YACrE,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;YAC1B,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;SAC/B;IACH,CAAC;IAEM,OAAO;QACZ,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;IAC5B,CAAC;IAEO,KAAK,CAAC,aAAa;QACzB,IAAI,IAAI,CAAC,aAAa,EAAE;YACtB,OAAO,IAAI,CAAC,aAAa,CAAC;SAC3B;QAED,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC;QACpE,IAAI,CAAC,OAAO,EAAE;YACZ,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;SAChE;QAED,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC;QAC7B,OAAO,OAAO,CAAC;IACjB,CAAC;;AArHH,8CAsHC;AArHyB,wBAAM,GAAG,CAAC,MAAM,EAAE,YAAY,CAAU,CAAC"} \ No newline at end of file diff --git a/packages/vscode-extension/out/services/GitHubIssuesService.js b/packages/vscode-extension/out/services/GitHubIssuesService.js index 1466cdfe..c22398eb 100644 --- a/packages/vscode-extension/out/services/GitHubIssuesService.js +++ b/packages/vscode-extension/out/services/GitHubIssuesService.js @@ -1,166 +1,152 @@ "use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.GitHubIssuesService = void 0; const core_1 = require("@stackcode/core"); /** - * GitHubIssuesService - Orquestra a busca de issues do GitHub + * GitHubIssuesService - Thin wrapper around core issues workflow + * + * This service now delegates all business logic to @stackcode/core, + * providing only VS Code-specific integration (auth + git detection). * - * Responsabilidades: - * 1. Integrar autenticação + detecção de repositório + busca de issues - * 2. Gerenciar cache local de issues - * 3. Fornecer interface simplificada para a UI + * @deprecated Consider using runIssuesWorkflow directly with authenticated client */ class GitHubIssuesService { - constructor(authService, gitMonitor) { - this._issuesCache = new Map(); - this.CACHE_TTL = 5 * 60 * 1000; // 5 minutos - this._authService = authService; - this._gitMonitor = gitMonitor; - } - /** - * Busca issues do repositório atual - */ - async fetchCurrentRepositoryIssues(options) { - try { - console.log( - "🔍 [GitHubIssuesService] Starting fetchCurrentRepositoryIssues...", - ); - // Verificar autenticação - if (!this._authService.isAuthenticated) { - console.warn("❌ [GitHubIssuesService] User not authenticated"); - throw new Error("User not authenticated with GitHub"); - } - console.log("✅ [GitHubIssuesService] User is authenticated"); - // Detectar repositório atual - console.log("🔍 [GitHubIssuesService] Detecting current repository..."); - const repository = await this._gitMonitor.getCurrentGitHubRepository(); - if (!repository) { - console.warn("❌ [GitHubIssuesService] No GitHub repository detected"); - throw new Error("No GitHub repository detected in current workspace"); - } - console.log( - `✅ [GitHubIssuesService] Repository detected: ${repository.owner}/${repository.repo}`, - ); - console.log("🚀 [GitHubIssuesService] Fetching issues..."); - const issues = await this.fetchRepositoryIssues(repository, options); - console.log(`✅ [GitHubIssuesService] Found ${issues.length} issues`); - return issues; - } catch (error) { - console.error( - "❌ [GitHubIssuesService] Failed to fetch current repository issues:", - error, - ); - throw error; + constructor(authService, gitMonitor) { + this._authService = authService; + this._gitMonitor = gitMonitor; + } + /** + * Fetches issues from current repository using centralized core workflow + */ + async fetchCurrentRepositoryIssues(options) { + try { + console.log("🔍 [GitHubIssuesService] Starting fetchCurrentRepositoryIssues..."); + if (!this._authService.isAuthenticated) { + console.warn("❌ [GitHubIssuesService] User not authenticated"); + throw new Error("User not authenticated with GitHub"); + } + console.log("✅ [GitHubIssuesService] User is authenticated"); + console.log("🔍 [GitHubIssuesService] Detecting current repository..."); + const repository = await this._gitMonitor.getCurrentGitHubRepository(); + if (!repository) { + console.warn("❌ [GitHubIssuesService] No GitHub repository detected"); + throw new Error("No GitHub repository detected in current workspace"); + } + console.log(`✅ [GitHubIssuesService] Repository detected: ${repository.owner}/${repository.repo}`); + console.log("🚀 [GitHubIssuesService] Fetching issues..."); + const issues = await this.fetchRepositoryIssues(repository, options); + console.log(`✅ [GitHubIssuesService] Found ${issues.length} issues`); + return issues; + } + catch (error) { + console.error("❌ [GitHubIssuesService] Failed to fetch current repository issues:", error); + throw error; + } + } + /** + * Fetches issues from a specific repository using centralized core workflow + */ + async fetchRepositoryIssues(repository, options) { + try { + const client = await this._authService.getAuthenticatedClient(); + const result = await (0, core_1.runIssuesWorkflow)({ + client, + repository: { + owner: repository.owner, + repo: repository.repo, + fullName: repository.fullName, + }, + fetchOptions: options, + enableCache: true, + }); + if (result.status === "error") { + throw new Error(result.error || "Failed to fetch repository issues"); + } + return result.issues; + } + catch (error) { + console.error("[GitHubIssuesService] Failed to fetch repository issues:", error); + throw error; + } } - } - /** - * Busca issues de um repositório específico - */ - async fetchRepositoryIssues(repository, options) { - try { - const cacheKey = this.getCacheKey(repository, options); - // Verificar cache - const cached = this._issuesCache.get(cacheKey); - if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) { - console.log("[GitHubIssuesService] Returning cached issues"); - return cached.issues; - } - // Buscar issues - const octokit = this._authService.getAuthenticatedClient(); - const fetchOptions = { - owner: repository.owner, - repo: repository.repo, - state: "open", - sort: "updated", - direction: "desc", - per_page: 30, - ...options, - }; - console.log( - `[GitHubIssuesService] Fetching issues for ${repository.fullName}`, - ); - const issues = await (0, core_1.fetchRepositoryIssues)( - octokit, - fetchOptions, - ); - // Atualizar cache - this._issuesCache.set(cacheKey, { - issues, - timestamp: Date.now(), - }); - return issues; - } catch (error) { - console.error( - "[GitHubIssuesService] Failed to fetch repository issues:", - error, - ); - throw error; + /** + * Fetches issues assigned to current user + */ + async fetchMyIssues(repository) { + try { + const userInfo = this._authService.userInfo; + if (!userInfo?.username) { + throw new Error("User information not available"); + } + const targetRepo = repository || (await this._gitMonitor.getCurrentGitHubRepository()); + if (!targetRepo) { + throw new Error("No GitHub repository available"); + } + return await this.fetchRepositoryIssues(targetRepo, { + assignee: userInfo.username, + state: "open", + }); + } + catch (error) { + console.error("[GitHubIssuesService] Failed to fetch my issues:", error); + throw error; + } } - } - /** - * Busca issues atribuídas ao usuário atual - */ - async fetchMyIssues(repository) { - try { - const userInfo = this._authService.userInfo; - if (!userInfo?.username) { - throw new Error("User information not available"); - } - const targetRepo = - repository || (await this._gitMonitor.getCurrentGitHubRepository()); - if (!targetRepo) { - throw new Error("No GitHub repository available"); - } - return await this.fetchRepositoryIssues(targetRepo, { - assignee: userInfo.username, - state: "open", - }); - } catch (error) { - console.error("[GitHubIssuesService] Failed to fetch my issues:", error); - throw error; + /** + * Limpa cache de issues (delega para o core) + */ + clearCache() { + Promise.resolve().then(() => __importStar(require("@stackcode/core"))).then(({ clearIssuesCache }) => { + clearIssuesCache(); + console.log("[GitHubIssuesService] Cache cleared via core"); + }); } - } - /** - * Limpa cache de issues - */ - clearCache() { - this._issuesCache.clear(); - console.log("[GitHubIssuesService] Cache cleared"); - } - /** - * Limpa cache expirado - */ - clearExpiredCache() { - const now = Date.now(); - for (const [key, cache] of this._issuesCache.entries()) { - if (now - cache.timestamp >= this.CACHE_TTL) { - this._issuesCache.delete(key); - } + /** + * Limpa cache expirado (delega para o core) + */ + clearExpiredCache() { + Promise.resolve().then(() => __importStar(require("@stackcode/core"))).then(({ clearExpiredIssuesCache }) => { + clearExpiredIssuesCache(); + }); } - } - /** - * Gera chave única para cache baseada no repositório e opções - */ - getCacheKey(repository, options) { - const optionsStr = JSON.stringify(options || {}); - return `${repository.fullName}:${optionsStr}`; - } - /** - * Força atualização de issues (ignora cache) - */ - async refreshIssues(repository) { - const targetRepo = - repository || (await this._gitMonitor.getCurrentGitHubRepository()); - if (!targetRepo) { - throw new Error("No GitHub repository available"); + /** + * Forces issue refresh (ignores cache) + */ + async refreshIssues(repository) { + const targetRepo = repository || (await this._gitMonitor.getCurrentGitHubRepository()); + if (!targetRepo) { + throw new Error("No GitHub repository available"); + } + (0, core_1.clearRepositoryCache)({ + owner: targetRepo.owner, + repo: targetRepo.repo, + fullName: targetRepo.fullName, + }); + return await this.fetchRepositoryIssues(targetRepo); } - // Limpar cache para este repositório - const cacheKeys = Array.from(this._issuesCache.keys()).filter((key) => - key.startsWith(targetRepo.fullName), - ); - cacheKeys.forEach((key) => this._issuesCache.delete(key)); - return await this.fetchRepositoryIssues(targetRepo); - } } exports.GitHubIssuesService = GitHubIssuesService; -//# sourceMappingURL=GitHubIssuesService.js.map +//# sourceMappingURL=GitHubIssuesService.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/services/GitHubIssuesService.js.map b/packages/vscode-extension/out/services/GitHubIssuesService.js.map index dfbffeb7..c411ae2e 100644 --- a/packages/vscode-extension/out/services/GitHubIssuesService.js.map +++ b/packages/vscode-extension/out/services/GitHubIssuesService.js.map @@ -1 +1 @@ -{"version":3,"file":"GitHubIssuesService.js","sourceRoot":"","sources":["../../src/services/GitHubIssuesService.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"GitHubIssuesService.js","sourceRoot":"","sources":["../../src/services/GitHubIssuesService.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,0CAKyB;AAEzB;;;;;;;GAOG;AACH,MAAa,mBAAmB;IAI9B,YAAY,WAA8B,EAAE,UAAsB;QAChE,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;QAChC,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC;IAChC,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,4BAA4B,CACvC,OAAqC;QAErC,IAAI;YACF,OAAO,CAAC,GAAG,CACT,mEAAmE,CACpE,CAAC;YAEF,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,eAAe,EAAE;gBACtC,OAAO,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;gBAC/D,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;aACvD;YACD,OAAO,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC;YAE7D,OAAO,CAAC,GAAG,CAAC,0DAA0D,CAAC,CAAC;YACxE,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,0BAA0B,EAAE,CAAC;YACvE,IAAI,CAAC,UAAU,EAAE;gBACf,OAAO,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC;gBACtE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;aACvE;YACD,OAAO,CAAC,GAAG,CACT,gDAAgD,UAAU,CAAC,KAAK,IAAI,UAAU,CAAC,IAAI,EAAE,CACtF,CAAC;YAEF,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;YAC3D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YACrE,OAAO,CAAC,GAAG,CAAC,iCAAiC,MAAM,CAAC,MAAM,SAAS,CAAC,CAAC;YAErE,OAAO,MAAM,CAAC;SACf;QAAC,OAAO,KAAK,EAAE;YACd,OAAO,CAAC,KAAK,CACX,oEAAoE,EACpE,KAAK,CACN,CAAC;YACF,MAAM,KAAK,CAAC;SACb;IACH,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,qBAAqB,CAChC,UAA4B,EAC5B,OAAqC;QAErC,IAAI;YACF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,sBAAsB,EAAE,CAAC;YAEhE,MAAM,MAAM,GAAG,MAAM,IAAA,wBAAiB,EAAC;gBACrC,MAAM;gBACN,UAAU,EAAE;oBACV,KAAK,EAAE,UAAU,CAAC,KAAK;oBACvB,IAAI,EAAE,UAAU,CAAC,IAAI;oBACrB,QAAQ,EAAE,UAAU,CAAC,QAAQ;iBAC9B;gBACD,YAAY,EAAE,OAAO;gBACrB,WAAW,EAAE,IAAI;aAClB,CAAC,CAAC;YAEH,IAAI,MAAM,CAAC,MAAM,KAAK,OAAO,EAAE;gBAC7B,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,mCAAmC,CAAC,CAAC;aACtE;YAED,OAAO,MAAM,CAAC,MAAM,CAAC;SACtB;QAAC,OAAO,KAAK,EAAE;YACd,OAAO,CAAC,KAAK,CACX,0DAA0D,EAC1D,KAAK,CACN,CAAC;YACF,MAAM,KAAK,CAAC;SACb;IACH,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,aAAa,CACxB,UAA6B;QAE7B,IAAI;YACF,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC;YAC5C,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE;gBACvB,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;aACnD;YAED,MAAM,UAAU,GACd,UAAU,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,0BAA0B,EAAE,CAAC,CAAC;YACtE,IAAI,CAAC,UAAU,EAAE;gBACf,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;aACnD;YAED,OAAO,MAAM,IAAI,CAAC,qBAAqB,CAAC,UAAU,EAAE;gBAClD,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,KAAK,EAAE,MAAM;aACd,CAAC,CAAC;SACJ;QAAC,OAAO,KAAK,EAAE;YACd,OAAO,CAAC,KAAK,CAAC,kDAAkD,EAAE,KAAK,CAAC,CAAC;YACzE,MAAM,KAAK,CAAC;SACb;IACH,CAAC;IAED;;OAEG;IACI,UAAU;QACf,kDAAO,iBAAiB,IAAE,IAAI,CAAC,CAAC,EAAE,gBAAgB,EAAE,EAAE,EAAE;YACtD,gBAAgB,EAAE,CAAC;YACnB,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACI,iBAAiB;QACtB,kDAAO,iBAAiB,IAAE,IAAI,CAAC,CAAC,EAAE,uBAAuB,EAAE,EAAE,EAAE;YAC7D,uBAAuB,EAAE,CAAC;QAC5B,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,aAAa,CACxB,UAA6B;QAE7B,MAAM,UAAU,GACd,UAAU,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,0BAA0B,EAAE,CAAC,CAAC;QACtE,IAAI,CAAC,UAAU,EAAE;YACf,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;SACnD;QAED,IAAA,2BAAoB,EAAC;YACnB,KAAK,EAAE,UAAU,CAAC,KAAK;YACvB,IAAI,EAAE,UAAU,CAAC,IAAI;YACrB,QAAQ,EAAE,UAAU,CAAC,QAAQ;SAC9B,CAAC,CAAC;QAEH,OAAO,MAAM,IAAI,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAC;IACtD,CAAC;CACF;AAxJD,kDAwJC"} \ No newline at end of file diff --git a/packages/vscode-extension/out/services/ProgressManager.js b/packages/vscode-extension/out/services/ProgressManager.js new file mode 100644 index 00000000..6ed5c81b --- /dev/null +++ b/packages/vscode-extension/out/services/ProgressManager.js @@ -0,0 +1,177 @@ +"use strict"; +/** + * @fileoverview Centralized progress manager for workflow events + * Manages progress state and broadcasts to webview + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ProgressManager = void 0; +const progress_events_1 = require("../types/progress-events"); +/** + * Manages workflow progress events and broadcasts to webviews + */ +class ProgressManager { + constructor() { + this._disposables = []; + this._currentState = { inProgress: false }; + this._webviewProviders = new Set(); + } + /** + * Register a webview provider to receive progress updates + */ + registerWebviewProvider(provider) { + this._webviewProviders.add(provider); + } + /** + * Unregister a webview provider + */ + unregisterWebviewProvider(provider) { + this._webviewProviders.delete(provider); + } + /** + * Start a new workflow progress session + */ + startWorkflow(workflowType) { + this._currentState = { + inProgress: true, + workflowType, + currentStep: undefined, + message: `Starting ${workflowType} workflow...`, + percentage: 0, + }; + this._broadcastState(); + } + /** + * Report progress for a workflow step + */ + reportProgress(workflowType, step, customMessage, data) { + const event = (0, progress_events_1.createProgressEvent)(workflowType, step, customMessage, data); + const percentage = (0, progress_events_1.calculateProgressPercentage)(workflowType, step); + const message = customMessage || (0, progress_events_1.getStepDescription)(workflowType, step); + this._currentState = { + inProgress: true, + workflowType, + currentStep: step, + message, + percentage, + }; + this._broadcastProgress(event); + this._broadcastState(); + if (this._progressReporter) { + const increment = percentage - (this._currentState.percentage || 0); + this._progressReporter.report({ + message, + increment: increment > 0 ? increment : undefined, + }); + } + } + /** + * Complete a workflow (success) + */ + completeWorkflow(workflowType, message) { + this._currentState = { + inProgress: false, + workflowType, + message: message || `${workflowType} workflow completed successfully`, + percentage: 100, + }; + this._broadcastComplete({ + workflowType, + success: true, + message, + }); + this._broadcastState(); + setTimeout(() => { + if (!this._currentState.inProgress) { + this._currentState = { inProgress: false }; + this._broadcastState(); + } + }, 3000); + } + /** + * Fail a workflow (error) + */ + failWorkflow(workflowType, error) { + this._currentState = { + inProgress: false, + workflowType, + message: `${workflowType} workflow failed`, + error, + percentage: 0, + }; + this._broadcastComplete({ + workflowType, + success: false, + error, + }); + this._broadcastState(); + } + /** + * Get current progress state + */ + getCurrentState() { + return { ...this._currentState }; + } + /** + * Create a progress hook for use with core workflows + */ + createProgressHook(workflowType) { + return (progress) => { + this.reportProgress(workflowType, progress.step, progress.message); + }; + } + /** + * Set the VS Code progress reporter (from withProgress) + */ + setVSCodeProgressReporter(reporter) { + this._progressReporter = reporter; + } + /** + * Clear the VS Code progress reporter + */ + clearVSCodeProgressReporter() { + this._progressReporter = undefined; + } + /** + * Broadcast progress event to all registered webviews + */ + _broadcastProgress(event) { + const message = { + type: "progress", + payload: event, + }; + for (const provider of this._webviewProviders) { + provider.sendMessage(message); + } + } + /** + * Broadcast state update to all registered webviews + */ + _broadcastState() { + const message = { + type: "progressState", + payload: this._currentState, + }; + for (const provider of this._webviewProviders) { + provider.sendMessage(message); + } + } + /** + * Broadcast completion message to all registered webviews + */ + _broadcastComplete(payload) { + const message = { + type: "progressComplete", + payload, + }; + for (const provider of this._webviewProviders) { + provider.sendMessage(message); + } + } + dispose() { + this._webviewProviders.clear(); + this._disposables.forEach((d) => d.dispose()); + this._disposables = []; + } +} +exports.ProgressManager = ProgressManager; +//# sourceMappingURL=ProgressManager.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/services/ProgressManager.js.map b/packages/vscode-extension/out/services/ProgressManager.js.map new file mode 100644 index 00000000..04fc0b2a --- /dev/null +++ b/packages/vscode-extension/out/services/ProgressManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ProgressManager.js","sourceRoot":"","sources":["../../src/services/ProgressManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/vscode-extension/out/test/__mocks__/vscode.js b/packages/vscode-extension/out/test/__mocks__/vscode.js deleted file mode 100644 index d666f80f..00000000 --- a/packages/vscode-extension/out/test/__mocks__/vscode.js +++ /dev/null @@ -1,66 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ConfigurationTarget = - exports.StatusBarAlignment = - exports.extensions = - exports.commands = - exports.workspace = - exports.window = - void 0; -const mockConfiguration = { - get: jest.fn((key, defaultValue) => { - const configs = { - "notifications.enabled": true, - "notifications.branchCheck": true, - "notifications.commitCheck": true, - "autoGenerate.readme": false, - "autoGenerate.gitignore": true, - "git.defaultBranchType": "feature", - "dashboard.autoOpen": false, - }; - return configs[key] !== undefined ? configs[key] : defaultValue; - }), - update: jest.fn(), -}; -exports.window = { - showInformationMessage: jest.fn(), - showWarningMessage: jest.fn(), - showErrorMessage: jest.fn(), - createStatusBarItem: jest.fn(() => ({ - show: jest.fn(), - hide: jest.fn(), - dispose: jest.fn(), - })), -}; -exports.workspace = { - getConfiguration: jest.fn(() => mockConfiguration), - workspaceFolders: [], - onDidChangeConfiguration: jest.fn(), -}; -exports.commands = { - registerCommand: jest.fn(), - executeCommand: jest.fn(), - getCommands: jest.fn(() => - Promise.resolve([ - "stackcode.init", - "stackcode.generate.readme", - "stackcode.git.start", - ]), - ), -}; -exports.extensions = { - getExtension: jest.fn(() => ({ - activate: jest.fn(() => Promise.resolve()), - isActive: true, - })), -}; -exports.StatusBarAlignment = { - Left: 1, - Right: 2, -}; -exports.ConfigurationTarget = { - Global: 1, - Workspace: 2, - WorkspaceFolder: 3, -}; -//# sourceMappingURL=vscode.js.map diff --git a/packages/vscode-extension/out/test/__mocks__/vscode.js.map b/packages/vscode-extension/out/test/__mocks__/vscode.js.map deleted file mode 100644 index 09cf3d11..00000000 --- a/packages/vscode-extension/out/test/__mocks__/vscode.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"vscode.js","sourceRoot":"","sources":["../../../src/test/__mocks__/vscode.ts"],"names":[],"mappings":";;;AAAA,MAAM,iBAAiB,GAAG;IACxB,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,GAAW,EAAE,YAAsB,EAAE,EAAE;QACnD,MAAM,OAAO,GAA+B;YAC1C,uBAAuB,EAAE,IAAI;YAC7B,2BAA2B,EAAE,IAAI;YACjC,2BAA2B,EAAE,IAAI;YACjC,qBAAqB,EAAE,KAAK;YAC5B,wBAAwB,EAAE,IAAI;YAC9B,uBAAuB,EAAE,SAAS;YAClC,oBAAoB,EAAE,KAAK;SAC5B,CAAC;QACF,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;IAClE,CAAC,CAAC;IACF,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE;CAClB,CAAC;AAEW,QAAA,MAAM,GAAG;IACpB,sBAAsB,EAAE,IAAI,CAAC,EAAE,EAAE;IACjC,kBAAkB,EAAE,IAAI,CAAC,EAAE,EAAE;IAC7B,gBAAgB,EAAE,IAAI,CAAC,EAAE,EAAE;IAC3B,mBAAmB,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;QAClC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE;QACf,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE;QACf,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE;KACnB,CAAC,CAAC;CACJ,CAAC;AAEW,QAAA,SAAS,GAAG;IACvB,gBAAgB,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC;IAClD,gBAAgB,EAAE,EAAE;IACpB,wBAAwB,EAAE,IAAI,CAAC,EAAE,EAAE;CACpC,CAAC;AAEW,QAAA,QAAQ,GAAG;IACtB,eAAe,EAAE,IAAI,CAAC,EAAE,EAAE;IAC1B,cAAc,EAAE,IAAI,CAAC,EAAE,EAAE;IACzB,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CACxB,OAAO,CAAC,OAAO,CAAC;QACd,gBAAgB;QAChB,2BAA2B;QAC3B,qBAAqB;KACtB,CAAC,CACH;CACF,CAAC;AAEW,QAAA,UAAU,GAAG;IACxB,YAAY,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;QAC3B,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QAC1C,QAAQ,EAAE,IAAI;KACf,CAAC,CAAC;CACJ,CAAC;AAEW,QAAA,kBAAkB,GAAG;IAChC,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACT,CAAC;AAEW,QAAA,mBAAmB,GAAG;IACjC,MAAM,EAAE,CAAC;IACT,SAAS,EAAE,CAAC;IACZ,eAAe,EAAE,CAAC;CACnB,CAAC"} \ No newline at end of file diff --git a/packages/vscode-extension/out/test/runSmokeTest.js b/packages/vscode-extension/out/test/runSmokeTest.js new file mode 100644 index 00000000..2814163d --- /dev/null +++ b/packages/vscode-extension/out/test/runSmokeTest.js @@ -0,0 +1,94 @@ +"use strict"; +/** + * Smoke Tests for VS Code Extension + * + * These tests launch a real VS Code instance and execute commands to verify + * that the extension works end-to-end in a realistic environment. + */ +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if ( + !desc || + ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) + ) { + desc = { + enumerable: true, + get: function () { + return m[k]; + }, + }; + } + Object.defineProperty(o, k2, desc); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); + } + : function (o, v) { + o["default"] = v; + }); +var __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) + __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __importStar(require("path")); +const fs = __importStar(require("fs/promises")); +const test_electron_1 = require("@vscode/test-electron"); +async function main() { + try { + // The folder containing the Extension Manifest package.json + const extensionDevelopmentPath = path.resolve(__dirname, "../../"); + // The path to the extension test runner script + const extensionTestsPath = path.resolve(__dirname, "./smoke/index"); + // Create a temporary workspace for testing + const testWorkspacePath = path.resolve(__dirname, "../../test-workspace"); + try { + await fs.mkdir(testWorkspacePath, { recursive: true }); + } catch (error) { + // Ignore if directory already exists + } + console.log("📦 Extension Development Path:", extensionDevelopmentPath); + console.log("🧪 Extension Tests Path:", extensionTestsPath); + console.log("📁 Test Workspace Path:", testWorkspacePath); + // Download VS Code, unzip it and run the integration test + await (0, test_electron_1.runTests)({ + extensionDevelopmentPath, + extensionTestsPath, + launchArgs: [ + testWorkspacePath, + "--disable-extensions", + "--disable-workspace-trust", // Don't prompt for workspace trust + ], + }); + // Cleanup: remove test workspace + try { + await fs.rm(testWorkspacePath, { recursive: true, force: true }); + } catch (error) { + console.warn("⚠️ Could not clean up test workspace:", error); + } + } catch (err) { + console.error("❌ Failed to run smoke tests:", err); + process.exit(1); + } +} +main(); +//# sourceMappingURL=runSmokeTest.js.map diff --git a/packages/vscode-extension/out/test/runSmokeTest.js.map b/packages/vscode-extension/out/test/runSmokeTest.js.map new file mode 100644 index 00000000..32cb899f --- /dev/null +++ b/packages/vscode-extension/out/test/runSmokeTest.js.map @@ -0,0 +1 @@ +{"version":3,"file":"runSmokeTest.js","sourceRoot":"","sources":["../../src/test/runSmokeTest.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,2CAA6B;AAC7B,gDAAkC;AAClC,yDAAiD;AAEjD,KAAK,UAAU,IAAI;IACjB,IAAI;QACF,4DAA4D;QAC5D,MAAM,wBAAwB,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAEnE,+CAA+C;QAC/C,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;QAEpE,2CAA2C;QAC3C,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC;QAE1E,IAAI;YACF,MAAM,EAAE,CAAC,KAAK,CAAC,iBAAiB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;SACxD;QAAC,OAAO,KAAK,EAAE;YACd,qCAAqC;SACtC;QAED,OAAO,CAAC,GAAG,CAAC,gCAAgC,EAAE,wBAAwB,CAAC,CAAC;QACxE,OAAO,CAAC,GAAG,CAAC,0BAA0B,EAAE,kBAAkB,CAAC,CAAC;QAC5D,OAAO,CAAC,GAAG,CAAC,yBAAyB,EAAE,iBAAiB,CAAC,CAAC;QAE1D,0DAA0D;QAC1D,MAAM,IAAA,wBAAQ,EAAC;YACb,wBAAwB;YACxB,kBAAkB;YAClB,UAAU,EAAE;gBACV,iBAAiB;gBACjB,sBAAsB;gBACtB,2BAA2B,EAAE,mCAAmC;aACjE;SACF,CAAC,CAAC;QAEH,iCAAiC;QACjC,IAAI;YACF,MAAM,EAAE,CAAC,EAAE,CAAC,iBAAiB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;SAClE;QAAC,OAAO,KAAK,EAAE;YACd,OAAO,CAAC,IAAI,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAC;SAC/D;KACF;IAAC,OAAO,GAAG,EAAE;QACZ,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,GAAG,CAAC,CAAC;QACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;KACjB;AACH,CAAC;AAED,IAAI,EAAE,CAAC"} \ No newline at end of file diff --git a/packages/vscode-extension/out/test/smoke/index.js b/packages/vscode-extension/out/test/smoke/index.js new file mode 100644 index 00000000..96a8dc3e --- /dev/null +++ b/packages/vscode-extension/out/test/smoke/index.js @@ -0,0 +1,96 @@ +"use strict"; +/** + * Smoke Test Index + * + * Entry point for vscode-test smoke tests. + * Configures Mocha and discovers test files. + */ +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if ( + !desc || + ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) + ) { + desc = { + enumerable: true, + get: function () { + return m[k]; + }, + }; + } + Object.defineProperty(o, k2, desc); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); + } + : function (o, v) { + o["default"] = v; + }); +var __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) + __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.run = void 0; +const path = __importStar(require("path")); +const mocha_1 = __importDefault(require("mocha")); +const glob_1 = require("glob"); +function run() { + // Create the mocha test + const mocha = new mocha_1.default({ + ui: "bdd", + color: true, + timeout: 60000, + slow: 10000, // Mark tests as slow if they take more than 10s + }); + const testsRoot = path.resolve(__dirname, "./smoke"); + return new Promise((resolve, reject) => { + (0, glob_1.glob)("**/*.test.js", { cwd: testsRoot }) + .then((files) => { + // Add files to the test suite + files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); + try { + // Run the mocha test + mocha.run((failures) => { + if (failures > 0) { + reject(new Error(`${failures} tests failed.`)); + } else { + resolve(); + } + }); + } catch (err) { + console.error(err); + reject(err); + } + }) + .catch((err) => { + reject(err); + }); + }); +} +exports.run = run; +//# sourceMappingURL=index.js.map diff --git a/packages/vscode-extension/out/test/smoke/index.js.map b/packages/vscode-extension/out/test/smoke/index.js.map new file mode 100644 index 00000000..c39190c1 --- /dev/null +++ b/packages/vscode-extension/out/test/smoke/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/test/smoke/index.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,2CAA6B;AAC7B,kDAA0B;AAC1B,+BAA4B;AAE5B,SAAgB,GAAG;IACjB,wBAAwB;IACxB,MAAM,KAAK,GAAG,IAAI,eAAK,CAAC;QACtB,EAAE,EAAE,KAAK;QACT,KAAK,EAAE,IAAI;QACX,OAAO,EAAE,KAAK;QACd,IAAI,EAAE,KAAK,EAAE,gDAAgD;KAC9D,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAErD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAA,WAAI,EAAC,cAAc,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;aACrC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;YACd,8BAA8B;YAC9B,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAEhE,IAAI;gBACF,qBAAqB;gBACrB,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;oBACrB,IAAI,QAAQ,GAAG,CAAC,EAAE;wBAChB,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,QAAQ,gBAAgB,CAAC,CAAC,CAAC;qBAChD;yBAAM;wBACL,OAAO,EAAE,CAAC;qBACX;gBACH,CAAC,CAAC,CAAC;aACJ;YAAC,OAAO,GAAG,EAAE;gBACZ,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gBACnB,MAAM,CAAC,GAAG,CAAC,CAAC;aACb;QACH,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACb,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACL,CAAC;AAnCD,kBAmCC"} \ No newline at end of file diff --git a/packages/vscode-extension/out/types.js b/packages/vscode-extension/out/types.js index 270a1e8f..11e638d1 100644 --- a/packages/vscode-extension/out/types.js +++ b/packages/vscode-extension/out/types.js @@ -1,3 +1,3 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -//# sourceMappingURL=types.js.map +//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/types/progress-events.js b/packages/vscode-extension/out/types/progress-events.js new file mode 100644 index 00000000..5e398fc4 --- /dev/null +++ b/packages/vscode-extension/out/types/progress-events.js @@ -0,0 +1,122 @@ +"use strict"; +/** + * @fileoverview Centralized progress event types for webview communication + * Ensures type-safe progress reporting across all workflows + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getStepDescription = exports.calculateProgressPercentage = exports.createProgressEvent = void 0; +/** + * Helper to create a progress event + */ +function createProgressEvent(type, step, message, data) { + return { + type, + step, + message, + id: `${type}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + timestamp: new Date().toISOString(), + data, + }; +} +exports.createProgressEvent = createProgressEvent; +/** + * Helper to calculate progress percentage based on workflow steps + */ +function calculateProgressPercentage(workflowType, currentStep) { + const stepMaps = { + commit: { + checkingStaged: 25, + buildingMessage: 50, + committing: 75, + completed: 100, + }, + release: { + detectingStrategy: 10, + lockedRecommendedBump: 20, + lockedUpdatingVersions: 40, + lockedGeneratingChangelog: 60, + independentFindingChanges: 20, + independentDeterminingBumps: 40, + independentPreparingPlan: 60, + independentUpdatingPackages: 80, + independentCommitting: 90, + completed: 100, + }, + issues: { + fetching: 50, + caching: 75, + completed: 100, + error: 100, + }, + init: { + scaffold: 15, + saveConfig: 25, + generateReadme: 35, + generateGitignore: 45, + setupHusky: 55, + initializeGit: 65, + validateDependencies: 75, + installDependencies: 90, + completed: 100, + }, + generate: { + checkingFile: 33, + generatingContent: 66, + writingFile: 90, + completed: 100, + }, + }; + return stepMaps[workflowType]?.[currentStep] ?? 0; +} +exports.calculateProgressPercentage = calculateProgressPercentage; +/** + * Helper to get user-friendly step descriptions + */ +function getStepDescription(workflowType, step) { + const descriptions = { + commit: { + checkingStaged: "Checking staged changes...", + buildingMessage: "Building commit message...", + committing: "Creating commit...", + completed: "Commit completed successfully", + }, + release: { + detectingStrategy: "Detecting versioning strategy...", + lockedRecommendedBump: "Determining version bump...", + lockedUpdatingVersions: "Updating versions...", + lockedGeneratingChangelog: "Generating changelog...", + independentFindingChanges: "Finding changed packages...", + independentDeterminingBumps: "Determining version bumps...", + independentPreparingPlan: "Preparing release plan...", + independentUpdatingPackages: "Updating package versions...", + independentCommitting: "Creating release commits...", + completed: "Release workflow completed", + }, + issues: { + fetching: "Fetching issues from GitHub...", + caching: "Caching results...", + completed: "Issues fetched successfully", + error: "Failed to fetch issues", + }, + init: { + scaffold: "Scaffolding project structure...", + saveConfig: "Saving configuration...", + generateReadme: "Generating README...", + generateGitignore: "Generating .gitignore...", + setupHusky: "Setting up Husky...", + initializeGit: "Initializing Git repository...", + validateDependencies: "Validating dependencies...", + installDependencies: "Installing dependencies...", + completed: "Project initialized successfully", + }, + generate: { + checkingFile: "Checking existing file...", + generatingContent: "Generating content...", + writingFile: "Writing file...", + completed: "File generated successfully", + }, + }; + return descriptions[workflowType]?.[step] ?? `Processing: ${step}`; +} +exports.getStepDescription = getStepDescription; +//# sourceMappingURL=progress-events.js.map \ No newline at end of file diff --git a/packages/vscode-extension/out/types/progress-events.js.map b/packages/vscode-extension/out/types/progress-events.js.map new file mode 100644 index 00000000..2ae33768 --- /dev/null +++ b/packages/vscode-extension/out/types/progress-events.js.map @@ -0,0 +1 @@ +{"version":3,"file":"progress-events.js","sourceRoot":"","sources":["../../src/types/progress-events.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/vscode-extension/package.json b/packages/vscode-extension/package.json index 84d11b11..eaac97b8 100644 --- a/packages/vscode-extension/package.json +++ b/packages/vscode-extension/package.json @@ -17,7 +17,12 @@ "dev:ui": "vite --config src/webview-ui/vite.config.ts", "compile:ext": "tsc -p ./", "package": "vsce package", - "test": "jest" + "test": "jest --coverage", + "test:watch": "jest --watch", + "test:integration": "jest --testPathPattern=integration", + "test:smoke": "ts-node src/test/runSmokeTest.ts", + "test:all": "npm run test && npm run test:integration && npm run test:smoke", + "pretest": "npm run compile:ext" }, "categories": [ "Other", @@ -72,6 +77,12 @@ "category": "StackCode", "icon": "$(check)" }, + { + "command": "stackcode.validate.commit", + "title": "Validate Commit Message", + "category": "StackCode", + "icon": "$(check)" + }, { "command": "stackcode.release", "title": "Create Release", @@ -250,26 +261,34 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4.1.11", + "@types/glob": "^8.1.0", "@types/jest": "^29.5.14", + "@types/mocha": "^10.0.6", "@types/node": "^16.18.126", "@types/react": "^19.1.9", "@types/react-dom": "^19.1.7", "@types/vscode": "^1.85.0", "@vitejs/plugin-react": "^5.0.0", + "@vscode/test-electron": "^2.3.9", "@vscode/vsce": "^2.19.0", "autoprefixer": "^10.4.21", + "glob": "^10.3.10", "jest": "^29.5.0", "lucide-react": "^0.539.0", + "mocha": "^10.3.0", "npm-run-all": "^4.1.5", "postcss": "^8.5.6", "react": "^19.1.1", "react-dom": "^19.1.1", "tailwindcss": "^4.1.11", "ts-jest": "^29.4.1", + "ts-node": "^10.9.2", "typescript": "^4.9.5", "vite": "^7.1.1" }, "dependencies": { + "@octokit/rest": "^22.0.0", + "@stackcode/github-auth": "^1.0.0", "@stackcode/core": "^1.0.4", "@stackcode/i18n": "^1.0.4" }, diff --git a/packages/vscode-extension/src/commands/AuthCommand.ts b/packages/vscode-extension/src/commands/AuthCommand.ts index f5159056..a0317f15 100644 --- a/packages/vscode-extension/src/commands/AuthCommand.ts +++ b/packages/vscode-extension/src/commands/AuthCommand.ts @@ -3,11 +3,11 @@ import { BaseCommand } from "./BaseCommand"; import { GitHubAuthService } from "../services/GitHubAuthService"; /** - * AuthCommand - Gerencia comandos de autenticação GitHub + * AuthCommand - Manages GitHub authentication commands * - * Comandos disponíveis: + * Available commands: * - stackcode.auth.login: Inicia processo de login - * - stackcode.auth.logout: Remove autenticação + * - stackcode.auth.logout: Removes authentication */ export class AuthCommand extends BaseCommand { private _authService: GitHubAuthService; @@ -18,7 +18,7 @@ export class AuthCommand extends BaseCommand { } /** - * Implementação do método abstrato - mostra status da autenticação + * Abstract method implementation - shows authentication status */ public async execute(): Promise { await this.showStatus(); @@ -85,7 +85,7 @@ export class AuthCommand extends BaseCommand { } /** - * Mostra status atual da autenticação + * Shows current authentication status */ public async showStatus(): Promise { if (this._authService.isAuthenticated) { diff --git a/packages/vscode-extension/src/commands/BaseCommand.ts b/packages/vscode-extension/src/commands/BaseCommand.ts index df2dc076..bf1ba30e 100644 --- a/packages/vscode-extension/src/commands/BaseCommand.ts +++ b/packages/vscode-extension/src/commands/BaseCommand.ts @@ -38,12 +38,13 @@ export abstract class BaseCommand { protected async confirmAction( message: string, confirmText: string = "Yes", + cancelText: string = "Cancel", ): Promise { const result = await vscode.window.showWarningMessage( message, { modal: true }, confirmText, - "Cancel", + cancelText, ); return result === confirmText; } diff --git a/packages/vscode-extension/src/commands/CommitCommand.ts b/packages/vscode-extension/src/commands/CommitCommand.ts index 5d80aed7..45529087 100644 --- a/packages/vscode-extension/src/commands/CommitCommand.ts +++ b/packages/vscode-extension/src/commands/CommitCommand.ts @@ -1,23 +1,406 @@ -import { BaseCommand } from "./BaseCommand"; +import * as vscode from "vscode"; +import { + runCommitWorkflow, + runIssuesWorkflow, + type CommitWorkflowStep, + type GitHubIssue, +} from "@stackcode/core"; import { t } from "@stackcode/i18n"; +import { BaseCommand } from "./BaseCommand"; +import { GitHubAuthService } from "../services/GitHubAuthService"; +import { GitMonitor } from "../monitors/GitMonitor"; +import { ProgressManager } from "../services/ProgressManager"; +interface CommitTypeQuickPickItem extends vscode.QuickPickItem { + value: string; +} + +/** + * Handles Conventional Commit workflow in VS Code. + * Prompts for commit details, links GitHub issues, and integrates progress feedback. + */ export class CommitCommand extends BaseCommand { - async execute(): Promise { + private readonly authService: GitHubAuthService; + private readonly gitMonitor: GitMonitor; + private readonly progressManager: ProgressManager; + private outputChannel?: vscode.OutputChannel; + + constructor( + authService: GitHubAuthService, + gitMonitor: GitMonitor, + progressManager: ProgressManager, + ) { + super(); + this.authService = authService; + this.gitMonitor = gitMonitor; + this.progressManager = progressManager; + } + + public async execute(): Promise { try { const workspaceFolder = this.getCurrentWorkspaceFolder(); if (!workspaceFolder) { - this.showError(t("vscode.common.no_workspace_folder")); + await this.showError(t("vscode.common.no_workspace_folder")); + return; + } + + const commitType = await this.selectCommitType(); + if (!commitType) { return; } - const command = `npx @stackcode/cli commit`; + const scope = await vscode.window.showInputBox({ + prompt: this.translate("commit.prompt.scope", "Scope (optional)"), + placeHolder: this.translate("commit.prompt.scope", "Scope (optional)"), + }); + + const shortDescription = await this.promptRequiredText( + this.translate( + "commit.prompt.short_description", + "Write a short, imperative description of the change", + ), + this.translate( + "commit.prompt.short_description", + "Write a short, imperative description of the change", + ), + ); + if (!shortDescription) { + return; + } + + const longDescription = await vscode.window.showInputBox({ + prompt: this.translate( + "commit.prompt.long_description", + "Provide a longer description (optional)", + ), + placeHolder: this.translate( + "commit.prompt.long_description", + "Provide a longer description (optional)", + ), + value: "", + }); + + const breakingChanges = await vscode.window.showInputBox({ + prompt: this.translate( + "commit.prompt.breaking_changes", + "Describe BREAKING CHANGES (optional)", + ), + placeHolder: this.translate( + "commit.prompt.breaking_changes", + "Describe BREAKING CHANGES (optional)", + ), + }); + + const issueReferences = await this.resolveIssueReferences(); + + this.progressManager.startWorkflow("commit"); + + const result = await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: this.translate( + "commit.command_description", + "Prepare a conventional commit", + ), + cancellable: false, + }, + async (progress) => { + this.progressManager.setVSCodeProgressReporter(progress); + return runCommitWorkflow( + { + cwd: workspaceFolder.uri.fsPath, + type: commitType, + scope: scope || undefined, + shortDescription, + longDescription: longDescription || undefined, + breakingChanges: breakingChanges || undefined, + affectedIssues: issueReferences || undefined, + }, + { + onProgress: ( + workflowProgress: import("@stackcode/core").CommitWorkflowProgress, + ) => { + this.reportCommitProgress(workflowProgress.step, progress); + this.progressManager.reportProgress( + "commit", + workflowProgress.step, + workflowProgress.message, + ); + }, + }, + ); + }, + ); + + this.progressManager.clearVSCodeProgressReporter(); + + if (result.status === "committed") { + this.progressManager.completeWorkflow( + "commit", + "Commit created successfully", + ); + this.appendCommitMessage(result.message ?? shortDescription); + await this.showSuccess(t("commit.success")); + return; + } + + this.progressManager.failWorkflow( + "commit", + result.error || "Commit workflow cancelled", + ); + + if (result.reason === "no-staged-changes") { + await this.showWarning(t("commit.error_no_changes_staged")); + return; + } + + const errorMessage = + result.error ?? + this.translate("common.error_generic", "An error occurred."); + await this.showError(errorMessage); + } catch (error) { + await this.showError( + `${this.translate("common.error_generic", "An error occurred.")} ${ + error instanceof Error ? error.message : String(error) + }`, + ); + } + } + + /** + * Displays commit message output for user reference. + * @param message - The final commit message created by the workflow. + */ + private appendCommitMessage(message: string): void { + const channel = this.ensureOutputChannel(); + channel.appendLine("―".repeat(60)); + channel.appendLine( + `${new Date().toISOString()} - ${this.translate( + "commit.output_channel_title", + "Commit message", + )}`, + ); + channel.appendLine(message); + channel.show(true); + } + + /** + * Prompts the user to select the Conventional Commit type. + */ + private async selectCommitType(): Promise { + const items: CommitTypeQuickPickItem[] = [ + { label: this.translate("commit.types.feat", "feat"), value: "feat" }, + { label: this.translate("commit.types.fix", "fix"), value: "fix" }, + { label: this.translate("commit.types.docs", "docs"), value: "docs" }, + { + label: this.translate("commit.types.style", "style"), + value: "style", + }, + { + label: this.translate("commit.types.refactor", "refactor"), + value: "refactor", + }, + { label: this.translate("commit.types.perf", "perf"), value: "perf" }, + { label: this.translate("commit.types.test", "test"), value: "test" }, + { + label: this.translate("commit.types.chore", "chore"), + value: "chore", + }, + { + label: this.translate("commit.types.revert", "revert"), + value: "revert", + }, + ]; + + const selection = await vscode.window.showQuickPick(items, { + placeHolder: this.translate( + "commit.prompt.select_type", + "Select the type of change", + ), + }); + + return selection?.value; + } + + /** + * Prompts the user for required text input, handling validation. + * @param prompt - Prompt message to display. + * @param placeHolder - Placeholder text for the input box. + */ + private async promptRequiredText( + prompt: string, + placeHolder: string, + ): Promise { + return vscode.window.showInputBox({ + prompt, + placeHolder, + validateInput: (value) => + value && value.trim().length > 0 + ? undefined + : this.translate("common.error_generic", "This field is required."), + }); + } + + /** + * Resolves GitHub issues references, asking the user if they wish to link issues. + * Now uses runIssuesWorkflow from @stackcode/core for centralized logic. + */ + private async resolveIssueReferences(): Promise { + try { + if (!this.authService.isAuthenticated) { + return this.promptManualIssueReference(); + } + + const repository = await this.gitMonitor.getCurrentGitHubRepository(); + if (!repository) { + return this.promptManualIssueReference(); + } - await this.runTerminalCommand(command, workspaceFolder.uri.fsPath); + const client = await this.authService.getAuthenticatedClient(); - this.showSuccess(t("vscode.commit.commit_dialog_opened")); + const result = await runIssuesWorkflow({ + client, + repository: { + owner: repository.owner, + repo: repository.repo, + fullName: repository.fullName, + }, + enableCache: true, + }); + + if (result.status === "error" || !result.issues.length) { + return this.promptManualIssueReference(); + } + + const selections = await vscode.window.showQuickPick( + result.issues.map((issue) => this.mapIssueToQuickPick(issue)), + { + canPickMany: true, + placeHolder: this.translate( + "commit.prompt.affected_issues", + "Select issues to reference", + ), + }, + ); + + if (!selections || selections.length === 0) { + return this.promptManualIssueReference(); + } + + return selections + .map((item) => + this.translate( + "commit.issues.reference_entry", + "closes #{issueNumber}", + { + issueNumber: item.issue.number, + }, + ), + ) + .join(", "); } catch (error) { - this.showError( - t("vscode.commit.failed_open_commit_dialog", { error: String(error) }), + const reason = error instanceof Error ? error.message : String(error); + await this.showWarning( + `${this.translate( + "github.issues.error_fetching", + "Failed to fetch issues:", + )} ${reason}`, + ); + return this.promptManualIssueReference(); + } + } + + /** + * Prompts the user for manual issue references when GitHub integration is unavailable. + */ + private async promptManualIssueReference(): Promise { + const manualValue = await vscode.window.showInputBox({ + prompt: this.translate( + "commit.prompt.affected_issues", + "Does this change affect any open issues?", + ), + placeHolder: this.translate( + "commit.placeholder.issue_reference", + "closes #123", + ), + }); + return manualValue?.trim() ? manualValue.trim() : undefined; + } + + /** + * Maps a GitHub issue to a VS Code quick pick item. + */ + private mapIssueToQuickPick(issue: GitHubIssue): { + label: string; + description: string; + issue: GitHubIssue; + } { + return { + label: `#${issue.number} ${issue.title}`, + description: issue.user?.login ?? "", + issue, + }; + } + + /** + * Updates progress reporting messages according to the workflow step. + */ + private reportCommitProgress( + step: CommitWorkflowStep, + progress: vscode.Progress<{ message?: string }>, + ): void { + const messages: Partial> = { + checkingStaged: this.translate( + "commit.progress.checking_staged", + "Checking staged changes...", + ), + buildingMessage: this.translate( + "commit.progress.building_message", + "Building commit message...", + ), + committing: this.translate( + "commit.progress.committing", + "Running git commit...", + ), + completed: this.translate( + "commit.progress.completed", + "Commit completed successfully.", + ), + }; + + const message = messages[step]; + if (message) { + progress.report({ message }); + this.ensureOutputChannel().appendLine(message); + } + } + + /** + * Lazily creates and returns the output channel used for commit logs. + */ + private ensureOutputChannel(): vscode.OutputChannel { + if (!this.outputChannel) { + this.outputChannel = + vscode.window.createOutputChannel("StackCode Commit"); + } + return this.outputChannel; + } + + /** + * Safely translates a key using i18n with a fallback string. + */ + private translate( + key: string, + fallback: string, + variables?: Record, + ): string { + try { + return variables ? t(key, variables) : t(key); + } catch { + if (!variables) return fallback; + return Object.entries(variables).reduce( + (acc, [varKey, value]) => acc.replace(`{${varKey}}`, String(value)), + fallback, ); } } diff --git a/packages/vscode-extension/src/commands/ConfigCommand.ts b/packages/vscode-extension/src/commands/ConfigCommand.ts index 604c25dd..9da2e908 100644 --- a/packages/vscode-extension/src/commands/ConfigCommand.ts +++ b/packages/vscode-extension/src/commands/ConfigCommand.ts @@ -1,6 +1,7 @@ import * as vscode from "vscode"; import { BaseCommand } from "./BaseCommand"; import { t } from "@stackcode/i18n"; +import { saveStackCodeConfig, type StackCodeConfig } from "@stackcode/core"; export class ConfigCommand extends BaseCommand { async execute(): Promise { @@ -52,9 +53,7 @@ export class ConfigCommand extends BaseCommand { this.showError(t("vscode.config.stackcoderc_not_found")); } } else if (action.label === t("vscode.config.create_project_config")) { - const command = `npx @stackcode/cli config init`; - await this.runTerminalCommand(command, workspaceFolder.uri.fsPath); - this.showSuccess(t("vscode.config.project_configuration_initialized")); + await this.createProjectConfig(workspaceFolder); } } catch (error) { this.showError( @@ -62,4 +61,50 @@ export class ConfigCommand extends BaseCommand { ); } } + + private async createProjectConfig( + workspaceFolder: vscode.WorkspaceFolder, + ): Promise { + try { + const configUri = vscode.Uri.joinPath( + workspaceFolder.uri, + ".stackcoderc.json", + ); + + try { + await vscode.workspace.fs.stat(configUri); + const overwrite = await this.confirmAction( + t("vscode.config.stackcoderc_exists_overwrite"), + t("vscode.config.overwrite"), + t("common.cancel"), + ); + if (!overwrite) { + return; + } + } catch (error) { + console.warn("Failed to read existing config:", error); + } + + const defaultConfig: StackCodeConfig = { + stack: undefined, + features: { + commitValidation: false, + husky: false, + docker: false, + }, + }; + + await saveStackCodeConfig(workspaceFolder.uri.fsPath, defaultConfig); + + const document = await vscode.workspace.openTextDocument(configUri); + await vscode.window.showTextDocument(document); + await this.showSuccess( + t("vscode.config.project_configuration_initialized"), + ); + } catch (error) { + await this.showError( + t("vscode.config.failed_create_config", { error: String(error) }), + ); + } + } } diff --git a/packages/vscode-extension/src/commands/GenerateCommand.ts b/packages/vscode-extension/src/commands/GenerateCommand.ts index ba2f5b88..7ec547d6 100644 --- a/packages/vscode-extension/src/commands/GenerateCommand.ts +++ b/packages/vscode-extension/src/commands/GenerateCommand.ts @@ -1,8 +1,15 @@ import * as vscode from "vscode"; import { BaseCommand } from "./BaseCommand"; -import { ProgressCallback } from "../types"; import { t } from "@stackcode/i18n"; import * as path from "path"; +import { + runGenerateWorkflow, + type GenerateFileType, + type GenerateWorkflowHooks, + type GenerateWorkflowOptions, + type GenerateWorkflowResult, + type GenerateWorkflowStep, +} from "@stackcode/core"; /** * Command to generate project files like README.md and .gitignore. @@ -47,171 +54,213 @@ export class GenerateCommand extends BaseCommand { } } - async generateReadme(): Promise { - try { - const workspaceFolder = this.getCurrentWorkspaceFolder(); - if (!workspaceFolder) { - this.showError(t("vscode.common.no_workspace_folder")); - return; - } - - const readmePath = path.join(workspaceFolder.uri.fsPath, "README.md"); + private async ensureWorkspaceFolder(): Promise< + vscode.WorkspaceFolder | undefined + > { + const workspaceFolder = this.getCurrentWorkspaceFolder(); + if (!workspaceFolder) { + await this.showError(t("vscode.common.no_workspace_folder")); + return undefined; + } + return workspaceFolder; + } - try { - await vscode.workspace.fs.stat(vscode.Uri.file(readmePath)); - const overwrite = await this.confirmAction( - t("vscode.generate.readme_exists_overwrite"), - t("vscode.generate.overwrite"), - ); - if (!overwrite) { - return; - } - } catch { - // File doesn't exist - proceed with generation - } + private async promptGitignoreTechnologies(): Promise { + const selections = await vscode.window.showQuickPick( + [ + { label: "node-ts", description: t("vscode.init.stacks.node_ts") }, + { label: "react", description: t("vscode.init.stacks.react") }, + { label: "vue", description: t("vscode.init.stacks.vue") }, + { label: "angular", description: t("vscode.init.stacks.angular") }, + { label: "python", description: t("vscode.init.stacks.python") }, + { label: "java", description: t("vscode.init.stacks.java") }, + { label: "go", description: t("vscode.init.stacks.go") }, + { label: "php", description: t("vscode.init.stacks.php") }, + ], + { + placeHolder: t("vscode.generate.select_project_type_gitignore"), + canPickMany: true, + }, + ); + if (!selections || selections.length === 0) return undefined; + return selections.map((s) => s.label); + } - vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: t("vscode.generate.generating_readme"), - cancellable: false, - }, - async (progress: ProgressCallback) => { - progress.report({ - increment: 0, - message: t("vscode.generate.setting_up_readme"), - }); + private stepMessage(step: GenerateWorkflowStep): string | undefined { + switch (step) { + case "checkingFile": + return t("vscode.generate.setting_up_readme"); + case "generatingContent": + return t("vscode.generate.running_generator"); + case "writingFile": + return t("vscode.generate.readme_created"); + default: + return undefined; + } + } - const command = `npx @stackcode/cli generate readme`; + private createWorkflowHooks( + progress: vscode.Progress<{ message?: string }>, + ): GenerateWorkflowHooks { + return { + onProgress: async ({ step }: { step: GenerateWorkflowStep }) => { + const message = this.stepMessage(step); + if (message) progress.report({ message }); + }, + onEducationalMessage: async (messageKey: string) => { + progress.report({ message: t(messageKey) }); + }, + shouldOverwriteFile: async ({ + fileType, + }: { + fileType: GenerateFileType; + filePath: string; + }) => { + const confirmLabel = t("vscode.generate.overwrite"); + const message = + fileType === "readme" + ? t("vscode.generate.readme_exists_overwrite") + : t("vscode.generate.gitignore_exists_overwrite"); + const choice = await vscode.window.showWarningMessage( + message, + { modal: true }, + confirmLabel, + ); + return choice === confirmLabel; + }, + }; + } - progress.report({ - increment: 50, - message: t("vscode.generate.running_generator"), - }); + private async runWorkflowWithProgress( + workspaceFolder: vscode.WorkspaceFolder, + options: GenerateWorkflowOptions, + ): Promise { + return vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: t("vscode.generate.running_generator"), + cancellable: false, + }, + async (progress) => { + const hooks = this.createWorkflowHooks(progress); + return runGenerateWorkflow(options, hooks); + }, + ); + } - await this.runTerminalCommand(command, workspaceFolder.uri.fsPath); + private async handleWorkflowOutcome( + workspaceFolder: vscode.WorkspaceFolder, + result: GenerateWorkflowResult, + fileTypes: GenerateFileType[], + ): Promise { + const created = result.files.filter( + (f: GenerateWorkflowResult["files"][number]) => + f.status === "created" || f.status === "overwritten", + ); + for (const f of created) { + if (f.fileType === "readme") { + await this.showSuccess(t("vscode.generate.readme_has_been_generated")); + } else if (f.fileType === "gitignore") { + await this.showSuccess( + t("vscode.generate.gitignore_has_been_generated"), + ); + } + } - progress.report({ - increment: 100, - message: t("vscode.generate.readme_created"), - }); - }, + for (const ft of fileTypes) { + const filePath = path.join( + workspaceFolder.uri.fsPath, + ft === "readme" ? "README.md" : ".gitignore", ); - - this.showSuccess(t("vscode.generate.readme_has_been_generated")); - - const openFile = await vscode.window.showInformationMessage( - t("vscode.generate.would_you_like_open_readme"), - t("vscode.generate.open_file"), + const openPromptKey = + ft === "readme" + ? "vscode.generate.would_you_like_open_readme" + : "vscode.generate.would_you_like_open_gitignore"; + const openLabel = t("vscode.generate.open_file"); + const choice = await vscode.window.showInformationMessage( + t(openPromptKey), + openLabel, ); - - if (openFile === t("vscode.generate.open_file")) { - const document = await vscode.workspace.openTextDocument(readmePath); + if (choice === openLabel) { + const document = await vscode.workspace.openTextDocument(filePath); await vscode.window.showTextDocument(document); } - } catch (error) { - this.showError( - t("vscode.generate.failed_generate_readme", { error: String(error) }), - ); } - } - async generateGitignore(): Promise { - try { - const workspaceFolder = this.getCurrentWorkspaceFolder(); - if (!workspaceFolder) { - this.showError(t("vscode.common.no_workspace_folder")); - return; + if (result.warnings.length > 0) { + for (const w of result.warnings) { + await this.showWarning(t(w)); } + } + } + async generateReadme(): Promise { + const workspaceFolder = await this.ensureWorkspaceFolder(); + if (!workspaceFolder) { + return; + } - const gitignorePath = path.join(workspaceFolder.uri.fsPath, ".gitignore"); - - try { - await vscode.workspace.fs.stat(vscode.Uri.file(gitignorePath)); - const overwrite = await this.confirmAction( - t("vscode.generate.gitignore_exists_overwrite"), - t("vscode.generate.overwrite"), - ); - if (!overwrite) { - return; - } - } catch { - // File doesn't exist - proceed with generation - } + try { + const result = await this.runWorkflowWithProgress(workspaceFolder, { + projectPath: workspaceFolder.uri.fsPath, + files: ["readme"], + }); - const projectType = await vscode.window.showQuickPick( - [ - { label: "node-ts", description: t("vscode.init.stacks.node_ts") }, - { label: "react", description: t("vscode.init.stacks.react") }, - { label: "vue", description: t("vscode.init.stacks.vue") }, - { label: "angular", description: t("vscode.init.stacks.angular") }, - { label: "python", description: t("vscode.init.stacks.python") }, - { label: "java", description: t("vscode.init.stacks.java") }, - { label: "go", description: t("vscode.init.stacks.go") }, - { label: "php", description: t("vscode.init.stacks.php") }, - { - label: "flutter", - description: t("vscode.generate.stacks.flutter"), - }, - { label: "swift", description: t("vscode.generate.stacks.swift") }, - { - label: "android", - description: t("vscode.generate.stacks.android"), - }, - ], - { - placeHolder: t("vscode.generate.select_project_type_gitignore"), - }, + await this.handleWorkflowOutcome(workspaceFolder, result, ["readme"]); + } catch (error) { + await this.showError( + t("vscode.generate.failed_generate_readme", { error: String(error) }), ); + } + } - if (!projectType) { - return; - } - - vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: t("vscode.generate.generating_gitignore"), - cancellable: false, - }, - async (progress: ProgressCallback) => { - progress.report({ - increment: 0, - message: t("vscode.generate.setting_up_gitignore"), - }); - - const command = `npx @stackcode/cli generate gitignore --type="${projectType.label}"`; + async generateGitignore(): Promise { + const workspaceFolder = await this.ensureWorkspaceFolder(); + if (!workspaceFolder) { + return; + } - progress.report({ - increment: 50, - message: t("vscode.generate.running_generator"), - }); + const technologies = await this.promptGitignoreTechnologies(); + if (!technologies) { + return; + } - await this.runTerminalCommand(command, workspaceFolder.uri.fsPath); + try { + const result = await this.runWorkflowWithProgress(workspaceFolder, { + projectPath: workspaceFolder.uri.fsPath, + files: ["gitignore"], + gitignoreTechnologies: technologies, + }); - progress.report({ - increment: 100, - message: t("vscode.generate.gitignore_created"), - }); - }, + await this.handleWorkflowOutcome(workspaceFolder, result, ["gitignore"]); + } catch (error) { + await this.showError( + t("vscode.generate.failed_generate_gitignore", { + error: String(error), + }), ); + } + } - this.showSuccess(t("vscode.generate.gitignore_has_been_generated")); + private async generateFiles( + fileTypes: GenerateFileType[], + gitignoreTechnologies?: string[], + ): Promise { + const workspaceFolder = await this.ensureWorkspaceFolder(); + if (!workspaceFolder) { + return; + } - const openFile = await vscode.window.showInformationMessage( - t("vscode.generate.would_you_like_open_gitignore"), - t("vscode.generate.open_file"), - ); + try { + const result = await this.runWorkflowWithProgress(workspaceFolder, { + projectPath: workspaceFolder.uri.fsPath, + files: fileTypes, + gitignoreTechnologies, + }); - if (openFile === t("vscode.generate.open_file")) { - const document = await vscode.workspace.openTextDocument(gitignorePath); - await vscode.window.showTextDocument(document); - } + await this.handleWorkflowOutcome(workspaceFolder, result, fileTypes); } catch (error) { - this.showError( - t("vscode.generate.failed_generate_gitignore", { - error: String(error), - }), + await this.showError( + t("vscode.generate.failed_generate_readme", { error: String(error) }), ); } } diff --git a/packages/vscode-extension/src/commands/GitCommand.ts b/packages/vscode-extension/src/commands/GitCommand.ts index 00266ca4..9ac70107 100644 --- a/packages/vscode-extension/src/commands/GitCommand.ts +++ b/packages/vscode-extension/src/commands/GitCommand.ts @@ -1,7 +1,7 @@ import * as vscode from "vscode"; import { BaseCommand } from "./BaseCommand"; -import { ProgressCallback } from "../types"; import { t } from "@stackcode/i18n"; +import { runGitStartWorkflow, runGitFinishWorkflow } from "@stackcode/core"; export class GitCommand extends BaseCommand { async execute(): Promise { @@ -71,7 +71,7 @@ export class GitCommand extends BaseCommand { return; } - vscode.window.withProgress( + await vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, title: t("vscode.git.creating_branch", { @@ -79,25 +79,52 @@ export class GitCommand extends BaseCommand { }), cancellable: false, }, - async (progress: ProgressCallback) => { - progress.report({ - increment: 0, - message: t("vscode.git.switching_to_develop"), - }); - - const command = `npx @stackcode/cli git start ${branchName} --type=${branchType.label}`; - - progress.report({ - increment: 50, - message: t("vscode.git.creating_new_branch"), - }); - - await this.runTerminalCommand(command, workspaceFolder.uri.fsPath); - - progress.report({ - increment: 100, - message: t("vscode.git.branch_created_successfully"), - }); + async ( + progress: vscode.Progress<{ message?: string; increment?: number }>, + ) => { + const result = await runGitStartWorkflow( + { + cwd: workspaceFolder.uri.fsPath, + branchName, + branchType: branchType.label, + }, + { + onProgress: ( + step: import("@stackcode/core").GitStartWorkflowProgress, + ) => { + switch (step.step) { + case "switchingBase": + progress.report({ + increment: 10, + message: t("vscode.git.switching_to_develop"), + }); + break; + case "pullingBase": + progress.report({ + increment: 50, + message: t("vscode.git.pulling_latest_changes"), + }); + break; + case "creatingBranch": + progress.report({ + increment: 80, + message: t("vscode.git.creating_new_branch"), + }); + break; + case "completed": + progress.report({ + increment: 100, + message: t("vscode.git.branch_created_successfully"), + }); + break; + } + }, + }, + ); + + if (result.status !== "created") { + throw new Error(result.error ?? t("vscode.common.unknown_error")); + } }, ); @@ -132,8 +159,8 @@ export class GitCommand extends BaseCommand { if (repo && repo.state.HEAD) { currentBranch = repo.state.HEAD.name || "current branch"; } - } catch { - // Git API unavailable - use generic message + } catch (error) { + console.warn("Git API unavailable:", error); } } @@ -145,8 +172,7 @@ export class GitCommand extends BaseCommand { if (!confirm) { return; } - - vscode.window.withProgress( + await vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, title: t("vscode.git.finishing_branch", { @@ -154,25 +180,49 @@ export class GitCommand extends BaseCommand { }), cancellable: false, }, - async (progress: ProgressCallback) => { - progress.report({ - increment: 0, - message: t("vscode.git.pushing_branch"), - }); - - const command = `npx @stackcode/cli git finish`; - - progress.report({ - increment: 50, - message: t("vscode.git.opening_pr"), - }); - - await this.runTerminalCommand(command, workspaceFolder.uri.fsPath); + async ( + progress: vscode.Progress<{ message?: string; increment?: number }>, + ) => { + const result = await runGitFinishWorkflow( + { cwd: workspaceFolder.uri.fsPath }, + { + onProgress: ( + step: import("@stackcode/core").GitFinishWorkflowProgress, + ) => { + switch (step.step) { + case "pushing": + progress.report({ + increment: 30, + message: t("vscode.git.pushing_branch"), + }); + break; + case "computingPrUrl": + progress.report({ + increment: 70, + message: t("vscode.git.opening_pr"), + }); + break; + case "completed": + progress.report({ + increment: 100, + message: t("vscode.git.branch_finished_successfully"), + }); + break; + } + }, + }, + ); + + if (result.status !== "pushed" || !result.prUrl || !result.branch) { + const errorMessage = + result.error === "not-on-branch" + ? t("vscode.git.branch_name_required") + : (result.error ?? t("vscode.common.unknown_error")); + throw new Error(errorMessage); + } - progress.report({ - increment: 100, - message: t("vscode.git.branch_finished_successfully"), - }); + await vscode.env.openExternal(vscode.Uri.parse(result.prUrl)); + currentBranch = result.branch; }, ); diff --git a/packages/vscode-extension/src/commands/InitCommand.ts b/packages/vscode-extension/src/commands/InitCommand.ts index aa3817c6..0b30ed03 100644 --- a/packages/vscode-extension/src/commands/InitCommand.ts +++ b/packages/vscode-extension/src/commands/InitCommand.ts @@ -1,7 +1,16 @@ import * as vscode from "vscode"; import { BaseCommand } from "./BaseCommand"; -import { ProgressCallback } from "../types"; import { t } from "@stackcode/i18n"; +import { + runInitWorkflow, + type InitFeature, + type InitWorkflowDependencyDecision, + type InitWorkflowHooks, + type InitWorkflowOptions, + type InitWorkflowProgress, + type InitWorkflowResult, + type InitWorkflowStep, +} from "@stackcode/core"; import * as path from "path"; /** @@ -32,16 +41,18 @@ export class InitCommand extends BaseCommand { return; } - const description = await vscode.window.showInputBox({ + const descriptionInput = await vscode.window.showInputBox({ prompt: t("vscode.init.enter_project_description"), placeHolder: t("vscode.init.brief_description"), }); + const description = descriptionInput ?? ""; - const authorName = await vscode.window.showInputBox({ + const authorInput = await vscode.window.showInputBox({ prompt: t("vscode.init.enter_author_name"), placeHolder: t("vscode.init.your_name"), value: await this.getGitUserName(), }); + const authorName = authorInput ?? ""; const stack = await vscode.window.showQuickPick( [ @@ -63,6 +74,81 @@ export class InitCommand extends BaseCommand { return; } + type FeatureQuickPickItem = vscode.QuickPickItem & { value: InitFeature }; + const featureItems: FeatureQuickPickItem[] = [ + { + label: this.safeTranslate( + "vscode.init.features.docker.label", + "Docker support", + ), + description: this.safeTranslate( + "vscode.init.features.docker.description", + "Adds Docker configuration to the project", + ), + picked: true, + value: "docker", + }, + { + label: this.safeTranslate( + "vscode.init.features.husky.label", + "Husky commit hooks", + ), + description: this.safeTranslate( + "vscode.init.features.husky.description", + "Installs Husky to enforce commit conventions", + ), + picked: true, + value: "husky", + }, + ]; + + const selectedFeatures = await vscode.window.showQuickPick(featureItems, { + canPickMany: true, + placeHolder: this.safeTranslate( + "init.prompt.features", + "Select optional features", + ), + }); + + if (typeof selectedFeatures === "undefined") { + return; + } + + const features = ( + selectedFeatures.length > 0 + ? selectedFeatures + : featureItems.filter((item) => item.picked) + ).map((item) => item.value as InitFeature); + + let commitValidation: boolean | undefined; + if (features.includes("husky")) { + type BooleanQuickPickItem = vscode.QuickPickItem & { value: boolean }; + const validationChoice = await vscode.window.showQuickPick( + [ + { + label: this.safeTranslate("common.yes", "Yes"), + value: true, + }, + { + label: this.safeTranslate("common.no", "No"), + value: false, + }, + ] satisfies BooleanQuickPickItem[], + { + placeHolder: this.safeTranslate( + "init.prompt.commit_validation", + "Enable commit validation hooks?", + ), + }, + ); + + if (!validationChoice) { + return; + } + + commitValidation = validationChoice.value; + } + const workspaceFolder = this.getCurrentWorkspaceFolder(); let projectPath: string; @@ -92,45 +178,107 @@ export class InitCommand extends BaseCommand { if (!overwrite) { return; } - } catch { - // Directory doesn't exist - proceed with creation + } catch (error) { + console.warn("Directory check failed:", error); } - vscode.window.withProgress( + const workflowOptions: InitWorkflowOptions = { + projectPath, + projectName, + description, + authorName, + stack: stack.label as InitWorkflowOptions["stack"], + features, + commitValidation, + }; + + const initializingTitle = this.safeTranslate( + "vscode.init.initializing_project", + `Initializing project ${projectName}`, + { projectName }, + ); + + const workflowResult = await vscode.window.withProgress< + InitWorkflowResult | undefined + >( { location: vscode.ProgressLocation.Notification, - title: t("vscode.init.initializing_project", { projectName }), + title: initializingTitle, cancellable: false, }, - async (progress: ProgressCallback) => { + async ( + progress: vscode.Progress<{ message?: string; increment?: number }>, + ) => { progress.report({ - increment: 0, - message: t("vscode.init.setting_up_structure"), + message: this.safeTranslate( + "vscode.init.setting_up_structure", + "Setting up project structure...", + ), }); - const command = `npx @stackcode/cli init --name="${projectName}" --description="${description}" --author="${authorName}" --stack="${stack.label}" --path="${projectPath}"`; + const hooks = this.createWorkflowHooks(progress); + return runInitWorkflow(workflowOptions, hooks); + }, + ); - progress.report({ - increment: 50, - message: t("vscode.init.running_stackcode_cli"), - }); + if (!workflowResult) { + return; + } + + if (workflowResult.status === "cancelled") { + await vscode.window.showWarningMessage( + this.safeTranslate( + "common.operation_cancelled", + "Operation cancelled.", + ), + ); + return; + } - await this.runTerminalCommand(command); + if ( + !workflowResult.dependenciesInstalled && + workflowResult.installCommand + ) { + const installCommandString = + `${workflowResult.installCommand.command} ${workflowResult.installCommand.args.join(" ")}`.trim(); + const lastWarning = + workflowResult.warnings.at(-1) ?? + this.safeTranslate( + "init.error.deps_install_unknown", + "Unknown error", + ); + const failureMessage = this.safeTranslate( + "init.error.deps_install_failed", + `Failed to install dependencies: ${lastWarning}`, + { error: lastWarning }, + ); + const manualMessage = this.safeTranslate( + "init.error.deps_install_manual", + "Please run the install command manually:", + ); + await vscode.window.showWarningMessage( + `${failureMessage}\n${manualMessage}\n${installCommandString}`, + ); + } - progress.report({ - increment: 100, - message: t("vscode.init.project_initialized_successfully"), - }); - }, + const openProjectLabel = this.safeTranslate( + "vscode.init.open_project", + "Open Project", + ); + const laterLabel = this.safeTranslate("vscode.init.later", "Later"); + const successMessage = this.safeTranslate( + "vscode.init.project_created_successfully", + `Project ${projectName} created successfully!`, + { projectName }, ); const openProject = await vscode.window.showInformationMessage( - t("vscode.init.project_created_successfully", { projectName }), - t("vscode.init.open_project"), - t("vscode.init.later"), + successMessage, + openProjectLabel, + laterLabel, ); - if (openProject === t("vscode.init.open_project")) { + if (openProject === openProjectLabel) { const uri = vscode.Uri.file(projectPath); await vscode.commands.executeCommand("vscode.openFolder", uri, true); } @@ -141,6 +289,168 @@ export class InitCommand extends BaseCommand { } } + /** + * Creates workflow hooks wired to VS Code progress feedback and dialogs. + * @param progress - The VS Code progress reporter. + * @returns Configured hooks for the init workflow. + */ + private createWorkflowHooks( + progress: vscode.Progress<{ message?: string; increment?: number }>, + ): InitWorkflowHooks { + const stepMessage = (step: InitWorkflowStep): string | undefined => { + switch (step) { + case "scaffold": + return this.safeTranslate( + "init.step.scaffold", + "Scaffolding project...", + ); + case "saveConfig": + return this.safeTranslate( + "vscode.init.step.save_config", + "Saving StackCode configuration...", + ); + case "generateReadme": + return this.safeTranslate( + "init.step.readme", + "Generating README.md...", + ); + case "generateGitignore": + return this.safeTranslate( + "init.step.gitignore", + "Creating .gitignore...", + ); + case "setupHusky": + return this.safeTranslate( + "init.step.husky", + "Configuring Husky hooks...", + ); + case "initializeGit": + return this.safeTranslate( + "init.step.git", + "Initializing Git repository...", + ); + case "validateDependencies": + return this.safeTranslate( + "init.step.validate_deps", + "Validating local dependencies...", + ); + case "installDependencies": + return this.safeTranslate( + "init.step.deps", + "Installing project dependencies...", + ); + case "completed": + return this.safeTranslate( + "vscode.init.project_initialized_successfully", + "Project initialized successfully!", + ); + default: + return undefined; + } + }; + + return { + onProgress: async ({ step }: InitWorkflowProgress) => { + const message = stepMessage(step); + if (message) { + progress.report({ message }); + } + }, + onEducationalMessage: async (messageKey: string) => { + const message = this.safeTranslate(messageKey, messageKey); + progress.report({ message }); + }, + onMissingDependencies: async ( + details: InitWorkflowDependencyDecision, + ) => { + await vscode.window.showWarningMessage( + this.formatMissingDependenciesMessage(details), + ); + }, + confirmContinueAfterMissingDependencies: async () => { + const continueLabel = this.safeTranslate("common.continue", "Continue"); + const cancelLabel = this.safeTranslate("common.cancel", "Cancel"); + const choice = await vscode.window.showWarningMessage( + this.safeTranslate( + "init.dependencies.prompt_continue", + "Continue even if some dependencies are missing?", + ), + { modal: true }, + continueLabel, + cancelLabel, + ); + return choice === continueLabel; + }, + }; + } + + /** + * Formats a readable warning message listing missing dependencies. + * @param details - The dependency validation outcome. + * @returns Formatted multi-line warning string. + */ + private formatMissingDependenciesMessage( + details: InitWorkflowDependencyDecision, + ): string { + const header = this.safeTranslate( + "init.dependencies.missing", + `Missing dependencies for ${details.stack}`, + { stack: details.stack }, + ); + const missingLines = details.missingDependencies.map((dependency: string) => + this.safeTranslate( + "init.dependencies.missing_detail", + ` - ${dependency}`, + { command: dependency }, + ), + ); + const instructionHeader = this.safeTranslate( + "init.dependencies.install_instructions", + "Install the following dependencies:", + ); + const installLines = details.missingDependencies.map((dependency: string) => + this.safeTranslate( + `init.dependencies.install_${dependency}`, + ` - ${dependency}`, + ), + ); + const optionalWarning = this.safeTranslate( + "init.dependencies.optional_skip", + "You can skip for now, but remember to install them later.", + ); + + return [ + header, + ...missingLines, + "", + instructionHeader, + ...installLines, + "", + optionalWarning, + ] + .filter((line) => line.length > 0) + .join("\n"); + } + + /** + * Attempts to translate a key, falling back to a default string when missing. + * @param key - Translation key to resolve. + * @param fallback - Fallback string when key is missing. + * @param variables - Optional translation variables. + * @returns Resolved translation or fallback. + */ + private safeTranslate( + key: string, + fallback: string, + variables?: Record, + ): string { + try { + return variables ? t(key, variables) : t(key); + } catch { + return fallback; + } + } + private async getGitUserName(): Promise { try { const terminal = vscode.window.createTerminal({ name: "temp" }); diff --git a/packages/vscode-extension/src/commands/ReleaseCommand.ts b/packages/vscode-extension/src/commands/ReleaseCommand.ts index b460badb..3bbf018c 100644 --- a/packages/vscode-extension/src/commands/ReleaseCommand.ts +++ b/packages/vscode-extension/src/commands/ReleaseCommand.ts @@ -1,59 +1,345 @@ import * as vscode from "vscode"; -import { BaseCommand } from "./BaseCommand"; -import { ProgressCallback } from "../types"; +import { + runReleaseWorkflow, + type ReleaseWorkflowHooks, + type ReleaseWorkflowProgress, + type ReleaseWorkflowResult, + type ReleaseWorkflowStep, + type ReleaseWorkflowGitHubInfo, + type PackageBumpInfo, + getCommandOutput, +} from "@stackcode/core"; import { t } from "@stackcode/i18n"; +import { BaseCommand } from "./BaseCommand"; +import { GitHubAuthService } from "../services/GitHubAuthService"; +import { ProgressManager } from "../services/ProgressManager"; +/** + * Handles monorepo release workflow in VS Code. + * Supports strategy detection, version management, and GitHub release creation. + */ export class ReleaseCommand extends BaseCommand { - async execute(): Promise { + private readonly authService: GitHubAuthService; + private readonly progressManager: ProgressManager; + private outputChannel?: vscode.OutputChannel; + + constructor( + authService: GitHubAuthService, + progressManager: ProgressManager, + ) { + super(); + this.authService = authService; + this.progressManager = progressManager; + } + + public async execute(): Promise { try { const workspaceFolder = this.getCurrentWorkspaceFolder(); if (!workspaceFolder) { - this.showError(t("vscode.common.no_workspace_folder")); + await this.showError(t("vscode.common.no_workspace_folder")); return; } - const confirm = await this.confirmAction( + const shouldProceed = await this.confirmAction( t("vscode.release.are_you_sure_create_release"), t("vscode.release.create_release"), + t("common.cancel"), ); - - if (!confirm) { + if (!shouldProceed) { return; } - vscode.window.withProgress( + const cwd = workspaceFolder.uri.fsPath; + + this.progressManager.startWorkflow("release"); + + const result = await vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, title: t("vscode.release.creating_release"), cancellable: false, }, - async (progress: ProgressCallback) => { - progress.report({ - increment: 0, - message: t("vscode.release.preparing_release"), - }); + async (progress) => { + this.progressManager.setVSCodeProgressReporter(progress); + const hooks = this.buildReleaseHooks(progress, cwd); + return runReleaseWorkflow({ cwd }, hooks); + }, + ); + + this.progressManager.clearVSCodeProgressReporter(); + + if (result.status === "prepared") { + this.progressManager.completeWorkflow( + "release", + "Release prepared successfully", + ); + } else { + this.progressManager.failWorkflow( + "release", + result.error || "Release workflow cancelled", + ); + } + + await this.handleReleaseResult(result, cwd); + } catch (error) { + await this.showError( + `${t("common.error_generic")} ${ + error instanceof Error ? error.message : String(error) + }`, + ); + } + } - const command = `npx @stackcode/cli release`; + /** + * Builds workflow hooks translating progress events into VS Code feedback. + */ + private buildReleaseHooks( + progress: vscode.Progress<{ message?: string }>, + cwd: string, + ): ReleaseWorkflowHooks { + return { + onProgress: (workflowProgress) => { + this.reportReleaseProgress(workflowProgress, progress); + this.progressManager.reportProgress( + "release", + workflowProgress.step, + workflowProgress.message, + ); + }, + confirmLockedRelease: ({ currentVersion, newVersion }) => + this.confirmAction( + t("release.prompt_confirm_release", { + currentVersion, + newVersion, + }), + t("common.continue"), + t("common.cancel"), + ), + displayIndependentPlan: (plan) => this.displayIndependentPlan(plan, cwd), + confirmIndependentRelease: () => + this.confirmAction( + t("release.independent_prompt_confirm"), + t("common.continue"), + t("common.cancel"), + ), + }; + } - progress.report({ - increment: 50, - message: t("vscode.release.creating_release_message"), - }); + /** + * Handles the final result returned by the release workflow. + */ + private async handleReleaseResult( + result: ReleaseWorkflowResult, + cwd: string, + ): Promise { + if (result.status === "cancelled") { + await this.handleCancelledRelease(result); + return; + } - await this.runTerminalCommand(command, workspaceFolder.uri.fsPath); + const channel = this.ensureOutputChannel(); + channel.appendLine(t("release.workflow_completed")); - progress.report({ - increment: 100, - message: t("vscode.release.release_created"), - }); - }, + if (result.strategy === "locked") { + await this.showSuccess(t("release.success_ready_to_commit")); + await this.showInfo(t("release.next_steps_commit")); + } else if (result.strategy === "independent") { + await this.showSuccess(t("release.independent_success")); + await this.showInfo(t("release.next_steps_push")); + } + + if (result.releaseNotes) { + channel.appendLine("―".repeat(60)); + channel.appendLine(result.releaseNotes); + channel.show(true); + } + + if (result.tagName && result.releaseNotes) { + await this.promptForGitHubRelease({ + tagName: result.tagName, + releaseNotes: result.releaseNotes, + cwd, + githubInfo: result.github, + }); + } + } + + /** + * Handles workflow cancellations by surfacing the appropriate message. + */ + private async handleCancelledRelease( + result: ReleaseWorkflowResult, + ): Promise { + switch (result.reason) { + case "invalid-structure": + await this.showError(t("release.error_structure")); + break; + case "no-changes": + await this.showSuccess(t("release.independent_mode_no_changes")); + break; + case "no-bumps": + await this.showWarning(t("release.independent_mode_no_bumps")); + break; + case "cancelled-by-user": + await this.showWarning(t("common.operation_cancelled")); + break; + default: + await this.showError(result.error ?? t("common.error_generic")); + } + } + + /** + * Reports release workflow progress to the notification UI and output channel. + */ + private reportReleaseProgress( + progress: ReleaseWorkflowProgress, + notification: vscode.Progress<{ message?: string }>, + ): void { + const messages: Partial> = { + detectingStrategy: t("release.step_detecting_strategy"), + lockedRecommendedBump: t("release.step_calculating_bump"), + lockedUpdatingVersions: t("release.step_updating_versions"), + lockedGeneratingChangelog: t("release.step_generating_changelog"), + independentFindingChanges: t("release.independent_mode_start"), + independentDeterminingBumps: t("release.step_determining_bumps"), + independentPreparingPlan: t("release.independent_mode_preparing_plan"), + independentUpdatingPackages: t("release.step_updating_version"), + independentCommitting: t("release.step_committing_and_tagging"), + completed: t("release.step_completed"), + }; + + const message = messages[progress.step]; + if (message) { + notification.report({ message }); + this.ensureOutputChannel().appendLine(message); + } + } + + /** + * Displays the independent release plan in the output channel. + */ + private async displayIndependentPlan( + plan: PackageBumpInfo[], + cwd: string, + ): Promise { + const channel = this.ensureOutputChannel(); + channel.show(true); + channel.appendLine("―".repeat(60)); + channel.appendLine(t("release.independent_mode_packages_to_update")); + plan.forEach((pkg) => { + channel.appendLine( + t("release.independent_plan_entry", { + package: pkg.pkg.name, + currentVersion: pkg.pkg.version ?? "?", + newVersion: pkg.newVersion, + bumpType: pkg.bumpType, + }), + ); + }); + channel.appendLine(""); + channel.appendLine(`cwd: ${cwd}`); + } + + /** + * Prompts the user to create a GitHub release using the authenticated session. + */ + private async promptForGitHubRelease(params: { + tagName: string; + releaseNotes: string; + cwd: string; + githubInfo?: ReleaseWorkflowGitHubInfo; + }): Promise { + const choice = await vscode.window.showInformationMessage( + t("release.prompt_create_github_release"), + t("common.yes"), + t("common.no"), + ); + + if (choice !== t("common.yes")) { + return; + } + + try { + await this.ensureAuthenticated(); + const client = await this.authService.getAuthenticatedClient(); + const { owner, repo } = await this.resolveRepositoryInfo( + params.cwd, + params.githubInfo, ); - this.showSuccess(t("vscode.release.release_process_started")); + await client.repos.createRelease({ + owner, + repo, + tag_name: params.tagName, + name: `Release ${params.tagName}`, + body: params.releaseNotes, + prerelease: false, + }); + + await this.showSuccess(t("release.success_github_release_created")); } catch (error) { - this.showError( - t("vscode.release.failed_create_release", { error: String(error) }), + await this.showError( + `${t("common.error_generic")} ${ + error instanceof Error ? error.message : String(error) + }`, ); } } + + /** + * Ensures the user is authenticated with GitHub, prompting login when required. + */ + private async ensureAuthenticated(): Promise { + if (this.authService.isAuthenticated) { + return; + } + + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: t("github.auth.login"), + cancellable: false, + }, + async () => { + await this.authService.login(); + }, + ); + } + + /** + * Resolves repository owner and name from workflow data or git remotes. + */ + private async resolveRepositoryInfo( + cwd: string, + info?: ReleaseWorkflowGitHubInfo, + ): Promise<{ owner: string; repo: string }> { + if (info?.owner && info?.repo) { + return { owner: info.owner, repo: info.repo }; + } + + const remoteUrl = await getCommandOutput( + "git", + ["remote", "get-url", "origin"], + { + cwd, + }, + ); + const match = remoteUrl.match(/github\.com[/:]([\w-]+)\/([\w-.]+)/); + if (!match) { + throw new Error(t("git.error_parsing_remote")); + } + + return { owner: match[1], repo: match[2].replace(/\.git$/, "") }; + } + + /** + * Lazily creates the release output channel. + */ + private ensureOutputChannel(): vscode.OutputChannel { + if (!this.outputChannel) { + this.outputChannel = + vscode.window.createOutputChannel("StackCode Release"); + } + return this.outputChannel; + } } diff --git a/packages/vscode-extension/src/commands/TestGitHubDetectionCommand.ts b/packages/vscode-extension/src/commands/TestGitHubDetectionCommand.ts index 76369fb4..26b05399 100644 --- a/packages/vscode-extension/src/commands/TestGitHubDetectionCommand.ts +++ b/packages/vscode-extension/src/commands/TestGitHubDetectionCommand.ts @@ -4,7 +4,7 @@ import { ProactiveNotificationManager } from "../notifications/ProactiveNotifica import { ConfigurationManager } from "../config/ConfigurationManager"; /** - * Comando de teste para verificar detecção de repositório GitHub + * Test command to verify GitHub repository detection */ export class TestGitHubDetectionCommand { private gitMonitor: GitMonitor; diff --git a/packages/vscode-extension/src/commands/ValidateCommand.ts b/packages/vscode-extension/src/commands/ValidateCommand.ts index 1133bdae..6252c986 100644 --- a/packages/vscode-extension/src/commands/ValidateCommand.ts +++ b/packages/vscode-extension/src/commands/ValidateCommand.ts @@ -1,7 +1,10 @@ import * as vscode from "vscode"; import { BaseCommand } from "./BaseCommand"; -import { ProgressCallback } from "../types"; import { t } from "@stackcode/i18n"; +import { + runProjectValidateWorkflow, + type ProjectValidateIssue, +} from "@stackcode/core"; export class ValidateCommand extends BaseCommand { async execute(): Promise { @@ -12,27 +15,35 @@ export class ValidateCommand extends BaseCommand { return; } - vscode.window.withProgress( + let resultIssues: ProjectValidateIssue[] = []; + + await vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, title: t("vscode.validate.validating_project_structure"), cancellable: false, }, - async (progress: ProgressCallback) => { + async ( + progress: vscode.Progress<{ message?: string; increment?: number }>, + ) => { progress.report({ increment: 0, message: t("vscode.validate.running_validation"), }); - - const command = `npx @stackcode/cli validate`; - - progress.report({ - increment: 50, - message: t("vscode.validate.checking_project_structure"), - }); - - await this.runTerminalCommand(command, workspaceFolder.uri.fsPath); - + const res = await runProjectValidateWorkflow( + { projectPath: workspaceFolder.uri.fsPath }, + { + onProgress: (p) => { + if (p.step === "checkingFiles") { + progress.report({ + increment: 50, + message: t("vscode.validate.checking_project_structure"), + }); + } + }, + }, + ); + resultIssues = res.issues; progress.report({ increment: 100, message: t("vscode.validate.validation_completed"), @@ -40,7 +51,81 @@ export class ValidateCommand extends BaseCommand { }, ); - this.showSuccess(t("vscode.validate.project_validation_completed")); + if (!resultIssues.length) { + await this.showSuccess( + t("vscode.validate.project_validation_completed"), + ); + return; + } + + const summary = resultIssues + .map((i) => `• ${t(i.messageKey)}`) + .join("\n"); + await this.showWarning( + t("vscode.validate.issues_summary", { + count: String(resultIssues.length), + }) + + "\n" + + summary, + ); + + const missingFiles: string[] = []; + const hasMissingReadme = resultIssues.some( + (i) => i.id === "missing-readme", + ); + const hasMissingGitignore = resultIssues.some( + (i) => i.id === "missing-gitignore", + ); + + if (hasMissingReadme) missingFiles.push("README.md"); + if (hasMissingGitignore) missingFiles.push(".gitignore"); + + if (missingFiles.length > 0) { + const action = await vscode.window.showInformationMessage( + t("vscode.common.project_missing_files", { + missingFiles: missingFiles.join(", "), + }), + t("vscode.common.generate_files"), + t("vscode.common.not_now"), + ); + if (action === t("vscode.common.generate_files")) { + if (hasMissingReadme) { + await vscode.commands.executeCommand("stackcode.generate.readme"); + } + if (hasMissingGitignore) { + await vscode.commands.executeCommand( + "stackcode.generate.gitignore", + ); + } + } + } + } catch (error) { + this.showError( + t("vscode.validate.failed_validate_project", { error: String(error) }), + ); + } + } + + async validateCommitMessage(): Promise { + try { + const message = await vscode.window.showInputBox({ + prompt: t("vscode.validate.enter_commit_message"), + placeHolder: "feat: add new feature", + validateInput: (value: string) => + !value ? t("ui.short_description_required") : null, + }); + + if (!message) return; + + const { isValid } = await import("@stackcode/core").then((m) => + m.runValidateWorkflow({ message }), + ); + + if (isValid) { + await this.showSuccess(t("validate.success")); + } else { + await this.showWarning(t("validate.error_invalid")); + } } catch (error) { this.showError( t("vscode.validate.failed_validate_project", { error: String(error) }), diff --git a/packages/vscode-extension/src/config/ConfigurationManager.ts b/packages/vscode-extension/src/config/ConfigurationManager.ts index 85b253cf..2e35277c 100644 --- a/packages/vscode-extension/src/config/ConfigurationManager.ts +++ b/packages/vscode-extension/src/config/ConfigurationManager.ts @@ -6,7 +6,6 @@ export class ConfigurationManager { constructor() { this.configuration = vscode.workspace.getConfiguration("stackcode"); - // Listen for configuration changes vscode.workspace.onDidChangeConfiguration( (event: vscode.ConfigurationChangeEvent) => { if (event.affectsConfiguration("stackcode")) { diff --git a/packages/vscode-extension/src/extension.ts b/packages/vscode-extension/src/extension.ts index f46af22b..647340c2 100644 --- a/packages/vscode-extension/src/extension.ts +++ b/packages/vscode-extension/src/extension.ts @@ -15,7 +15,7 @@ import { TestGitHubDetectionCommand } from "./commands/TestGitHubDetectionComman import { DashboardProvider } from "./providers/DashboardProvider"; import { ProjectViewProvider } from "./providers/ProjectViewProvider"; import { GitHubAuthService } from "./services/GitHubAuthService"; -import { GitHubIssuesService } from "./services/GitHubIssuesService"; +import { ProgressManager } from "./services/ProgressManager"; let proactiveManager: ProactiveNotificationManager; let gitMonitor: GitMonitor; @@ -24,9 +24,7 @@ let configManager: ConfigurationManager; let dashboardProvider: DashboardProvider; let projectViewProvider: ProjectViewProvider; let gitHubAuthService: GitHubAuthService; -let gitHubIssuesService: GitHubIssuesService; - -// Command instances +let progressManager: ProgressManager; let initCommand: InitCommand; let generateCommand: GenerateCommand; let gitCommand: GitCommand; @@ -36,51 +34,38 @@ let releaseCommand: ReleaseCommand; let configCommand: ConfigCommand; let authCommand: AuthCommand; +/** + * Activates the StackCode VS Code extension. + * Initializes all services, monitors, providers, and registers commands. + */ export async function activate(context: vscode.ExtensionContext) { - console.log("🚀 [StackCode] Extension activation started!"); - console.log("🚀 [StackCode] Extension is now active!"); - console.log("🚀 [StackCode] Extension activation started..."); - console.log( - "🚀 [StackCode] Workspace folders:", - vscode.workspace.workspaceFolders?.length || 0, - ); - console.log("🚀 [StackCode] Extension path:", context.extensionPath); + console.log("🚀 [StackCode] Extension activating..."); - // Initialize configuration manager configManager = new ConfigurationManager(); - - // Initialize GitHub authentication service gitHubAuthService = new GitHubAuthService(context); - - // Initialize notification manager + progressManager = new ProgressManager(); proactiveManager = new ProactiveNotificationManager(configManager); - - // Initialize monitors FIRST (dependencies for other services) gitMonitor = new GitMonitor(proactiveManager, configManager); fileMonitor = new FileMonitor(proactiveManager, configManager); - - // Initialize GitHub issues service (depends on gitMonitor) - gitHubIssuesService = new GitHubIssuesService(gitHubAuthService, gitMonitor); - - // Initialize providers (after services are ready) dashboardProvider = new DashboardProvider( context, - gitHubIssuesService, gitHubAuthService, + gitMonitor, + progressManager, ); projectViewProvider = new ProjectViewProvider(context.workspaceState); - - // Initialize commands initCommand = new InitCommand(); generateCommand = new GenerateCommand(); gitCommand = new GitCommand(); - commitCommand = new CommitCommand(); + commitCommand = new CommitCommand( + gitHubAuthService, + gitMonitor, + progressManager, + ); validateCommand = new ValidateCommand(); - releaseCommand = new ReleaseCommand(); + releaseCommand = new ReleaseCommand(gitHubAuthService, progressManager); configCommand = new ConfigCommand(); authCommand = new AuthCommand(gitHubAuthService); - - // Register webview providers context.subscriptions.push( vscode.window.registerWebviewViewProvider( "stackcode.dashboard", @@ -92,12 +77,13 @@ export async function activate(context: vscode.ExtensionContext) { ), ); - // Register all commands const commands = [ - // Core functionality commands vscode.commands.registerCommand("stackcode.init", () => initCommand.execute(), ), + vscode.commands.registerCommand("stackcode.validate.commit", () => + validateCommand.validateCommitMessage(), + ), vscode.commands.registerCommand("stackcode.generate.readme", () => generateCommand.generateReadme(), ), @@ -125,29 +111,15 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand("stackcode.dashboard", () => dashboardProvider.show(), ), - vscode.commands.registerCommand("stackcode.auth.login", () => { - console.log("🔐 [StackCode] AUTH LOGIN command executed!"); - vscode.window.showInformationMessage( - "🔐 StackCode: Executando login GitHub...", - ); - return authCommand.executeLogin(); - }), - vscode.commands.registerCommand("stackcode.auth.logout", () => { - console.log("🔓 [StackCode] AUTH LOGOUT command executed!"); - vscode.window.showInformationMessage( - "🔓 StackCode: Executando logout GitHub...", - ); - return authCommand.executeLogout(); - }), - - // Test commands (development only) - vscode.commands.registerCommand("stackcode.test.github.detection", () => { - console.log("🧪 [StackCode] TEST GITHUB DETECTION command executed!"); - const testCommand = new TestGitHubDetectionCommand(); - return testCommand.execute(); - }), - - // Legacy commands for backward compatibility + vscode.commands.registerCommand("stackcode.auth.login", () => + authCommand.executeLogin(), + ), + vscode.commands.registerCommand("stackcode.auth.logout", () => + authCommand.executeLogout(), + ), + vscode.commands.registerCommand("stackcode.test.github.detection", () => + new TestGitHubDetectionCommand().execute(), + ), vscode.commands.registerCommand("stackcode.createBranch", () => gitCommand.startBranch(), ), @@ -157,17 +129,10 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand("stackcode.checkBestPractices", () => validateCommand.execute(), ), - - // Project view commands vscode.commands.registerCommand("stackcode.projectView.refresh", () => projectViewProvider.refresh(), ), - - // Webview commands - vscode.commands.registerCommand("webviewReady", () => { - console.log("[StackCode] Webview is ready!"); - // Pode enviar dados iniciais aqui se necessário - }), + vscode.commands.registerCommand("webviewReady", () => {}), vscode.commands.registerCommand("stackcode.webview.init", () => initCommand.execute(), ), @@ -189,7 +154,6 @@ export async function activate(context: vscode.ExtensionContext) { ), ]; - // Add all to context subscriptions for cleanup context.subscriptions.push( ...commands, gitMonitor, @@ -197,33 +161,29 @@ export async function activate(context: vscode.ExtensionContext) { proactiveManager, dashboardProvider, gitHubAuthService, + progressManager, ); - console.log("📋 [StackCode] Commands registered:", commands.length); - console.log("🔐 [StackCode] Auth commands should be available now"); - console.log( - "🎯 [StackCode] Available commands: stackcode.auth.login, stackcode.auth.logout, stackcode.dashboard", - ); - - // Initialize GitHub authentication await gitHubAuthService.initializeFromStorage(); - - // Start monitoring gitMonitor.startMonitoring(); fileMonitor.startMonitoring(); - // Auto-open dashboard if configured if (configManager.dashboardAutoOpen) { - setTimeout(() => { - dashboardProvider.show(); - }, 1000); + setTimeout(() => dashboardProvider.show(), 1000); } - // Show welcome message proactiveManager.showWelcomeMessage(); + console.log("✅ [StackCode] Extension activated successfully"); } +/** + * Deactivates the extension and cleans up resources. + */ + export function deactivate() { + if (progressManager) { + progressManager.dispose(); + } if (gitMonitor) { gitMonitor.dispose(); } diff --git a/packages/vscode-extension/src/monitors/GitMonitor.ts b/packages/vscode-extension/src/monitors/GitMonitor.ts index 7a59106e..84f31dc6 100644 --- a/packages/vscode-extension/src/monitors/GitMonitor.ts +++ b/packages/vscode-extension/src/monitors/GitMonitor.ts @@ -214,7 +214,7 @@ export class GitMonitor implements vscode.Disposable { } /** - * Detecta o repositório GitHub atual usando múltiplas estratégias + * Detects current GitHub repository using multiple strategies */ public async getCurrentGitHubRepository(): Promise { try { @@ -250,7 +250,7 @@ export class GitMonitor implements vscode.Disposable { } /** - * Estratégia 1: Lê repositório diretamente do .git/config + * Strategy 1: Reads repository directly from .git/config */ private async getRepositoryFromGitConfig(): Promise { try { @@ -307,7 +307,7 @@ export class GitMonitor implements vscode.Disposable { return null; } } /** - * Estratégia 2: Via Git Extension API (método original como fallback) + * Strategy 2: Via Git Extension API (original method as fallback) */ private async getRepositoryFromGitAPI(): Promise { try { diff --git a/packages/vscode-extension/src/notifications/ProactiveNotificationManager.ts b/packages/vscode-extension/src/notifications/ProactiveNotificationManager.ts index f21a7162..2cba8622 100644 --- a/packages/vscode-extension/src/notifications/ProactiveNotificationManager.ts +++ b/packages/vscode-extension/src/notifications/ProactiveNotificationManager.ts @@ -129,7 +129,6 @@ export class ProactiveNotificationManager { async runFullBestPracticesCheck(): Promise { const issues: string[] = []; - // Check if working on main branch try { const gitExtension = vscode.extensions.getExtension("vscode.git")?.exports; @@ -143,11 +142,9 @@ export class ProactiveNotificationManager { } } } catch (error: unknown) { - // Git extension not available or error accessing it console.log("Git extension error:", error); } - // Check for missing files const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; if (workspaceFolder) { const files = await vscode.workspace.fs.readDirectory( @@ -180,7 +177,6 @@ export class ProactiveNotificationManager { } private async handleApplyFix(message: string): Promise { - // Enhanced fix handling with specific actions if (message.includes("README")) { await vscode.commands.executeCommand("stackcode.generate.readme"); } else if (message.includes("gitignore")) { @@ -258,7 +254,5 @@ export class ProactiveNotificationManager { outputChannel.show(); } - dispose(): void { - // Cleanup if needed - } + dispose(): void {} } diff --git a/packages/vscode-extension/src/providers/DashboardProvider.backup.ts b/packages/vscode-extension/src/providers/DashboardProvider.backup.ts deleted file mode 100644 index 039f3635..00000000 --- a/packages/vscode-extension/src/providers/DashboardProvider.backup.ts +++ /dev/null @@ -1,313 +0,0 @@ -import * as vscode from "vscode"; -import * as path from "path"; -import * as fs from "fs"; - -export class DashboardProvider - implements vscode.WebviewViewProvider, vscode.Disposable -{ - public static readonly viewType = "stackcode.dashboard"; - private _view?: vscode.WebviewView; - private readonly _extensionUri: vscode.Uri; - private _disposables: vscode.Disposable[] = []; - - constructor(context: vscode.ExtensionContext) { - this._extensionUri = context.extensionUri; - } - - public resolveWebviewView( - webviewView: vscode.WebviewView, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - context: vscode.WebviewViewResolveContext, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - token: vscode.CancellationToken, - ) { - this._view = webviewView; - - webviewView.webview.options = { - enableScripts: true, - localResourceRoots: [vscode.Uri.joinPath(this._extensionUri, "dist")], - }; - - webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); - - webviewView.webview.onDidReceiveMessage( - async (data: { type: string; payload?: unknown }) => { - console.log(`[StackCode] Received command from webview: ${data.type}`); - - try { - // Tratar comandos específicos do webview - switch (data.type) { - case "webviewReady": - console.log( - "[StackCode] Webview reported ready, sending initial data", - ); - this.updateProjectStats(); - return; - - case "refreshStats": - this.updateProjectStats(); - return; - - default: - // Executar comando normal do VS Code - await vscode.commands.executeCommand(data.type, data.payload); - } - } catch (error) { - console.error( - `[StackCode] Error executing command ${data.type}:`, - error, - ); - this.sendMessage({ - type: "commandError", - payload: { - command: data.type, - error: error instanceof Error ? error.message : "Unknown error", - }, - }); - } - }, - undefined, - this._disposables, - ); - - // WebviewView doesn't have onDidBecomeVisible, so we'll update stats immediately - this.updateProjectStats(); - } - - public sendMessage(message: { type: string; payload?: unknown }) { - if (this._view) { - this._view.webview.postMessage(message); - } - } - - public show() { - if (this._view) { - this._view.show?.(true); - } else { - // If view is not created yet, trigger the creation by executing the show command - vscode.commands.executeCommand("workbench.view.extension.stackcode"); - } - } - - private async updateProjectStats() { - if (!this._view) { - console.log("[StackCode] No view available for stats update"); - return; - } - - const workspaceFolders = vscode.workspace.workspaceFolders; - console.log( - "[StackCode] Workspace folders:", - workspaceFolders?.length || 0, - ); - console.log("[StackCode] Workspace name:", vscode.workspace.name); - console.log( - "[StackCode] Workspace file:", - vscode.workspace.workspaceFile?.toString(), - ); - - if (!workspaceFolders || workspaceFolders.length === 0) { - console.log( - "[StackCode] No workspace folders found, using alternative detection", - ); - - // Fallback: usar informações do contexto da extensão - const extensionWorkspace = path.dirname( - path.dirname(path.dirname(this._extensionUri.fsPath)), - ); - console.log("[StackCode] Extension workspace path:", extensionWorkspace); - - this.sendMessage({ - type: "updateStats", - payload: { - files: 0, - workspaceName: "StackCode (Debug)", - workspacePath: extensionWorkspace, - mode: "development", - }, - }); - return; - } - - try { - const files = await vscode.workspace.findFiles( - "**/*", - "**/node_modules/**", - 1000, - ); - console.log("[StackCode] Found files:", files.length); - - this.sendMessage({ - type: "updateStats", - payload: { - files: files.length, - workspaceName: workspaceFolders[0].name, - workspacePath: workspaceFolders[0].uri.fsPath, - mode: "production", - }, - }); - } catch (e) { - console.error("[StackCode] Error fetching project stats:", e); - this.sendMessage({ - type: "updateStats", - payload: { files: 0, error: "Failed to scan files" }, - }); - } - } - - private _getHtmlForWebview(webview: vscode.Webview): string { - const nonce = getNonce(); - const buildPath = vscode.Uri.joinPath( - this._extensionUri, - "dist", - "webview-ui", - ); - - // Lê o manifest.json do Vite - const manifestPath = path.join(buildPath.fsPath, ".vite", "manifest.json"); - - console.log("[StackCode] Build path:", buildPath.fsPath); - console.log("[StackCode] Manifest path:", manifestPath); - console.log("[StackCode] Manifest exists:", fs.existsSync(manifestPath)); - - try { - const manifestContent = fs.readFileSync(manifestPath, "utf-8"); - const manifest = JSON.parse(manifestContent); - console.log("[StackCode] Manifest content:", manifest); - - // Pega os arquivos do manifest do Vite - const indexEntry = manifest["index.html"]; - const scriptFile = indexEntry.file; - const cssFiles = indexEntry.css || []; - - const scriptUri = webview.asWebviewUri( - vscode.Uri.joinPath(buildPath, scriptFile), - ); - const cssUris = cssFiles.map((cssFile: string) => - webview.asWebviewUri(vscode.Uri.joinPath(buildPath, cssFile)), - ); - - console.log("[StackCode] Script URI:", scriptUri.toString()); - console.log( - "[StackCode] CSS URIs:", - cssUris.map((uri: vscode.Uri) => uri.toString()), - ); - - return ` - - - - - - ${cssUris.map((uri: vscode.Uri) => ``).join("\n ")} - StackCode Dashboard - - -
- ⏳ Carregando StackCode Dashboard... -
-
- - - -`; - } catch (error) { - console.error("[StackCode] Error reading manifest:", error); - // Fallback melhorado para desenvolvimento - return ` - - - - - StackCode Dashboard - - - -
-
Development Mode
-

🏗️ StackCode Dashboard

-
- Build Required: O webview-ui precisa ser compilado primeiro. -

- Execute: npm run build:ui -

- Erro: ${error instanceof Error ? error.message : "Manifest não encontrado"} -
-

Status da extensão: ✅ Ativa

-

Workspace: ${vscode.workspace.workspaceFolders?.[0]?.name || "Nenhum"}

-
- -`; - } - } - - // CORREÇÃO: Adicionando o método dispose para conformidade. - public dispose() { - while (this._disposables.length) { - const x = this._disposables.pop(); - if (x) { - x.dispose(); - } - } - } -} - -function getNonce() { - let text = ""; - const possible = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - for (let i = 0; i < 32; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; -} diff --git a/packages/vscode-extension/src/providers/DashboardProvider.ts b/packages/vscode-extension/src/providers/DashboardProvider.ts index fd1af4ff..3d3b2128 100644 --- a/packages/vscode-extension/src/providers/DashboardProvider.ts +++ b/packages/vscode-extension/src/providers/DashboardProvider.ts @@ -1,31 +1,52 @@ import * as vscode from "vscode"; import * as path from "path"; import * as fs from "fs"; -import { GitHubIssuesService } from "../services/GitHubIssuesService"; +import { runIssuesWorkflow, clearRepositoryCache } from "@stackcode/core"; import { GitHubAuthService } from "../services/GitHubAuthService"; +import { GitMonitor } from "../monitors/GitMonitor"; +import { + ProgressManager, + WebviewProgressListener, +} from "../services/ProgressManager"; +import type { + WebviewProgressMessage, + WebviewProgressStateMessage, + WebviewProgressCompleteMessage, +} from "../types/progress-events"; /** * Provides the StackCode dashboard webview interface. - * Manages project statistics, GitHub issues, and integration with various services. + * Manages project statistics, GitHub issues, and integrates with core workflows. + * Implements WebviewProgressListener to receive and display progress updates. */ export class DashboardProvider - implements vscode.WebviewViewProvider, vscode.Disposable + implements + vscode.WebviewViewProvider, + vscode.Disposable, + WebviewProgressListener { public static readonly viewType = "stackcode.dashboard"; private _view?: vscode.WebviewView; private readonly _extensionUri: vscode.Uri; private _disposables: vscode.Disposable[] = []; - private _issuesService?: GitHubIssuesService; private _authService?: GitHubAuthService; + private _gitMonitor?: GitMonitor; + private _progressManager?: ProgressManager; constructor( context: vscode.ExtensionContext, - issuesService?: GitHubIssuesService, authService?: GitHubAuthService, + gitMonitor?: GitMonitor, + progressManager?: ProgressManager, ) { this._extensionUri = context.extensionUri; - this._issuesService = issuesService; this._authService = authService; + this._gitMonitor = gitMonitor; + this._progressManager = progressManager; + + if (this._progressManager) { + this._progressManager.registerWebviewProvider(this); + } } public resolveWebviewView(webviewView: vscode.WebviewView) { @@ -40,41 +61,27 @@ export class DashboardProvider webviewView.webview.onDidReceiveMessage( async (data: { type: string; payload?: unknown }) => { - console.log(`[StackCode] Received command from webview: ${data.type}`); - try { switch (data.type) { case "webviewReady": - console.log( - "[StackCode] Webview reported ready, sending initial data", - ); this.updateProjectStats(); if (this._authService?.isAuthenticated) { await this.updateIssues(); } return; - case "refreshStats": this.updateProjectStats(); return; - case "fetchIssues": await this.updateIssues(); return; - case "refreshIssues": await this.updateIssues(true); return; - default: - // Executar comando normal do VS Code await vscode.commands.executeCommand(data.type, data.payload); } } catch (error) { - console.error( - `[StackCode] Error executing command ${data.type}:`, - error, - ); this.sendMessage({ type: "commandError", payload: { @@ -88,11 +95,16 @@ export class DashboardProvider this._disposables, ); - // WebviewView doesn't have onDidBecomeVisible, so we'll update stats immediately this.updateProjectStats(); } - public sendMessage(message: { type: string; payload?: unknown }) { + public sendMessage( + message: + | { type: string; payload?: unknown } + | WebviewProgressMessage + | WebviewProgressStateMessage + | WebviewProgressCompleteMessage, + ) { if (this._view) { this._view.webview.postMessage(message); } @@ -102,19 +114,16 @@ export class DashboardProvider if (this._view) { this._view.show?.(true); } else { - // If view is not created yet, trigger the creation by executing the show command vscode.commands.executeCommand("workbench.view.extension.stackcode"); } } private async updateIssues(forceRefresh = false): Promise { try { - if (!this._issuesService || !this._authService) { - console.warn("[DashboardProvider] Issues service not available"); + if (!this._authService || !this._gitMonitor) { return; } - // Verificar se está autenticado if (!this._authService.isAuthenticated) { this.sendMessage({ type: "updateIssues", @@ -127,25 +136,81 @@ export class DashboardProvider return; } - console.log("[DashboardProvider] Fetching GitHub issues..."); + const repository = await this._gitMonitor.getCurrentGitHubRepository(); + if (!repository) { + this.sendMessage({ + type: "updateIssues", + payload: { + issues: [], + error: "No GitHub repository detected", + needsAuth: false, + }, + }); + return; + } + + if (forceRefresh) { + clearRepositoryCache({ + owner: repository.owner, + repo: repository.repo, + fullName: repository.fullName, + }); + } + + const client = await this._authService.getAuthenticatedClient(); - const issues = forceRefresh - ? await this._issuesService.refreshIssues() - : await this._issuesService.fetchCurrentRepositoryIssues(); + if (this._progressManager) { + this._progressManager.startWorkflow("issues"); + } + + const result = await runIssuesWorkflow( + { + client, + repository: { + owner: repository.owner, + repo: repository.repo, + fullName: repository.fullName, + }, + enableCache: !forceRefresh, + }, + { + onProgress: this._progressManager + ? this._progressManager.createProgressHook("issues") + : undefined, + }, + ); + + if (result.status === "error") { + if (this._progressManager) { + this._progressManager.failWorkflow( + "issues", + result.error || "Failed to fetch issues", + ); + } + throw new Error(result.error || "Failed to fetch issues"); + } + + if (this._progressManager) { + this._progressManager.completeWorkflow( + "issues", + `Fetched ${result.issues.length} issues`, + ); + } this.sendMessage({ type: "updateIssues", payload: { - issues, - timestamp: new Date().toISOString(), + issues: result.issues, + timestamp: result.timestamp, }, }); - - console.log( - `[DashboardProvider] Sent ${issues.length} issues to webview`, - ); } catch (error) { - console.error("[DashboardProvider] Failed to fetch issues:", error); + if (this._progressManager) { + this._progressManager.failWorkflow( + "issues", + error instanceof Error ? error.message : "Failed to fetch issues", + ); + } this.sendMessage({ type: "updateIssues", @@ -163,31 +228,15 @@ export class DashboardProvider private async updateProjectStats() { if (!this._view) { - console.log("[StackCode] No view available for stats update"); return; } const workspaceFolders = vscode.workspace.workspaceFolders; - console.log( - "[StackCode] Workspace folders:", - workspaceFolders?.length || 0, - ); - console.log("[StackCode] Workspace name:", vscode.workspace.name); - console.log( - "[StackCode] Workspace file:", - vscode.workspace.workspaceFile?.toString(), - ); if (!workspaceFolders || workspaceFolders.length === 0) { - console.log( - "[StackCode] No workspace folders found, using alternative detection", - ); - - // Fallback: usar informações do contexto da extensão const extensionWorkspace = path.dirname( path.dirname(path.dirname(this._extensionUri.fsPath)), ); - console.log("[StackCode] Extension workspace path:", extensionWorkspace); this.sendMessage({ type: "updateStats", @@ -207,7 +256,6 @@ export class DashboardProvider "**/node_modules/**", 1000, ); - console.log("[StackCode] Found files:", files.length); this.sendMessage({ type: "updateStats", @@ -218,8 +266,7 @@ export class DashboardProvider mode: "production", }, }); - } catch (e) { - console.error("[StackCode] Error fetching project stats:", e); + } catch { this.sendMessage({ type: "updateStats", payload: { files: 0, error: "Failed to scan files" }, @@ -235,19 +282,12 @@ export class DashboardProvider "webview-ui", ); - // Lê o manifest.json do Vite const manifestPath = path.join(buildPath.fsPath, ".vite", "manifest.json"); - console.log("[StackCode] Build path:", buildPath.fsPath); - console.log("[StackCode] Manifest path:", manifestPath); - console.log("[StackCode] Manifest exists:", fs.existsSync(manifestPath)); - try { const manifestContent = fs.readFileSync(manifestPath, "utf-8"); const manifest = JSON.parse(manifestContent); - console.log("[StackCode] Manifest content:", manifest); - // Pega os arquivos do manifest do Vite const indexEntry = manifest["index.html"]; const scriptFile = indexEntry.file; const cssFiles = indexEntry.css || []; @@ -259,12 +299,6 @@ export class DashboardProvider webview.asWebviewUri(vscode.Uri.joinPath(buildPath, cssFile)), ); - console.log("[StackCode] Script URI:", scriptUri.toString()); - console.log( - "[StackCode] CSS URIs:", - cssUris.map((uri: vscode.Uri) => uri.toString()), - ); - return ` @@ -280,33 +314,9 @@ export class DashboardProvider
- `; } catch (error) { - console.error("[StackCode] Error reading manifest:", error); - // Fallback melhorado para desenvolvimento return ` @@ -349,22 +359,25 @@ export class DashboardProvider
Development Mode

🏗️ StackCode Dashboard

- Build Required: O webview-ui precisa ser compilado primeiro. + Build Required: The webview-ui needs to be compiled first.

- Execute: npm run build:ui + Run: npm run build:ui

- Erro: ${error instanceof Error ? error.message : "Manifest não encontrado"} + Error: ${error instanceof Error ? error.message : "Manifest not found"}
-

Status da extensão: ✅ Ativa

-

Workspace: ${vscode.workspace.workspaceFolders?.[0]?.name || "Nenhum"}

+

Extension Status: ✅ Active

+

Workspace: ${vscode.workspace.workspaceFolders?.[0]?.name || "None"}

`; } } - // CORREÇÃO: Adicionando o método dispose para conformidade. public dispose() { + if (this._progressManager) { + this._progressManager.unregisterWebviewProvider(this); + } + while (this._disposables.length) { const x = this._disposables.pop(); if (x) { @@ -374,7 +387,10 @@ export class DashboardProvider } } -function getNonce() { +/** + * Generates a cryptographically random nonce for CSP. + */ +function getNonce(): string { let text = ""; const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; diff --git a/packages/vscode-extension/src/providers/ProjectViewProvider.ts b/packages/vscode-extension/src/providers/ProjectViewProvider.ts index 0f8d5176..b7e79a52 100644 --- a/packages/vscode-extension/src/providers/ProjectViewProvider.ts +++ b/packages/vscode-extension/src/providers/ProjectViewProvider.ts @@ -28,7 +28,6 @@ export class ProjectViewProvider getTreeItem(element: ProjectItem): vscode.TreeItem { const item = new vscode.TreeItem(element.label, element.collapsibleState); - // Enhanced icons and styling const iconMap: Record = { "StackCode Project": "rocket", "Quick Actions": "zap", @@ -48,14 +47,11 @@ export class ProjectViewProvider "Help & Documentation": "question", }; - // Set icons with theme support item.iconPath = new vscode.ThemeIcon( iconMap[element.label] || "circle-filled", ); - // Add commands for interactive items if (element.command) { - // Handle both string and Command object types if (typeof element.command === "string") { item.command = { command: element.command, @@ -65,7 +61,6 @@ export class ProjectViewProvider item.command = element.command; } - // Add hover descriptions const tooltips: Record = { "Initialize Project": "Create a new project with StackCode scaffolding", "Generate README": "Generate a comprehensive README.md file", @@ -84,7 +79,6 @@ export class ProjectViewProvider item.tooltip = tooltips[element.label] || element.label; } - // Style for different types if (element.children && element.children.length > 0) { item.contextValue = "stackcode-category"; } else if (element.command) { @@ -96,7 +90,6 @@ export class ProjectViewProvider getChildren(element?: ProjectItem): Thenable { if (!element) { - // Root level - show main categories with enhanced structure return Promise.resolve([ { label: "StackCode Project", @@ -254,14 +247,12 @@ export class ProjectViewProvider ]; } - // Project name items.push({ label: workspaceFolder.name, description: "Project root", icon: "folder", }); - // Git status try { const gitExtension = vscode.extensions.getExtension("vscode.git"); if (gitExtension && gitExtension.isActive) { @@ -277,11 +268,10 @@ export class ProjectViewProvider }); } } - } catch { - // Git not available + } catch (error) { + console.warn("Git info unavailable:", error); } - // Quick actions items.push( { label: "Initialize Project", diff --git a/packages/vscode-extension/src/services/GitHubAuthService.ts b/packages/vscode-extension/src/services/GitHubAuthService.ts index 4c341b39..bba32a85 100644 --- a/packages/vscode-extension/src/services/GitHubAuthService.ts +++ b/packages/vscode-extension/src/services/GitHubAuthService.ts @@ -1,91 +1,67 @@ import * as vscode from "vscode"; -import { Octokit } from "@octokit/rest"; - -/** - * GitHubAuthService - Gerencia autenticação OAuth2 com GitHub - * - * Responsabilidades: - * 1. Implementar fluxo OAuth2 usando VS Code native authentication - * 2. Armazenar token seguramente usando SecretStorage - * 3. Fornecer cliente Octokit autenticado - * 4. Gerenciar login/logout - */ -export class GitHubAuthService { - private static readonly GITHUB_PROVIDER_ID = "github"; - private static readonly TOKEN_KEY = "stackcode.github.token"; - private static readonly SCOPES = ["repo", "user:email"]; - - private _context: vscode.ExtensionContext; - private _octokit: Octokit | null = null; - private _session: vscode.AuthenticationSession | null = null; +import type { Octokit } from "@octokit/rest"; +import { + createFileTokenStorage, + createGitHubAuth, + createVSCodeAuthProvider, + type GitHubAuthContext, +} from "@stackcode/github-auth"; - constructor(context: vscode.ExtensionContext) { - this._context = context; +export class GitHubAuthService { + private static readonly SCOPES = ["repo", "user:email"] as const; + + private readonly auth; + private cachedContext: GitHubAuthContext | null = null; + + constructor(private readonly context: vscode.ExtensionContext) { + const sharedStorage = createFileTokenStorage(); + this.auth = createGitHubAuth({ + provider: createVSCodeAuthProvider({ + vscode, + context, + scopes: GitHubAuthService.SCOPES, + sharedStorage, + shareTokens: true, + }), + }); } - /** - * Verifica se o usuário está autenticado - */ public get isAuthenticated(): boolean { - return this._session !== null && this._octokit !== null; + return this.auth.isAuthenticated(); } - /** - * Retorna informações do usuário autenticado - */ public get userInfo(): { username?: string; email?: string } | null { - if (!this._session) return null; + const account = this.cachedContext?.session.account; + if (!account) { + return null; + } return { - username: this._session.account.label, - email: this._session.account.id, + username: account.username ?? account.displayName, + email: account.email ?? undefined, }; } - /** - * Obtém cliente Octokit autenticado - * Throws se não estiver autenticado - */ - public getAuthenticatedClient(): Octokit { - if (!this._octokit) { - throw new Error("User not authenticated. Please login first."); - } - return this._octokit; + public async getAuthenticatedClient(): Promise { + const context = await this.ensureSession(); + return context.client; } - /** - * Inicia processo de login OAuth2 - */ public async login(): Promise { try { - // Usar VS Code native authentication - this._session = await vscode.authentication.getSession( - GitHubAuthService.GITHUB_PROVIDER_ID, - GitHubAuthService.SCOPES, - { createIfNone: true }, - ); + const context = await this.auth.login({ interactive: true }); + this.cachedContext = context; - if (this._session) { - // Criar cliente Octokit com token - this._octokit = new Octokit({ - auth: this._session.accessToken, - }); + const label = + context.session.account?.username ?? + context.session.account?.displayName ?? + context.session.account?.id ?? + "GitHub"; - // Armazenar token seguramente - await this._context.secrets.store( - GitHubAuthService.TOKEN_KEY, - this._session.accessToken, - ); - - // Verificar se o token funciona - await this._validateToken(); - - vscode.window.showInformationMessage( - `✅ Successfully logged in to GitHub as ${this._session.account.label}`, - ); - - console.log("[StackCode] GitHub authentication successful"); - } + vscode.window.showInformationMessage( + `✅ Successfully logged in to GitHub as ${label}`, + ); + console.log("[StackCode] GitHub authentication successful"); } catch (error) { console.error("[StackCode] GitHub authentication failed:", error); vscode.window.showErrorMessage( @@ -97,22 +73,10 @@ export class GitHubAuthService { } } - /** - * Remove autenticação e limpa dados - */ public async logout(): Promise { try { - // Remover token do storage seguro - await this._context.secrets.delete(GitHubAuthService.TOKEN_KEY); - - // Limpar sessão do VS Code - if (this._session) { - // Note: VS Code handles session cleanup automatically - this._session = null; - } - - // Limpar cliente - this._octokit = null; + await this.auth.logout(); + this.cachedContext = null; vscode.window.showInformationMessage( "✅ Successfully logged out from GitHub", @@ -128,61 +92,37 @@ export class GitHubAuthService { } } - /** - * Tenta restaurar sessão existente na inicialização - */ public async initializeFromStorage(): Promise { try { - // Tentar recuperar sessão existente (sem criar nova) - const session = await vscode.authentication.getSession( - GitHubAuthService.GITHUB_PROVIDER_ID, - GitHubAuthService.SCOPES, - { createIfNone: false }, - ); - - if (session) { - this._session = session; - this._octokit = new Octokit({ - auth: session.accessToken, - }); - - // Validar token - await this._validateToken(); + const context = await this.auth.getSession({ forceRefresh: true }); + if (context) { + this.cachedContext = context; console.log("[StackCode] GitHub session restored successfully"); + } else { + this.cachedContext = null; } } catch (error) { console.warn("[StackCode] Failed to restore GitHub session:", error); - // Se não conseguir restaurar, limpar dados corrompidos - await this._context.secrets.delete(GitHubAuthService.TOKEN_KEY); - this._session = null; - this._octokit = null; + this.cachedContext = null; + await this.auth.removeToken(); } } - /** - * Valida se o token atual ainda é válido - */ - private async _validateToken(): Promise { - if (!this._octokit) { - throw new Error("No Octokit client available"); + public dispose(): void { + this.cachedContext = null; + } + + private async ensureSession(): Promise { + if (this.cachedContext) { + return this.cachedContext; } - try { - // Fazer uma chamada simples para validar o token - await this._octokit.users.getAuthenticated(); - } catch (error) { - console.error("[StackCode] Token validation failed:", error); - // Token inválido, limpar tudo - await this.logout(); - throw new Error("GitHub token is invalid or expired"); + const context = await this.auth.getSession({ forceRefresh: false }); + if (!context) { + throw new Error("User not authenticated. Please login first."); } - } - /** - * Limpa recursos ao desativar extensão - */ - public dispose(): void { - this._session = null; - this._octokit = null; + this.cachedContext = context; + return context; } } diff --git a/packages/vscode-extension/src/services/GitHubIssuesService.ts b/packages/vscode-extension/src/services/GitHubIssuesService.ts index b63558fd..b7fd635a 100644 --- a/packages/vscode-extension/src/services/GitHubIssuesService.ts +++ b/packages/vscode-extension/src/services/GitHubIssuesService.ts @@ -1,27 +1,23 @@ import { GitHubAuthService } from "./GitHubAuthService"; import { GitMonitor, type GitHubRepository } from "../monitors/GitMonitor"; import { - fetchRepositoryIssues, + runIssuesWorkflow, + clearRepositoryCache, type GitHubIssue, type FetchIssuesOptions, } from "@stackcode/core"; /** - * GitHubIssuesService - Orquestra a busca de issues do GitHub + * GitHubIssuesService - Thin wrapper around core issues workflow * - * Responsabilidades: - * 1. Integrar autenticação + detecção de repositório + busca de issues - * 2. Gerenciar cache local de issues - * 3. Fornecer interface simplificada para a UI + * This service now delegates all business logic to @stackcode/core, + * providing only VS Code-specific integration (auth + git detection). + * + * @deprecated Consider using runIssuesWorkflow directly with authenticated client */ export class GitHubIssuesService { private _authService: GitHubAuthService; private _gitMonitor: GitMonitor; - private _issuesCache: Map< - string, - { issues: GitHubIssue[]; timestamp: number } - > = new Map(); - private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutos constructor(authService: GitHubAuthService, gitMonitor: GitMonitor) { this._authService = authService; @@ -29,7 +25,7 @@ export class GitHubIssuesService { } /** - * Busca issues do repositório atual + * Fetches issues from current repository using centralized core workflow */ public async fetchCurrentRepositoryIssues( options?: Partial, @@ -39,14 +35,12 @@ export class GitHubIssuesService { "🔍 [GitHubIssuesService] Starting fetchCurrentRepositoryIssues...", ); - // Verificar autenticação if (!this._authService.isAuthenticated) { console.warn("❌ [GitHubIssuesService] User not authenticated"); throw new Error("User not authenticated with GitHub"); } console.log("✅ [GitHubIssuesService] User is authenticated"); - // Detectar repositório atual console.log("🔍 [GitHubIssuesService] Detecting current repository..."); const repository = await this._gitMonitor.getCurrentGitHubRepository(); if (!repository) { @@ -72,46 +66,31 @@ export class GitHubIssuesService { } /** - * Busca issues de um repositório específico + * Fetches issues from a specific repository using centralized core workflow */ public async fetchRepositoryIssues( repository: GitHubRepository, options?: Partial, ): Promise { try { - const cacheKey = this.getCacheKey(repository, options); + const client = await this._authService.getAuthenticatedClient(); + + const result = await runIssuesWorkflow({ + client, + repository: { + owner: repository.owner, + repo: repository.repo, + fullName: repository.fullName, + }, + fetchOptions: options, + enableCache: true, + }); - // Verificar cache - const cached = this._issuesCache.get(cacheKey); - if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) { - console.log("[GitHubIssuesService] Returning cached issues"); - return cached.issues; + if (result.status === "error") { + throw new Error(result.error || "Failed to fetch repository issues"); } - // Buscar issues - const octokit = this._authService.getAuthenticatedClient(); - const fetchOptions: FetchIssuesOptions = { - owner: repository.owner, - repo: repository.repo, - state: "open", - sort: "updated", - direction: "desc", - per_page: 30, - ...options, - }; - - console.log( - `[GitHubIssuesService] Fetching issues for ${repository.fullName}`, - ); - const issues = await fetchRepositoryIssues(octokit, fetchOptions); - - // Atualizar cache - this._issuesCache.set(cacheKey, { - issues, - timestamp: Date.now(), - }); - - return issues; + return result.issues; } catch (error) { console.error( "[GitHubIssuesService] Failed to fetch repository issues:", @@ -122,7 +101,7 @@ export class GitHubIssuesService { } /** - * Busca issues atribuídas ao usuário atual + * Fetches issues assigned to current user */ public async fetchMyIssues( repository?: GitHubRepository, @@ -150,38 +129,26 @@ export class GitHubIssuesService { } /** - * Limpa cache de issues + * Limpa cache de issues (delega para o core) */ public clearCache(): void { - this._issuesCache.clear(); - console.log("[GitHubIssuesService] Cache cleared"); + import("@stackcode/core").then(({ clearIssuesCache }) => { + clearIssuesCache(); + console.log("[GitHubIssuesService] Cache cleared via core"); + }); } /** - * Limpa cache expirado + * Limpa cache expirado (delega para o core) */ public clearExpiredCache(): void { - const now = Date.now(); - for (const [key, cache] of this._issuesCache.entries()) { - if (now - cache.timestamp >= this.CACHE_TTL) { - this._issuesCache.delete(key); - } - } - } - - /** - * Gera chave única para cache baseada no repositório e opções - */ - private getCacheKey( - repository: GitHubRepository, - options?: Partial, - ): string { - const optionsStr = JSON.stringify(options || {}); - return `${repository.fullName}:${optionsStr}`; + import("@stackcode/core").then(({ clearExpiredIssuesCache }) => { + clearExpiredIssuesCache(); + }); } /** - * Força atualização de issues (ignora cache) + * Forces issue refresh (ignores cache) */ public async refreshIssues( repository?: GitHubRepository, @@ -192,11 +159,11 @@ export class GitHubIssuesService { throw new Error("No GitHub repository available"); } - // Limpar cache para este repositório - const cacheKeys = Array.from(this._issuesCache.keys()).filter((key) => - key.startsWith(targetRepo.fullName), - ); - cacheKeys.forEach((key) => this._issuesCache.delete(key)); + clearRepositoryCache({ + owner: targetRepo.owner, + repo: targetRepo.repo, + fullName: targetRepo.fullName, + }); return await this.fetchRepositoryIssues(targetRepo); } diff --git a/packages/vscode-extension/src/services/ProgressManager.ts b/packages/vscode-extension/src/services/ProgressManager.ts new file mode 100644 index 00000000..6ddfd87f --- /dev/null +++ b/packages/vscode-extension/src/services/ProgressManager.ts @@ -0,0 +1,241 @@ +/** + * @fileoverview Centralized progress manager for workflow events + * Manages progress state and broadcasts to webview + */ + +import * as vscode from "vscode"; +import type { + ProgressEvent, + ProgressState, + WebviewProgressMessage, + WebviewProgressStateMessage, + WebviewProgressCompleteMessage, +} from "../types/progress-events"; +import { + createProgressEvent, + calculateProgressPercentage, + getStepDescription, +} from "../types/progress-events"; + +/** + * Manages workflow progress events and broadcasts to webviews + */ +export class ProgressManager implements vscode.Disposable { + private _disposables: vscode.Disposable[] = []; + private _currentState: ProgressState = { inProgress: false }; + private _webviewProviders: Set = new Set(); + private _progressReporter?: vscode.Progress<{ + message?: string; + increment?: number; + }>; + + /** + * Register a webview provider to receive progress updates + */ + public registerWebviewProvider(provider: WebviewProgressListener): void { + this._webviewProviders.add(provider); + } + + /** + * Unregister a webview provider + */ + public unregisterWebviewProvider(provider: WebviewProgressListener): void { + this._webviewProviders.delete(provider); + } + + /** + * Start a new workflow progress session + */ + public startWorkflow(workflowType: ProgressEvent["type"]): void { + this._currentState = { + inProgress: true, + workflowType, + currentStep: undefined, + message: `Starting ${workflowType} workflow...`, + percentage: 0, + }; + + this._broadcastState(); + } + + /** + * Report progress for a workflow step + */ + public reportProgress( + workflowType: ProgressEvent["type"], + step: string, + customMessage?: string, + data?: Record, + ): void { + const event = createProgressEvent(workflowType, step, customMessage, data); + const percentage = calculateProgressPercentage(workflowType, step); + const message = customMessage || getStepDescription(workflowType, step); + + this._currentState = { + inProgress: true, + workflowType, + currentStep: step, + message, + percentage, + }; + + this._broadcastProgress(event); + this._broadcastState(); + + if (this._progressReporter) { + const increment = percentage - (this._currentState.percentage || 0); + this._progressReporter.report({ + message, + increment: increment > 0 ? increment : undefined, + }); + } + } + + /** + * Complete a workflow (success) + */ + public completeWorkflow( + workflowType: ProgressEvent["type"], + message?: string, + ): void { + this._currentState = { + inProgress: false, + workflowType, + message: message || `${workflowType} workflow completed successfully`, + percentage: 100, + }; + + this._broadcastComplete({ + workflowType, + success: true, + message, + }); + this._broadcastState(); + + setTimeout(() => { + if (!this._currentState.inProgress) { + this._currentState = { inProgress: false }; + this._broadcastState(); + } + }, 3000); + } + + /** + * Fail a workflow (error) + */ + public failWorkflow( + workflowType: ProgressEvent["type"], + error: string, + ): void { + this._currentState = { + inProgress: false, + workflowType, + message: `${workflowType} workflow failed`, + error, + percentage: 0, + }; + + this._broadcastComplete({ + workflowType, + success: false, + error, + }); + this._broadcastState(); + } + + /** + * Get current progress state + */ + public getCurrentState(): ProgressState { + return { ...this._currentState }; + } + + /** + * Create a progress hook for use with core workflows + */ + public createProgressHook( + workflowType: T, + ): (progress: { step: string; message?: string }) => void { + return (progress) => { + this.reportProgress(workflowType, progress.step, progress.message); + }; + } + + /** + * Set the VS Code progress reporter (from withProgress) + */ + public setVSCodeProgressReporter( + reporter: vscode.Progress<{ message?: string; increment?: number }>, + ): void { + this._progressReporter = reporter; + } + + /** + * Clear the VS Code progress reporter + */ + public clearVSCodeProgressReporter(): void { + this._progressReporter = undefined; + } + + /** + * Broadcast progress event to all registered webviews + */ + private _broadcastProgress(event: ProgressEvent): void { + const message: WebviewProgressMessage = { + type: "progress", + payload: event, + }; + + for (const provider of this._webviewProviders) { + provider.sendMessage(message); + } + } + + /** + * Broadcast state update to all registered webviews + */ + private _broadcastState(): void { + const message: WebviewProgressStateMessage = { + type: "progressState", + payload: this._currentState, + }; + + for (const provider of this._webviewProviders) { + provider.sendMessage(message); + } + } + + /** + * Broadcast completion message to all registered webviews + */ + private _broadcastComplete( + payload: WebviewProgressCompleteMessage["payload"], + ): void { + const message: WebviewProgressCompleteMessage = { + type: "progressComplete", + payload, + }; + + for (const provider of this._webviewProviders) { + provider.sendMessage(message); + } + } + + public dispose(): void { + this._webviewProviders.clear(); + this._disposables.forEach((d) => d.dispose()); + this._disposables = []; + } +} + +/** + * Interface that webview providers must implement to receive progress updates + */ +export interface WebviewProgressListener { + sendMessage( + message: + | WebviewProgressMessage + | WebviewProgressStateMessage + | WebviewProgressCompleteMessage, + ): void; +} diff --git a/demo-educational-mode.sh b/packages/vscode-extension/src/test/README.md similarity index 100% rename from demo-educational-mode.sh rename to packages/vscode-extension/src/test/README.md diff --git a/packages/vscode-extension/src/test/__mocks__/core-workflows.ts b/packages/vscode-extension/src/test/__mocks__/core-workflows.ts new file mode 100644 index 00000000..a192c80a --- /dev/null +++ b/packages/vscode-extension/src/test/__mocks__/core-workflows.ts @@ -0,0 +1,257 @@ +/** + * Mock for @stackcode/core workflows + * Used in integration tests to verify extension behavior without external dependencies + */ + +import type { + InitWorkflowStep, + CommitWorkflowStep, + GenerateWorkflowStep, + ReleaseWorkflowStep, + InitWorkflowProgress, + CommitWorkflowProgress, + GenerateWorkflowProgress, + ReleaseWorkflowProgress, +} from "@stackcode/core"; + +interface MockWorkflowResult { + success: boolean; + data?: T; + error?: Error; +} + +/** + * Mock implementation of runInitWorkflow + */ +export const runInitWorkflow = jest.fn( + async ( + options: { + projectName: string; + description?: string; + stack?: string; + template?: string; + gitInit?: boolean; + installDeps?: boolean; + cwd?: string; + }, + hooks?: { + onProgress?: (progress: InitWorkflowProgress) => void | Promise; + }, + ): Promise => { + // Simulate workflow steps + const steps: InitWorkflowStep[] = [ + "scaffold", + "saveConfig", + "generateReadme", + "generateGitignore", + "setupHusky", + "initializeGit", + "validateDependencies", + "installDependencies", + "completed", + ]; + + for (const step of steps) { + await hooks?.onProgress?.({ step, message: `Executing ${step}` }); + } + + return { + success: true, + data: { + projectPath: options.cwd || "/test/project", + filesCreated: ["package.json", "README.md", ".gitignore"], + }, + }; + }, +); + +/** + * Mock implementation of runCommitWorkflow + */ +export const runCommitWorkflow = jest.fn( + async ( + options: { + cwd?: string; + type?: string; + scope?: string; + message?: string; + body?: string; + breaking?: boolean; + issueNumber?: string; + }, + hooks?: { + onProgress?: (progress: CommitWorkflowProgress) => void | Promise; + }, + ): Promise => { + const steps: CommitWorkflowStep[] = [ + "checkingStaged", + "buildingMessage", + "committing", + "completed", + ]; + + for (const step of steps) { + await hooks?.onProgress?.({ step, message: `Executing ${step}` }); + } + + return { + success: true, + data: { + commitHash: "abc123def456", + message: `${options.type}${options.scope ? `(${options.scope})` : ""}: ${options.message}`, + }, + }; + }, +); + +/** + * Mock implementation of runGenerateWorkflow + */ +export const runGenerateWorkflow = jest.fn( + async ( + options: { + type: "readme" | "gitignore" | "contributing" | "license"; + cwd?: string; + force?: boolean; + }, + hooks?: { + onProgress?: (progress: GenerateWorkflowProgress) => void | Promise; + }, + ): Promise => { + const steps: GenerateWorkflowStep[] = [ + "checkingFile", + "generatingContent", + "writingFile", + "completed", + ]; + + for (const step of steps) { + await hooks?.onProgress?.({ step }); + } + + return { + success: true, + data: { + filePath: `${options.cwd || "/test/project"}/${options.type === "readme" ? "README.md" : `.${options.type}`}`, + content: `Mock ${options.type} content`, + }, + }; + }, +); + +/** + * Mock implementation of runReleaseWorkflow + */ +export const runReleaseWorkflow = jest.fn( + async ( + options: { + cwd?: string; + releaseType?: "major" | "minor" | "patch"; + preRelease?: string; + skipGitTag?: boolean; + skipGitPush?: boolean; + }, + hooks?: { + onProgress?: (progress: ReleaseWorkflowProgress) => void | Promise; + }, + ): Promise => { + const steps: ReleaseWorkflowStep[] = [ + "detectingStrategy", + "lockedRecommendedBump", + "lockedUpdatingVersions", + "lockedGeneratingChangelog", + "independentFindingChanges", + "independentDeterminingBumps", + "independentPreparingPlan", + "independentUpdatingPackages", + "independentCommitting", + "completed", + ]; + + for (const step of steps) { + await hooks?.onProgress?.({ step, message: `Executing ${step}` }); + } + + return { + success: true, + data: { + version: "1.2.3", + tag: "v1.2.3", + changelog: "## [1.2.3] - 2025-10-02\n\n### Features\n- Mock feature", + }, + }; + }, +); + +/** + * Mock implementation of runIssuesWorkflow + */ +export const runIssuesWorkflow = jest.fn( + async (options: { + owner: string; + repo: string; + token: string; + state?: "open" | "closed" | "all"; + }): Promise => { + return { + success: true, + data: { + issues: [ + { + number: 1, + title: "Test Issue", + state: "open", + labels: ["bug"], + created_at: "2025-10-01T00:00:00Z", + }, + ], + }, + }; + }, +); + +/** + * Mock implementation of validateProjectStructure + */ +export const validateProjectStructure = jest.fn( + async (cwd: string): Promise<{ isValid: boolean; errors: string[] }> => { + return { + isValid: true, + errors: [], + }; + }, +); + +/** + * Mock implementation of validateCommitMessage + */ +export const validateCommitMessage = jest.fn( + (message: string): { isValid: boolean; errors: string[] } => { + const conventionalPattern = + /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?: .{1,}/; + const isValid = conventionalPattern.test(message); + + return { + isValid, + errors: isValid + ? [] + : ["Message does not follow conventional commits format"], + }; + }, +); + +/** + * Mock implementation of clearRepositoryCache + */ +export const clearRepositoryCache = jest.fn(async (): Promise => { + // No-op for mock +}); + +/** + * Mock implementation of saveStackCodeConfig + */ +export const saveStackCodeConfig = jest.fn( + async (cwd: string, config: Record): Promise => { + // No-op for mock + }, +); diff --git a/packages/vscode-extension/src/test/__mocks__/i18n.ts b/packages/vscode-extension/src/test/__mocks__/i18n.ts new file mode 100644 index 00000000..a7930542 --- /dev/null +++ b/packages/vscode-extension/src/test/__mocks__/i18n.ts @@ -0,0 +1,8 @@ +/** + * Mock for @stackcode/i18n + */ +export const t = jest.fn((key: string) => key); + +export const setLanguage = jest.fn(); + +export const getAvailableLanguages = jest.fn(() => ["en", "pt-BR", "es"]); diff --git a/packages/vscode-extension/src/test/integration/CommitCommand.integration.test.ts b/packages/vscode-extension/src/test/integration/CommitCommand.integration.test.ts new file mode 100644 index 00000000..6baa9839 --- /dev/null +++ b/packages/vscode-extension/src/test/integration/CommitCommand.integration.test.ts @@ -0,0 +1,261 @@ +/** + * @file Integration tests for CommitCommand + * + * Tests the interaction between CommitCommand and the @stackcode/core commit workflow, + * ensuring proper VSCode UI integration, user input handling, and error scenarios. + */ + +import * as vscode from "vscode"; +import { CommitCommand } from "../../commands/CommitCommand"; +import { GitHubAuthService } from "../../services/GitHubAuthService"; +import { GitMonitor } from "../../monitors/GitMonitor"; +import { ProgressManager } from "../../services/ProgressManager"; +import { runCommitWorkflow } from "@stackcode/core"; + +jest.mock("vscode", () => { + const mockQuickPick = { + show: jest.fn(), + hide: jest.fn(), + onDidChangeSelection: jest.fn(), + onDidHide: jest.fn(), + dispose: jest.fn(), + }; + + return { + window: { + showQuickPick: jest.fn(), + showInputBox: jest.fn(), + showInformationMessage: jest.fn(), + showErrorMessage: jest.fn(), + withProgress: jest.fn((_, callback) => callback({ report: jest.fn() })), + createQuickPick: jest.fn(() => mockQuickPick), + }, + workspace: { + workspaceFolders: [ + { + uri: { fsPath: "/test/workspace" }, + name: "test", + index: 0, + }, + ], + getConfiguration: jest.fn(() => ({ + get: jest.fn(), + has: jest.fn(), + inspect: jest.fn(), + update: jest.fn(), + })), + }, + ProgressLocation: { + Notification: 15, + }, + Uri: { + file: jest.fn((path) => ({ fsPath: path })), + }, + }; +}); + +jest.mock("@stackcode/core", () => ({ + runCommitWorkflow: jest.fn(), +})); + +jest.mock("@stackcode/i18n", () => ({ + t: jest.fn((key) => key), +})); + +describe("CommitCommand Integration Tests", () => { + let commitCommand: CommitCommand; + let mockAuthService: jest.Mocked; + let mockGitMonitor: jest.Mocked; + let mockProgressManager: jest.Mocked; + + beforeEach(() => { + mockAuthService = { + isAuthenticated: jest.fn().mockResolvedValue(true) as any, + getAuthToken: jest.fn().mockResolvedValue("mock-token"), + authenticate: jest.fn().mockResolvedValue(true), + } as any; + + mockGitMonitor = { + getCurrentGitHubRepository: jest.fn().mockResolvedValue(null), + refresh: jest.fn(), + } as any; + + mockProgressManager = { + startWorkflow: jest.fn(), + reportProgress: jest.fn(), + completeWorkflow: jest.fn(), + failWorkflow: jest.fn(), + setVSCodeProgressReporter: jest.fn(), + clearVSCodeProgressReporter: jest.fn(), + } as any; + + commitCommand = new CommitCommand( + mockAuthService, + mockGitMonitor, + mockProgressManager, + ); + + (vscode.window.showQuickPick as jest.Mock).mockClear(); + (vscode.window.showInputBox as jest.Mock).mockClear(); + (vscode.window.showInformationMessage as jest.Mock).mockClear(); + (vscode.window.showErrorMessage as jest.Mock).mockClear(); + (runCommitWorkflow as jest.Mock).mockClear(); + }); + + describe("execute()", () => { + it("should execute commit workflow successfully", async () => { + (vscode.window.showQuickPick as jest.Mock).mockResolvedValueOnce({ + label: "feat", + value: "feat", + }); + (vscode.window.showInputBox as jest.Mock) + .mockResolvedValueOnce("") // scope + .mockResolvedValueOnce("Add new feature") // short description + .mockResolvedValueOnce("") // long description + .mockResolvedValueOnce("") // breaking changes + .mockResolvedValueOnce(""); // issue references + + // Mock workflow + + (runCommitWorkflow as jest.Mock).mockResolvedValue({ + status: "committed", + message: "feat: Add new feature", + } as any); + + // Execute command + await commitCommand.execute(); + + // Verify workflow was called + expect(runCommitWorkflow as jest.Mock).toHaveBeenCalledWith( + expect.objectContaining({ + type: "feat", + shortDescription: "Add new feature", + cwd: "/test/workspace", + }), + expect.objectContaining({ + onProgress: expect.any(Function), + }), + ); + + // Verify progress tracking + expect(mockProgressManager.startWorkflow).toHaveBeenCalledWith("commit"); + expect(mockProgressManager.completeWorkflow).toHaveBeenCalled(); + }); + + it("should handle missing workspace folder", async () => { + // Mock no workspace + // Save original + const originalWorkspaceFolders = vscode.workspace.workspaceFolders; + // Save original + (vscode.workspace as any).workspaceFolders = undefined; + + const showErrorSpy = jest.spyOn(vscode.window, "showErrorMessage"); + + await commitCommand.execute(); + + expect(showErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("no_workspace_folder"), + ); + expect(runCommitWorkflow).not.toHaveBeenCalled(); + // Restore + (vscode.workspace as any).workspaceFolders = originalWorkspaceFolders; + }); + + it("should handle user cancellation at commit type selection", async () => { + // Mock user cancels + (vscode.window.showQuickPick as jest.Mock).mockResolvedValueOnce( + undefined, + ); + + await commitCommand.execute(); + + expect(runCommitWorkflow).not.toHaveBeenCalled(); + }); + + it("should handle workflow errors gracefully", async () => { + // Mock user inputs + (vscode.window.showQuickPick as jest.Mock).mockResolvedValueOnce({ + label: "feat", + value: "feat", + }); + (vscode.window.showInputBox as jest.Mock) + .mockResolvedValueOnce("") // scope + .mockResolvedValueOnce("Add feature") // short description + .mockResolvedValueOnce("") // long description + .mockResolvedValueOnce("") // breaking changes + .mockResolvedValueOnce(""); // issue references + + // Mock workflow error + const mockError = new Error("Git commit failed"); + + (runCommitWorkflow as jest.Mock).mockRejectedValue(mockError); + + await commitCommand.execute(); + + // Verify error handling + // When workflow throws, it's caught in catch block + expect(vscode.window.showErrorMessage).toHaveBeenCalled(); + }); + + it("should handle breaking changes flag", async () => { + // Mock user inputs with breaking changes + (vscode.window.showQuickPick as jest.Mock).mockResolvedValueOnce({ + label: "feat", + value: "feat", + }); + (vscode.window.showInputBox as jest.Mock) + .mockResolvedValueOnce("api") // scope + .mockResolvedValueOnce("Change API response format") // short description + .mockResolvedValueOnce("Details about the change") // long description + .mockResolvedValueOnce("API response structure changed"); // breaking changes + + (runCommitWorkflow as jest.Mock).mockResolvedValue({ + status: "committed", + message: "feat(api)!: Change API response format", + } as any); + + await commitCommand.execute(); + + expect(runCommitWorkflow as jest.Mock).toHaveBeenCalledWith( + expect.objectContaining({ + type: "feat", + scope: "api", + shortDescription: "Change API response format", + cwd: "/test/workspace", + breakingChanges: "API response structure changed", + }), + expect.any(Object), + ); + }); + }); + + describe("Progress Tracking", () => { + it("should track workflow progress through ProgressManager", async () => { + // Mock user inputs + (vscode.window.showQuickPick as jest.Mock).mockResolvedValueOnce({ + label: "feat", + value: "feat", + }); + (vscode.window.showInputBox as jest.Mock) + .mockResolvedValueOnce("") // scope + .mockResolvedValueOnce("Add feature") // short description + .mockResolvedValueOnce("") // long description + .mockResolvedValueOnce("") // breaking changes + .mockResolvedValueOnce(""); // issue references + + (runCommitWorkflow as jest.Mock).mockResolvedValue({ + status: "committed", + message: "feat: Add feature", + } as any); + + await commitCommand.execute(); + + // Verify progress manager methods were called + expect(mockProgressManager.startWorkflow).toHaveBeenCalledWith("commit"); + expect(mockProgressManager.setVSCodeProgressReporter).toHaveBeenCalled(); + expect( + mockProgressManager.clearVSCodeProgressReporter, + ).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/vscode-extension/src/test/integration/GitCommand.integration.test.ts b/packages/vscode-extension/src/test/integration/GitCommand.integration.test.ts new file mode 100644 index 00000000..f2ef4498 --- /dev/null +++ b/packages/vscode-extension/src/test/integration/GitCommand.integration.test.ts @@ -0,0 +1,209 @@ +/** + * @file Integration tests for GitCommand + * + * Tests Git Flow workflows (start branch, finish branch) with VSCode integration, + * including user input validation, branch creation, and remote push operations. + */ + +import * as vscode from "vscode"; +import { GitCommand } from "../../commands/GitCommand"; +import { runGitStartWorkflow, runGitFinishWorkflow } from "@stackcode/core"; + +jest.mock("vscode", () => ({ + window: { + showQuickPick: jest.fn(), + showInputBox: jest.fn(), + showInformationMessage: jest.fn(), + showErrorMessage: jest.fn(), + showWarningMessage: jest.fn(), + withProgress: jest.fn((_, callback) => callback({ report: jest.fn() })), + }, + workspace: { + workspaceFolders: [ + { + uri: { fsPath: "/test/workspace" }, + name: "test", + index: 0, + }, + ], + }, + extensions: { + getExtension: jest.fn().mockReturnValue(null), + }, + ProgressLocation: { + Notification: 15, + }, + Uri: { + file: jest.fn((path) => ({ fsPath: path })), + }, +})); + +jest.mock("@stackcode/core", () => ({ + runGitStartWorkflow: jest.fn(), + runGitFinishWorkflow: jest.fn(), +})); + +jest.mock("@stackcode/i18n", () => ({ + t: jest.fn((key) => key), +})); + +describe("GitCommand Integration Tests", () => { + let gitCommand: GitCommand; + + beforeEach(() => { + gitCommand = new GitCommand(); + jest.clearAllMocks(); + }); + + describe("startBranch()", () => { + it("should create and switch to a new branch", async () => { + (vscode.window.showInputBox as jest.Mock).mockResolvedValueOnce( + "new-feature", + ); + + (vscode.window.showQuickPick as jest.Mock).mockResolvedValueOnce({ + label: "feature", + description: "vscode.git.feature_description", + }); + + (runGitStartWorkflow as jest.Mock).mockResolvedValue({ + status: "created", + branch: "feature/new-feature", + } as any); + + await (gitCommand as any).startBranch(); + + expect(runGitStartWorkflow).toHaveBeenCalledWith( + expect.objectContaining({ + branchName: "new-feature", + branchType: "feature", + cwd: "/test/workspace", + }), + expect.any(Object), + ); + + expect(vscode.window.showInformationMessage).toHaveBeenCalled(); + }); + + it("should validate branch name format", async () => { + (vscode.window.showInputBox as jest.Mock).mockResolvedValueOnce( + "invalid branch name!", + ); + + await (gitCommand as any).startBranch(); + + expect(runGitStartWorkflow).not.toHaveBeenCalled(); + }); + + it("should handle empty branch name", async () => { + // Mock empty input + (vscode.window.showInputBox as jest.Mock).mockResolvedValueOnce(""); + + await (gitCommand as any).startBranch(); + + expect(runGitStartWorkflow).not.toHaveBeenCalled(); + }); + + it("should handle git workflow errors", async () => { + // Mock user input - branch name + (vscode.window.showInputBox as jest.Mock).mockResolvedValueOnce("test"); + + // Mock user input - branch type + (vscode.window.showQuickPick as jest.Mock).mockResolvedValueOnce({ + label: "feature", + description: "vscode.git.feature_description", + }); + + (runGitStartWorkflow as jest.Mock).mockRejectedValue( + new Error("Branch already exists"), + ); + + await (gitCommand as any).startBranch(); + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining("vscode.git.failed_create_branch"), + ); + }); + }); + + describe("finishBranch()", () => { + it("should merge and cleanup branch", async () => { + // Mock confirmation with showWarningMessage + (vscode.window.showWarningMessage as jest.Mock).mockResolvedValueOnce( + "vscode.git.finish_branch", + ); + + (runGitFinishWorkflow as jest.Mock).mockResolvedValue({ + status: "merged", + branch: "feature/old-feature", + } as any); + + await (gitCommand as any).finishBranch(); + + expect(runGitFinishWorkflow).toHaveBeenCalledWith( + expect.objectContaining({ + cwd: "/test/workspace", + }), + expect.any(Object), + ); + }); + + it("should handle user cancellation", async () => { + // Mock user cancelling the confirmation + (vscode.window.showWarningMessage as jest.Mock).mockResolvedValueOnce( + undefined, + ); + + await (gitCommand as any).finishBranch(); + + expect(runGitFinishWorkflow).not.toHaveBeenCalled(); + }); + }); + + describe("execute()", () => { + it("should show action picker and execute start", async () => { + // Mock action selection + (vscode.window.showQuickPick as jest.Mock) + .mockResolvedValueOnce({ + label: "start", + }) + // Mock branch type selection + .mockResolvedValueOnce({ + label: "feature", + description: "vscode.git.feature_description", + }); + + // Mock branch name input + (vscode.window.showInputBox as jest.Mock).mockResolvedValueOnce("test"); + + (runGitStartWorkflow as jest.Mock).mockResolvedValue({ + status: "created", + branch: "feature/test", + } as any); + + await gitCommand.execute(); + + expect(runGitStartWorkflow).toHaveBeenCalled(); + }); + + it("should show action picker and execute finish", async () => { + // Mock action selection + (vscode.window.showQuickPick as jest.Mock).mockResolvedValueOnce({ + label: "finish", + }); + + // Mock confirmation + (vscode.window.showWarningMessage as jest.Mock).mockResolvedValueOnce( + "vscode.git.finish_branch", + ); + + (runGitFinishWorkflow as jest.Mock).mockResolvedValue({ + status: "merged", + } as any); + + await gitCommand.execute(); + + expect(runGitFinishWorkflow).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/vscode-extension/src/test/integration/ReleaseCommand.integration.test.ts b/packages/vscode-extension/src/test/integration/ReleaseCommand.integration.test.ts new file mode 100644 index 00000000..5e5dd4ab --- /dev/null +++ b/packages/vscode-extension/src/test/integration/ReleaseCommand.integration.test.ts @@ -0,0 +1,192 @@ +/** + * @file Integration tests for ReleaseCommand + * + * Tests the release workflow integration with VSCode, including version bumping, + * changelog generation, user confirmations, and GitHub release preparation. + */ + +import * as vscode from "vscode"; +import { ReleaseCommand } from "../../commands/ReleaseCommand"; +import { GitHubAuthService } from "../../services/GitHubAuthService"; +import { ProgressManager } from "../../services/ProgressManager"; +import { runReleaseWorkflow } from "@stackcode/core"; + +jest.mock("vscode", () => ({ + window: { + showQuickPick: jest.fn(), + showInputBox: jest.fn(), + showInformationMessage: jest.fn(), + showErrorMessage: jest.fn(), + showWarningMessage: jest.fn(), + withProgress: jest.fn((_, callback) => callback({ report: jest.fn() })), + }, + workspace: { + workspaceFolders: [ + { + uri: { fsPath: "/test/workspace" }, + name: "test", + index: 0, + }, + ], + }, + ProgressLocation: { + Notification: 15, + }, + Uri: { + file: jest.fn((path) => ({ fsPath: path })), + }, +})); + +jest.mock("@stackcode/core", () => ({ + runReleaseWorkflow: jest.fn(), +})); + +jest.mock("@stackcode/i18n", () => ({ + t: jest.fn((key) => key), +})); + +describe("ReleaseCommand Integration Tests", () => { + let releaseCommand: ReleaseCommand; + let mockAuthService: jest.Mocked; + let mockProgressManager: jest.Mocked; + + beforeEach(() => { + mockAuthService = { + isAuthenticated: jest.fn().mockResolvedValue(true) as any, + getAuthToken: jest.fn().mockResolvedValue("mock-token"), + authenticate: jest.fn().mockResolvedValue(true), + } as any; + + mockProgressManager = { + startWorkflow: jest.fn(), + reportProgress: jest.fn(), + completeWorkflow: jest.fn(), + failWorkflow: jest.fn(), + setVSCodeProgressReporter: jest.fn(), + clearVSCodeProgressReporter: jest.fn(), + } as any; + + releaseCommand = new ReleaseCommand(mockAuthService, mockProgressManager); + + jest.clearAllMocks(); + }); + + describe("execute()", () => { + it("should execute release workflow successfully", async () => { + (vscode.window.showWarningMessage as jest.Mock).mockResolvedValueOnce( + "vscode.release.create_release", + ); + + (runReleaseWorkflow as jest.Mock).mockResolvedValue({ + status: "prepared", + packages: [ + { + name: "@stackcode/core", + version: "1.0.1", + oldVersion: "1.0.0", + }, + ], + } as any); + + await releaseCommand.execute(); + + expect(runReleaseWorkflow as jest.Mock).toHaveBeenCalledWith( + expect.objectContaining({ + cwd: "/test/workspace", + }), + expect.any(Object), + ); + + expect(mockProgressManager.startWorkflow).toHaveBeenCalledWith("release"); + expect(mockProgressManager.completeWorkflow).toHaveBeenCalled(); + }); + + it("should handle missing workspace folder", async () => { + // Save original + const originalWorkspaceFolders = vscode.workspace.workspaceFolders; + (vscode.workspace as any).workspaceFolders = undefined; + + await releaseCommand.execute(); + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining("no_workspace_folder"), + ); + expect(runReleaseWorkflow).not.toHaveBeenCalled(); + + // Restore + (vscode.workspace as any).workspaceFolders = originalWorkspaceFolders; + }); + + it("should handle user cancellation", async () => { + (vscode.window.showQuickPick as jest.Mock).mockResolvedValueOnce( + undefined, + ); + + await releaseCommand.execute(); + + expect(runReleaseWorkflow).not.toHaveBeenCalled(); + }); + + it("should handle release workflow errors", async () => { + // Mock confirmation + (vscode.window.showWarningMessage as jest.Mock).mockResolvedValueOnce( + "vscode.release.create_release", + ); + + (runReleaseWorkflow as jest.Mock).mockRejectedValue( + new Error("No packages to release"), + ); + + await releaseCommand.execute(); + + // When workflow throws an error, it's caught in the catch block + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining("No packages to release"), + ); + }); + + it("should set VS Code progress reporter", async () => { + // Mock confirmation + (vscode.window.showWarningMessage as jest.Mock).mockResolvedValueOnce( + "vscode.release.create_release", + ); + + (runReleaseWorkflow as jest.Mock).mockResolvedValue({ + status: "prepared", + packages: [], + } as any); + + await releaseCommand.execute(); + + expect(mockProgressManager.setVSCodeProgressReporter).toHaveBeenCalled(); + expect( + mockProgressManager.clearVSCodeProgressReporter, + ).toHaveBeenCalled(); + }); + }); + + describe("GitHub Integration", () => { + it("should check authentication status", async () => { + // Mock confirmation + (vscode.window.showWarningMessage as jest.Mock).mockResolvedValueOnce( + "vscode.release.create_release", + ); + + (runReleaseWorkflow as jest.Mock).mockResolvedValue({ + status: "prepared", + packages: [ + { + name: "@stackcode/core", + version: "1.0.1", + oldVersion: "1.0.0", + }, + ], + } as any); + + await releaseCommand.execute(); + + // Auth service should be available for GitHub release creation + expect(mockAuthService).toBeDefined(); + }); + }); +}); diff --git a/packages/vscode-extension/src/test/runSmokeTest.ts b/packages/vscode-extension/src/test/runSmokeTest.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-extension/src/test/smoke/index.ts b/packages/vscode-extension/src/test/smoke/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-extension/src/types.ts b/packages/vscode-extension/src/types.ts index 8e9dd107..64f72160 100644 --- a/packages/vscode-extension/src/types.ts +++ b/packages/vscode-extension/src/types.ts @@ -1,5 +1,3 @@ -import * as vscode from "vscode"; - export interface BranchType { label: string; description: string; @@ -27,8 +25,3 @@ export interface BestPracticesIssue { message: string; action?: () => Promise; } - -export type ProgressCallback = vscode.Progress<{ - increment?: number; - message?: string; -}>; diff --git a/packages/vscode-extension/src/types/progress-events.ts b/packages/vscode-extension/src/types/progress-events.ts new file mode 100644 index 00000000..f12bd99f --- /dev/null +++ b/packages/vscode-extension/src/types/progress-events.ts @@ -0,0 +1,247 @@ +/** + * @fileoverview Centralized progress event types for webview communication + * Ensures type-safe progress reporting across all workflows + */ + +import type { + CommitWorkflowStep, + ReleaseWorkflowStep, + IssuesWorkflowStep, + InitWorkflowStep, + GenerateWorkflowStep, +} from "@stackcode/core"; + +/** + * Base interface for all progress events + */ +export interface BaseProgressEvent { + /** Unique identifier for this progress event */ + id: string; + /** Timestamp when the event occurred */ + timestamp: string; + /** Optional data associated with the event */ + data?: Record; +} + +/** + * Progress event for commit workflow + */ +export interface CommitProgressEvent extends BaseProgressEvent { + type: "commit"; + step: CommitWorkflowStep; + message?: string; +} + +/** + * Progress event for release workflow + */ +export interface ReleaseProgressEvent extends BaseProgressEvent { + type: "release"; + step: ReleaseWorkflowStep; + message?: string; +} + +/** + * Progress event for issues workflow + */ +export interface IssuesProgressEvent extends BaseProgressEvent { + type: "issues"; + step: IssuesWorkflowStep; + message?: string; +} + +/** + * Progress event for init workflow + */ +export interface InitProgressEvent extends BaseProgressEvent { + type: "init"; + step: InitWorkflowStep; + message?: string; +} + +/** + * Progress event for generate workflow + */ +export interface GenerateProgressEvent extends BaseProgressEvent { + type: "generate"; + step: GenerateWorkflowStep; + message?: string; +} + +/** + * Union type of all progress events + */ +export type ProgressEvent = + | CommitProgressEvent + | ReleaseProgressEvent + | IssuesProgressEvent + | InitProgressEvent + | GenerateProgressEvent; + +/** + * Progress state for UI rendering + */ +export interface ProgressState { + /** Whether the workflow is currently in progress */ + inProgress: boolean; + /** Current workflow type */ + workflowType?: ProgressEvent["type"]; + /** Current step */ + currentStep?: string; + /** Progress message to display */ + message?: string; + /** Progress percentage (0-100) */ + percentage?: number; + /** Error message if workflow failed */ + error?: string; +} + +/** + * Message types sent from extension to webview + */ +export interface WebviewProgressMessage { + type: "progress"; + payload: ProgressEvent; +} + +export interface WebviewProgressStateMessage { + type: "progressState"; + payload: ProgressState; +} + +export interface WebviewProgressCompleteMessage { + type: "progressComplete"; + payload: { + workflowType: ProgressEvent["type"]; + success: boolean; + message?: string; + error?: string; + }; +} + +/** + * Helper to create a progress event + */ +export function createProgressEvent( + type: T, + step: string, + message?: string, + data?: Record, +): ProgressEvent { + return { + type, + step, + message, + id: `${type}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + timestamp: new Date().toISOString(), + data, + } as ProgressEvent; +} + +/** + * Helper to calculate progress percentage based on workflow steps + */ +export function calculateProgressPercentage( + workflowType: ProgressEvent["type"], + currentStep: string, +): number { + const stepMaps: Record> = { + commit: { + checkingStaged: 25, + buildingMessage: 50, + committing: 75, + completed: 100, + }, + release: { + detectingStrategy: 10, + lockedRecommendedBump: 20, + lockedUpdatingVersions: 40, + lockedGeneratingChangelog: 60, + independentFindingChanges: 20, + independentDeterminingBumps: 40, + independentPreparingPlan: 60, + independentUpdatingPackages: 80, + independentCommitting: 90, + completed: 100, + }, + issues: { + fetching: 50, + caching: 75, + completed: 100, + error: 100, + }, + init: { + scaffold: 15, + saveConfig: 25, + generateReadme: 35, + generateGitignore: 45, + setupHusky: 55, + initializeGit: 65, + validateDependencies: 75, + installDependencies: 90, + completed: 100, + }, + generate: { + checkingFile: 33, + generatingContent: 66, + writingFile: 90, + completed: 100, + }, + }; + + return stepMaps[workflowType]?.[currentStep] ?? 0; +} + +/** + * Helper to get user-friendly step descriptions + */ +export function getStepDescription( + workflowType: ProgressEvent["type"], + step: string, +): string { + const descriptions: Record> = { + commit: { + checkingStaged: "Checking staged changes...", + buildingMessage: "Building commit message...", + committing: "Creating commit...", + completed: "Commit completed successfully", + }, + release: { + detectingStrategy: "Detecting versioning strategy...", + lockedRecommendedBump: "Determining version bump...", + lockedUpdatingVersions: "Updating versions...", + lockedGeneratingChangelog: "Generating changelog...", + independentFindingChanges: "Finding changed packages...", + independentDeterminingBumps: "Determining version bumps...", + independentPreparingPlan: "Preparing release plan...", + independentUpdatingPackages: "Updating package versions...", + independentCommitting: "Creating release commits...", + completed: "Release workflow completed", + }, + issues: { + fetching: "Fetching issues from GitHub...", + caching: "Caching results...", + completed: "Issues fetched successfully", + error: "Failed to fetch issues", + }, + init: { + scaffold: "Scaffolding project structure...", + saveConfig: "Saving configuration...", + generateReadme: "Generating README...", + generateGitignore: "Generating .gitignore...", + setupHusky: "Setting up Husky...", + initializeGit: "Initializing Git repository...", + validateDependencies: "Validating dependencies...", + installDependencies: "Installing dependencies...", + completed: "Project initialized successfully", + }, + generate: { + checkingFile: "Checking existing file...", + generatingContent: "Generating content...", + writingFile: "Writing file...", + completed: "File generated successfully", + }, + }; + + return descriptions[workflowType]?.[step] ?? `Processing: ${step}`; +} diff --git a/packages/vscode-extension/src/webview-ui/index.html b/packages/vscode-extension/src/webview-ui/index.html index 0463f8c2..88d5c67b 100644 --- a/packages/vscode-extension/src/webview-ui/index.html +++ b/packages/vscode-extension/src/webview-ui/index.html @@ -3,7 +3,7 @@ - StackCode UI + StackCode
diff --git a/packages/vscode-extension/src/webview-ui/src/components/Dashboard.tsx b/packages/vscode-extension/src/webview-ui/src/components/Dashboard.tsx index d3d8bd6c..760a0ade 100644 --- a/packages/vscode-extension/src/webview-ui/src/components/Dashboard.tsx +++ b/packages/vscode-extension/src/webview-ui/src/components/Dashboard.tsx @@ -17,6 +17,8 @@ import { Code, } from "lucide-react"; import IssuesPanel from "./IssuesPanel"; +import ProgressIndicator from "./ProgressIndicator"; +import { useProgress } from "../hooks/useProgress"; // Interfaces para Issues do GitHub interface GitHubIssue { @@ -213,6 +215,9 @@ const Dashboard: React.FC = ({ }, ]; + // Use progress hook + const progressState = useProgress(); + useEffect(() => { // Animate numbers on load const animateNumbers = () => { @@ -238,6 +243,9 @@ const Dashboard: React.FC = ({ return (
+ {/* Progress Indicator */} + + {/* Header */}
diff --git a/packages/vscode-extension/src/webview-ui/src/components/IssuesPanel.tsx b/packages/vscode-extension/src/webview-ui/src/components/IssuesPanel.tsx index 69bf87e8..b1f9f003 100644 --- a/packages/vscode-extension/src/webview-ui/src/components/IssuesPanel.tsx +++ b/packages/vscode-extension/src/webview-ui/src/components/IssuesPanel.tsx @@ -87,12 +87,17 @@ export default function IssuesPanel({

- Repository Issues + {t("github.ui.repository_issues")}

-
- - Loading issues... +
+ + + {t("github.ui.fetching_issues")} + + + {t("github.ui.please_wait")} +
); diff --git a/packages/vscode-extension/src/webview-ui/src/components/ProgressIndicator.tsx b/packages/vscode-extension/src/webview-ui/src/components/ProgressIndicator.tsx new file mode 100644 index 00000000..e30eefd2 --- /dev/null +++ b/packages/vscode-extension/src/webview-ui/src/components/ProgressIndicator.tsx @@ -0,0 +1,110 @@ +import React from "react"; +import { Loader2, CheckCircle, XCircle, Activity } from "lucide-react"; + +interface ProgressIndicatorProps { + inProgress: boolean; + workflowType?: "commit" | "release" | "issues" | "init" | "generate"; + currentStep?: string; + message?: string; + percentage?: number; + error?: string; +} + +const workflowIcons: Record = { + commit: , + release: , + issues: , + init: , + generate: , +}; + +const workflowLabels: Record = { + commit: "Commit", + release: "Release", + issues: "Issues", + init: "Initialize", + generate: "Generate", +}; + +const ProgressIndicator: React.FC = ({ + inProgress, + workflowType, + currentStep, + message, + percentage = 0, + error, +}) => { + if (!inProgress && !error) { + return null; + } + + return ( +
+ {/* Header */} +
+
+ {error ? ( + + ) : inProgress ? ( + + ) : ( + + )} + + {workflowType ? workflowLabels[workflowType] : "Processing"} + +
+ {workflowType && ( + + {workflowIcons[workflowType]} + + )} +
+ + {/* Content */} +
+ {/* Message */} + {message &&

{message}

} + + {/* Current Step */} + {currentStep && !error && ( +

+ Step: {currentStep} +

+ )} + + {/* Error Message */} + {error && ( +
+

{error}

+
+ )} + + {/* Progress Bar */} + {inProgress && !error && ( +
+
+
+
+

+ {percentage}% +

+
+ )} +
+
+ ); +}; + +export default ProgressIndicator; diff --git a/packages/vscode-extension/src/webview-ui/src/hooks/useProgress.ts b/packages/vscode-extension/src/webview-ui/src/hooks/useProgress.ts new file mode 100644 index 00000000..b3eea68a --- /dev/null +++ b/packages/vscode-extension/src/webview-ui/src/hooks/useProgress.ts @@ -0,0 +1,108 @@ +import { useState, useEffect } from "react"; + +/** + * Progress state interface matching the extension-side types + */ +interface ProgressState { + inProgress: boolean; + workflowType?: "commit" | "release" | "issues" | "init" | "generate"; + currentStep?: string; + message?: string; + percentage?: number; + error?: string; +} + +/** + * Progress event from extension + */ +interface ProgressEvent { + type: "commit" | "release" | "issues" | "init" | "generate"; + step: string; + message?: string; + id: string; + timestamp: string; + data?: Record; +} + +/** + * Message types from extension + */ +interface ProgressMessage { + type: "progress"; + payload: ProgressEvent; +} + +interface ProgressStateMessage { + type: "progressState"; + payload: ProgressState; +} + +interface ProgressCompleteMessage { + type: "progressComplete"; + payload: { + workflowType: ProgressEvent["type"]; + success: boolean; + message?: string; + error?: string; + }; +} + +type VSCodeMessage = + | ProgressMessage + | ProgressStateMessage + | ProgressCompleteMessage; + +/** + * Custom hook to manage progress state from VS Code extension + */ +export function useProgress() { + const [progressState, setProgressState] = useState({ + inProgress: false, + }); + + useEffect(() => { + const handleMessage = (event: MessageEvent) => { + const message = event.data as VSCodeMessage; + + switch (message.type) { + case "progress": + console.log("[WebviewUI] Progress event:", message.payload); + break; + + case "progressState": + setProgressState(message.payload); + break; + + case "progressComplete": + if (message.payload.success) { + setProgressState({ + inProgress: false, + workflowType: message.payload.workflowType, + message: message.payload.message || "Completed successfully", + percentage: 100, + }); + } else { + setProgressState({ + inProgress: false, + workflowType: message.payload.workflowType, + error: message.payload.error || "An error occurred", + percentage: 0, + }); + } + + setTimeout(() => { + setProgressState({ inProgress: false }); + }, 5000); + break; + } + }; + + window.addEventListener("message", handleMessage); + + return () => { + window.removeEventListener("message", handleMessage); + }; + }, []); + + return progressState; +} diff --git a/packages/vscode-extension/src/webview-ui/src/utils/i18n.ts b/packages/vscode-extension/src/webview-ui/src/utils/i18n.ts index 8b608025..97093bf0 100644 --- a/packages/vscode-extension/src/webview-ui/src/utils/i18n.ts +++ b/packages/vscode-extension/src/webview-ui/src/utils/i18n.ts @@ -1,5 +1,5 @@ /** - * Hook de tradução para componentes React da extensão VSCode + * Translation hook for VSCode extension React components */ const translations = { @@ -77,7 +77,7 @@ export function useTranslation() { } /** - * Função utilitária para tradução direta + * Utility function for direct translation */ export function translate(key: string, language: "pt" | "en" = "pt"): string { const translation = diff --git a/packages/vscode-extension/src/webview-ui/vite.config.ts b/packages/vscode-extension/src/webview-ui/vite.config.ts index d9f7768a..4c51375b 100644 --- a/packages/vscode-extension/src/webview-ui/vite.config.ts +++ b/packages/vscode-extension/src/webview-ui/vite.config.ts @@ -1,5 +1,3 @@ -// packages/vscode-extension/src/webview-ui/vite.config.ts - import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import path from "path"; @@ -7,7 +5,6 @@ import path from "path"; export default defineConfig({ root: path.resolve(__dirname), - // Configuração do CSS com PostCSS css: { postcss: path.resolve(__dirname, "postcss.config.js"), }, @@ -17,8 +14,6 @@ export default defineConfig({ build: { outDir: path.resolve(__dirname, "..", "..", "dist", "webview-ui"), manifest: true, - rollupOptions: { - // ... - }, + rollupOptions: {}, }, }); diff --git a/packages/vscode-extension/tsconfig.json b/packages/vscode-extension/tsconfig.json index aabf7348..9f3b6a0f 100644 --- a/packages/vscode-extension/tsconfig.json +++ b/packages/vscode-extension/tsconfig.json @@ -17,5 +17,12 @@ "composite": false }, "include": ["src/**/*"], - "exclude": ["node_modules", "out", "dist", "**/*.test.ts", "src/webview-ui"] + "exclude": [ + "node_modules", + "out", + "dist", + "**/*.test.ts", + "src/test/__mocks__", + "src/webview-ui" + ] } diff --git a/scripts/install-test-deps.sh b/scripts/install-test-deps.sh new file mode 100755 index 00000000..ebd7524a --- /dev/null +++ b/scripts/install-test-deps.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +set -e + +echo "🔧 Instalando dependências para testes da extensão VS Code..." +echo "" + +cd "$(dirname "$0")/../packages/vscode-extension" || exit 1 + +echo "📦 Instalando dependências de teste..." +npm install --save-dev \ + @vscode/test-electron@^2.3.9 \ + @types/mocha@^10.0.6 \ + @types/glob@^8.1.0 \ + mocha@^10.3.0 \ + glob@^10.3.10 \ + ts-node@^10.9.2 + +echo "" +echo "✅ Dependências instaladas com sucesso!" +echo "" +echo "📋 Próximos passos:" +echo " 1. Compilar a extensão: npm run compile:ext" +echo " 2. Executar testes: npm run test:all" +echo " 3. Ou usar o script helper: ../../scripts/run-extension-tests.sh" +echo "" diff --git a/scripts/run-extension-tests.sh b/scripts/run-extension-tests.sh new file mode 100755 index 00000000..937422b0 --- /dev/null +++ b/scripts/run-extension-tests.sh @@ -0,0 +1,104 @@ +#!/bin/bash + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +print_color() { + color=$1 + message=$2 + echo -e "${color}${message}${NC}" +} + +print_header() { + echo "" + print_color "$BLUE" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + print_color "$BLUE" " $1" + print_color "$BLUE" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" +} + +cd "$(dirname "$0")/../.." || exit 1 + +print_header "🧪 StackCode Extension - Test Suite" + +if [ ! -f "package.json" ]; then + print_color "$RED" "❌ Erro: package.json não encontrado. Execute este script do diretório da extensão." + exit 1 +fi + +if [ ! -d "node_modules" ]; then + print_color "$YELLOW" "⚠️ node_modules não encontrado. Instalando dependências..." + npm install +fi + +print_header "🔨 Compilando Extensão" +npm run compile:ext +if [ $? -ne 0 ]; then + print_color "$RED" "❌ Falha na compilação" + exit 1 +fi +print_color "$GREEN" "✅ Compilação concluída" + +FAILED_TESTS=0 + +print_header "🧪 Executando Testes Unitários (Jest)" +npm test -- --coverage --verbose +if [ $? -ne 0 ]; then + print_color "$RED" "❌ Testes unitários falharam" + FAILED_TESTS=$((FAILED_TESTS + 1)) +else + print_color "$GREEN" "✅ Testes unitários passaram" +fi + +print_header "🔗 Executando Testes de Integração" +npm run test:integration -- --verbose +if [ $? -ne 0 ]; then + print_color "$RED" "❌ Testes de integração falharam" + FAILED_TESTS=$((FAILED_TESTS + 1)) +else + print_color "$GREEN" "✅ Testes de integração passaram" +fi + +if [ "$(uname)" = "Linux" ]; then + print_header "🔥 Executando Smoke Tests" + + if ! command -v xvfb-run &> /dev/null; then + print_color "$YELLOW" "⚠️ xvfb não encontrado. Instale com: sudo apt-get install xvfb" + print_color "$YELLOW" "⚠️ Pulando smoke tests..." + else + xvfb-run -a npm run test:smoke + if [ $? -ne 0 ]; then + print_color "$RED" "❌ Smoke tests falharam" + FAILED_TESTS=$((FAILED_TESTS + 1)) + else + print_color "$GREEN" "✅ Smoke tests passaram" + fi + fi +else + print_color "$YELLOW" "⚠️ Smoke tests são executados apenas no Linux. Pulando..." +fi + +print_header "📊 Relatório de Testes" + +if [ $FAILED_TESTS -eq 0 ]; then + print_color "$GREEN" "✅ TODOS OS TESTES PASSARAM!" + echo "" + print_color "$GREEN" "🎉 A extensão está pronta para deploy!" + echo "" + + if [ -f "coverage/coverage-summary.json" ]; then + print_color "$BLUE" "📊 Relatório de Cobertura:" + cat coverage/coverage-summary.json | grep -A 4 '"total"' + fi + + exit 0 +else + print_color "$RED" "❌ $FAILED_TESTS grupo(s) de testes falharam" + echo "" + print_color "$RED" "Por favor, corrija os erros antes de fazer commit." + echo "" + exit 1 +fi diff --git a/scripts/test-cli-commands.sh b/scripts/test-cli-commands.sh new file mode 100755 index 00000000..0a3402c7 --- /dev/null +++ b/scripts/test-cli-commands.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2086 + +# ----------------------------------------------------------------------------- +# StackCode CLI smoke test harness +# ----------------------------------------------------------------------------- +# This script performs a lightweight end-to-end check against the StackCode CLI. +# It builds the CLI, runs its automated test suite, and then exercises every +# registered command to ensure they load correctly. For non-interactive +# commands, it also performs a minimal functional check inside an isolated temp +# workspace so the repository remains untouched. +# +# Usage: +# ./scripts/test-cli-commands.sh +# +# Environment flags: +# SKIP_CLI_BUILD=1 -> Skip the build step (assumes dist/ is up-to-date) +# SKIP_CLI_TESTS=1 -> Skip the vitest suite execution +# ----------------------------------------------------------------------------- + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +CLI_WORKSPACE="@stackcode/cli" +CLI_BIN="$ROOT_DIR/packages/cli/dist/index.js" + +run() { + local cmd=("$@") + echo "[exec] ${cmd[*]}" + "${cmd[@]}" +} + +if [[ "${SKIP_CLI_BUILD:-0}" != "1" ]]; then + echo "\n==> Building StackCode CLI" + run npm run build --workspace "$CLI_WORKSPACE" +else + echo "\n==> Skipping CLI build (SKIP_CLI_BUILD=1)" +fi + +if [[ ! -f "$CLI_BIN" ]]; then + echo "[error] CLI binary not found at $CLI_BIN" >&2 + exit 1 +fi + +if [[ "${SKIP_CLI_TESTS:-0}" != "1" ]]; then + echo "\n==> Running CLI automated tests" + run npm run test --workspace "$CLI_WORKSPACE" +else + echo "\n==> Skipping CLI tests (SKIP_CLI_TESTS=1)" +fi + +# Smoke-check every command entry point with --help to ensure the handler loads. +declare -a HELP_COMMANDS=( + "commit --help" + "config --help" + "generate --help" + "git --help" + "git start --help" + "git finish --help" + "init --help" + "release --help" + "validate --help" + "github --help" + "github auth --help" + "github issues --help" +) + +echo "\n==> Verifying CLI command registration" +for entry in "${HELP_COMMANDS[@]}"; do + # shellcheck disable=SC2086 # we want word splitting for the sub-arguments + if node "$CLI_BIN" $entry >/dev/null 2>&1; then + printf '[pass] stackcode %s\n' "$entry" + else + printf '[fail] stackcode %s\n' "$entry" >&2 + exit 1 + fi +done + +# Functional smoke tests for non-interactive commands inside a temp workspace. +TEMP_DIR="$(mktemp -d)" +cleanup() { + rm -rf "$TEMP_DIR" +} +trap cleanup EXIT + +# Isolate Configstore writes so the user's environment is untouched. +export XDG_CONFIG_HOME="$TEMP_DIR/.config" +mkdir -p "$XDG_CONFIG_HOME" + +pushd "$TEMP_DIR" >/dev/null + +# Initialise a tiny git repo to keep git-dependent commands happy when invoked +# with --help (some flows inspect git presence during setup). +git init -q +git config user.name "StackCode Smoke" +git config user.email "cli-smoke@example.com" + +echo "\n==> Running functional smoke checks inside $TEMP_DIR" + +# 1. Validate command should accept a well-formed message. +node "$CLI_BIN" validate "chore: smoke-check" + +# 2. Generate README and .gitignore without prompts. +node "$CLI_BIN" generate readme +node "$CLI_BIN" generate gitignore + +# 3. Config command in non-interactive mode (writes to temp XDG_CONFIG_HOME). +node "$CLI_BIN" config set lang en + +popd >/dev/null + +echo "\nAll StackCode CLI smoke checks passed!" \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 6d7d3693..8e19408b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "references": [ { "path": "packages/core" }, { "path": "packages/cli" }, - { "path": "packages/i18n" } + { "path": "packages/i18n" }, + { "path": "packages/github-auth" } ] }