From e12c6c47bbfddd7f55f579ad04bc97c71bd0898b Mon Sep 17 00:00:00 2001 From: Masaharu Tashiro Date: Sat, 26 Oct 2024 01:13:07 +0900 Subject: [PATCH] refactor plugin-zip --- src/plugin/packer/__tests__/cli.test.ts | 49 ++++++++++-------- .../pack-plugin-from-manifest.test.ts | 5 +- src/plugin/packer/__tests__/packer.test.ts | 9 ++-- src/plugin/packer/cli.ts | 7 ++- ...ate-contents-zip.test.ts => index.test.ts} | 17 ++++--- .../contents-zip/create-contents-zip.ts | 32 ------------ src/plugin/packer/contents-zip/index.ts | 51 +++++++++++++------ src/plugin/packer/index.ts | 7 +-- .../packer/pack-plugin-from-manifest.ts | 3 +- src/plugin/packer/plugin-zip/index.ts | 24 ++++++++- .../helpers/zip.ts => zip/index.ts} | 16 ++++++ 11 files changed, 130 insertions(+), 90 deletions(-) rename src/plugin/packer/contents-zip/__tests__/{create-contents-zip.test.ts => index.test.ts} (50%) delete mode 100644 src/plugin/packer/contents-zip/create-contents-zip.ts rename src/plugin/packer/{__tests__/helpers/zip.ts => zip/index.ts} (62%) diff --git a/src/plugin/packer/__tests__/cli.test.ts b/src/plugin/packer/__tests__/cli.test.ts index 059507d2f9..c6308e8421 100644 --- a/src/plugin/packer/__tests__/cli.test.ts +++ b/src/plugin/packer/__tests__/cli.test.ts @@ -5,6 +5,17 @@ import { globSync } from "glob"; import cli from "../cli"; import { logger } from "../../../utils/log"; import type { ContentsZipInterface } from "../contents-zip"; +import type { PluginZipInterface } from "../plugin-zip"; +import { ZipFile } from "../zip"; + +type MockedPacker = jest.MockedFunction< + ( + contentsZip: ContentsZipInterface, + privateKey_?: string, + ) => Promise<{ plugin: PluginZipInterface; privateKey: string; id: string }> +>; + +class MockPluginZip extends ZipFile implements PluginZipInterface {} const fixturesDir = path.posix.join(__dirname, "fixtures"); const sampleDir = path.posix.join(fixturesDir, "sample-plugin"); @@ -12,14 +23,7 @@ const ppkPath = path.posix.join(fixturesDir, "private.ppk"); const ID = "aaa"; const PRIVATE_KEY = "PRIVATE_KEY"; -const PLUGIN_BUFFER = Buffer.from("foo"); - -type MockedPacker = jest.MockedFunction< - ( - contentsZip: ContentsZipInterface, - privateKey_?: string, - ) => Promise<{ plugin: Buffer; privateKey: string; id: string }> ->; +const PLUGIN_ZIP = new MockPluginZip(Buffer.from("foo")); describe("cli", () => { beforeEach(() => { @@ -36,10 +40,11 @@ describe("cli", () => { describe("validation", () => { let packer: MockedPacker; beforeEach(() => { - packer = jest.fn().mockReturnValue({ + packer = jest.fn(); + packer.mockResolvedValue({ id: ID, privateKey: PRIVATE_KEY, - plugin: PLUGIN_BUFFER, + plugin: PLUGIN_ZIP, }); }); @@ -95,10 +100,11 @@ describe("cli", () => { let resultPluginPath: string; beforeEach(async () => { - packer = jest.fn().mockReturnValue({ + packer = jest.fn(); + packer.mockResolvedValue({ id: ID, privateKey: PRIVATE_KEY, - plugin: PLUGIN_BUFFER, + plugin: PLUGIN_ZIP, }); // TODO: use os tempdir @@ -131,7 +137,7 @@ describe("cli", () => { it("generates a plugin file", () => { const pluginBuffer = fs.readFileSync(resultPluginPath); - expect(PLUGIN_BUFFER.equals(pluginBuffer)).toBe(true); + expect(PLUGIN_ZIP.buffer.equals(pluginBuffer)).toBe(true); }); }); @@ -139,10 +145,11 @@ describe("cli", () => { const pluginDir = path.join(sampleDir, "plugin-dir"); let packer: MockedPacker; beforeEach(async () => { - packer = jest.fn().mockReturnValue({ + packer = jest.fn(); + packer.mockResolvedValue({ id: ID, privateKey: PRIVATE_KEY, - plugin: PLUGIN_BUFFER, + plugin: PLUGIN_ZIP, }); await rimraf(`${sampleDir}/*.*(ppk|zip)`, { glob: true }); @@ -163,10 +170,11 @@ describe("cli", () => { it("includes files listed in manifest.json only", async () => { const pluginDir = path.join(fixturesDir, "plugin-full-manifest"); - const packer: MockedPacker = jest.fn().mockReturnValue({ + const packer: MockedPacker = jest.fn(); + packer.mockResolvedValue({ id: ID, privateKey: PRIVATE_KEY, - plugin: PLUGIN_BUFFER, + plugin: PLUGIN_ZIP, }); await rimraf(`${sampleDir}/*.*(ppk|zip)`, { glob: true }); @@ -191,10 +199,11 @@ describe("cli", () => { const pluginDir = path.join(sampleDir, "plugin-dir"); const outputDir = path.join("test", ".output"); const outputPluginPath = path.join(outputDir, "foo.zip"); - const packer = jest.fn().mockReturnValue({ + const packer: MockedPacker = jest.fn(); + packer.mockResolvedValue({ id: ID, privateKey: PRIVATE_KEY, - plugin: PLUGIN_BUFFER, + plugin: PLUGIN_ZIP, }); await rimraf(outputDir); @@ -205,7 +214,7 @@ describe("cli", () => { expect(resultPluginPath).toBe(outputPluginPath); const pluginBuffer = fs.readFileSync(outputPluginPath); - expect(PLUGIN_BUFFER.equals(pluginBuffer)).toBe(true); + expect(PLUGIN_ZIP.buffer.equals(pluginBuffer)).toBe(true); const ppk = fs.readFileSync(path.join(outputDir, `${ID}.ppk`)); expect(PRIVATE_KEY).toBe(ppk.toString()); diff --git a/src/plugin/packer/__tests__/pack-plugin-from-manifest.test.ts b/src/plugin/packer/__tests__/pack-plugin-from-manifest.test.ts index ef98acd689..a7e4a188b1 100644 --- a/src/plugin/packer/__tests__/pack-plugin-from-manifest.test.ts +++ b/src/plugin/packer/__tests__/pack-plugin-from-manifest.test.ts @@ -1,6 +1,5 @@ import path from "path"; import fs from "fs"; -import { readZipContentsNames } from "./helpers/zip"; import packer from "../index"; import { packPluginFromManifest } from "../pack-plugin-from-manifest"; import { ManifestV1 } from "../manifest"; @@ -24,10 +23,10 @@ describe("pack-plugin-from-manifest", () => { const result2 = await packer(contentsZip, privateKey); expect(result1.id).toBe(result2.id); - expect(result1.plugin.length).toBe(result2.plugin.length); + expect(result1.plugin.buffer.length).toBe(result2.plugin.buffer.length); expect(result1.privateKey).toBe(result2.privateKey); - const files = await readZipContentsNames(result1.plugin); + const files = await result1.plugin.fileList(); expect(files).toStrictEqual(["contents.zip", "PUBKEY", "SIGNATURE"]); }); }); diff --git a/src/plugin/packer/__tests__/packer.test.ts b/src/plugin/packer/__tests__/packer.test.ts index e0742aec9e..5d72eaa172 100644 --- a/src/plugin/packer/__tests__/packer.test.ts +++ b/src/plugin/packer/__tests__/packer.test.ts @@ -4,10 +4,9 @@ import fs from "fs"; import RSA from "node-rsa"; import yauzl from "yauzl"; -import { readZipContentsNames } from "./helpers/zip"; - import packer from "../index"; import { ContentsZip } from "../contents-zip"; +import type { PluginZipInterface } from "../plugin-zip"; const privateKeyPath = path.join(__dirname, "fixtures", "private.ppk"); const contentsZipPath = path.join(__dirname, "fixtures", "contents.zip"); @@ -44,7 +43,7 @@ describe("packer", () => { }); it("the zip contains 3 files", async () => { - const files = await readZipContentsNames(output.plugin); + const files = await output.plugin.fileList(); expect(files.sort()).toStrictEqual( ["contents.zip", "PUBKEY", "SIGNATURE"].sort(), ); @@ -123,7 +122,7 @@ const readZipContents = ( }); }; -const verifyPlugin = async (plugin: Buffer): Promise => { +const verifyPlugin = async (plugin: PluginZipInterface): Promise => { const fromBuffer = (buffer: Buffer) => new Promise((resolve, reject) => { yauzl.fromBuffer(buffer, (err, zipfile) => { @@ -133,7 +132,7 @@ const verifyPlugin = async (plugin: Buffer): Promise => { resolve(zipfile); }); }); - const zipEntry = await fromBuffer(plugin); + const zipEntry = await fromBuffer(plugin.buffer); const zipContentsMap = await readZipContents(zipEntry); const contentZip = zipContentsMap.get("contents.zip"); expect(contentZip).toBeDefined(); diff --git a/src/plugin/packer/cli.ts b/src/plugin/packer/cli.ts index 4d13fd5410..1f8144216d 100644 --- a/src/plugin/packer/cli.ts +++ b/src/plugin/packer/cli.ts @@ -10,6 +10,7 @@ import { logger } from "../../utils/log"; import { ManifestV1 } from "./manifest"; import { generateErrorMessages } from "./manifest/validate"; import { ContentsZip } from "./contents-zip"; +import type { PluginZipInterface } from "./plugin-zip"; const debug = _debug("cli"); const writeFile = promisify(fs.writeFile); @@ -21,6 +22,8 @@ type Options = Partial<{ packerMock_: typeof packer; }>; +// TODO: Reduce statements in this func +// eslint-disable-next-line max-statements const cli = async (pluginDir: string, options_?: Options) => { const options = options_ || {}; const packerLocal = options.packerMock_ ? options.packerMock_ : packer; @@ -130,9 +133,9 @@ export = cli; */ const outputPlugin = async ( outputPath: string, - plugin: Buffer, + plugin: PluginZipInterface, ): Promise => { - await writeFile(outputPath, plugin); + await writeFile(outputPath, plugin.buffer); return outputPath; }; diff --git a/src/plugin/packer/contents-zip/__tests__/create-contents-zip.test.ts b/src/plugin/packer/contents-zip/__tests__/index.test.ts similarity index 50% rename from src/plugin/packer/contents-zip/__tests__/create-contents-zip.test.ts rename to src/plugin/packer/contents-zip/__tests__/index.test.ts index b033f3aa8f..c5f226e589 100644 --- a/src/plugin/packer/contents-zip/__tests__/create-contents-zip.test.ts +++ b/src/plugin/packer/contents-zip/__tests__/index.test.ts @@ -1,19 +1,22 @@ import path from "path"; -import { readZipContentsNames } from "../../__tests__/helpers/zip"; -import { createContentsZip } from "../create-contents-zip"; import { ManifestV1 } from "../../manifest"; +import { ContentsZip } from "../index"; const fixturesDir = path.join(__dirname, "fixtures"); const pluginDir = path.join(fixturesDir, "sample-plugin", "plugin-dir"); -describe("create-contents-zip", () => { - it("should be able to create buffer from a plugin directory", async () => { +describe("ContentsZip", () => { + it("should be able to create ContentsZip from a plugin directory", async () => { const manifestJSONPath = path.join(pluginDir, "manifest.json"); const manifest = await ManifestV1.loadJsonFile(manifestJSONPath); - const buffer = await createContentsZip(pluginDir, manifest); - const files = await readZipContentsNames(buffer as Buffer); + const contentsZip = await ContentsZip.createFromManifest( + pluginDir, + manifest, + ); + const files = await contentsZip.fileList(); expect(files).toStrictEqual(["manifest.json", "image/icon.png"]); - expect(buffer).toBeInstanceOf(Buffer); + expect(contentsZip).toBeInstanceOf(ContentsZip); + expect(contentsZip.buffer).toBeInstanceOf(Buffer); }); }); diff --git a/src/plugin/packer/contents-zip/create-contents-zip.ts b/src/plugin/packer/contents-zip/create-contents-zip.ts deleted file mode 100644 index a8a0a72e5b..0000000000 --- a/src/plugin/packer/contents-zip/create-contents-zip.ts +++ /dev/null @@ -1,32 +0,0 @@ -import path from "path"; -import { ZipFile } from "yazl"; -import streamBuffers from "stream-buffers"; -import _debug from "debug"; -import type { ManifestInterface } from "../manifest"; - -const debug = _debug("create-contents-zip"); - -/** - * Create a zipped contents - */ -export const createContentsZip = async ( - pluginDir: string, - manifest: ManifestInterface, -): Promise => { - return new Promise((res) => { - const output = new streamBuffers.WritableStreamBuffer(); - const zipFile = new ZipFile(); - let size: any = null; - output.on("finish", () => { - debug(`plugin.zip: ${size} bytes`); - res(output.getContents() as any); - }); - zipFile.outputStream.pipe(output); - manifest.sourceList().forEach((src) => { - zipFile.addFile(path.join(pluginDir, src), src); - }); - zipFile.end(undefined, ((finalSize: number) => { - size = finalSize; - }) as any); - }); -}; diff --git a/src/plugin/packer/contents-zip/index.ts b/src/plugin/packer/contents-zip/index.ts index 8443010933..631204d963 100644 --- a/src/plugin/packer/contents-zip/index.ts +++ b/src/plugin/packer/contents-zip/index.ts @@ -1,18 +1,22 @@ -import { readZipContentsNames } from "../__tests__/helpers/zip"; -import { createContentsZip } from "./create-contents-zip"; import type { ManifestInterface } from "../manifest"; import { validateContentsZip } from "./zip"; +import { ZipFile } from "../zip"; +import streamBuffers from "stream-buffers"; +import path from "path"; +import yazl from "yazl"; + +import _debug from "debug"; + +const debug = _debug("contents-zip"); export interface ContentsZipInterface { fileList(): Promise; get buffer(): Buffer; } -export class ContentsZip implements ContentsZipInterface { - private readonly zip: Buffer; - - private constructor(zipFile: Buffer) { - this.zip = zipFile; +export class ContentsZip extends ZipFile implements ContentsZipInterface { + private constructor(buffer: Buffer) { + super(buffer); } public static async createFromManifest( @@ -28,12 +32,29 @@ export class ContentsZip implements ContentsZipInterface { await validateContentsZip(buffer); return new ContentsZip(buffer); } - - async fileList(): Promise { - return readZipContentsNames(this.zip); - } - - public get buffer() { - return this.zip; - } } + +/** + * Create a zipped contents + */ +export const createContentsZip = async ( + pluginDir: string, + manifest: ManifestInterface, +): Promise => { + return new Promise((res) => { + const output = new streamBuffers.WritableStreamBuffer(); + const zipFile = new yazl.ZipFile(); + let size: any = null; + output.on("finish", () => { + debug(`plugin.zip: ${size} bytes`); + res(output.getContents() as any); + }); + zipFile.outputStream.pipe(output); + manifest.sourceList().forEach((src) => { + zipFile.addFile(path.join(pluginDir, src), src); + }); + zipFile.end(undefined, ((finalSize: number) => { + size = finalSize; + }) as any); + }); +}; diff --git a/src/plugin/packer/index.ts b/src/plugin/packer/index.ts index f2fea9919f..b0215fe89b 100644 --- a/src/plugin/packer/index.ts +++ b/src/plugin/packer/index.ts @@ -1,6 +1,7 @@ import _debug from "debug"; import { PrivateKey } from "./crypto"; -import { zip } from "./plugin-zip"; +import type { PluginZipInterface } from "./plugin-zip"; +import { PluginZip } from "./plugin-zip"; import type { ContentsZipInterface } from "./contents-zip"; const debug = _debug("packer"); @@ -9,7 +10,7 @@ const packer = async ( contentsZip: ContentsZipInterface, privateKey_?: string, ): Promise<{ - plugin: Buffer; + plugin: PluginZipInterface; privateKey: string; id: string; }> => { @@ -26,7 +27,7 @@ const packer = async ( const id = key.uuid(); debug(`id : ${id}`); - const plugin = await zip(contentsZip, key); + const plugin = await PluginZip.build(contentsZip, key); return { plugin, privateKey, diff --git a/src/plugin/packer/pack-plugin-from-manifest.ts b/src/plugin/packer/pack-plugin-from-manifest.ts index 80a914b220..9997344147 100644 --- a/src/plugin/packer/pack-plugin-from-manifest.ts +++ b/src/plugin/packer/pack-plugin-from-manifest.ts @@ -2,11 +2,12 @@ import path from "path"; import packer from "./index"; import { ManifestV1 } from "./manifest"; import { ContentsZip } from "./contents-zip"; +import type { PluginZip } from "./plugin-zip"; export const packPluginFromManifest = async ( manifestJSONPath: string, privateKey: string, -): Promise<{ plugin: Buffer; privateKey: string; id: string }> => { +): Promise<{ plugin: PluginZip; privateKey: string; id: string }> => { const manifest = await ManifestV1.loadJsonFile(manifestJSONPath); const contentsZip = await ContentsZip.createFromManifest( path.dirname(manifestJSONPath), diff --git a/src/plugin/packer/plugin-zip/index.ts b/src/plugin/packer/plugin-zip/index.ts index 2308b1e848..37ebb67d45 100644 --- a/src/plugin/packer/plugin-zip/index.ts +++ b/src/plugin/packer/plugin-zip/index.ts @@ -1,11 +1,31 @@ import streamBuffers from "stream-buffers"; -import { ZipFile } from "yazl"; +import yazl from "yazl"; import _debug from "debug"; import type { PrivateKeyInterface } from "../crypto"; import type { ContentsZipInterface } from "../contents-zip"; +import { ZipFile } from "../zip"; const debug = _debug("plugin-zip"); +export interface PluginZipInterface extends ZipFile {} + +export class PluginZip extends ZipFile implements PluginZipInterface { + private constructor(buffer: Buffer) { + super(buffer); + } + + /** + * Create plugin.zip + */ + public static async build( + contentsZip: ContentsZipInterface, + privateKey: PrivateKeyInterface, + ): Promise { + const buffer = await zip(contentsZip, privateKey); + return new PluginZip(buffer); + } +} + /** * Create plugin.zip */ @@ -19,7 +39,7 @@ export const zip = async ( debug(`zip(): start`); return new Promise((res) => { const output = new streamBuffers.WritableStreamBuffer(); - const zipFile = new ZipFile(); + const zipFile = new yazl.ZipFile(); output.on("finish", () => { debug(`zip(): output finish event`); res(output.getContents() as any); diff --git a/src/plugin/packer/__tests__/helpers/zip.ts b/src/plugin/packer/zip/index.ts similarity index 62% rename from src/plugin/packer/__tests__/helpers/zip.ts rename to src/plugin/packer/zip/index.ts index e78b0225e6..2e006b3bb3 100644 --- a/src/plugin/packer/__tests__/helpers/zip.ts +++ b/src/plugin/packer/zip/index.ts @@ -1,5 +1,21 @@ import yauzl from "yauzl"; +export class ZipFile { + private readonly _buffer: Buffer; + + constructor(buffer: Buffer) { + this._buffer = buffer; + } + + public async fileList(): Promise { + return readZipContentsNames(this.buffer); + } + + public get buffer() { + return this._buffer; + } +} + export const readZipContentsNames = async ( zipFilePath: Buffer, ): Promise => {