Feature(custom): add new script system

ISSUES CLOSED: #462
This commit is contained in:
Kuingsmile
2026-01-26 17:57:44 +08:00
parent a10e701cc9
commit 9c8698907e
34 changed files with 1359 additions and 143 deletions

View File

@@ -26,6 +26,7 @@ import { configPaths } from '~/utils/configPaths'
import { IPasteStyle, IWindowList } from '~/utils/enum'
import { isMacOSVersionGreaterThanOrEqualTo } from '~/utils/getMacOSVersion'
import pasteTemplate from '~/utils/pasteTemplate'
import { runScriptInStage } from '~/utils/runScript'
import { hideMiniWindow, openMainWindow, openMiniWindow } from '~/utils/windowHelper'
import menubarPng from '../../../../../resources/menubar.png?asset&asarUnpack'
@@ -308,8 +309,8 @@ export function createTray(tooltip: string) {
const rawInput = cloneDeep(files)
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
const res = await uploader.setWebContents(trayWindow?.webContents).uploadReturnCtx(files)
const imgs = res[0] ? res[0] : false
const backImgs = res[1] ? res[1] : false
const imgs = res.ctx?.output ? res.ctx.output : false
const backImgs = res.backupCtx?.output ? res.backupCtx.output : false
const deleteLocalFile = allConfig.settings?.deleteLocalFile || false
if (imgs !== false) {
const pasteText: string[] = []
@@ -334,7 +335,8 @@ export function createTray(tooltip: string) {
notification.show()
}, i * 100)
}
await GalleryDB.getInstance().insert(imgs[i])
const inserted = await GalleryDB.getInstance().insert(imgs[i])
runScriptInStage('onUploadSuccess', res.ctx || picgo, { galleryItem: inserted })
}
handleCopyUrl(pasteText.join('\n'))
trayWindow?.webContents.send('dragFiles', imgs)

View File

