From 8446c7a7ed148d31d4b9a03db8054511b2166606 Mon Sep 17 00:00:00 2001 From: Iain Lane Date: Wed, 22 May 2024 09:29:03 +0100 Subject: [PATCH] fix: preserve mtime when zipping with the node zipper This means it's possible for code to retrieve the correct mtime, for example to return in a `Last-Modified` header. Since we have a test which verifies a checksum of a zip file created by this function, we can be sure the results are deterministic. To support this, update the test inputs to have specified mtimes. --- src/tests/util.test.ts | 40 ++++++++++++++++++++++++++++++++-------- src/utils.ts | 18 +++++++++--------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/tests/util.test.ts b/src/tests/util.test.ts index 24feacdb..9d1c11df 100644 --- a/src/tests/util.test.ts +++ b/src/tests/util.test.ts @@ -18,15 +18,29 @@ describe('utils/findProjectRoot', () => { }); describe('utils/zip', () => { + const mtime = new Date(2024, 0, 1, 0, 0, 0, 0); + beforeEach(() => { mockFs({ - '/src': { - 'test.txt': 'lorem ipsum', - modules: { - 'module.txt': 'lorem ipsum 2', + '/src': mockFs.directory({ + mtime, + items: { + 'test.txt': mockFs.file({ + mtime, + content: 'lorem ipsum', + }), + modules: mockFs.directory({ + mtime, + items: { + 'module.txt': mockFs.file({ + mtime, + content: 'lorem ipsum 2', + }), + }, + }), }, - }, - '/dist': {}, + }), + '/dist': mockFs.directory({ mtime }), }); }); @@ -67,7 +81,17 @@ describe('utils/zip', () => { }, ]; - await zip(zipPath, filesPathList, useNativeZip); + // Check the mtimes are set correctly + const sourceStat = fs.statSync(source); + expect(sourceStat.mtime).toEqual(mtime); + + const testStat = fs.statSync('/src/test.txt'); + expect(testStat.mtime).toEqual(mtime); + + const moduleStat = fs.statSync('/src/modules/module.txt'); + expect(moduleStat.mtime).toEqual(mtime); + + await expect(zip(zipPath, filesPathList, useNativeZip)).resolves.toBeUndefined(); expect(fs.existsSync(zipPath)).toEqual(true); @@ -81,7 +105,7 @@ describe('utils/zip', () => { if (!useNativeZip) { const data = fs.readFileSync(zipPath); const fileHash = crypto.createHash('sha256').update(data).digest('base64'); - expect(fileHash).toEqual('iCZdyHJ7ON2LLwBIE6gQmRvBTzXBogSqJTMvHSenzGk='); + expect(fileHash).toEqual('PHu2gv7OIMv+lAOCXYPNd30X8/7EKYTuV7KYJjw3Qd4='); } } ); diff --git a/src/utils.ts b/src/utils.ts index f1d59cd7..03c31b96 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,14 +1,8 @@ -import { bestzip } from 'bestzip'; -import archiver from 'archiver'; -import execa from 'execa'; -import { pipe } from 'fp-ts/lib/function'; import * as IO from 'fp-ts/lib/IO'; import * as IOO from 'fp-ts/lib/IOOption'; import * as TE from 'fp-ts/lib/TaskEither'; -import fs from 'fs-extra'; -import path from 'path'; -import os from 'os'; +import type { IFile, IFiles } from './types'; import { copyTask, mkdirpTask, @@ -18,7 +12,13 @@ import { taskFromPromise, } from './utils/fp-fs'; -import type { IFile, IFiles } from './types'; +import archiver from 'archiver'; +import { bestzip } from 'bestzip'; +import execa from 'execa'; +import fs from 'fs-extra'; +import os from 'os'; +import path from 'path'; +import { pipe } from 'fp-ts/lib/function'; export class SpawnError extends Error { constructor(message: string, public stdout: string, public stderr: string) { @@ -118,7 +118,7 @@ function nodeZip(zipPath: string, filesPathList: IFiles): Promise { zipArchive.append(fs.readFileSync(file.rootPath), { name: file.localPath, mode: stats.mode, - date: new Date(0), // necessary to get the same hash when zipping the same content + date: new Date(stats.mtime), }); });