farseer-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +741 -0
  3. package/dist/commands/app.d.ts +2 -0
  4. package/dist/commands/app.js +349 -0
  5. package/dist/commands/app.js.map +7 -0
  6. package/dist/commands/apps.d.ts +2 -0
  7. package/dist/commands/apps.js +111 -0
  8. package/dist/commands/apps.js.map +7 -0
  9. package/dist/commands/checkout.d.ts +2 -0
  10. package/dist/commands/checkout.js +166 -0
  11. package/dist/commands/checkout.js.map +7 -0
  12. package/dist/commands/config.d.ts +2 -0
  13. package/dist/commands/config.js +139 -0
  14. package/dist/commands/config.js.map +7 -0
  15. package/dist/commands/diff.d.ts +2 -0
  16. package/dist/commands/diff.js +183 -0
  17. package/dist/commands/diff.js.map +7 -0
  18. package/dist/commands/files.js +99 -0
  19. package/dist/commands/files.js.map +7 -0
  20. package/dist/commands/install.d.ts +2 -0
  21. package/dist/commands/install.js +79 -0
  22. package/dist/commands/install.js.map +7 -0
  23. package/dist/commands/list.d.ts +2 -0
  24. package/dist/commands/list.js +92 -0
  25. package/dist/commands/list.js.map +7 -0
  26. package/dist/commands/login.d.ts +2 -0
  27. package/dist/commands/login.js +134 -0
  28. package/dist/commands/login.js.map +7 -0
  29. package/dist/commands/logout.d.ts +2 -0
  30. package/dist/commands/logout.js +59 -0
  31. package/dist/commands/logout.js.map +7 -0
  32. package/dist/commands/mcp-server.d.ts +8 -0
  33. package/dist/commands/mcp-server.js +41 -0
  34. package/dist/commands/mcp-server.js.map +7 -0
  35. package/dist/commands/model.d.ts +2 -0
  36. package/dist/commands/model.js +189 -0
  37. package/dist/commands/model.js.map +7 -0
  38. package/dist/commands/pull.d.ts +2 -0
  39. package/dist/commands/pull.js +287 -0
  40. package/dist/commands/pull.js.map +7 -0
  41. package/dist/commands/push.d.ts +2 -0
  42. package/dist/commands/push.js +251 -0
  43. package/dist/commands/push.js.map +7 -0
  44. package/dist/commands/run.d.ts +2 -0
  45. package/dist/commands/run.js +246 -0
  46. package/dist/commands/run.js.map +7 -0
  47. package/dist/commands/setup.d.ts +2 -0
  48. package/dist/commands/setup.js +137 -0
  49. package/dist/commands/status.d.ts +2 -0
  50. package/dist/commands/status.js +145 -0
  51. package/dist/commands/status.js.map +7 -0
  52. package/dist/commands/unsetup.d.ts +2 -0
  53. package/dist/commands/unsetup.js +122 -0
  54. package/dist/commands/whoami.d.ts +2 -0
  55. package/dist/commands/whoami.js +63 -0
  56. package/dist/commands/whoami.js.map +7 -0
  57. package/dist/index.d.ts +2 -0
  58. package/dist/index.js +135 -0
  59. package/dist/index.js.map +7 -0
  60. package/dist/mcp/index.d.ts +7 -0
  61. package/dist/mcp/index.js +35 -0
  62. package/dist/mcp/index.js.map +7 -0
  63. package/dist/mcp/prompts/workflows.d.ts +7 -0
  64. package/dist/mcp/prompts/workflows.js +374 -0
  65. package/dist/mcp/prompts/workflows.js.map +7 -0
  66. package/dist/mcp/resources/documentation.d.ts +8 -0
  67. package/dist/mcp/resources/documentation.js +167 -0
  68. package/dist/mcp/resources/documentation.js.map +7 -0
  69. package/dist/mcp/server.d.ts +7 -0
  70. package/dist/mcp/server.js +49 -0
  71. package/dist/mcp/server.js.map +7 -0
  72. package/dist/mcp/tools/appTools.d.ts +7 -0
  73. package/dist/mcp/tools/appTools.js +377 -0
  74. package/dist/mcp/tools/appTools.js.map +7 -0
  75. package/dist/mcp/tools/authTools.d.ts +7 -0
  76. package/dist/mcp/tools/authTools.js +158 -0
  77. package/dist/mcp/tools/authTools.js.map +7 -0
  78. package/dist/mcp/tools/modelTools.d.ts +7 -0
  79. package/dist/mcp/tools/modelTools.js +331 -0
  80. package/dist/mcp/tools/modelTools.js.map +7 -0
  81. package/dist/mcp/tools/runTools.d.ts +7 -0
  82. package/dist/mcp/tools/runTools.js +231 -0
  83. package/dist/mcp/tools/runTools.js.map +7 -0
  84. package/dist/mcp/tools/syncTools.d.ts +7 -0
  85. package/dist/mcp/tools/syncTools.js +382 -0
  86. package/dist/mcp/tools/syncTools.js.map +7 -0
  87. package/dist/mcp/utils/helpers.d.ts +69 -0
  88. package/dist/mcp/utils/helpers.js +113 -0
  89. package/dist/mcp/utils/helpers.js.map +7 -0
  90. package/dist/services/appSyncService.d.ts +75 -0
  91. package/dist/services/appSyncService.js +370 -0
  92. package/dist/services/appSyncService.js.map +7 -0
  93. package/dist/services/configService.d.ts +39 -0
  94. package/dist/services/configService.js +196 -0
  95. package/dist/services/configService.js.map +7 -0
  96. package/dist/services/farseerApi.d.ts +166 -0
  97. package/dist/services/farseerApi.js +378 -0
  98. package/dist/services/farseerApi.js.map +7 -0
  99. package/dist/services/farseerFactory.d.ts +88 -0
  100. package/dist/services/farseerFactory.js +179 -0
  101. package/dist/services/farseerFactory.js.map +7 -0
  102. package/dist/services/farseerService.d.ts +96 -0
  103. package/dist/services/farseerService.js +614 -0
  104. package/dist/services/farseerService.js.map +7 -0
  105. package/dist/services/gitService.d.ts +31 -0
  106. package/dist/services/gitService.js +134 -0
  107. package/dist/services/gitService.js.map +7 -0
  108. package/dist/services/syncService.d.ts +44 -0
  109. package/dist/services/syncService.js +320 -0
  110. package/dist/services/syncService.js.map +7 -0
  111. package/dist/utils/constants.d.ts +7 -0
  112. package/dist/utils/constants.js +46 -0
  113. package/dist/utils/constants.js.map +7 -0
  114. package/dist/utils/helpers.d.ts +69 -0
  115. package/dist/utils/helpers.js +413 -0
  116. package/dist/utils/helpers.js.map +7 -0
  117. package/dist/utils/logger.d.ts +14 -0
  118. package/dist/utils/logger.js +76 -0
  119. package/dist/utils/logger.js.map +7 -0
  120. package/package.json +62 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/services/farseerService.ts"],
