Skip to content

Instantly share code, notes, and snippets.

@nadeesha
Created November 11, 2024 01:14
Show Gist options
  • Save nadeesha/0df80eebf27fd1056d57084e4e39c7c4 to your computer and use it in GitHub Desktop.
Save nadeesha/0df80eebf27fd1056d57084e4e39c7c4 to your computer and use it in GitHub Desktop.
A straightforward and type-safe .netrc reader/writer
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
interface NetrcEntry {
machine: string;
login: string;
password: string;
account?: string;
}
interface NetrcData {
[key: string]: NetrcEntry;
}
export class NetrcUtils {
private netrcPath: string;
constructor(customPath?: string) {
this.netrcPath = customPath || path.join(os.homedir(), '.netrc');
}
/**
* Read and parse the .netrc file
* @returns Promise<NetrcData> Parsed netrc data as JSON
*/
async read(): Promise<NetrcData> {
try {
const content = await fs.promises.readFile(this.netrcPath, 'utf8');
return this.parseNetrc(content);
} catch (error) {
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
return {};
}
throw error;
}
}
/**
* Write data to the .netrc file
* @param data NetrcData to write
*/
async write(data: NetrcData): Promise<void> {
const content = this.stringifyNetrc(data);
await fs.promises.writeFile(this.netrcPath, content, {
mode: 0o600, // Set file permissions to be readable/writable only by owner
});
}
/**
* Parse netrc content into JSON format
* @param content Raw netrc file content
* @returns Parsed NetrcData
*/
private parseNetrc(content: string): NetrcData {
const result: NetrcData = {};
const lines = content.split('\n');
let currentMachine: string | null = null;
let currentEntry: Partial<NetrcEntry> = {};
for (const line of lines) {
const tokens = line.trim().split(/\s+/);
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
switch (token) {
case 'machine':
if (currentMachine && Object.keys(currentEntry).length) {
result[currentMachine] = currentEntry as NetrcEntry;
}
currentMachine = tokens[++i];
currentEntry = { machine: currentMachine };
break;
case 'login':
currentEntry.login = tokens[++i];
break;
case 'password':
currentEntry.password = tokens[++i];
break;
case 'account':
currentEntry.account = tokens[++i];
break;
}
}
}
// Add the last entry
if (currentMachine && Object.keys(currentEntry).length) {
result[currentMachine] = currentEntry as NetrcEntry;
}
return result;
}
/**
* Convert NetrcData back to netrc file format
* @param data NetrcData to convert
* @returns Formatted netrc content
*/
private stringifyNetrc(data: NetrcData): string {
return Object.entries(data)
.map(([_, entry]) => {
let result = `machine ${entry.machine}\n login ${entry.login}\n password ${entry.password}`;
if (entry.account) {
result += `\n account ${entry.account}`;
}
return result;
})
.join('\n\n');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment