build(scripts): cleanup

This commit is contained in:
Lukas Obermann
2023-11-06 22:59:42 +01:00
parent 64c0fd10bc
commit d20e60b5a1
17 changed files with 323 additions and 368 deletions
+123
View File
@@ -0,0 +1,123 @@
// @ts-check
import builder from "electron-builder"
import { notarize } from "./notarize.js"
const args = process.argv.slice(2)
// Detect channel
if (!args.includes("--stable") && !args.includes("--prerelease")) {
throw new TypeError(`Missing channel argument (either "--stable" or "--prerelease")`)
}
const isPrerelease = args.includes("--prerelease")
console.log(`Detected channel: ${isPrerelease ? "prerelease" : "stable"}`)
// Detect OS
let osKey
let osName
if (process.argv.includes("--linux") || process.platform === "linux") {
osKey = /** @type {const} */ ("LINUX")
osName = "Linux"
} else if (process.argv.includes("--mac") || process.platform === "darwin") {
osKey = /** @type {const} */ ("MAC")
osName = "macOS"
} else if (process.argv.includes("--win") || process.platform === "win32") {
osKey = /** @type {const} */ ("WINDOWS")
osName = "Windows"
} else {
throw new TypeError(`The target operating system cannot be inferred from the environment.`)
}
console.log(`Detected operating system: ${osName}`)
// Prepare electron-builder configuration
/**
* @type {import("electron-builder").Configuration}
*/
const config = {
appId: isPrerelease ? "com.lukasobermann.optolithinsider" : "com.lukasobermann.optolith",
productName: isPrerelease ? "Optolith Insider" : "Optolith",
copyright:
"© 2017present Lukas Obermann. This product was created under a license. Das Schwarze Auge and its logo as well as Aventuria, Dere, Myranor, Riesland, Tharun and Uthuria and their logos are trademarks of Significant GbR. The title and contents of this book are protected under the copyright laws of the United States of America. No part of this publication may be reproduced, stored in retrieval systems or transmitted, in any form or by any means, whether electronic, mechanical, photocopy, recording, or otherwise, without prior written consent by Ulisses Spiele GmbH, Waldems. This publication includes material that is protected under copyright laws by Ulisses Spiele and/or other authors. Such material is used under the Community Content Agreement for the SCRIPTORIUM AVENTURIS. All other original materials in this work is copyright 2017-present by Lukas Obermann and published under the Community Content Agreement for the SCRIPTORIUM AVENTURIS.",
files: [
".webpack/**",
"LICENSE",
"node_modules/optolith-database-schema/schema/**",
"src/database/contents/cache/**",
"src/database/contents/Compatibility/**",
"src/database/contents/Data/**",
],
asar: false, // otherwise optolith-database-schema package is not properly resolved
asarUnpack: [
"node_modules/optolith-character-schema/**",
"node_modules/optolith-database-schema/**",
"src/database/contents/**",
],
directories: {
output: isPrerelease ? "dist/insider" : "dist",
},
win: {
target: [
{
target: "nsis",
arch: ["x64", "ia32"],
},
],
icon: isPrerelease ? "src/assets/icon/AppList.targetsize-512.pre.png" : "src/assets/icon/icon.ico",
artifactName: isPrerelease ? "OptolithInsiderSetup_${version}.${ext}" : "OptolithSetup_${version}.${ext}",
},
nsis: {
perMachine: true,
differentialPackage: true,
deleteAppDataOnUninstall: false,
},
linux: {
category: "RolePlaying",
target: [
{
target: "AppImage",
arch: ["x64"],
},
{
target: "tar.gz",
arch: ["x64"],
},
],
executableName: isPrerelease ? "OptolithInsider" : "Optolith",
icon: isPrerelease ? "src/assets/icon/icon.pre.png" : "src/assets/icon/AppList.targetsize-512.png",
artifactName: isPrerelease ? "OptolithInsider_${version}.${ext}" : "Optolith_${version}.${ext}",
},
mac: {
category: "public.app-category.role-playing-games",
type: "distribution",
target: [
{
target: "default",
arch: "universal",
},
],
icon: isPrerelease ? "src/assets/icon/AppIcon.pre.icns" : "src/assets/icon/AppIcon.icns",
artifactName: isPrerelease ? "OptolithInsider_${version}.${ext}" : "Optolith_${version}.${ext}",
mergeASARs: false,
darkModeSupport: true,
gatekeeperAssess: true,
},
publish: {
provider: "generic",
url: isPrerelease ? `${process.env.UPDATE_URL}/insider/\${os}` : `${process.env.UPDATE_URL}/\${os}`,
channel: "latest",
},
afterSign: osKey === "MAC" ? notarize : undefined,
}
await builder.build({
config,
targets: builder.Platform[osKey].createTarget(),
})
console.log(`Build finished successfully.`)
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.debugger</key>
<true/>
</dict>
</plist>
+18 -6
View File
@@ -1,16 +1,22 @@
// @ts-check
import { notarize as electronNotarize } from '@electron/notarize'
import { notarize as electronNotarize } from "@electron/notarize"
/**
* Notarizes the app package after it has been built. It detects the
* authentication method from the environment variables.
* @param context {import("electron-builder").AfterPackContext}
*/
export const notarize = async (context) => {
export const notarize = async context => {
const { electronPlatformName, appOutDir } = context
if (electronPlatformName === 'darwin') {
if (electronPlatformName === "darwin") {
const appName = context.packager.appInfo.productFilename
if (typeof process.env.APPLEID === "string" && typeof process.env.APPLEIDPASS === "string" && typeof process.env.TEAMID === "string") {
if (
typeof process.env.APPLEID === "string" &&
typeof process.env.APPLEIDPASS === "string" &&
typeof process.env.TEAMID === "string"
) {
console.log(`Notarizing "${appName}.app" via Apple ID ...`)
await electronNotarize({
tool: "notarytool",
@@ -20,7 +26,11 @@ export const notarize = async (context) => {
teamId: process.env.TEAMID,
})
console.log(`Notarization successful`)
} else if (typeof process.env.APPLEAPIKEY === "string" && typeof process.env.APPLEAPIKEYID === "string" && typeof process.env.APPLEAPIISSUER === "string") {
} else if (
typeof process.env.APPLEAPIKEY === "string" &&
typeof process.env.APPLEAPIKEYID === "string" &&
typeof process.env.APPLEAPIISSUER === "string"
) {
console.log(`Notarizing "${appName}.app" via App Store Connect API ...`)
await electronNotarize({
tool: "notarytool",
@@ -40,7 +50,9 @@ export const notarize = async (context) => {
})
console.log(`Notarization successful`)
} else {
throw new Error(`Notarization failed: No valid authentication method found in environment. Please provide either APPLEID and APPLEIDPASS, or APPLEAPIKEY, APPLEAPIKEYID and APPLEAPIISSUER, or (optional) KEYCHAIN and KEYCHAINPROFILE.`)
throw new Error(
`Notarization failed: No valid authentication method found in environment. Please provide either APPLEID and APPLEIDPASS, or APPLEAPIKEY, APPLEAPIKEYID and APPLEAPIISSUER, or (optional) KEYCHAIN and KEYCHAINPROFILE.`,
)
}
}
}
-52
View File
@@ -1,52 +0,0 @@
// @ts-check
/**
* @param {"prerelease" | "stable"} channel
*/
const channelSuffix = channel => {
switch (channel) {
case "prerelease": return "Insider"
case "stable": return ""
}
}
/**
* @param {import("./platform.js").System} os
* @param {"prerelease" | "stable"} channel
*/
const prefix = (os, channel) => {
switch (os) {
case "win": return `Optolith${channelSuffix(channel)}Setup`
case "linux":
case "mac": return `Optolith${channelSuffix(channel)}`
}
}
/**
* @param {import("./platform.js").System} os
*/
const extensions = os => {
switch (os) {
case "win": return [".exe", ".exe.blockmap"]
case "linux": return [".AppImage", ".tar.gz"]
case "mac": return [".dmg", ".dmg.blockmap", ".zip", ".zip.blockmap"]
}
}
/**
* @param {import("./platform.js").System} os
* @param {"prerelease" | "stable"} channel
* @param {string} version
*/
export const getApplicationFileNames = (os, channel, version) =>
extensions(os).map(ext => `${prefix(os, channel)}_${version}${ext}`)
/**
* @param {"win" | "mac" | "linux"} os
*/
export const getUpdateFileName = os => {
switch (os) {
case "win": return "latest.yml"
case "linux": return "latest-linux.yml"
default: return "latest-mac.yml"
}
}
-70
View File
@@ -1,70 +0,0 @@
// @ts-check
import { notarize } from "./notarize.js"
/**
* @type {import("electron-builder").Configuration}
*/
export const baseConfig = {
copyright:
"© 2017present Lukas Obermann. This product was created under a license. Das Schwarze Auge and its logo as well as Aventuria, Dere, Myranor, Riesland, Tharun and Uthuria and their logos are trademarks of Significant GbR. The title and contents of this book are protected under the copyright laws of the United States of America. No part of this publication may be reproduced, stored in retrieval systems or transmitted, in any form or by any means, whether electronic, mechanical, photocopy, recording, or otherwise, without prior written consent by Ulisses Spiele GmbH, Waldems. This publication includes material that is protected under copyright laws by Ulisses Spiele and/or other authors. Such material is used under the Community Content Agreement for the SCRIPTORIUM AVENTURIS. All other original materials in this work is copyright 2017-present by Lukas Obermann and published under the Community Content Agreement for the SCRIPTORIUM AVENTURIS.",
files: [
".webpack/**",
"LICENSE",
"node_modules/optolith-database-schema/schema/**",
"src/database/contents/cache/**",
"src/database/contents/Compatibility/**",
"src/database/contents/Data/**",
],
asar: false, // otherwise optolith-database-schema package is not properly resolved
asarUnpack: [
"node_modules/optolith-character-schema/**",
"node_modules/optolith-database-schema/**",
"src/database/contents/**",
],
win: {
target: [
{
target: "nsis",
arch: ["x64", "ia32"],
},
],
},
nsis: {
perMachine: true,
differentialPackage: true,
deleteAppDataOnUninstall: false,
},
linux: {
category: "RolePlaying",
target: [
{
target: "AppImage",
arch: ["x64"],
},
{
target: "tar.gz",
arch: ["x64"],
},
],
},
mac: {
category: "public.app-category.role-playing-games",
type: "distribution",
target: [
{
target: "default",
arch: "universal",
},
],
entitlements: "deploy/entitlements.mac.plist",
entitlementsInherit: "deploy/entitlements.mac.plist",
mergeASARs: false,
darkModeSupport: true,
gatekeeperAssess: true,
},
afterSign: async context => {
if (context.electronPlatformName === "darwin") {
await notarize(context)
}
},
}
-26
View File
@@ -1,26 +0,0 @@
// @ts-check
import builder from "electron-builder"
import { getSystem } from "./platform.js"
const args = process.argv.slice(2)
if (!args.includes("--stable") && !args.includes("--prerelease")) {
throw new TypeError(`build requires a specified channel ("--stable" or "--prerelease")`)
}
const config = args.includes("--prerelease")
? (await import("./build.prerelease.config.js")).prereleaseConfig
: (await import("./build.stable.config.js")).stableConfig
process.on("unhandledRejection", error => {
throw new Error(`Unhandled promise rejection: ${/** @type {Error} */ (error).toString()}`)
})
const os = getSystem()
const plaformKey = os === "linux" ? "LINUX" : os === "mac" ? "MAC" : "WINDOWS"
await builder.build({
config,
targets: builder.Platform[plaformKey].createTarget(),
})
-35
View File
@@ -1,35 +0,0 @@
// @ts-check
import { baseConfig } from "./build.base.config.js"
/**
* @type {import("electron-builder").Configuration}
*/
export const prereleaseConfig = {
...baseConfig,
appId: "com.lukasobermann.optolithinsider",
productName: "Optolith Insider",
directories: {
output: "dist/insider",
},
win: {
...baseConfig.win,
icon: "src/assets/icon/AppList.targetsize-512.pre.png",
artifactName: "OptolithInsiderSetup_${version}.${ext}",
},
linux: {
...baseConfig.linux,
executableName: "OptolithInsider",
icon: "src/assets/icon/icon.pre.png",
artifactName: "OptolithInsider_${version}.${ext}",
},
mac: {
...baseConfig.mac,
icon: "src/assets/icon/AppIcon.pre.icns",
artifactName: "OptolithInsider_${version}.${ext}",
},
publish: {
provider: "generic",
url: `${process.env.UPDATE_URL}/insider/\${os}`,
channel: "latest",
},
}
-35
View File
@@ -1,35 +0,0 @@
// @ts-check
import { baseConfig } from "./build.base.config.js"
/**
* @type {import("electron-builder").Configuration}
*/
export const stableConfig = {
...baseConfig,
appId: "com.lukasobermann.optolith",
productName: "Optolith",
directories: {
output: "dist",
},
win: {
...baseConfig.win,
icon: "src/assets/icon/icon.ico",
artifactName: "OptolithSetup_${version}.${ext}",
},
linux: {
...baseConfig.linux,
executableName: "Optolith",
icon: "src/assets/icon/AppList.targetsize-512.png",
artifactName: "Optolith_${version}.${ext}",
},
mac: {
...baseConfig.mac,
icon: "src/assets/icon/AppIcon.icns",
artifactName: "Optolith_${version}.${ext}",
},
publish: {
provider: "generic",
url: `${process.env.UPDATE_URL}/\${os}`,
channel: "latest",
},
}
+63 -25
View File
@@ -1,10 +1,7 @@
// @ts-check
import { join } from "node:path"
import packageJson from "../package.json" assert { type: "json" }
import { getApplicationFileNames, getUpdateFileName } from "./assetNames.js"
import { getLocalPath } from "./localPath.js"
import { getSystem, getSystemName } from "./platform.js"
import { run, upload } from "./remoteConnection.js"
import { getRemotePath } from "./remotePath.js"
import { run, upload } from "./uploader.js"
/**
* Needed env variables:
@@ -16,40 +13,81 @@ import { getRemotePath } from "./remotePath.js"
* Optional env variables:
* - `CI`
*/
const {
HOST,
USERNAME,
PASSWORD,
ROOT = "/",
CI,
} = process.env
const { HOST, USERNAME, PASSWORD, ROOT = "/", CI } = process.env
const args = process.argv.slice(2)
const os = getSystem()
// Detect channel
if (!args.includes("--stable") && !args.includes("--prerelease")) {
throw new TypeError(`publishToServer requires a specified channel ("--stable" or "--prerelease")`)
throw new TypeError(`Missing channel argument (either "--stable" or "--prerelease")`)
}
const channel = args.includes("--prerelease") ? "prerelease" : "stable"
const isPrerelease = args.includes("--prerelease")
console.log(`Preparing to upload update files for "${getSystemName(os)}" on "${channel}" channel...`)
console.log(`Detected channel: ${isPrerelease ? "prerelease" : "stable"}`)
const localDir = getLocalPath(channel)
const remoteDir = getRemotePath(ROOT, channel, os)
// Detect OS
const updateFileName = getUpdateFileName(os)
const applicationFileNames = getApplicationFileNames(os, channel, packageJson.version)
let os
let osName
console.log(`Files to upload: ${[updateFileName, ...applicationFileNames] .join (", ")}.`)
if (process.argv.includes("--linux") || process.platform === "linux") {
os = /** @type {const} */ ("linux")
osName = "Linux"
} else if (process.argv.includes("--mac") || process.platform === "darwin") {
os = /** @type {const} */ ("mac")
osName = "macOS"
} else if (process.argv.includes("--win") || process.platform === "win32") {
os = /** @type {const} */ ("win")
osName = "Windows"
} else {
throw new TypeError(`The target operating system cannot be inferred from the environment.`)
}
console.log(`Detected operating system: ${osName}`)
// Infer files to upload
let fileExtensions
let updateInfoFileName
switch (os) {
case "win":
fileExtensions = [".exe", ".exe.blockmap"]
updateInfoFileName = "latest.yml"
break
case "linux":
fileExtensions = [".AppImage", ".tar.gz"]
updateInfoFileName = "latest-linux.yml"
break
case "mac":
fileExtensions = [".dmg", ".dmg.blockmap", ".zip", ".zip.blockmap"]
updateInfoFileName = "latest-mac.yml"
break
}
const fileNames = [
...fileExtensions.map(ext => {
const chPart = isPrerelease ? "Insider" : ""
const osPart = os === "win" ? "Setup" : ""
return `Optolith${chPart}${osPart}_${packageJson.version}${ext}`
}),
updateInfoFileName,
]
console.log(`Files to upload: ${fileNames.join(", ")}.`)
// Upload files
const localDir = isPrerelease ? join("dist", "insider") : join("dist")
const remoteDir = join(ROOT, isPrerelease ? "insider" : ".", os)
await run({ host: HOST, username: USERNAME, password: PASSWORD }, async client => {
for (const applicationFileName of applicationFileNames) {
await upload(client, localDir, remoteDir, applicationFileName)
for (const fileName of fileNames) {
console.log(`Uploading ${fileName} ...`)
await upload(client, localDir, remoteDir, fileName)
}
await upload(client, localDir, remoteDir, updateFileName)
})
console.log(`Uploading files finished successfully.`)
-12
View File
@@ -1,12 +0,0 @@
// @ts-check
import { join } from "path"
/**
* @param {"prerelease" | "stable"} channel
*/
export const getLocalPath = channel => {
switch (channel) {
case "prerelease": return join("dist", "insider")
case "stable": return join("dist")
}
}
-35
View File
@@ -1,35 +0,0 @@
// @ts-check
import { platform } from "os"
const [_channel, ...args] = process.argv.slice(2)
/**
* @typedef {"win" | "mac" | "linux"} System
* @returns {System}
*/
export const getSystem = () => {
if (args.includes("--linux")) {
return "linux"
} else if (args.includes("--mac")) {
return "mac"
} else if (args.includes("--win")) {
return "win"
}
switch (platform()) {
case "win32": return "win"
case "darwin": return "mac"
default: return "linux"
}
}
/**
* @param {System} system
*/
export const getSystemName = system => {
switch (system) {
case "win": return "Windows"
case "mac": return "macOS"
case "linux": return "Linux"
}
}
-41
View File
@@ -1,41 +0,0 @@
// @ts-check
import { join } from "path"
import { join as joinPosix } from "path/posix"
import Client from "ssh2-sftp-client"
/**
* @param {Client.ConnectOptions} options
* @param {(client: Client) => Promise<void>} action
*/
export const run = async (options, action) => {
const client = new Client()
console.log("Connecting to server ...")
await client.connect(options)
console.log(`Server connection established.`)
await action(client)
await client.end ()
console.log("Closed server connection.")
}
/**
* @param {Client} client
* @param {string} localDir
* @param {string} remoteDir
* @param {string} fileName
*/
export const upload = async (client, localDir, remoteDir, fileName) => {
console.log(`Uploading ${fileName} ...`);
await client.fastPut (
join (localDir, fileName),
joinPosix (remoteDir, fileName),
{
step: !process.env.CI ? (totalTransferred, _, total) => {
const percent = Math.floor (totalTransferred / total * 100)
console.log(`Progress: ${percent}%`);
} : undefined
}
)
console.log(`Upload done: ${fileName}.`);
}
-31
View File
@@ -1,31 +0,0 @@
// @ts-check
import { join } from "path/posix"
/**
* @param {"prerelease" | "stable"} channel
*/
const remoteChannelPath = channel => {
switch (channel) {
case "prerelease": return "insider"
case "stable": return "."
}
}
/**
* @param {import("./platform.js").System} os
*/
const remoteOsPath = os => {
switch (os) {
case "win": return "win"
case "linux": return "linux"
default: return "mac"
}
}
/**
* @param {string} root
* @param {"prerelease" | "stable"} channel
* @param {import("./platform.js").System} os
*/
export const getRemotePath = (root, channel, os) =>
join(root, remoteChannelPath(channel), remoteOsPath(os))
+51
View File
@@ -0,0 +1,51 @@
// @ts-check
import { join } from "path"
import { join as joinPosix } from "path/posix"
import Client from "ssh2-sftp-client"
/**
* Create a new sftp/ssh client and use it for the callback. The connection will
* be cleaned up afterwards.
* @param {Client.ConnectOptions} options
* @param {(client: Client) => Promise<void>} action
*/
export const run = async (options, action) => {
const client = new Client()
console.log("Connecting to server ...")
await client.connect(options)
console.log(`Server connection established.`)
await action(client)
await client.end()
console.log("Closed server connection.")
}
/**
* Uploads a file from a local directory to a remote directory using the given
* client.
* @param {Client} client
* @param {string} localDir
* @param {string} remoteDir
* @param {string} fileName
*/
export const upload = async (client, localDir, remoteDir, fileName) => {
console.log(`Uploading ${fileName} ...`)
let didStep = false
await client.fastPut(join(localDir, fileName), joinPosix(remoteDir, fileName), {
step: !process.env.CI
? (totalTransferred, _, total) => {
if (didStep) {
process.stdout.clearLine(0)
process.stdout.cursorTo(0)
} else {
didStep = true
}
const percent = Math.floor((totalTransferred / total) * 100)
process.stdout.write(`Progress: ${percent}%`)
}
: undefined,
})
console.log(`Upload done: ${fileName}`)
}
+57
View File
@@ -41,6 +41,7 @@
"@types/react-dom": "^18.2.14",
"@types/react-redux": "^7.1.28",
"@types/semver": "^7.5.4",
"@types/ssh2-sftp-client": "^9.0.2",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
"css-loader": "^6.8.1",
@@ -1520,6 +1521,33 @@
"integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==",
"dev": true
},
"node_modules/@types/ssh2": {
"version": "1.11.15",
"resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.11.15.tgz",
"integrity": "sha512-QFPpT9Gamh+oOKWH6uDUxe8izo8NaCJaN5HdYcbCIiS3hs7fB65KAfyGWBVXaXxXLj7IhFam5Q/ZxQ4eIPc/1Q==",
"dev": true,
"dependencies": {
"@types/node": "^18.11.18"
}
},
"node_modules/@types/ssh2-sftp-client": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/@types/ssh2-sftp-client/-/ssh2-sftp-client-9.0.2.tgz",
"integrity": "sha512-Ryi6McklD4aiNpRhYvWxPYFJHVGxrR3ojsTp4HBg/NFnmKOD0iShSKH1b10uO7/UTPS0MH9o5MjuHle2WQQtdQ==",
"dev": true,
"dependencies": {
"@types/ssh2": "*"
}
},
"node_modules/@types/ssh2/node_modules/@types/node": {
"version": "18.18.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.8.tgz",
"integrity": "sha512-OLGBaaK5V3VRBS1bAkMVP2/W9B+H8meUfl866OrMNQqt7wDgdpWPp5o6gmIc9pB+lIQHSq4ZL8ypeH1vPxcPaQ==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/unist": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz",
@@ -12959,6 +12987,35 @@
"integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==",
"dev": true
},
"@types/ssh2": {
"version": "1.11.15",
"resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.11.15.tgz",
"integrity": "sha512-QFPpT9Gamh+oOKWH6uDUxe8izo8NaCJaN5HdYcbCIiS3hs7fB65KAfyGWBVXaXxXLj7IhFam5Q/ZxQ4eIPc/1Q==",
"dev": true,
"requires": {
"@types/node": "^18.11.18"
},
"dependencies": {
"@types/node": {
"version": "18.18.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.8.tgz",
"integrity": "sha512-OLGBaaK5V3VRBS1bAkMVP2/W9B+H8meUfl866OrMNQqt7wDgdpWPp5o6gmIc9pB+lIQHSq4ZL8ypeH1vPxcPaQ==",
"dev": true,
"requires": {
"undici-types": "~5.26.4"
}
}
}
},
"@types/ssh2-sftp-client": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/@types/ssh2-sftp-client/-/ssh2-sftp-client-9.0.2.tgz",
"integrity": "sha512-Ryi6McklD4aiNpRhYvWxPYFJHVGxrR3ojsTp4HBg/NFnmKOD0iShSKH1b10uO7/UTPS0MH9o5MjuHle2WQQtdQ==",
"dev": true,
"requires": {
"@types/ssh2": "*"
}
},
"@types/unist": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz",
+1
View File
@@ -43,6 +43,7 @@
"@types/react-dom": "^18.2.14",
"@types/react-redux": "^7.1.28",
"@types/semver": "^7.5.4",
"@types/ssh2-sftp-client": "^9.0.2",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
"css-loader": "^6.8.1",