1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Remove symlink on extension install and manual runtime uninstall (#1718)

* Remove broken symlink on extension install

Signed-off-by: Panu Horsmalahti <phorsmalahti@mirantis.com>

* Remove broken symlink on manual uninstall during runtime

Signed-off-by: Panu Horsmalahti <phorsmalahti@mirantis.com>
This commit is contained in:
Panu Horsmalahti 2020-12-10 12:39:37 +02:00 committed by Jari Kolehmainen
parent c113a6030e
commit d128219328
2 changed files with 41 additions and 22 deletions

View File

@ -18,7 +18,7 @@ const mockedWatch = watch as jest.MockedFunction<typeof watch>;
describe("ExtensionDiscovery", () => { describe("ExtensionDiscovery", () => {
it("emits add for added extension", async done => { it("emits add for added extension", async done => {
globalThis.__non_webpack_require__.mockImplementationOnce(() => ({ globalThis.__non_webpack_require__.mockImplementation(() => ({
name: "my-extension" name: "my-extension"
})); }));
let addHandler: (filePath: string) => void; let addHandler: (filePath: string) => void;
@ -61,9 +61,6 @@ describe("ExtensionDiscovery", () => {
}); });
it("doesn't emit add for added file under extension", async done => { it("doesn't emit add for added file under extension", async done => {
globalThis.__non_webpack_require__.mockImplementationOnce(() => ({
name: "my-extension"
}));
let addHandler: (filePath: string) => void; let addHandler: (filePath: string) => void;
const mockWatchInstance: any = { const mockWatchInstance: any = {

View File

@ -155,23 +155,26 @@ export class ExtensionDiscovery {
.on("unlinkDir", this.handleWatchUnlinkDir); .on("unlinkDir", this.handleWatchUnlinkDir);
} }
handleWatchFileAdd = async (filePath: string) => { handleWatchFileAdd = async (manifestPath: string) => {
// e.g. "foo/package.json" // e.g. "foo/package.json"
const relativePath = path.relative(this.localFolderPath, filePath); const relativePath = path.relative(this.localFolderPath, manifestPath);
// Converts "foo/package.json" to ["foo", "package.json"], where length of 2 implies // Converts "foo/package.json" to ["foo", "package.json"], where length of 2 implies
// that the added file is in a folder under local folder path. // that the added file is in a folder under local folder path.
// This safeguards against a file watch being triggered under a sub-directory which is not an extension. // This safeguards against a file watch being triggered under a sub-directory which is not an extension.
const isUnderLocalFolderPath = relativePath.split(path.sep).length === 2; const isUnderLocalFolderPath = relativePath.split(path.sep).length === 2;
if (path.basename(filePath) === manifestFilename && isUnderLocalFolderPath) { if (path.basename(manifestPath) === manifestFilename && isUnderLocalFolderPath) {
try { try {
const absPath = path.dirname(filePath); const absPath = path.dirname(manifestPath);
// this.loadExtensionFromPath updates this.packagesJson // this.loadExtensionFromPath updates this.packagesJson
const extension = await this.loadExtensionFromPath(absPath); const extension = await this.loadExtensionFromFolder(absPath);
if (extension) { if (extension) {
// Remove a broken symlink left by a previous installation if it exists.
await this.removeSymlinkByManifestPath(manifestPath);
// Install dependencies for the new extension // Install dependencies for the new extension
await this.installPackages(); await this.installPackages();
@ -199,6 +202,9 @@ export class ExtensionDiscovery {
.find(([, extensionFolder]) => filePath === extensionFolder)?.[0]; .find(([, extensionFolder]) => filePath === extensionFolder)?.[0];
if (extensionName !== undefined) { if (extensionName !== undefined) {
// If the extension is deleted manually while the application is running, also remove the symlink
await this.removeSymlinkByPackageName(extensionName);
delete this.packagesJson.dependencies[extensionName]; delete this.packagesJson.dependencies[extensionName];
// Reinstall dependencies to remove the extension from package.json // Reinstall dependencies to remove the extension from package.json
@ -216,6 +222,26 @@ export class ExtensionDiscovery {
} }
}; };
/**
* Remove the symlink under node_modules if exists.
* If we don't remove the symlink, the uninstall would leave a non-working symlink,
* which wouldn't be fixed if the extension was reinstalled, causing the extension not to work.
* @param name e.g. "@mirantis/lens-extension-cc"
*/
removeSymlinkByPackageName(name: string) {
return fs.remove(this.getInstalledPath(name));
}
/**
* Remove the symlink under node_modules if it exists.
* @param manifestPath Path to package.json
*/
removeSymlinkByManifestPath(manifestPath: string) {
const manifestJson = __non_webpack_require__(manifestPath);
return this.removeSymlinkByPackageName(manifestJson.name);
}
/** /**
* Uninstalls extension. * Uninstalls extension.
* The application will detect the folder unlink and remove the extension from the UI automatically. * The application will detect the folder unlink and remove the extension from the UI automatically.
@ -224,16 +250,7 @@ export class ExtensionDiscovery {
async uninstallExtension({ absolutePath, manifest }: InstalledExtension) { async uninstallExtension({ absolutePath, manifest }: InstalledExtension) {
logger.info(`${logModule} Uninstalling ${manifest.name}`); logger.info(`${logModule} Uninstalling ${manifest.name}`);
// remove the symlink under node_modules. await this.removeSymlinkByPackageName(manifest.name);
// If we don't remove the symlink, the uninstall would leave a non-working symlink,
// which wouldn't be fixed if the extension was reinstalled, causing the extension not to work.
await fs.remove(this.getInstalledPath(manifest.name));
const exists = await fs.pathExists(absolutePath);
if (!exists) {
throw new Error(`Extension path ${absolutePath} doesn't exist`);
}
// fs.remove does nothing if the path doesn't exist anymore // fs.remove does nothing if the path doesn't exist anymore
await fs.remove(absolutePath); await fs.remove(absolutePath);
@ -290,6 +307,10 @@ export class ExtensionDiscovery {
return path.join(this.getInstalledPath(name), manifestFilename); return path.join(this.getInstalledPath(name), manifestFilename);
} }
/**
* Returns InstalledExtension from path to package.json file.
* Also updates this.packagesJson.
*/
protected async getByManifest(manifestPath: string, { isBundled = false }: { protected async getByManifest(manifestPath: string, { isBundled = false }: {
isBundled?: boolean; isBundled?: boolean;
} = {}): Promise<InstalledExtension | null> { } = {}): Promise<InstalledExtension | null> {
@ -349,7 +370,7 @@ export class ExtensionDiscovery {
} }
const absPath = path.resolve(folderPath, fileName); const absPath = path.resolve(folderPath, fileName);
const extension = await this.loadExtensionFromPath(absPath, { isBundled: true }); const extension = await this.loadExtensionFromFolder(absPath, { isBundled: true });
if (extension) { if (extension) {
extensions.push(extension); extensions.push(extension);
@ -384,7 +405,7 @@ export class ExtensionDiscovery {
continue; continue;
} }
const extension = await this.loadExtensionFromPath(absPath); const extension = await this.loadExtensionFromFolder(absPath);
if (extension) { if (extension) {
extensions.push(extension); extensions.push(extension);
@ -398,8 +419,9 @@ export class ExtensionDiscovery {
/** /**
* Loads extension from absolute path, updates this.packagesJson to include it and returns the extension. * Loads extension from absolute path, updates this.packagesJson to include it and returns the extension.
* @param absPath Folder path to extension
*/ */
async loadExtensionFromPath(absPath: string, { isBundled = false }: { async loadExtensionFromFolder(absPath: string, { isBundled = false }: {
isBundled?: boolean; isBundled?: boolean;
} = {}): Promise<InstalledExtension | null> { } = {}): Promise<InstalledExtension | null> {
const manifestPath = path.resolve(absPath, manifestFilename); const manifestPath = path.resolve(absPath, manifestFilename);