@@ -1,46 +1,56 @@
import path from 'node:path'
import { themesDir } from '@core/datastore/dirs'
import * as fsWalk from '@nodelib/fs.walk'
import AdmZip from 'adm-zip'
import axios from 'axios'
import fs from 'fs-extra'
import { randomStringGenerator } from '@/manage/utils/common'
import logger from '~/apis/core/picgo/logger'
import { IWindowList } from '~/utils/enum'
import windowManager from '../window/windowManager'
export async function resolveThemes(): Promise<{ key: string; label: string }[]> {
const files = fsWalk.walkSync(themesDir(), {
followSymbolicLinks: true,
fs,
stats: true,
throwErrorOnBrokenSymbolicLink: false,
})
const result: string[] = []
files.forEach(item => {
if (item.stats?.isFile()) {
result.push(path.basename(item.path))
}
})
const themes = await Promise.all(
result
.filter(file => file.endsWith('.css'))
.map(async file => {
const css = (await fs.readFile(path.join(themesDir(), file), 'utf-8')) || ''
let name = file
if (css.startsWith('/*')) {
name = css.split('\n')[0].replace('/*', '').replace('*/', '').trim() || file
const dir = themesDir()
const entries = await fs.readdir(dir, { withFileTypes: true })
const themePromises = entries
.filter(entry => entry.isFile() && entry.name.endsWith('.css'))
.map(async entry => {
const filePath = path.join(dir, entry.name)
let label = entry.name
let fd
try {
fd = await fs.open(filePath, 'r')
const buffer = Buffer.alloc(256)
const { bytesRead } = await fs.read(fd, buffer, 0, 256, 0)
const content = buffer.toString('utf8', 0, bytesRead)
const match = content.match(/^\/\*\s*(.*?)\s*\*\//)
if (match && match[1]) {
label = match[1].trim()
}
return { key: file, label: name }
}),
)
if (themes.find(theme => theme.key === 'default.css')) {
return themes
} catch (e: any) {
logger.error(e)
} finally {
if (fd !== undefined) await fs.close(fd)
}
return { key: entry.name, label }
})
const themes = await Promise.all(themePromises)
const hasDefault = themes.some(t => t.key === 'default.css')
if (!hasDefault) {
themes.unshift({ key: 'default.css', label: '默认' })
} else {
return [{ key: 'default.css', label: '默认' }, ...themes]
const idx = themes.findIndex(t => t.key === 'default.css')
const [defaultTheme] = themes.splice(idx, 1)
themes.unshift(defaultTheme)
}
return themes
}
export async function fetchThemes(): Promise<boolean> {

View File

@@ -11,8 +11,9 @@ import { handleCopyUrl, handleUrlEncodeWithSetting } from '~/utils/common'
import { configPaths } from '~/utils/configPaths'
import { IPasteStyle, IWindowList } from '~/utils/enum'
import pasteTemplate from '~/utils/pasteTemplate'
import { runScriptInStage } from '~/utils/runScript'
const handleClipboardUploadingReturnCtx = async (img?: IUploadOption): Promise<(ImgInfo[] | false)[]> => {
const handleClipboardUploadingReturnCtx = async (img?: IUploadOption): Promise<IuploadReturnCtxResult> => {
const useBuiltinClipboardConfig = picgo.getConfig<boolean | undefined>(configPaths.settings.useBuiltinClipboard)
const useBuiltinClipboard = useBuiltinClipboardConfig === undefined ? true : !!useBuiltinClipboardConfig
const win = windowManager.getAvailableWindow()
@@ -26,8 +27,8 @@ export const uploadClipboardFiles = async (): Promise<IStringKeyMap> => {
let img: ImgInfo[] | false = false
let backImg: ImgInfo[] | false = false
const res = await handleClipboardUploadingReturnCtx()
img = res[0] ? res[0] : false
backImg = res[1] ? res[1] : false
img = res.ctx?.output ? res.ctx.output : false
backImg = res.backupCtx?.output ? res.backupCtx.output : false
const allConfig = picgo.getConfig<any>() || {}
if (img !== false) {
if (img.length > 0) {
@@ -50,6 +51,7 @@ export const uploadClipboardFiles = async (): Promise<IStringKeyMap> => {
}, 100)
}
const inserted = await GalleryDB.getInstance().insert(img[0])
runScriptInStage('onUploadSuccess', res.ctx || picgo, { galleryItem: inserted })
// trayWindow just be created in mac/windows, not in linux
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
trayWindow?.webContents?.send('clipboardFiles', [])
@@ -93,8 +95,8 @@ export const uploadChoosedFiles = async (
let imgs: ImgInfo[] | false = false
let backImgs: ImgInfo[] | false = false
const res = await uploader.setWebContents(webContents).uploadReturnCtx(input)
imgs = res[0] ? res[0] : false
backImgs = res[1] ? res[1] : false
imgs = res.ctx?.output ? res.ctx.output : false
backImgs = res.backupCtx?.output ? res.backupCtx.output : false
const result = []
const allConfig = picgo.getConfig<any>() || {}
if (imgs !== false) {
@@ -140,6 +142,7 @@ export const uploadChoosedFiles = async (
}
}
const inserted = await GalleryDB.getInstance().insert(imgs[i])
runScriptInStage('onUploadSuccess', res.ctx || picgo, { galleryItem: inserted })
result.push({
url: handleUrlEncodeWithSetting(inserted.imgUrl!),
fullResult: inserted,

View File

@@ -118,15 +118,15 @@ class Uploader {
return filePath
}
async uploadWithBuildInClipboardReturnCtx(img?: IUploadOption): Promise<(ImgInfo[] | false)[]> {
async uploadWithBuildInClipboardReturnCtx(img?: IUploadOption): Promise<IuploadReturnCtxResult> {
let imgPath: string | false = false
try {
imgPath = await this.getClipboardImagePath()
if (!imgPath) return [false, false]
if (!imgPath) return { ctx: undefined, backupCtx: undefined }
return await this.uploadReturnCtx(img ?? [imgPath])
} catch (e: any) {
logger.error(e)
return [false, false]
return { ctx: undefined, backupCtx: undefined }
} finally {
if (imgPath && imgPath.startsWith(path.join(picgo.baseDir, CLIPBOARD_IMAGE_FOLDER))) {
fs.remove(imgPath)
@@ -134,24 +134,24 @@ class Uploader {
}
}
async uploadReturnCtx(img?: IUploadOption): Promise<(ImgInfo[] | false)[]> {
async uploadReturnCtx(img?: IUploadOption): Promise<IuploadReturnCtxResult> {
try {
const result = [false, false] as (ImgInfo[] | false)[]
const result = { ctx: undefined, backupCtx: undefined } as IuploadReturnCtxResult
const res = await picgo.uploadReturnCtx(img)
const allConfig = picgo.getConfig<any>() || {}
if (Array.isArray(res.output) && res.output.some((item: ImgInfo) => item.imgUrl)) {
res.output.forEach((item: ImgInfo) => {
if (Array.isArray(res.ctx?.output) && res.ctx?.output.some((item: ImgInfo) => item.imgUrl)) {
res.ctx.output.forEach((item: ImgInfo) => {
item.config = JSON.parse(JSON.stringify(allConfig.picBed?.[item.type!]))
})
result[0] = res.output
result.ctx = res.ctx
}
if (Array.isArray(res.backupOutput) && res.backupOutput.some((item: ImgInfo) => item.imgUrl)) {
res.backupOutput.forEach((item: ImgInfo) => {
if (Array.isArray(res.backupCtx?.output) && res.backupCtx?.output.some((item: ImgInfo) => item.imgUrl)) {
res.backupCtx.output.forEach((item: ImgInfo) => {
item.config = JSON.parse(JSON.stringify(allConfig.picBed?.[item.type!]))
})
result[1] = res.backupOutput
result.backupCtx = res.backupCtx
}
return result
} catch (e: any) {
@@ -163,7 +163,7 @@ class Uploader {
clickToCopy: true,
})
}, 500)
return [false, false]
return { ctx: undefined, backupCtx: undefined } as IuploadReturnCtxResult
} finally {
ipcMain.removeAllListeners(GET_RENAME_FILE_NAME)
}

View File

@@ -43,6 +43,10 @@ export function defaultDir() {
return userDataDir()
}
export function scriptsDir() {
return path.join(dataDir(), 'scripts')
}
export function defaultConfigPath() {
if (isPortable()) {
return path.join(exeDir(), 'data', 'data.json')

View File

@@ -13,6 +13,7 @@ import { T as $t } from '~/i18n'
import { handleCopyUrl } from '~/utils/common'
import { IPasteStyle } from '~/utils/enum'
import pasteTemplate from '~/utils/pasteTemplate'
import { runScriptInStage } from '~/utils/runScript'
// Cross-process support may be required in the future
class GuiApi implements IGuiApi {
@@ -74,8 +75,8 @@ class GuiApi implements IGuiApi {
const webContents = this.getWebcontentsByWindowId(this.windowId)
const rawInput = cloneDeep(input)
const res = await uploader.setWebContents(webContents!).uploadReturnCtx(input)
const imgs = res[0] ? res[0] : false
const backImgs = res[1] ? res[1] : false
const imgs = res.ctx?.output ? res.ctx.output : false
const backImgs = res.backupCtx?.output ? res.backupCtx.output : false
let result: ImgInfo[] = []
const allConfig = picgo.getConfig<any>() || {}
if (imgs !== false) {
@@ -103,7 +104,8 @@ class GuiApi implements IGuiApi {
notification.show()
}, i * 100)
}
await GalleryDB.getInstance().insert(imgs[i])
const inserted = await GalleryDB.getInstance().insert(imgs[i])
runScriptInStage('onUploadSuccess', res.ctx || picgo, { galleryItem: inserted })
}
handleCopyUrl(pasteText.join('\n'))
webContents?.send('uploadFiles')

View File

@@ -6,6 +6,7 @@ import { clipboard } from 'electron'
import { RPCRouter } from '~/events/rpc/router'
import { ICOREBuildInEvent, IPasteStyle, IRPCActionType, IRPCType } from '~/utils/enum'
import pasteTemplate from '~/utils/pasteTemplate'
import { runScriptInStage } from '~/utils/runScript'
interface IFilter {
orderBy?: 'asc' | 'desc'
limit?: number
@@ -38,6 +39,7 @@ const galleryRoutes = [
action: IRPCActionType.GALLERY_REMOVE_FILES,
handler: async (_: IIPCEvent, args: [files: ImgInfo[]]) => {
setTimeout(() => {
runScriptInStage('onGalleryRemove', picgo, { galleryItem: args[0] })
picgo.emit(ICOREBuildInEvent.REMOVE, args[0], GuiApi.getInstance())
}, 500)
},

View File

@@ -1,12 +1,15 @@
import path from 'node:path'
import { dataDir } from '@core/datastore/dirs'
import { dataDir, scriptsDir } from '@core/datastore/dirs'
import picgo from '@core/picgo'
import { IpcMainEvent, shell } from 'electron'
import fs from 'fs-extra'
import logger from '~/apis/core/picgo/logger'
import { isAutoStartEnabled, setAutoStart } from '~/utils/autoStart'
import { getDirectoryTree } from '~/utils/common'
import { IRPCActionType, IRPCType } from '~/utils/enum'
import { runScript } from '~/utils/runScript'
const STORE_PATH = dataDir()
@@ -57,9 +60,110 @@ export default [
action: IRPCActionType.WRITE_FILE_CONTENT,
handler: async (_: IIPCEvent, args: [fileName: string, content: string]) => {
const abFilePath = path.join(STORE_PATH, args[0])
fs.ensureDirSync(path.dirname(abFilePath))
fs.writeFileSync(abFilePath, args[1], 'utf-8')
},
},
{
action: IRPCActionType.RUN_SCRIPT_FILE,
handler: async (_: IIPCEvent, args: [fileName: string[]]) => {
const abFilePath = path.join(scriptsDir(), ...args[0])
if (!fs.existsSync(abFilePath)) {
throw new Error('Script file does not exist')
}
const scriptContent = fs.readFileSync(abFilePath, 'utf-8')
try {
await runScript(picgo, scriptContent, {})
logger.info(`Script ${args[0].join('/')} executed successfully`)
return 'Script executed successfully'
} catch (e) {
return Error(`Script execution failed: ${e}`)
}
},
type: IRPCType.INVOKE,
},
{
action: IRPCActionType.CREATE_SCRIPTS_FILE,
handler: async (_: IIPCEvent, args: [fileName: string[], content: string]) => {
const abFilePath = path.join(scriptsDir(), ...args[0])
fs.ensureDirSync(path.dirname(abFilePath))
fs.writeFileSync(abFilePath, args[1], 'utf-8')
},
},
{
action: IRPCActionType.READ_SCRIPTS_FILE,
handler: async (_: IIPCEvent, args: [fileName: string[]]) => {
const abFilePath = path.join(scriptsDir(), ...args[0])
if (!fs.existsSync(abFilePath)) {
return null
}
return fs.readFileSync(abFilePath, 'utf-8')
},
type: IRPCType.INVOKE,
},
{
action: IRPCActionType.LIST_SCRIPTS_FILES,
handler: async (_: IIPCEvent, args: [dirPath: string[]]) => {
const dir = scriptsDir()
const targetDir = path.join(dir, ...(args[0] || []))
if (!(await fs.pathExists(targetDir))) {
return {}
}
try {
return await getDirectoryTree(targetDir)
} catch (error) {
console.error('Failed to list scripts:', error)
return {}
}
},
type: IRPCType.INVOKE,
},
{
action: IRPCActionType.WRITE_SCRIPT_FILE,
handler: async (_: IIPCEvent, args: [fileName: string[], content: string]) => {
const abFilePath = path.join(scriptsDir(), ...args[0])
fs.ensureDirSync(path.dirname(abFilePath))
fs.writeFileSync(abFilePath, args[1], 'utf-8')
},
},
{
action: IRPCActionType.DELETE_SCRIPTS_FILE,
handler: async (_: IIPCEvent, args: [fileName: string[]]) => {
const abFilePath = path.join(scriptsDir(), ...args[0])
if (fs.existsSync(abFilePath)) {
fs.unlinkSync(abFilePath)
}
},
},
{
action: IRPCActionType.GET_FILES_STAT,
handler: async (_: IIPCEvent, [filePaths, type]: [string[][], 'scripts' | 'config']) => {
const basePath = type === 'scripts' ? scriptsDir() : STORE_PATH
const result: IObj[] = []
const statPromises = filePaths.map(async filePath => {
const absolutePath = path.join(basePath, ...filePath)
try {
return await fs.promises.stat(absolutePath)
} catch {
return undefined
}
})
const statsResults = await Promise.all(statPromises)
for (const [index, item] of filePaths.entries()) {
result.push({
fileName: item[item.length - 1],
stats: statsResults[index],
category: item.length > 1 ? item.slice(0, -1).join('.') : '',
filePath: item,
})
}
return result
},
type: IRPCType.INVOKE,
},
{
action: IRPCActionType.PICLIST_OPEN_DIRECTORY,
handler: async (_: IIPCEvent, args: [dirPath?: string, inStorePath?: boolean]) => {

View File

@@ -32,8 +32,8 @@ const trayRoutes = [
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
// macOS use builtin clipboard is OK
const res = await uploader.setWebContents(trayWindow?.webContents).uploadWithBuildInClipboardReturnCtx()
const img = res[0] ? res[0] : false
const backupImgs = res[1] ? res[1] : false
const img = res.ctx?.output ? res.ctx.output : false
const backupImgs = res.backupCtx?.output ? res.backupCtx.output : false
const allConfig = picgo.getConfig<any>() || {}
if (img !== false) {
const pasteStyle = allConfig.settings?.pasteStyle || IPasteStyle.MARKDOWN

View File

@@ -34,7 +34,7 @@ import { IRemoteNoticeTriggerHook, ISartMode, IWindowList } from '~/utils/enum'
import { getUploadFiles } from '~/utils/handleArgv'
import { initI18n } from '~/utils/handleI18n'
import { notificationList } from '~/utils/notification'
import { MemoryMonitor } from '~/utils/performanceOptimizer'
import { runScriptInStage } from '~/utils/runScript'
import { CLIPBOARD_IMAGE_FOLDER } from '~/utils/static'
import updateChecker from '~/utils/updateChecker'
const isDevelopment = process.env.NODE_ENV !== 'production'
@@ -146,9 +146,6 @@ class LifeCycle {
notice.show()
}
}
if (isDevelopment) {
MemoryMonitor.start()
}
await remoteNoticeHandler.init()
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.APP_START)
if (startMode === ISartMode.MINI && process.platform !== 'darwin') {
@@ -196,6 +193,7 @@ class LifeCycle {
}
const clipboardDir = path.join(picgo.baseDir, CLIPBOARD_IMAGE_FOLDER)
fs.emptyDir(clipboardDir)
runScriptInStage('onSoftwareOpen', picgo, {})
}
app.whenReady().then(readyFunction)
}
@@ -251,7 +249,7 @@ class LifeCycle {
server.shutdown()
webServer.stop()
stopFileServer()
MemoryMonitor.stop()
runScriptInStage('onSoftwareClose', picgo, {})
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {

View File

@@ -375,3 +375,23 @@ export function getUploaderType(ctx: IPicGo): {
const id = picBedConfig._id || ''
return { picBed, id }
}
export async function getDirectoryTree(currentPath: string): Promise<Record<string, any>> {
const result: Record<string, any> = {}
const entries = await fs.readdir(currentPath, { withFileTypes: true })
await Promise.all(
entries.map(async entry => {
const fullPath = path.join(currentPath, entry.name)
if (entry.isDirectory()) {
result[entry.name] = await getDirectoryTree(fullPath)
} else if (entry.isFile()) {
result[entry.name] = null
}
}),
)
return result
}

View File

@@ -87,6 +87,7 @@ export const IRPCActionType = {
SET_CURRENT_LANGUAGE: 'SET_CURRENT_LANGUAGE',
OPEN_WINDOW: 'OPEN_WINDOW',
OPEN_MINI_WINDOW: 'OPEN_MINI_WINDOW',
RELOAD_WINDOW: 'RELOAD_WINDOW',
CLOSE_WINDOW: 'CLOSE_WINDOW',
MINIMIZE_WINDOW: 'MINIMIZE_WINDOW',
SHOW_MINI_PAGE_MENU: 'SHOW_MINI_PAGE_MENU',
@@ -123,9 +124,17 @@ export const IRPCActionType = {
PICLIST_OPEN_DIRECTORY: 'PICLIST_OPEN_DIRECTORY',
PICLIST_AUTO_START: 'PICLIST_AUTO_START',
PICLIST_AUTO_START_STATUS: 'PICLIST_AUTO_START_STATUS',
// file operation rpc
READ_FILE_CONTENT: 'READ_FILE_CONTENT',
WRITE_FILE_CONTENT: 'WRITE_FILE_CONTENT',
RELOAD_WINDOW: 'RELOAD_WINDOW',
CREATE_SCRIPTS_FILE: 'CREATE_SCRIPTS_FILE',
READ_SCRIPTS_FILE: 'READ_SCRIPTS_FILE',
LIST_SCRIPTS_FILES: 'LIST_SCRIPTS_FILES',
WRITE_SCRIPT_FILE: 'WRITE_SCRIPT_FILE',
GET_FILES_STAT: 'GET_FILES_STAT',
DELETE_SCRIPTS_FILE: 'DELETE_SCRIPTS_FILE',
RUN_SCRIPT_FILE: 'RUN_SCRIPT_FILE',
// shortkey setting rpc
SHORTKEY_UPDATE: 'SHORTKEY_UPDATE',

123
src/main/utils/runScript.ts Normal file
View File

@@ -0,0 +1,123 @@
import crypto from 'node:crypto'
import os from 'node:os'
import path from 'node:path'
import vm from 'node:vm'
import { scriptsDir } from '@core/datastore/dirs'
import picgo from '@core/picgo'
import logger from '@core/picgo/logger'
import axios from 'axios'
import fs from 'fs-extra'
import { IPicGo } from 'piclist'
export const scriptLifecycleStages = [
'onSoftwareOpen',
'onSoftwareClose',
'preProcess',
'beforeTransform',
'transform',
'beforeUpload',
'upload',
'afterUpload',
'onUploadSuccess',
'onGalleryRemove',
'manualTrigger',
'uploader.advancedplist',
] as const
function format(data: unknown): string {
if (data instanceof Error) {
return `${data.name}: ${data.message}\n${data.stack}`
}
try {
return JSON.stringify(data)
} catch {
return String(data)
}
}
export async function runScript(ctx: IPicGo, script: string, extra: Record<string, any>): Promise<any> {
try {
const base64Decode = (str: string): string => Buffer.from(str, 'base64').toString('utf-8')
const base64Encode = (data: Buffer | string): string =>
(Buffer.isBuffer(data) ? data : Buffer.from(String(data))).toString('base64')
const exposedAPI = {
ctx,
extra,
console: Object.freeze({
log: (...args: unknown[]) => ctx.log.info(args.map(format).join(' ')),
info: (...args: unknown[]) => ctx.log.info(args.map(format).join(' ')),
error: (...args: unknown[]) => ctx.log.error(args.map(format).join(' ')),
debug: (...args: unknown[]) => ctx.log.debug(args.map(format).join(' ')),
}),
axios,
crypto,
setTimeout,
setInterval,
clearTimeout,
clearInterval,
fs,
path,
base64Decode,
base64Encode,
os,
Buffer,
}
vm.createContext(exposedAPI)
ctx.log.info('start to run script')
vm.runInContext(script, exposedAPI)
const promise = vm.runInContext(
`(async () => {
const result = main(ctx, extra)
if (result instanceof Promise) return await result
return result
})()`,
exposedAPI,
)
const result = await promise
ctx.log.info('script executed successfully')
return result
} catch (e) {
ctx.log.error(`script execution failed: ${e}`)
throw e
}
}
export async function runScriptInStage(stage: string, ctx: IPicGo, extra: Record<string, any>): Promise<void> {
const baseDir = scriptsDir()
const enabledPaths: string[] = []
let scriptDir: string
const allConfig = picgo.getConfig<any>() || {}
const disabledList: string[] = allConfig.scripts?.disabledList || []
if (stage === 'uploader.advancedplist') {
scriptDir = path.join(baseDir, 'uploader', 'advancedplist')
stage = 'uploader/advancedplist'
} else {
scriptDir = path.join(baseDir, stage)
}
const files = await fs.readdir(scriptDir).catch(() => [])
if (files.length === 0) {
return
}
for (const file of files) {
if (file.endsWith('.js')) {
if (!disabledList.includes(`${stage}/${file}`)) {
enabledPaths.push(path.join(scriptDir, file))
}
}
}
if (enabledPaths.length === 0) {
logger.info(`no enabled scripts found in stage ${stage}`)
return
}
for (const scriptPath of enabledPaths) {
const scriptContent = fs.readFileSync(scriptPath, 'utf-8')
try {
await runScript(ctx, scriptContent, extra)
logger.info(`script ${scriptPath} in stage ${stage} executed successfully`)
} catch (e) {
logger.error(`script ${scriptPath} in stage ${stage} execution failed: ${e}`)
}
}
}

View File

@@ -238,8 +238,8 @@ class UploadTaskQueueManager {
const rawInput = cloneDeep(input)
const res = await uploader.setWebContents(webContents).uploadReturnCtx(input)
const imgs = res[0] ? res[0] : false
const backupImgs = res[1] ? res[1] : false
const imgs = res.ctx?.output ? res.ctx.output : false
const backupImgs = res.backupCtx?.output ? res.backupCtx.output : false
const allConfig = picgo.getConfig<any>() || {}
if (imgs !== false && imgs.length > 0) {