fix(desktop): plugin installation
This commit is contained in:
@@ -10,8 +10,9 @@ import {
|
|||||||
deleteDir,
|
deleteDir,
|
||||||
deleteDirContents,
|
deleteDirContents,
|
||||||
ensureDirectoryExists,
|
ensureDirectoryExists,
|
||||||
extractNestedZip,
|
extractZip,
|
||||||
FileDoesNotExist,
|
FileDoesNotExist,
|
||||||
|
moveDirContents,
|
||||||
readJSONFile,
|
readJSONFile,
|
||||||
} from '../Utils/FileUtils'
|
} from '../Utils/FileUtils'
|
||||||
import { timeout } from '../Utils/Utils'
|
import { timeout } from '../Utils/Utils'
|
||||||
@@ -267,6 +268,30 @@ async function checkForUpdate(webContents: Electron.WebContents, mapping: Mappin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some plugin zips may be served directly from GitHub's Releases feature, which automatically generates a zip file.
|
||||||
|
* However, the actual assets end up being nested inside a root level folder within that zip.
|
||||||
|
* We can detect if we have a legacy nested structure if after unzipping the zip we end up with
|
||||||
|
* only 1 entry and that entry is a folder.
|
||||||
|
*/
|
||||||
|
async function usesLegacyNestedFolderStructure(dir: string) {
|
||||||
|
const fileNames = await fs.promises.readdir(dir)
|
||||||
|
if (fileNames.length > 1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const stat = fs.lstatSync(path.join(dir, fileNames[0]))
|
||||||
|
return stat.isDirectory()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function unnestLegacyStructure(dir: string) {
|
||||||
|
const fileNames = await fs.promises.readdir(dir)
|
||||||
|
const sourceDir = path.join(dir, fileNames[0])
|
||||||
|
const destDir = dir
|
||||||
|
|
||||||
|
await moveDirContents(sourceDir, destDir)
|
||||||
|
}
|
||||||
|
|
||||||
async function installComponent(
|
async function installComponent(
|
||||||
webContents: Electron.WebContents,
|
webContents: Electron.WebContents,
|
||||||
mapping: MappingFileHandler,
|
mapping: MappingFileHandler,
|
||||||
@@ -312,7 +337,12 @@ async function installComponent(
|
|||||||
])
|
])
|
||||||
|
|
||||||
logMessage('Extracting', paths.downloadPath, 'to', paths.absolutePath)
|
logMessage('Extracting', paths.downloadPath, 'to', paths.absolutePath)
|
||||||
await extractNestedZip(paths.downloadPath, paths.absolutePath)
|
await extractZip(paths.downloadPath, paths.absolutePath)
|
||||||
|
|
||||||
|
const legacyStructure = await usesLegacyNestedFolderStructure(paths.absolutePath)
|
||||||
|
if (legacyStructure) {
|
||||||
|
await unnestLegacyStructure(paths.absolutePath)
|
||||||
|
}
|
||||||
|
|
||||||
let main = 'index.html'
|
let main = 'index.html'
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -169,31 +169,37 @@ export async function moveDirContents(srcDir: string, destDir: string): Promise<
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function extractNestedZip(source: string, dest: string): Promise<void> {
|
export async function extractZip(source: string, dest: string): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
yauzl.open(source, { lazyEntries: true, autoClose: true }, (err, zipFile) => {
|
yauzl.open(source, { lazyEntries: true, autoClose: true }, (err, zipFile) => {
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
|
|
||||||
const tryReject = (err: Error) => {
|
const tryReject = (err: Error) => {
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
cancelled = true
|
cancelled = true
|
||||||
reject(err)
|
reject(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return tryReject(err)
|
return tryReject(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!zipFile) {
|
if (!zipFile) {
|
||||||
return tryReject(new Error('zipFile === undefined'))
|
return tryReject(new Error('zipFile === undefined'))
|
||||||
}
|
}
|
||||||
|
|
||||||
zipFile.readEntry()
|
zipFile.readEntry()
|
||||||
|
|
||||||
zipFile.on('close', resolve)
|
zipFile.on('close', resolve)
|
||||||
|
|
||||||
zipFile.on('entry', (entry) => {
|
zipFile.on('entry', (entry) => {
|
||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (entry.fileName.endsWith('/')) {
|
|
||||||
/** entry is a directory, skip and read next entry */
|
const isEntryDirectory = entry.fileName.endsWith('/')
|
||||||
|
if (isEntryDirectory) {
|
||||||
zipFile.readEntry()
|
zipFile.readEntry()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -202,21 +208,19 @@ export async function extractNestedZip(source: string, dest: string): Promise<vo
|
|||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return tryReject(err)
|
return tryReject(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!stream) {
|
if (!stream) {
|
||||||
return tryReject(new Error('stream === undefined'))
|
return tryReject(new Error('stream === undefined'))
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.on('error', tryReject)
|
stream.on('error', tryReject)
|
||||||
const filepath = path.join(
|
|
||||||
dest,
|
const filepath = path.join(dest, entry.fileName)
|
||||||
/**
|
|
||||||
* Remove the first element of the entry's path, which is the base
|
|
||||||
* directory we want to ignore
|
|
||||||
*/
|
|
||||||
entry.fileName.substring(entry.fileName.indexOf('/') + 1),
|
|
||||||
)
|
|
||||||
try {
|
try {
|
||||||
await ensureDirectoryExists(path.dirname(filepath))
|
await ensureDirectoryExists(path.dirname(filepath))
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import path from 'path'
|
|||||||
import {
|
import {
|
||||||
deleteDir,
|
deleteDir,
|
||||||
ensureDirectoryExists,
|
ensureDirectoryExists,
|
||||||
extractNestedZip,
|
extractZip,
|
||||||
FileDoesNotExist,
|
FileDoesNotExist,
|
||||||
moveDirContents,
|
moveDirContents,
|
||||||
readJSONFile,
|
readJSONFile,
|
||||||
@@ -25,7 +25,7 @@ test.afterEach(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('extracts a zip and unnests the folders by one level', async (t) => {
|
test('extracts a zip and unnests the folders by one level', async (t) => {
|
||||||
await extractNestedZip(path.join(dataPath, 'zip-file.zip'), zipFileDestination)
|
await extractZip(path.join(dataPath, 'zip-file.zip'), zipFileDestination)
|
||||||
t.deepEqual(await fs.readdir(zipFileDestination), ['package.json', 'test-file.txt'])
|
t.deepEqual(await fs.readdir(zipFileDestination), ['package.json', 'test-file.txt'])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user