From af044c14a98ac95e49ad7732fe6faf91b56a56f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ole=C5=9B?= Date: Wed, 24 Feb 2021 23:12:51 +0100 Subject: [PATCH] feat: add lock file storage --- README.md | 10 +++--- src/sandbox.ts | 86 ++++++++++++++++++++++++++++++++------------------ 2 files changed, 60 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index ff7e1e5..f5b38f8 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@

karton

-

Create sandbox for E2E tests 📦

+

Test your package in a sandbox 📦

+

⚠️ In development ⚠️

## Installation -This loader requires minimum Node.js 10 +This package requires minimum Node.js 10 ```sh # with npm @@ -47,7 +48,6 @@ describe('my-package', () => { }); afterAll(async () => { await sandbox.cleanup(); - await myPackage.remove(); }) it.each([ @@ -55,9 +55,7 @@ describe('my-package', () => { externalPackage('webpack', '^5.0.0') ])('works with webpack %p', async (webpack) => { await sandbox.load(path.join(__dirname, 'fixtures/basic')); - await sandbox.install('yarn', { - dependencies: [myPackage, webpack] - }); + await sandbox.install('yarn', [myPackage, webpack]); const result = await sandbox.exec('node src/test.js'); expect(result).toEqual('my-package awesome output'); diff --git a/src/sandbox.ts b/src/sandbox.ts index 5aa4736..2a33ef7 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -1,6 +1,7 @@ import { resolve, dirname } from "path"; import fs from "fs-extra"; import os from "os"; +import crypto from "crypto"; import { exec, ChildProcess } from "child_process"; import spawn from "cross-spawn"; import stripAnsi from "strip-ansi"; @@ -9,28 +10,24 @@ import { defaultLogger, Logger } from "./logger"; import { Package } from "./package"; import { retry, RetryOptions, wait } from "./async"; -type PackageManager = "yarn" | "npm"; - -interface InstallOverwrites { - dependencies?: Package[]; - devDependencies?: Package[]; - optionalDependencies?: Package[]; -} - interface CommandOptions { cwd?: string; env?: Record; } +interface InstallOptions { + lockDirectory?: string; +} + interface Sandbox { context: string; reset(options?: RetryOptions): Promise; cleanup(options?: RetryOptions): Promise; load(directory: string, options?: RetryOptions): Promise; install( - manager: PackageManager, - overwrites: InstallOverwrites, - options?: RetryOptions + manager: "yarn" | "npm", + overwrites: Package[], + options?: InstallOptions & RetryOptions ): Promise; write(path: string, content: string, options?: RetryOptions): Promise; read(path: string, options?: RetryOptions): Promise; @@ -103,9 +100,9 @@ async function createSandbox(logger: Logger = defaultLogger): Promise { return retry(() => fs.copy(directory, context), logger, options); }, install: async ( - manager: PackageManager, - overwrites: InstallOverwrites = {}, - options?: RetryOptions + manager: "yarn" | "npm", + overwrites: Package[], + options?: InstallOptions & RetryOptions ) => { logger.log("Installing dependencies..."); @@ -115,23 +112,15 @@ async function createSandbox(logger: Logger = defaultLogger): Promise { ); } - const packageJSON = JSON.parse( + const originalPackageJSON = JSON.parse( await sandbox.read("package.json", options) ); - for (const target of [ - "dependencies", - "devDependencies", - "optionalDependencies", - ] as const) { - const packages = overwrites[target]; - if (packages) { - if (!packageJSON[target]) { - packageJSON[target] = {}; - } - for (const pkg of packages) { - packageJSON[target][pkg.name] = pkg.version; - } - } + const packageJSON = { + ...originalPackageJSON, + dependencies: originalPackageJSON.dependencies || {}, + }; + for (const { name, version } of overwrites) { + packageJSON.dependencies[name] = version; } await sandbox.write( "package.json", @@ -139,12 +128,49 @@ async function createSandbox(logger: Logger = defaultLogger): Promise { options ); + let lockFile: string | undefined; + if (options?.lockDirectory) { + lockFile = resolve( + options.lockDirectory, + `${crypto + .createHash("md5") + .update( + JSON.stringify([ + manager, + overwrites.filter((pkg) => pkg.immutable), + originalPackageJSON.dependencies, + originalPackageJSON.devDependencies, + ]) + ) + .digest("hex")}.lock` + ); + } + + const tryToLoadLockFile = async (managerFile: string) => { + if (lockFile && (await fs.pathExists(lockFile))) { + await sandbox.write( + managerFile, + await fs.readFile(lockFile, "utf-8") + ); + } + }; + + const tryToStoreLockFile = async (managerFile: string) => { + if (lockFile && (await sandbox.exists(managerFile))) { + await fs.writeFile(lockFile, await sandbox.read(managerFile)); + } + }; + switch (manager) { case "yarn": + await tryToLoadLockFile("yarn.lock"); await sandbox.exec(`yarn install --prefer-offline`, options); + await tryToStoreLockFile("yarn.lock"); break; case "npm": + await tryToLoadLockFile("package-lock.json"); await sandbox.exec(`npm install`, options); + await tryToStoreLockFile("package-lock.json"); break; } }, @@ -313,4 +339,4 @@ async function createSandbox(logger: Logger = defaultLogger): Promise { return sandbox; } -export { Sandbox, createSandbox, PackageManager, InstallOverwrites }; +export { Sandbox, createSandbox };