From a4b2a7a8946893c6e524c3863eb6f4fdb936690c Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 27 Jun 2025 13:53:59 -0400 Subject: [PATCH] fix: maintain unix file permissions from file read to zip options --- src/cli/pack.ts | 26 +++++++++++++++++++++++--- src/node/files.ts | 13 ++++++++++--- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/cli/pack.ts b/src/cli/pack.ts index 75396fc..4527867 100644 --- a/src/cli/pack.ts +++ b/src/cli/pack.ts @@ -1,5 +1,6 @@ import { confirm } from "@inquirer/prompts"; import { createHash } from "crypto"; +import type { Zippable } from "fflate"; import { zipSync } from "fflate"; import { existsSync, @@ -140,8 +141,9 @@ export async function packExtension({ >(); const shallowFiles: Array<{ path: string; size: number }> = []; - for (const [filePath, content] of fileEntries) { + for (const [filePath, fileData] of fileEntries) { const relPath = relative(resolvedPath, filePath); + const content = fileData.data; const size = typeof content === "string" ? Buffer.byteLength(content, "utf8") @@ -184,8 +186,26 @@ export async function packExtension({ } } - // Create zip - const zipData = zipSync(files, { + // Create zip with preserved file permissions + const zipFiles: Zippable = {}; + + const isUnix = process.platform !== "win32"; + + for (const [filePath, fileData] of Object.entries(files)) { + if (isUnix) { + // Set external file attributes to preserve Unix permissions + // The mode needs to be shifted to the upper 16 bits for ZIP format + zipFiles[filePath] = [ + fileData.data, + { os: 3, attrs: (fileData.mode & 0o777) << 16 }, + ]; + } else { + // On Windows, use default ZIP attributes (no Unix permissions) + zipFiles[filePath] = fileData.data; + } + } + + const zipData = zipSync(zipFiles, { level: 9, // Maximum compression mtime: new Date(), }); diff --git a/src/node/files.ts b/src/node/files.ts index cf17e1f..71f5329 100644 --- a/src/node/files.ts +++ b/src/node/files.ts @@ -146,15 +146,19 @@ export function getAllFiles( return fileList; } +interface FileWithPermissions { + data: Uint8Array; + mode: number; +} export interface GetAllFilesResult { - files: Record; + files: Record; ignoredCount: number; } export function getAllFilesWithCount( dirPath: string, baseDir: string = dirPath, - fileList: Record = {}, + fileList: Record = {}, additionalPatterns: string[] = [], ignoredCount = 0, ): GetAllFilesResult { @@ -183,7 +187,10 @@ export function getAllFilesWithCount( } else { // Use forward slashes in zip file paths const zipPath = relativePath.split(sep).join("/"); - fileList[zipPath] = readFileSync(filePath); + fileList[zipPath] = { + data: readFileSync(filePath), + mode: stat.mode, + }; } }