farseer-cli 1.0.0 → 1.0.1

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.
@@ -32,8 +32,11 @@ __export(farseerApi_exports, {
32
32
  module.exports = __toCommonJS(farseerApi_exports);
33
33
  var import_axios = __toESM(require("axios"));
34
34
  var import_configService = require("./configService");
35
+ var import_farseerService = require("./farseerService");
35
36
  class FarseerApi {
36
37
  constructor(accessToken, tenant, tenantId) {
38
+ this.isRefreshing = false;
39
+ this.refreshSubscribers = [];
37
40
  this.tenant = tenant;
38
41
  this.tenantId = tenantId || tenant;
39
42
  const credential = (0, import_configService.getCredential)(tenant);
@@ -47,6 +50,51 @@ class FarseerApi {
47
50
  "X-TENANT-ID": this.tenantId
48
51
  }
49
52
  });
53
+ this.client.interceptors.response.use(
54
+ (response) => response,
55
+ async (error) => {
56
+ const originalRequest = error.config;
57
+ if (error.response?.status === 401 && !originalRequest._retry) {
58
+ originalRequest._retry = true;
59
+ if (this.isRefreshing) {
60
+ return new Promise((resolve) => {
61
+ this.refreshSubscribers.push((token) => {
62
+ originalRequest.headers["Authorization"] = `Bearer ${token}`;
63
+ resolve(this.client(originalRequest));
64
+ });
65
+ });
66
+ }
67
+ this.isRefreshing = true;
68
+ try {
69
+ const auth = (0, import_configService.getUserAuth)();
70
+ if (!auth?.refreshToken) {
71
+ throw new Error("No refresh token available");
72
+ }
73
+ const refreshed = await (0, import_farseerService.refreshAccessToken)(auth.refreshToken, auth.realm || "master");
74
+ if (!refreshed) {
75
+ throw new Error("Token refresh failed");
76
+ }
77
+ (0, import_configService.setUserAuth)({
78
+ accessToken: refreshed.accessToken,
79
+ refreshToken: refreshed.refreshToken,
80
+ expiresAt: new Date(Date.now() + refreshed.expiresIn * 1e3).toISOString(),
81
+ realm: auth.realm
82
+ });
83
+ this.updateAccessToken(refreshed.accessToken);
84
+ this.refreshSubscribers.forEach((callback) => callback(refreshed.accessToken));
85
+ this.refreshSubscribers = [];
86
+ originalRequest.headers["Authorization"] = `Bearer ${refreshed.accessToken}`;
87
+ return this.client(originalRequest);
88
+ } catch (refreshError) {
89
+ this.refreshSubscribers = [];
90
+ throw error;
91
+ } finally {
92
+ this.isRefreshing = false;
93
+ }
94
+ }
95
+ return Promise.reject(error);
96
+ }
97
+ );
50
98
  }
51
99
  /**
52
100
  * Update the access token (used when token is refreshed)
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/services/farseerApi.ts"],
4
- "sourcesContent": ["/**\n * FarseerApi - Direct HTTP API client using JWT Bearer token authentication\n *\n * This is an alternative to FarseerService that doesn't use farseer-client library.\n * Use this when authenticating via browser login (Keycloak JWT token) instead of API key.\n */\n\nimport axios, { AxiosInstance } from 'axios';\nimport { getCredential } from './configService';\n\nexport interface RemoteFile {\n name: string;\n path: string;\n reference: string;\n}\n\ninterface FolderItem {\n id: number;\n name: string;\n type: 'folder' | 'farseer-file';\n reference?: string;\n}\n\ninterface FolderContent {\n items: FolderItem[];\n}\n\n// App (Remote Job) types\nexport interface AppArgument {\n name: string;\n type: 'variable';\n defaultValue: string;\n}\n\nexport interface AppScriptFile {\n id: string;\n name: string;\n}\n\nexport interface RemoteApp {\n id: number;\n name: string;\n description: string;\n expectedArguments: AppArgument[];\n status: string;\n lastHeartbeat: string;\n mainScriptFileId: string | null;\n scriptFiles: AppScriptFile[];\n creationType: string;\n predefinedActions: unknown[];\n}\n\nexport interface AppListItem {\n id: number;\n name: string;\n type: string;\n reference: string;\n}\n\nexport class FarseerApi {\n private client: AxiosInstance;\n private tenant: string;\n private tenantId: string;\n\n constructor(accessToken: string, tenant: string, tenantId?: string) {\n this.tenant = tenant;\n this.tenantId = tenantId || tenant;\n\n // Try to get basePath from existing config, or generate from tenant name\n const credential = getCredential(tenant);\n const basePath = credential?.basePath || `https://${tenant}.farseer.io/api/v3`;\n\n this.client = axios.create({\n baseURL: basePath,\n headers: {\n 'Authorization': `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'x-api-version': '3.3.0',\n 'X-TENANT-ID': this.tenantId,\n },\n });\n }\n\n /**\n * Update the access token (used when token is refreshed)\n */\n updateAccessToken(newToken: string): void {\n this.client.defaults.headers['Authorization'] = `Bearer ${newToken}`;\n }\n\n async testConnection(): Promise<boolean> {\n try {\n await this.getItemByPath(['Files']);\n return true;\n } catch {\n return false;\n }\n }\n\n async getFilesFolder(): Promise<{ id: number; name: string; type: string } | null> {\n try {\n const item = await this.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 private async listFolderItems(folderId: number | null): Promise<FolderContent> {\n // Body is array: [{ id: null }] for root, [{ id: folderId }] for specific folder\n const response = await this.client.post('/folders/items', [{ id: folderId }]);\n return response.data[0] || { items: [] };\n }\n\n private async findFolderByName(parentId: number | null, name: string): Promise<FolderItem | null> {\n const content = await this.listFolderItems(parentId);\n const folder = content.items?.find(\n (item) => item.name.toLowerCase() === name.toLowerCase() && item.type.toLowerCase() === 'folder'\n );\n return folder || null;\n }\n\n private async getItemByPath(path: string[]): Promise<FolderItem> {\n let currentId: number | null = null;\n\n for (const segment of path) {\n const folder = await this.findFolderByName(currentId, segment);\n if (!folder) {\n throw new Error(`Folder not found: ${segment}`);\n }\n currentId = folder.id;\n }\n\n if (currentId === null) {\n throw new Error('Path is empty');\n }\n\n // Return a minimal FolderItem with the found id\n return { id: currentId, name: path[path.length - 1], type: 'folder' };\n }\n\n async listFiles(folderPath: string[] = ['Files']): Promise<RemoteFile[]> {\n const files: RemoteFile[] = [];\n\n try {\n const folder = await this.getItemByPath(folderPath);\n const folderContent = await this.listFolderItems(folder.id);\n\n for (const item of folderContent.items || []) {\n const itemPath = [...folderPath.slice(1), item.name].join('/');\n const itemType = item.type.toLowerCase();\n\n if (itemType === 'folder') {\n const subFiles = await this.listFiles([...folderPath, item.name]);\n files.push(...subFiles);\n } else if ((itemType === 'farseer-file' || itemType === 'farseer_file') && item.reference) {\n files.push({\n name: item.name,\n path: itemPath,\n reference: item.reference,\n });\n }\n }\n } catch (error) {\n if (axios.isAxiosError(error)) {\n const status = error.response?.status;\n const data = error.response?.data;\n const message = typeof data === 'string' ? data : (data?.message || JSON.stringify(data));\n console.error(`API Error: ${status} \"${message}\" (path: ${folderPath.join('/')})`);\n if (data?.code === 'Forbidden') {\n const errorMessage = data?.message || 'Access denied';\n throw new Error(`Access denied: ${errorMessage}\\nYou may need to use --tenant-id to specify a different tenant ID.`);\n }\n }\n return [];\n }\n\n return files;\n }\n\n async getFileContent(reference: string): Promise<string> {\n const response = await this.client.get(`/farseerFiles/${reference}`, {\n responseType: 'text',\n });\n return response.data;\n }\n\n async getFileContentAsBuffer(reference: string): Promise<Buffer> {\n const response = await this.client.get(`/farseerFiles/${reference}`, {\n responseType: 'arraybuffer',\n });\n return Buffer.from(response.data);\n }\n\n async createFile(content: string | Buffer, fileName: string, folderPath: string[] = ['Files']): Promise<void> {\n await this.ensureFolderPath(folderPath);\n\n const folder = await this.getItemByPath(folderPath);\n\n const formData = new FormData();\n const blob = content instanceof Buffer ? new Blob([content]) : new Blob([content]);\n formData.append('file', blob, fileName);\n formData.append('category', 'GENERAL');\n formData.append('folderId', folder.id.toString());\n\n await this.client.post('/farseerFiles', formData, {\n headers: {\n 'Content-Type': 'multipart/form-data',\n },\n });\n }\n\n async updateFile(reference: string, content: string | Buffer, fileName: string): Promise<void> {\n const formData = new FormData();\n const blob = content instanceof Buffer ? new Blob([content]) : new Blob([content]);\n formData.append('file', blob, fileName);\n\n await this.client.put(`/farseerFiles/${reference}`, formData, {\n headers: {\n 'Content-Type': 'multipart/form-data',\n },\n });\n }\n\n async deleteFile(reference: string): Promise<void> {\n await this.client.delete(`/farseerFiles/${reference}`);\n }\n\n private 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.getItemByPath(currentPath);\n } catch {\n if (currentPath.length > 1) {\n const parentPath = currentPath.slice(0, -1);\n const parent = await this.getItemByPath(parentPath);\n await this.client.post('/folders', {\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.getItemByPath(folderPath);\n const folderContent = await this.listFolderItems(folder.id);\n\n for (const item of folderContent.items || []) {\n const itemType = item.type.toLowerCase();\n if (item.name === fileName && (itemType === 'farseer-file' || itemType === 'farseer_file') && item.reference) {\n return {\n name: item.name,\n path: filePath,\n reference: item.reference,\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 response = await this.axios.get(`/farseerFiles/${reference}/metadata`);\n const data = response.data;\n\n return {\n uploadTime: data.uploadTime,\n uploader: data.uploader ? {\n id: data.uploader.id,\n email: data.uploader.email || '',\n firstName: data.uploader.firstName || '',\n lastName: data.uploader.lastName || '',\n } : undefined,\n size: data.size,\n };\n } catch (error) {\n // Metadata fetch failed - non-critical, continue without it\n return null;\n }\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 * Get the Apps folder ID\n */\n async getAppsFolderId(): Promise<number | null> {\n const content = await this.listFolderItems(null);\n const appsFolder = content.items?.find(\n (item) => item.name.toLowerCase() === 'apps' && item.type.toLowerCase() === 'folder'\n );\n return appsFolder?.id || null;\n }\n\n /**\n * List all apps (Remote Jobs) on the tenant, including those in subfolders\n */\n async listApps(): Promise<AppListItem[]> {\n const appsFolderId = await this.getAppsFolderId();\n if (!appsFolderId) {\n return [];\n }\n\n return this.listAppsRecursive(appsFolderId);\n }\n\n /**\n * Recursively list all apps in a folder and its subfolders\n */\n private async listAppsRecursive(folderId: number): Promise<AppListItem[]> {\n const apps: AppListItem[] = [];\n const content = await this.listFolderItems(folderId);\n\n for (const item of content.items || []) {\n const itemType = item.type.toLowerCase();\n\n if (itemType === 'remote_job') {\n // Found an app\n apps.push({\n id: item.id,\n name: item.name,\n type: item.type,\n reference: item.reference || '',\n });\n } else if (itemType === 'folder') {\n // Recurse into subfolder\n const subApps = await this.listAppsRecursive(item.id);\n apps.push(...subApps);\n }\n }\n\n return apps;\n }\n\n /**\n * Get app details by reference ID\n */\n async getApp(referenceId: string): Promise<RemoteApp | null> {\n try {\n const response = await this.client.get(`/remoteJobs/${referenceId}`);\n return response.data;\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 const appsFolderId = await this.getAppsFolderId();\n if (!appsFolderId) {\n throw new Error('Apps folder not found');\n }\n\n const response = await this.client.post('/remoteJobs', {\n folderId: appsFolderId,\n name,\n });\n return response.data;\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.client.put(`/remoteJobs/${referenceId}`, {\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.client.delete(`/remoteJobs/${referenceId}`);\n }\n\n /**\n * Run an app with arguments\n */\n async runApp(\n referenceId: string,\n args: Record<string, string> = {}\n ): Promise<{ executionId: string }> {\n const response = await this.client.post(`/remoteJobs/${referenceId}/run`, {\n arguments: args,\n });\n return response.data;\n }\n\n /**\n * Get script files available for apps (searches all .ts/.js files in Files folder)\n * Returns full path relative to Files/ folder (e.g., \"Scripts/test.ts\", \"Scripts/subfolder/helper.ts\")\n */\n async getScriptFiles(): Promise<AppScriptFile[]> {\n // listFiles already searches recursively, so just filter for .ts/.js files\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.path, // Use full path to support scripts in subfolders\n }));\n }\n\n // ==================== Model Export API ====================\n\n /**\n * Export the entire model structure - all dimension tables, variables, and configurations.\n * Essential for understanding a tenant's data model.\n */\n async exportModel(): Promise<ModelExport> {\n const response = await this.client.post('/model/export');\n return response.data;\n }\n}\n\n// Model Export types\nexport interface ModelExport {\n tables: ModelExportTable[];\n variables: ModelExportVariable[];\n dependencies: {\n tables: string[];\n members: string[];\n variables: string[];\n };\n}\n\nexport interface ModelExportTable {\n name: string;\n columns: ModelExportColumn[];\n defaultMember?: string | null;\n folderPath?: string[];\n rows: any[][];\n autoIncrementTemplate?: string | null;\n}\n\nexport interface ModelExportColumn {\n name?: string;\n type: string; // PRIMARY_KEY, FORMULA, ORDER, ACTIVE, DESCRIPTION, FOREIGN_KEY, NUMBER, TEXT, DATE, FILE\n foreignKeyTableName?: string;\n}\n\nexport interface ModelExportVariable {\n name: string;\n description?: string | null;\n formula?: string | null;\n tables: string[];\n members: string[];\n rollupType: string; // SUM, AVERAGE, CLOSING STATE, BEGINNING STATE, FORMULA, MAX, MIN\n dataType: string; // DECIMAL, INTEGER\n numberFormat?: {\n type: string; // NUMBER, CURRENCY, PERCENTAGE, DATE, CUSTOM\n custom?: boolean;\n formatString?: string;\n decimals?: number;\n };\n numberValidation?: {\n min?: number | null;\n max?: number | null;\n };\n topDownMapping?: string | null;\n readonly: boolean;\n calculateOnDemand: boolean;\n zConstraint?: boolean;\n active?: boolean;\n optimizeCalculation?: boolean;\n useHierarchy?: boolean;\n folderPath?: string[];\n cells?: ModelExportCell[];\n}\n\nexport interface ModelExportCell {\n value: number;\n members: string[];\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * FarseerApi - Direct HTTP API client using JWT Bearer token authentication\n *\n * This is an alternative to FarseerService that doesn't use farseer-client library.\n * Use this when authenticating via browser login (Keycloak JWT token) instead of API key.\n */\n\nimport axios, { AxiosInstance, InternalAxiosRequestConfig } from 'axios';\nimport { getCredential, getUserAuth, setUserAuth } from './configService';\nimport { refreshAccessToken } from './farseerService';\n\nexport interface RemoteFile {\n name: string;\n path: string;\n reference: string;\n}\n\ninterface FolderItem {\n id: number;\n name: string;\n type: 'folder' | 'farseer-file';\n reference?: string;\n}\n\ninterface FolderContent {\n items: FolderItem[];\n}\n\n// App (Remote Job) types\nexport interface AppArgument {\n name: string;\n type: 'variable';\n defaultValue: string;\n}\n\nexport interface AppScriptFile {\n id: string;\n name: string;\n}\n\nexport interface RemoteApp {\n id: number;\n name: string;\n description: string;\n expectedArguments: AppArgument[];\n status: string;\n lastHeartbeat: string;\n mainScriptFileId: string | null;\n scriptFiles: AppScriptFile[];\n creationType: string;\n predefinedActions: unknown[];\n}\n\nexport interface AppListItem {\n id: number;\n name: string;\n type: string;\n reference: string;\n}\n\nexport class FarseerApi {\n private client: AxiosInstance;\n private tenant: string;\n private tenantId: string;\n\n private isRefreshing = false;\n private refreshSubscribers: ((token: string) => void)[] = [];\n\n constructor(accessToken: string, tenant: string, tenantId?: string) {\n this.tenant = tenant;\n this.tenantId = tenantId || tenant;\n\n // Try to get basePath from existing config, or generate from tenant name\n const credential = getCredential(tenant);\n const basePath = credential?.basePath || `https://${tenant}.farseer.io/api/v3`;\n\n this.client = axios.create({\n baseURL: basePath,\n headers: {\n 'Authorization': `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'x-api-version': '3.3.0',\n 'X-TENANT-ID': this.tenantId,\n },\n });\n\n // Add response interceptor for automatic token refresh on 401\n this.client.interceptors.response.use(\n (response) => response,\n async (error) => {\n const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean };\n\n // Only handle 401 errors and avoid infinite loops\n if (error.response?.status === 401 && !originalRequest._retry) {\n originalRequest._retry = true;\n\n // If already refreshing, wait for the new token\n if (this.isRefreshing) {\n return new Promise((resolve) => {\n this.refreshSubscribers.push((token: string) => {\n originalRequest.headers['Authorization'] = `Bearer ${token}`;\n resolve(this.client(originalRequest));\n });\n });\n }\n\n this.isRefreshing = true;\n\n try {\n const auth = getUserAuth();\n if (!auth?.refreshToken) {\n throw new Error('No refresh token available');\n }\n\n const refreshed = await refreshAccessToken(auth.refreshToken, auth.realm || 'master');\n if (!refreshed) {\n throw new Error('Token refresh failed');\n }\n\n // Save the new token\n setUserAuth({\n accessToken: refreshed.accessToken,\n refreshToken: refreshed.refreshToken,\n expiresAt: new Date(Date.now() + refreshed.expiresIn * 1000).toISOString(),\n realm: auth.realm,\n });\n\n // Update this client's token\n this.updateAccessToken(refreshed.accessToken);\n\n // Notify all waiting requests\n this.refreshSubscribers.forEach((callback) => callback(refreshed.accessToken));\n this.refreshSubscribers = [];\n\n // Retry the original request\n originalRequest.headers['Authorization'] = `Bearer ${refreshed.accessToken}`;\n return this.client(originalRequest);\n } catch (refreshError) {\n // Token refresh failed - let the error propagate\n this.refreshSubscribers = [];\n throw error;\n } finally {\n this.isRefreshing = false;\n }\n }\n\n return Promise.reject(error);\n }\n );\n }\n\n /**\n * Update the access token (used when token is refreshed)\n */\n updateAccessToken(newToken: string): void {\n this.client.defaults.headers['Authorization'] = `Bearer ${newToken}`;\n }\n\n async testConnection(): Promise<boolean> {\n try {\n await this.getItemByPath(['Files']);\n return true;\n } catch {\n return false;\n }\n }\n\n async getFilesFolder(): Promise<{ id: number; name: string; type: string } | null> {\n try {\n const item = await this.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 private async listFolderItems(folderId: number | null): Promise<FolderContent> {\n // Body is array: [{ id: null }] for root, [{ id: folderId }] for specific folder\n const response = await this.client.post('/folders/items', [{ id: folderId }]);\n return response.data[0] || { items: [] };\n }\n\n private async findFolderByName(parentId: number | null, name: string): Promise<FolderItem | null> {\n const content = await this.listFolderItems(parentId);\n const folder = content.items?.find(\n (item) => item.name.toLowerCase() === name.toLowerCase() && item.type.toLowerCase() === 'folder'\n );\n return folder || null;\n }\n\n private async getItemByPath(path: string[]): Promise<FolderItem> {\n let currentId: number | null = null;\n\n for (const segment of path) {\n const folder = await this.findFolderByName(currentId, segment);\n if (!folder) {\n throw new Error(`Folder not found: ${segment}`);\n }\n currentId = folder.id;\n }\n\n if (currentId === null) {\n throw new Error('Path is empty');\n }\n\n // Return a minimal FolderItem with the found id\n return { id: currentId, name: path[path.length - 1], type: 'folder' };\n }\n\n async listFiles(folderPath: string[] = ['Files']): Promise<RemoteFile[]> {\n const files: RemoteFile[] = [];\n\n try {\n const folder = await this.getItemByPath(folderPath);\n const folderContent = await this.listFolderItems(folder.id);\n\n for (const item of folderContent.items || []) {\n const itemPath = [...folderPath.slice(1), item.name].join('/');\n const itemType = item.type.toLowerCase();\n\n if (itemType === 'folder') {\n const subFiles = await this.listFiles([...folderPath, item.name]);\n files.push(...subFiles);\n } else if ((itemType === 'farseer-file' || itemType === 'farseer_file') && item.reference) {\n files.push({\n name: item.name,\n path: itemPath,\n reference: item.reference,\n });\n }\n }\n } catch (error) {\n if (axios.isAxiosError(error)) {\n const status = error.response?.status;\n const data = error.response?.data;\n const message = typeof data === 'string' ? data : (data?.message || JSON.stringify(data));\n console.error(`API Error: ${status} \"${message}\" (path: ${folderPath.join('/')})`);\n if (data?.code === 'Forbidden') {\n const errorMessage = data?.message || 'Access denied';\n throw new Error(`Access denied: ${errorMessage}\\nYou may need to use --tenant-id to specify a different tenant ID.`);\n }\n }\n return [];\n }\n\n return files;\n }\n\n async getFileContent(reference: string): Promise<string> {\n const response = await this.client.get(`/farseerFiles/${reference}`, {\n responseType: 'text',\n });\n return response.data;\n }\n\n async getFileContentAsBuffer(reference: string): Promise<Buffer> {\n const response = await this.client.get(`/farseerFiles/${reference}`, {\n responseType: 'arraybuffer',\n });\n return Buffer.from(response.data);\n }\n\n async createFile(content: string | Buffer, fileName: string, folderPath: string[] = ['Files']): Promise<void> {\n await this.ensureFolderPath(folderPath);\n\n const folder = await this.getItemByPath(folderPath);\n\n const formData = new FormData();\n const blob = content instanceof Buffer ? new Blob([content]) : new Blob([content]);\n formData.append('file', blob, fileName);\n formData.append('category', 'GENERAL');\n formData.append('folderId', folder.id.toString());\n\n await this.client.post('/farseerFiles', formData, {\n headers: {\n 'Content-Type': 'multipart/form-data',\n },\n });\n }\n\n async updateFile(reference: string, content: string | Buffer, fileName: string): Promise<void> {\n const formData = new FormData();\n const blob = content instanceof Buffer ? new Blob([content]) : new Blob([content]);\n formData.append('file', blob, fileName);\n\n await this.client.put(`/farseerFiles/${reference}`, formData, {\n headers: {\n 'Content-Type': 'multipart/form-data',\n },\n });\n }\n\n async deleteFile(reference: string): Promise<void> {\n await this.client.delete(`/farseerFiles/${reference}`);\n }\n\n private 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.getItemByPath(currentPath);\n } catch {\n if (currentPath.length > 1) {\n const parentPath = currentPath.slice(0, -1);\n const parent = await this.getItemByPath(parentPath);\n await this.client.post('/folders', {\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.getItemByPath(folderPath);\n const folderContent = await this.listFolderItems(folder.id);\n\n for (const item of folderContent.items || []) {\n const itemType = item.type.toLowerCase();\n if (item.name === fileName && (itemType === 'farseer-file' || itemType === 'farseer_file') && item.reference) {\n return {\n name: item.name,\n path: filePath,\n reference: item.reference,\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 response = await this.axios.get(`/farseerFiles/${reference}/metadata`);\n const data = response.data;\n\n return {\n uploadTime: data.uploadTime,\n uploader: data.uploader ? {\n id: data.uploader.id,\n email: data.uploader.email || '',\n firstName: data.uploader.firstName || '',\n lastName: data.uploader.lastName || '',\n } : undefined,\n size: data.size,\n };\n } catch (error) {\n // Metadata fetch failed - non-critical, continue without it\n return null;\n }\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 * Get the Apps folder ID\n */\n async getAppsFolderId(): Promise<number | null> {\n const content = await this.listFolderItems(null);\n const appsFolder = content.items?.find(\n (item) => item.name.toLowerCase() === 'apps' && item.type.toLowerCase() === 'folder'\n );\n return appsFolder?.id || null;\n }\n\n /**\n * List all apps (Remote Jobs) on the tenant, including those in subfolders\n */\n async listApps(): Promise<AppListItem[]> {\n const appsFolderId = await this.getAppsFolderId();\n if (!appsFolderId) {\n return [];\n }\n\n return this.listAppsRecursive(appsFolderId);\n }\n\n /**\n * Recursively list all apps in a folder and its subfolders\n */\n private async listAppsRecursive(folderId: number): Promise<AppListItem[]> {\n const apps: AppListItem[] = [];\n const content = await this.listFolderItems(folderId);\n\n for (const item of content.items || []) {\n const itemType = item.type.toLowerCase();\n\n if (itemType === 'remote_job') {\n // Found an app\n apps.push({\n id: item.id,\n name: item.name,\n type: item.type,\n reference: item.reference || '',\n });\n } else if (itemType === 'folder') {\n // Recurse into subfolder\n const subApps = await this.listAppsRecursive(item.id);\n apps.push(...subApps);\n }\n }\n\n return apps;\n }\n\n /**\n * Get app details by reference ID\n */\n async getApp(referenceId: string): Promise<RemoteApp | null> {\n try {\n const response = await this.client.get(`/remoteJobs/${referenceId}`);\n return response.data;\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 const appsFolderId = await this.getAppsFolderId();\n if (!appsFolderId) {\n throw new Error('Apps folder not found');\n }\n\n const response = await this.client.post('/remoteJobs', {\n folderId: appsFolderId,\n name,\n });\n return response.data;\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.client.put(`/remoteJobs/${referenceId}`, {\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.client.delete(`/remoteJobs/${referenceId}`);\n }\n\n /**\n * Run an app with arguments\n */\n async runApp(\n referenceId: string,\n args: Record<string, string> = {}\n ): Promise<{ executionId: string }> {\n const response = await this.client.post(`/remoteJobs/${referenceId}/run`, {\n arguments: args,\n });\n return response.data;\n }\n\n /**\n * Get script files available for apps (searches all .ts/.js files in Files folder)\n * Returns full path relative to Files/ folder (e.g., \"Scripts/test.ts\", \"Scripts/subfolder/helper.ts\")\n */\n async getScriptFiles(): Promise<AppScriptFile[]> {\n // listFiles already searches recursively, so just filter for .ts/.js files\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.path, // Use full path to support scripts in subfolders\n }));\n }\n\n // ==================== Model Export API ====================\n\n /**\n * Export the entire model structure - all dimension tables, variables, and configurations.\n * Essential for understanding a tenant's data model.\n */\n async exportModel(): Promise<ModelExport> {\n const response = await this.client.post('/model/export');\n return response.data;\n }\n}\n\n// Model Export types\nexport interface ModelExport {\n tables: ModelExportTable[];\n variables: ModelExportVariable[];\n dependencies: {\n tables: string[];\n members: string[];\n variables: string[];\n };\n}\n\nexport interface ModelExportTable {\n name: string;\n columns: ModelExportColumn[];\n defaultMember?: string | null;\n folderPath?: string[];\n rows: any[][];\n autoIncrementTemplate?: string | null;\n}\n\nexport interface ModelExportColumn {\n name?: string;\n type: string; // PRIMARY_KEY, FORMULA, ORDER, ACTIVE, DESCRIPTION, FOREIGN_KEY, NUMBER, TEXT, DATE, FILE\n foreignKeyTableName?: string;\n}\n\nexport interface ModelExportVariable {\n name: string;\n description?: string | null;\n formula?: string | null;\n tables: string[];\n members: string[];\n rollupType: string; // SUM, AVERAGE, CLOSING STATE, BEGINNING STATE, FORMULA, MAX, MIN\n dataType: string; // DECIMAL, INTEGER\n numberFormat?: {\n type: string; // NUMBER, CURRENCY, PERCENTAGE, DATE, CUSTOM\n custom?: boolean;\n formatString?: string;\n decimals?: number;\n };\n numberValidation?: {\n min?: number | null;\n max?: number | null;\n };\n topDownMapping?: string | null;\n readonly: boolean;\n calculateOnDemand: boolean;\n zConstraint?: boolean;\n active?: boolean;\n optimizeCalculation?: boolean;\n useHierarchy?: boolean;\n folderPath?: string[];\n cells?: ModelExportCell[];\n}\n\nexport interface ModelExportCell {\n value: number;\n members: string[];\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["axios"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "farseer-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "CLI tool for syncing Farseer App scripts between local repository and Farseer instances",
5
5
  "main": "dist/index.js",
6
6
  "bin": {