Some checks failed
CI Pipeline / Test and Build (20.x) (push) Failing after 3m22s
CI Pipeline / Code Quality Check (push) Failing after 11m52s
CI Pipeline / Security Audit (push) Failing after 11m53s
CI Pipeline / Test and Build (18.x) (push) Failing after 12m31s
CI Pipeline / Build Release Artifacts (push) Has been cancelled
CI Pipeline / Notification (push) Has been cancelled
367 lines
12 KiB
TypeScript
367 lines
12 KiB
TypeScript
import { exec } from 'child_process';
|
|
import { promisify } from 'util';
|
|
import * as path from 'path';
|
|
import * as fs from 'fs-extra';
|
|
import { DiagramFormat } from '../types/diagram-types.js';
|
|
|
|
const execAsync = promisify(exec);
|
|
|
|
// Functional types
|
|
type VSCodeConfig = Readonly<{
|
|
workspaceRoot: string;
|
|
extensionId: string;
|
|
}>;
|
|
|
|
type ExtensionStatus = Readonly<{
|
|
isInstalled: boolean;
|
|
isVSCodeAvailable: boolean;
|
|
version?: string;
|
|
}>;
|
|
|
|
type SetupResult = Readonly<{
|
|
success: boolean;
|
|
message: string;
|
|
}>;
|
|
|
|
type FileExtensionMap = Readonly<Record<DiagramFormat, string>>;
|
|
|
|
type PlatformCommandMap = Readonly<Record<string, string>>;
|
|
|
|
// Pure function to create VSCode configuration
|
|
export const createVSCodeConfig = (workspaceRoot?: string): VSCodeConfig => ({
|
|
workspaceRoot: workspaceRoot || process.cwd(),
|
|
extensionId: 'hediet.vscode-drawio'
|
|
});
|
|
|
|
// Pure function to get file extension mapping
|
|
const getFileExtensionMap = (): FileExtensionMap => ({
|
|
[DiagramFormat.DRAWIO]: 'drawio',
|
|
[DiagramFormat.DRAWIO_SVG]: 'drawio.svg',
|
|
[DiagramFormat.DRAWIO_PNG]: 'drawio.png',
|
|
[DiagramFormat.DIO]: 'dio',
|
|
[DiagramFormat.XML]: 'xml'
|
|
});
|
|
|
|
// Pure function to get file extension for diagram format
|
|
export const getFileExtension = (format: DiagramFormat): string => {
|
|
const extensionMap = getFileExtensionMap();
|
|
return extensionMap[format] || 'drawio';
|
|
};
|
|
|
|
// Pure function to get platform-specific file explorer commands
|
|
const getPlatformCommandMap = (targetPath: string): PlatformCommandMap => ({
|
|
'win32': `explorer "${targetPath}"`,
|
|
'darwin': `open "${targetPath}"`,
|
|
'linux': `xdg-open "${targetPath}"`
|
|
});
|
|
|
|
// Pure function to get platform-specific file explorer command
|
|
const getFileExplorerCommand = (targetPath: string): string => {
|
|
const platform = process.platform;
|
|
const commandMap = getPlatformCommandMap(targetPath);
|
|
|
|
const command = commandMap[platform];
|
|
if (!command) {
|
|
throw new Error(`Unsupported platform: ${platform}`);
|
|
}
|
|
|
|
return command;
|
|
};
|
|
|
|
// Higher-order function for command execution with error handling
|
|
const withCommandErrorHandling = <T extends any[], R>(
|
|
operation: (...args: T) => Promise<R>
|
|
) => async (...args: T): Promise<R> => {
|
|
try {
|
|
return await operation(...args);
|
|
} catch (error) {
|
|
throw new Error(`Command execution failed: ${error}`);
|
|
}
|
|
};
|
|
|
|
// Pure function to execute VSCode command
|
|
export const executeVSCodeCommand = withCommandErrorHandling(async (command: string, args?: readonly string[]): Promise<string> => {
|
|
const fullCommand = args ? `${command} ${args.join(' ')}` : command;
|
|
const { stdout } = await execAsync(fullCommand);
|
|
return stdout;
|
|
});
|
|
|
|
// Curried function to open diagram file in VSCode
|
|
export const openDiagramInVSCode = (config: VSCodeConfig) => async (filePath: string): Promise<void> => {
|
|
const absolutePath = path.isAbsolute(filePath)
|
|
? filePath
|
|
: path.resolve(config.workspaceRoot, filePath);
|
|
|
|
await executeVSCodeCommand('code', [absolutePath]);
|
|
};
|
|
|
|
// Curried function to check if draw.io extension is installed
|
|
export const isDrawioExtensionInstalled = (config: VSCodeConfig) => async (): Promise<boolean> => {
|
|
try {
|
|
const stdout = await executeVSCodeCommand('code', ['--list-extensions']);
|
|
return stdout.includes(config.extensionId);
|
|
} catch (error) {
|
|
console.warn('Could not check VSCode extensions:', error);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
// Curried function to install draw.io extension
|
|
export const installDrawioExtension = (config: VSCodeConfig) => async (): Promise<void> => {
|
|
await executeVSCodeCommand('code', ['--install-extension', config.extensionId]);
|
|
};
|
|
|
|
// Curried function to open VSCode workspace
|
|
export const openWorkspace = (config: VSCodeConfig) => async (): Promise<void> => {
|
|
await executeVSCodeCommand('code', [config.workspaceRoot]);
|
|
};
|
|
|
|
// Pure function to check if VSCode is available
|
|
export const isVSCodeAvailable = async (): Promise<boolean> => {
|
|
try {
|
|
await executeVSCodeCommand('code', ['--version']);
|
|
return true;
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
// Pure function to get VSCode version
|
|
export const getVSCodeVersion = async (): Promise<string> => {
|
|
const stdout = await executeVSCodeCommand('code', ['--version']);
|
|
return stdout.split('\n')[0];
|
|
};
|
|
|
|
// Curried function to create and open new diagram in VSCode
|
|
export const createAndOpenDiagram = (config: VSCodeConfig) =>
|
|
(fileName: string) =>
|
|
(content: string) =>
|
|
(format: DiagramFormat) =>
|
|
async (outputDir?: string): Promise<string> => {
|
|
const dir = outputDir || config.workspaceRoot;
|
|
const extension = getFileExtension(format);
|
|
const fullFileName = fileName.endsWith(extension) ? fileName : `${fileName}.${extension}`;
|
|
const filePath = path.join(dir, fullFileName);
|
|
|
|
// Write file
|
|
await fs.ensureDir(path.dirname(filePath));
|
|
await fs.writeFile(filePath, content, 'utf8');
|
|
|
|
// Open in VSCode
|
|
await openDiagramInVSCode(config)(filePath);
|
|
|
|
return filePath;
|
|
};
|
|
|
|
// Curried function to get workspace folders
|
|
export const getWorkspaceFolders = (config: VSCodeConfig) => async (): Promise<readonly string[]> => {
|
|
try {
|
|
// This would require VSCode API integration
|
|
// For now, return current workspace
|
|
return [config.workspaceRoot];
|
|
} catch (error) {
|
|
console.warn('Could not get workspace folders:', error);
|
|
return [config.workspaceRoot];
|
|
}
|
|
};
|
|
|
|
// Pure function to show notification (placeholder for VSCode API)
|
|
export const showNotification = async (
|
|
message: string,
|
|
type: 'info' | 'warning' | 'error' = 'info'
|
|
): Promise<void> => {
|
|
try {
|
|
// This would require VSCode extension API
|
|
// For now, just log to console
|
|
console.log(`[${type.toUpperCase()}] ${message}`);
|
|
} catch (error) {
|
|
console.warn('Could not show notification:', error);
|
|
}
|
|
};
|
|
|
|
// Curried function to open file explorer at specific path
|
|
export const openFileExplorer = (config: VSCodeConfig) => async (dirPath?: string): Promise<void> => {
|
|
const targetPath = dirPath || config.workspaceRoot;
|
|
const command = getFileExplorerCommand(targetPath);
|
|
await executeVSCodeCommand(command);
|
|
};
|
|
|
|
// Pure function to refresh workspace (placeholder for VSCode API)
|
|
export const refreshWorkspace = async (): Promise<void> => {
|
|
try {
|
|
// This would require VSCode API integration
|
|
// For now, just log
|
|
console.log('Workspace refresh requested');
|
|
} catch (error) {
|
|
console.warn('Could not refresh workspace:', error);
|
|
}
|
|
};
|
|
|
|
// Curried function to check VSCode extension status
|
|
export const checkExtensionStatus = (config: VSCodeConfig) => async (): Promise<ExtensionStatus> => {
|
|
const isVSCodeAvail = await isVSCodeAvailable();
|
|
|
|
if (!isVSCodeAvail) {
|
|
return {
|
|
isInstalled: false,
|
|
isVSCodeAvailable: false
|
|
};
|
|
}
|
|
|
|
const isInstalled = await isDrawioExtensionInstalled(config)();
|
|
const version = isVSCodeAvail ? await getVSCodeVersion() : undefined;
|
|
|
|
return {
|
|
isInstalled,
|
|
isVSCodeAvailable: isVSCodeAvail,
|
|
version
|
|
};
|
|
};
|
|
|
|
// Curried function to setup VSCode environment for draw.io
|
|
export const setupVSCodeEnvironment = (config: VSCodeConfig) => async (): Promise<SetupResult> => {
|
|
try {
|
|
const status = await checkExtensionStatus(config)();
|
|
|
|
if (!status.isVSCodeAvailable) {
|
|
return {
|
|
success: false,
|
|
message: 'VSCode is not available. Please install VSCode first.'
|
|
};
|
|
}
|
|
|
|
if (!status.isInstalled) {
|
|
await installDrawioExtension(config)();
|
|
return {
|
|
success: true,
|
|
message: 'Draw.io extension installed successfully.'
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
message: 'VSCode environment is ready for draw.io.'
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
message: `Failed to setup VSCode environment: ${error}`
|
|
};
|
|
}
|
|
};
|
|
|
|
// Curried function to open multiple diagrams in VSCode
|
|
export const openMultipleDiagrams = (config: VSCodeConfig) => async (filePaths: readonly string[]): Promise<readonly string[]> => {
|
|
const results: string[] = [];
|
|
const openDiagram = openDiagramInVSCode(config);
|
|
|
|
for (const filePath of filePaths) {
|
|
try {
|
|
await openDiagram(filePath);
|
|
results.push(`Successfully opened: ${filePath}`);
|
|
} catch (error) {
|
|
results.push(`Failed to open ${filePath}: ${error}`);
|
|
}
|
|
}
|
|
|
|
return results;
|
|
};
|
|
|
|
// Pure function to create VSCode workspace configuration for draw.io
|
|
export const createWorkspaceConfig = (config: VSCodeConfig): Readonly<Record<string, any>> => ({
|
|
'hediet.vscode-drawio': {
|
|
'local-storage': path.join(config.workspaceRoot, '.vscode', 'drawio-storage'),
|
|
'theme': 'automatic',
|
|
'online-url': 'https://embed.diagrams.net/',
|
|
'offline': false
|
|
}
|
|
});
|
|
|
|
// Curried function to save VSCode workspace settings
|
|
export const saveWorkspaceSettings = (config: VSCodeConfig) => async (settings: Readonly<Record<string, any>>): Promise<void> => {
|
|
const settingsPath = path.join(config.workspaceRoot, '.vscode', 'settings.json');
|
|
|
|
try {
|
|
await fs.ensureDir(path.dirname(settingsPath));
|
|
|
|
let existingSettings = {};
|
|
if (await fs.pathExists(settingsPath)) {
|
|
const content = await fs.readFile(settingsPath, 'utf8');
|
|
existingSettings = JSON.parse(content);
|
|
}
|
|
|
|
const mergedSettings = { ...existingSettings, ...settings };
|
|
await fs.writeFile(settingsPath, JSON.stringify(mergedSettings, null, 2), 'utf8');
|
|
} catch (error) {
|
|
throw new Error(`Failed to save workspace settings: ${error}`);
|
|
}
|
|
};
|
|
|
|
// Utility functions for functional composition
|
|
export const pipe = <T>(...fns: Array<(arg: T) => T>) => (value: T): T =>
|
|
fns.reduce((acc, fn) => fn(acc), value);
|
|
|
|
export const compose = <T>(...fns: Array<(arg: T) => T>) => (value: T): T =>
|
|
fns.reduceRight((acc, fn) => fn(acc), value);
|
|
|
|
// Higher-order function for operations with retry logic
|
|
export const withRetry = <T extends any[], R>(
|
|
operation: (...args: T) => Promise<R>,
|
|
maxRetries: number = 3,
|
|
delay: number = 1000
|
|
) => async (...args: T): Promise<R> => {
|
|
let lastError: Error;
|
|
|
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
try {
|
|
return await operation(...args);
|
|
} catch (error) {
|
|
lastError = error as Error;
|
|
if (attempt === maxRetries) break;
|
|
|
|
await new Promise(resolve => setTimeout(resolve, delay * attempt));
|
|
}
|
|
}
|
|
|
|
throw lastError!;
|
|
};
|
|
|
|
// Higher-order function for operations with timeout
|
|
export const withTimeout = <T extends any[], R>(
|
|
operation: (...args: T) => Promise<R>,
|
|
timeoutMs: number = 30000
|
|
) => async (...args: T): Promise<R> => {
|
|
return Promise.race([
|
|
operation(...args),
|
|
new Promise<never>((_, reject) =>
|
|
setTimeout(() => reject(new Error(`Operation timed out after ${timeoutMs}ms`)), timeoutMs)
|
|
)
|
|
]);
|
|
};
|
|
|
|
// Functional VSCode operations with retry and timeout
|
|
export const openDiagramInVSCodeWithRetry = (config: VSCodeConfig) =>
|
|
withRetry(openDiagramInVSCode(config));
|
|
|
|
export const setupVSCodeEnvironmentWithTimeout = (config: VSCodeConfig) =>
|
|
withTimeout(setupVSCodeEnvironment(config));
|
|
|
|
// Pure function to validate VSCode configuration
|
|
export const validateVSCodeConfig = (config: VSCodeConfig): boolean => {
|
|
return typeof config.workspaceRoot === 'string' &&
|
|
config.workspaceRoot.length > 0 &&
|
|
typeof config.extensionId === 'string' &&
|
|
config.extensionId.length > 0;
|
|
};
|
|
|
|
// Pure function to create validated VSCode configuration
|
|
export const createValidatedVSCodeConfig = (workspaceRoot?: string): VSCodeConfig => {
|
|
const config = createVSCodeConfig(workspaceRoot);
|
|
|
|
if (!validateVSCodeConfig(config)) {
|
|
throw new Error('Invalid VSCode configuration');
|
|
}
|
|
|
|
return config;
|
|
};
|