Fix ZIP extraction compatibility issue with Bun runtime
- Replace unzipper library with Bun's built-in ZIP reading capabilities - Use zipFile.entries() to read ZIP entries directly in memory - Process files in memory without filesystem extraction - Maintain proper file categorization (public, server, site, core) - Handle binary vs text files correctly with appropriate decoding - Remove dependency on problematic unzipper library that has mkdir compatibility issues - Clean up unused file system functions and imports This fixes the "mkdir is not a function" error and makes ZIP loading fully compatible with Bun. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
9c92da52d2
commit
2fe9c28729
|
|
@ -6,14 +6,12 @@ import {
|
||||||
removeAsync,
|
removeAsync,
|
||||||
writeAsync,
|
writeAsync,
|
||||||
} from "fs-jetpack";
|
} from "fs-jetpack";
|
||||||
import { readdir } from "fs/promises";
|
|
||||||
import { decode } from "msgpackr";
|
import { decode } from "msgpackr";
|
||||||
import { createRouter } from "radix3";
|
import { createRouter } from "radix3";
|
||||||
import { startBrCompress } from "./br-load";
|
import { startBrCompress } from "./br-load";
|
||||||
import { dir } from "./dir";
|
import { dir } from "./dir";
|
||||||
import { g } from "./global";
|
import { g } from "./global";
|
||||||
import { gunzipAsync } from "./gzip";
|
import { gunzipAsync } from "./gzip";
|
||||||
import * as unzipper from "unzipper";
|
|
||||||
|
|
||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
|
|
||||||
|
|
@ -224,136 +222,138 @@ export const deploy = {
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[DEBUG] ZIP file exists, size: ${await zipFile.size} bytes`);
|
console.log(`[DEBUG] ZIP file exists, size: ${await zipFile.size} bytes`);
|
||||||
const zipBuffer = Buffer.from(await zipFile.arrayBuffer());
|
|
||||||
|
|
||||||
// Create a directory for extraction
|
// Use Bun's built-in ZIP reading capability
|
||||||
const extractDir = dir(`app/web/deploy/${ts}_extracted`);
|
console.log(`[DEBUG] Reading ZIP entries...`);
|
||||||
console.log(`[DEBUG] Creating extract directory: ${extractDir}`);
|
const zipEntries = await zipFile.entries();
|
||||||
await dirAsync(extractDir);
|
console.log(`[DEBUG] ZIP entries loaded: ${zipEntries.length} entries`);
|
||||||
|
|
||||||
// Extract ZIP file
|
// Create deploy content structure
|
||||||
console.log(`[DEBUG] Starting ZIP extraction...`);
|
g.deploy.content = {
|
||||||
const directory = await unzipper.Open.buffer(zipBuffer);
|
layouts: [],
|
||||||
console.log(`[DEBUG] ZIP opened, files count: ${directory.files.length}`);
|
pages: [],
|
||||||
|
comps: [],
|
||||||
|
site: null,
|
||||||
|
public: {},
|
||||||
|
code: {
|
||||||
|
server: {},
|
||||||
|
site: {},
|
||||||
|
core: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
await directory.extract({ path: extractDir });
|
let foundMetadata = false;
|
||||||
console.log(`[DEBUG] ZIP extraction completed`);
|
let loadedFiles = 0;
|
||||||
|
|
||||||
// Load metadata from ZIP
|
// Process each entry in the ZIP
|
||||||
const metadataPath = dir(`${extractDir}/metadata.json`);
|
for (const entry of zipEntries) {
|
||||||
console.log(`[DEBUG] Looking for metadata at: ${metadataPath}`);
|
const path = entry[0];
|
||||||
|
const zipEntry = entry[1];
|
||||||
|
|
||||||
if (await Bun.file(metadataPath).exists()) {
|
if (path.endsWith('/')) {
|
||||||
console.log(`[DEBUG] Found metadata.json, reading content...`);
|
// Skip directory entries
|
||||||
const metadataContent = await Bun.file(metadataPath).text();
|
continue;
|
||||||
console.log(`[DEBUG] Metadata content length: ${metadataContent.length} chars`);
|
|
||||||
const metadata = JSON.parse(metadataContent);
|
|
||||||
|
|
||||||
console.log(`[DEBUG] Parsed metadata:`, {
|
|
||||||
layouts: metadata.layouts?.length || 0,
|
|
||||||
pages: metadata.pages?.length || 0,
|
|
||||||
components: metadata.components?.length || 0,
|
|
||||||
hasSite: !!metadata.site
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set up the deploy content structure
|
|
||||||
g.deploy.content = {
|
|
||||||
layouts: metadata.layouts || [],
|
|
||||||
pages: metadata.pages || [],
|
|
||||||
comps: metadata.components || [],
|
|
||||||
site: metadata.site,
|
|
||||||
public: {},
|
|
||||||
code: {
|
|
||||||
server: {},
|
|
||||||
site: {},
|
|
||||||
core: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load public files
|
|
||||||
const publicDir = dir(`${extractDir}/public`);
|
|
||||||
if (await existsAsync(publicDir)) {
|
|
||||||
console.log(`[DEBUG] Loading public files from: ${publicDir}`);
|
|
||||||
await this.loadFilesFromDirectory(publicDir, "public", g.deploy.content.public);
|
|
||||||
console.log(`[DEBUG] Loaded ${Object.keys(g.deploy.content.public).length} public files`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load server files
|
try {
|
||||||
const serverDir = dir(`${extractDir}/server`);
|
const content = await zipEntry.arrayBuffer();
|
||||||
if (await existsAsync(serverDir)) {
|
const fileContent = new Uint8Array(content);
|
||||||
console.log(`[DEBUG] Loading server files from: ${serverDir}`);
|
|
||||||
await this.loadFilesFromDirectory(serverDir, "server", g.deploy.content.code.server);
|
|
||||||
console.log(`[DEBUG] Loaded ${Object.keys(g.deploy.content.code.server).length} server files`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load site files
|
console.log(`[DEBUG] Processing file: ${path} (${fileContent.length} bytes)`);
|
||||||
const siteDir = dir(`${extractDir}/site`);
|
|
||||||
if (await existsAsync(siteDir)) {
|
|
||||||
console.log(`[DEBUG] Loading site files from: ${siteDir}`);
|
|
||||||
await this.loadFilesFromDirectory(siteDir, "site", g.deploy.content.code.site);
|
|
||||||
console.log(`[DEBUG] Loaded ${Object.keys(g.deploy.content.code.site).length} site files`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load core files
|
if (path === 'metadata.json') {
|
||||||
const coreDir = dir(`${extractDir}/core`);
|
console.log(`[DEBUG] Found metadata.json, parsing content...`);
|
||||||
if (await existsAsync(coreDir)) {
|
const metadataStr = new TextDecoder().decode(fileContent);
|
||||||
console.log(`[DEBUG] Loading core files from: ${coreDir}`);
|
const metadata = JSON.parse(metadataStr);
|
||||||
await this.loadFilesFromDirectory(coreDir, "core", g.deploy.content.code.core);
|
|
||||||
console.log(`[DEBUG] Loaded ${Object.keys(g.deploy.content.code.core).length} core files`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[DEBUG] Successfully loaded ZIP deployment with metadata`);
|
console.log(`[DEBUG] Parsed metadata:`, {
|
||||||
} else {
|
layouts: metadata.layouts?.length || 0,
|
||||||
// List available files in the ZIP
|
pages: metadata.pages?.length || 0,
|
||||||
|
components: metadata.components?.length || 0,
|
||||||
|
hasSite: !!metadata.site
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update deploy content with metadata
|
||||||
|
g.deploy.content.layouts = metadata.layouts || [];
|
||||||
|
g.deploy.content.pages = metadata.pages || [];
|
||||||
|
g.deploy.content.comps = metadata.components || [];
|
||||||
|
g.deploy.content.site = metadata.site;
|
||||||
|
foundMetadata = true;
|
||||||
|
} else if (path.startsWith('public/')) {
|
||||||
|
const relativePath = path.slice(7); // Remove 'public/' prefix
|
||||||
|
const binaryExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.ico', '.svg', '.woff', '.woff2', '.ttf', '.eot', '.js', '.css', '.map'];
|
||||||
|
const isBinary = binaryExtensions.some(ext => relativePath.toLowerCase().endsWith(ext));
|
||||||
|
|
||||||
|
if (isBinary) {
|
||||||
|
g.deploy.content.public[relativePath] = fileContent;
|
||||||
|
} else {
|
||||||
|
g.deploy.content.public[relativePath] = new TextDecoder().decode(fileContent);
|
||||||
|
}
|
||||||
|
} else if (path.startsWith('server/')) {
|
||||||
|
const relativePath = path.slice(7); // Remove 'server/' prefix
|
||||||
|
const binaryExtensions = ['.js', '.map', '.json'];
|
||||||
|
const isBinary = binaryExtensions.some(ext => relativePath.toLowerCase().endsWith(ext));
|
||||||
|
|
||||||
|
if (isBinary) {
|
||||||
|
g.deploy.content.code.server[relativePath] = fileContent;
|
||||||
|
} else {
|
||||||
|
g.deploy.content.code.server[relativePath] = new TextDecoder().decode(fileContent);
|
||||||
|
}
|
||||||
|
} else if (path.startsWith('site/')) {
|
||||||
|
const relativePath = path.slice(5); // Remove 'site/' prefix
|
||||||
|
const binaryExtensions = ['.js', '.css', '.map', '.json', '.woff', '.woff2', '.ttf'];
|
||||||
|
const isBinary = binaryExtensions.some(ext => relativePath.toLowerCase().endsWith(ext));
|
||||||
|
|
||||||
|
if (isBinary) {
|
||||||
|
g.deploy.content.code.site[relativePath] = fileContent;
|
||||||
|
} else {
|
||||||
|
g.deploy.content.code.site[relativePath] = new TextDecoder().decode(fileContent);
|
||||||
|
}
|
||||||
|
} else if (path.startsWith('core/')) {
|
||||||
|
const relativePath = path.slice(6); // Remove 'core/' prefix
|
||||||
|
const binaryExtensions = ['.js', '.json'];
|
||||||
|
const isBinary = binaryExtensions.some(ext => relativePath.toLowerCase().endsWith(ext));
|
||||||
|
|
||||||
|
if (isBinary) {
|
||||||
|
g.deploy.content.code.core[relativePath] = fileContent;
|
||||||
|
} else {
|
||||||
|
g.deploy.content.code.core[relativePath] = new TextDecoder().decode(fileContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedFiles++;
|
||||||
|
} catch (fileError) {
|
||||||
|
console.warn(`[WARN] Failed to process file ${path}:`, fileError.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[DEBUG] ZIP processing completed: ${loadedFiles} files loaded`);
|
||||||
|
|
||||||
|
if (!foundMetadata) {
|
||||||
console.log(`[DEBUG] Available files in ZIP:`);
|
console.log(`[DEBUG] Available files in ZIP:`);
|
||||||
directory.files.forEach((file) => {
|
for (const entry of zipEntries) {
|
||||||
console.log(`[DEBUG] - ${file.path}`);
|
console.log(`[DEBUG] - ${entry[0]}`);
|
||||||
});
|
}
|
||||||
throw new Error("metadata.json not found in ZIP file");
|
throw new Error("metadata.json not found in ZIP file");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up extracted directory
|
console.log(`[DEBUG] Successfully loaded ZIP deployment`);
|
||||||
console.log(`[DEBUG] Cleaning up extract directory...`);
|
console.log(`[DEBUG] Final content summary:`, {
|
||||||
await removeAsync(extractDir);
|
layouts: g.deploy.content.layouts?.length || 0,
|
||||||
console.log(`[DEBUG] ZIP load completed successfully`);
|
pages: g.deploy.content.pages?.length || 0,
|
||||||
|
comps: g.deploy.content.comps?.length || 0,
|
||||||
|
publicFiles: Object.keys(g.deploy.content.public).length,
|
||||||
|
serverFiles: Object.keys(g.deploy.content.code.server).length,
|
||||||
|
siteFiles: Object.keys(g.deploy.content.code.site).length,
|
||||||
|
coreFiles: Object.keys(g.deploy.content.code.core).length,
|
||||||
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[ERROR] Failed to load ZIP deployment:", error);
|
console.error("[ERROR] Failed to load ZIP deployment:", error);
|
||||||
console.error("[ERROR] Stack trace:", error.stack);
|
console.error("[ERROR] Stack trace:", error.stack);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async loadFilesFromDirectory(dirPath: string, prefix: string, target: Record<string, any>) {
|
|
||||||
const files = await readdir(dirPath);
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
const fullPath = dir(`${dirPath}/${file}`);
|
|
||||||
const stat = await Bun.file(fullPath).stat();
|
|
||||||
|
|
||||||
if (stat.isDirectory) {
|
|
||||||
// Recursively load subdirectories
|
|
||||||
await this.loadFilesFromDirectory(fullPath, `${prefix}/${file}`, target);
|
|
||||||
} else {
|
|
||||||
// Load file content
|
|
||||||
const content = await Bun.file(fullPath).arrayBuffer();
|
|
||||||
|
|
||||||
// Calculate relative path from the prefix
|
|
||||||
const relativePath = file;
|
|
||||||
|
|
||||||
// Determine if it's binary or text based on file extension
|
|
||||||
const binaryExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.ico', '.svg', '.woff', '.woff2', '.ttf', '.eot', '.js', '.css', '.map'];
|
|
||||||
const isBinary = binaryExtensions.some(ext => file.toLowerCase().endsWith(ext));
|
|
||||||
|
|
||||||
// Build the full path relative to the target structure
|
|
||||||
const targetPath = prefix === '' ? relativePath : `${prefix}/${relativePath}`;
|
|
||||||
|
|
||||||
if (isBinary) {
|
|
||||||
target[targetPath] = new Uint8Array(content);
|
|
||||||
} else {
|
|
||||||
target[targetPath] = new TextDecoder().decode(content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
get config() {
|
get config() {
|
||||||
if (!g.deploy) {
|
if (!g.deploy) {
|
||||||
g.deploy = {
|
g.deploy = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue