Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CLI Support for LinkedIn Export Conversion #67

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.cache
node_modules
dist

resume.json
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,11 @@ To build the project in production mode run:
`npm run build`

and serve the files from the `dist` folder.

## Using the CLI

You can also use the command-line interface to convert your LinkedIn export file.

`npm run cli path/to/your/linkedin-export.zip`

This will generate a `resume.json` file in your current directory.
15 changes: 10 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"develop": "parcel src/index.html",
"build": "parcel build src/index.html --public-url ./",
"lint": "./node_modules/jshint/bin/jshint src/js/*.js",
"test": "npm run build"
"test": "npm run build",
"cli": "ts-node --transpileOnly src/js/cli.ts"
},
"repository": {
"type": "git",
Expand All @@ -21,12 +22,16 @@
},
"homepage": "https://github.com/JMPerez/linkedin-to-json-resume#readme",
"devDependencies": {
"jshint": "^2.9.6",
"parcel-bundler": "^1.9.7",
"typescript": "^3.5.2"
"@types/node": "^22.13.4",
"@types/yauzl": "^2.10.3",
"jshint": "^2.13.6",
"parcel-bundler": "^1.12.5",
"ts-node": "^10.9.2",
"typescript": "^5.7.3"
},
"dependencies": {
"isomorphic-unzip": "^1.1.1",
"moment": "^2.18.1"
"moment": "^2.30.1",
"yauzl": "^3.2.0"
}
}
100 changes: 100 additions & 0 deletions src/js/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import LinkedInToJsonResume, { processors } from './converter';
import moment from 'moment';
import fs from 'fs';
import path from 'path';
import yauzl from 'yauzl';
import CSVToArray from './csvtoarray';

if (process.argv.length < 3) {
console.error('Usage: node cli.js <linkedin-export.zip>');
process.exit(1);
}

const zipFile = process.argv[2];
const linkedinToJsonResume = new LinkedInToJsonResume();

// Process the ZIP file
yauzl.open(zipFile, { lazyEntries: true }, async (err, zipfile) => {
if (err) {
console.error('Error opening ZIP file:', err);
process.exit(1);
}

const entries: yauzl.Entry[] = [];
const contents: { [key: string]: string } = {};

// Function to read the contents of a file from the ZIP
const readEntryContents = (entry: yauzl.Entry): Promise<string> => {
return new Promise((resolve, reject) => {
zipfile.openReadStream(entry, (err, readStream) => {
if (err) return reject(err);
let content = '';
readStream.on('data', (chunk) => {
content += chunk;
});
readStream.on('end', () => {
contents[entry.fileName] = content;
resolve(content);
});
readStream.on('error', reject);
});
});
};

// First collect all entries
zipfile.on('entry', async (entry) => {
entries.push(entry);
if (!entry.fileName.endsWith('/')) {
try {
await readEntryContents(entry);
} catch (error) {
console.error(`Error reading ${entry.fileName}:`, error);
}
}
zipfile.readEntry();
});

// When we finish reading all entries, process them
zipfile.on('end', async () => {
// Ensure Profile.csv is first
const profileIndex = entries.findIndex(
(entry) => entry.fileName === "Profile.csv"
);
if (profileIndex !== -1) {
const [profileEntry] = entries.splice(profileIndex, 1);
entries.unshift(profileEntry);
}

// Ensure Skills.csv is processed before endorsements
const skillsIndex = entries.findIndex(
(entry) => entry.fileName === "Skills.csv"
);
if (skillsIndex !== -1) {
const [skillsEntry] = entries.splice(skillsIndex, 1);
entries.unshift(skillsEntry);
}

// Process each file in the ZIP
const promises = entries.map((entry) => {
const content = contents[entry.fileName];
if (!content) return Promise.resolve();

for (const [csvName, processor] of Object.entries(processors)) {
if (entry.fileName.indexOf(csvName) !== -1) {
processor({ content, linkedinToJsonResume });
return Promise.resolve();
}
}
return Promise.resolve();
});

await Promise.all(promises);

// Save the result to a JSON file
const output = linkedinToJsonResume.getOutput();
fs.writeFileSync('resume.json', JSON.stringify(output, null, 2));
console.log('Successfully generated resume.json file');
});

zipfile.readEntry();
});
Loading