265 lines
7.5 KiB
TypeScript
265 lines
7.5 KiB
TypeScript
import crypto from "node:crypto"
|
|
import fs, { readdirSync } from "node:fs"
|
|
import { cp, readdir } from "node:fs/promises"
|
|
import path, { resolve } from "node:path"
|
|
|
|
import { FuseV1Options, FuseVersion } from "@electron/fuses"
|
|
import { MakerDMG } from "@electron-forge/maker-dmg"
|
|
import { MakerSquirrel } from "@electron-forge/maker-squirrel"
|
|
import { MakerZIP } from "@electron-forge/maker-zip"
|
|
import { FusesPlugin } from "@electron-forge/plugin-fuses"
|
|
import type { ForgeConfig } from "@electron-forge/shared-types"
|
|
import MakerAppImage from "@pengx17/electron-forge-maker-appimage"
|
|
import setLanguages from "electron-packager-languages"
|
|
import { rimraf, rimrafSync } from "rimraf"
|
|
|
|
const artifactRegex = /.*\.(?:exe|dmg|AppImage|zip)$/
|
|
const platformNamesMap = {
|
|
darwin: "macos",
|
|
linux: "linux",
|
|
win32: "windows",
|
|
}
|
|
const ymlMapsMap = {
|
|
darwin: "latest-mac.yml",
|
|
linux: "latest-linux.yml",
|
|
win32: "latest.yml",
|
|
}
|
|
|
|
const keepModules = new Set(["font-list", "vscode-languagedetection"])
|
|
const keepLanguages = new Set(["en", "en_GB", "en-US", "en_US"])
|
|
|
|
// remove folders & files not to be included in the app
|
|
async function cleanSources(buildPath, electronVersion, platform, arch, callback) {
|
|
// folders & files to be included in the app
|
|
const appItems = new Set(["dist", "node_modules", "package.json", "resources"])
|
|
|
|
if (platform === "darwin") {
|
|
const frameworkResourcePath = resolve(
|
|
buildPath,
|
|
"../../Frameworks/Electron Framework.framework/Versions/A/Resources",
|
|
)
|
|
|
|
for (const file of readdirSync(frameworkResourcePath)) {
|
|
if (file.endsWith(".lproj") && !keepLanguages.has(file.split(".")[0])) {
|
|
rimrafSync(resolve(frameworkResourcePath, file))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Keep only node_modules to be included in the app
|
|
|
|
await Promise.all([
|
|
...(await readdir(buildPath).then((items) =>
|
|
items.filter((item) => !appItems.has(item)).map((item) => rimraf(path.join(buildPath, item))),
|
|
)),
|
|
...(await readdir(path.join(buildPath, "node_modules")).then((items) =>
|
|
items
|
|
.filter((item) => !keepModules.has(item))
|
|
.map((item) => rimraf(path.join(buildPath, "node_modules", item))),
|
|
)),
|
|
])
|
|
|
|
// copy needed node_modules to be included in the app
|
|
await Promise.all(
|
|
keepModules.values().map((item) => {
|
|
// Check is exist
|
|
if (fs.existsSync(path.join(buildPath, "node_modules", item))) {
|
|
// eslint-disable-next-line array-callback-return
|
|
return
|
|
}
|
|
return cp(
|
|
path.join(process.cwd(), "node_modules", item),
|
|
path.join(buildPath, "node_modules", item),
|
|
{
|
|
recursive: true,
|
|
},
|
|
)
|
|
}),
|
|
)
|
|
|
|
callback()
|
|
}
|
|
|
|
const noopAfterCopy = (buildPath, electronVersion, platform, arch, callback) => callback()
|
|
|
|
const ignorePattern = new RegExp(`^/node_modules/(?!${[...keepModules].join("|")})`)
|
|
|
|
const config: ForgeConfig = {
|
|
packagerConfig: {
|
|
appBundleId: "is.follow",
|
|
icon: "resources/icon",
|
|
extraResource: ["./resources/app-update.yml"],
|
|
protocols: [
|
|
{
|
|
name: "Follow",
|
|
schemes: ["follow"],
|
|
},
|
|
],
|
|
|
|
afterCopy: [
|
|
cleanSources,
|
|
process.platform !== "win32" ? noopAfterCopy : setLanguages([...keepLanguages.values()]),
|
|
],
|
|
asar: true,
|
|
ignore: [ignorePattern],
|
|
|
|
prune: true,
|
|
...(process.env.APPLE_ID &&
|
|
process.env.APPLE_PASSWORD &&
|
|
process.env.APPLE_TEAM_ID && {
|
|
osxSign: {
|
|
optionsForFile: () => ({
|
|
entitlements: "build/entitlements.mac.plist",
|
|
}),
|
|
keychain: process.env.KEYCHAIN_PATH,
|
|
},
|
|
osxNotarize: {
|
|
appleId: process.env.APPLE_ID!,
|
|
appleIdPassword: process.env.APPLE_PASSWORD!,
|
|
teamId: process.env.APPLE_TEAM_ID!,
|
|
},
|
|
}),
|
|
},
|
|
rebuildConfig: {},
|
|
makers: [
|
|
new MakerZIP({}, ["darwin"]),
|
|
new MakerDMG({
|
|
overwrite: true,
|
|
background: "resources/dmg-background.png",
|
|
icon: "resources/dmg-icon.icns",
|
|
iconSize: 160,
|
|
additionalDMGOptions: {
|
|
window: {
|
|
size: {
|
|
width: 660,
|
|
height: 400,
|
|
},
|
|
},
|
|
},
|
|
contents: (opts) => [
|
|
{
|
|
x: 180,
|
|
y: 170,
|
|
type: "file",
|
|
path: (opts as any).appPath,
|
|
},
|
|
{
|
|
x: 480,
|
|
y: 170,
|
|
type: "link",
|
|
path: "/Applications",
|
|
},
|
|
],
|
|
}),
|
|
new MakerSquirrel({
|
|
name: "Follow",
|
|
setupIcon: "resources/icon.ico",
|
|
iconUrl: "https://app.follow.is/favicon.ico",
|
|
}),
|
|
new MakerAppImage({
|
|
options: {
|
|
icon: "resources/icon.png",
|
|
mimeType: ["x-scheme-handler/follow"],
|
|
},
|
|
}),
|
|
],
|
|
plugins: [
|
|
// Fuses are used to enable/disable various Electron functionality
|
|
// at package time, before code signing the application
|
|
new FusesPlugin({
|
|
version: FuseVersion.V1,
|
|
[FuseV1Options.RunAsNode]: false,
|
|
[FuseV1Options.EnableCookieEncryption]: true,
|
|
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
|
|
[FuseV1Options.EnableNodeCliInspectArguments]: false,
|
|
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
|
|
[FuseV1Options.OnlyLoadAppFromAsar]: true,
|
|
}),
|
|
],
|
|
publishers: [
|
|
{
|
|
name: "@electron-forge/publisher-github",
|
|
config: {
|
|
repository: {
|
|
owner: "RSSNext",
|
|
name: "follow",
|
|
},
|
|
draft: true,
|
|
},
|
|
},
|
|
],
|
|
hooks: {
|
|
postMake: async (config, makeResults) => {
|
|
const yml: {
|
|
version?: string
|
|
files: {
|
|
url: string
|
|
sha512: string
|
|
size: number
|
|
}[]
|
|
releaseDate?: string
|
|
} = {
|
|
version: makeResults[0].packageJSON.version,
|
|
files: [],
|
|
}
|
|
makeResults = makeResults.map((result) => {
|
|
result.artifacts = result.artifacts.map((artifact) => {
|
|
if (artifactRegex.test(artifact)) {
|
|
const newArtifact = `${path.dirname(artifact)}/${
|
|
result.packageJSON.name
|
|
}-${result.packageJSON.version}-${
|
|
platformNamesMap[result.platform]
|
|
}-${result.arch}${path.extname(artifact)}`
|
|
fs.renameSync(artifact, newArtifact)
|
|
|
|
try {
|
|
const fileData = fs.readFileSync(newArtifact)
|
|
const hash = crypto.createHash("sha512").update(fileData).digest("base64")
|
|
const { size } = fs.statSync(newArtifact)
|
|
|
|
yml.files.push({
|
|
url: path.basename(newArtifact),
|
|
sha512: hash,
|
|
size,
|
|
})
|
|
} catch {
|
|
console.error(`Failed to hash ${newArtifact}`)
|
|
}
|
|
return newArtifact
|
|
} else {
|
|
return artifact
|
|
}
|
|
})
|
|
return result
|
|
})
|
|
yml.releaseDate = new Date().toISOString()
|
|
const ymlStr =
|
|
`version: ${yml.version}\n` +
|
|
`files:\n${yml.files
|
|
.map(
|
|
(file) =>
|
|
` - url: ${file.url}\n` +
|
|
` sha512: ${file.sha512}\n` +
|
|
` size: ${file.size}\n`,
|
|
)
|
|
.join("")}releaseDate: ${yml.releaseDate}\n`
|
|
|
|
const ymlPath = `${path.dirname(makeResults[0].artifacts[0])}/${
|
|
ymlMapsMap[makeResults[0].platform]
|
|
}`
|
|
fs.writeFileSync(ymlPath, ymlStr)
|
|
|
|
makeResults.push({
|
|
artifacts: [ymlPath],
|
|
platform: makeResults[0].platform,
|
|
arch: makeResults[0].arch,
|
|
packageJSON: makeResults[0].packageJSON,
|
|
})
|
|
|
|
return makeResults
|
|
},
|
|
},
|
|
}
|
|
|
|
export default config
|