
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
bodevops-features
Advanced tools
BoDevOps features library - utilities for Google Drive, Google Sheets, and iDrive e2
A collection of framework-agnostic TypeScript utilities for Google Drive, Google Sheets, and iDrive e2 operations.
any typesnpm install bodevops-features
GGDrive)Manage files and folders in Google Drive with ease.
Features:
GGSheet)Read, write, and manage Google Spreadsheet data.
Features:
import { GGDrive } from 'bodevops-features';
// Initialize client
const driveClient = new GGDrive.GoogleDriveClient({
keyFilePath: './service-account.json',
});
// Get storage information
const storage = await driveClient.getStorageInfo();
console.log(`Used: ${storage.formattedUsed} / ${storage.formattedTotal}`);
// Upload a file
const result = await driveClient.uploadFile({
localFilePath: './document.pdf',
driveFolder: 'MyFolder/SubFolder',
fileName: 'uploaded-document.pdf',
});
console.log(`View at: ${result.webViewLink}`);
// Upload and share with someone
const sharedResult = await driveClient.uploadFileAndShare({
localFilePath: './report.pdf',
driveFolder: 'SharedReports',
shareWithEmail: 'colleague@example.com',
role: 'writer',
});
// List files in a folder
const files = await driveClient.listFilesInFolder({ folderId: 'root' });
for (const file of files) {
console.log(`${file.name} (${file.mimeType})`);
}
import { GGSheet } from 'bodevops-features';
// Initialize client
const sheetClient = new GGSheet.GoogleSheetClient({
keyFilePath: './service-account.json',
});
// Read data from a sheet
const data = await sheetClient.getValues({
sheetUrl: 'https://docs.google.com/spreadsheets/d/1abc123...',
sheetName: 'Sheet1',
});
// Convert to typed objects
interface Person {
name: string;
email: string;
age: string;
}
const people = GGSheet.convertValueSheet<Person>({
values: data,
rowOffset: 0, // First row is header
});
// Export data (Overwrite mode)
await sheetClient.export({
sheetUrl: 'https://docs.google.com/spreadsheets/d/1abc123...',
sheetName: 'Sheet1',
listCols: ['Name', 'Email', 'Age'],
valsExport: [
['John Doe', 'john@example.com', '30'],
['Jane Smith', 'jane@example.com', '25'],
],
typeExport: GGSheet.ETypeExport.Overwrite,
});
// Update specific cells
await sheetClient.updateValuesMultiCells({
sheetUrl: 'https://docs.google.com/spreadsheets/d/1abc123...',
sheetName: 'Sheet1',
cells: [
{ row: 0, col: 0, content: 'Updated Value' },
{ row: 1, col: 1, content: 'Another Update' },
],
rowOffset: 0,
});
// Find a row by value
const rowIndex = await sheetClient.getIdxRow({
sheetUrl: 'https://docs.google.com/spreadsheets/d/1abc123...',
sheetName: 'Sheet1',
colName: 'A',
value: 'John Doe',
});
Both Google Drive and Google Sheets require a Google Service Account for authentication.
// Option 1: Using key file path
const client = new GGDrive.GoogleDriveClient({
keyFilePath: './service-account.json',
});
// Option 2: Using credentials object
const client = new GGDrive.GoogleDriveClient({
credentials: {
type: 'service_account',
project_id: 'your-project-id',
private_key: '-----BEGIN PRIVATE KEY-----\n...',
client_email: 'service-account@project.iam.gserviceaccount.com',
// ... other fields
},
});
GoogleDriveClientConstructor:
new GoogleDriveClient(config: IGoogleDriveConfig)
Methods:
| Method | Description |
|---|---|
getStorageInfo() | Get Drive storage quota information |
uploadFile(params) | Upload a file to Drive |
uploadFileAndShare(params) | Upload and share with email |
deleteFile(params) | Delete a file from Drive |
listFilesInFolder(params) | List files in a folder |
makeFilePublic(params) | Make file accessible via link |
transferFileOwnership(params) | Transfer file to another user |
shareFolderWithEmail(params) | Share folder with permissions |
getFolderIdByPath(params) | Get folder ID from path string |
fileExistsInFolder(params) | Check if file exists |
Utility Functions:
formatBytes({ bytes }) - Convert bytes to human-readable formatnormalizeFilePath({ filePath }) - Normalize file pathvalidateFileExists({ filePath }) - Check if file existsgetFileInfo({ filePath }) - Get file metadataGoogleSheetClientConstructor:
new GoogleSheetClient(config: IGoogleSheetConfig)
Methods:
| Method | Description |
|---|---|
getSheetInfo(params) | Get spreadsheet metadata |
getValues(params) | Read all values from sheet |
getIdxRow(params) | Find row index by value |
export(params) | Export data (Append/Overwrite) |
updateValuesMultiCells(params) | Update specific cells |
updateValuesMultiColsByRow(params) | Update columns in a row |
updateValuesMultiRowsByCol(params) | Update rows in a column |
updateValuesMultiRowsMultiCols(params) | Batch update range |
deleteRowSheet(params) | Delete a row |
Utility Functions:
convertIndexToColumnName({ columnIndex }) - Convert 0 → "A", 26 → "AA"convertColumnNameToIndex({ columnName }) - Convert "A" → 0, "AA" → 26convertValueSheet({ values, rowOffset }) - Parse sheet to typed objectsgetSheetIdFromUrl({ sheetUrl }) - Extract spreadsheet IDcalculateActualRow({ dataRowIndex, rowOffset }) - Calculate sheet row numberEnums:
ETypeExport.Append - Append data to existingETypeExport.Overwrite - Replace all dataconst files = ['file1.pdf', 'file2.pdf', 'file3.pdf'];
for (const file of files) {
const result = await driveClient.uploadFile({
localFilePath: `./documents/${file}`,
driveFolder: 'Uploads/2024',
fileName: file,
});
console.log(`✓ Uploaded: ${result.name}`);
}
import { GGSheet } from 'bodevops-features';
// Assume you have query results
const users = await database.query('SELECT name, email, created_at FROM users');
// Map to column definitions
const colsMapping = {
name: 'Full Name',
email: 'Email Address',
created_at: 'Registration Date',
};
// Extract columns and values
const { listCols, valsExport } = GGSheet.getListColsAndValsExport({
colsForSheet: colsMapping,
resultItems: users,
});
// Export to sheet
await sheetClient.export({
sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
sheetName: 'Users',
listCols,
valsExport,
typeExport: GGSheet.ETypeExport.Overwrite,
});
// Update column C for rows 0-4
await sheetClient.updateValuesMultiRowsByCol({
sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
sheetName: 'Sheet1',
col: 2, // Column C (0-based)
values: [
{ row: 0, content: 'Status: Complete' },
{ row: 1, content: 'Status: Pending' },
{ row: 2, content: 'Status: Complete' },
{ row: 3, content: 'Status: Failed' },
{ row: 4, content: 'Status: Complete' },
],
});
rowOffsetAll update and delete methods support a rowOffset parameter to handle different sheet structures:
rowOffset: 0 (default): Header at row 1, data starts at row 2rowOffset: 1: Header at row 1, skip row 2, data starts at row 3rowOffset: 2: Header at row 1, skip rows 2-3, data starts at row 4Example Sheet Structure:
Row 1: [Name, Email, Status] ← Header
Row 2: [John, john@example.com, Active] ← Data row 0 (with rowOffset=0)
Row 3: [Jane, jane@example.com, Pending] ← Data row 1 (with rowOffset=0)
updateValuesMultiCells() - Update Specific CellsUpdate multiple cells at specific row and column positions.
Use Case: Update scattered cells across the sheet
await sheetClient.updateValuesMultiCells({
sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
sheetName: 'Sheet1',
cells: [
{ row: 0, col: 0, content: 'John Doe' }, // A2
{ row: 0, col: 2, content: 'Active' }, // C2
{ row: 1, col: 1, content: 'jane@new.com' }, // B3
{ row: 5, col: 3, content: 'Updated' }, // D7
],
rowOffset: 0, // Optional, default is 0
});
Parameters:
cells: Array of { row, col, content } objects
row: 0-based data row indexcol: 0-based column index (0=A, 1=B, 2=C, ...)content: String value to writeupdateValuesMultiColsByRow() - Update Multiple Columns in One RowUpdate several columns in a single row.
Use Case: Update a complete user record
// Update row 3 (data row index 2) - columns A, B, C
await sheetClient.updateValuesMultiColsByRow({
sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
sheetName: 'Users',
row: 2, // Data row index (actual sheet row 4 with rowOffset=0)
values: [
{ col: 0, content: 'Updated Name' }, // Column A
{ col: 1, content: 'new@email.com' }, // Column B
{ col: 2, content: 'Active' }, // Column C
{ col: 4, content: '2026-01-07' }, // Column E
],
rowOffset: 0,
});
Real-world Example:
// Update user status and last login
await sheetClient.updateValuesMultiColsByRow({
sheetUrl: SHEET_URL,
sheetName: 'Users',
row: userId,
values: [
{ col: 5, content: 'Online' }, // Status column
{ col: 6, content: new Date().toISOString() }, // Last login column
],
});
updateValuesMultiRowsByCol() - Update Multiple Rows in One ColumnUpdate several rows in a single column.
Use Case: Batch update status for multiple items
// Update status column (C) for multiple rows
await sheetClient.updateValuesMultiRowsByCol({
sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
sheetName: 'Tasks',
col: 2, // Column C (0-based)
values: [
{ row: 0, content: 'Completed' },
{ row: 1, content: 'In Progress' },
{ row: 2, content: 'Completed' },
{ row: 5, content: 'Pending' },
{ row: 8, content: 'Completed' },
],
rowOffset: 0,
});
Real-world Example:
// Mark all selected tasks as completed
const completedTaskIds = [0, 3, 5, 7];
await sheetClient.updateValuesMultiRowsByCol({
sheetUrl: SHEET_URL,
sheetName: 'Tasks',
col: 3, // Status column
values: completedTaskIds.map((taskId) => ({
row: taskId,
content: 'Completed ✓',
})),
});
updateValuesMultiRowsMultiCols() - Batch Update a RangeUpdate a rectangular range of cells (multiple rows and columns).
Use Case: Update a table section or paste data block
// Update a 3x3 range starting at row 0, column 0
await sheetClient.updateValuesMultiRowsMultiCols({
sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
sheetName: 'Data',
values: [
['A1', 'B1', 'C1'],
['A2', 'B2', 'C2'],
['A3', 'B3', 'C3'],
],
startRow: 0, // Start at data row 0 (sheet row 2)
startCol: 0, // Start at column A
rowOffset: 0,
});
Advanced Example with Custom Range:
// Update columns D-F (indices 3-5) for rows 10-15
await sheetClient.updateValuesMultiRowsMultiCols({
sheetUrl: SHEET_URL,
sheetName: 'Report',
values: [
['Q1', '1000', '95%'],
['Q2', '1200', '98%'],
['Q3', '1100', '96%'],
['Q4', '1300', '99%'],
],
startRow: 10, // Data row 10
endRow: 13, // Data row 13 (4 rows total)
startCol: 3, // Column D
rowOffset: 0,
});
Paste Clipboard Data:
// Paste a copied table from Excel/Sheets
const clipboardData = [
['Product', 'Price', 'Stock'],
['Item A', '100', '50'],
['Item B', '200', '30'],
['Item C', '150', '40'],
];
await sheetClient.updateValuesMultiRowsMultiCols({
sheetUrl: SHEET_URL,
sheetName: 'Inventory',
values: clipboardData,
startRow: 0,
startCol: 0,
});
deleteRowSheet() - Delete a RowDelete a specific row from the sheet.
Use Case: Remove a record from the sheet
// Delete data row 5 (actual sheet row 7 with rowOffset=0)
await sheetClient.deleteRowSheet({
sheetUrl: 'https://docs.google.com/spreadsheets/d/...',
sheetName: 'Users',
row: 5, // Data row index
rowOffset: 0,
});
Real-world Example:
// Delete user by finding their row first
const userEmail = 'user@example.com';
// Find the row
const rowIndex = await sheetClient.getIdxRow({
sheetUrl: SHEET_URL,
sheetName: 'Users',
colName: 'B', // Email column
value: userEmail,
});
if (rowIndex >= 0) {
// Delete the row
await sheetClient.deleteRowSheet({
sheetUrl: SHEET_URL,
sheetName: 'Users',
row: rowIndex,
});
console.log(`Deleted user: ${userEmail}`);
}
⚠️ Important Notes:
| Scenario | Recommended Method |
|---|---|
| Update 1-2 specific cells | updateValuesMultiCells() |
| Update entire user record (one row) | updateValuesMultiColsByRow() |
| Batch update status column | updateValuesMultiRowsByCol() |
| Update a table section/range | updateValuesMultiRowsMultiCols() |
| Remove a record | deleteRowSheet() |
1. Batch Operations for Performance:
// ❌ Bad: Multiple individual updates
for (const item of items) {
await sheetClient.updateValuesMultiCells({
sheetUrl: SHEET_URL,
sheetName: 'Data',
cells: [{ row: item.id, col: 2, content: item.status }],
});
}
// ✅ Good: Single batch update
await sheetClient.updateValuesMultiRowsByCol({
sheetUrl: SHEET_URL,
sheetName: 'Data',
col: 2,
values: items.map((item) => ({ row: item.id, content: item.status })),
});
2. Handle rowOffset Correctly:
// If your sheet has a description row after header:
// Row 1: [Name, Email, Status] ← Header
// Row 2: [Enter name, Enter email, ...] ← Description
// Row 3: [John, john@example.com, Active] ← First data row
await sheetClient.updateValuesMultiCells({
sheetUrl: SHEET_URL,
sheetName: 'Sheet1',
cells: [{ row: 0, col: 0, content: 'Updated' }],
rowOffset: 1, // Skip the description row
});
// This updates row 3 (first data row)
3. Validate Before Delete:
// Always confirm before deleting
const confirmDelete = await getUserConfirmation();
if (confirmDelete) {
await sheetClient.deleteRowSheet({
sheetUrl: SHEET_URL,
sheetName: 'Users',
row: rowIndex,
});
}
This library is written in TypeScript and provides full type definitions.
import { GGDrive, GGSheet } from 'bodevops-features';
// All types are exported
type UploadResult = GGDrive.IUploadFileResult;
type SheetInfo = GGSheet.ISpreadsheetInfo;
type ExportMode = GGSheet.ETypeExport;
All methods throw descriptive errors. Wrap calls in try-catch:
try {
const result = await driveClient.uploadFile({
localFilePath: './document.pdf',
driveFolder: 'MyFolder',
});
} catch (error) {
console.error('Upload failed:', error.message);
// Handle error appropriately
}
Contributions are welcome! Please follow these guidelines:
MIT © BoDevOps
FAQs
BoDevOps features library - utilities for Google Drive, Google Sheets, and iDrive e2
We found that bodevops-features demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.