Skip to content

Instantly share code, notes, and snippets.

@seafarer
Created April 4, 2026 02:12
Show Gist options
  • Select an option

  • Save seafarer/26db87db8bb8997f3e46fa7e3edcc869 to your computer and use it in GitHub Desktop.

Select an option

Save seafarer/26db87db8bb8997f3e46fa7e3edcc869 to your computer and use it in GitHub Desktop.
Use this to download a Lovable project. All subfolders must be open. Will not download binary files. Paste into dev tools and go.
(async () => {
const sleep = ms => new Promise(r => setTimeout(r, ms));
if (!window.JSZip) {
await new Promise((resolve, reject) => {
const s = document.createElement('script');
s.src = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js';
s.onload = resolve;
s.onerror = reject;
document.head.appendChild(s);
});
}
const panel = document.querySelector('[class*="min-w-64"]');
const zip = new JSZip();
const isFile = text => /\.\w{1,10}$/.test(text.trim());
const getDoc = () => {
const cm = document.querySelector('.cm-content');
return cm?.cmTile?.view?.state?.doc?.toString() || '';
};
const items = [...panel.querySelectorAll('div[class*="cursor-pointer"]')];
const dirStack = [];
const files = [];
for (const el of items) {
const text = el.textContent.trim();
const mlDiv = el.querySelector('div[class*="mr-1"]');
const ml = mlDiv ? parseInt(mlDiv.style.marginLeft) || 0 : 0;
const depth = ml / 12;
while (dirStack.length > depth) dirStack.pop();
if (isFile(text)) {
files.push({ el, name: text, path: [...dirStack, text].join('/') });
} else {
dirStack.push(text);
}
}
console.log(`Found ${files.length} files:`);
files.forEach(f => console.log(` ${f.path}`));
let previousContent = '';
let success = 0;
for (const file of files) {
console.log(`Clicking: ${file.path}`);
file.el.click();
let content = '';
let stableCount = 0;
let lastContent = '';
for (let i = 0; i < 50; i++) {
await sleep(200);
content = getDoc();
if (content === previousContent && i < 40) continue;
if (content === lastContent && content !== previousContent) {
stableCount++;
if (stableCount >= 3) break;
} else {
stableCount = 0;
lastContent = content;
}
}
if (content.length > 0) {
zip.file(file.path, content);
previousContent = content;
success++;
console.log(` ✅ ${file.path} (${content.length} chars)`);
} else {
console.log(` ❌ ${file.path} skipped`);
}
}
console.log(`\nDone: ${success}/${files.length} files captured`);
const blob = await zip.generateAsync({ type: 'blob' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'lovable-project.zip';
a.click();
console.log('🎉 Download started!');
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment