repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
Projectflow
stars
latest
clone command
git clone gitlawb://did:key:z6Mkfh4Y...QBEi/projectflowgit clone gitlawb://did:key:z6Mkfh4Y.../projectflowb3cded1async from playground23h ago| #1 | import JSZip from "jszip"; |
| #2 | |
| #3 | // Import all source files as raw text at build time via Vite |
| #4 | const srcFiles = import.meta.glob("/src/**/*", { as: "raw", eager: true }); |
| #5 | |
| #6 | const rootFiles = import.meta.glob( |
| #7 | [ |
| #8 | "/package.json", |
| #9 | "/vite.config.ts", |
| #10 | "/tsconfig.json", |
| #11 | "/tsconfig.app.json", |
| #12 | "/tsconfig.node.json", |
| #13 | "/tailwind.config.js", |
| #14 | "/postcss.config.js", |
| #15 | "/index.html", |
| #16 | "/.gitignore", |
| #17 | "/AGENTS.md", |
| #18 | "/bun.lock", |
| #19 | ], |
| #20 | { as: "raw", eager: true } |
| #21 | ); |
| #22 | |
| #23 | // Simple gitignore pattern matching |
| #24 | function matchesGitignorePattern(filePath: string, pattern: string): boolean { |
| #25 | // Handle negation patterns (!) |
| #26 | if (pattern.startsWith("!")) return false; |
| #27 | |
| #28 | // Remove trailing slash (directory indicator) |
| #29 | const clean = pattern.replace(/\/$/, ""); |
| #30 | |
| #31 | // Exact match or basename match |
| #32 | if (filePath === clean || filePath.endsWith("/" + clean)) return true; |
| #33 | |
| #34 | // Glob-style * matching |
| #35 | if (clean.includes("*")) { |
| #36 | const regex = new RegExp( |
| #37 | "^" + |
| #38 | clean |
| #39 | .replace(/[.+^${}()|[\]\\]/g, "\\$&") |
| #40 | .replace(/\*/g, "[^/]*") + |
| #41 | "$" |
| #42 | ); |
| #43 | const basename = filePath.split("/").pop() || filePath; |
| #44 | if (regex.test(basename) || regex.test(filePath)) return true; |
| #45 | } |
| #46 | |
| #47 | // Directory prefix match (e.g., "dist" matches "dist/foo/bar") |
| #48 | if (filePath.startsWith(clean + "/")) return true; |
| #49 | |
| #50 | return false; |
| #51 | } |
| #52 | |
| #53 | function isGitignored(filePath: string): boolean { |
| #54 | const patterns = [ |
| #55 | "node_modules", |
| #56 | "dist", |
| #57 | "dist-ssr", |
| #58 | ".DS_Store", |
| #59 | "*.local", |
| #60 | ".env.local", |
| #61 | ".env.*.local", |
| #62 | ".vscode/*", |
| #63 | ".idea", |
| #64 | ]; |
| #65 | const negations = [".vscode/extensions.json"]; |
| #66 | |
| #67 | // Check negations first (they override) |
| #68 | for (const neg of negations) { |
| #69 | if (matchesGitignorePattern(filePath, neg)) return false; |
| #70 | } |
| #71 | |
| #72 | for (const pattern of patterns) { |
| #73 | if (matchesGitignorePattern(filePath, pattern)) return true; |
| #74 | } |
| #75 | |
| #76 | return false; |
| #77 | } |
| #78 | |
| #79 | export async function downloadProjectZip() { |
| #80 | const zip = new JSZip(); |
| #81 | |
| #82 | // Add root config/meta files |
| #83 | for (const [path, content] of Object.entries(rootFiles)) { |
| #84 | const fileName = path.slice(1); // remove leading "/" |
| #85 | if (!isGitignored(fileName)) { |
| #86 | zip.file(fileName, content as string); |
| #87 | } |
| #88 | } |
| #89 | |
| #90 | // Add all src files |
| #91 | for (const [path, content] of Object.entries(srcFiles)) { |
| #92 | const filePath = path.slice(1); // remove leading "/" |
| #93 | if (!isGitignored(filePath)) { |
| #94 | zip.file(filePath, content as string); |
| #95 | } |
| #96 | } |
| #97 | |
| #98 | const blob = await zip.generateAsync({ type: "blob" }); |
| #99 | const url = URL.createObjectURL(blob); |
| #100 | const a = document.createElement("a"); |
| #101 | a.href = url; |
| #102 | a.download = "project.zip"; |
| #103 | document.body.appendChild(a); |
| #104 | a.click(); |
| #105 | document.body.removeChild(a); |
| #106 | URL.revokeObjectURL(url); |
| #107 | } |
| #108 |