4
+ "sourcesContent": ["import * as farseer from 'farseer-client';\nimport { ApiKeyCredential, getUserAuth } from './configService';\nimport { AppArgument, AppListItem, RemoteApp } from './farseerApi';\n\n// Basic app info returned from list endpoint\ninterface RemoteAppBasic {\n id: number;\n name: string;\n description: string;\n status: string;\n creationType: string;\n}\n\nexport interface RemoteFile {\n name: string;\n path: string;\n reference: string;\n content?: string;\n}\n\nexport interface FolderItem {\n id: number;\n name: string;\n type: string;\n reference?: string;\n}\n\nexport class FarseerService {\n private client: farseer.FarseerClient;\n private credential: ApiKeyCredential;\n\n constructor(credential: ApiKeyCredential) {\n this.credential = credential;\n this.client = new farseer.FarseerClient({\n basePath: credential.basePath,\n headers: {\n 'X-TENANT-ID': credential.tenantId,\n 'X-API-KEY': credential.apiKey,\n },\n });\n }\n\n static fromUserAuth(tenant: string, basePath: string): FarseerService | null {\n const auth = getUserAuth();\n if (!auth) return null;\n\n const service = new FarseerService({\n type: 'apiKey',\n tenantId: tenant,\n apiKey: '',\n basePath: basePath,\n });\n\n // Override with access token\n service.client = new farseer.FarseerClient({\n basePath: basePath,\n headers: {\n 'X-TENANT-ID': tenant,\n },\n accessToken: auth.accessToken,\n });\n\n return service;\n }\n\n async testConnection(): Promise<boolean> {\n try {\n // Try to get the Files folder to test connection\n await this.client.getItemByPath(['Files']);\n return true;\n } catch {\n return false;\n }\n }\n\n async getFilesFolder(): Promise<FolderItem | null> {\n try {\n const item = await this.client.getItemByPath(['Files']);\n return {\n id: item.id,\n name: item.name,\n type: item.type,\n };\n } catch {\n return null;\n }\n }\n\n async listFiles(folderPath: string[] = ['Files']): Promise<RemoteFile[]> {\n const files: RemoteFile[] = [];\n\n try {\n const folder = await this.client.getItemByPath(folderPath);\n const folderContent = await this.client.folders.listItemsBatch([{ id: folder.id }]);\n\n for (const item of folderContent[0]?.items || []) {\n // Build path relative to Files folder\n const itemPath = [...folderPath.slice(1), item.name].join('/');\n\n if (item.type === farseer.FolderItemType.Folder) {\n // Recursively get files from subfolders\n const subFiles = await this.listFiles([...folderPath, item.name]);\n files.push(...subFiles);\n } else if (\n item.type === farseer.FolderItemType.FarseerFile &&\n 'reference' in item\n ) {\n // Accept all file types\n files.push({\n name: item.name,\n path: itemPath,\n reference: item.reference as string,\n });\n }\n }\n } catch (error) {\n // Folder might not exist\n return [];\n }\n\n return files;\n }\n\n async getFileContent(reference: string): Promise<string> {\n const blob = await this.client.farseerFiles.get(reference);\n return await blob.text();\n }\n\n async createFile(content: string | Buffer, fileName: string, folderPath: string[] = ['Files']): Promise<void> {\n // Ensure folder structure exists\n await this.ensureFolderPath(folderPath);\n\n const folder = await this.client.getItemByPath(folderPath);\n const blob = content instanceof Buffer ? new Blob([content]) : new Blob([content]);\n const file = new farseer.WebFile([blob], fileName);\n await this.client.farseerFiles.create(file, 'GENERAL', folder.id.toString());\n }\n\n async updateFile(reference: string, content: string | Buffer, fileName: string): Promise<void> {\n const blob = content instanceof Buffer ? new Blob([content]) : new Blob([content]);\n const file = new farseer.WebFile([blob], fileName);\n await this.client.farseerFiles.update(reference, file);\n }\n\n async deleteFile(reference: string): Promise<void> {\n await this.client.farseerFiles.remove(reference);\n }\n\n async ensureFolderPath(folderPath: string[]): Promise<void> {\n let currentPath: string[] = [];\n\n for (const segment of folderPath) {\n currentPath.push(segment);\n try {\n await this.client.getItemByPath(currentPath);\n } catch {\n // Folder doesn't exist, create it\n if (currentPath.length > 1) {\n const parentPath = currentPath.slice(0, -1);\n const parent = await this.client.getItemByPath(parentPath);\n await this.client.folders.create({\n parentId: parent.id,\n name: segment,\n allowedTypes: [],\n });\n }\n }\n }\n }\n\n async getFileByPath(filePath: string): Promise<RemoteFile | null> {\n const pathParts = ['Files', ...filePath.split('/')];\n const fileName = pathParts.pop()!;\n const folderPath = pathParts;\n\n try {\n const folder = await this.client.getItemByPath(folderPath);\n const folderContent = await this.client.folders.listItemsBatch([{ id: folder.id }]);\n\n for (const item of folderContent[0]?.items || []) {\n if (item.name === fileName && item.type === farseer.FolderItemType.FarseerFile && 'reference' in item) {\n return {\n name: item.name,\n path: filePath,\n reference: item.reference as string,\n };\n }\n }\n } catch {\n return null;\n }\n\n return null;\n }\n\n async getFileMetadata(reference: string): Promise<import('./farseerFactory').FileMetadata | null> {\n try {\n const metadata = await this.client.farseerFiles.getMetadata(reference);\n\n return {\n uploadTime: metadata.uploadTime?.toISOString(),\n uploader: metadata.uploader ? {\n id: metadata.uploader.id,\n email: metadata.uploader.email || '',\n firstName: metadata.uploader.firstName || '',\n lastName: metadata.uploader.lastName || '',\n } : undefined,\n size: metadata.size,\n };\n } catch (error) {\n // Metadata fetch failed - non-critical, continue without it\n return null;\n }\n }\n\n async getFileContentAsBuffer(reference: string): Promise<Buffer> {\n const blob = await this.client.farseerFiles.get(reference);\n const arrayBuffer = await blob.arrayBuffer();\n return Buffer.from(arrayBuffer);\n }\n\n isTextFile(filename: string): boolean {\n const ext = filename.toLowerCase().split('.').pop() || '';\n return ['ts', 'js', 'mjs', 'cjs', 'json', 'txt', 'md', 'csv', 'xml', 'html', 'css', 'yaml', 'yml'].includes(ext);\n }\n\n isBinaryFile(filename: string): boolean {\n return !this.isTextFile(filename);\n }\n\n // ==================== Apps (Remote Jobs) API ====================\n\n /**\n * List all apps (Remote Jobs) on the tenant\n */\n async listApps(): Promise<AppListItem[]> {\n const response = await this.apiRequest<RemoteAppBasic[]>('/remoteJobs', 'GET');\n return response.map((app) => ({\n id: app.id,\n name: app.name,\n type: 'REMOTE_JOB',\n reference: String(app.id),\n }));\n }\n\n /**\n * Get app details by reference ID\n */\n async getApp(referenceId: string): Promise<RemoteApp | null> {\n try {\n return await this.apiRequest<RemoteApp>(`/remoteJobs/${referenceId}`, 'GET');\n } catch {\n return null;\n }\n }\n\n /**\n * Get app by name\n */\n async getAppByName(name: string): Promise<RemoteApp | null> {\n const apps = await this.listApps();\n const app = apps.find((a) => a.name.toLowerCase() === name.toLowerCase());\n if (!app) {\n return null;\n }\n return this.getApp(app.reference);\n }\n\n /**\n * Create a new app\n */\n async createApp(name: string): Promise<RemoteApp> {\n // Get Apps folder ID first\n const appsFolderId = await this.getAppsFolderId();\n if (!appsFolderId) {\n throw new Error('Apps folder not found');\n }\n\n return await this.apiRequest<RemoteApp>('/remoteJobs', 'POST', {\n folderId: appsFolderId,\n name,\n });\n }\n\n /**\n * Update an app\n */\n async updateApp(\n referenceId: string,\n update: {\n name?: string;\n description?: string;\n expectedArguments?: AppArgument[];\n mainScriptFileId?: string | null;\n scriptFileIds?: string[];\n }\n ): Promise<RemoteApp> {\n await this.apiRequest(`/remoteJobs/${referenceId}`, 'PUT', {\n ...update,\n schedule: null,\n runnerName: null,\n predefinedActions: [],\n });\n // API returns 204 No Content, so fetch the updated app\n const app = await this.getApp(referenceId);\n if (!app) {\n throw new Error('Failed to fetch updated app');\n }\n return app;\n }\n\n /**\n * Delete an app\n */\n async deleteApp(referenceId: string): Promise<void> {\n await this.apiRequest(`/remoteJobs/${referenceId}`, 'DELETE');\n }\n\n /**\n * Get script files available for apps\n */\n async getScriptFiles(): Promise<{ id: string; name: string }[]> {\n const allFiles = await this.listFiles(['Files']);\n return allFiles\n .filter((f) => f.name.endsWith('.ts') || f.name.endsWith('.js'))\n .map((f) => ({\n id: f.reference,\n name: f.name,\n }));\n }\n\n /**\n * Export the entire model structure\n */\n async exportModel(): Promise<import('./farseerApi').ModelExport> {\n return this.apiRequest('/model/export', 'POST');\n }\n\n /**\n * Get the Apps folder ID\n */\n private async getAppsFolderId(): Promise<number | null> {\n try {\n const item = await this.client.getItemByPath(['Apps']);\n return item.id;\n } catch {\n return null;\n }\n }\n\n /**\n * Make a direct API request (for endpoints not in farseer-client)\n */\n private async apiRequest<T>(\n endpoint: string,\n method: 'GET' | 'POST' | 'PUT' | 'DELETE',\n body?: unknown\n ): Promise<T> {\n const url = `${this.credential.basePath}${endpoint}`;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'X-TENANT-ID': this.credential.tenantId,\n 'X-API-KEY': this.credential.apiKey,\n 'x-api-version': '3.3.0',\n };\n\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${await response.text()}`);\n }\n\n if (response.status === 204) {\n return undefined as unknown as T;\n }\n\n return response.json() as Promise<T>;\n }\n}\n\nexport async function loginWithPassword(\n email: string,\n password: string\n): Promise<{\n accessToken: string;\n refreshToken: string;\n expiresIn: number;\n} | null> {\n const tokenUrl = 'https://login.farseer.io/auth/realms/master/protocol/openid-connect/token';\n\n try {\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'password',\n client_id: 'security-admin-console',\n username: email,\n password: password,\n }),\n });\n\n if (!response.ok) {\n return null;\n }\n\n const data = (await response.json()) as {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n };\n return {\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresIn: data.expires_in,\n };\n } catch {\n return null;\n }\n}\n\nfunction getErrorPage(title: string, message: string): string {\n return `\n <html>\n <head>\n <title>Farseer CLI - Error</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: #fafafa;\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .container {\n text-align: center;\n padding: 48px;\n }\n .error-icon {\n width: 80px;\n height: 80px;\n border-radius: 50%;\n background: #ef4444;\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0 auto 24px;\n }\n .error-icon svg {\n width: 40px;\n height: 40px;\n stroke: white;\n stroke-width: 3;\n fill: none;\n }\n h1 {\n color: #18181b;\n font-size: 24px;\n font-weight: 600;\n margin-bottom: 8px;\n }\n p {\n color: #71717a;\n font-size: 15px;\n }\n </style>\n </head>\n <body>\n <div class=\"container\">\n <div class=\"error-icon\">\n <svg viewBox=\"0 0 24 24\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </div>\n <h1>${title}</h1>\n <p>${message}</p>\n <p style=\"margin-top: 16px;\">You can close this window.</p>\n </div>\n </body>\n </html>\n `;\n}\n\n// Browser-based OAuth login with PKCE\nexport async function loginWithBrowser(realm: string = 'master'): Promise<{\n accessToken: string;\n refreshToken: string;\n expiresIn: number;\n} | null> {\n const http = await import('http');\n const crypto = await import('crypto');\n const { exec } = await import('child_process');\n const { promisify } = await import('util');\n const execAsync = promisify(exec);\n\n const KEYCLOAK_BASE = `https://login.farseer.io/auth/realms/${realm}/protocol/openid-connect`;\n const CLIENT_ID = 'auth'; // Use auth client with full scope access\n const REDIRECT_PORT = 8787;\n const REDIRECT_URI = `http://localhost:${REDIRECT_PORT}/callback`;\n\n // Generate PKCE code verifier and challenge\n const codeVerifier = crypto.randomBytes(32).toString('base64url');\n const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('base64url');\n\n // Generate state for CSRF protection\n const state = crypto.randomBytes(16).toString('hex');\n\n return new Promise((resolve) => {\n const server = http.createServer(async (req, res) => {\n const url = new URL(req.url || '', `http://localhost:${REDIRECT_PORT}`);\n\n if (url.pathname === '/callback') {\n const code = url.searchParams.get('code');\n const returnedState = url.searchParams.get('state');\n const error = url.searchParams.get('error');\n\n if (error) {\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getErrorPage('Login Failed', `Error: ${error}`));\n server.close();\n resolve(null);\n return;\n }\n\n if (!code || returnedState !== state) {\n res.writeHead(400, { 'Content-Type': 'text/html' });\n res.end(getErrorPage('Invalid Response', 'Missing code or state mismatch.'));\n server.close();\n resolve(null);\n return;\n }\n\n // Exchange code for tokens\n try {\n const tokenResponse = await fetch(`${KEYCLOAK_BASE}/token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: CLIENT_ID,\n code: code,\n redirect_uri: REDIRECT_URI,\n code_verifier: codeVerifier,\n }),\n });\n\n if (!tokenResponse.ok) {\n throw new Error('Token exchange failed');\n }\n\n const data = (await tokenResponse.json()) as {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n };\n\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(`\n <html>\n <head>\n <title>Farseer CLI - Login</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: #fafafa;\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .container {\n text-align: center;\n padding: 48px;\n }\n .checkmark {\n width: 80px;\n height: 80px;\n border-radius: 50%;\n background: #22c55e;\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0 auto 24px;\n animation: scale-in 0.3s ease-out;\n }\n .checkmark svg {\n width: 40px;\n height: 40px;\n stroke: white;\n stroke-width: 3;\n fill: none;\n animation: draw 0.4s ease-out 0.2s forwards;\n stroke-dasharray: 50;\n stroke-dashoffset: 50;\n }\n @keyframes scale-in {\n from { transform: scale(0); }\n to { transform: scale(1); }\n }\n @keyframes draw {\n to { stroke-dashoffset: 0; }\n }\n h1 {\n color: #18181b;\n font-size: 24px;\n font-weight: 600;\n margin-bottom: 8px;\n }\n p {\n color: #71717a;\n font-size: 15px;\n }\n </style>\n </head>\n <body>\n <div class=\"container\">\n <div class=\"checkmark\">\n <svg viewBox=\"0 0 24 24\">\n <polyline points=\"20 6 9 17 4 12\"></polyline>\n </svg>\n </div>\n <h1>Login Successful</h1>\n <p>You can close this window and return to the terminal.</p>\n </div>\n </body>\n </html>\n `);\n\n server.close();\n resolve({\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresIn: data.expires_in,\n });\n } catch {\n res.writeHead(500, { 'Content-Type': 'text/html' });\n res.end(getErrorPage('Token Exchange Failed', 'Could not complete login.'));\n server.close();\n resolve(null);\n }\n }\n });\n\n server.listen(REDIRECT_PORT, async () => {\n // Build authorization URL\n const authUrl = new URL(`${KEYCLOAK_BASE}/auth`);\n authUrl.searchParams.set('client_id', CLIENT_ID);\n authUrl.searchParams.set('redirect_uri', REDIRECT_URI);\n authUrl.searchParams.set('response_type', 'code');\n authUrl.searchParams.set('scope', 'openid offline_access');\n authUrl.searchParams.set('state', state);\n authUrl.searchParams.set('code_challenge', codeChallenge);\n authUrl.searchParams.set('code_challenge_method', 'S256');\n\n // Open browser\n const url = authUrl.toString();\n try {\n if (process.platform === 'darwin') {\n await execAsync(`open \"${url}\"`);\n } else if (process.platform === 'win32') {\n await execAsync(`start \"${url}\"`);\n } else {\n await execAsync(`xdg-open \"${url}\"`);\n }\n } catch {\n // If we can't open browser, show URL to user\n console.log(`\\nOpen this URL in your browser:\\n${url}\\n`);\n }\n });\n\n // Timeout after 5 minutes\n setTimeout(() => {\n server.close();\n resolve(null);\n }, 5 * 60 * 1000);\n });\n}\n\nexport async function refreshAccessToken(refreshToken: string, realm: string = 'master'): Promise<{\n accessToken: string;\n refreshToken: string;\n expiresIn: number;\n} | null> {\n const tokenUrl = `https://login.farseer.io/auth/realms/${realm}/protocol/openid-connect/token`;\n\n try {\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: 'auth',\n refresh_token: refreshToken,\n }),\n });\n\n if (!response.ok) {\n return null;\n }\n\n const data = (await response.json()) as {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n };\n return {\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresIn: data.expires_in,\n };\n } catch {\n return null;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,31 @@
1
+ export declare class GitService {
2
+ private git;
3
+ private repoRoot;
4
+ constructor();
5
+ pull(): Promise<{
6
+ success: boolean;
7
+ message: string;
8
+ }>;
9
+ add(files: string[]): Promise<void>;
10
+ commit(message: string, author?: string): Promise<{
11
+ success: boolean;
12
+ commitHash?: string;
13
+ message: string;
14
+ }>;
15
+ push(): Promise<{
16
+ success: boolean;
17
+ message: string;
18
+ }>;
19
+ status(): Promise<{
20
+ isClean: boolean;
21
+ modified: string[];
22
+ added: string[];
23
+ deleted: string[];
24
+ untracked: string[];
25
+ }>;
26
+ hasUnpulledChanges(): Promise<boolean>;
27
+ hasUnpushedChanges(): Promise<boolean>;
28
+ getCurrentBranch(): Promise<string>;
29
+ getLastCommitMessage(): Promise<string>;
30
+ getRepoRoot(): string;
31
+ }
@@ -0,0 +1,134 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+ var gitService_exports = {};
29
+ __export(gitService_exports, {
30
+ GitService: () => GitService
31
+ });
32
+ module.exports = __toCommonJS(gitService_exports);
33
+ var import_simple_git = __toESM(require("simple-git"));
34
+ var path = __toESM(require("path"));
35
+ class GitService {
36
+ constructor() {
37
+ this.repoRoot = path.resolve(__dirname, "..", "..", "..");
38
+ this.git = (0, import_simple_git.default)(this.repoRoot);
39
+ }
40
+ async pull() {
41
+ try {
42
+ const result = await this.git.pull();
43
+ if (result.summary.changes === 0 && result.summary.insertions === 0 && result.summary.deletions === 0) {
44
+ return { success: true, message: "Already up to date." };
45
+ }
46
+ return {
47
+ success: true,
48
+ message: `Updated: ${result.summary.changes} files changed, ${result.summary.insertions} insertions, ${result.summary.deletions} deletions`
49
+ };
50
+ } catch (error) {
51
+ return {
52
+ success: false,
53
+ message: error instanceof Error ? error.message : "Git pull failed"
54
+ };
55
+ }
56
+ }
57
+ async add(files) {
58
+ await this.git.add(files);
59
+ }
60
+ async commit(message, author) {
61
+ try {
62
+ let result;
63
+ if (author) {
64
+ result = await this.git.commit(message, { "--author": author });
65
+ } else {
66
+ result = await this.git.commit(message);
67
+ }
68
+ return {
69
+ success: true,
70
+ commitHash: result.commit,
71
+ message: `Committed: ${result.commit}`
72
+ };
73
+ } catch (error) {
74
+ return {
75
+ success: false,
76
+ message: error instanceof Error ? error.message : "Git commit failed"
77
+ };
78
+ }
79
+ }
80
+ async push() {
81
+ try {
82
+ await this.git.push();
83
+ return { success: true, message: "Pushed to remote." };
84
+ } catch (error) {
85
+ return {
86
+ success: false,
87
+ message: error instanceof Error ? error.message : "Git push failed"
88
+ };
89
+ }
90
+ }
91
+ async status() {
92
+ const status = await this.git.status();
93
+ return {
94
+ isClean: status.isClean(),
95
+ modified: status.modified,
96
+ added: status.created,
97
+ deleted: status.deleted,
98
+ untracked: status.not_added
99
+ };
100
+ }
101
+ async hasUnpulledChanges() {
102
+ try {
103
+ await this.git.fetch();
104
+ const status = await this.git.status();
105
+ return status.behind > 0;
106
+ } catch {
107
+ return false;
108
+ }
109
+ }
110
+ async hasUnpushedChanges() {
111
+ try {
112
+ const status = await this.git.status();
113
+ return status.ahead > 0;
114
+ } catch {
115
+ return false;
116
+ }
117
+ }
118
+ async getCurrentBranch() {
119
+ const status = await this.git.status();
120
+ return status.current || "unknown";
121
+ }
122
+ async getLastCommitMessage() {
123
+ const log = await this.git.log({ maxCount: 1 });
124
+ return log.latest?.message || "";
125
+ }
126
+ getRepoRoot() {
127
+ return this.repoRoot;
128
+ }
129
+ }
130
+ // Annotate the CommonJS export names for ESM import in node:
131
+ 0 && (module.exports = {
132
+ GitService
133
+ });
134
+ //# sourceMappingURL=gitService.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/services/gitService.ts"],
4
+ "sourcesContent": ["import simpleGit, { SimpleGit } from 'simple-git';\nimport * as path from 'path';\n\nexport class GitService {\n private git: SimpleGit;\n private repoRoot: string;\n\n constructor() {\n // Navigate from cli/ to repo root\n this.repoRoot = path.resolve(__dirname, '..', '..', '..');\n this.git = simpleGit(this.repoRoot);\n }\n\n async pull(): Promise<{ success: boolean; message: string }> {\n try {\n const result = await this.git.pull();\n if (result.summary.changes === 0 && result.summary.insertions === 0 && result.summary.deletions === 0) {\n return { success: true, message: 'Already up to date.' };\n }\n return {\n success: true,\n message: `Updated: ${result.summary.changes} files changed, ${result.summary.insertions} insertions, ${result.summary.deletions} deletions`,\n };\n } catch (error) {\n return {\n success: false,\n message: error instanceof Error ? error.message : 'Git pull failed',\n };\n }\n }\n\n async add(files: string[]): Promise<void> {\n await this.git.add(files);\n }\n\n async commit(message: string, author?: string): Promise<{ success: boolean; commitHash?: string; message: string }> {\n try {\n let result;\n if (author) {\n result = await this.git.commit(message, { '--author': author });\n } else {\n result = await this.git.commit(message);\n }\n return {\n success: true,\n commitHash: result.commit,\n message: `Committed: ${result.commit}`,\n };\n } catch (error) {\n return {\n success: false,\n message: error instanceof Error ? error.message : 'Git commit failed',\n };\n }\n }\n\n async push(): Promise<{ success: boolean; message: string }> {\n try {\n await this.git.push();\n return { success: true, message: 'Pushed to remote.' };\n } catch (error) {\n return {\n success: false,\n message: error instanceof Error ? error.message : 'Git push failed',\n };\n }\n }\n\n async status(): Promise<{\n isClean: boolean;\n modified: string[];\n added: string[];\n deleted: string[];\n untracked: string[];\n }> {\n const status = await this.git.status();\n return {\n isClean: status.isClean(),\n modified: status.modified,\n added: status.created,\n deleted: status.deleted,\n untracked: status.not_added,\n };\n }\n\n async hasUnpulledChanges(): Promise<boolean> {\n try {\n await this.git.fetch();\n const status = await this.git.status();\n return status.behind > 0;\n } catch {\n return false;\n }\n }\n\n async hasUnpushedChanges(): Promise<boolean> {\n try {\n const status = await this.git.status();\n return status.ahead > 0;\n } catch {\n return false;\n }\n }\n\n async getCurrentBranch(): Promise<string> {\n const status = await this.git.status();\n return status.current || 'unknown';\n }\n\n async getLastCommitMessage(): Promise<string> {\n const log = await this.git.log({ maxCount: 1 });\n return log.latest?.message || '';\n }\n\n getRepoRoot(): string {\n return this.repoRoot;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["simpleGit"]
7
+ }
@@ -0,0 +1,44 @@
1
+ import { IFarseerClient } from './farseerFactory';
2
+ export interface SyncFileInfo {
3
+ localHash: string;
4
+ remoteHash: string;
5
+ }
6
+ export interface SyncState {
7
+ lastSync: string;
8
+ files: Record<string, SyncFileInfo>;
9
+ }
10
+ export interface SyncStatus {
11
+ modifiedLocally: string[];
12
+ modifiedRemotely: string[];
13
+ onlyLocal: string[];
14
+ onlyRemote: string[];
15
+ inSync: string[];
16
+ }
17
+ export declare class SyncService {
18
+ private tenant;
19
+ private farseerClient;
20
+ constructor(tenant: string, farseerClient: IFarseerClient);
21
+ loadSyncState(): SyncState;
22
+ saveSyncState(state: SyncState): void;
23
+ getStatus(options?: {
24
+ all?: boolean;
25
+ }): Promise<SyncStatus>;
26
+ pull(options?: {
27
+ all?: boolean;
28
+ }): Promise<{
29
+ downloaded: string[];
30
+ deleted: string[];
31
+ unchanged: string[];
32
+ }>;
33
+ push(): Promise<{
34
+ uploaded: string[];
35
+ updated: string[];
36
+ deleted: string[];
37
+ unchanged: string[];
38
+ }>;
39
+ checkRemoteChanges(): Promise<boolean>;
40
+ getFileDiff(filePath: string): Promise<{
41
+ localContent: string | null;
42
+ remoteContent: string | null;
43
+ }>;
44
+ }
@@ -0,0 +1,320 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+ var syncService_exports = {};
29
+ __export(syncService_exports, {
30
+ SyncService: () => SyncService
31
+ });
32
+ module.exports = __toCommonJS(syncService_exports);
33
+ var fs = __toESM(require("fs"));
34
+ var path = __toESM(require("path"));
35
+ var import_farseerFactory = require("./farseerFactory");
36
+ var import_helpers = require("../utils/helpers");
37
+ class SyncService {
38
+ constructor(tenant, farseerClient) {
39
+ this.tenant = tenant;
40
+ this.farseerClient = farseerClient;
41
+ }
42
+ loadSyncState() {
43
+ const syncFilePath = (0, import_helpers.getSyncFilePath)(this.tenant);
44
+ if (!fs.existsSync(syncFilePath)) {
45
+ return {
46
+ lastSync: "",
47
+ files: {}
48
+ };
49
+ }
50
+ try {
51
+ const content = fs.readFileSync(syncFilePath, "utf-8");
52
+ return JSON.parse(content);
53
+ } catch {
54
+ return {
55
+ lastSync: "",
56
+ files: {}
57
+ };
58
+ }
59
+ }
60
+ saveSyncState(state) {
61
+ const syncFilePath = (0, import_helpers.getSyncFilePath)(this.tenant);
62
+ (0, import_helpers.ensureDirectoryExists)(path.dirname(syncFilePath));
63
+ fs.writeFileSync(syncFilePath, JSON.stringify(state, null, 2), "utf-8");
64
+ }
65
+ async getStatus(options) {
66
+ const syncState = this.loadSyncState();
67
+ const filesDir = (0, import_helpers.getTenantFilesDir)(this.tenant);
68
+ let localFiles = (0, import_helpers.getAllFilesInDir)(filesDir);
69
+ if (!options?.all) {
70
+ localFiles = localFiles.filter(
71
+ (f) => import_farseerFactory.DEFAULT_SCRIPT_EXTENSIONS.some((ext) => f.toLowerCase().endsWith(ext))
72
+ );
73
+ }
74
+ const localFileHashes = {};
75
+ for (const file of localFiles) {
76
+ const fullPath = path.join(filesDir, file);
77
+ const content = fs.readFileSync(fullPath);
78
+ localFileHashes[file] = (0, import_helpers.calculateHash)(content);
79
+ }
80
+ let remoteFiles = await this.farseerClient.listFiles();
81
+ if (!options?.all) {
82
+ remoteFiles = remoteFiles.filter(
83
+ (f) => import_farseerFactory.DEFAULT_SCRIPT_EXTENSIONS.some((ext) => f.name.toLowerCase().endsWith(ext))
84
+ );
85
+ }
86
+ const remoteFileHashes = {};
87
+ for (const file of remoteFiles) {
88
+ const content = await this.farseerClient.getFileContentAsBuffer(file.reference);
89
+ remoteFileHashes[file.path] = (0, import_helpers.calculateHash)(content);
90
+ }
91
+ const status = {
92
+ modifiedLocally: [],
93
+ modifiedRemotely: [],
94
+ onlyLocal: [],
95
+ onlyRemote: [],
96
+ inSync: []
97
+ };
98
+ const allFiles = /* @__PURE__ */ new Set([...Object.keys(localFileHashes), ...Object.keys(remoteFileHashes)]);
99
+ for (const file of allFiles) {
100
+ const localHash = localFileHashes[file];
101
+ const remoteHash = remoteFileHashes[file];
102
+ const syncInfo = syncState.files[file];
103
+ if (localHash && remoteHash) {
104
+ if (localHash === remoteHash) {
105
+ status.inSync.push(file);
106
+ } else if (syncInfo) {
107
+ const localChanged = localHash !== syncInfo.localHash;
108
+ const remoteChanged = remoteHash !== syncInfo.remoteHash;
109
+ if (localChanged && remoteChanged) {
110
+ status.modifiedLocally.push(file);
111
+ status.modifiedRemotely.push(file);
112
+ } else if (localChanged) {
113
+ status.modifiedLocally.push(file);
114
+ } else if (remoteChanged) {
115
+ status.modifiedRemotely.push(file);
116
+ } else {
117
+ status.inSync.push(file);
118
+ }
119
+ } else {
120
+ status.modifiedLocally.push(file);
121
+ }
122
+ } else if (localHash && !remoteHash) {
123
+ if (syncInfo && syncInfo.remoteHash) {
124
+ status.modifiedRemotely.push(file);
125
+ } else {
126
+ status.onlyLocal.push(file);
127
+ }
128
+ } else if (!localHash && remoteHash) {
129
+ if (syncInfo && syncInfo.localHash) {
130
+ status.modifiedLocally.push(file);
131
+ } else {
132
+ status.onlyRemote.push(file);
133
+ }
134
+ }
135
+ }
136
+ return status;
137
+ }
138
+ async pull(options) {
139
+ const filesDir = (0, import_helpers.getTenantFilesDir)(this.tenant);
140
+ (0, import_helpers.ensureDirectoryExists)(filesDir);
141
+ const syncState = this.loadSyncState();
142
+ let remoteFiles = await this.farseerClient.listFiles();
143
+ if (!options?.all) {
144
+ remoteFiles = remoteFiles.filter(
145
+ (f) => import_farseerFactory.DEFAULT_SCRIPT_EXTENSIONS.some((ext) => f.name.toLowerCase().endsWith(ext))
146
+ );
147
+ }
148
+ remoteFiles = await this.fetchMetadataForFiles(remoteFiles);
149
+ const result = {
150
+ downloaded: [],
151
+ deleted: [],
152
+ unchanged: []
153
+ };
154
+ const remoteFilePaths = new Set(remoteFiles.map((f) => f.path));
155
+ for (const file of remoteFiles) {
156
+ const localPath = path.join(filesDir, file.path);
157
+ const content = await this.farseerClient.getFileContentAsBuffer(file.reference);
158
+ const remoteHash = (0, import_helpers.calculateHash)(content);
159
+ let needsUpdate = true;
160
+ if (fs.existsSync(localPath)) {
161
+ const localContent = fs.readFileSync(localPath);
162
+ const localHash = (0, import_helpers.calculateHash)(localContent);
163
+ if (localHash === remoteHash) {
164
+ needsUpdate = false;
165
+ result.unchanged.push(file.path);
166
+ }
167
+ }
168
+ if (needsUpdate) {
169
+ (0, import_helpers.ensureDirectoryExists)(path.dirname(localPath));
170
+ fs.writeFileSync(localPath, content);
171
+ result.downloaded.push(file.path);
172
+ }
173
+ syncState.files[file.path] = {
174
+ localHash: remoteHash,
175
+ remoteHash,
176
+ metadata: file.metadata ? {
177
+ uploadTime: file.metadata.uploadTime,
178
+ uploaderEmail: file.metadata.uploader?.email,
179
+ uploaderName: file.metadata.uploader ? `${file.metadata.uploader.firstName} ${file.metadata.uploader.lastName}`.trim() : void 0,
180
+ lastFetched: (/* @__PURE__ */ new Date()).toISOString()
181
+ } : void 0
182
+ };
183
+ }
184
+ const localFiles = (0, import_helpers.getAllFilesInDir)(filesDir);
185
+ for (const localFile of localFiles) {
186
+ if (!remoteFilePaths.has(localFile)) {
187
+ const syncInfo = syncState.files[localFile];
188
+ if (syncInfo) {
189
+ const localPath = path.join(filesDir, localFile);
190
+ fs.unlinkSync(localPath);
191
+ result.deleted.push(localFile);
192
+ delete syncState.files[localFile];
193
+ }
194
+ }
195
+ }
196
+ syncState.lastSync = (/* @__PURE__ */ new Date()).toISOString();
197
+ this.saveSyncState(syncState);
198
+ return result;
199
+ }
200
+ async push() {
201
+ const filesDir = (0, import_helpers.getTenantFilesDir)(this.tenant);
202
+ const syncState = this.loadSyncState();
203
+ const result = {
204
+ uploaded: [],
205
+ updated: [],
206
+ deleted: [],
207
+ unchanged: []
208
+ };
209
+ const remoteFiles = await this.farseerClient.listFiles();
210
+ const remoteFileMap = /* @__PURE__ */ new Map();
211
+ for (const file of remoteFiles) {
212
+ remoteFileMap.set(file.path, file);
213
+ }
214
+ const localFiles = (0, import_helpers.getAllFilesInDir)(filesDir);
215
+ for (const localFile of localFiles) {
216
+ const localPath = path.join(filesDir, localFile);
217
+ const content = fs.readFileSync(localPath);
218
+ const localHash = (0, import_helpers.calculateHash)(content);
219
+ const remoteFile = remoteFileMap.get(localFile);
220
+ if (remoteFile) {
221
+ const remoteContent = await this.farseerClient.getFileContentAsBuffer(remoteFile.reference);
222
+ const remoteHash = (0, import_helpers.calculateHash)(remoteContent);
223
+ if (localHash !== remoteHash) {
224
+ await this.farseerClient.updateFile(remoteFile.reference, content, path.basename(localFile));
225
+ result.updated.push(localFile);
226
+ } else {
227
+ result.unchanged.push(localFile);
228
+ }
229
+ } else {
230
+ const pathParts = localFile.split("/");
231
+ const fileName = pathParts.pop();
232
+ const folderPath = ["Files", ...pathParts];
233
+ await this.farseerClient.createFile(content, fileName, folderPath);
234
+ result.uploaded.push(localFile);
235
+ }
236
+ syncState.files[localFile] = {
237
+ localHash,
238
+ remoteHash: localHash
239
+ };
240
+ }
241
+ const localFileSet = new Set(localFiles);
242
+ for (const [remotePath, remoteFile] of remoteFileMap) {
243
+ if (!localFileSet.has(remotePath)) {
244
+ const syncInfo = syncState.files[remotePath];
245
+ if (syncInfo) {
246
+ await this.farseerClient.deleteFile(remoteFile.reference);
247
+ result.deleted.push(remotePath);
248
+ delete syncState.files[remotePath];
249
+ }
250
+ }
251
+ }
252
+ syncState.lastSync = (/* @__PURE__ */ new Date()).toISOString();
253
+ this.saveSyncState(syncState);
254
+ return result;
255
+ }
256
+ async checkRemoteChanges() {
257
+ const syncState = this.loadSyncState();
258
+ let remoteFiles = await this.farseerClient.listFiles();
259
+ remoteFiles = remoteFiles.filter(
260
+ (f) => import_farseerFactory.DEFAULT_SCRIPT_EXTENSIONS.some((ext) => f.name.toLowerCase().endsWith(ext))
261
+ );
262
+ for (const file of remoteFiles) {
263
+ const content = await this.farseerClient.getFileContentAsBuffer(file.reference);
264
+ const remoteHash = (0, import_helpers.calculateHash)(content);
265
+ const syncInfo = syncState.files[file.path];
266
+ if (!syncInfo || syncInfo.remoteHash !== remoteHash) {
267
+ return true;
268
+ }
269
+ }
270
+ for (const filePath of Object.keys(syncState.files)) {
271
+ const isScript = import_farseerFactory.DEFAULT_SCRIPT_EXTENSIONS.some((ext) => filePath.toLowerCase().endsWith(ext));
272
+ if (!isScript) continue;
273
+ const exists = remoteFiles.some((f) => f.path === filePath);
274
+ if (!exists && syncState.files[filePath].remoteHash) {
275
+ return true;
276
+ }
277
+ }
278
+ return false;
279
+ }
280
+ async getFileDiff(filePath) {
281
+ const filesDir = (0, import_helpers.getTenantFilesDir)(this.tenant);
282
+ const localPath = path.join(filesDir, filePath);
283
+ let localContent = null;
284
+ let remoteContent = null;
285
+ if (fs.existsSync(localPath)) {
286
+ localContent = fs.readFileSync(localPath, "utf-8");
287
+ }
288
+ const remoteFile = await this.farseerClient.getFileByPath(filePath);
289
+ if (remoteFile) {
290
+ remoteContent = await this.farseerClient.getFileContent(remoteFile.reference);
291
+ }
292
+ return { localContent, remoteContent };
293
+ }
294
+ /**
295
+ * Batch fetch metadata for multiple files in parallel.
296
+ * Fetches metadata in batches of 10 to avoid overwhelming the API.
297
+ * Non-critical operation - failures are silently ignored.
298
+ */
299
+ async fetchMetadataForFiles(files) {
300
+ const batchSize = 10;
301
+ for (let i = 0; i < files.length; i += batchSize) {
302
+ const batch = files.slice(i, i + batchSize);
303
+ await Promise.all(batch.map(async (file) => {
304
+ try {
305
+ const metadata = await this.farseerClient.getFileMetadata(file.reference);
306
+ if (metadata) {
307
+ file.metadata = metadata;
308
+ }
309
+ } catch {
310
+ }
311
+ }));
312
+ }
313
+ return files;
314
+ }
315
+ }
316
+ // Annotate the CommonJS export names for ESM import in node:
317
+ 0 && (module.exports = {
318
+ SyncService
319
+ });
320
+ //# sourceMappingURL=syncService.js.map