From 59bbbd43c5990899532cc08b3eda59bd668d1877 Mon Sep 17 00:00:00 2001 From: Test User Date: Wed, 31 Dec 2025 18:42:33 -0500 Subject: [PATCH] feat: add Node.js version management and improve error handling - Introduced a .nvmrc file to specify the Node.js version (22) for the project, ensuring consistent development environments. - Enhanced error handling in the startServer function to provide clearer messages when the Node.js executable cannot be found, improving debugging experience. - Updated package.json files across various modules to enforce Node.js version compatibility and ensure consistent dependency versions. These changes aim to streamline development processes and enhance the application's reliability by enforcing version control and improving error reporting. --- .nvmrc | 2 + apps/server/package.json | 57 ++-- apps/server/src/lib/auth.ts | 7 +- apps/server/src/providers/claude-provider.ts | 21 ++ apps/server/src/services/terminal-service.ts | 12 +- .../unit/services/terminal-service.test.ts | 271 ++++++++++-------- apps/ui/package.json | 149 +++++----- apps/ui/src/main.ts | 7 +- init.mjs | 4 +- libs/dependency-resolver/package.json | 11 +- libs/git-utils/package.json | 13 +- libs/model-resolver/package.json | 11 +- libs/platform/package.json | 13 +- libs/platform/src/secure-fs.ts | 13 +- libs/platform/src/system-paths.ts | 5 +- libs/prompts/package.json | 11 +- libs/prompts/src/defaults.ts | 6 + libs/types/package.json | 7 +- libs/utils/package.json | 13 +- package-lock.json | 8 +- package.json | 15 +- 21 files changed, 387 insertions(+), 269 deletions(-) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..42126c05 --- /dev/null +++ b/.nvmrc @@ -0,0 +1,2 @@ +22 + diff --git a/apps/server/package.json b/apps/server/package.json index 1eb415a8..d923fa9b 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -5,6 +5,9 @@ "author": "AutoMaker Team", "license": "SEE LICENSE IN LICENSE", "private": true, + "engines": { + "node": ">=22.0.0 <23.0.0" + }, "type": "module", "main": "dist/index.js", "scripts": { @@ -21,35 +24,35 @@ "test:unit": "vitest run tests/unit" }, "dependencies": { - "@anthropic-ai/claude-agent-sdk": "^0.1.72", - "@automaker/dependency-resolver": "^1.0.0", - "@automaker/git-utils": "^1.0.0", - "@automaker/model-resolver": "^1.0.0", - "@automaker/platform": "^1.0.0", - "@automaker/prompts": "^1.0.0", - "@automaker/types": "^1.0.0", - "@automaker/utils": "^1.0.0", - "@modelcontextprotocol/sdk": "^1.25.1", - "cookie-parser": "^1.4.7", - "cors": "^2.8.5", - "dotenv": "^17.2.3", - "express": "^5.2.1", - "morgan": "^1.10.1", + "@anthropic-ai/claude-agent-sdk": "0.1.72", + "@automaker/dependency-resolver": "1.0.0", + "@automaker/git-utils": "1.0.0", + "@automaker/model-resolver": "1.0.0", + "@automaker/platform": "1.0.0", + "@automaker/prompts": "1.0.0", + "@automaker/types": "1.0.0", + "@automaker/utils": "1.0.0", + "@modelcontextprotocol/sdk": "1.25.1", + "cookie-parser": "1.4.7", + "cors": "2.8.5", + "dotenv": "17.2.3", + "express": "5.2.1", + "morgan": "1.10.1", "node-pty": "1.1.0-beta41", - "ws": "^8.18.3" + "ws": "8.18.3" }, "devDependencies": { - "@types/cookie": "^0.6.0", - "@types/cookie-parser": "^1.4.10", - "@types/cors": "^2.8.19", - "@types/express": "^5.0.6", - "@types/morgan": "^1.9.10", - "@types/node": "^22", - "@types/ws": "^8.18.1", - "@vitest/coverage-v8": "^4.0.16", - "@vitest/ui": "^4.0.16", - "tsx": "^4.21.0", - "typescript": "^5", - "vitest": "^4.0.16" + "@types/cookie": "0.6.0", + "@types/cookie-parser": "1.4.10", + "@types/cors": "2.8.19", + "@types/express": "5.0.6", + "@types/morgan": "1.9.10", + "@types/node": "22.19.3", + "@types/ws": "8.18.1", + "@vitest/coverage-v8": "4.0.16", + "@vitest/ui": "4.0.16", + "tsx": "4.21.0", + "typescript": "5.9.3", + "vitest": "4.0.16" } } diff --git a/apps/server/src/lib/auth.ts b/apps/server/src/lib/auth.ts index acf8bb26..5f24b319 100644 --- a/apps/server/src/lib/auth.ts +++ b/apps/server/src/lib/auth.ts @@ -76,7 +76,10 @@ async function saveSessions(): Promise { try { await secureFs.mkdir(path.dirname(SESSIONS_FILE), { recursive: true }); const sessions = Array.from(validSessions.entries()); - await secureFs.writeFile(SESSIONS_FILE, JSON.stringify(sessions), 'utf-8'); + await secureFs.writeFile(SESSIONS_FILE, JSON.stringify(sessions), { + encoding: 'utf-8', + mode: 0o600, + }); } catch (error) { console.error('[Auth] Failed to save sessions:', error); } @@ -113,7 +116,7 @@ function ensureApiKey(): string { const newKey = crypto.randomUUID(); try { secureFs.mkdirSync(path.dirname(API_KEY_FILE), { recursive: true }); - secureFs.writeFileSync(API_KEY_FILE, newKey, { encoding: 'utf-8' }); + secureFs.writeFileSync(API_KEY_FILE, newKey, { encoding: 'utf-8', mode: 0o600 }); console.log('[Auth] Generated new API key'); } catch (error) { console.error('[Auth] Failed to save API key:', error); diff --git a/apps/server/src/providers/claude-provider.ts b/apps/server/src/providers/claude-provider.ts index 286a733f..1a5e83d2 100644 --- a/apps/server/src/providers/claude-provider.ts +++ b/apps/server/src/providers/claude-provider.ts @@ -15,6 +15,24 @@ import type { ModelDefinition, } from './types.js'; +// Automaker-specific environment variables that should not pollute agent processes +// These are internal to Automaker and would interfere with user projects +// (e.g., PORT=3008 would cause Next.js/Vite to use the wrong port) +const AUTOMAKER_ENV_VARS = ['PORT', 'DATA_DIR', 'AUTOMAKER_API_KEY', 'NODE_PATH']; + +/** + * Build a clean environment for the SDK, excluding Automaker-specific variables + */ +function buildCleanEnv(): Record { + const cleanEnv: Record = {}; + for (const [key, value] of Object.entries(process.env)) { + if (!AUTOMAKER_ENV_VARS.includes(key)) { + cleanEnv[key] = value; + } + } + return cleanEnv; +} + export class ClaudeProvider extends BaseProvider { getName(): string { return 'claude'; @@ -57,6 +75,9 @@ export class ClaudeProvider extends BaseProvider { systemPrompt, maxTurns, cwd, + // Pass clean environment to SDK, excluding Automaker-specific variables + // This prevents PORT, DATA_DIR, etc. from polluting agent-spawned processes + env: buildCleanEnv(), // Only restrict tools if explicitly set OR (no MCP / unrestricted disabled) ...(allowedTools && shouldRestrictTools && { allowedTools }), ...(!allowedTools && shouldRestrictTools && { allowedTools: defaultTools }), diff --git a/apps/server/src/services/terminal-service.ts b/apps/server/src/services/terminal-service.ts index 1aea267d..06d56981 100644 --- a/apps/server/src/services/terminal-service.ts +++ b/apps/server/src/services/terminal-service.ts @@ -257,8 +257,18 @@ export class TerminalService extends EventEmitter { // Build environment with some useful defaults // These settings ensure consistent terminal behavior across platforms + // First, create a clean copy of process.env excluding Automaker-specific variables + // that could pollute user shells (e.g., PORT would affect Next.js/other dev servers) + const automakerEnvVars = ['PORT', 'DATA_DIR', 'AUTOMAKER_API_KEY', 'NODE_PATH']; + const cleanEnv: Record = {}; + for (const [key, value] of Object.entries(process.env)) { + if (value !== undefined && !automakerEnvVars.includes(key)) { + cleanEnv[key] = value; + } + } + const env: Record = { - ...process.env, + ...cleanEnv, TERM: 'xterm-256color', COLORTERM: 'truecolor', TERM_PROGRAM: 'automaker-terminal', diff --git a/apps/server/tests/unit/services/terminal-service.test.ts b/apps/server/tests/unit/services/terminal-service.test.ts index 44e823b0..ca90937d 100644 --- a/apps/server/tests/unit/services/terminal-service.test.ts +++ b/apps/server/tests/unit/services/terminal-service.test.ts @@ -2,11 +2,22 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { TerminalService, getTerminalService } from '@/services/terminal-service.js'; import * as pty from 'node-pty'; import * as os from 'os'; -import * as fs from 'fs'; +import * as platform from '@automaker/platform'; +import * as secureFs from '@/lib/secure-fs.js'; vi.mock('node-pty'); -vi.mock('fs'); vi.mock('os'); +vi.mock('@automaker/platform', async () => { + const actual = await vi.importActual('@automaker/platform'); + return { + ...actual, + systemPathExists: vi.fn(), + systemPathReadFileSync: vi.fn(), + getWslVersionPath: vi.fn(), + isAllowedSystemPath: vi.fn(() => true), // Allow all paths in tests + }; +}); +vi.mock('@/lib/secure-fs.js'); describe('terminal-service.ts', () => { let service: TerminalService; @@ -29,6 +40,12 @@ describe('terminal-service.ts', () => { vi.mocked(os.homedir).mockReturnValue('/home/user'); vi.mocked(os.platform).mockReturnValue('linux'); vi.mocked(os.arch).mockReturnValue('x64'); + + // Default mocks for system paths and secureFs + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(platform.systemPathReadFileSync).mockReturnValue(''); + vi.mocked(platform.getWslVersionPath).mockReturnValue('/proc/version'); + vi.mocked(secureFs.stat).mockResolvedValue({ isDirectory: () => true } as any); }); afterEach(() => { @@ -38,7 +55,7 @@ describe('terminal-service.ts', () => { describe('detectShell', () => { it('should detect PowerShell Core on Windows when available', () => { vi.mocked(os.platform).mockReturnValue('win32'); - vi.mocked(fs.existsSync).mockImplementation((path: any) => { + vi.mocked(platform.systemPathExists).mockImplementation((path: string) => { return path === 'C:\\Program Files\\PowerShell\\7\\pwsh.exe'; }); @@ -50,7 +67,7 @@ describe('terminal-service.ts', () => { it('should fall back to PowerShell on Windows if Core not available', () => { vi.mocked(os.platform).mockReturnValue('win32'); - vi.mocked(fs.existsSync).mockImplementation((path: any) => { + vi.mocked(platform.systemPathExists).mockImplementation((path: string) => { return path === 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'; }); @@ -62,7 +79,7 @@ describe('terminal-service.ts', () => { it('should fall back to cmd.exe on Windows if no PowerShell', () => { vi.mocked(os.platform).mockReturnValue('win32'); - vi.mocked(fs.existsSync).mockReturnValue(false); + vi.mocked(platform.systemPathExists).mockReturnValue(false); const result = service.detectShell(); @@ -73,7 +90,7 @@ describe('terminal-service.ts', () => { it('should detect user shell on macOS', () => { vi.mocked(os.platform).mockReturnValue('darwin'); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/zsh' }); - vi.mocked(fs.existsSync).mockReturnValue(true); + vi.mocked(platform.systemPathExists).mockReturnValue(true); const result = service.detectShell(); @@ -84,7 +101,7 @@ describe('terminal-service.ts', () => { it('should fall back to zsh on macOS if user shell not available', () => { vi.mocked(os.platform).mockReturnValue('darwin'); vi.spyOn(process, 'env', 'get').mockReturnValue({}); - vi.mocked(fs.existsSync).mockImplementation((path: any) => { + vi.mocked(platform.systemPathExists).mockImplementation((path: string) => { return path === '/bin/zsh'; }); @@ -97,7 +114,7 @@ describe('terminal-service.ts', () => { it('should fall back to bash on macOS if zsh not available', () => { vi.mocked(os.platform).mockReturnValue('darwin'); vi.spyOn(process, 'env', 'get').mockReturnValue({}); - vi.mocked(fs.existsSync).mockReturnValue(false); + vi.mocked(platform.systemPathExists).mockReturnValue(false); const result = service.detectShell(); @@ -108,7 +125,7 @@ describe('terminal-service.ts', () => { it('should detect user shell on Linux', () => { vi.mocked(os.platform).mockReturnValue('linux'); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); - vi.mocked(fs.existsSync).mockReturnValue(true); + vi.mocked(platform.systemPathExists).mockReturnValue(true); const result = service.detectShell(); @@ -119,7 +136,7 @@ describe('terminal-service.ts', () => { it('should fall back to bash on Linux if user shell not available', () => { vi.mocked(os.platform).mockReturnValue('linux'); vi.spyOn(process, 'env', 'get').mockReturnValue({}); - vi.mocked(fs.existsSync).mockImplementation((path: any) => { + vi.mocked(platform.systemPathExists).mockImplementation((path: string) => { return path === '/bin/bash'; }); @@ -132,7 +149,7 @@ describe('terminal-service.ts', () => { it('should fall back to sh on Linux if bash not available', () => { vi.mocked(os.platform).mockReturnValue('linux'); vi.spyOn(process, 'env', 'get').mockReturnValue({}); - vi.mocked(fs.existsSync).mockReturnValue(false); + vi.mocked(platform.systemPathExists).mockReturnValue(false); const result = service.detectShell(); @@ -143,8 +160,10 @@ describe('terminal-service.ts', () => { it('should detect WSL and use appropriate shell', () => { vi.mocked(os.platform).mockReturnValue('linux'); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.readFileSync).mockReturnValue('Linux version 5.10.0-microsoft-standard-WSL2'); + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(platform.systemPathReadFileSync).mockReturnValue( + 'Linux version 5.10.0-microsoft-standard-WSL2' + ); const result = service.detectShell(); @@ -155,43 +174,45 @@ describe('terminal-service.ts', () => { describe('isWSL', () => { it('should return true if /proc/version contains microsoft', () => { - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.readFileSync).mockReturnValue('Linux version 5.10.0-microsoft-standard-WSL2'); + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(platform.systemPathReadFileSync).mockReturnValue( + 'Linux version 5.10.0-microsoft-standard-WSL2' + ); expect(service.isWSL()).toBe(true); }); it('should return true if /proc/version contains wsl', () => { - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.readFileSync).mockReturnValue('Linux version 5.10.0-wsl2'); + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(platform.systemPathReadFileSync).mockReturnValue('Linux version 5.10.0-wsl2'); expect(service.isWSL()).toBe(true); }); it('should return true if WSL_DISTRO_NAME is set', () => { - vi.mocked(fs.existsSync).mockReturnValue(false); + vi.mocked(platform.systemPathExists).mockReturnValue(false); vi.spyOn(process, 'env', 'get').mockReturnValue({ WSL_DISTRO_NAME: 'Ubuntu' }); expect(service.isWSL()).toBe(true); }); it('should return true if WSLENV is set', () => { - vi.mocked(fs.existsSync).mockReturnValue(false); + vi.mocked(platform.systemPathExists).mockReturnValue(false); vi.spyOn(process, 'env', 'get').mockReturnValue({ WSLENV: 'PATH/l' }); expect(service.isWSL()).toBe(true); }); it('should return false if not in WSL', () => { - vi.mocked(fs.existsSync).mockReturnValue(false); + vi.mocked(platform.systemPathExists).mockReturnValue(false); vi.spyOn(process, 'env', 'get').mockReturnValue({}); expect(service.isWSL()).toBe(false); }); it('should return false if error reading /proc/version', () => { - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.readFileSync).mockImplementation(() => { + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(platform.systemPathReadFileSync).mockImplementation(() => { throw new Error('Permission denied'); }); @@ -203,7 +224,7 @@ describe('terminal-service.ts', () => { it('should return platform information', () => { vi.mocked(os.platform).mockReturnValue('linux'); vi.mocked(os.arch).mockReturnValue('x64'); - vi.mocked(fs.existsSync).mockReturnValue(true); + vi.mocked(platform.systemPathExists).mockReturnValue(true); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); const info = service.getPlatformInfo(); @@ -216,20 +237,21 @@ describe('terminal-service.ts', () => { }); describe('createSession', () => { - it('should create a new terminal session', () => { - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as any); + it('should create a new terminal session', async () => { + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(secureFs.stat).mockResolvedValue({ isDirectory: () => true } as any); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); - const session = service.createSession({ + const session = await service.createSession({ cwd: '/test/dir', cols: 100, rows: 30, }); - expect(session.id).toMatch(/^term-/); - expect(session.cwd).toBe('/test/dir'); - expect(session.shell).toBe('/bin/bash'); + expect(session).not.toBeNull(); + expect(session!.id).toMatch(/^term-/); + expect(session!.cwd).toBe('/test/dir'); + expect(session!.shell).toBe('/bin/bash'); expect(pty.spawn).toHaveBeenCalledWith( '/bin/bash', ['--login'], @@ -241,12 +263,12 @@ describe('terminal-service.ts', () => { ); }); - it('should use default cols and rows if not provided', () => { - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as any); + it('should use default cols and rows if not provided', async () => { + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(secureFs.stat).mockResolvedValue({ isDirectory: () => true } as any); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); - service.createSession(); + await service.createSession(); expect(pty.spawn).toHaveBeenCalledWith( expect.any(String), @@ -258,66 +280,68 @@ describe('terminal-service.ts', () => { ); }); - it('should fall back to home directory if cwd does not exist', () => { - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.statSync).mockImplementation(() => { - throw new Error('ENOENT'); - }); + it('should fall back to home directory if cwd does not exist', async () => { + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(secureFs.stat).mockRejectedValue(new Error('ENOENT')); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); - const session = service.createSession({ + const session = await service.createSession({ cwd: '/nonexistent', }); - expect(session.cwd).toBe('/home/user'); + expect(session).not.toBeNull(); + expect(session!.cwd).toBe('/home/user'); }); - it('should fall back to home directory if cwd is not a directory', () => { - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => false } as any); + it('should fall back to home directory if cwd is not a directory', async () => { + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(secureFs.stat).mockResolvedValue({ isDirectory: () => false } as any); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); - const session = service.createSession({ + const session = await service.createSession({ cwd: '/file.txt', }); - expect(session.cwd).toBe('/home/user'); + expect(session).not.toBeNull(); + expect(session!.cwd).toBe('/home/user'); }); - it('should fix double slashes in path', () => { - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as any); + it('should fix double slashes in path', async () => { + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(secureFs.stat).mockResolvedValue({ isDirectory: () => true } as any); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); - const session = service.createSession({ + const session = await service.createSession({ cwd: '//test/dir', }); - expect(session.cwd).toBe('/test/dir'); + expect(session).not.toBeNull(); + expect(session!.cwd).toBe('/test/dir'); }); - it('should preserve WSL UNC paths', () => { - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as any); + it('should preserve WSL UNC paths', async () => { + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(secureFs.stat).mockResolvedValue({ isDirectory: () => true } as any); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); - const session = service.createSession({ + const session = await service.createSession({ cwd: '//wsl$/Ubuntu/home', }); - expect(session.cwd).toBe('//wsl$/Ubuntu/home'); + expect(session).not.toBeNull(); + expect(session!.cwd).toBe('//wsl$/Ubuntu/home'); }); - it('should handle data events from PTY', () => { + it('should handle data events from PTY', async () => { vi.useFakeTimers(); - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as any); + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(secureFs.stat).mockResolvedValue({ isDirectory: () => true } as any); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); const dataCallback = vi.fn(); service.onData(dataCallback); - service.createSession(); + await service.createSession(); // Simulate data event const onDataHandler = mockPtyProcess.onData.mock.calls[0][0]; @@ -331,33 +355,34 @@ describe('terminal-service.ts', () => { vi.useRealTimers(); }); - it('should handle exit events from PTY', () => { - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as any); + it('should handle exit events from PTY', async () => { + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(secureFs.stat).mockResolvedValue({ isDirectory: () => true } as any); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); const exitCallback = vi.fn(); service.onExit(exitCallback); - const session = service.createSession(); + const session = await service.createSession(); // Simulate exit event const onExitHandler = mockPtyProcess.onExit.mock.calls[0][0]; onExitHandler({ exitCode: 0 }); - expect(exitCallback).toHaveBeenCalledWith(session.id, 0); - expect(service.getSession(session.id)).toBeUndefined(); + expect(session).not.toBeNull(); + expect(exitCallback).toHaveBeenCalledWith(session!.id, 0); + expect(service.getSession(session!.id)).toBeUndefined(); }); }); describe('write', () => { - it('should write data to existing session', () => { - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as any); + it('should write data to existing session', async () => { + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(secureFs.stat).mockResolvedValue({ isDirectory: () => true } as any); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); - const session = service.createSession(); - const result = service.write(session.id, 'ls\n'); + const session = await service.createSession(); + const result = service.write(session!.id, 'ls\n'); expect(result).toBe(true); expect(mockPtyProcess.write).toHaveBeenCalledWith('ls\n'); @@ -372,13 +397,13 @@ describe('terminal-service.ts', () => { }); describe('resize', () => { - it('should resize existing session', () => { - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as any); + it('should resize existing session', async () => { + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(secureFs.stat).mockResolvedValue({ isDirectory: () => true } as any); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); - const session = service.createSession(); - const result = service.resize(session.id, 120, 40); + const session = await service.createSession(); + const result = service.resize(session!.id, 120, 40); expect(result).toBe(true); expect(mockPtyProcess.resize).toHaveBeenCalledWith(120, 40); @@ -391,30 +416,30 @@ describe('terminal-service.ts', () => { expect(mockPtyProcess.resize).not.toHaveBeenCalled(); }); - it('should handle resize errors', () => { - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as any); + it('should handle resize errors', async () => { + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(secureFs.stat).mockResolvedValue({ isDirectory: () => true } as any); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); mockPtyProcess.resize.mockImplementation(() => { throw new Error('Resize failed'); }); - const session = service.createSession(); - const result = service.resize(session.id, 120, 40); + const session = await service.createSession(); + const result = service.resize(session!.id, 120, 40); expect(result).toBe(false); }); }); describe('killSession', () => { - it('should kill existing session', () => { + it('should kill existing session', async () => { vi.useFakeTimers(); - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as any); + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(secureFs.stat).mockResolvedValue({ isDirectory: () => true } as any); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); - const session = service.createSession(); - const result = service.killSession(session.id); + const session = await service.createSession(); + const result = service.killSession(session!.id); expect(result).toBe(true); expect(mockPtyProcess.kill).toHaveBeenCalledWith('SIGTERM'); @@ -423,7 +448,7 @@ describe('terminal-service.ts', () => { vi.advanceTimersByTime(1000); expect(mockPtyProcess.kill).toHaveBeenCalledWith('SIGKILL'); - expect(service.getSession(session.id)).toBeUndefined(); + expect(service.getSession(session!.id)).toBeUndefined(); vi.useRealTimers(); }); @@ -434,29 +459,29 @@ describe('terminal-service.ts', () => { expect(result).toBe(false); }); - it('should handle kill errors', () => { - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as any); + it('should handle kill errors', async () => { + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(secureFs.stat).mockResolvedValue({ isDirectory: () => true } as any); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); mockPtyProcess.kill.mockImplementation(() => { throw new Error('Kill failed'); }); - const session = service.createSession(); - const result = service.killSession(session.id); + const session = await service.createSession(); + const result = service.killSession(session!.id); expect(result).toBe(false); }); }); describe('getSession', () => { - it('should return existing session', () => { - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as any); + it('should return existing session', async () => { + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(secureFs.stat).mockResolvedValue({ isDirectory: () => true } as any); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); - const session = service.createSession(); - const retrieved = service.getSession(session.id); + const session = await service.createSession(); + const retrieved = service.getSession(session!.id); expect(retrieved).toBe(session); }); @@ -469,15 +494,15 @@ describe('terminal-service.ts', () => { }); describe('getScrollback', () => { - it('should return scrollback buffer for existing session', () => { - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as any); + it('should return scrollback buffer for existing session', async () => { + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(secureFs.stat).mockResolvedValue({ isDirectory: () => true } as any); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); - const session = service.createSession(); - session.scrollbackBuffer = 'test scrollback'; + const session = await service.createSession(); + session!.scrollbackBuffer = 'test scrollback'; - const scrollback = service.getScrollback(session.id); + const scrollback = service.getScrollback(session!.id); expect(scrollback).toBe('test scrollback'); }); @@ -490,19 +515,21 @@ describe('terminal-service.ts', () => { }); describe('getAllSessions', () => { - it('should return all active sessions', () => { - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as any); + it('should return all active sessions', async () => { + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(secureFs.stat).mockResolvedValue({ isDirectory: () => true } as any); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); - const session1 = service.createSession({ cwd: '/dir1' }); - const session2 = service.createSession({ cwd: '/dir2' }); + const session1 = await service.createSession({ cwd: '/dir1' }); + const session2 = await service.createSession({ cwd: '/dir2' }); const sessions = service.getAllSessions(); expect(sessions).toHaveLength(2); - expect(sessions[0].id).toBe(session1.id); - expect(sessions[1].id).toBe(session2.id); + expect(session1).not.toBeNull(); + expect(session2).not.toBeNull(); + expect(sessions[0].id).toBe(session1!.id); + expect(sessions[1].id).toBe(session2!.id); expect(sessions[0].cwd).toBe('/dir1'); expect(sessions[1].cwd).toBe('/dir2'); }); @@ -535,30 +562,32 @@ describe('terminal-service.ts', () => { }); describe('cleanup', () => { - it('should clean up all sessions', () => { - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as any); + it('should clean up all sessions', async () => { + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(secureFs.stat).mockResolvedValue({ isDirectory: () => true } as any); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); - const session1 = service.createSession(); - const session2 = service.createSession(); + const session1 = await service.createSession(); + const session2 = await service.createSession(); service.cleanup(); - expect(service.getSession(session1.id)).toBeUndefined(); - expect(service.getSession(session2.id)).toBeUndefined(); + expect(session1).not.toBeNull(); + expect(session2).not.toBeNull(); + expect(service.getSession(session1!.id)).toBeUndefined(); + expect(service.getSession(session2!.id)).toBeUndefined(); expect(service.getAllSessions()).toHaveLength(0); }); - it('should handle cleanup errors gracefully', () => { - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as any); + it('should handle cleanup errors gracefully', async () => { + vi.mocked(platform.systemPathExists).mockReturnValue(true); + vi.mocked(secureFs.stat).mockResolvedValue({ isDirectory: () => true } as any); vi.spyOn(process, 'env', 'get').mockReturnValue({ SHELL: '/bin/bash' }); mockPtyProcess.kill.mockImplementation(() => { throw new Error('Kill failed'); }); - service.createSession(); + await service.createSession(); expect(() => service.cleanup()).not.toThrow(); }); diff --git a/apps/ui/package.json b/apps/ui/package.json index b069e28c..fb846c15 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -10,6 +10,9 @@ "author": "AutoMaker Team", "license": "SEE LICENSE IN LICENSE", "private": true, + "engines": { + "node": ">=22.0.0 <23.0.0" + }, "main": "dist-electron/main.js", "scripts": { "dev": "vite", @@ -35,87 +38,87 @@ "dev:electron:wsl:gpu": "cross-env MESA_D3D12_DEFAULT_ADAPTER_NAME=NVIDIA vite" }, "dependencies": { - "@automaker/dependency-resolver": "^1.0.0", - "@automaker/types": "^1.0.0", - "@codemirror/lang-xml": "^6.1.0", - "@codemirror/theme-one-dark": "^6.1.3", - "@dnd-kit/core": "^6.3.1", - "@dnd-kit/sortable": "^10.0.0", - "@dnd-kit/utilities": "^3.2.2", - "@lezer/highlight": "^1.2.3", - "@radix-ui/react-checkbox": "^1.3.3", - "@radix-ui/react-collapsible": "^1.1.12", - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-label": "^2.1.8", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-radio-group": "^1.3.8", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-slider": "^1.3.6", - "@radix-ui/react-slot": "^1.2.4", - "@radix-ui/react-switch": "^1.2.6", - "@radix-ui/react-tabs": "^1.1.13", - "@radix-ui/react-tooltip": "^1.2.8", - "@tanstack/react-query": "^5.90.12", - "@tanstack/react-router": "^1.141.6", - "@uiw/react-codemirror": "^4.25.4", - "@xterm/addon-fit": "^0.10.0", - "@xterm/addon-search": "^0.15.0", - "@xterm/addon-web-links": "^0.11.0", - "@xterm/addon-webgl": "^0.18.0", - "@xterm/xterm": "^5.5.0", - "@xyflow/react": "^12.10.0", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "cmdk": "^1.1.1", - "dagre": "^0.8.5", - "dotenv": "^17.2.3", - "geist": "^1.5.1", - "lucide-react": "^0.562.0", + "@automaker/dependency-resolver": "1.0.0", + "@automaker/types": "1.0.0", + "@codemirror/lang-xml": "6.1.0", + "@codemirror/theme-one-dark": "6.1.3", + "@dnd-kit/core": "6.3.1", + "@dnd-kit/sortable": "10.0.0", + "@dnd-kit/utilities": "3.2.2", + "@lezer/highlight": "1.2.3", + "@radix-ui/react-checkbox": "1.3.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-dropdown-menu": "2.1.16", + "@radix-ui/react-label": "2.1.8", + "@radix-ui/react-popover": "1.1.15", + "@radix-ui/react-radio-group": "1.3.8", + "@radix-ui/react-select": "2.2.6", + "@radix-ui/react-slider": "1.3.6", + "@radix-ui/react-slot": "1.2.4", + "@radix-ui/react-switch": "1.2.6", + "@radix-ui/react-tabs": "1.1.13", + "@radix-ui/react-tooltip": "1.2.8", + "@tanstack/react-query": "5.90.12", + "@tanstack/react-router": "1.141.6", + "@uiw/react-codemirror": "4.25.4", + "@xterm/addon-fit": "0.10.0", + "@xterm/addon-search": "0.15.0", + "@xterm/addon-web-links": "0.11.0", + "@xterm/addon-webgl": "0.18.0", + "@xterm/xterm": "5.5.0", + "@xyflow/react": "12.10.0", + "class-variance-authority": "0.7.1", + "clsx": "2.1.1", + "cmdk": "1.1.1", + "dagre": "0.8.5", + "dotenv": "17.2.3", + "geist": "1.5.1", + "lucide-react": "0.562.0", "react": "19.2.3", "react-dom": "19.2.3", - "react-markdown": "^10.1.0", - "react-resizable-panels": "^3.0.6", - "rehype-raw": "^7.0.0", - "sonner": "^2.0.7", - "tailwind-merge": "^3.4.0", - "usehooks-ts": "^3.1.1", - "zustand": "^5.0.9" + "react-markdown": "10.1.0", + "react-resizable-panels": "3.0.6", + "rehype-raw": "7.0.0", + "sonner": "2.0.7", + "tailwind-merge": "3.4.0", + "usehooks-ts": "3.1.1", + "zustand": "5.0.9" }, "optionalDependencies": { - "lightningcss-darwin-arm64": "^1.29.2", - "lightningcss-darwin-x64": "^1.29.2", - "lightningcss-linux-arm-gnueabihf": "^1.29.2", - "lightningcss-linux-arm64-gnu": "^1.29.2", - "lightningcss-linux-arm64-musl": "^1.29.2", - "lightningcss-linux-x64-gnu": "^1.29.2", - "lightningcss-linux-x64-musl": "^1.29.2", - "lightningcss-win32-arm64-msvc": "^1.29.2", - "lightningcss-win32-x64-msvc": "^1.29.2" + "lightningcss-darwin-arm64": "1.29.2", + "lightningcss-darwin-x64": "1.29.2", + "lightningcss-linux-arm-gnueabihf": "1.29.2", + "lightningcss-linux-arm64-gnu": "1.29.2", + "lightningcss-linux-arm64-musl": "1.29.2", + "lightningcss-linux-x64-gnu": "1.29.2", + "lightningcss-linux-x64-musl": "1.29.2", + "lightningcss-win32-arm64-msvc": "1.29.2", + "lightningcss-win32-x64-msvc": "1.29.2" }, "devDependencies": { - "@electron/rebuild": "^4.0.2", - "@eslint/js": "^9.0.0", - "@playwright/test": "^1.57.0", - "@tailwindcss/vite": "^4.1.18", - "@tanstack/router-plugin": "^1.141.7", - "@types/dagre": "^0.7.53", - "@types/node": "^22", - "@types/react": "^19.2.7", - "@types/react-dom": "^19.2.3", - "@typescript-eslint/eslint-plugin": "^8.50.0", - "@typescript-eslint/parser": "^8.50.0", - "@vitejs/plugin-react": "^5.1.2", - "cross-env": "^10.1.0", + "@electron/rebuild": "4.0.2", + "@eslint/js": "9.0.0", + "@playwright/test": "1.57.0", + "@tailwindcss/vite": "4.1.18", + "@tanstack/router-plugin": "1.141.7", + "@types/dagre": "0.7.53", + "@types/node": "22.19.3", + "@types/react": "19.2.7", + "@types/react-dom": "19.2.3", + "@typescript-eslint/eslint-plugin": "8.50.0", + "@typescript-eslint/parser": "8.50.0", + "@vitejs/plugin-react": "5.1.2", + "cross-env": "10.1.0", "electron": "39.2.7", - "electron-builder": "^26.0.12", - "eslint": "^9.39.2", - "tailwindcss": "^4.1.18", - "tw-animate-css": "^1.4.0", + "electron-builder": "26.0.12", + "eslint": "9.39.2", + "tailwindcss": "4.1.18", + "tw-animate-css": "1.4.0", "typescript": "5.9.3", - "vite": "^7.3.0", - "vite-plugin-electron": "^0.29.0", - "vite-plugin-electron-renderer": "^0.14.6" + "vite": "7.3.0", + "vite-plugin-electron": "0.29.0", + "vite-plugin-electron-renderer": "0.14.6" }, "build": { "appId": "com.automaker.app", diff --git a/apps/ui/src/main.ts b/apps/ui/src/main.ts index e8bcaaa9..88c97ee9 100644 --- a/apps/ui/src/main.ts +++ b/apps/ui/src/main.ts @@ -355,8 +355,11 @@ async function startServer(): Promise { `Node.js executable not found at: ${command} (source: ${nodeResult.source})` ); } - } catch { - throw new Error(`Node.js executable not found at: ${command} (source: ${nodeResult.source})`); + } catch (error) { + const originalError = error instanceof Error ? error.message : String(error); + throw new Error( + `Failed to verify Node.js executable at: ${command} (source: ${nodeResult.source}). Reason: ${originalError}` + ); } } diff --git a/init.mjs b/init.mjs index 68387ba5..f9d7d69c 100644 --- a/init.mjs +++ b/init.mjs @@ -39,7 +39,9 @@ function validateScriptPath(targetPath) { const resolved = path.resolve(__dirname, targetPath); const normalizedBase = path.resolve(__dirname); if (!resolved.startsWith(normalizedBase + path.sep) && resolved !== normalizedBase) { - throw new Error(`[init.mjs] Security: Path access denied outside script directory: ${targetPath}`); + throw new Error( + `[init.mjs] Security: Path access denied outside script directory: ${targetPath}` + ); } return resolved; } diff --git a/libs/dependency-resolver/package.json b/libs/dependency-resolver/package.json index 0ba6f756..4f7c30fd 100644 --- a/libs/dependency-resolver/package.json +++ b/libs/dependency-resolver/package.json @@ -25,12 +25,15 @@ ], "author": "AutoMaker Team", "license": "SEE LICENSE IN LICENSE", + "engines": { + "node": ">=22.0.0 <23.0.0" + }, "dependencies": { - "@automaker/types": "^1.0.0" + "@automaker/types": "1.0.0" }, "devDependencies": { - "@types/node": "^22.10.5", - "typescript": "^5.7.3", - "vitest": "^4.0.16" + "@types/node": "22.19.3", + "typescript": "5.9.3", + "vitest": "4.0.16" } } diff --git a/libs/git-utils/package.json b/libs/git-utils/package.json index a34ac9af..ee8fbb79 100644 --- a/libs/git-utils/package.json +++ b/libs/git-utils/package.json @@ -18,13 +18,16 @@ ], "author": "AutoMaker Team", "license": "SEE LICENSE IN LICENSE", + "engines": { + "node": ">=22.0.0 <23.0.0" + }, "dependencies": { - "@automaker/types": "^1.0.0", - "@automaker/utils": "^1.0.0" + "@automaker/types": "1.0.0", + "@automaker/utils": "1.0.0" }, "devDependencies": { - "@types/node": "^22.10.5", - "typescript": "^5.7.3", - "vitest": "^4.0.16" + "@types/node": "22.19.3", + "typescript": "5.9.3", + "vitest": "4.0.16" } } diff --git a/libs/model-resolver/package.json b/libs/model-resolver/package.json index 742144f7..06a0d252 100644 --- a/libs/model-resolver/package.json +++ b/libs/model-resolver/package.json @@ -18,12 +18,15 @@ ], "author": "AutoMaker Team", "license": "SEE LICENSE IN LICENSE", + "engines": { + "node": ">=22.0.0 <23.0.0" + }, "dependencies": { - "@automaker/types": "^1.0.0" + "@automaker/types": "1.0.0" }, "devDependencies": { - "@types/node": "^22.10.5", - "typescript": "^5.7.3", - "vitest": "^4.0.16" + "@types/node": "22.19.3", + "typescript": "5.9.3", + "vitest": "4.0.16" } } diff --git a/libs/platform/package.json b/libs/platform/package.json index 35663d05..21729ef9 100644 --- a/libs/platform/package.json +++ b/libs/platform/package.json @@ -17,13 +17,16 @@ ], "author": "AutoMaker Team", "license": "SEE LICENSE IN LICENSE", + "engines": { + "node": ">=22.0.0 <23.0.0" + }, "dependencies": { - "@automaker/types": "^1.0.0", - "p-limit": "^6.2.0" + "@automaker/types": "1.0.0", + "p-limit": "6.2.0" }, "devDependencies": { - "@types/node": "^22.10.5", - "typescript": "^5.7.3", - "vitest": "^4.0.16" + "@types/node": "22.19.3", + "typescript": "5.9.3", + "vitest": "4.0.16" } } diff --git a/libs/platform/src/secure-fs.ts b/libs/platform/src/secure-fs.ts index e324b0c3..919e555d 100644 --- a/libs/platform/src/secure-fs.ts +++ b/libs/platform/src/secure-fs.ts @@ -165,17 +165,26 @@ export async function readFile( }, `readFile(${filePath})`); } +/** + * Options for writeFile + */ +export interface WriteFileOptions { + encoding?: BufferEncoding; + mode?: number; + flag?: string; +} + /** * Wrapper around fs.writeFile that validates path first */ export async function writeFile( filePath: string, data: string | Buffer, - encoding?: BufferEncoding + optionsOrEncoding?: BufferEncoding | WriteFileOptions ): Promise { const validatedPath = validatePath(filePath); return executeWithRetry( - () => fs.writeFile(validatedPath, data, encoding), + () => fs.writeFile(validatedPath, data, optionsOrEncoding), `writeFile(${filePath})` ); } diff --git a/libs/platform/src/system-paths.ts b/libs/platform/src/system-paths.ts index 30a8aef8..95fa4b24 100644 --- a/libs/platform/src/system-paths.ts +++ b/libs/platform/src/system-paths.ts @@ -114,13 +114,16 @@ export function getShellPaths(): string[] { if (process.platform === 'win32') { return [ process.env.COMSPEC || 'cmd.exe', + 'cmd.exe', 'powershell.exe', + 'pwsh.exe', // PowerShell Core short form 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', + 'C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe', // Preview versions ]; } - return ['/bin/zsh', '/bin/bash', '/bin/sh']; + return ['/bin/zsh', '/bin/bash', '/bin/sh', '/usr/bin/zsh', '/usr/bin/bash']; } // ============================================================================= diff --git a/libs/prompts/package.json b/libs/prompts/package.json index e5954174..0012859f 100644 --- a/libs/prompts/package.json +++ b/libs/prompts/package.json @@ -18,12 +18,15 @@ ], "author": "AutoMaker Team", "license": "SEE LICENSE IN LICENSE", + "engines": { + "node": ">=22.0.0 <23.0.0" + }, "dependencies": { - "@automaker/types": "^1.0.0" + "@automaker/types": "1.0.0" }, "devDependencies": { - "@types/node": "^22.10.5", - "typescript": "^5.7.3", - "vitest": "^4.0.16" + "@types/node": "22.19.3", + "typescript": "5.9.3", + "vitest": "4.0.16" } } diff --git a/libs/prompts/src/defaults.ts b/libs/prompts/src/defaults.ts index 09e4f644..43213a1d 100644 --- a/libs/prompts/src/defaults.ts +++ b/libs/prompts/src/defaults.ts @@ -208,6 +208,9 @@ This feature depends on: {{dependencies}} **Verification:** {{verificationInstructions}} {{/if}} + +**CRITICAL - Port Protection:** +NEVER kill or terminate processes running on ports 3007 or 3008. These are reserved for the Automaker application. Killing these ports will crash Automaker and terminate this session. `; export const DEFAULT_AUTO_MODE_FOLLOW_UP_PROMPT_TEMPLATE = `## Follow-up on Feature Implementation @@ -299,6 +302,9 @@ You have access to several tools: 4. Ask questions when requirements are unclear 5. Guide users toward good software design principles +**CRITICAL - Port Protection:** +NEVER kill or terminate processes running on ports 3007 or 3008. These are reserved for the Automaker application itself. Killing these ports will crash Automaker and terminate your session. + Remember: You're a collaborative partner in the development process. Be helpful, clear, and thorough.`; /** diff --git a/libs/types/package.json b/libs/types/package.json index acd0ba75..3a5c2a83 100644 --- a/libs/types/package.json +++ b/libs/types/package.json @@ -15,8 +15,11 @@ ], "author": "AutoMaker Team", "license": "SEE LICENSE IN LICENSE", + "engines": { + "node": ">=22.0.0 <23.0.0" + }, "devDependencies": { - "@types/node": "^22.10.5", - "typescript": "^5.7.3" + "@types/node": "22.19.3", + "typescript": "5.9.3" } } diff --git a/libs/utils/package.json b/libs/utils/package.json index c7d612e8..118747be 100644 --- a/libs/utils/package.json +++ b/libs/utils/package.json @@ -17,13 +17,16 @@ ], "author": "AutoMaker Team", "license": "SEE LICENSE IN LICENSE", + "engines": { + "node": ">=22.0.0 <23.0.0" + }, "dependencies": { - "@automaker/platform": "^1.0.0", - "@automaker/types": "^1.0.0" + "@automaker/platform": "1.0.0", + "@automaker/types": "1.0.0" }, "devDependencies": { - "@types/node": "^22.10.5", - "typescript": "^5.7.3", - "vitest": "^4.0.16" + "@types/node": "22.19.3", + "typescript": "5.9.3", + "vitest": "4.0.16" } } diff --git a/package-lock.json b/package-lock.json index d8190a03..401c398f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1246,7 +1246,7 @@ }, "node_modules/@electron/node-gyp": { "version": "10.2.0-electron.1", - "resolved": "git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2", + "resolved": "git+ssh://git@github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2", "integrity": "sha512-4MSBTT8y07YUDqf69/vSh80Hh791epYqGtWHO3zSKhYFwQg+gx9wi1PqbqP6YqC4WMsNxZ5l9oDmnWdK5pfCKQ==", "dev": true, "license": "MIT", @@ -13953,9 +13953,9 @@ } }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" diff --git a/package.json b/package.json index fb5d89b6..9aff9d1a 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,9 @@ "name": "automaker", "version": "1.0.0", "private": true, + "engines": { + "node": ">=22.0.0 <23.0.0" + }, "workspaces": [ "apps/*", "libs/*" @@ -53,13 +56,13 @@ ] }, "dependencies": { - "cross-spawn": "^7.0.6", - "rehype-sanitize": "^6.0.0", - "tree-kill": "^1.2.2" + "cross-spawn": "7.0.6", + "rehype-sanitize": "6.0.0", + "tree-kill": "1.2.2" }, "devDependencies": { - "husky": "^9.1.7", - "lint-staged": "^16.2.7", - "prettier": "^3.7.4" + "husky": "9.1.7", + "lint-staged": "16.2.7", + "prettier": "3.7.4" } }