feat: backup all files

1. remove window.api.compress window.api.decompress
This commit is contained in:
kangfenmao 2024-09-27 22:35:22 +08:00
parent ca897db0d2
commit b9250df347
13 changed files with 477 additions and 108 deletions

View File

@ -32,11 +32,14 @@
"dependencies": {
"@electron-toolkit/preload": "^3.0.0",
"@electron-toolkit/utils": "^3.0.0",
"archiver": "^7.0.1",
"electron-log": "^5.1.5",
"electron-store": "^8.2.0",
"electron-updater": "^6.1.7",
"electron-window-state": "^5.0.3",
"html2canvas": "^1.4.1"
"fs-extra": "^11.2.0",
"html2canvas": "^1.4.1",
"unzipper": "^0.12.3"
},
"devDependencies": {
"@anthropic-ai/sdk": "^0.24.3",
@ -47,11 +50,13 @@
"@hello-pangea/dnd": "^16.6.0",
"@kangfenmao/keyv-storage": "^0.1.0",
"@reduxjs/toolkit": "^2.2.5",
"@types/fs-extra": "^11",
"@types/lodash": "^4.17.5",
"@types/node": "^18.19.9",
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
"@types/tinycolor2": "^1",
"@types/unzipper": "^0",
"@vitejs/plugin-react": "^4.2.1",
"antd": "^5.18.3",
"axios": "^1.7.3",

View File

@ -1,13 +1,13 @@
import { FileType } from '@types'
import { BrowserWindow, ipcMain, OpenDialogOptions, session, shell } from 'electron'
import { BrowserWindow, ipcMain, session, shell } from 'electron'
import { appConfig, titleBarOverlayDark, titleBarOverlayLight } from './config'
import AppUpdater from './services/AppUpdater'
import BackupManager from './services/BackupManager'
import FileManager from './services/FileManager'
import { compress, decompress } from './utils/zip'
import { createMinappWindow } from './window'
const fileManager = new FileManager()
const backupManager = new BackupManager()
export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
const { autoUpdater } = new AppUpdater(mainWindow)
@ -29,24 +29,22 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
ipcMain.handle('reload', () => mainWindow.reload())
ipcMain.handle('zip:compress', (_, text: string) => compress(text))
ipcMain.handle('zip:decompress', (_, text: Buffer) => decompress(text))
ipcMain.handle('backup:save', backupManager.backup)
ipcMain.handle('backup:restore', backupManager.restore)
ipcMain.handle('file:open', fileManager.open)
ipcMain.handle('file:save', fileManager.save)
ipcMain.handle('file:select', fileManager.selectFile)
ipcMain.handle('file:upload', fileManager.uploadFile)
ipcMain.handle('file:clear', fileManager.clear)
ipcMain.handle('file:read', fileManager.readFile)
ipcMain.handle('file:delete', fileManager.deleteFile)
ipcMain.handle('file:get', fileManager.getFile)
ipcMain.handle('file:selectFolder', fileManager.selectFolder)
ipcMain.handle('file:create', fileManager.createTempFile)
ipcMain.handle('file:write', fileManager.writeFile)
ipcMain.handle('file:saveImage', fileManager.saveImage)
ipcMain.handle('file:base64Image', async (_, id) => await fileManager.base64Image(id))
ipcMain.handle('file:select', async (_, options?: OpenDialogOptions) => await fileManager.selectFile(options))
ipcMain.handle('file:upload', async (_, file: FileType) => await fileManager.uploadFile(file))
ipcMain.handle('file:clear', async () => await fileManager.clear())
ipcMain.handle('file:read', async (_, id: string) => await fileManager.readFile(id))
ipcMain.handle('file:delete', async (_, id: string) => await fileManager.deleteFile(id))
ipcMain.handle('file:get', async (_, filePath: string) => await fileManager.getFile(filePath))
ipcMain.handle('file:create', async (_, fileName: string) => await fileManager.createTempFile(fileName))
ipcMain.handle(
'file:write',
async (_, filePath: string, data: Uint8Array | string) => await fileManager.writeFile(filePath, data)
)
ipcMain.handle('file:base64Image', fileManager.base64Image)
ipcMain.handle('minapp', (_, args) => {
createMinappWindow({

View File

@ -0,0 +1,82 @@
import archiver from 'archiver'
import { app } from 'electron'
import Logger from 'electron-log'
import * as fs from 'fs-extra'
import * as path from 'path'
import * as unzipper from 'unzipper'
class BackupManager {
private tempDir: string
constructor() {
this.tempDir = path.join(app.getPath('temp'), 'CherryStudio', 'backup')
this.backup = this.backup.bind(this)
this.restore = this.restore.bind(this)
}
async backup(_: Electron.IpcMainInvokeEvent, data: string, fileName: string, destinationPath: string): Promise<void> {
try {
// 创建临时目录
await fs.ensureDir(this.tempDir)
// 将 data 写入临时文件
const tempDataPath = path.join(this.tempDir, 'data.json')
await fs.writeFile(tempDataPath, data)
// 复制 Data 目录到临时目录
const sourcePath = path.join(app.getPath('userData'), 'Data')
const tempDataDir = path.join(this.tempDir, 'Data')
await fs.copy(sourcePath, tempDataDir)
// 创建 zip 文件
const output = fs.createWriteStream(path.join(destinationPath, `${fileName}.zip`))
const archive = archiver('zip', { zlib: { level: 9 } })
archive.pipe(output)
archive.directory(this.tempDir, false)
await archive.finalize()
// 清理临时目录
await fs.remove(this.tempDir)
Logger.log('Backup completed successfully')
} catch (error) {
Logger.error('Backup failed:', error)
throw error
}
}
async restore(_: Electron.IpcMainInvokeEvent, backupPath: string): Promise<{ data: string; success: boolean }> {
try {
// 创建临时目录
await fs.ensureDir(this.tempDir)
// 解压备份文件到临时目录
await fs
.createReadStream(backupPath)
.pipe(unzipper.Extract({ path: this.tempDir }))
.promise()
// 读取 data.json
const dataPath = path.join(this.tempDir, 'data.json')
const data = await fs.readFile(dataPath, 'utf-8')
// 恢复 Data 目录
const sourcePath = path.join(this.tempDir, 'Data')
const destPath = path.join(app.getPath('userData'), 'Data')
await fs.remove(destPath)
await fs.copy(sourcePath, destPath)
// 清理临时目录
await fs.remove(this.tempDir)
Logger.log('Restore completed successfully')
return { data, success: true }
} catch (error) {
Logger.error('Restore failed:', error)
return { data: '', success: false }
}
}
}
export default BackupManager

View File

@ -17,20 +17,19 @@ import * as path from 'path'
import { v4 as uuidv4 } from 'uuid'
class FileManager {
private storageDir: string
private storageDir = path.join(app.getPath('userData'), 'Data', 'Files')
constructor() {
this.storageDir = path.join(app.getPath('userData'), 'Data', 'Files')
this.initStorageDir()
}
private initStorageDir(): void {
private initStorageDir = (): void => {
if (!fs.existsSync(this.storageDir)) {
fs.mkdirSync(this.storageDir, { recursive: true })
}
}
private async getFileHash(filePath: string): Promise<string> {
private getFileHash = async (filePath: string): Promise<string> => {
return new Promise((resolve, reject) => {
const hash = crypto.createHash('md5')
const stream = fs.createReadStream(filePath)
@ -40,7 +39,7 @@ class FileManager {
})
}
async findDuplicateFile(filePath: string): Promise<FileType | null> {
findDuplicateFile = async (filePath: string): Promise<FileType | null> => {
const stats = fs.statSync(filePath)
const fileSize = stats.size
@ -76,7 +75,10 @@ class FileManager {
return null
}
async selectFile(options?: OpenDialogOptions): Promise<FileType[] | null> {
public selectFile = async (
_: Electron.IpcMainInvokeEvent,
options?: OpenDialogOptions
): Promise<FileType[] | null> => {
const defaultOptions: OpenDialogOptions = {
properties: ['openFile']
}
@ -110,7 +112,7 @@ class FileManager {
return Promise.all(fileMetadataPromises)
}
async uploadFile(file: FileType): Promise<FileType> {
public uploadFile = async (_: Electron.IpcMainInvokeEvent, file: FileType): Promise<FileType> => {
const duplicateFile = await this.findDuplicateFile(file.path)
if (duplicateFile) {
@ -141,7 +143,7 @@ class FileManager {
return fileMetadata
}
async getFile(filePath: string): Promise<FileType | null> {
public getFile = async (_: Electron.IpcMainInvokeEvent, filePath: string): Promise<FileType | null> => {
if (!fs.existsSync(filePath)) {
return null
}
@ -165,16 +167,16 @@ class FileManager {
return fileInfo
}
async deleteFile(id: string): Promise<void> {
public deleteFile = async (_: Electron.IpcMainInvokeEvent, id: string): Promise<void> => {
await fs.promises.unlink(path.join(this.storageDir, id))
}
async readFile(id: string): Promise<string> {
public readFile = async (_: Electron.IpcMainInvokeEvent, id: string): Promise<string> => {
const filePath = path.join(this.storageDir, id)
return fs.readFileSync(filePath, 'utf8')
}
async createTempFile(fileName: string): Promise<string> {
public createTempFile = async (_: Electron.IpcMainInvokeEvent, fileName: string): Promise<string> => {
const tempDir = path.join(app.getPath('temp'), 'CherryStudio')
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true })
@ -183,11 +185,18 @@ class FileManager {
return tempFilePath
}
async writeFile(filePath: string, data: Uint8Array | string): Promise<void> {
public writeFile = async (
_: Electron.IpcMainInvokeEvent,
filePath: string,
data: Uint8Array | string
): Promise<void> => {
await fs.promises.writeFile(filePath, data)
}
async base64Image(id: string): Promise<{ mime: string; base64: string; data: string }> {
public base64Image = async (
_: Electron.IpcMainInvokeEvent,
id: string
): Promise<{ mime: string; base64: string; data: string }> => {
const filePath = path.join(this.storageDir, id)
const data = await fs.promises.readFile(filePath)
const base64 = data.toString('base64')
@ -199,15 +208,15 @@ class FileManager {
}
}
async clear(): Promise<void> {
public clear = async (): Promise<void> => {
await fs.promises.rmdir(this.storageDir, { recursive: true })
await this.initStorageDir()
}
async open(
public open = async (
_: Electron.IpcMainInvokeEvent,
options: OpenDialogOptions
): Promise<{ fileName: string; content: Buffer } | null> {
): Promise<{ fileName: string; filePath: string; content: Buffer } | null> => {
try {
const result: OpenDialogReturnValue = await dialog.showOpenDialog({
title: '打开文件',
@ -220,7 +229,7 @@ class FileManager {
const filePath = result.filePaths[0]
const fileName = filePath.split('/').pop() || ''
const content = await readFile(filePath)
return { fileName, content }
return { fileName, filePath, content }
}
return null
@ -230,12 +239,12 @@ class FileManager {
}
}
async save(
public save = async (
_: Electron.IpcMainInvokeEvent,
fileName: string,
content: string,
options?: SaveDialogOptions
): Promise<void> {
): Promise<void> => {
try {
const result: SaveDialogReturnValue = await dialog.showSaveDialog({
title: '保存文件',
@ -251,7 +260,7 @@ class FileManager {
}
}
async saveImage(_: Electron.IpcMainInvokeEvent, name: string, data: string): Promise<void> {
public saveImage = async (_: Electron.IpcMainInvokeEvent, name: string, data: string): Promise<void> => {
try {
const filePath = dialog.showSaveDialogSync({
defaultPath: `${name}.png`,
@ -266,6 +275,25 @@ class FileManager {
logger.error('[IPC - Error]', 'An error occurred saving the image:', error)
}
}
public selectFolder = async (_: Electron.IpcMainInvokeEvent, options: OpenDialogOptions): Promise<string | null> => {
try {
const result: OpenDialogReturnValue = await dialog.showOpenDialog({
title: '选择文件夹',
properties: ['openDirectory'],
...options
})
if (!result.canceled && result.filePaths.length > 0) {
return result.filePaths[0]
}
return null
} catch (err) {
logger.error('[IPC - Error]', 'An error occurred selecting the folder:', err)
return null
}
}
}
export default FileManager

View File

@ -1,39 +0,0 @@
import util from 'node:util'
import zlib from 'node:zlib'
import logger from 'electron-log'
// 将 zlib 的 gzip 和 gunzip 方法转换为 Promise 版本
const gzipPromise = util.promisify(zlib.gzip)
const gunzipPromise = util.promisify(zlib.gunzip)
/**
*
* @param {string} string - JSON
* @returns {Promise<Buffer>} Buffer
*/
export async function compress(str) {
try {
const buffer = Buffer.from(str, 'utf-8')
const compressedBuffer = await gzipPromise(buffer)
return compressedBuffer
} catch (error) {
logger.error('Compression failed:', error)
throw error
}
}
/**
* Buffer JSON
* @param {Buffer} compressedBuffer - Buffer
* @returns {Promise<string>} JSON
*/
export async function decompress(compressedBuffer) {
try {
const buffer = await gunzipPromise(compressedBuffer)
return buffer.toString('utf-8')
} catch (error) {
logger.error('Decompression failed:', error)
throw error
}
}

View File

@ -19,19 +19,24 @@ declare global {
reload: () => void
compress: (text: string) => Promise<Buffer>
decompress: (text: Buffer) => Promise<string>
backup: {
save: (data: string, fileName: string, destinationPath: string) => Promise<void>
restore: (backupPath: string) => Promise<{ data: string; success: boolean }>
}
file: {
select: (options?: OpenDialogOptions) => Promise<FileType[] | null>
upload: (file: FileType) => Promise<FileType>
delete: (fileId: string) => Promise<void>
read: (fileId: string) => Promise<string>
base64Image: (fileId: string) => Promise<{ mime: string; base64: string; data: string }>
clear: () => Promise<void>
get: (filePath: string) => Promise<FileType | null>
selectFolder: () => Promise<string | null>
create: (fileName: string) => Promise<string>
write: (filePath: string, data: Uint8Array | string) => Promise<void>
open: (options?: OpenDialogOptions) => Promise<{ fileName: string; content: Buffer } | null>
open: (options?: OpenDialogOptions) => Promise<{ fileName: string; filePath: string; content: Buffer } | null>
save: (path: string, content: string | NodeJS.ArrayBufferView, options?: SaveDialogOptions) => void
saveImage: (name: string, data: string) => void
base64Image: (fileId: string) => Promise<{ mime: string; base64: string; data: string }>
}
}
}

View File

@ -10,23 +10,27 @@ const api = {
setTheme: (theme: 'light' | 'dark') => ipcRenderer.invoke('set-theme', theme),
minApp: (url: string) => ipcRenderer.invoke('minapp', url),
reload: () => ipcRenderer.invoke('reload'),
compress: (text: string) => ipcRenderer.invoke('zip:compress', text),
decompress: (text: Buffer) => ipcRenderer.invoke('zip:decompress', text),
backup: {
save: (data: string, fileName: string, destinationPath: string) => {
ipcRenderer.invoke('backup:save', data, fileName, destinationPath)
},
restore: (backupPath: string) => ipcRenderer.invoke('backup:restore', backupPath)
},
file: {
select: (options?: OpenDialogOptions) => ipcRenderer.invoke('file:select', options),
upload: (filePath: string) => ipcRenderer.invoke('file:upload', filePath),
delete: (fileId: string) => ipcRenderer.invoke('file:delete', fileId),
read: (fileId: string) => ipcRenderer.invoke('file:read', fileId),
base64Image: (fileId: string) => ipcRenderer.invoke('file:base64Image', fileId),
clear: () => ipcRenderer.invoke('file:clear'),
get: (filePath: string) => ipcRenderer.invoke('file:get', filePath),
create: (fileName: string) => ipcRenderer.invoke('file:create', fileName),
write: (filePath: string, data: Uint8Array | string) => ipcRenderer.invoke('file:write', filePath, data),
open: (options?: { decompress: boolean }) => ipcRenderer.invoke('file:open', options),
save: (path: string, content: string, options?: { compress: boolean }) => {
return ipcRenderer.invoke('file:save', path, content, options)
},
saveImage: (name: string, data: string) => ipcRenderer.invoke('file:saveImage', name, data)
save: (path: string, content: string, options?: { compress: boolean }) =>
ipcRenderer.invoke('file:save', path, content, options),
selectFolder: () => ipcRenderer.invoke('file:selectFolder'),
saveImage: (name: string, data: string) => ipcRenderer.invoke('file:saveImage', name, data),
base64Image: (fileId: string) => ipcRenderer.invoke('file:base64Image', fileId)
}
}

View File

@ -12,7 +12,7 @@ import styled from 'styled-components'
const FilesPage: FC = () => {
const { t } = useTranslation()
const files = useLiveQuery<FileType[]>(() => db.files.orderBy('created_at').reverse().toArray())
const files = useLiveQuery<FileType[]>(() => db.files.orderBy('ext').reverse().toArray())
const dataSource = files?.map((file) => {
const isImage = file.type === FileTypes.IMAGE
@ -65,8 +65,14 @@ const FilesPage: FC = () => {
<NavbarCenter style={{ borderRight: 'none' }}>{t('files.title')}</NavbarCenter>
</Navbar>
<ContentContainer id="content-container">
<VStack style={{ flex: 1 }}>
<Table dataSource={dataSource} columns={columns} style={{ width: '100%', height: '100%' }} size="small" />
<VStack style={{ width: '100%' }}>
<Table
dataSource={dataSource}
columns={columns}
style={{ width: '100%', marginBottom: 20 }}
size="small"
pagination={{ pageSize: 15 }}
/>
</VStack>
</ContentContainer>
</Container>

View File

@ -58,7 +58,7 @@ export default class AnthropicProvider extends BaseProvider {
const { contextCount, maxTokens, streamOutput } = getAssistantSettings(assistant)
const userMessagesParams: MessageParam[] = []
const _messages = filterContextMessages(takeRight(messages, contextCount + 2))
const _messages = filterContextMessages(takeRight(messages, contextCount + 1))
onFilterMessages(_messages)

View File

@ -59,7 +59,7 @@ export default class GeminiProvider extends BaseProvider {
const model = assistant.model || defaultModel
const { contextCount, maxTokens, streamOutput } = getAssistantSettings(assistant)
const userMessages = filterContextMessages(takeRight(messages, contextCount + 2))
const userMessages = filterContextMessages(takeRight(messages, contextCount + 1))
onFilterMessages(userMessages)
if (first(userMessages)?.role === 'assistant') {

View File

@ -117,7 +117,7 @@ export default class OpenAIProvider extends BaseProvider {
const systemMessage = assistant.prompt ? { role: 'system', content: assistant.prompt } : undefined
const userMessages: ChatCompletionMessageParam[] = []
const _messages = filterContextMessages(takeRight(messages, contextCount + 1))
const _messages = filterContextMessages(takeRight(messages, contextCount))
onFilterMessages(_messages)
for (const message of _messages) {

View File

@ -4,7 +4,7 @@ import dayjs from 'dayjs'
import localforage from 'localforage'
export async function backup() {
const version = 2
const version = 3
const time = new Date().getTime()
const data = {
@ -14,22 +14,31 @@ export async function backup() {
indexedDB: await backupDatabase()
}
const filename = `cherry-studio.${dayjs().format('YYYYMMDD')}.bak`
const filename = `cherry-studio.${dayjs().format('YYYYMMDDHHmm')}`
const fileContnet = JSON.stringify(data)
const file = await window.api.compress(fileContnet)
await window.api.file.save(filename, file)
const selectFolder = await window.api.file.selectFolder()
if (selectFolder) {
await window.api.backup.save(fileContnet, filename, selectFolder)
window.message.success({ content: i18n.t('message.backup.success'), key: 'backup' })
}
}
export async function restore() {
const file = await window.api.file.open()
const file = await window.api.file.open({ filters: [{ name: '备份文件', extensions: ['bak', 'zip'] }] })
if (file) {
try {
const content = await window.api.decompress(file.content)
const data = JSON.parse(content)
let data: Record<string, any> = {}
// zip backup file
if (file?.fileName.endsWith('.zip')) {
const restoreData = await window.api.backup.restore(file.filePath)
data = JSON.parse(restoreData.data)
} else {
data = JSON.parse(await window.api.decompress(file.content))
}
if (data.version === 1) {
await clearDatabase()
@ -49,7 +58,7 @@ export async function restore() {
return
}
if (data.version === 2) {
if (data.version >= 2) {
localStorage.setItem('persist:cherry-studio', data.localStorage['persist:cherry-studio'])
await restoreDatabase(data.indexedDB)
window.message.success({ content: i18n.t('message.restore.success'), key: 'restore' })

285
yarn.lock
View File

@ -1793,6 +1793,16 @@ __metadata:
languageName: node
linkType: hard
"@types/fs-extra@npm:^11":
version: 11.0.4
resolution: "@types/fs-extra@npm:11.0.4"
dependencies:
"@types/jsonfile": "npm:*"
"@types/node": "npm:*"
checksum: 10c0/9e34f9b24ea464f3c0b18c3f8a82aefc36dc524cc720fc2b886e5465abc66486ff4e439ea3fb2c0acebf91f6d3f74e514f9983b1f02d4243706bdbb7511796ad
languageName: node
linkType: hard
"@types/glob@npm:^7.1.0":
version: 7.2.0
resolution: "@types/glob@npm:7.2.0"
@ -1845,6 +1855,15 @@ __metadata:
languageName: node
linkType: hard
"@types/jsonfile@npm:*":
version: 6.1.4
resolution: "@types/jsonfile@npm:6.1.4"
dependencies:
"@types/node": "npm:*"
checksum: 10c0/b12d068b021e4078f6ac4441353965769be87acf15326173e2aea9f3bf8ead41bd0ad29421df5bbeb0123ec3fc02eb0a734481d52903704a1454a1845896b9eb
languageName: node
linkType: hard
"@types/katex@npm:^0.16.0":
version: 0.16.7
resolution: "@types/katex@npm:0.16.7"
@ -2013,6 +2032,15 @@ __metadata:
languageName: node
linkType: hard
"@types/unzipper@npm:^0":
version: 0.10.10
resolution: "@types/unzipper@npm:0.10.10"
dependencies:
"@types/node": "npm:*"
checksum: 10c0/10e9da33791be1087adb25adc2fe4d5ab267dae51fbcf7b1f10d0aca3130a13ef5fed994d7be45af8c465ff3946bc360a53eff6e5aab4eb9ac9489477535342f
languageName: node
linkType: hard
"@types/use-sync-external-store@npm:^0.0.3":
version: 0.0.3
resolution: "@types/use-sync-external-store@npm:0.0.3"
@ -2202,13 +2230,16 @@ __metadata:
"@hello-pangea/dnd": "npm:^16.6.0"
"@kangfenmao/keyv-storage": "npm:^0.1.0"
"@reduxjs/toolkit": "npm:^2.2.5"
"@types/fs-extra": "npm:^11"
"@types/lodash": "npm:^4.17.5"
"@types/node": "npm:^18.19.9"
"@types/react": "npm:^18.2.48"
"@types/react-dom": "npm:^18.2.18"
"@types/tinycolor2": "npm:^1"
"@types/unzipper": "npm:^0"
"@vitejs/plugin-react": "npm:^4.2.1"
antd: "npm:^5.18.3"
archiver: "npm:^7.0.1"
axios: "npm:^1.7.3"
browser-image-compression: "npm:^2.0.2"
dayjs: "npm:^1.11.11"
@ -2231,6 +2262,7 @@ __metadata:
eslint-plugin-react-hooks: "npm:^4.6.2"
eslint-plugin-simple-import-sort: "npm:^12.1.1"
eslint-plugin-unused-imports: "npm:^4.0.0"
fs-extra: "npm:^11.2.0"
gpt-tokens: "npm:^1.3.10"
html2canvas: "npm:^1.4.1"
i18next: "npm:^23.11.5"
@ -2257,6 +2289,7 @@ __metadata:
styled-components: "npm:^6.1.11"
tinycolor2: "npm:^1.6.0"
typescript: "npm:^5.6.2"
unzipper: "npm:^0.12.3"
uuid: "npm:^10.0.0"
vite: "npm:^5.0.12"
peerDependencies:
@ -2558,6 +2591,36 @@ __metadata:
languageName: node
linkType: hard
"archiver-utils@npm:^5.0.0, archiver-utils@npm:^5.0.2":
version: 5.0.2
resolution: "archiver-utils@npm:5.0.2"
dependencies:
glob: "npm:^10.0.0"
graceful-fs: "npm:^4.2.0"
is-stream: "npm:^2.0.1"
lazystream: "npm:^1.0.0"
lodash: "npm:^4.17.15"
normalize-path: "npm:^3.0.0"
readable-stream: "npm:^4.0.0"
checksum: 10c0/3782c5fa9922186aa1a8e41ed0c2867569faa5f15c8e5e6418ea4c1b730b476e21bd68270b3ea457daf459ae23aaea070b2b9f90cf90a59def8dc79b9e4ef538
languageName: node
linkType: hard
"archiver@npm:^7.0.1":
version: 7.0.1
resolution: "archiver@npm:7.0.1"
dependencies:
archiver-utils: "npm:^5.0.2"
async: "npm:^3.2.4"
buffer-crc32: "npm:^1.0.0"
readable-stream: "npm:^4.0.0"
readdir-glob: "npm:^1.1.2"
tar-stream: "npm:^3.0.0"
zip-stream: "npm:^6.0.1"
checksum: 10c0/02afd87ca16f6184f752db8e26884e6eff911c476812a0e7f7b26c4beb09f06119807f388a8e26ed2558aa8ba9db28646ebd147a4f99e46813b8b43158e1438e
languageName: node
linkType: hard
"argparse@npm:^2.0.1":
version: 2.0.1
resolution: "argparse@npm:2.0.1"
@ -2712,7 +2775,7 @@ __metadata:
languageName: node
linkType: hard
"async@npm:^3.2.3":
"async@npm:^3.2.3, async@npm:^3.2.4":
version: 3.2.6
resolution: "async@npm:3.2.6"
checksum: 10c0/36484bb15ceddf07078688d95e27076379cc2f87b10c03b6dd8a83e89475a3c8df5848859dd06a4c95af1e4c16fc973de0171a77f18ea00be899aca2a4f85e70
@ -2774,6 +2837,13 @@ __metadata:
languageName: node
linkType: hard
"b4a@npm:^1.6.4":
version: 1.6.7
resolution: "b4a@npm:1.6.7"
checksum: 10c0/ec2f004d1daae04be8c5a1f8aeb7fea213c34025e279db4958eb0b82c1729ee25f7c6e89f92a5f65c8a9cf2d017ce27e3dda912403341d1781bd74528a4849d4
languageName: node
linkType: hard
"bail@npm:^2.0.0":
version: 2.0.2
resolution: "bail@npm:2.0.2"
@ -2788,6 +2858,13 @@ __metadata:
languageName: node
linkType: hard
"bare-events@npm:^2.2.0":
version: 2.5.0
resolution: "bare-events@npm:2.5.0"
checksum: 10c0/afbeec4e8be4d93fb4a3be65c3b4a891a2205aae30b5a38fafd42976cc76cf30dad348963fe330a0d70186e15dc507c11af42c89af5dddab2a54e5aff02e2896
languageName: node
linkType: hard
"base64-arraybuffer@npm:^1.0.2":
version: 1.0.2
resolution: "base64-arraybuffer@npm:1.0.2"
@ -2827,7 +2904,7 @@ __metadata:
languageName: node
linkType: hard
"bluebird@npm:^3.5.5":
"bluebird@npm:^3.5.5, bluebird@npm:~3.7.2":
version: 3.7.2
resolution: "bluebird@npm:3.7.2"
checksum: 10c0/680de03adc54ff925eaa6c7bb9a47a0690e8b5de60f4792604aae8ed618c65e6b63a7893b57ca924beaf53eee69c5af4f8314148c08124c550fe1df1add897d2
@ -2899,6 +2976,13 @@ __metadata:
languageName: node
linkType: hard
"buffer-crc32@npm:^1.0.0":
version: 1.0.0
resolution: "buffer-crc32@npm:1.0.0"
checksum: 10c0/8b86e161cee4bb48d5fa622cbae4c18f25e4857e5203b89e23de59e627ab26beb82d9d7999f2b8de02580165f61f83f997beaf02980cdf06affd175b651921ab
languageName: node
linkType: hard
"buffer-crc32@npm:~0.2.3":
version: 0.2.13
resolution: "buffer-crc32@npm:0.2.13"
@ -2937,6 +3021,16 @@ __metadata:
languageName: node
linkType: hard
"buffer@npm:^6.0.3":
version: 6.0.3
resolution: "buffer@npm:6.0.3"
dependencies:
base64-js: "npm:^1.3.1"
ieee754: "npm:^1.2.1"
checksum: 10c0/2a905fbbcde73cc5d8bd18d1caa23715d5f83a5935867c2329f0ac06104204ba7947be098fe1317fbd8830e26090ff8e764f08cd14fefc977bb248c3487bcbd0
languageName: node
linkType: hard
"builder-util-runtime@npm:9.2.4":
version: 9.2.4
resolution: "builder-util-runtime@npm:9.2.4"
@ -3356,6 +3450,19 @@ __metadata:
languageName: node
linkType: hard
"compress-commons@npm:^6.0.2":
version: 6.0.2
resolution: "compress-commons@npm:6.0.2"
dependencies:
crc-32: "npm:^1.2.0"
crc32-stream: "npm:^6.0.0"
is-stream: "npm:^2.0.1"
normalize-path: "npm:^3.0.0"
readable-stream: "npm:^4.0.0"
checksum: 10c0/2347031b7c92c8ed5011b07b93ec53b298fa2cd1800897532ac4d4d1aeae06567883f481b6e35f13b65fc31b190c751df6635434d525562f0203fde76f1f0814
languageName: node
linkType: hard
"compute-scroll-into-view@npm:^3.0.2":
version: 3.1.0
resolution: "compute-scroll-into-view@npm:3.1.0"
@ -3440,6 +3547,25 @@ __metadata:
languageName: node
linkType: hard
"crc-32@npm:^1.2.0":
version: 1.2.2
resolution: "crc-32@npm:1.2.2"
bin:
crc32: bin/crc32.njs
checksum: 10c0/11dcf4a2e77ee793835d49f2c028838eae58b44f50d1ff08394a610bfd817523f105d6ae4d9b5bef0aad45510f633eb23c903e9902e4409bed1ce70cb82b9bf0
languageName: node
linkType: hard
"crc32-stream@npm:^6.0.0":
version: 6.0.0
resolution: "crc32-stream@npm:6.0.0"
dependencies:
crc-32: "npm:^1.2.0"
readable-stream: "npm:^4.0.0"
checksum: 10c0/bf9c84571ede2d119c2b4f3a9ef5eeb9ff94b588493c0d3862259af86d3679dcce1c8569dd2b0a6eff2f35f5e2081cc1263b846d2538d4054da78cf34f262a3d
languageName: node
linkType: hard
"crc@npm:^3.8.0":
version: 3.8.0
resolution: "crc@npm:3.8.0"
@ -3845,6 +3971,15 @@ __metadata:
languageName: node
linkType: hard
"duplexer2@npm:~0.1.4":
version: 0.1.4
resolution: "duplexer2@npm:0.1.4"
dependencies:
readable-stream: "npm:^2.0.2"
checksum: 10c0/0765a4cc6fe6d9615d43cc6dbccff6f8412811d89a6f6aa44828ca9422a0a469625ce023bf81cee68f52930dbedf9c5716056ff264ac886612702d134b5e39b4
languageName: node
linkType: hard
"eastasianwidth@npm:^0.2.0":
version: 0.2.0
resolution: "eastasianwidth@npm:0.2.0"
@ -4562,6 +4697,13 @@ __metadata:
languageName: node
linkType: hard
"events@npm:^3.3.0":
version: 3.3.0
resolution: "events@npm:3.3.0"
checksum: 10c0/d6b6f2adbccbcda74ddbab52ed07db727ef52e31a61ed26db9feb7dc62af7fc8e060defa65e5f8af9449b86b52cc1a1f6a79f2eafcf4e62add2b7a1fa4a432f6
languageName: node
linkType: hard
"exif-parser@npm:^0.1.12":
version: 0.1.12
resolution: "exif-parser@npm:0.1.12"
@ -4642,6 +4784,13 @@ __metadata:
languageName: node
linkType: hard
"fast-fifo@npm:^1.2.0, fast-fifo@npm:^1.3.2":
version: 1.3.2
resolution: "fast-fifo@npm:1.3.2"
checksum: 10c0/d53f6f786875e8b0529f784b59b4b05d4b5c31c651710496440006a398389a579c8dbcd2081311478b5bf77f4b0b21de69109c5a4eabea9d8e8783d1eb864e4c
languageName: node
linkType: hard
"fast-glob@npm:^3.2.9":
version: 3.3.2
resolution: "fast-glob@npm:3.3.2"
@ -4899,6 +5048,17 @@ __metadata:
languageName: node
linkType: hard
"fs-extra@npm:^11.2.0":
version: 11.2.0
resolution: "fs-extra@npm:11.2.0"
dependencies:
graceful-fs: "npm:^4.2.0"
jsonfile: "npm:^6.0.1"
universalify: "npm:^2.0.0"
checksum: 10c0/d77a9a9efe60532d2e790e938c81a02c1b24904ef7a3efb3990b835514465ba720e99a6ea56fd5e2db53b4695319b644d76d5a0e9988a2beef80aa7b1da63398
languageName: node
linkType: hard
"fs-extra@npm:^8.1.0":
version: 8.1.0
resolution: "fs-extra@npm:8.1.0"
@ -5083,7 +5243,7 @@ __metadata:
languageName: node
linkType: hard
"glob@npm:^10.2.2, glob@npm:^10.3.10":
"glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.3.10":
version: 10.4.5
resolution: "glob@npm:10.4.5"
dependencies:
@ -5216,7 +5376,7 @@ __metadata:
languageName: node
linkType: hard
"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6":
"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.2, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6":
version: 4.2.11
resolution: "graceful-fs@npm:4.2.11"
checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2
@ -6083,6 +6243,13 @@ __metadata:
languageName: node
linkType: hard
"is-stream@npm:^2.0.1":
version: 2.0.1
resolution: "is-stream@npm:2.0.1"
checksum: 10c0/7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5
languageName: node
linkType: hard
"is-string@npm:^1.0.5, is-string@npm:^1.0.7":
version: 1.0.7
resolution: "is-string@npm:1.0.7"
@ -6495,6 +6662,15 @@ __metadata:
languageName: node
linkType: hard
"lazystream@npm:^1.0.0":
version: 1.0.1
resolution: "lazystream@npm:1.0.1"
dependencies:
readable-stream: "npm:^2.0.5"
checksum: 10c0/ea4e509a5226ecfcc303ba6782cc269be8867d372b9bcbd625c88955df1987ea1a20da4643bf9270336415a398d33531ebf0d5f0d393b9283dc7c98bfcbd7b69
languageName: node
linkType: hard
"lcid@npm:^1.0.0":
version: 1.0.0
resolution: "lcid@npm:1.0.0"
@ -7419,7 +7595,7 @@ __metadata:
languageName: node
linkType: hard
"minimatch@npm:^5.0.1, minimatch@npm:^5.1.1":
"minimatch@npm:^5.0.1, minimatch@npm:^5.1.0, minimatch@npm:^5.1.1":
version: 5.1.6
resolution: "minimatch@npm:5.1.6"
dependencies:
@ -7642,6 +7818,13 @@ __metadata:
languageName: node
linkType: hard
"node-int64@npm:^0.4.0":
version: 0.4.0
resolution: "node-int64@npm:0.4.0"
checksum: 10c0/a6a4d8369e2f2720e9c645255ffde909c0fbd41c92ea92a5607fc17055955daac99c1ff589d421eee12a0d24e99f7bfc2aabfeb1a4c14742f6c099a51863f31a
languageName: node
linkType: hard
"node-releases@npm:^2.0.18":
version: 2.0.18
resolution: "node-releases@npm:2.0.18"
@ -8427,6 +8610,13 @@ __metadata:
languageName: node
linkType: hard
"queue-tick@npm:^1.0.1":
version: 1.0.1
resolution: "queue-tick@npm:1.0.1"
checksum: 10c0/0db998e2c9b15215317dbcf801e9b23e6bcde4044e115155dae34f8e7454b9a783f737c9a725528d677b7a66c775eb7a955cf144fe0b87f62b575ce5bfd515a9
languageName: node
linkType: hard
"quick-lru@npm:^5.1.1":
version: 5.1.1
resolution: "quick-lru@npm:5.1.1"
@ -9191,7 +9381,7 @@ __metadata:
languageName: node
linkType: hard
"readable-stream@npm:^2.2.2, readable-stream@npm:~2.3.6":
"readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:^2.2.2, readable-stream@npm:~2.3.6":
version: 2.3.8
resolution: "readable-stream@npm:2.3.8"
dependencies:
@ -9217,6 +9407,19 @@ __metadata:
languageName: node
linkType: hard
"readable-stream@npm:^4.0.0":
version: 4.5.2
resolution: "readable-stream@npm:4.5.2"
dependencies:
abort-controller: "npm:^3.0.0"
buffer: "npm:^6.0.3"
events: "npm:^3.3.0"
process: "npm:^0.11.10"
string_decoder: "npm:^1.3.0"
checksum: 10c0/a2c80e0e53aabd91d7df0330929e32d0a73219f9477dbbb18472f6fdd6a11a699fc5d172a1beff98d50eae4f1496c950ffa85b7cc2c4c196963f289a5f39275d
languageName: node
linkType: hard
"readable-web-to-node-stream@npm:^3.0.0":
version: 3.0.2
resolution: "readable-web-to-node-stream@npm:3.0.2"
@ -9226,6 +9429,15 @@ __metadata:
languageName: node
linkType: hard
"readdir-glob@npm:^1.1.2":
version: 1.1.3
resolution: "readdir-glob@npm:1.1.3"
dependencies:
minimatch: "npm:^5.1.0"
checksum: 10c0/a37e0716726650845d761f1041387acd93aa91b28dd5381950733f994b6c349ddc1e21e266ec7cc1f9b92e205a7a972232f9b89d5424d07361c2c3753d5dbace
languageName: node
linkType: hard
"readdirp@npm:~3.6.0":
version: 3.6.0
resolution: "readdirp@npm:3.6.0"
@ -10043,6 +10255,21 @@ __metadata:
languageName: node
linkType: hard
"streamx@npm:^2.15.0":
version: 2.20.1
resolution: "streamx@npm:2.20.1"
dependencies:
bare-events: "npm:^2.2.0"
fast-fifo: "npm:^1.3.2"
queue-tick: "npm:^1.0.1"
text-decoder: "npm:^1.1.0"
dependenciesMeta:
bare-events:
optional: true
checksum: 10c0/34ffa2ee9465d70e18c7e2ba70189720c166d150ab83eb7700304620fa23ff42a69cb37d712ea4b5fc6234d8e74346a88bb4baceb873c6b05e52ac420f8abb4d
languageName: node
linkType: hard
"string-convert@npm:^0.2.0":
version: 0.2.1
resolution: "string-convert@npm:0.2.1"
@ -10147,7 +10374,7 @@ __metadata:
languageName: node
linkType: hard
"string_decoder@npm:^1.1.1":
"string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0":
version: 1.3.0
resolution: "string_decoder@npm:1.3.0"
dependencies:
@ -10329,6 +10556,17 @@ __metadata:
languageName: node
linkType: hard
"tar-stream@npm:^3.0.0":
version: 3.1.7
resolution: "tar-stream@npm:3.1.7"
dependencies:
b4a: "npm:^1.6.4"
fast-fifo: "npm:^1.2.0"
streamx: "npm:^2.15.0"
checksum: 10c0/a09199d21f8714bd729993ac49b6c8efcb808b544b89f23378ad6ffff6d1cb540878614ba9d4cfec11a64ef39e1a6f009a5398371491eb1fda606ffc7f70f718
languageName: node
linkType: hard
"tar@npm:^6.1.11, tar@npm:^6.1.12, tar@npm:^6.2.1":
version: 6.2.1
resolution: "tar@npm:6.2.1"
@ -10353,6 +10591,15 @@ __metadata:
languageName: node
linkType: hard
"text-decoder@npm:^1.1.0":
version: 1.2.0
resolution: "text-decoder@npm:1.2.0"
dependencies:
b4a: "npm:^1.6.4"
checksum: 10c0/398171bef376e06864cd6ba24e0787cc626bebc84a1bbda758d06a6e9b729cc8613f7923dd0d294abd88e8bb5cd7261aad5fda7911fb87253fe71b2b5ac6e507
languageName: node
linkType: hard
"text-segmentation@npm:^1.0.3":
version: 1.0.3
resolution: "text-segmentation@npm:1.0.3"
@ -10800,6 +11047,19 @@ __metadata:
languageName: node
linkType: hard
"unzipper@npm:^0.12.3":
version: 0.12.3
resolution: "unzipper@npm:0.12.3"
dependencies:
bluebird: "npm:~3.7.2"
duplexer2: "npm:~0.1.4"
fs-extra: "npm:^11.2.0"
graceful-fs: "npm:^4.2.2"
node-int64: "npm:^0.4.0"
checksum: 10c0/4cae2ad23bfd47011d5f8a6d61fb1dc0e4b5008bc3896e6f3d5ab946a64e9482714992a988128bce541440aa646e16e5e5c9bf35e49097edbaf833e7f814d36d
languageName: node
linkType: hard
"update-browserslist-db@npm:^1.1.0":
version: 1.1.0
resolution: "update-browserslist-db@npm:1.1.0"
@ -11355,6 +11615,17 @@ __metadata:
languageName: node
linkType: hard
"zip-stream@npm:^6.0.1":
version: 6.0.1
resolution: "zip-stream@npm:6.0.1"
dependencies:
archiver-utils: "npm:^5.0.0"
compress-commons: "npm:^6.0.2"
readable-stream: "npm:^4.0.0"
checksum: 10c0/50f2fb30327fb9d09879abf7ae2493705313adf403e794b030151aaae00009162419d60d0519e807673ec04d442e140c8879ca14314df0a0192de3b233e8f28b
languageName: node
linkType: hard
"zwitch@npm:^2.0.0":
version: 2.0.4
resolution: "zwitch@npm:2.0.4"