diff --git a/packages/infrastructure/lens-link/index.ts b/packages/infrastructure/lens-link/index.ts index d3f1a3915a..74715098fd 100644 --- a/packages/infrastructure/lens-link/index.ts +++ b/packages/infrastructure/lens-link/index.ts @@ -1,13 +1,8 @@ -import { lensLinkFor } from "./src/lens-link"; -import path from "path"; +import { getDi } from "./src/get-di"; +import lensLinkInjectable from "./src/lens-link.injectable"; -const lensIde = path.join("/Users/jsavolainen/Documents/work/test-lens-link", "lens-ide"); +const di = getDi(); -const mikkoFeature = path.join("/Users/jsavolainen/Documents/work/test-lens-link", "mikko-feature"); +const lensLink = di.inject(lensLinkInjectable); -const lensLink = lensLinkFor(); - -lensLink({ - targetDirectory: mikkoFeature, - toDirectory: lensIde, -}); +lensLink(); diff --git a/packages/infrastructure/lens-link/package.json b/packages/infrastructure/lens-link/package.json index 0284cd07d4..3c3efe659f 100644 --- a/packages/infrastructure/lens-link/package.json +++ b/packages/infrastructure/lens-link/package.json @@ -19,7 +19,11 @@ }, "license": "MIT", "homepage": "https://github.com/lensapp/lens", + "dependencies": { + "@ogre-tools/fp": "^15.3.1", + "@ogre-tools/injectable": "^15.3.1", + "@ogre-tools/injectable-extension-for-auto-registration": "^15.3.1", "fs-extra": "^9.0.1" }, diff --git a/packages/infrastructure/lens-link/src/create-lens-link-directories-for.ts b/packages/infrastructure/lens-link/src/create-lens-link-directories-for.ts deleted file mode 100644 index 7c2262d865..0000000000 --- a/packages/infrastructure/lens-link/src/create-lens-link-directories-for.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { awaitAll } from "./await-all"; -import type { GetLensLinkDirectory } from "./get-lens-link-directory"; -import type { EnsureDirectory } from "./lens-link"; -import { pipeline } from "@ogre-tools/fp"; -import { map } from "lodash/fp"; -import type { PackageJsonAndPath } from "./package-json-and-path"; - -export type CreateLensLinkDirectories = (packageJsons: PackageJsonAndPath[]) => Promise; - -export const createLensLinkDirectoriesFor = - (getLensLinkDirectory: GetLensLinkDirectory, ensureDirectory: EnsureDirectory) => - async (packageJsons: PackageJsonAndPath[]) => { - await pipeline( - packageJsons, - map(({ content: { name } }) => getLensLinkDirectory(name)), - map(ensureDirectory), - awaitAll, - ); - }; diff --git a/packages/infrastructure/lens-link/src/create-lens-link-directories.injectable.ts b/packages/infrastructure/lens-link/src/create-lens-link-directories.injectable.ts new file mode 100644 index 0000000000..0d140a1118 --- /dev/null +++ b/packages/infrastructure/lens-link/src/create-lens-link-directories.injectable.ts @@ -0,0 +1,28 @@ +import { awaitAll } from "./await-all"; +import { pipeline } from "@ogre-tools/fp"; +import { map } from "lodash/fp"; +import type { PackageJsonAndPath } from "./package-json-and-path"; + +import { getInjectable } from "@ogre-tools/injectable"; +import { ensureDirectoryInjectable } from "./fs/ensure-directory.injectable"; +import { getLensLinkDirectoryInjectable } from "./get-lens-link-directory.injectable"; + +export type CreateLensLinkDirectories = (packageJsons: PackageJsonAndPath[]) => Promise; + +export const createLensLinkDirectoriesInjectable = getInjectable({ + id: "create-lens-link-directories", + + instantiate: (di): CreateLensLinkDirectories => { + const getLensLinkDirectory = di.inject(getLensLinkDirectoryInjectable); + const ensureDirectory = di.inject(ensureDirectoryInjectable); + + return async (packageJsons: PackageJsonAndPath[]) => { + await pipeline( + packageJsons, + map(({ content: { name } }) => getLensLinkDirectory(name)), + map(ensureDirectory), + awaitAll, + ); + }; + }, +}); diff --git a/packages/infrastructure/lens-link/src/fs/create-symlink.injectable.ts b/packages/infrastructure/lens-link/src/fs/create-symlink.injectable.ts new file mode 100644 index 0000000000..ac8dd29193 --- /dev/null +++ b/packages/infrastructure/lens-link/src/fs/create-symlink.injectable.ts @@ -0,0 +1,9 @@ +import { getInjectable } from "@ogre-tools/injectable"; +import fse from "fs-extra"; + +export type CreateSymlink = (target: string, path: string, type: "dir" | "file") => Promise; + +export const createSymlinkInjectable = getInjectable({ + id: "create-symlink", + instantiate: (): CreateSymlink => fse.symlink, +}); diff --git a/packages/infrastructure/lens-link/src/fs/ensure-directory.injectable.ts b/packages/infrastructure/lens-link/src/fs/ensure-directory.injectable.ts new file mode 100644 index 0000000000..02057b316f --- /dev/null +++ b/packages/infrastructure/lens-link/src/fs/ensure-directory.injectable.ts @@ -0,0 +1,9 @@ +import { getInjectable } from "@ogre-tools/injectable"; +import fse from "fs-extra"; + +export type EnsureDirectory = (path: string) => Promise; + +export const ensureDirectoryInjectable = getInjectable({ + id: "ensure-directory", + instantiate: (): EnsureDirectory => fse.ensureDir, +}); diff --git a/packages/infrastructure/lens-link/src/fs/exists.injectable.ts b/packages/infrastructure/lens-link/src/fs/exists.injectable.ts new file mode 100644 index 0000000000..add929315c --- /dev/null +++ b/packages/infrastructure/lens-link/src/fs/exists.injectable.ts @@ -0,0 +1,9 @@ +import { getInjectable } from "@ogre-tools/injectable"; +import fse from "fs-extra"; + +export type Exists = (path: string) => Promise; + +export const existsInjectable = getInjectable({ + id: "exists", + instantiate: (): Exists => fse.pathExists, +}); diff --git a/packages/infrastructure/lens-link/src/fs/read-json-file.injectable.ts b/packages/infrastructure/lens-link/src/fs/read-json-file.injectable.ts new file mode 100644 index 0000000000..65b50533ba --- /dev/null +++ b/packages/infrastructure/lens-link/src/fs/read-json-file.injectable.ts @@ -0,0 +1,10 @@ +import { getInjectable } from "@ogre-tools/injectable"; +import fse from "fs-extra"; +import type { JsonValue } from "type-fest"; + +export type ReadJsonFile = (path: string) => Promise; + +export const readJsonFileInjectable = getInjectable({ + id: "read-json-file", + instantiate: (): ReadJsonFile => fse.readJson, +}); diff --git a/packages/infrastructure/lens-link/src/fs/remove-directory.injectable.ts b/packages/infrastructure/lens-link/src/fs/remove-directory.injectable.ts new file mode 100644 index 0000000000..d895235ee1 --- /dev/null +++ b/packages/infrastructure/lens-link/src/fs/remove-directory.injectable.ts @@ -0,0 +1,9 @@ +import { getInjectable } from "@ogre-tools/injectable"; +import fse from "fs-extra"; + +export type RemoveDirectory = (path: string) => Promise; + +export const removeDirectoryInjectable = getInjectable({ + id: "remove-directory", + instantiate: (): RemoveDirectory => fse.remove, +}); diff --git a/packages/infrastructure/lens-link/src/fs/write-json-file.injectable.ts b/packages/infrastructure/lens-link/src/fs/write-json-file.injectable.ts new file mode 100644 index 0000000000..a5be5b1bdf --- /dev/null +++ b/packages/infrastructure/lens-link/src/fs/write-json-file.injectable.ts @@ -0,0 +1,10 @@ +import { getInjectable } from "@ogre-tools/injectable"; +import fse from "fs-extra"; +import type { JsonValue } from "type-fest"; + +export type WriteJsonFile = (path: string, value: JsonValue) => Promise; + +export const writeJsonFileInjectable = getInjectable({ + id: "write-json-file", + instantiate: (): WriteJsonFile => fse.writeJson, +}); diff --git a/packages/infrastructure/lens-link/src/get-di.ts b/packages/infrastructure/lens-link/src/get-di.ts new file mode 100644 index 0000000000..edddc764b1 --- /dev/null +++ b/packages/infrastructure/lens-link/src/get-di.ts @@ -0,0 +1,14 @@ +import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration"; +import { createContainer } from "@ogre-tools/injectable"; + +export const getDi = () => { + const di = createContainer("lens-link"); + + autoRegister({ + di, + targetModule: module, + getRequireContexts: () => [require.context("./", true, /\.injectable\.(ts|tsx)$/)], + }); + + return di; +}; diff --git a/packages/infrastructure/lens-link/src/get-lens-link-directory.injectable.ts b/packages/infrastructure/lens-link/src/get-lens-link-directory.injectable.ts new file mode 100644 index 0000000000..7cd2d4173f --- /dev/null +++ b/packages/infrastructure/lens-link/src/get-lens-link-directory.injectable.ts @@ -0,0 +1,14 @@ +import { getInjectable } from "@ogre-tools/injectable"; +import { workingDirectoryInjectable } from "./working-directory.injectable"; +import { resolvePathInjectable } from "./path/resolve-path.injectable"; + +export const getLensLinkDirectoryInjectable = getInjectable({ + id: "get-lens-link-directory", + + instantiate: (di) => { + const resolvePath = di.inject(resolvePathInjectable); + const workingDirectory = di.inject(workingDirectoryInjectable); + + return (moduleName: string) => resolvePath(workingDirectory, "node_modules", moduleName); + }, +}); diff --git a/packages/infrastructure/lens-link/src/get-lens-link-directory.ts b/packages/infrastructure/lens-link/src/get-lens-link-directory.ts deleted file mode 100644 index 7507dcabb7..0000000000 --- a/packages/infrastructure/lens-link/src/get-lens-link-directory.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { ResolvePath } from "./lens-link"; - -export type GetLensLinkDirectory = (moduleName: string) => string; - -export const getLensLinkDirectoryFor = - (workingDirectory: string, resolvePath: ResolvePath): GetLensLinkDirectory => - (moduleName) => - resolvePath(workingDirectory, "node_modules", moduleName); diff --git a/packages/infrastructure/lens-link/src/get-missing-package-jsons-for.ts b/packages/infrastructure/lens-link/src/get-missing-package-jsons-for.ts deleted file mode 100644 index 825e969977..0000000000 --- a/packages/infrastructure/lens-link/src/get-missing-package-jsons-for.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { Exists } from "./lens-link"; -import { pipeline } from "@ogre-tools/fp"; -import { map, filter } from "lodash/fp"; -import { awaitAll } from "./await-all"; - -export const getMissingPackageJsonsFor = (exists: Exists) => async (packageJsonPaths: string[]) => - pipeline( - packageJsonPaths, - map(async (packageJsonPath) => ({ packageJsonPath, exists: await exists(packageJsonPath) })), - awaitAll, - filter(({ exists }) => !exists), - map(({ packageJsonPath }) => packageJsonPath), - ); diff --git a/packages/infrastructure/lens-link/src/get-missing-package-jsons.injectable.ts b/packages/infrastructure/lens-link/src/get-missing-package-jsons.injectable.ts new file mode 100644 index 0000000000..39f82190de --- /dev/null +++ b/packages/infrastructure/lens-link/src/get-missing-package-jsons.injectable.ts @@ -0,0 +1,27 @@ +import { pipeline } from "@ogre-tools/fp"; +import { map, filter } from "lodash/fp"; +import { awaitAll } from "./await-all"; +import { getInjectable } from "@ogre-tools/injectable"; +import { existsInjectable } from "./fs/exists.injectable"; + +export const getMissingPackageJsonsInjectable = getInjectable({ + id: "get-missing-package-jsons", + + instantiate: (di) => { + const exists = di.inject(existsInjectable); + + return async (packageJsonPaths: string[]) => + pipeline( + packageJsonPaths, + + map(async (packageJsonPath) => ({ + packageJsonPath, + exists: await exists(packageJsonPath), + })), + + awaitAll, + filter(({ exists }) => !exists), + map(({ packageJsonPath }) => packageJsonPath), + ); + }, +}); diff --git a/packages/infrastructure/lens-link/src/get-package-json-paths-for.ts b/packages/infrastructure/lens-link/src/get-package-json-paths-for.ts deleted file mode 100644 index 4e6e83f40b..0000000000 --- a/packages/infrastructure/lens-link/src/get-package-json-paths-for.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { ReadJsonFile, ResolvePath } from "./lens-link"; - -export const getPackageJsonPathsFor = - ({ - resolvePath, - readJsonFile, - workingDirectory, - }: { - workingDirectory: string; - resolvePath: ResolvePath; - readJsonFile: ReadJsonFile; - }) => - async (configFilePath: string) => { - const configFile = (await readJsonFile(configFilePath)) as string[]; - - return configFile.map((linkPath: string) => resolvePath(workingDirectory, linkPath, "package.json")); - }; diff --git a/packages/infrastructure/lens-link/src/get-package-json-paths.injectable.ts b/packages/infrastructure/lens-link/src/get-package-json-paths.injectable.ts new file mode 100644 index 0000000000..ea4581d198 --- /dev/null +++ b/packages/infrastructure/lens-link/src/get-package-json-paths.injectable.ts @@ -0,0 +1,20 @@ +import { getInjectable } from "@ogre-tools/injectable"; +import { resolvePathInjectable } from "./path/resolve-path.injectable"; +import { workingDirectoryInjectable } from "./working-directory.injectable"; +import { readJsonFileInjectable } from "./fs/read-json-file.injectable"; + +export const getPackageJsonPathsInjectable = getInjectable({ + id: "get-package-json-paths", + + instantiate: (di) => { + const readJsonFile = di.inject(readJsonFileInjectable); + const resolvePath = di.inject(resolvePathInjectable); + const workingDirectory = di.inject(workingDirectoryInjectable); + + return async (configFilePath: string) => { + const configFile = (await readJsonFile(configFilePath)) as string[]; + + return configFile.map((linkPath: string) => resolvePath(workingDirectory, linkPath, "package.json")); + }; + }, +}); diff --git a/packages/infrastructure/lens-link/src/get-package-jsons-for.ts b/packages/infrastructure/lens-link/src/get-package-jsons-for.ts deleted file mode 100644 index 1fab1f7222..0000000000 --- a/packages/infrastructure/lens-link/src/get-package-jsons-for.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { awaitAll } from "./await-all"; -import type { ReadJsonFile } from "./lens-link"; -import { pipeline } from "@ogre-tools/fp"; -import { map } from "lodash/fp"; -import type { PackageJson } from "./package-json-and-path"; - -export const getPackageJsonsFor = (readJsonFile: ReadJsonFile) => async (packageJsonPaths: string[]) => - pipeline( - packageJsonPaths, - - map(async (packageJsonPath) => ({ - packageJsonPath, - content: (await readJsonFile(packageJsonPath)) as unknown as PackageJson, - })), - - awaitAll, - ); diff --git a/packages/infrastructure/lens-link/src/get-package-jsons.injectable.ts b/packages/infrastructure/lens-link/src/get-package-jsons.injectable.ts new file mode 100644 index 0000000000..9d00c027d9 --- /dev/null +++ b/packages/infrastructure/lens-link/src/get-package-jsons.injectable.ts @@ -0,0 +1,26 @@ +import { awaitAll } from "./await-all"; +import { pipeline } from "@ogre-tools/fp"; +import { map } from "lodash/fp"; +import type { PackageJson } from "./package-json-and-path"; +import { getInjectable } from "@ogre-tools/injectable"; +import { readJsonFileInjectable } from "./fs/read-json-file.injectable"; + +export const getPackageJsonsInjectable = getInjectable({ + id: "get-package-jsons", + + instantiate: (di) => { + const readJsonFile = di.inject(readJsonFileInjectable); + + return async (packageJsonPaths: string[]) => + pipeline( + packageJsonPaths, + + map(async (packageJsonPath) => ({ + packageJsonPath, + content: (await readJsonFile(packageJsonPath)) as unknown as PackageJson, + })), + + awaitAll, + ); + }, +}); diff --git a/packages/infrastructure/lens-link/src/lens-link.injectable.ts b/packages/infrastructure/lens-link/src/lens-link.injectable.ts new file mode 100644 index 0000000000..d6c507422c --- /dev/null +++ b/packages/infrastructure/lens-link/src/lens-link.injectable.ts @@ -0,0 +1,91 @@ +import { dirname } from "path"; +import { pipeline } from "@ogre-tools/fp"; +import { flatMap, map } from "lodash/fp"; +import { removeExistingLensLinkDirectoriesInjectable } from "./remove-existing-lens-link-directories.injectable"; +import { createLensLinkDirectoriesInjectable } from "./create-lens-link-directories.injectable"; +import { getMissingPackageJsonsInjectable } from "./get-missing-package-jsons.injectable"; +import { getPackageJsonsInjectable } from "./get-package-jsons.injectable"; +import { getPackageJsonPathsInjectable } from "./get-package-json-paths.injectable"; +import { getInjectable } from "@ogre-tools/injectable"; +import { getLensLinkDirectoryInjectable } from "./get-lens-link-directory.injectable"; +import { resolvePathInjectable } from "./path/resolve-path.injectable"; +import { existsInjectable } from "./fs/exists.injectable"; +import { writeJsonFileInjectable } from "./fs/write-json-file.injectable"; +import { createSymlinkInjectable } from "./fs/create-symlink.injectable"; +import { workingDirectoryInjectable } from "./working-directory.injectable"; + +export type LensLink = () => Promise; + +const lensLinkInjectable = getInjectable({ + id: "lens-link", + + instantiate: (di): LensLink => { + const getPackageJsons = di.inject(getPackageJsonsInjectable); + const getLensLinkDirectory = di.inject(getLensLinkDirectoryInjectable); + const getMissingPackageJsons = di.inject(getMissingPackageJsonsInjectable); + const removeExistingLensLinkDirectories = di.inject(removeExistingLensLinkDirectoriesInjectable); + const createLensLinkDirectories = di.inject(createLensLinkDirectoriesInjectable); + const getPackageJsonPaths = di.inject(getPackageJsonPathsInjectable); + const resolvePath = di.inject(resolvePathInjectable); + const exists = di.inject(existsInjectable); + const writeJsonFile = di.inject(writeJsonFileInjectable); + const createSymlink = di.inject(createSymlinkInjectable); + const workingDirectory = di.inject(workingDirectoryInjectable); + + return async () => { + const configFilePath = resolvePath(workingDirectory, ".lens-links.json"); + + const configFileExists = await exists(configFilePath); + + if (!configFileExists) { + await writeJsonFile(configFilePath, []); + + return; + } + + const packageJsonPaths = await getPackageJsonPaths(configFilePath); + + const missingPackageJsons = await getMissingPackageJsons(packageJsonPaths); + + if (missingPackageJsons.length) { + throw new Error( + `Tried to install Lens links, but configured package.jsons were not found: "${missingPackageJsons.join( + '", "', + )}".`, + ); + } + + const packageJsons = await getPackageJsons(packageJsonPaths); + + await removeExistingLensLinkDirectories(packageJsons); + + await createLensLinkDirectories(packageJsons); + + pipeline( + packageJsons, + + flatMap(({ packageJsonPath, content }) => { + const lensLinkDirectory = getLensLinkDirectory(content.name); + + return [ + { + target: packageJsonPath, + source: resolvePath(lensLinkDirectory, "package.json"), + type: "file" as const, + }, + + ...content.files.map((x) => ({ + target: resolvePath(dirname(packageJsonPath), x), + source: resolvePath(lensLinkDirectory, x), + type: "dir" as const, + })), + ]; + }), + + map(({ target, source, type }) => createSymlink(target, source, type)), + ); + }; + }, +}); + +export default lensLinkInjectable; diff --git a/packages/infrastructure/lens-link/src/lens-link.test.ts b/packages/infrastructure/lens-link/src/lens-link.test.ts index 64f0c0f4e8..f66c09072d 100644 --- a/packages/infrastructure/lens-link/src/lens-link.test.ts +++ b/packages/infrastructure/lens-link/src/lens-link.test.ts @@ -1,17 +1,24 @@ -import { - EnsureDirectory, - CreateSymlink, - Exists, - lensLinkFor, - ReadJsonFile, - WriteJsonFile, - RemoveDirectory, -} from "./lens-link"; -import type { LensLink } from "./lens-link"; +import type { LensLink } from "./lens-link.injectable"; import asyncFn from "@async-fn/jest"; import type { AsyncFnMock } from "@async-fn/jest"; import { getPromiseStatus } from "@k8slens/test-utils"; import path from "path"; +import lensLinkInjectable from "./lens-link.injectable"; +import type { Exists } from "./fs/exists.injectable"; +import type { ReadJsonFile } from "./fs/read-json-file.injectable"; +import type { WriteJsonFile } from "./fs/write-json-file.injectable"; +import type { CreateSymlink } from "./fs/create-symlink.injectable"; +import type { EnsureDirectory } from "./fs/ensure-directory.injectable"; +import type { RemoveDirectory } from "./fs/remove-directory.injectable"; +import { workingDirectoryInjectable } from "./working-directory.injectable"; +import { resolvePathInjectable } from "./path/resolve-path.injectable"; +import { existsInjectable } from "./fs/exists.injectable"; +import { readJsonFileInjectable } from "./fs/read-json-file.injectable"; +import { writeJsonFileInjectable } from "./fs/write-json-file.injectable"; +import { createSymlinkInjectable } from "./fs/create-symlink.injectable"; +import { ensureDirectoryInjectable } from "./fs/ensure-directory.injectable"; +import { removeDirectoryInjectable } from "./fs/remove-directory.injectable"; +import { getDi } from "./get-di"; describe("lens-link", () => { let lensLink: LensLink; @@ -30,16 +37,18 @@ describe("lens-link", () => { ensureDirectoryMock = asyncFn(); removeDirectoryMock = asyncFn(); - lensLink = lensLinkFor({ - workingDirectory: "/some-directory/some-project", - resolvePath: path.posix.resolve, - exists: existsMock, - readJsonFile: readJsonFileMock, - writeJsonFile: writeJsonFileMock, - createSymlink: createSymlinkMock, - ensureDirectory: ensureDirectoryMock, - removeDirectory: removeDirectoryMock, - }); + const di = getDi(); + + di.override(workingDirectoryInjectable, () => "/some-directory/some-project"); + di.override(resolvePathInjectable, () => path.posix.resolve); + di.override(existsInjectable, () => existsMock); + di.override(readJsonFileInjectable, () => readJsonFileMock); + di.override(writeJsonFileInjectable, () => writeJsonFileMock); + di.override(createSymlinkInjectable, () => createSymlinkMock); + di.override(ensureDirectoryInjectable, () => ensureDirectoryMock); + di.override(removeDirectoryInjectable, () => removeDirectoryMock); + + lensLink = di.inject(lensLinkInjectable); }); describe("when called", () => { @@ -265,149 +274,5 @@ describe("lens-link", () => { }); }); }); - - xdescribe("normally", () => { - it("checks for existence of package.jsons", () => { - expect(existsMock.mock.calls).toEqual([["some-target-directory/package.json"]]); - }); - - it("when package.json for target directory is missing, throws", () => { - existsMock.resolveSpecific( - ([packageJsonPath]) => packageJsonPath === "some-target-directory/package.json", - false, - ); - - existsMock.resolveSpecific(([packageJsonPath]) => packageJsonPath === "some-to-directory/package.json", true); - - return expect(actualPromise).rejects.toThrow( - `Tried to link package but package.json is missing in "some-target-directory/package.json"`, - ); - }); - - it("when package.json for to directory is missing, throws", () => { - existsMock.resolveSpecific( - ([packageJsonPath]) => packageJsonPath === "some-target-directory/package.json", - true, - ); - - existsMock.resolveSpecific(([packageJsonPath]) => packageJsonPath === "some-to-directory/package.json", false); - - return expect(actualPromise).rejects.toThrow( - `Tried to link package but package.json is missing in "some-to-directory/package.json"`, - ); - }); - - it("when both package.jsons are missing, throws", () => { - existsMock.resolveSpecific( - ([packageJsonPath]) => packageJsonPath === "some-target-directory/package.json", - false, - ); - - existsMock.resolveSpecific(([packageJsonPath]) => packageJsonPath === "some-to-directory/package.json", false); - - return expect(actualPromise).rejects.toThrow( - `Tried to link package but package.jsons are missing in "some-target-directory/package.json", "some-to-directory/package.json"`, - ); - }); - - describe("when check for package.jsons resolves with both existing", () => { - beforeEach(async () => { - await existsMock.resolve(true); - await existsMock.resolve(true); - }); - - it("reads the target package json", () => { - expect(readJsonFileMock).toHaveBeenCalledWith("some-target-directory/package.json"); - }); - - it("does not read the to package json", () => { - expect(readJsonFileMock).not.toHaveBeenCalledWith("some-to-directory/package.json"); - }); - - describe("when package json resolves with enough data", () => { - beforeEach(async () => { - existsMock.mockClear(); - - await readJsonFileMock.resolve({ - name: "some-npm-package", - main: "some-directory-in-package/some-entrypoint.js", - files: ["some-directory-in-package", "some-other-directory-in-package"], - - irrelevant: "property", - }); - }); - - it("checks for existence of target npm package in node modules of the to-package", () => { - expect(existsMock).toHaveBeenCalledWith("some-to-directory/node_modules/some-npm-package"); - }); - - it("when check for existence of target npm package in node_modules resolves with existing directory, throws", () => { - existsMock.resolve(true); - - // Note, this shouldn't throw, it should ask whether user wants to override existing - return expect(actualPromise).rejects.toThrow("Asd"); - }); - - describe("when check for existence of target npm package in node_modules resolves with non existing directory", () => { - beforeEach(async () => { - await existsMock.resolve(false); - }); - - it("ensures directory for the target npm package in node modules", () => { - expect(ensureDirectoryMock).toHaveBeenCalledWith("some-to-directory/node_modules/some-npm-package"); - }); - - describe("when ensuring directory resolves", () => { - beforeEach(async () => { - await ensureDirectoryMock.resolve(); - }); - - it("creates minimal package.json in the node_modules", () => { - expect(writeJsonFileMock).toHaveBeenCalledWith( - "some-to-directory/node_modules/some-npm-package/package.json", - - { - name: "some-npm-package", - main: "some-directory-in-package/some-entrypoint.js", - files: ["some-directory-in-package", "some-other-directory-in-package"], - }, - ); - }); - - it("creates the symlinks for the files or directories in package.json", () => { - expect(createSymlinkMock.mock.calls).toEqual([ - [ - "some-target-directory/some-directory-in-package", - "some-to-directory/node_modules/some-npm-package/some-directory-in-package", - "dir", - ], - [ - "some-target-directory/some-other-directory-in-package", - "some-to-directory/node_modules/some-npm-package/some-other-directory-in-package", - "dir", - ], - ]); - }); - - it("does not resolve yet", async () => { - const promiseStatus = await getPromiseStatus(actualPromise); - - expect(promiseStatus.fulfilled).toBe(false); - }); - - it("when creation files and symlinks resolve, resolves", async () => { - writeJsonFileMock.resolve(); - createSymlinkMock.resolve(); - createSymlinkMock.resolve(); - - const promiseStatus = await getPromiseStatus(actualPromise); - - expect(promiseStatus.fulfilled).toBe(true); - }); - }); - }); - }); - }); - }); }); }); diff --git a/packages/infrastructure/lens-link/src/lens-link.ts b/packages/infrastructure/lens-link/src/lens-link.ts deleted file mode 100644 index 65bc52ad26..0000000000 --- a/packages/infrastructure/lens-link/src/lens-link.ts +++ /dev/null @@ -1,133 +0,0 @@ -import type { JsonValue } from "type-fest"; -import fse from "fs-extra"; -import { dirname, resolve as _resolvePath } from "path"; -import { pipeline } from "@ogre-tools/fp"; -import { flatMap, map } from "lodash/fp"; -import { getLensLinkDirectoryFor } from "./get-lens-link-directory"; -import { removeExistingLensLinkDirectoriesFor } from "./remove-existing-lens-link-directories-for"; -import { createLensLinkDirectoriesFor } from "./create-lens-link-directories-for"; -import { getMissingPackageJsonsFor } from "./get-missing-package-jsons-for"; -import { getPackageJsonsFor } from "./get-package-jsons-for"; -import { getPackageJsonPathsFor } from "./get-package-json-paths-for"; - -export type LensLink = () => Promise; - -export type CreateSymlink = (target: string, path: string, type: "dir" | "file") => Promise; -export type EnsureDirectory = (path: string) => Promise; -export type RemoveDirectory = (path: string) => Promise; -export type WriteJsonFile = (path: string, value: JsonValue) => Promise; -export type ReadJsonFile = (path: string) => Promise; -export type Exists = (path: string) => Promise; -export type ResolvePath = typeof _resolvePath; - -interface Dependencies { - workingDirectory: string; - resolvePath: ResolvePath; - exists: Exists; - readJsonFile: ReadJsonFile; - writeJsonFile: WriteJsonFile; - createSymlink: CreateSymlink; - ensureDirectory: EnsureDirectory; - removeDirectory: RemoveDirectory; -} - -const existsAsd = fse.pathExists; -const readJsonFileAsd = fse.readJson; -const writeJsonFileAsd = fse.writeJson; -const createSymlinkAsd = fse.symlink; -const ensureDirectoryAsd = fse.ensureDir; -const removeDirectoryAsd = fse.remove; - -export const lensLinkFor = - ( - { - workingDirectory, - resolvePath, - exists, - readJsonFile, - writeJsonFile, - createSymlink, - ensureDirectory, - removeDirectory, - }: Dependencies = { - workingDirectory: process.cwd(), - resolvePath: _resolvePath, - exists: existsAsd, - readJsonFile: readJsonFileAsd, - writeJsonFile: writeJsonFileAsd, - createSymlink: createSymlinkAsd, - ensureDirectory: ensureDirectoryAsd, - removeDirectory: removeDirectoryAsd, - }, - ): LensLink => - async () => { - const getPackageJsons = getPackageJsonsFor(readJsonFile); - const getLensLinkDirectory = getLensLinkDirectoryFor(workingDirectory, resolvePath); - const getMissingPackageJsons = getMissingPackageJsonsFor(exists); - - const removeExistingLensLinkDirectories = removeExistingLensLinkDirectoriesFor( - getLensLinkDirectory, - exists, - removeDirectory, - ); - - const createLensLinkDirectories = createLensLinkDirectoriesFor(getLensLinkDirectory, ensureDirectory); - - const getPackageJsonPaths = getPackageJsonPathsFor({ - workingDirectory, - resolvePath, - readJsonFile, - }); - - const configFilePath = resolvePath(workingDirectory, ".lens-links.json"); - - const configFileExists = await exists(configFilePath); - - if (!configFileExists) { - await writeJsonFile(configFilePath, []); - - return; - } - - const packageJsonPaths = await getPackageJsonPaths(configFilePath); - - const missingPackageJsons = await getMissingPackageJsons(packageJsonPaths); - - if (missingPackageJsons.length) { - throw new Error( - `Tried to install Lens links, but configured package.jsons were not found: "${missingPackageJsons.join( - '", "', - )}".`, - ); - } - - const packageJsons = await getPackageJsons(packageJsonPaths); - - await removeExistingLensLinkDirectories(packageJsons); - - await createLensLinkDirectories(packageJsons); - - pipeline( - packageJsons, - - flatMap(({ packageJsonPath, content }) => { - const lensLinkDirectory = getLensLinkDirectory(content.name); - - return [ - { - target: packageJsonPath, - source: resolvePath(lensLinkDirectory, "package.json"), - type: "file" as const, - }, - - ...content.files.map((x) => ({ - target: resolvePath(dirname(packageJsonPath), x), - source: resolvePath(lensLinkDirectory, x), - type: "dir" as const, - })), - ]; - }), - - map(({ target, source, type }) => createSymlink(target, source, type)), - ); - }; diff --git a/packages/infrastructure/lens-link/src/path/resolve-path.injectable.ts b/packages/infrastructure/lens-link/src/path/resolve-path.injectable.ts new file mode 100644 index 0000000000..963275ae97 --- /dev/null +++ b/packages/infrastructure/lens-link/src/path/resolve-path.injectable.ts @@ -0,0 +1,9 @@ +import { getInjectable } from "@ogre-tools/injectable"; +import path from "path"; + +export type ResolvePath = typeof path.resolve; + +export const resolvePathInjectable = getInjectable({ + id: "resolve-path", + instantiate: (): ResolvePath => path.resolve, +}); diff --git a/packages/infrastructure/lens-link/src/remove-existing-lens-link-directories-for.ts b/packages/infrastructure/lens-link/src/remove-existing-lens-link-directories-for.ts deleted file mode 100644 index 9658145270..0000000000 --- a/packages/infrastructure/lens-link/src/remove-existing-lens-link-directories-for.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { GetLensLinkDirectory } from "./get-lens-link-directory"; -import type { Exists, RemoveDirectory } from "./lens-link"; -import { pipeline } from "@ogre-tools/fp"; -import { map, filter } from "lodash/fp"; -import type { PackageJsonAndPath } from "./ensure-lens-link-directories"; -import { awaitAll } from "./await-all"; - -export type RemoveExistingLensLinkDirectories = (packageJsons: PackageJsonAndPath[]) => Promise; - -export const removeExistingLensLinkDirectoriesFor = - ( - getLensLinkDirectory: GetLensLinkDirectory, - exists: Exists, - removeDirectory: RemoveDirectory, - ): RemoveExistingLensLinkDirectories => - async (packageJsons) => { - await pipeline( - packageJsons, - - map(async ({ content }) => { - const lensLinkDirectory = getLensLinkDirectory(content.name); - - return { - directory: lensLinkDirectory, - exists: await exists(lensLinkDirectory), - }; - }), - - awaitAll, - - filter(({ exists }) => exists), - - map(({ directory }) => removeDirectory(directory)), - - awaitAll, - ); - }; diff --git a/packages/infrastructure/lens-link/src/remove-existing-lens-link-directories.injectable.ts b/packages/infrastructure/lens-link/src/remove-existing-lens-link-directories.injectable.ts new file mode 100644 index 0000000000..45571e4ca0 --- /dev/null +++ b/packages/infrastructure/lens-link/src/remove-existing-lens-link-directories.injectable.ts @@ -0,0 +1,43 @@ +import { pipeline } from "@ogre-tools/fp"; +import { map, filter } from "lodash/fp"; +import { awaitAll } from "./await-all"; +import { getInjectable } from "@ogre-tools/injectable"; +import { getLensLinkDirectoryInjectable } from "./get-lens-link-directory.injectable"; +import { existsInjectable } from "./fs/exists.injectable"; +import { removeDirectoryInjectable } from "./fs/remove-directory.injectable"; +import type { PackageJsonAndPath } from "./package-json-and-path"; + +export type RemoveExistingLensLinkDirectories = (packageJsons: PackageJsonAndPath[]) => Promise; + +export const removeExistingLensLinkDirectoriesInjectable = getInjectable({ + id: "remove-existing-lens-link-directories", + + instantiate: (di): RemoveExistingLensLinkDirectories => { + const getLensLinkDirectory = di.inject(getLensLinkDirectoryInjectable); + const exists = di.inject(existsInjectable); + const removeDirectory = di.inject(removeDirectoryInjectable); + + return async (packageJsons) => { + await pipeline( + packageJsons, + + map(async ({ content }) => { + const lensLinkDirectory = getLensLinkDirectory(content.name); + + return { + directory: lensLinkDirectory, + exists: await exists(lensLinkDirectory), + }; + }), + + awaitAll, + + filter(({ exists }) => exists), + + map(({ directory }) => removeDirectory(directory)), + + awaitAll, + ); + }; + }, +}); diff --git a/packages/infrastructure/lens-link/src/working-directory.injectable.ts b/packages/infrastructure/lens-link/src/working-directory.injectable.ts new file mode 100644 index 0000000000..96e194f69e --- /dev/null +++ b/packages/infrastructure/lens-link/src/working-directory.injectable.ts @@ -0,0 +1,6 @@ +import { getInjectable } from "@ogre-tools/injectable"; + +export const workingDirectoryInjectable = getInjectable({ + id: "working-directory", + instantiate: () => process.cwd(), +});