init: workspaces (#6264)

* init: workspaces
This commit is contained in:
naman-bruno
2025-12-04 04:56:43 +05:30
committed by GitHub
parent 6786f19d04
commit ebe0203415
108 changed files with 7315 additions and 908 deletions

View File

@@ -22,7 +22,7 @@ async function findUniqueFolderName(baseName, collectionLocation, counter = 0) {
/**
* Import a collection - shared logic used by both IPC handler and onboarding service
*/
async function importCollection(collection, collectionLocation, mainWindow, lastOpenedCollections, uniqueFolderName = null, format = 'bru') {
async function importCollection(collection, collectionLocation, mainWindow, uniqueFolderName = null, format = 'bru') {
// Use provided unique folder name or use collection name
let folderName = uniqueFolderName ? sanitizeName(uniqueFolderName) : sanitizeName(collection.name);
let collectionPath = path.join(collectionLocation, folderName);
@@ -100,13 +100,13 @@ async function importCollection(collection, collectionLocation, mainWindow, last
let brunoConfig = getBrunoJsonConfig(collection);
if (format === 'yml') {
const collectionContent = await stringifyCollection(collection.root, brunoConfig, { format });
const collectionContent = await stringifyCollection(collection.root, { format });
await writeFile(path.join(collectionPath, 'opencollection.yml'), collectionContent);
} else if (format === 'bru') {
const stringifiedBrunoConfig = await stringifyJson(brunoConfig);
await writeFile(path.join(collectionPath, 'bruno.json'), stringifiedBrunoConfig);
const collectionContent = await stringifyCollection(collection.root, brunoConfig, { format });
const collectionContent = await stringifyCollection(collection.root, { format });
await writeFile(path.join(collectionPath, 'collection.bru'), collectionContent);
} else {
throw new Error(`Invalid format: ${format}`);
@@ -119,8 +119,6 @@ async function importCollection(collection, collectionLocation, mainWindow, last
mainWindow.webContents.send('main:collection-opened', collectionPath, uid, brunoConfig);
ipcMain.emit('main:collection-opened', mainWindow, collectionPath, uid, brunoConfig);
lastOpenedCollections.add(collectionPath);
// create folder and files based on collection
await parseCollectionItems(collection.items, collectionPath);
await parseEnvironments(collection.environments, collectionPath);

View File

@@ -0,0 +1,225 @@
const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');
const { writeFile, validateName } = require('./filesystem');
const WORKSPACE_TYPE = 'workspace';
const makeRelativePath = (workspacePath, absolutePath) => {
if (!path.isAbsolute(absolutePath)) {
return absolutePath;
}
try {
return path.relative(workspacePath, absolutePath);
} catch (error) {
return absolutePath;
}
};
const normalizeCollectionEntry = (workspacePath, collection) => {
const relativePath = makeRelativePath(workspacePath, collection.path);
const normalizedCollection = {
name: collection.name,
path: relativePath
};
if (collection.remote) {
normalizedCollection.remote = collection.remote;
}
return normalizedCollection;
};
const validateWorkspacePath = (workspacePath) => {
if (!workspacePath) {
throw new Error('Workspace path is required');
}
if (!fs.existsSync(workspacePath)) {
throw new Error(`Workspace path does not exist: ${workspacePath}`);
}
const workspaceFilePath = path.join(workspacePath, 'workspace.yml');
if (!fs.existsSync(workspaceFilePath)) {
throw new Error('Invalid workspace: workspace.yml not found');
}
return true;
};
const validateWorkspaceDirectory = (dirPath) => {
if (!validateName(path.basename(dirPath))) {
throw new Error(`Invalid workspace directory name: ${dirPath}`);
}
return true;
};
const createWorkspaceConfig = (workspaceName) => ({
name: workspaceName,
type: WORKSPACE_TYPE,
version: '1.0.0',
docs: '',
collections: []
});
const readWorkspaceConfig = (workspacePath) => {
const workspaceFilePath = path.join(workspacePath, 'workspace.yml');
if (!fs.existsSync(workspaceFilePath)) {
throw new Error('Invalid workspace: workspace.yml not found');
}
const yamlContent = fs.readFileSync(workspaceFilePath, 'utf8');
const workspaceConfig = yaml.load(yamlContent);
if (!workspaceConfig || typeof workspaceConfig !== 'object') {
throw new Error('Invalid workspace: workspace.yml is malformed');
}
return workspaceConfig;
};
const writeWorkspaceConfig = async (workspacePath, config) => {
const yamlContent = yaml.dump(config, {
indent: 2,
lineWidth: -1,
noRefs: true
});
await writeFile(path.join(workspacePath, 'workspace.yml'), yamlContent);
};
const validateWorkspaceConfig = (config) => {
if (!config || typeof config !== 'object') {
throw new Error('Workspace configuration must be an object');
}
if (config.type !== WORKSPACE_TYPE) {
throw new Error('Invalid workspace: not a bruno workspace');
}
if (!config.name || typeof config.name !== 'string') {
throw new Error('Workspace must have a valid name');
}
return true;
};
const updateWorkspaceName = async (workspacePath, newName) => {
const config = readWorkspaceConfig(workspacePath);
config.name = newName;
await writeWorkspaceConfig(workspacePath, config);
return config;
};
const updateWorkspaceDocs = async (workspacePath, docs) => {
const config = readWorkspaceConfig(workspacePath);
config.docs = docs;
await writeWorkspaceConfig(workspacePath, config);
return docs;
};
const addCollectionToWorkspace = async (workspacePath, collection) => {
const config = readWorkspaceConfig(workspacePath);
if (!config.collections) {
config.collections = [];
}
// Normalize collection entry
const normalizedCollection = {
name: collection.name,
path: collection.path
};
if (collection.remote) {
normalizedCollection.remote = collection.remote;
}
// Check if collection already exists
const existingIndex = config.collections.findIndex((c) => c.name === normalizedCollection.name || c.path === normalizedCollection.path);
if (existingIndex >= 0) {
config.collections[existingIndex] = normalizedCollection;
} else {
config.collections.push(normalizedCollection);
}
await writeWorkspaceConfig(workspacePath, config);
return config.collections;
};
const removeCollectionFromWorkspace = async (workspacePath, collectionPath) => {
const config = readWorkspaceConfig(workspacePath);
let removedCollection = null;
let shouldDeleteFiles = false;
config.collections = (config.collections || []).filter((c) => {
const collectionPathFromYml = c.path;
if (!collectionPathFromYml) {
return true;
}
// Convert to absolute path for comparison
const absoluteCollectionPath = path.isAbsolute(collectionPathFromYml)
? collectionPathFromYml
: path.resolve(workspacePath, collectionPathFromYml);
if (path.normalize(absoluteCollectionPath) === path.normalize(collectionPath)) {
removedCollection = c;
// Delete files only for workspace collections (not remote, not external absolute paths)
const hasRemote = c.remote;
const isExternalPath = path.isAbsolute(collectionPathFromYml);
shouldDeleteFiles = !hasRemote && !isExternalPath;
return false; // Remove from array
}
return true; // Keep in array
});
await writeWorkspaceConfig(workspacePath, config);
return {
removedCollection,
shouldDeleteFiles,
updatedConfig: config
};
};
const getWorkspaceCollections = (workspacePath) => {
const config = readWorkspaceConfig(workspacePath);
const collections = config.collections || [];
// Resolve relative paths to absolute
return collections.map((collection) => {
if (collection.path && !path.isAbsolute(collection.path)) {
return {
...collection,
path: path.join(workspacePath, collection.path)
};
}
return collection;
});
};
module.exports = {
makeRelativePath,
normalizeCollectionEntry,
validateWorkspacePath,
validateWorkspaceDirectory,
createWorkspaceConfig,
readWorkspaceConfig,
writeWorkspaceConfig,
validateWorkspaceConfig,
updateWorkspaceName,
updateWorkspaceDocs,
addCollectionToWorkspace,
removeCollectionFromWorkspace,
getWorkspaceCollections
};