mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
feat: Implement first draft of better NPM-linking script
It's better because it doesn't suffer from npm link's habit of bringing in node_modules of link target, which breaks peer dependencies. Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
parent
192d8119db
commit
3c9abdb015
485
package-lock.json
generated
485
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
6
packages/infrastructure/lens-link/.eslintrc.json
Normal file
6
packages/infrastructure/lens-link/.eslintrc.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "@k8slens/eslint-config/eslint",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json"
|
||||
}
|
||||
}
|
||||
1
packages/infrastructure/lens-link/.prettierrc
Normal file
1
packages/infrastructure/lens-link/.prettierrc
Normal file
@ -0,0 +1 @@
|
||||
"@k8slens/eslint-config/prettier"
|
||||
13
packages/infrastructure/lens-link/index.ts
Normal file
13
packages/infrastructure/lens-link/index.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { lensLinkFor } from "./src/lens-link";
|
||||
import path from "path";
|
||||
|
||||
const lensIde = path.join("/Users/jsavolainen/Documents/work/test-lens-link", "lens-ide");
|
||||
|
||||
const mikkoFeature = path.join("/Users/jsavolainen/Documents/work/test-lens-link", "mikko-feature");
|
||||
|
||||
const lensLink = lensLinkFor();
|
||||
|
||||
lensLink({
|
||||
targetDirectory: mikkoFeature,
|
||||
toDirectory: lensIde,
|
||||
});
|
||||
1
packages/infrastructure/lens-link/jest.config.js
Normal file
1
packages/infrastructure/lens-link/jest.config.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require("@k8slens/jest").monorepoPackageConfig(__dirname).configForReact;
|
||||
40
packages/infrastructure/lens-link/package.json
Normal file
40
packages/infrastructure/lens-link/package.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@k8slens/lens-link",
|
||||
"private": false,
|
||||
"version": "1.0.0-alpha.0",
|
||||
"description": "Equivalent for npm link, but doing it properly.",
|
||||
"type": "commonjs",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/lensapp/lens.git"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"author": {
|
||||
"name": "OpenLens Authors",
|
||||
"email": "info@k8slens.dev"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/lensapp/lens",
|
||||
"dependencies": {
|
||||
"fs-extra": "^9.0.1"
|
||||
},
|
||||
|
||||
"devDependencies": {
|
||||
"@async-fn/jest": "^1.6.4"
|
||||
},
|
||||
|
||||
"scripts": {
|
||||
"clean": "rimraf dist/",
|
||||
"build": "webpack",
|
||||
"dev-build": "webpack --watch",
|
||||
"test:unit": "jest --coverage --runInBand",
|
||||
"lint": "lens-lint",
|
||||
"lint:fix": "lens-lint --fix",
|
||||
|
||||
"mikko": "chmod +x ./dist/index.js; node ./dist/index.js"
|
||||
}
|
||||
}
|
||||
1
packages/infrastructure/lens-link/src/await-all.ts
Normal file
1
packages/infrastructure/lens-link/src/await-all.ts
Normal file
@ -0,0 +1 @@
|
||||
export const awaitAll = <T extends Promise<unknown>[]>(x: T) => Promise.all(x);
|
||||
@ -0,0 +1,19 @@
|
||||
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<void>;
|
||||
|
||||
export const createLensLinkDirectoriesFor =
|
||||
(getLensLinkDirectory: GetLensLinkDirectory, ensureDirectory: EnsureDirectory) =>
|
||||
async (packageJsons: PackageJsonAndPath[]) => {
|
||||
await pipeline(
|
||||
packageJsons,
|
||||
map(({ content: { name } }) => getLensLinkDirectory(name)),
|
||||
map(ensureDirectory),
|
||||
awaitAll,
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,8 @@
|
||||
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);
|
||||
@ -0,0 +1,13 @@
|
||||
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),
|
||||
);
|
||||
@ -0,0 +1,17 @@
|
||||
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"));
|
||||
};
|
||||
@ -0,0 +1,17 @@
|
||||
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,
|
||||
);
|
||||
413
packages/infrastructure/lens-link/src/lens-link.test.ts
Normal file
413
packages/infrastructure/lens-link/src/lens-link.test.ts
Normal file
@ -0,0 +1,413 @@
|
||||
import {
|
||||
EnsureDirectory,
|
||||
CreateSymlink,
|
||||
Exists,
|
||||
lensLinkFor,
|
||||
ReadJsonFile,
|
||||
WriteJsonFile,
|
||||
RemoveDirectory,
|
||||
} from "./lens-link";
|
||||
import type { LensLink } from "./lens-link";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import { getPromiseStatus } from "@k8slens/test-utils";
|
||||
import path from "path";
|
||||
|
||||
describe("lens-link", () => {
|
||||
let lensLink: LensLink;
|
||||
let existsMock: AsyncFnMock<Exists>;
|
||||
let readJsonFileMock: AsyncFnMock<ReadJsonFile>;
|
||||
let writeJsonFileMock: AsyncFnMock<WriteJsonFile>;
|
||||
let createSymlinkMock: AsyncFnMock<CreateSymlink>;
|
||||
let ensureDirectoryMock: AsyncFnMock<EnsureDirectory>;
|
||||
let removeDirectoryMock: AsyncFnMock<RemoveDirectory>;
|
||||
|
||||
beforeEach(() => {
|
||||
existsMock = asyncFn();
|
||||
readJsonFileMock = asyncFn();
|
||||
writeJsonFileMock = asyncFn();
|
||||
createSymlinkMock = asyncFn();
|
||||
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,
|
||||
});
|
||||
});
|
||||
|
||||
describe("when called", () => {
|
||||
let actualPromise: Promise<void>;
|
||||
|
||||
beforeEach(() => {
|
||||
actualPromise = lensLink();
|
||||
});
|
||||
|
||||
it("checks for existence of config file", () => {
|
||||
expect(existsMock).toHaveBeenCalledWith("/some-directory/some-project/.lens-links.json");
|
||||
});
|
||||
|
||||
describe("given config file does not exist", () => {
|
||||
beforeEach(async () => {
|
||||
await existsMock.resolve(false);
|
||||
});
|
||||
|
||||
it("creates it as empty", () => {
|
||||
expect(writeJsonFileMock).toHaveBeenCalledWith("/some-directory/some-project/.lens-links.json", []);
|
||||
});
|
||||
|
||||
it("does not read config file", () => {
|
||||
expect(readJsonFileMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not stop the script yet", async () => {
|
||||
const promiseStatus = await getPromiseStatus(actualPromise);
|
||||
|
||||
expect(promiseStatus.fulfilled).toBe(false);
|
||||
});
|
||||
|
||||
it("when creation resolves, stops the script", async () => {
|
||||
await writeJsonFileMock.resolve();
|
||||
|
||||
const promiseStatus = await getPromiseStatus(actualPromise);
|
||||
|
||||
expect(promiseStatus.fulfilled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given config file exists", () => {
|
||||
beforeEach(async () => {
|
||||
await existsMock.resolve(true);
|
||||
});
|
||||
|
||||
it("does not create it again", () => {
|
||||
expect(writeJsonFileMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reads config file", () => {
|
||||
expect(readJsonFileMock).toHaveBeenCalledWith("/some-directory/some-project/.lens-links.json");
|
||||
});
|
||||
|
||||
describe("when config file resolves as empty", () => {
|
||||
beforeEach(async () => {
|
||||
await readJsonFileMock.resolve([]);
|
||||
});
|
||||
|
||||
it("stops the script", async () => {
|
||||
const promiseStatus = await getPromiseStatus(actualPromise);
|
||||
|
||||
expect(promiseStatus.fulfilled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when config file resolves with module paths", () => {
|
||||
beforeEach(async () => {
|
||||
existsMock.mockClear();
|
||||
|
||||
await readJsonFileMock.resolve(["../some-module", "/some-other-directory/some-other-module"]);
|
||||
});
|
||||
|
||||
it("checks for existence of package.jsons in configured module paths", () => {
|
||||
expect(existsMock.mock.calls).toEqual([
|
||||
["/some-directory/some-module/package.json"],
|
||||
["/some-other-directory/some-other-module/package.json"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("given some of the package.jsons do not exist, throws", () => {
|
||||
existsMock.resolve(false);
|
||||
existsMock.resolve(false);
|
||||
|
||||
return expect(actualPromise).rejects.toThrow(
|
||||
'Tried to install Lens links, but configured package.jsons were not found: "/some-directory/some-module/package.json", "/some-other-directory/some-other-module/package.json".',
|
||||
);
|
||||
});
|
||||
|
||||
describe("given all configured package.jsons exist", () => {
|
||||
beforeEach(async () => {
|
||||
readJsonFileMock.mockClear();
|
||||
|
||||
await existsMock.resolve(true);
|
||||
await existsMock.resolve(true);
|
||||
});
|
||||
|
||||
it("reads contents of package.jsons", () => {
|
||||
expect(readJsonFileMock.mock.calls).toEqual([
|
||||
["/some-directory/some-module/package.json"],
|
||||
["/some-other-directory/some-other-module/package.json"],
|
||||
]);
|
||||
});
|
||||
|
||||
xit("when any of the reading fails, throws", () => {});
|
||||
|
||||
xit("given some of the package is scoped, when all contents resolve", () => {});
|
||||
|
||||
describe("when all contents resolve", () => {
|
||||
beforeEach(async () => {
|
||||
await existsMock.mockClear();
|
||||
|
||||
await readJsonFileMock.resolveSpecific(([path]) => path === "/some-directory/some-module/package.json", {
|
||||
name: "some-module",
|
||||
files: ["some-build-directory"],
|
||||
main: "some-build-directory/index.js",
|
||||
});
|
||||
|
||||
await readJsonFileMock.resolveSpecific(
|
||||
([path]) => path === "/some-other-directory/some-other-module/package.json",
|
||||
{
|
||||
name: "some-other-module",
|
||||
files: ["some-other-build-directory"],
|
||||
main: "some-other-build-directory/index.js",
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("checks for existing Lens link directories", () => {
|
||||
expect(existsMock.mock.calls).toEqual([
|
||||
["/some-directory/some-project/node_modules/some-module"],
|
||||
["/some-directory/some-project/node_modules/some-other-module"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not create the Lens link directories yet", () => {
|
||||
expect(ensureDirectoryMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("given some Lens link directories exist", () => {
|
||||
beforeEach(async () => {
|
||||
await existsMock.resolveSpecific(
|
||||
([path]) => path === "/some-directory/some-project/node_modules/some-module",
|
||||
false,
|
||||
);
|
||||
|
||||
await existsMock.resolveSpecific(
|
||||
([path]) => path === "/some-directory/some-project/node_modules/some-other-module",
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("does not create the Lens link directories yet", () => {
|
||||
expect(ensureDirectoryMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("removes the existing Lens link directories", () => {
|
||||
expect(removeDirectoryMock.mock.calls).toEqual([
|
||||
["/some-directory/some-project/node_modules/some-other-module"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("when removing resolves, creates the Lens link directories", async () => {
|
||||
await removeDirectoryMock.resolve();
|
||||
|
||||
expect(ensureDirectoryMock.mock.calls).toEqual([
|
||||
["/some-directory/some-project/node_modules/some-module"],
|
||||
["/some-directory/some-project/node_modules/some-other-module"],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given Lens link directories does not exist", () => {
|
||||
beforeEach(async () => {
|
||||
await existsMock.resolve(false);
|
||||
await existsMock.resolve(false);
|
||||
});
|
||||
|
||||
it("creates the Lens link directories", () => {
|
||||
expect(ensureDirectoryMock.mock.calls).toEqual([
|
||||
["/some-directory/some-project/node_modules/some-module"],
|
||||
["/some-directory/some-project/node_modules/some-other-module"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not create symlinks yet", () => {
|
||||
expect(createSymlinkMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("when creation of Lens link directories resolve", () => {
|
||||
beforeEach(async () => {
|
||||
await ensureDirectoryMock.resolve();
|
||||
await ensureDirectoryMock.resolve();
|
||||
});
|
||||
|
||||
it("creates the symlinks", () => {
|
||||
expect(createSymlinkMock.mock.calls).toEqual([
|
||||
[
|
||||
"/some-directory/some-module/package.json",
|
||||
"/some-directory/some-project/node_modules/some-module/package.json",
|
||||
"file",
|
||||
],
|
||||
[
|
||||
"/some-directory/some-module/some-build-directory",
|
||||
"/some-directory/some-project/node_modules/some-module/some-build-directory",
|
||||
"dir",
|
||||
],
|
||||
[
|
||||
"/some-other-directory/some-other-module/package.json",
|
||||
"/some-directory/some-project/node_modules/some-other-module/package.json",
|
||||
"file",
|
||||
],
|
||||
[
|
||||
"/some-other-directory/some-other-module/some-other-build-directory",
|
||||
"/some-directory/some-project/node_modules/some-other-module/some-other-build-directory",
|
||||
"dir",
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
133
packages/infrastructure/lens-link/src/lens-link.ts
Normal file
133
packages/infrastructure/lens-link/src/lens-link.ts
Normal file
@ -0,0 +1,133 @@
|
||||
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<void>;
|
||||
|
||||
export type CreateSymlink = (target: string, path: string, type: "dir" | "file") => Promise<void>;
|
||||
export type EnsureDirectory = (path: string) => Promise<void>;
|
||||
export type RemoveDirectory = (path: string) => Promise<void>;
|
||||
export type WriteJsonFile = (path: string, value: JsonValue) => Promise<void>;
|
||||
export type ReadJsonFile = (path: string) => Promise<JsonValue>;
|
||||
export type Exists = (path: string) => Promise<boolean>;
|
||||
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)),
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,10 @@
|
||||
export interface PackageJson {
|
||||
name: string;
|
||||
main: string;
|
||||
files: string[];
|
||||
}
|
||||
|
||||
export interface PackageJsonAndPath {
|
||||
packageJsonPath: string;
|
||||
content: PackageJson;
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
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<void>;
|
||||
|
||||
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,
|
||||
);
|
||||
};
|
||||
4
packages/infrastructure/lens-link/tsconfig.json
Normal file
4
packages/infrastructure/lens-link/tsconfig.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "@k8slens/typescript/config/base.json",
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
1
packages/infrastructure/lens-link/webpack.config.js
Normal file
1
packages/infrastructure/lens-link/webpack.config.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require("@k8slens/webpack").configForNode;
|
||||
Loading…
Reference in New Issue
Block a user