1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/src/renderer/components/+extensions/attempt-install/unpack-extension/unpack-extension.injectable.tsx
Sebastian Malton 156de6138a Fix test flakiness because of path side effects, propagate uses to as many places
Signed-off-by: Sebastian Malton <sebastian@malton.name>
2022-10-04 17:55:54 -04:00

131 lines
5.7 KiB
TypeScript

/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { getInjectable } from "@ogre-tools/injectable";
import extensionLoaderInjectable from "../../../../../extensions/extension-loader/extension-loader.injectable";
import getExtensionDestFolderInjectable from "../get-extension-dest-folder/get-extension-dest-folder.injectable";
import extensionInstallationStateStoreInjectable from "../../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import type { InstallRequestValidated } from "../create-temp-files-and-validate/create-temp-files-and-validate.injectable";
import type { Disposer } from "../../../../utils";
import { noop } from "../../../../utils";
import { extensionDisplayName } from "../../../../../extensions/lens-extension";
import joinPathsInjectable from "../../../../../common/path/join-paths.injectable";
import loggerInjectable from "../../../../../common/logger.injectable";
import { when } from "mobx";
import { getMessageFromError } from "../../get-message-from-error/get-message-from-error";
import showSuccessNotificationInjectable from "../../../notifications/show-success-notification.injectable";
import showErrorNotificationInjectable from "../../../notifications/show-error-notification.injectable";
import getDirnameOfPathInjectable from "../../../../../common/path/get-dirname.injectable";
import getBasenameOfPathInjectable from "../../../../../common/path/get-basename.injectable";
import extractTarInjectable from "../../../../../common/fs/extract-tar.injectable";
import ensureDirInjectable from "../../../../../common/fs/ensure-dir.injectable";
import removePathInjectable from "../../../../../common/fs/remove-path.injectable";
import deleteFileInjectable from "../../../../../common/fs/delete-file.injectable";
import readDirectoryInjectable from "../../../../../common/fs/read-directory.injectable";
import moveInjectable from "../../../../../common/fs/move.injectable";
export type UnpackExtension = (request: InstallRequestValidated, disposeDownloading?: Disposer) => Promise<void>;
const unpackExtensionInjectable = getInjectable({
id: "unpack-extension",
instantiate: (di): UnpackExtension => {
const extensionLoader = di.inject(extensionLoaderInjectable);
const getExtensionDestFolder = di.inject(getExtensionDestFolderInjectable);
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
const joinPaths = di.inject(joinPathsInjectable);
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
const getBasenameOfPath = di.inject(getBasenameOfPathInjectable);
const logger = di.inject(loggerInjectable);
const showOkNotification = di.inject(showSuccessNotificationInjectable);
const showErrorNotification = di.inject(showErrorNotificationInjectable);
const extractTar = di.inject(extractTarInjectable);
const ensureDir = di.inject(ensureDirInjectable);
const removePath = di.inject(removePathInjectable);
const deleteFile = di.inject(deleteFileInjectable);
const readDirectory = di.inject(readDirectoryInjectable);
const move = di.inject(moveInjectable);
return async (request, disposeDownloading) => {
const {
id,
fileName,
tempFile,
manifest: { name, version },
} = request;
extensionInstallationStateStore.setInstalling(id);
disposeDownloading?.();
const displayName = extensionDisplayName(name, version);
const extensionFolder = getExtensionDestFolder(name);
const unpackingTempFolder = joinPaths(
getDirnameOfPath(tempFile),
`${getBasenameOfPath(tempFile)}-unpacked`,
);
logger.info(`Unpacking extension ${displayName}`, { fileName, tempFile });
try {
// extract to temp folder first
await removePath(unpackingTempFolder).catch(noop);
await ensureDir(unpackingTempFolder);
await extractTar(tempFile, { cwd: unpackingTempFolder });
// move contents to extensions folder
const unpackedFiles = await readDirectory(unpackingTempFolder);
let unpackedRootFolder = unpackingTempFolder;
if (unpackedFiles.length === 1) {
// check if %extension.tgz was packed with single top folder,
// e.g. "npm pack %ext_name" downloads file with "package" root folder within tarball
unpackedRootFolder = joinPaths(unpackingTempFolder, unpackedFiles[0]);
}
await ensureDir(extensionFolder);
await move(unpackedRootFolder, extensionFolder, { overwrite: true });
// wait for the loader has actually install it
await when(() => extensionLoader.userExtensions.has(id));
// Enable installed extensions by default.
extensionLoader.setIsEnabled(id, true);
showOkNotification(
<p>
{"Extension "}
<b>{displayName}</b>
{" successfully installed!"}
</p>,
);
} catch (error) {
const message = getMessageFromError(error);
logger.info(
`[EXTENSION-INSTALLATION]: installing ${request.fileName} has failed: ${message}`,
{ error },
);
showErrorNotification(
<p>
{"Installing extension "}
<b>{displayName}</b>
{" has failed: "}
<em>{message}</em>
</p>,
);
} finally {
// Remove install state once finished
extensionInstallationStateStore.clearInstalling(id);
// clean up
removePath(unpackingTempFolder).catch(noop);
deleteFile(tempFile).catch(noop);
}
};
},
});
export default unpackExtensionInjectable;