chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# files
|
||||
Makefile
|
||||
Gulpfile.js
|
||||
Gruntfile.js
|
||||
.tern-project
|
||||
.gitattributes
|
||||
.editorconfig
|
||||
.eslintrc
|
||||
.jshintrc
|
||||
.flowconfig
|
||||
.documentup.json
|
||||
.yarn-metadata.json
|
||||
.travis.yml
|
||||
appveyor.yml
|
||||
LICENSE.txt
|
||||
LICENSE
|
||||
AUTHORS
|
||||
CONTRIBUTORS
|
||||
.yarn-integrity
|
||||
*.md
|
||||
*.ts
|
||||
*.js.map
|
||||
*.ts.map
|
||||
*.jst
|
||||
*.coffee
|
||||
*.d.cts
|
||||
*.d.mts
|
||||
tsconfig.json
|
||||
.nycrc
|
||||
.nycrc.json
|
||||
opslevel.yml
|
||||
package-support.json
|
||||
bench.js
|
||||
tests.js
|
||||
|
||||
# folders
|
||||
__tests__
|
||||
test
|
||||
tests
|
||||
powered-test
|
||||
docs
|
||||
doc
|
||||
website
|
||||
images
|
||||
assets
|
||||
example
|
||||
examples
|
||||
coverage
|
||||
.nyc_output
|
||||
dist/esm
|
||||
zmodem2/dist/cjs
|
||||
zmodem2/dist/browser
|
||||
zmodem2/dist/esm
|
||||
trzsz2/dist/cjs
|
||||
trzsz2/dist/esm
|
||||
package-lock.json
|
||||
.github
|
||||
.circleci
|
||||
scripts
|
||||
samplejson
|
||||
flash
|
||||
third_party
|
||||
tools
|
||||
bench
|
||||
benchmarks
|
||||
spec
|
||||
specs
|
||||
fixture
|
||||
fixtures
|
||||
umd
|
||||
@@ -0,0 +1,8 @@
|
||||
const { exec } = require('shelljs')
|
||||
const os = require('os')
|
||||
const platform = os.platform()
|
||||
console.log('platform:', platform)
|
||||
const cmd = platform.startsWith('win')
|
||||
? 'node_modules\\.bin\\cross-env NODE_ENV=development node_modules\\.bin\\electron -r dotenv/config src\\app\\app'
|
||||
: 'node_modules/.bin/cross-env NODE_ENV=development node_modules/.bin/electron -r dotenv/config src/app/app'
|
||||
exec(cmd)
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Install .desktop file and icon for AppImage on Linux
|
||||
* so that taskbars (e.g. UOS/Deepin dde-dock) can find the icon.
|
||||
*
|
||||
* This is standard practice for AppImage — the format uses a volatile
|
||||
* FUSE mount path, so the embedded .desktop file and icon are never
|
||||
* found by desktop environments that look in XDG standard locations.
|
||||
*/
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const os = require('os')
|
||||
const log = require('../common/log')
|
||||
|
||||
function installDesktopFile () {
|
||||
if (process.platform !== 'linux' || !process.env.APPIMAGE) {
|
||||
return
|
||||
}
|
||||
|
||||
const appImagePath = process.env.APPIMAGE
|
||||
const home = os.homedir()
|
||||
const desktopId = 'electerm'
|
||||
const appsDir = path.join(home, '.local', 'share', 'applications')
|
||||
const iconsDir = path.join(
|
||||
home, '.local', 'share', 'icons',
|
||||
'hicolor', '128x128', 'apps'
|
||||
)
|
||||
const desktopFilePath = path.join(appsDir, `${desktopId}.desktop`)
|
||||
const iconFilePath = path.join(iconsDir, `${desktopId}.png`)
|
||||
|
||||
// Icon source inside the AppImage (asar-aware path — Electron's fs
|
||||
// module transparently reads from inside app.asar)
|
||||
const srcIconPath = path.join(
|
||||
process.resourcesPath,
|
||||
'app.asar', 'assets', 'images', 'electerm-round-128x128.png'
|
||||
)
|
||||
|
||||
const desktopContent = [
|
||||
'[Desktop Entry]',
|
||||
'Name=electerm',
|
||||
'Comment=Terminal/SSH/SFTP client',
|
||||
`Exec=${appImagePath} %U`,
|
||||
`Icon=${iconFilePath}`,
|
||||
'Terminal=false',
|
||||
'Type=Application',
|
||||
'Categories=Development;System;TerminalEmulator;',
|
||||
'StartupWMClass=electerm',
|
||||
'MimeType=x-scheme-handler/ssh;x-scheme-handler/telnet;' +
|
||||
'x-scheme-handler/rdp;x-scheme-handler/vnc;' +
|
||||
'x-scheme-handler/serial;x-scheme-handler/spice;' +
|
||||
'x-scheme-handler/electerm;',
|
||||
''
|
||||
].join('\n')
|
||||
|
||||
try {
|
||||
// Skip if a system-level .desktop file exists (deb/rpm installs one
|
||||
// to /usr/share/applications/) — don't override it.
|
||||
// Also clean up any leftover user-level file from a previous AppImage run.
|
||||
const sysDesktop = '/usr/share/applications/electerm.desktop'
|
||||
if (fs.existsSync(sysDesktop)) {
|
||||
try {
|
||||
fs.unlinkSync(desktopFilePath)
|
||||
fs.unlinkSync(iconFilePath)
|
||||
} catch (_) {}
|
||||
return
|
||||
}
|
||||
|
||||
// Skip if .desktop file already has the correct content
|
||||
let existing = ''
|
||||
try {
|
||||
existing = fs.readFileSync(desktopFilePath, 'utf8')
|
||||
} catch (_) {}
|
||||
if (existing === desktopContent) {
|
||||
return
|
||||
}
|
||||
|
||||
fs.mkdirSync(appsDir, { recursive: true })
|
||||
fs.mkdirSync(iconsDir, { recursive: true })
|
||||
|
||||
// Copy icon once (doesn't change between versions)
|
||||
if (!fs.existsSync(iconFilePath) && fs.existsSync(srcIconPath)) {
|
||||
fs.copyFileSync(srcIconPath, iconFilePath)
|
||||
}
|
||||
|
||||
fs.writeFileSync(desktopFilePath, desktopContent)
|
||||
|
||||
// Refresh desktop database so the DE picks up the new entry
|
||||
try {
|
||||
require('child_process').execSync(
|
||||
'update-desktop-database ~/.local/share/applications 2>/dev/null'
|
||||
)
|
||||
} catch (_) {}
|
||||
|
||||
log.info(`[appimage] Installed .desktop file to ${desktopFilePath}`)
|
||||
} catch (e) {
|
||||
log.error('[appimage] Failed to install desktop file:', e.message)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { installDesktopFile }
|
||||
@@ -0,0 +1,342 @@
|
||||
/**
|
||||
* common functions for build
|
||||
*/
|
||||
|
||||
const { exec } = require('child_process')
|
||||
const { resolve } = require('path')
|
||||
const { writeFileSync, readFileSync } = require('fs')
|
||||
const replace = require('replace-in-file')
|
||||
const { rm, mv } = require('shelljs')
|
||||
|
||||
exports.run = function (cmd) {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log('Executing command:', cmd)
|
||||
const childProcess = exec(cmd, {
|
||||
env: {
|
||||
...process.env,
|
||||
DEBUG: 'electron-builder:*',
|
||||
ELECTRON_BUILDER_CACHE: process.env.ELECTRON_BUILDER_CACHE || ''
|
||||
},
|
||||
maxBuffer: 1024 * 1024 * 50 // 50MB buffer for large debug output
|
||||
}, (err, stdout, stderr) => {
|
||||
// Always log stdout and stderr regardless of success/failure
|
||||
if (stdout) {
|
||||
console.log('=== STDOUT ===')
|
||||
console.log(stdout)
|
||||
}
|
||||
if (stderr) {
|
||||
console.log('=== STDERR ===')
|
||||
console.log(stderr)
|
||||
}
|
||||
|
||||
if (err) {
|
||||
console.error('=== COMMAND FAILED ===')
|
||||
console.error('Command:', cmd)
|
||||
console.error('Exit code:', err.code)
|
||||
console.error('Signal:', err.signal)
|
||||
console.error('Error message:', err.message)
|
||||
|
||||
// Create a more detailed error message
|
||||
const detailedError = new Error(`Command failed with exit code ${err.code}: ${cmd}`)
|
||||
detailedError.originalError = err
|
||||
detailedError.stdout = stdout
|
||||
detailedError.stderr = stderr
|
||||
detailedError.command = cmd
|
||||
return reject(detailedError)
|
||||
}
|
||||
|
||||
resolve(stdout)
|
||||
})
|
||||
|
||||
// Also pipe output in real-time for long-running commands
|
||||
childProcess.stdout.on('data', (data) => {
|
||||
process.stdout.write(data)
|
||||
})
|
||||
|
||||
childProcess.stderr.on('data', (data) => {
|
||||
process.stderr.write(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.writeSrc = function (src) {
|
||||
const p = resolve(__dirname, '../../work/app/lib/install-src.js')
|
||||
writeFileSync(p, `module.exports = '${src}'`)
|
||||
if (src.includes('AppImage')) {
|
||||
exports.patchAppImage()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch work/app to include AppImage desktop integration.
|
||||
* Copies the integration module and wires it into create-app.js
|
||||
* so the .desktop file + icon are installed on first run.
|
||||
* Only called for AppImage builds — zero overhead for other targets.
|
||||
*/
|
||||
exports.patchAppImage = function patchAppImage () {
|
||||
const fs = require('fs')
|
||||
const workLib = resolve(__dirname, '../../work/app/lib')
|
||||
const workAssets = resolve(__dirname, '../../work/app/assets/images')
|
||||
|
||||
// Copy integration module to work/app/lib/
|
||||
const modSrc = resolve(__dirname, 'appimage-integration.js')
|
||||
const modDst = resolve(workLib, 'appimage-integration.js')
|
||||
fs.copyFileSync(modSrc, modDst)
|
||||
|
||||
// Copy icon for desktop integration (outside asar, accessible at runtime)
|
||||
const iconSrc = resolve(
|
||||
__dirname, '../../node_modules/@electerm/electerm-resource/res/imgs/electerm-round-128x128.png'
|
||||
)
|
||||
const iconDst = resolve(workAssets, 'electerm-round-128x128.png')
|
||||
if (fs.existsSync(iconSrc)) {
|
||||
fs.copyFileSync(iconSrc, iconDst)
|
||||
}
|
||||
|
||||
// Patch create-app.js to import and call installDesktopFile
|
||||
const createAppPath = resolve(workLib, 'create-app.js')
|
||||
let code = fs.readFileSync(createAppPath, 'utf8')
|
||||
if (!code.includes('installDesktopFile')) {
|
||||
code = code.replace(
|
||||
"const log = require('../common/log')",
|
||||
"const log = require('../common/log')\n" +
|
||||
"const { installDesktopFile } = require('./appimage-integration')"
|
||||
)
|
||||
code = code.replace(
|
||||
'app.setName(packInfo.name)',
|
||||
'app.setName(packInfo.name)\n installDesktopFile()'
|
||||
)
|
||||
fs.writeFileSync(createAppPath, code)
|
||||
}
|
||||
console.log('[appimage] Patched work/app for desktop integration')
|
||||
}
|
||||
|
||||
exports.builder = resolve(
|
||||
__dirname, '../../node_modules/.bin/electron-builder'
|
||||
)
|
||||
|
||||
exports.reBuild = resolve(
|
||||
__dirname, '../../node_modules/.bin/electron-rebuild'
|
||||
)
|
||||
|
||||
exports.replaceArr = function (froms, tos) {
|
||||
const pth = resolve(__dirname, '../../electron-builder.json')
|
||||
console.log('electron-builder', pth)
|
||||
let str = readFileSync(pth, 'utf8')
|
||||
for (let i = 0; i < froms.length; i++) {
|
||||
str = str.replace(froms[i], tos[i])
|
||||
}
|
||||
writeFileSync(pth, str)
|
||||
}
|
||||
|
||||
exports.replaceJSON = function (func) {
|
||||
const pth = resolve(__dirname, '../../electron-builder.json')
|
||||
const js = require(pth)
|
||||
func(js)
|
||||
writeFileSync(pth, JSON.stringify(js, null, 2))
|
||||
}
|
||||
|
||||
exports.setChannel = function (suffix) {
|
||||
exports.replaceJSON((data) => {
|
||||
if (!data.linux) { data.linux = {} }
|
||||
if (!data.linux.publish) { data.linux.publish = {} }
|
||||
data.linux.publish.channel = '${env.WORKFLOW_NAME}-' + suffix // eslint-disable-line
|
||||
})
|
||||
}
|
||||
|
||||
const options = {
|
||||
files: require('path').resolve(__dirname, '../../electron-builder.json'),
|
||||
from: ['"asar": true', '${productName}-${version}-${os}-${arch}.${ext}', ', "appx", "nsis"'], // eslint-disable-line
|
||||
to: ['"asar": false', '${productName}-${version}-${os}-${arch}-loose.${ext}', ''] // eslint-disable-line
|
||||
}
|
||||
|
||||
exports.replaceRun = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
replace(options, (err) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
console.log('start build loose file(no asar)')
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const shouldKeepFile = !!process.env.KEEP_FILE
|
||||
|
||||
exports.renameDist = function renameDist () {
|
||||
if (!shouldKeepFile) {
|
||||
return rm('-rf', 'dist')
|
||||
}
|
||||
mv('dist', 'dist' + new Date().getTime())
|
||||
}
|
||||
|
||||
exports.uploadToR2 = async function uploadToR2 (src) {
|
||||
// Must be running in CI
|
||||
if (!process.env.CI) {
|
||||
console.log('[r2] Skipping upload: not in CI environment')
|
||||
return
|
||||
}
|
||||
|
||||
// Check commit message contains "[r2]"
|
||||
const { execSync } = require('child_process')
|
||||
let commitMsg = ''
|
||||
try {
|
||||
commitMsg = execSync('git log -1 --format=%s', { encoding: 'utf8' }).trim()
|
||||
} catch (e) {
|
||||
console.log('[r2] Skipping upload: could not read commit message')
|
||||
return
|
||||
}
|
||||
if (!commitMsg.includes('[r2]')) {
|
||||
console.log('[r2] Skipping upload: commit message does not contain "[r2]"')
|
||||
return
|
||||
}
|
||||
|
||||
// Check required env vars
|
||||
const accountId = process.env.CF_R2_ACCOUNT_ID
|
||||
const bucket = process.env.CF_R2_BUCKET
|
||||
const accessKeyId = process.env.CF_R2_ACCESS_KEY_ID
|
||||
const secretAccessKey = process.env.CF_R2_SECRET_ACCESS_KEY
|
||||
|
||||
if (!accountId || !bucket || !accessKeyId || !secretAccessKey) {
|
||||
console.error('[r2] Missing required env vars: CF_R2_ACCOUNT_ID, CF_R2_BUCKET, CF_R2_ACCESS_KEY_ID, CF_R2_SECRET_ACCESS_KEY')
|
||||
return
|
||||
}
|
||||
|
||||
// Find the built file in dist/
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const distDir = resolve(__dirname, '../../dist')
|
||||
let filePath = null
|
||||
if (fs.existsSync(distDir)) {
|
||||
const files = fs.readdirSync(distDir)
|
||||
const match = files.find(f => f.endsWith(src))
|
||||
if (match) {
|
||||
filePath = path.join(distDir, match)
|
||||
}
|
||||
}
|
||||
if (!filePath) {
|
||||
// Try to find recursively one level deeper
|
||||
if (fs.existsSync(distDir)) {
|
||||
const entries = fs.readdirSync(distDir, { withFileTypes: true })
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory()) {
|
||||
const subDir = path.join(distDir, entry.name)
|
||||
const subFiles = fs.readdirSync(subDir)
|
||||
const match = subFiles.find(f => f.endsWith(src))
|
||||
if (match) {
|
||||
filePath = path.join(subDir, match)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!filePath) {
|
||||
console.error(`[r2] Could not find built file matching "${src}" in dist/`)
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`[r2] Uploading ${filePath} to R2 bucket "${bucket}" as "${path.basename(filePath)}"`)
|
||||
|
||||
// Polyfill crypto.getRandomValues for Node < 19.
|
||||
// @smithy/core reads getRandomValues from require('node:crypto') directly
|
||||
// (line: const _getRandomValues = node_crypto.getRandomValues), so we must
|
||||
// patch the crypto module itself, not just globalThis.crypto.
|
||||
const nodeCrypto = require('crypto')
|
||||
if (!nodeCrypto.getRandomValues) {
|
||||
nodeCrypto.getRandomValues = (buf) => nodeCrypto.randomFillSync(buf)
|
||||
}
|
||||
if (!globalThis.crypto) {
|
||||
globalThis.crypto = nodeCrypto
|
||||
}
|
||||
|
||||
let S3Client, PutObjectCommand
|
||||
try {
|
||||
const s3Module = require('@aws-sdk/client-s3')
|
||||
S3Client = s3Module.S3Client
|
||||
PutObjectCommand = s3Module.PutObjectCommand
|
||||
} catch (e) {
|
||||
console.error('[r2] @aws-sdk/client-s3 is not installed. Run: npm install --save-dev @aws-sdk/client-s3')
|
||||
throw e
|
||||
}
|
||||
|
||||
const client = new S3Client({
|
||||
region: 'auto',
|
||||
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
|
||||
credentials: {
|
||||
accessKeyId,
|
||||
secretAccessKey
|
||||
}
|
||||
})
|
||||
|
||||
const fileStream = fs.createReadStream(filePath)
|
||||
const key = path.basename(filePath)
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
Body: fileStream,
|
||||
ContentLength: fs.statSync(filePath).size
|
||||
})
|
||||
|
||||
await client.send(command)
|
||||
console.log(`[r2] Successfully uploaded ${key} to R2`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch the electron-builder NSIS template so that the installer always tries
|
||||
* to keep shortcuts (desktop / start-menu / taskbar pins) when upgrading.
|
||||
*
|
||||
* By default, when `allowToChangeInstallationDirectory` is set, the template
|
||||
* sets `isTryToKeepShortcuts = "false"` for manual (non-auto-update) installs,
|
||||
* which causes `WinShell::UninstShortcut` / `UninstAppUserModelId` to be called
|
||||
* and the taskbar pin to be silently removed. Removing the guard makes the
|
||||
* installer always pass `--keep-shortcuts` to the old uninstaller when the
|
||||
* previous install wrote `KeepShortcuts = "true"` to the registry.
|
||||
*/
|
||||
exports.patchNsisKeepShortcuts = function patchNsisKeepShortcuts () {
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const templatePath = path.join(
|
||||
require.resolve('app-builder-lib/package.json'),
|
||||
'../templates/nsis/include/installUtil.nsh'
|
||||
)
|
||||
const original = fs.readFileSync(templatePath, 'utf-8')
|
||||
const patched = original.replace(
|
||||
/(!macro setIsTryToKeepShortcuts\s+StrCpy \$isTryToKeepShortcuts "true"\s*)!ifdef allowToChangeInstallationDirectory[\s\S]*?!endif(\s*!macroend)/,
|
||||
'$1$2'
|
||||
)
|
||||
if (patched === original) {
|
||||
console.log('NSIS keep-shortcuts patch: already applied or pattern not found, skipping')
|
||||
} else {
|
||||
fs.writeFileSync(templatePath, patched, 'utf-8')
|
||||
console.log('NSIS keep-shortcuts patch: applied successfully')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch electron-builder's Snap target so classic confinement builds keep the
|
||||
* Chromium sandbox and do not force --no-sandbox into the generated launcher.
|
||||
*
|
||||
* electerm's packaged entrypoint rejects the --no-sandbox CLI flag, and classic
|
||||
* snaps do not need the browser-support plug path used by strict confinement.
|
||||
*/
|
||||
exports.patchSnapClassicSandbox = function patchSnapClassicSandbox () {
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const targetPath = path.join(
|
||||
require.resolve('app-builder-lib/package.json'),
|
||||
'../out/targets/snap.js'
|
||||
)
|
||||
const original = fs.readFileSync(targetPath, 'utf-8')
|
||||
const patched = original.replace(
|
||||
' if (this.isElectronVersionGreaterOrEqualThan("5.0.0") && !isBrowserSandboxAllowed(snap)) {',
|
||||
' if (this.isElectronVersionGreaterOrEqualThan("5.0.0") && snap.confinement !== "classic" && !isBrowserSandboxAllowed(snap)) {'
|
||||
)
|
||||
|
||||
if (patched === original) {
|
||||
console.log('Snap classic sandbox patch: already applied or pattern not found, skipping')
|
||||
} else {
|
||||
fs.writeFileSync(targetPath, patched, 'utf-8')
|
||||
console.log('Snap classic sandbox patch: applied successfully')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
const { echo } = require('shelljs')
|
||||
const {
|
||||
run,
|
||||
writeSrc,
|
||||
uploadToR2,
|
||||
builder: pb,
|
||||
renameDist,
|
||||
reBuild,
|
||||
replaceJSON,
|
||||
setChannel
|
||||
} = require('./build-common')
|
||||
|
||||
// Function to add "-legacy" suffix to artifact names in electron-builder.json
|
||||
function addLegacySuffix () {
|
||||
echo('============')
|
||||
echo('Updating electron-builder.json to add -legacy suffix')
|
||||
replaceJSON((data) => {
|
||||
// Update main artifactName
|
||||
if (data.artifactName) {
|
||||
data.artifactName = data.artifactName.replace('${productName}-${version}-${os}-${arch}.${ext}', '${productName}-${version}-${os}-${arch}-legacy.${ext}') // eslint-disable-line
|
||||
}
|
||||
})
|
||||
echo('Updated artifact names with -legacy suffix')
|
||||
}
|
||||
|
||||
async function main () {
|
||||
echo('============================================')
|
||||
echo('==== Start: running build for linux part 3 arm64/armv7l legacy ====')
|
||||
echo('============================================')
|
||||
|
||||
// Add legacy suffix to names
|
||||
addLegacySuffix()
|
||||
|
||||
echo('============================================')
|
||||
echo('==== Start: build linux.arm64.tar.gz ====')
|
||||
echo('============================================')
|
||||
let src = 'linux-arm64-legacy.tar.gz'
|
||||
renameDist()
|
||||
writeSrc(src)
|
||||
replaceJSON(
|
||||
(data) => {
|
||||
data.linux.target = ['tar.gz']
|
||||
}
|
||||
)
|
||||
setChannel(src)
|
||||
// await run(`${reBuild} --arch arm64 -f work/app`)
|
||||
await run(`${pb} --linux --arm64`).catch(error => {
|
||||
echo('❌ Fatal error in build linux.arm64.tar.gz:')
|
||||
console.error(error)
|
||||
})
|
||||
await uploadToR2(src)
|
||||
|
||||
echo('============================================')
|
||||
echo('==== Start: build linux.arm64.deb ====')
|
||||
echo('============================================')
|
||||
src = 'linux-arm64-legacy.deb'
|
||||
renameDist()
|
||||
writeSrc(src)
|
||||
replaceJSON(
|
||||
(data) => {
|
||||
data.linux.target = ['deb']
|
||||
}
|
||||
)
|
||||
setChannel(src)
|
||||
await run(`${pb} --linux --arm64`).catch(error => {
|
||||
echo('❌ Fatal error in build linux.arm64.deb:')
|
||||
console.error(error)
|
||||
})
|
||||
await uploadToR2(src)
|
||||
|
||||
echo('============================================')
|
||||
echo('==== Start: build linux.aarch64.rpm ====')
|
||||
echo('============================================')
|
||||
src = 'linux-aarch64-legacy.rpm'
|
||||
renameDist()
|
||||
writeSrc(src)
|
||||
replaceJSON(
|
||||
(data) => {
|
||||
data.linux.target = ['rpm']
|
||||
}
|
||||
)
|
||||
setChannel(src)
|
||||
await run(`${pb} --linux --arm64`).catch(error => {
|
||||
echo('❌ Fatal error in build linux.aarch64.rpm:')
|
||||
console.error(error)
|
||||
})
|
||||
await uploadToR2(src)
|
||||
|
||||
echo('============================================')
|
||||
echo('==== Start: build linux.arm64.AppImage ====')
|
||||
echo('============================================')
|
||||
src = 'linux-arm64-legacy.AppImage'
|
||||
renameDist()
|
||||
writeSrc(src)
|
||||
replaceJSON(
|
||||
(data) => {
|
||||
data.linux.target = ['AppImage']
|
||||
}
|
||||
)
|
||||
setChannel(src)
|
||||
await run(`${pb} --linux --arm64`).catch(error => {
|
||||
echo('❌ Fatal error in build linux.arm64.AppImage:')
|
||||
console.error(error)
|
||||
})
|
||||
await uploadToR2(src)
|
||||
|
||||
echo('============================================')
|
||||
echo('==== Start: build linux.armv7l.tar.gz ====')
|
||||
echo('============================================')
|
||||
src = 'linux-armv7l-legacy.tar.gz'
|
||||
renameDist()
|
||||
writeSrc(src)
|
||||
replaceJSON(
|
||||
(data) => {
|
||||
data.linux.target = ['tar.gz']
|
||||
}
|
||||
)
|
||||
setChannel(src)
|
||||
await run(`${reBuild} --arch armv7l -f work/app`)
|
||||
await run(`${pb} --linux --armv7l`).catch(error => {
|
||||
echo('❌ Fatal error in build linux.armv7l:')
|
||||
console.error(error)
|
||||
})
|
||||
await uploadToR2(src)
|
||||
|
||||
echo('============================================')
|
||||
echo('==== Start: build linux.armv7l.deb ====')
|
||||
echo('============================================')
|
||||
src = 'linux-armv7l-legacy.deb'
|
||||
renameDist()
|
||||
writeSrc(src)
|
||||
replaceJSON(
|
||||
(data) => {
|
||||
data.linux.target = ['deb']
|
||||
}
|
||||
)
|
||||
setChannel(src)
|
||||
await run(`${pb} --linux --armv7l`).catch(error => {
|
||||
echo('❌ Fatal error in build linux.armv7l.deb:')
|
||||
console.error(error)
|
||||
})
|
||||
await uploadToR2(src)
|
||||
|
||||
echo('============================================')
|
||||
echo('==== Start: build linux.armv7l.rpm ====')
|
||||
echo('============================================')
|
||||
src = 'linux-armv7l-legacy.rpm'
|
||||
renameDist()
|
||||
writeSrc(src)
|
||||
replaceJSON(
|
||||
(data) => {
|
||||
data.linux.target = ['rpm']
|
||||
}
|
||||
)
|
||||
setChannel(src)
|
||||
await run(`${pb} --linux --armv7l`).catch(error => {
|
||||
echo('❌ Fatal error in build linux.armv7l.rpm:')
|
||||
console.error(error)
|
||||
})
|
||||
await uploadToR2(src)
|
||||
|
||||
echo('============================================')
|
||||
echo('==== Start: build linux.armv7l.AppImage ====')
|
||||
echo('============================================')
|
||||
src = 'linux-armv7l-legacy.AppImage'
|
||||
renameDist()
|
||||
writeSrc(src)
|
||||
replaceJSON(
|
||||
(data) => {
|
||||
data.linux.target = ['AppImage']
|
||||
}
|
||||
)
|
||||
setChannel(src)
|
||||
await run(`${pb} --linux --armv7l`).catch(error => {
|
||||
echo('❌ Fatal error in build linux.armv7l.AppImage:')
|
||||
console.error(error)
|
||||
})
|
||||
await uploadToR2(src)
|
||||
|
||||
echo('✅ All Linux ARM builds completed successfully')
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,158 @@
|
||||
const { echo } = require('shelljs')
|
||||
const {
|
||||
run,
|
||||
writeSrc,
|
||||
uploadToR2,
|
||||
builder: pb,
|
||||
renameDist,
|
||||
reBuild,
|
||||
replaceJSON
|
||||
} = require('./build-common')
|
||||
|
||||
async function main () {
|
||||
echo('============================================')
|
||||
echo('==== Start: running build for linux part 3 arm64/armv7l ====')
|
||||
echo('============================================')
|
||||
|
||||
echo('============================================')
|
||||
echo('==== Start: build linux.arm64.tar.gz ====')
|
||||
echo('============================================')
|
||||
let src = 'linux-arm64.tar.gz'
|
||||
renameDist()
|
||||
writeSrc(src)
|
||||
replaceJSON(
|
||||
(data) => {
|
||||
data.linux.target = ['tar.gz']
|
||||
}
|
||||
)
|
||||
// await run(`${reBuild} --arch arm64 -f work/app`)
|
||||
await run(`${pb} --linux --arm64`).catch(error => {
|
||||
echo('❌ Fatal error in build linux.arm64.tar.gz:')
|
||||
console.error(error)
|
||||
})
|
||||
await uploadToR2(src)
|
||||
|
||||
echo('============================================')
|
||||
echo('==== Start: build linux.arm64.deb ====')
|
||||
echo('============================================')
|
||||
src = 'linux-arm64.deb'
|
||||
renameDist()
|
||||
writeSrc(src)
|
||||
replaceJSON(
|
||||
(data) => {
|
||||
data.linux.target = ['deb']
|
||||
}
|
||||
)
|
||||
await run(`${pb} --linux --arm64`).catch(error => {
|
||||
echo('❌ Fatal error in build linux.arm64.deb:')
|
||||
console.error(error)
|
||||
})
|
||||
await uploadToR2(src)
|
||||
|
||||
echo('============================================')
|
||||
echo('==== Start: build linux.aarch64.rpm ====')
|
||||
echo('============================================')
|
||||
src = 'linux-aarch64.rpm'
|
||||
renameDist()
|
||||
writeSrc(src)
|
||||
replaceJSON(
|
||||
(data) => {
|
||||
data.linux.target = ['rpm']
|
||||
}
|
||||
)
|
||||
await run(`${pb} --linux --arm64`).catch(error => {
|
||||
echo('❌ Fatal error in build linux.aarch64.rpm:')
|
||||
console.error(error)
|
||||
})
|
||||
await uploadToR2(src)
|
||||
|
||||
echo('============================================')
|
||||
echo('==== Start: build linux.arm64.AppImage ====')
|
||||
echo('============================================')
|
||||
src = 'linux-arm64.AppImage'
|
||||
renameDist()
|
||||
writeSrc(src)
|
||||
replaceJSON(
|
||||
(data) => {
|
||||
data.linux.target = ['AppImage']
|
||||
}
|
||||
)
|
||||
await run(`${pb} --linux --arm64`).catch(error => {
|
||||
echo('❌ Fatal error in build linux.arm64.AppImage:')
|
||||
console.error(error)
|
||||
})
|
||||
await uploadToR2(src)
|
||||
|
||||
echo('============================================')
|
||||
echo('==== Start: build linux.armv7l.tar.gz ====')
|
||||
echo('============================================')
|
||||
src = 'linux-armv7l.tar.gz'
|
||||
renameDist()
|
||||
writeSrc(src)
|
||||
replaceJSON(
|
||||
(data) => {
|
||||
data.linux.target = ['tar.gz']
|
||||
}
|
||||
)
|
||||
await run(`${reBuild} --arch armv7l -f work/app`)
|
||||
await run(`${pb} --linux --armv7l`).catch(error => {
|
||||
echo('❌ Fatal error in build linux.armv7l:')
|
||||
console.error(error)
|
||||
})
|
||||
await uploadToR2(src)
|
||||
|
||||
echo('============================================')
|
||||
echo('==== Start: build linux.armv7l.deb ====')
|
||||
echo('============================================')
|
||||
src = 'linux-armv7l.deb'
|
||||
renameDist()
|
||||
writeSrc(src)
|
||||
replaceJSON(
|
||||
(data) => {
|
||||
data.linux.target = ['deb']
|
||||
}
|
||||
)
|
||||
await run(`${pb} --linux --armv7l`).catch(error => {
|
||||
echo('❌ Fatal error in build linux.armv7l.deb:')
|
||||
console.error(error)
|
||||
})
|
||||
await uploadToR2(src)
|
||||
|
||||
echo('============================================')
|
||||
echo('==== Start: build linux.armv7l.rpm ====')
|
||||
echo('============================================')
|
||||
src = 'linux-armv7l.rpm'
|
||||
renameDist()
|
||||
writeSrc(src)
|
||||
replaceJSON(
|
||||
(data) => {
|
||||
data.linux.target = ['rpm']
|
||||
}
|
||||
)
|
||||
await run(`${pb} --linux --armv7l`).catch(error => {
|
||||
echo('❌ Fatal error in build linux.armv7l.rpm:')
|
||||
console.error(error)
|
||||
})
|
||||
await uploadToR2(src)
|
||||
|
||||
echo('============================================')
|
||||
echo('==== Start: build linux.armv7l.AppImage ====')
|
||||
echo('============================================')
|
||||
src = 'linux-armv7l.AppImage'
|
||||
renameDist()
|
||||
writeSrc(src)
|
||||
replaceJSON(
|
||||
(data) => {
|
||||
data.linux.target = ['AppImage']
|
||||
}
|
||||
)
|
||||
await run(`${pb} --linux --armv7l`).catch(error => {
|
||||
echo('❌ Fatal error in build linux.armv7l.AppImage:')
|
||||
console.error(error)
|
||||
})
|
||||
await uploadToR2(src)
|
||||
|
||||
echo('✅ All Linux ARM builds completed successfully')
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,44 @@
|
||||
const { echo, rm } = require('shelljs')
|
||||
const {
|
||||
run,
|
||||
writeSrc,
|
||||
uploadToR2,
|
||||
builder: pb,
|
||||
replaceJSON,
|
||||
renameDist
|
||||
} = require('./build-common')
|
||||
|
||||
async function main () {
|
||||
echo('running build for linux part 1')
|
||||
|
||||
echo('build tar.gz')
|
||||
rm('-rf', 'dist')
|
||||
let src = 'linux-x64.tar.gz'
|
||||
writeSrc(src)
|
||||
await run(`${pb} --linux tar.gz`)
|
||||
await uploadToR2(src)
|
||||
renameDist()
|
||||
|
||||
echo('build deb')
|
||||
rm('-rf', 'dist')
|
||||
src = 'linux-amd64.deb'
|
||||
writeSrc(src)
|
||||
await run(`${pb} --linux deb`)
|
||||
await uploadToR2(src)
|
||||
renameDist()
|
||||
|
||||
echo('build linux-x86_64.AppImage')
|
||||
rm('-rf', 'dist')
|
||||
src = 'linux-x86_64.AppImage'
|
||||
writeSrc(src)
|
||||
replaceJSON(
|
||||
(data) => {
|
||||
data.linux.target = ['AppImage']
|
||||
}
|
||||
)
|
||||
await run(`${pb} --linux`)
|
||||
await uploadToR2(src)
|
||||
renameDist()
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,88 @@
|
||||
const { echo, rm } = require('shelljs')
|
||||
const {
|
||||
run,
|
||||
writeSrc,
|
||||
uploadToR2,
|
||||
builder: pb,
|
||||
replaceJSON,
|
||||
renameDist,
|
||||
setChannel
|
||||
} = require('./build-common')
|
||||
|
||||
// Function to add "-legacy" suffix to artifact names in electron-builder.json
|
||||
function addLegacySuffix () {
|
||||
echo('============')
|
||||
echo('Updating electron-builder.json to add -legacy suffix')
|
||||
replaceJSON((data) => {
|
||||
// Update main artifactName
|
||||
if (data.artifactName) {
|
||||
data.artifactName = data.artifactName.replace('${productName}-${version}-${os}-${arch}.${ext}', '${productName}-${version}-${os}-${arch}-legacy.${ext}') // eslint-disable-line
|
||||
}
|
||||
})
|
||||
echo('Updated artifact names with -legacy suffix')
|
||||
}
|
||||
|
||||
async function main () {
|
||||
echo('============')
|
||||
echo('Starting legacy Linux build process')
|
||||
echo('============')
|
||||
|
||||
// Add legacy suffix to names
|
||||
addLegacySuffix()
|
||||
|
||||
echo('============')
|
||||
echo('Building tar.gz package')
|
||||
echo('============')
|
||||
rm('-rf', 'dist')
|
||||
let src = 'linux-x64-legacy.tar.gz'
|
||||
writeSrc(src)
|
||||
setChannel(src)
|
||||
await run(`${pb} --linux tar.gz`)
|
||||
await uploadToR2(src)
|
||||
renameDist()
|
||||
|
||||
echo('============')
|
||||
echo('Building deb package')
|
||||
echo('============')
|
||||
rm('-rf', 'dist')
|
||||
src = 'linux-amd64-legacy.deb'
|
||||
writeSrc(src)
|
||||
setChannel(src)
|
||||
await run(`${pb} --linux deb`)
|
||||
await uploadToR2(src)
|
||||
renameDist()
|
||||
|
||||
echo('============')
|
||||
echo('Building rpm package')
|
||||
echo('============')
|
||||
rm('-rf', 'dist')
|
||||
src = 'linux-x86_64-legacy.rpm'
|
||||
writeSrc(src)
|
||||
replaceJSON((data) => {
|
||||
data.linux.target = ['rpm']
|
||||
})
|
||||
setChannel(src)
|
||||
await run(`${pb} --linux rpm`)
|
||||
await uploadToR2(src)
|
||||
renameDist()
|
||||
|
||||
echo('============')
|
||||
echo('Building AppImage package')
|
||||
echo('============')
|
||||
rm('-rf', 'dist')
|
||||
src = 'linux-x86_64-legacy.AppImage'
|
||||
writeSrc(src)
|
||||
replaceJSON((data) => {
|
||||
data.linux.target = ['AppImage']
|
||||
})
|
||||
setChannel(src)
|
||||
await run(`${pb} --linux`)
|
||||
await uploadToR2(src)
|
||||
renameDist()
|
||||
|
||||
echo('============')
|
||||
echo('Legacy Linux build process completed')
|
||||
echo('============')
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Build electerm for loong64 (LoongArch64) Linux - Legacy (old world)
|
||||
*
|
||||
* This script orchestrates the loong64 legacy build process:
|
||||
* 1. Build x64 version with legacy deps to get the asar
|
||||
* 2. Download electron loong64 from Loongnix FTP
|
||||
* 3. Cross-compile native modules for loong64 (node-pty@0.10.1, serialport@10.5.0)
|
||||
* 4. Merge x64 asar with loong64 electron and native modules
|
||||
* 5. Upload tar.gz to GitHub release draft
|
||||
* 6. Build deb package and upload to GitHub release draft
|
||||
*/
|
||||
|
||||
const { resolve } = require('path')
|
||||
const { echo } = require('shelljs')
|
||||
const { run } = require('./build-common')
|
||||
|
||||
const SCRIPT_PATH = resolve(__dirname, 'build-linux-loong64-legacy.sh')
|
||||
|
||||
async function main () {
|
||||
echo('Starting electerm loong64-legacy build...')
|
||||
|
||||
try {
|
||||
await run(`bash ${SCRIPT_PATH}`)
|
||||
echo('Loong64-legacy build completed successfully!')
|
||||
} catch (err) {
|
||||
console.error('Loong64-legacy build failed:', err.message)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
Executable
+559
@@ -0,0 +1,559 @@
|
||||
#!/bin/bash
|
||||
# Build electerm for loong64 (LoongArch64) Linux - Legacy (old world)
|
||||
#
|
||||
# Strategy:
|
||||
# 1. Build x64 version with legacy deps to get the asar
|
||||
# 2. Download electron loong64 from Loongnix FTP
|
||||
# 3. Cross-compile native modules for loong64 (node-pty@0.10.1, serialport@10.5.0)
|
||||
# 4. Merge x64 asar with loong64 electron and native modules
|
||||
# 5. Upload tar.gz to GitHub release draft
|
||||
# 6. Build deb package and upload to GitHub release draft
|
||||
#
|
||||
# Prerequisites:
|
||||
# - loongarch64-linux-gnu-g++ (cross-compiler, preferably GCC 8 from Loongnix)
|
||||
# - curl, tar, unzip
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
WORK_DIR="$PROJECT_ROOT/work-loong64-legacy"
|
||||
ELECTRON_VERSION="22.3.27"
|
||||
# Loongnix FTP electron download URL - update version as needed
|
||||
ELECTRON_LOONG64_URL="https://ftp.loongnix.cn/electron/LoongArch/v${ELECTRON_VERSION}/electron-v${ELECTRON_VERSION}-linux-loong64.zip"
|
||||
OUTPUT_DIR="$PROJECT_ROOT/dist-loong64-legacy"
|
||||
SKIP_NATIVE="${SKIP_NATIVE:-0}"
|
||||
WORKFLOW_NAME="${WORKFLOW_NAME:-linux-loong64-legacy}"
|
||||
|
||||
# Legacy native module versions
|
||||
NODE_PTY_VERSION="1.1.0"
|
||||
SERIALPORT_VERSION="10.5.0"
|
||||
|
||||
# GCC 8 from Loongnix (old-world ABI)
|
||||
GCC8_PREFIX="${GCC8_PREFIX:-/opt/gcc8-loong64}"
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
|
||||
|
||||
# ============================================================================
|
||||
# Helpers
|
||||
# ============================================================================
|
||||
get_version() {
|
||||
node -e "console.log(require('$PROJECT_ROOT/package.json').version)"
|
||||
}
|
||||
|
||||
write_install_src() {
|
||||
local src="$1"
|
||||
node -e "
|
||||
const { writeFileSync } = require('fs');
|
||||
writeFileSync('$PROJECT_ROOT/work/app/lib/install-src.js', \"module.exports = '${src}'\");
|
||||
"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Step 0: Install prerequisites
|
||||
# ============================================================================
|
||||
install_prerequisites() {
|
||||
log_info "Checking prerequisites..."
|
||||
|
||||
local SUDO="sudo"
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
SUDO=""
|
||||
fi
|
||||
|
||||
for cmd in curl tar unzip; do
|
||||
if ! command -v "$cmd" &>/dev/null; then
|
||||
$SUDO apt-get install -y "$cmd"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for GCC 8 from Loongnix
|
||||
if [ -d "$GCC8_PREFIX/bin" ]; then
|
||||
log_info "Using GCC 8 from Loongnix at $GCC8_PREFIX"
|
||||
export PATH="$GCC8_PREFIX/bin:$PATH"
|
||||
loongarch64-linux-gnu-gcc --version | head -1
|
||||
fi
|
||||
|
||||
log_info "Prerequisites ready."
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Step 1: Build x64 version with legacy deps
|
||||
# ============================================================================
|
||||
build_x64() {
|
||||
log_info "Step 1: Building x64 version with electron v${ELECTRON_VERSION}..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# Save original electron version
|
||||
local original_electron_version
|
||||
original_electron_version=$(node -e "console.log(require('./package.json').devDependencies.electron)")
|
||||
|
||||
# Temporarily set electron version
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
pkg.devDependencies.electron = '${ELECTRON_VERSION}';
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
||||
"
|
||||
|
||||
log_info "Running npm run b..."
|
||||
npm run b
|
||||
npm run pb
|
||||
|
||||
# Build x64 tar.gz
|
||||
rm -rf dist
|
||||
local builder="./node_modules/.bin/electron-builder"
|
||||
write_install_src "linux-loong64-legacy.tar.gz"
|
||||
|
||||
export WORKFLOW_NAME
|
||||
$builder --linux tar.gz --x64 --publish=never
|
||||
|
||||
# Find the built tar.gz
|
||||
local tar_gz
|
||||
tar_gz=$(find dist -name "*.tar.gz" -type f | head -1)
|
||||
if [ -z "$tar_gz" ]; then
|
||||
log_error "Could not find built tar.gz in dist/"
|
||||
exit 1
|
||||
fi
|
||||
log_info "x64 build complete: $tar_gz"
|
||||
|
||||
# Extract asar and full app directory from tar.gz
|
||||
mkdir -p "$WORK_DIR/x64-extract"
|
||||
tar xzf "$tar_gz" -C "$WORK_DIR/x64-extract"
|
||||
|
||||
local asar_file
|
||||
asar_file=$(find "$WORK_DIR/x64-extract" -name "app.asar" -type f | head -1)
|
||||
if [ -z "$asar_file" ]; then
|
||||
log_error "Could not find app.asar in extracted tar.gz"
|
||||
exit 1
|
||||
fi
|
||||
cp "$asar_file" "$WORK_DIR/app.asar"
|
||||
|
||||
local app_dir
|
||||
app_dir=$(find "$WORK_DIR/x64-extract" -maxdepth 1 -name "electerm*" -type d | head -1)
|
||||
if [ -n "$app_dir" ]; then
|
||||
cp -r "$app_dir" "$WORK_DIR/x64-app"
|
||||
fi
|
||||
|
||||
# Restore original electron version
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
pkg.devDependencies.electron = '${original_electron_version}';
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
||||
"
|
||||
|
||||
rm -rf "$WORK_DIR/x64-extract"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Step 2: Download electron loong64 from Loongnix
|
||||
# ============================================================================
|
||||
download_electron_loong64() {
|
||||
log_info "Step 2: Downloading electron v${ELECTRON_VERSION} for loong64 from Loongnix..."
|
||||
|
||||
mkdir -p "$WORK_DIR/electron-loong64"
|
||||
local zip_file="$WORK_DIR/electron-loong64.zip"
|
||||
|
||||
if [ ! -f "$zip_file" ]; then
|
||||
curl -L -o "$zip_file" "$ELECTRON_LOONG64_URL"
|
||||
fi
|
||||
|
||||
# Try to unzip, the archive may contain electron directly or in a subdirectory
|
||||
unzip -q -o "$zip_file" -d "$WORK_DIR/electron-loong64-raw"
|
||||
|
||||
# Find electron binary - may be at root or in a subdirectory
|
||||
if [ -f "$WORK_DIR/electron-loong64-raw/electron" ]; then
|
||||
cp -r "$WORK_DIR/electron-loong64-raw"/* "$WORK_DIR/electron-loong64/"
|
||||
else
|
||||
# Look for electron binary in subdirectory
|
||||
local electron_bin
|
||||
electron_bin=$(find "$WORK_DIR/electron-loong64-raw" -name "electron" -type f | head -1)
|
||||
if [ -n "$electron_bin" ]; then
|
||||
local subdir
|
||||
subdir=$(dirname "$electron_bin")
|
||||
cp -r "$subdir"/* "$WORK_DIR/electron-loong64/"
|
||||
fi
|
||||
fi
|
||||
rm -rf "$WORK_DIR/electron-loong64-raw"
|
||||
|
||||
if [ ! -f "$WORK_DIR/electron-loong64/electron" ]; then
|
||||
log_error "Could not find electron binary in extracted zip"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Electron loong64 downloaded and extracted."
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Step 3: Rebuild native modules for loong64 using cross-compilation
|
||||
# ============================================================================
|
||||
rebuild_native_modules() {
|
||||
if [ "$SKIP_NATIVE" = "1" ]; then
|
||||
log_warn "Step 3: Skipping native module rebuild (SKIP_NATIVE=1)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! command -v loongarch64-linux-gnu-g++ &>/dev/null; then
|
||||
log_warn "Step 3: loongarch64-linux-gnu-g++ not found, skipping native module rebuild"
|
||||
log_warn "Install with: sudo apt-get install g++-loongarch64-linux-gnu"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Step 3: Cross-compiling native modules for loong64 (legacy deps)..."
|
||||
log_info " node-pty@${NODE_PTY_VERSION}, serialport@${SERIALPORT_VERSION}"
|
||||
|
||||
local native_modules_dir="$WORK_DIR/native-modules-loong64"
|
||||
mkdir -p "$native_modules_dir"
|
||||
|
||||
local cross_build_dir="$WORK_DIR/cross-build"
|
||||
rm -rf "$cross_build_dir"
|
||||
mkdir -p "$cross_build_dir"
|
||||
cd "$cross_build_dir"
|
||||
|
||||
# Use Electron 22.3.27 headers which have:
|
||||
# - NODE_MODULE_VERSION 110 (matching Loongnix Electron)
|
||||
# - v8 10.8 headers (matching Electron's Chromium-based v8)
|
||||
# Standard Node 16 headers have v8 9.4 and ABI 93 - wrong for Electron.
|
||||
local headers_dir="$WORK_DIR/electron-headers"
|
||||
|
||||
log_info "Downloading Electron v${ELECTRON_VERSION} headers..."
|
||||
curl -sL "https://artifacts.electronjs.org/headers/dist/v${ELECTRON_VERSION}/node-v${ELECTRON_VERSION}-headers.tar.gz" -o "$WORK_DIR/electron-headers.tar.gz"
|
||||
mkdir -p "$headers_dir"
|
||||
tar xzf "$WORK_DIR/electron-headers.tar.gz" -C "$headers_dir" --strip-components=1
|
||||
log_info "Electron headers NODE_MODULE_VERSION: $(grep NODE_MODULE_VERSION "$headers_dir/include/node/node_version.h" | grep define | head -1)"
|
||||
|
||||
# Build node-pty (legacy version)
|
||||
log_info "Building node-pty@${NODE_PTY_VERSION} for loong64..."
|
||||
log_info "CC=loongarch64-linux-gnu-gcc CXX=loongarch64-linux-gnu-g++"
|
||||
log_info "npm_config_nodedir=$headers_dir"
|
||||
log_info "npm_config_arch=loong64 npm_config_target_arch=loong64"
|
||||
mkdir -p build-node-pty && cd build-node-pty
|
||||
npm init -y 2>/dev/null
|
||||
npm_config_nodedir="$headers_dir" npm_config_arch=loong64 npm_config_target_arch=loong64 CC=loongarch64-linux-gnu-gcc CXX=loongarch64-linux-gnu-g++ npm install node-pty@${NODE_PTY_VERSION} --build-from-source 2>&1 | tee /tmp/node-pty-build.log | tail -30
|
||||
log_info "Full node-pty build log saved to /tmp/node-pty-build.log"
|
||||
find . -name "pty.node" -type f | while read f; do
|
||||
log_info "Found pty.node: $f"
|
||||
file "$f"
|
||||
log_info "ELF headers:"
|
||||
readelf -h "$f" 2>/dev/null | grep -E "Machine|Class" || true
|
||||
log_info "Dynamic dependencies:"
|
||||
readelf -d "$f" 2>/dev/null | grep NEEDED || true
|
||||
log_info "Undefined symbols:"
|
||||
readelf -s "$f" 2>/dev/null | grep "UND" | grep -v "UND$" | head -20 || true
|
||||
cp "$f" "$native_modules_dir/node-pty.node"
|
||||
done
|
||||
cd "$cross_build_dir"
|
||||
|
||||
# Build @serialport/bindings-cpp (legacy serialport@10.5.0)
|
||||
log_info "Building serialport@${SERIALPORT_VERSION} bindings for loong64..."
|
||||
mkdir -p build-serialport && cd build-serialport
|
||||
npm init -y 2>/dev/null
|
||||
npm_config_nodedir="$headers_dir" npm_config_arch=loong64 npm_config_target_arch=loong64 CC=loongarch64-linux-gnu-gcc CXX=loongarch64-linux-gnu-g++ npm install @serialport/bindings-cpp --build-from-source 2>&1 | tee /tmp/serialport-build.log | tail -30
|
||||
find . -path "*/build/Release/bindings.node" -type f | while read f; do
|
||||
log_info "Found bindings.node: $f"
|
||||
file "$f"
|
||||
log_info "ELF headers:"
|
||||
readelf -h "$f" 2>/dev/null | grep -E "Machine|Class" || true
|
||||
cp "$f" "$native_modules_dir/serialport-bindings.node"
|
||||
done
|
||||
cd "$cross_build_dir"
|
||||
|
||||
unset CC CXX AR RANLIB LINK GYP_DEFINES npm_config_arch npm_config_target_arch npm_config_nodedir
|
||||
|
||||
# Verify native modules are LoongArch64 ELF binaries
|
||||
if [ -d "$native_modules_dir" ] && [ "$(ls -A "$native_modules_dir" 2>/dev/null)" ]; then
|
||||
log_info "Native modules built successfully:"
|
||||
ls -la "$native_modules_dir/"
|
||||
for mod in "$native_modules_dir"/*.node; do
|
||||
log_info "Verifying $mod:"
|
||||
file "$mod"
|
||||
if ! file "$mod" | grep -q "LoongArch"; then
|
||||
log_error "WARNING: $mod does not appear to be a LoongArch binary!"
|
||||
fi
|
||||
done
|
||||
else
|
||||
log_warn "No native modules were built."
|
||||
fi
|
||||
|
||||
if [ -d "$native_modules_dir" ] && [ "$(ls -A "$native_modules_dir" 2>/dev/null)" ]; then
|
||||
log_info "Native modules built successfully:"
|
||||
ls -la "$native_modules_dir/"
|
||||
else
|
||||
log_warn "No native modules were built."
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Step 4: Merge x64 asar with loong64 electron and native modules
|
||||
# ============================================================================
|
||||
merge_loong64() {
|
||||
log_info "Step 4: Merging x64 asar with loong64 electron..."
|
||||
|
||||
local electerm_version
|
||||
electerm_version=$(get_version)
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
local output_name="electerm-${electerm_version}-linux-loong64-legacy"
|
||||
local output_dir="$OUTPUT_DIR/$output_name"
|
||||
rm -rf "$output_dir"
|
||||
mkdir -p "$output_dir"
|
||||
|
||||
# Start with x64 app directory
|
||||
if [ -d "$WORK_DIR/x64-app" ]; then
|
||||
cp -r "$WORK_DIR/x64-app"/* "$output_dir/"
|
||||
fi
|
||||
|
||||
# Replace x64 binary with loong64 electron
|
||||
rm -f "$output_dir/electerm"
|
||||
cp "$WORK_DIR/electron-loong64/electron" "$output_dir/electerm"
|
||||
chmod +x "$output_dir/electerm"
|
||||
|
||||
# Copy loong64 electron runtime files (libraries, locales, etc.)
|
||||
for f in "$WORK_DIR/electron-loong64"/*; do
|
||||
local base
|
||||
base=$(basename "$f")
|
||||
if [ "$base" = "electron" ]; then
|
||||
continue
|
||||
fi
|
||||
if [ -d "$f" ] && [ "$base" != "locales" ]; then
|
||||
continue
|
||||
fi
|
||||
if [ -d "$f" ]; then
|
||||
cp -r "$f" "$output_dir/"
|
||||
else
|
||||
cp "$f" "$output_dir/"
|
||||
fi
|
||||
done
|
||||
|
||||
# Replace native modules with loong64 versions
|
||||
local native_modules_dir="$WORK_DIR/native-modules-loong64"
|
||||
if [ -d "$native_modules_dir" ] && [ "$(ls -A "$native_modules_dir" 2>/dev/null)" ]; then
|
||||
log_info "Replacing native modules with loong64 versions..."
|
||||
if [ -f "$native_modules_dir/node-pty.node" ]; then
|
||||
find "$output_dir" -path "*/node-pty/build/Release/*.node" -exec cp "$native_modules_dir/node-pty.node" {} \; 2>/dev/null || true
|
||||
fi
|
||||
if [ -f "$native_modules_dir/serialport-bindings.node" ]; then
|
||||
find "$output_dir" -path "*@serialport/bindings-cpp*" -name "*.node" -exec cp "$native_modules_dir/serialport-bindings.node" {} \; 2>/dev/null || true
|
||||
fi
|
||||
if [ -f "$native_modules_dir/ssh2-crypto.node" ]; then
|
||||
find "$output_dir" -path "*@electerm/ssh2*" -name "*.node" -exec cp "$native_modules_dir/ssh2-crypto.node" {} \; 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Ensure asar is in place
|
||||
if [ -f "$WORK_DIR/app.asar" ] && [ ! -f "$output_dir/resources/app.asar" ]; then
|
||||
mkdir -p "$output_dir/resources"
|
||||
cp "$WORK_DIR/app.asar" "$output_dir/resources/"
|
||||
fi
|
||||
|
||||
# Create tar.gz
|
||||
log_info "Creating loong64-legacy tar.gz..."
|
||||
cd "$OUTPUT_DIR"
|
||||
tar czf "${output_name}.tar.gz" "$output_name"
|
||||
|
||||
log_info "Tar.gz complete: $OUTPUT_DIR/${output_name}.tar.gz"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Step 5: Build deb package
|
||||
# ============================================================================
|
||||
patch_asar_install_src() {
|
||||
local asar_file="$1"
|
||||
local new_src="$2"
|
||||
|
||||
log_info "Patching asar install-src to '${new_src}'..."
|
||||
|
||||
local asar_bin="$PROJECT_ROOT/node_modules/@electron/asar/bin/asar.js"
|
||||
local tmp_dir
|
||||
tmp_dir=$(mktemp -d)
|
||||
|
||||
# Extract, patch, repack using CLI
|
||||
node "$asar_bin" extract "$asar_file" "$tmp_dir"
|
||||
echo "module.exports = '${new_src}'" > "$tmp_dir/lib/install-src.js"
|
||||
node "$asar_bin" pack "$tmp_dir" "$asar_file"
|
||||
|
||||
rm -rf "$tmp_dir"
|
||||
log_info "Asar patched successfully."
|
||||
}
|
||||
|
||||
build_deb() {
|
||||
local install_src="${1:-linux-loong64-legacy.deb}"
|
||||
local deb_arch="${2:-loong64}"
|
||||
local suffix="$deb_arch"
|
||||
log_info "Step 5: Building ${suffix} deb package..."
|
||||
|
||||
local electerm_version
|
||||
electerm_version=$(get_version)
|
||||
|
||||
local output_dir="$OUTPUT_DIR/electerm-${electerm_version}-linux-loong64-legacy"
|
||||
local deb_build="$OUTPUT_DIR/deb-build-${suffix}"
|
||||
local deb_name="electerm_${electerm_version}_loongarch64"
|
||||
local deb_dir="$deb_build/$deb_name"
|
||||
|
||||
if [ ! -d "$output_dir" ]; then
|
||||
log_error "Output directory not found: $output_dir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Patch asar to use deb install-src
|
||||
local asar_file="$output_dir/resources/app.asar"
|
||||
if [ -f "$asar_file" ]; then
|
||||
patch_asar_install_src "$asar_file" "$install_src"
|
||||
else
|
||||
log_warn "app.asar not found at $asar_file, skipping install-src patch"
|
||||
fi
|
||||
|
||||
rm -rf "$deb_build"
|
||||
mkdir -p "$deb_dir/DEBIAN"
|
||||
mkdir -p "$deb_dir/opt/electerm"
|
||||
mkdir -p "$deb_dir/usr/share/applications"
|
||||
mkdir -p "$deb_dir/usr/share/icons/hicolor/128x128/apps"
|
||||
|
||||
cp -r "$output_dir"/* "$deb_dir/opt/electerm/"
|
||||
|
||||
# Install icon
|
||||
local icon_src="$PROJECT_ROOT/node_modules/@electerm/electerm-resource/res/imgs/electerm-round-128x128.png"
|
||||
if [ -f "$icon_src" ]; then
|
||||
cp "$icon_src" "$deb_dir/usr/share/icons/hicolor/128x128/apps/electerm.png"
|
||||
fi
|
||||
|
||||
# Install desktop file
|
||||
cat > "$deb_dir/usr/share/applications/electerm.desktop" <<'DESKTOP'
|
||||
[Desktop Entry]
|
||||
Name=electerm
|
||||
Comment=Terminal/SSH/SFTP client
|
||||
Exec=/opt/electerm/electerm %U
|
||||
Icon=electerm
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Development;System;TerminalEmulator;
|
||||
StartupWMClass=electerm
|
||||
MimeType=x-scheme-handler/ssh;x-scheme-handler/telnet;x-scheme-handler/rdp;x-scheme-handler/vnc;x-scheme-handler/serial;x-scheme-handler/spice;x-scheme-handler/electerm;
|
||||
DESKTOP
|
||||
|
||||
cat > "$deb_dir/DEBIAN/control" <<CTRL
|
||||
Package: electerm
|
||||
Version: ${electerm_version}
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: ${deb_arch}
|
||||
Depends: libglib2.0-0, libnss3, libnspr4, libdbus-1-3, libatk1.0-0, libatk-bridge2.0-0, libcups2, libcairo2, libpango-1.0-0, libx11-6, libxcomposite1, libxdamage1, libxext6, libxfixes3, libxrandr2, libxkbcommon0, libdrm2, libgbm1, libatspi2.0-0, libpulse0, libgtk-3-0
|
||||
Maintainer: ZHAO Xudong <zxdong@gmail.com>
|
||||
Description: Open-sourced terminal/ssh/sftp/telnet/serialport/RDP/VNC/Spice/ftp client
|
||||
CTRL
|
||||
|
||||
cat > "$deb_dir/DEBIAN/postinst" <<'POSTINST'
|
||||
#!/bin/bash
|
||||
chown root:root /opt/electerm/chrome-sandbox
|
||||
chmod 4755 /opt/electerm/chrome-sandbox
|
||||
update-desktop-database /usr/share/applications/ 2>/dev/null || true
|
||||
gtk-update-icon-cache /usr/share/icons/hicolor/ 2>/dev/null || true
|
||||
POSTINST
|
||||
chmod 755 "$deb_dir/DEBIAN/postinst"
|
||||
|
||||
local deb_file="$OUTPUT_DIR/electerm-${electerm_version}-linux-${suffix}-legacy.deb"
|
||||
# Use gzip compression for compatibility with dpkg < 1.21.18 (e.g. Loongnix 20 has dpkg 1.19.7)
|
||||
if command -v fakeroot &>/dev/null; then
|
||||
fakeroot dpkg-deb -Zgzip --root-owner-group --build "$deb_dir" "$deb_file"
|
||||
else
|
||||
dpkg-deb -Zgzip --root-owner-group --build "$deb_dir" "$deb_file"
|
||||
fi
|
||||
|
||||
if [ ! -f "$deb_file" ]; then
|
||||
log_error "Deb build failed: $deb_file not created"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Deb complete: $deb_file"
|
||||
|
||||
rm -rf "$deb_build"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Step 6: Upload to GitHub release draft
|
||||
# ============================================================================
|
||||
upload_to_github() {
|
||||
local file="$1"
|
||||
local filename
|
||||
filename=$(basename "$file")
|
||||
|
||||
if [ ! -f "$file" ]; then
|
||||
log_warn "Upload skipped: file not found: $file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -z "${GH_TOKEN:-}" ]; then
|
||||
log_warn "Upload skipped: GH_TOKEN not set"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local electerm_version
|
||||
electerm_version=$(get_version)
|
||||
|
||||
local release_name="v${electerm_version}"
|
||||
|
||||
log_info "Uploading $filename to GitHub release draft '${release_name}'..."
|
||||
|
||||
if ! gh release view "$release_name" >/dev/null 2>&1; then
|
||||
gh release create "$release_name" \
|
||||
--draft \
|
||||
--title "$release_name" \
|
||||
--generate-notes \
|
||||
"$file"
|
||||
else
|
||||
gh release upload "$release_name" "$file" --clobber
|
||||
fi
|
||||
|
||||
log_info "Uploaded $filename."
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main
|
||||
# ============================================================================
|
||||
main() {
|
||||
log_info "Starting electerm loong64-legacy build..."
|
||||
log_info "Project root: $PROJECT_ROOT"
|
||||
log_info "Work directory: $WORK_DIR"
|
||||
log_info "Output directory: $OUTPUT_DIR"
|
||||
log_info "Electron version: $ELECTRON_VERSION"
|
||||
log_info "node-pty: $NODE_PTY_VERSION, serialport: $SERIALPORT_VERSION"
|
||||
|
||||
rm -rf "$WORK_DIR"
|
||||
mkdir -p "$WORK_DIR"
|
||||
|
||||
install_prerequisites
|
||||
build_x64
|
||||
download_electron_loong64
|
||||
rebuild_native_modules
|
||||
merge_loong64
|
||||
|
||||
local electerm_version
|
||||
electerm_version=$(get_version)
|
||||
|
||||
# Upload tar.gz
|
||||
upload_to_github "$OUTPUT_DIR/electerm-${electerm_version}-linux-loong64-legacy.tar.gz"
|
||||
|
||||
# Build and upload debs
|
||||
build_deb "linux-loong64-legacy.deb" "loong64"
|
||||
upload_to_github "$OUTPUT_DIR/electerm-${electerm_version}-linux-loong64-legacy.deb"
|
||||
|
||||
build_deb "linux-loongarch64-legacy.deb" "loongarch64"
|
||||
upload_to_github "$OUTPUT_DIR/electerm-${electerm_version}-linux-loongarch64-legacy.deb"
|
||||
|
||||
log_info "=========================================="
|
||||
log_info "Build complete!"
|
||||
log_info " $OUTPUT_DIR/electerm-${electerm_version}-linux-loong64-legacy.tar.gz"
|
||||
log_info " $OUTPUT_DIR/electerm-${electerm_version}-linux-loong64-legacy.deb"
|
||||
log_info " $OUTPUT_DIR/electerm-${electerm_version}-linux-loongarch64-legacy.deb"
|
||||
log_info "=========================================="
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Build electerm for loong64 (LoongArch64) Linux
|
||||
*
|
||||
* This script orchestrates the loong64 build process:
|
||||
* 1. Build x64 version with electron v39.2.7 to get the asar
|
||||
* 2. Download electron loong64 v39.2.7
|
||||
* 3. Cross-compile native modules for loong64
|
||||
* 4. Merge x64 asar with loong64 electron and native modules
|
||||
* 5. Upload tar.gz to GitHub release draft
|
||||
* 6. Build deb package (patched asar) and upload to GitHub release draft
|
||||
*/
|
||||
|
||||
const { resolve } = require('path')
|
||||
const { echo } = require('shelljs')
|
||||
const { run } = require('./build-common')
|
||||
|
||||
const SCRIPT_PATH = resolve(__dirname, 'build-linux-loong64.sh')
|
||||
|
||||
async function main () {
|
||||
echo('Starting electerm loong64 build...')
|
||||
|
||||
try {
|
||||
await run(`bash ${SCRIPT_PATH}`)
|
||||
echo('Loong64 build completed successfully!')
|
||||
} catch (err) {
|
||||
console.error('Loong64 build failed:', err.message)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
Executable
+481
@@ -0,0 +1,481 @@
|
||||
#!/bin/bash
|
||||
# Build electerm for loong64 (LoongArch64) Linux
|
||||
#
|
||||
# Strategy:
|
||||
# 1. Build x64 version with electron v39.2.7 to get the asar
|
||||
# 2. Download electron loong64 v39.2.7 from darkyzhou/electron-loong64
|
||||
# 3. Cross-compile native modules for loong64
|
||||
# 4. Merge x64 asar with loong64 electron and native modules
|
||||
# 5. Upload tar.gz to GitHub release draft
|
||||
# 6. Build deb packages (loong64 and loongarch64, patched asar) and upload to GitHub release draft
|
||||
#
|
||||
# Prerequisites:
|
||||
# - loongarch64-linux-gnu-g++ (for native module cross-compilation)
|
||||
# - curl, tar, unzip
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
WORK_DIR="$PROJECT_ROOT/work-loong64"
|
||||
ELECTRON_VERSION="39.2.7"
|
||||
ELECTRON_LOONG64_URL="https://github.com/darkyzhou/electron-loong64/releases/download/v${ELECTRON_VERSION}/electron-v${ELECTRON_VERSION}-linux-loong64.zip"
|
||||
OUTPUT_DIR="$PROJECT_ROOT/dist-loong64"
|
||||
SKIP_NATIVE="${SKIP_NATIVE:-0}" # Set to 1 to skip native module rebuild
|
||||
WORKFLOW_NAME="${WORKFLOW_NAME:-linux-loong64}" # For electron-builder publish channel
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
|
||||
|
||||
# ============================================================================
|
||||
# Helpers
|
||||
# ============================================================================
|
||||
get_version() {
|
||||
node -e "console.log(require('$PROJECT_ROOT/package.json').version)"
|
||||
}
|
||||
|
||||
write_install_src() {
|
||||
local src="$1"
|
||||
node -e "
|
||||
const { writeFileSync } = require('fs');
|
||||
writeFileSync('$PROJECT_ROOT/work/app/lib/install-src.js', \"module.exports = '${src}'\");
|
||||
"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Step 0: Install prerequisites
|
||||
# ============================================================================
|
||||
install_prerequisites() {
|
||||
log_info "Checking prerequisites..."
|
||||
|
||||
local SUDO="sudo"
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
SUDO=""
|
||||
fi
|
||||
|
||||
for cmd in curl tar unzip; do
|
||||
if ! command -v "$cmd" &>/dev/null; then
|
||||
$SUDO apt-get install -y "$cmd"
|
||||
fi
|
||||
done
|
||||
|
||||
log_info "Prerequisites ready."
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Step 1: Build x64 version with electron v39.2.7
|
||||
# ============================================================================
|
||||
build_x64() {
|
||||
log_info "Step 1: Building x64 version with electron v${ELECTRON_VERSION}..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# Save original electron version
|
||||
local original_electron_version
|
||||
original_electron_version=$(node -e "console.log(require('./package.json').devDependencies.electron)")
|
||||
|
||||
# Temporarily set electron version
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
pkg.devDependencies.electron = '${ELECTRON_VERSION}';
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
||||
"
|
||||
|
||||
log_info "Running npm run b..."
|
||||
npm run b
|
||||
npm run pb
|
||||
|
||||
# Build x64 tar.gz
|
||||
rm -rf dist
|
||||
local builder="./node_modules/.bin/electron-builder"
|
||||
write_install_src "linux-loong64.tar.gz"
|
||||
|
||||
export WORKFLOW_NAME
|
||||
$builder --linux tar.gz --x64 --publish=never
|
||||
|
||||
# Find the built tar.gz
|
||||
local tar_gz
|
||||
tar_gz=$(find dist -name "*.tar.gz" -type f | head -1)
|
||||
if [ -z "$tar_gz" ]; then
|
||||
log_error "Could not find built tar.gz in dist/"
|
||||
exit 1
|
||||
fi
|
||||
log_info "x64 build complete: $tar_gz"
|
||||
|
||||
# Extract asar and full app directory from tar.gz
|
||||
mkdir -p "$WORK_DIR/x64-extract"
|
||||
tar xzf "$tar_gz" -C "$WORK_DIR/x64-extract"
|
||||
|
||||
local asar_file
|
||||
asar_file=$(find "$WORK_DIR/x64-extract" -name "app.asar" -type f | head -1)
|
||||
if [ -z "$asar_file" ]; then
|
||||
log_error "Could not find app.asar in extracted tar.gz"
|
||||
exit 1
|
||||
fi
|
||||
cp "$asar_file" "$WORK_DIR/app.asar"
|
||||
|
||||
local app_dir
|
||||
app_dir=$(find "$WORK_DIR/x64-extract" -maxdepth 1 -name "electerm*" -type d | head -1)
|
||||
if [ -n "$app_dir" ]; then
|
||||
cp -r "$app_dir" "$WORK_DIR/x64-app"
|
||||
fi
|
||||
|
||||
# Restore original electron version
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
pkg.devDependencies.electron = '${original_electron_version}';
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
||||
"
|
||||
|
||||
rm -rf "$WORK_DIR/x64-extract"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Step 2: Download electron loong64
|
||||
# ============================================================================
|
||||
download_electron_loong64() {
|
||||
log_info "Step 2: Downloading electron v${ELECTRON_VERSION} for loong64..."
|
||||
|
||||
mkdir -p "$WORK_DIR/electron-loong64"
|
||||
local zip_file="$WORK_DIR/electron-loong64.zip"
|
||||
|
||||
if [ ! -f "$zip_file" ]; then
|
||||
curl -L -o "$zip_file" "$ELECTRON_LOONG64_URL"
|
||||
fi
|
||||
|
||||
unzip -q -o "$zip_file" -d "$WORK_DIR/electron-loong64"
|
||||
|
||||
if [ ! -f "$WORK_DIR/electron-loong64/electron" ]; then
|
||||
log_error "Could not find electron binary in extracted zip"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Electron loong64 downloaded and extracted."
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Step 3: Rebuild native modules for loong64 using cross-compilation
|
||||
# ============================================================================
|
||||
rebuild_native_modules() {
|
||||
if [ "$SKIP_NATIVE" = "1" ]; then
|
||||
log_warn "Step 3: Skipping native module rebuild (SKIP_NATIVE=1)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! command -v loongarch64-linux-gnu-g++ &>/dev/null; then
|
||||
log_warn "Step 3: loongarch64-linux-gnu-g++ not found, skipping native module rebuild"
|
||||
log_warn "Install with: sudo apt-get install g++-loongarch64-linux-gnu"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Step 3: Cross-compiling native modules for loong64..."
|
||||
|
||||
local native_modules_dir="$WORK_DIR/native-modules-loong64"
|
||||
mkdir -p "$native_modules_dir"
|
||||
|
||||
local cross_build_dir="$WORK_DIR/cross-build"
|
||||
rm -rf "$cross_build_dir"
|
||||
mkdir -p "$cross_build_dir"
|
||||
cd "$cross_build_dir"
|
||||
|
||||
# Build node-pty
|
||||
log_info "Building node-pty for loong64..."
|
||||
mkdir -p build-node-pty && cd build-node-pty
|
||||
npm init -y 2>/dev/null
|
||||
npm_config_arch=loong64 npm_config_target_arch=loong64 CC=loongarch64-linux-gnu-gcc CXX=loongarch64-linux-gnu-g++ npm install node-pty@1.1.0-beta34 --build-from-source 2>&1 | tail -5
|
||||
find . -name "pty.node" -type f | while read f; do
|
||||
cp "$f" "$native_modules_dir/node-pty.node"
|
||||
done
|
||||
cd "$cross_build_dir"
|
||||
|
||||
# Build @serialport/bindings-cpp
|
||||
log_info "Building @serialport/bindings-cpp for loong64..."
|
||||
mkdir -p build-serialport && cd build-serialport
|
||||
npm init -y 2>/dev/null
|
||||
npm_config_arch=loong64 npm_config_target_arch=loong64 CC=loongarch64-linux-gnu-gcc CXX=loongarch64-linux-gnu-g++ npm install @serialport/bindings-cpp --build-from-source 2>&1 | tail -5
|
||||
find . -path "*/build/Release/bindings.node" -type f | while read f; do
|
||||
cp "$f" "$native_modules_dir/serialport-bindings.node"
|
||||
done
|
||||
cd "$cross_build_dir"
|
||||
|
||||
unset CC CXX AR RANLIB LINK GYP_DEFINES npm_config_arch npm_config_target_arch
|
||||
|
||||
if [ -d "$native_modules_dir" ] && [ "$(ls -A "$native_modules_dir" 2>/dev/null)" ]; then
|
||||
log_info "Native modules built successfully:"
|
||||
ls -la "$native_modules_dir/"
|
||||
else
|
||||
log_warn "No native modules were built."
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Step 4: Merge x64 asar with loong64 electron and native modules
|
||||
# ============================================================================
|
||||
merge_loong64() {
|
||||
log_info "Step 4: Merging x64 asar with loong64 electron..."
|
||||
|
||||
local electerm_version
|
||||
electerm_version=$(get_version)
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
local output_name="electerm-${electerm_version}-linux-loong64"
|
||||
local output_dir="$OUTPUT_DIR/$output_name"
|
||||
rm -rf "$output_dir"
|
||||
mkdir -p "$output_dir"
|
||||
|
||||
# Start with x64 app directory
|
||||
if [ -d "$WORK_DIR/x64-app" ]; then
|
||||
cp -r "$WORK_DIR/x64-app"/* "$output_dir/"
|
||||
fi
|
||||
|
||||
# Replace x64 binary with loong64 electron
|
||||
rm -f "$output_dir/electerm"
|
||||
cp "$WORK_DIR/electron-loong64/electron" "$output_dir/electerm"
|
||||
chmod +x "$output_dir/electerm"
|
||||
|
||||
# Copy loong64 electron runtime files (libraries, locales, etc.)
|
||||
for f in "$WORK_DIR/electron-loong64"/*; do
|
||||
local base
|
||||
base=$(basename "$f")
|
||||
if [ "$base" = "electron" ]; then
|
||||
continue
|
||||
fi
|
||||
if [ -d "$f" ] && [ "$base" != "locales" ]; then
|
||||
continue
|
||||
fi
|
||||
if [ -d "$f" ]; then
|
||||
cp -r "$f" "$output_dir/"
|
||||
else
|
||||
cp "$f" "$output_dir/"
|
||||
fi
|
||||
done
|
||||
|
||||
# Replace native modules with loong64 versions
|
||||
local native_modules_dir="$WORK_DIR/native-modules-loong64"
|
||||
if [ -d "$native_modules_dir" ] && [ "$(ls -A "$native_modules_dir" 2>/dev/null)" ]; then
|
||||
log_info "Replacing native modules with loong64 versions..."
|
||||
if [ -f "$native_modules_dir/node-pty.node" ]; then
|
||||
find "$output_dir" -path "*/node-pty/build/Release/*.node" -exec cp "$native_modules_dir/node-pty.node" {} \; 2>/dev/null || true
|
||||
fi
|
||||
if [ -f "$native_modules_dir/serialport-bindings.node" ]; then
|
||||
find "$output_dir" -path "*@serialport/bindings-cpp*" -name "*.node" -exec cp "$native_modules_dir/serialport-bindings.node" {} \; 2>/dev/null || true
|
||||
fi
|
||||
if [ -f "$native_modules_dir/ssh2-crypto.node" ]; then
|
||||
find "$output_dir" -path "*@electerm/ssh2*" -name "*.node" -exec cp "$native_modules_dir/ssh2-crypto.node" {} \; 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Ensure asar is in place
|
||||
if [ -f "$WORK_DIR/app.asar" ] && [ ! -f "$output_dir/resources/app.asar" ]; then
|
||||
mkdir -p "$output_dir/resources"
|
||||
cp "$WORK_DIR/app.asar" "$output_dir/resources/"
|
||||
fi
|
||||
|
||||
# Create tar.gz
|
||||
log_info "Creating loong64 tar.gz..."
|
||||
cd "$OUTPUT_DIR"
|
||||
tar czf "${output_name}.tar.gz" "$output_name"
|
||||
|
||||
log_info "Tar.gz complete: $OUTPUT_DIR/${output_name}.tar.gz"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Step 5: Build deb package
|
||||
# ============================================================================
|
||||
patch_asar_install_src() {
|
||||
local asar_file="$1"
|
||||
local new_src="$2"
|
||||
|
||||
log_info "Patching asar install-src to '${new_src}'..."
|
||||
|
||||
local asar_bin="$PROJECT_ROOT/node_modules/@electron/asar/bin/asar.js"
|
||||
local tmp_dir
|
||||
tmp_dir=$(mktemp -d)
|
||||
|
||||
# Extract, patch, repack using CLI
|
||||
node "$asar_bin" extract "$asar_file" "$tmp_dir"
|
||||
echo "module.exports = '${new_src}'" > "$tmp_dir/lib/install-src.js"
|
||||
node "$asar_bin" pack "$tmp_dir" "$asar_file"
|
||||
|
||||
rm -rf "$tmp_dir"
|
||||
log_info "Asar patched successfully."
|
||||
}
|
||||
|
||||
build_deb() {
|
||||
local install_src="${1:-linux-loong64.deb}"
|
||||
local deb_arch="${2:-loong64}"
|
||||
local suffix="$deb_arch"
|
||||
log_info "Step 5: Building ${suffix} deb package..."
|
||||
|
||||
local electerm_version
|
||||
electerm_version=$(get_version)
|
||||
|
||||
local output_dir="$OUTPUT_DIR/electerm-${electerm_version}-linux-loong64"
|
||||
local deb_build="$OUTPUT_DIR/deb-build-${suffix}"
|
||||
local deb_name="electerm_${electerm_version}_loongarch64"
|
||||
local deb_dir="$deb_build/$deb_name"
|
||||
|
||||
if [ ! -d "$output_dir" ]; then
|
||||
log_error "Output directory not found: $output_dir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Patch asar to use deb install-src
|
||||
local asar_file="$output_dir/resources/app.asar"
|
||||
if [ -f "$asar_file" ]; then
|
||||
patch_asar_install_src "$asar_file" "$install_src"
|
||||
else
|
||||
log_warn "app.asar not found at $asar_file, skipping install-src patch"
|
||||
fi
|
||||
|
||||
rm -rf "$deb_build"
|
||||
mkdir -p "$deb_dir/DEBIAN"
|
||||
mkdir -p "$deb_dir/opt/electerm"
|
||||
mkdir -p "$deb_dir/usr/share/applications"
|
||||
mkdir -p "$deb_dir/usr/share/icons/hicolor/128x128/apps"
|
||||
|
||||
cp -r "$output_dir"/* "$deb_dir/opt/electerm/"
|
||||
|
||||
# Install icon
|
||||
local icon_src="$PROJECT_ROOT/node_modules/@electerm/electerm-resource/res/imgs/electerm-round-128x128.png"
|
||||
if [ -f "$icon_src" ]; then
|
||||
cp "$icon_src" "$deb_dir/usr/share/icons/hicolor/128x128/apps/electerm.png"
|
||||
fi
|
||||
|
||||
# Install desktop file
|
||||
cat > "$deb_dir/usr/share/applications/electerm.desktop" <<'DESKTOP'
|
||||
[Desktop Entry]
|
||||
Name=electerm
|
||||
Comment=Terminal/SSH/SFTP client
|
||||
Exec=/opt/electerm/electerm %U
|
||||
Icon=electerm
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Development;System;TerminalEmulator;
|
||||
StartupWMClass=electerm
|
||||
MimeType=x-scheme-handler/ssh;x-scheme-handler/telnet;x-scheme-handler/rdp;x-scheme-handler/vnc;x-scheme-handler/serial;x-scheme-handler/spice;x-scheme-handler/electerm;
|
||||
DESKTOP
|
||||
|
||||
cat > "$deb_dir/DEBIAN/control" <<CTRL
|
||||
Package: electerm
|
||||
Version: ${electerm_version}
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: ${deb_arch}
|
||||
Depends: libglib2.0-0, libnss3, libnspr4, libdbus-1-3, libatk1.0-0, libatk-bridge2.0-0, libcups2, libcairo2, libpango-1.0-0, libx11-6, libxcomposite1, libxdamage1, libxext6, libxfixes3, libxrandr2, libxkbcommon0, libdrm2, libgbm1, libatspi2.0-0, libpulse0, libgtk-3-0
|
||||
Maintainer: ZHAO Xudong <zxdong@gmail.com>
|
||||
Description: Open-sourced terminal/ssh/sftp/telnet/serialport/RDP/VNC/Spice/ftp client
|
||||
CTRL
|
||||
|
||||
cat > "$deb_dir/DEBIAN/postinst" <<'POSTINST'
|
||||
#!/bin/bash
|
||||
chown root:root /opt/electerm/chrome-sandbox
|
||||
chmod 4755 /opt/electerm/chrome-sandbox
|
||||
update-desktop-database /usr/share/applications/ 2>/dev/null || true
|
||||
gtk-update-icon-cache /usr/share/icons/hicolor/ 2>/dev/null || true
|
||||
POSTINST
|
||||
chmod 755 "$deb_dir/DEBIAN/postinst"
|
||||
|
||||
local deb_file="$OUTPUT_DIR/electerm-${electerm_version}-linux-${suffix}.deb"
|
||||
if command -v fakeroot &>/dev/null; then
|
||||
fakeroot dpkg-deb --build "$deb_dir" "$deb_file"
|
||||
else
|
||||
dpkg-deb --build "$deb_dir" "$deb_file"
|
||||
fi
|
||||
|
||||
if [ ! -f "$deb_file" ]; then
|
||||
log_error "Deb build failed: $deb_file not created"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Deb complete: $deb_file"
|
||||
|
||||
rm -rf "$deb_build"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Step 6: Upload to GitHub release draft
|
||||
# ============================================================================
|
||||
upload_to_github() {
|
||||
local file="$1"
|
||||
local filename
|
||||
filename=$(basename "$file")
|
||||
|
||||
if [ ! -f "$file" ]; then
|
||||
log_warn "Upload skipped: file not found: $file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -z "${GH_TOKEN:-}" ]; then
|
||||
log_warn "Upload skipped: GH_TOKEN not set"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local electerm_version
|
||||
electerm_version=$(get_version)
|
||||
|
||||
local release_name="v${electerm_version}"
|
||||
|
||||
log_info "Uploading $filename to GitHub release draft '${release_name}'..."
|
||||
|
||||
if ! gh release view "$release_name" >/dev/null 2>&1; then
|
||||
gh release create "$release_name" \
|
||||
--draft \
|
||||
--title "$release_name" \
|
||||
--generate-notes \
|
||||
"$file"
|
||||
else
|
||||
gh release upload "$release_name" "$file" --clobber
|
||||
fi
|
||||
|
||||
log_info "Uploaded $filename."
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main
|
||||
# ============================================================================
|
||||
main() {
|
||||
log_info "Starting electerm loong64 build..."
|
||||
log_info "Project root: $PROJECT_ROOT"
|
||||
log_info "Work directory: $WORK_DIR"
|
||||
log_info "Output directory: $OUTPUT_DIR"
|
||||
|
||||
rm -rf "$WORK_DIR"
|
||||
mkdir -p "$WORK_DIR"
|
||||
|
||||
install_prerequisites
|
||||
build_x64
|
||||
download_electron_loong64
|
||||
rebuild_native_modules
|
||||
merge_loong64
|
||||
|
||||
local electerm_version
|
||||
electerm_version=$(get_version)
|
||||
|
||||
# Upload tar.gz
|
||||
upload_to_github "$OUTPUT_DIR/electerm-${electerm_version}-linux-loong64.tar.gz"
|
||||
|
||||
# Build and upload debs
|
||||
build_deb "linux-loong64.deb" "loong64"
|
||||
upload_to_github "$OUTPUT_DIR/electerm-${electerm_version}-linux-loong64.deb"
|
||||
|
||||
build_deb "linux-loongarch64.deb" "loongarch64"
|
||||
upload_to_github "$OUTPUT_DIR/electerm-${electerm_version}-linux-loongarch64.deb"
|
||||
|
||||
log_info "=========================================="
|
||||
log_info "Build complete!"
|
||||
log_info " $OUTPUT_DIR/electerm-${electerm_version}-linux-loong64.tar.gz"
|
||||
log_info " $OUTPUT_DIR/electerm-${electerm_version}-linux-loong64.deb"
|
||||
log_info " $OUTPUT_DIR/electerm-${electerm_version}-linux-loongarch64.deb"
|
||||
log_info "=========================================="
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,29 @@
|
||||
const { echo, rm } = require('shelljs')
|
||||
const {
|
||||
run,
|
||||
writeSrc,
|
||||
uploadToR2,
|
||||
builder: pb,
|
||||
patchSnapClassicSandbox
|
||||
} = require('./build-common')
|
||||
|
||||
async function main () {
|
||||
echo('running build for linux part 2')
|
||||
|
||||
echo('build rpm')
|
||||
rm('-rf', 'dist')
|
||||
let src = 'linux-x86_64.rpm'
|
||||
writeSrc(src)
|
||||
await run(`${pb} --linux rpm`)
|
||||
await uploadToR2(src)
|
||||
|
||||
echo('build snap')
|
||||
rm('-rf', 'dist')
|
||||
src = 'linux-amd64.snap'
|
||||
writeSrc(src)
|
||||
patchSnapClassicSandbox()
|
||||
await run(`${pb} --linux snap -p always`)
|
||||
await uploadToR2(src)
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,41 @@
|
||||
const { rm, echo } = require('shelljs')
|
||||
const replace = require('replace-in-file')
|
||||
const {
|
||||
run,
|
||||
writeSrc,
|
||||
uploadToR2,
|
||||
builder
|
||||
} = require('./build-common')
|
||||
|
||||
const options = {
|
||||
files: require('path').resolve(__dirname, '../../electron-builder.json'),
|
||||
from: ['${productName}-${version}-${os}-${arch}.${ext}'], // eslint-disable-line
|
||||
to: ['${productName}-${version}-${os}10-${arch}.${ext}'] // eslint-disable-line
|
||||
}
|
||||
|
||||
function replaceRun () {
|
||||
return new Promise((resolve, reject) => {
|
||||
replace(options, (err) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
console.log('start build mac10 file')
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function main () {
|
||||
const pb = builder
|
||||
echo('running build for mac10 (macOS 10.15 Catalina compatible)')
|
||||
|
||||
echo('build dmg')
|
||||
await replaceRun()
|
||||
rm('-rf', 'dist')
|
||||
const src = 'mac10-x64.dmg'
|
||||
writeSrc(src)
|
||||
await run(`${pb} --mac --x64`)
|
||||
await uploadToR2(src)
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,22 @@
|
||||
const { echo, rm } = require('shelljs')
|
||||
const {
|
||||
run,
|
||||
writeSrc,
|
||||
uploadToR2,
|
||||
builder: pb,
|
||||
reBuild
|
||||
} = require('./build-common')
|
||||
|
||||
async function main () {
|
||||
echo('running build for mac arm')
|
||||
|
||||
echo('build dmg')
|
||||
const src = 'mac-arm64.dmg'
|
||||
rm('-rf', 'dist')
|
||||
writeSrc(src)
|
||||
await run(`${reBuild} --arch arm64 -f work/app`)
|
||||
await run(`${pb} --mac --arm64`)
|
||||
await uploadToR2(src)
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,28 @@
|
||||
// const cmd = 'rm -rf dist && ' +
|
||||
// 'echo "build x64 mac" && ' +
|
||||
// './node_modules/.bin/electron-builder --mac --x64'
|
||||
// 'echo "build arm64 mac" && ' +
|
||||
// './node_modules/.bin/electron-rebuild --arch arm64 -f -p work/app && ' +
|
||||
// './node_modules/.bin/electron-builder --mac --arm64'
|
||||
// const cmd = './node_modules/.bin/electron-builder --mac'
|
||||
|
||||
const { echo, rm } = require('shelljs')
|
||||
const {
|
||||
run,
|
||||
writeSrc,
|
||||
uploadToR2,
|
||||
builder: pb
|
||||
} = require('./build-common')
|
||||
|
||||
async function main () {
|
||||
echo('running build for mac')
|
||||
|
||||
echo('build dmg')
|
||||
const src = 'mac-x64.dmg'
|
||||
rm('-rf', 'dist')
|
||||
writeSrc(src)
|
||||
await run(`${pb} --mac`)
|
||||
await uploadToR2(src)
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,25 @@
|
||||
const { rm, echo } = require('shelljs')
|
||||
const { resolve } = require('path')
|
||||
const fs = require('fs')
|
||||
const {
|
||||
run,
|
||||
writeSrc,
|
||||
uploadToR2,
|
||||
builder
|
||||
} = require('./build-common')
|
||||
|
||||
async function main () {
|
||||
const pb = builder
|
||||
echo('build appx')
|
||||
const p = resolve(__dirname, '../../electron-builder.json')
|
||||
const txt = require('../../electron-builder.json')
|
||||
delete txt.electronLanguages
|
||||
fs.writeFileSync(p, JSON.stringify(txt, null, 2))
|
||||
rm('-rf', 'dist')
|
||||
const src = 'win-x64.appx'
|
||||
writeSrc(src)
|
||||
await run(`${pb} --win appx`)
|
||||
await uploadToR2(src)
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,28 @@
|
||||
const { echo, rm } = require('shelljs')
|
||||
const {
|
||||
run,
|
||||
writeSrc,
|
||||
uploadToR2,
|
||||
builder: pb,
|
||||
reBuild,
|
||||
replaceJSON
|
||||
} = require('./build-common')
|
||||
|
||||
async function main () {
|
||||
echo('running build for Windows ARM64')
|
||||
|
||||
echo('build tar.gz for Windows ARM64')
|
||||
const src = 'win-arm64.tar.gz'
|
||||
rm('-rf', 'dist')
|
||||
writeSrc(src)
|
||||
replaceJSON(
|
||||
(data) => {
|
||||
data.win.target = ['tar.gz']
|
||||
}
|
||||
)
|
||||
await run(`${reBuild} --arch arm64 -f work/app`)
|
||||
await run(`${pb} --win --arm64`)
|
||||
await uploadToR2(src)
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,21 @@
|
||||
const { rm, echo } = require('shelljs')
|
||||
const {
|
||||
run,
|
||||
writeSrc,
|
||||
uploadToR2,
|
||||
builder,
|
||||
replaceRun
|
||||
} = require('./build-common')
|
||||
|
||||
async function main () {
|
||||
const pb = builder
|
||||
echo('build loose tar.gz')
|
||||
await replaceRun()
|
||||
rm('-rf', 'dist')
|
||||
const src = 'win-x64-loose.tar.gz'
|
||||
writeSrc(src)
|
||||
await run(`${pb} --win tar.gz`)
|
||||
await uploadToR2(src)
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,32 @@
|
||||
const { echo, rm } = require('shelljs')
|
||||
const {
|
||||
run,
|
||||
writeSrc,
|
||||
uploadToR2,
|
||||
builder: pb,
|
||||
reBuild,
|
||||
replaceJSON,
|
||||
patchNsisKeepShortcuts
|
||||
} = require('./build-common')
|
||||
|
||||
async function main () {
|
||||
echo('running build for Windows ARM64')
|
||||
|
||||
patchNsisKeepShortcuts()
|
||||
|
||||
echo('build tar.gz for Windows ARM64')
|
||||
const src = 'win-arm64-installer.exe'
|
||||
rm('-rf', 'dist')
|
||||
writeSrc(src)
|
||||
replaceJSON(
|
||||
(data) => {
|
||||
data.win.target = ['nsis']
|
||||
data.afterAllArtifactBuild = 'build/bin/clean-yml.js'
|
||||
}
|
||||
)
|
||||
await run(`${reBuild} --arch arm64 -f work/app`)
|
||||
await run(`${pb} --win --arm64`)
|
||||
await uploadToR2(src)
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,24 @@
|
||||
const { rm, echo } = require('shelljs')
|
||||
const {
|
||||
run,
|
||||
writeSrc,
|
||||
uploadToR2,
|
||||
builder,
|
||||
patchNsisKeepShortcuts
|
||||
} = require('./build-common')
|
||||
|
||||
async function main () {
|
||||
const pb = builder
|
||||
echo('running build for win part nsis installer')
|
||||
|
||||
patchNsisKeepShortcuts()
|
||||
|
||||
echo('build nsis')
|
||||
const src = 'win-x64-installer.exe'
|
||||
rm('-rf', 'dist')
|
||||
writeSrc(src)
|
||||
await run(`${pb} --win nsis`)
|
||||
await uploadToR2(src)
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
const replace = require('replace-in-file')
|
||||
const { rm, echo } = require('shelljs')
|
||||
const {
|
||||
run,
|
||||
writeSrc,
|
||||
uploadToR2,
|
||||
builder
|
||||
} = require('./build-common')
|
||||
const options = {
|
||||
files: require('path').resolve(__dirname, '../../electron-builder.json'),
|
||||
from: ['${productName}-${version}-${os}-${arch}.${ext}', ', "appx", "nsis"'], // eslint-disable-line
|
||||
to: ['${productName}-${version}-${os}-${arch}-portable.${ext}', ''] // eslint-disable-line
|
||||
}
|
||||
|
||||
function replaceRun () {
|
||||
return new Promise((resolve, reject) => {
|
||||
replace(options, (err) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
console.log('start build portable file')
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function main () {
|
||||
const pb = builder
|
||||
echo('running build for win part 3')
|
||||
|
||||
echo('build portable tar.gz')
|
||||
await replaceRun()
|
||||
rm('-rf', 'dist')
|
||||
const src = 'win-x64-portable.tar.gz'
|
||||
writeSrc(src)
|
||||
await run(`${pb} --win tar.gz`)
|
||||
await uploadToR2(src)
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,21 @@
|
||||
const { rm, echo } = require('shelljs')
|
||||
const {
|
||||
run,
|
||||
writeSrc,
|
||||
uploadToR2,
|
||||
builder
|
||||
} = require('./build-common')
|
||||
|
||||
async function main () {
|
||||
const pb = builder
|
||||
echo('running build for win part 1')
|
||||
|
||||
echo('build tar.gz')
|
||||
rm('-rf', 'dist')
|
||||
const src = 'win-x64.tar.gz'
|
||||
writeSrc(src)
|
||||
await run(`${pb} --win tar.gz`)
|
||||
await uploadToR2(src)
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,39 @@
|
||||
const { rm, echo } = require('shelljs')
|
||||
const replace = require('replace-in-file')
|
||||
const {
|
||||
run,
|
||||
writeSrc,
|
||||
uploadToR2,
|
||||
builder
|
||||
} = require('./build-common')
|
||||
const options = {
|
||||
files: require('path').resolve(__dirname, '../../electron-builder.json'),
|
||||
from: ['${productName}-${version}-${os}-${arch}.${ext}', ', "appx", "nsis"'], // eslint-disable-line
|
||||
to: ['${productName}-${version}-${os}7.${ext}', ''] // eslint-disable-line
|
||||
}
|
||||
function replaceRun () {
|
||||
return new Promise((resolve, reject) => {
|
||||
replace(options, (err) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
console.log('start build win7 file')
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function main () {
|
||||
const pb = builder
|
||||
echo('running build for win7')
|
||||
|
||||
echo('build tar.gz')
|
||||
await replaceRun()
|
||||
rm('-rf', 'dist')
|
||||
const src = 'win7.tar.gz'
|
||||
writeSrc(src)
|
||||
await run(`${pb} --win tar.gz`)
|
||||
await uploadToR2(src)
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* build
|
||||
*/
|
||||
|
||||
const { exec, echo } = require('shelljs')
|
||||
|
||||
echo('start build')
|
||||
|
||||
const timeStart = +new Date()
|
||||
|
||||
// echo('clean')
|
||||
// exec('npm run clean')
|
||||
echo('version file')
|
||||
echo('js/css file')
|
||||
exec('npm run vite-build')
|
||||
echo('copy file')
|
||||
exec('node ./build/bin/copy.js')
|
||||
echo('html file')
|
||||
exec('node ./build/bin/pug.js')
|
||||
|
||||
const endTime = +new Date()
|
||||
echo(`done build in ${(endTime - timeStart) / 1000} s`)
|
||||
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
/**
|
||||
* Clean empty folders recursively in a given directory
|
||||
* @param {string} dirPath - The directory path to clean
|
||||
* @returns {number} - Number of empty folders removed
|
||||
*/
|
||||
function cleanEmptyFolders (dirPath) {
|
||||
let removedCount = 0
|
||||
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
console.log('Directory does not exist:', dirPath)
|
||||
return removedCount
|
||||
}
|
||||
|
||||
try {
|
||||
const items = fs.readdirSync(dirPath)
|
||||
|
||||
// First, recursively clean subdirectories
|
||||
for (const item of items) {
|
||||
const itemPath = path.join(dirPath, item)
|
||||
const stats = fs.statSync(itemPath)
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
removedCount += cleanEmptyFolders(itemPath)
|
||||
}
|
||||
}
|
||||
|
||||
// After cleaning subdirectories, check if current directory is now empty
|
||||
const remainingItems = fs.readdirSync(dirPath)
|
||||
if (remainingItems.length === 0) {
|
||||
// Don't remove the root node_modules directory itself
|
||||
const nodeModulesPath = path.resolve(process.cwd(), 'work/app/node_modules')
|
||||
if (path.resolve(dirPath) !== nodeModulesPath) {
|
||||
console.log('Removing empty directory:', dirPath)
|
||||
fs.rmdirSync(dirPath)
|
||||
removedCount++
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing directory ' + dirPath + ':', error.message)
|
||||
}
|
||||
|
||||
return removedCount
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to clean empty folders in work/app/node_modules
|
||||
*/
|
||||
function main () {
|
||||
const targetDir = path.resolve(process.cwd(), 'work/app/node_modules')
|
||||
|
||||
console.log('Starting cleanup of empty folders in:', targetDir)
|
||||
console.log('='.repeat(60))
|
||||
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
console.log('Target directory does not exist:', targetDir)
|
||||
return
|
||||
}
|
||||
|
||||
const startTime = Date.now()
|
||||
const removedCount = cleanEmptyFolders(targetDir)
|
||||
const endTime = Date.now()
|
||||
|
||||
console.log('='.repeat(60))
|
||||
console.log('Cleanup completed!')
|
||||
console.log('Empty folders removed:', removedCount)
|
||||
console.log('Time taken:', endTime - startTime + 'ms')
|
||||
|
||||
if (removedCount === 0) {
|
||||
console.log('No empty folders found.')
|
||||
}
|
||||
}
|
||||
|
||||
// Run the script if called directly
|
||||
if (require.main === module) {
|
||||
main()
|
||||
}
|
||||
|
||||
module.exports = { cleanEmptyFolders, main }
|
||||
@@ -0,0 +1,16 @@
|
||||
// delete-yml.js
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
exports.default = async function (context) {
|
||||
// context.outDir is the path to the build output directory
|
||||
const ymlFiles = fs.readdirSync(context.outDir).filter(file => {
|
||||
return path.extname(file) === '.yml'
|
||||
})
|
||||
|
||||
ymlFiles.forEach(file => {
|
||||
const filePath = path.join(context.outDir, file)
|
||||
console.log(`Deleting auto-update file: ${filePath}`)
|
||||
fs.unlinkSync(filePath)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const { rm } = require('shelljs')
|
||||
|
||||
rm('-rf', [
|
||||
'work'
|
||||
])
|
||||
@@ -0,0 +1,39 @@
|
||||
const { resolve } = require('path')
|
||||
const { cp } = require('shelljs')
|
||||
const from = resolve(
|
||||
__dirname,
|
||||
'../../node_modules/@electerm/electerm-resource/tray-icons/*'
|
||||
)
|
||||
const from0 = resolve(
|
||||
__dirname,
|
||||
'../../node_modules/electerm-icons/icons'
|
||||
)
|
||||
const to1 = resolve(
|
||||
__dirname,
|
||||
'../../work/app/assets/images/'
|
||||
)
|
||||
const to2 = resolve(
|
||||
__dirname,
|
||||
'../../work/app/assets/icons'
|
||||
)
|
||||
const arr = [
|
||||
{
|
||||
from,
|
||||
to: to1,
|
||||
file: true
|
||||
}, {
|
||||
from: from0,
|
||||
to: to2
|
||||
}
|
||||
]
|
||||
|
||||
for (const obj of arr) {
|
||||
const {
|
||||
file, from, to
|
||||
} = obj
|
||||
if (file) {
|
||||
cp(from, to)
|
||||
} else {
|
||||
cp('-r', from, to)
|
||||
}
|
||||
}
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
chown root:root /opt/electerm/chrome-sandbox
|
||||
chmod 4755 /opt/electerm/chrome-sandbox
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
cd `dirname $0`
|
||||
cd ../..
|
||||
git co master
|
||||
git pull
|
||||
git pull
|
||||
git delete-branch test-npm
|
||||
git create-branch test-npm
|
||||
git push origin test-npm -u
|
||||
git co -
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* post install script
|
||||
*/
|
||||
const { cp, exec, rm } = require('shelljs')
|
||||
const { existsSync } = require('fs')
|
||||
const { resolve } = require('path')
|
||||
const prePushPath = resolve(__dirname, '../../.git/hooks/pre-push')
|
||||
const prePushPathFrom = resolve(__dirname, 'pre-push')
|
||||
const os = require('os')
|
||||
|
||||
const platform = os.platform()
|
||||
const isWin = platform === 'win32'
|
||||
// const rest = ''
|
||||
if (isWin && process.env.CI) {
|
||||
exec('npm cache clear -f')
|
||||
exec('npm uninstall node-gyp -g')
|
||||
exec('npm install node-gyp -g')
|
||||
}
|
||||
|
||||
// Remove optional native module that may fail to rebuild
|
||||
try {
|
||||
// Check multiple potential locations for cpu-features
|
||||
const cpuFeaturesPaths = [
|
||||
resolve(__dirname, '../../node_modules/cpu-features'),
|
||||
resolve(__dirname, '../../work/app/node_modules/cpu-features')
|
||||
]
|
||||
|
||||
cpuFeaturesPaths.forEach(cpuFeaturesPath => {
|
||||
if (existsSync(cpuFeaturesPath)) {
|
||||
rm('-rf', cpuFeaturesPath)
|
||||
console.log('Removed optional module:', cpuFeaturesPath)
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
console.warn('Failed to remove cpu-features:', e?.message || e)
|
||||
}
|
||||
|
||||
exec(resolve('./node_modules/.bin/electron-rebuild'))
|
||||
|
||||
if (!existsSync(prePushPath)) {
|
||||
cp(prePushPathFrom, prePushPath)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const { resolve } = require('path')
|
||||
const { cp, rm } = require('shelljs')
|
||||
|
||||
const from = resolve(__dirname, '../../package.json')
|
||||
const to = resolve(__dirname, '../../package-bak.json')
|
||||
cp(to, from)
|
||||
rm(to)
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
cd `dirname $0`
|
||||
cd ../../
|
||||
npm run lint
|
||||
@@ -0,0 +1,3 @@
|
||||
const { cp } = require('shelljs')
|
||||
|
||||
cp('-r', 'build/electron-builder.json', './')
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* prepare the files to be packed
|
||||
*/
|
||||
|
||||
const pack = require('../../package.json')
|
||||
const os = require('os')
|
||||
const { resolve } = require('path')
|
||||
const { version } = pack
|
||||
const { mkdir, rm, exec, echo, cp } = require('shelljs')
|
||||
const dir = 'dist/v' + version
|
||||
const cwd = process.cwd()
|
||||
|
||||
const platform = os.platform()
|
||||
const isWin = platform === 'win32'
|
||||
|
||||
pack.main = 'app.js'
|
||||
delete pack.scripts
|
||||
delete pack.standard
|
||||
delete pack.files
|
||||
delete pack.engines
|
||||
delete pack.preferGlobal
|
||||
|
||||
if (isWin) {
|
||||
delete pack.dependencies['node-bash']
|
||||
} else {
|
||||
delete pack.dependencies['node-powershell']
|
||||
}
|
||||
|
||||
echo('start pack prepare')
|
||||
// echo('install test deps')
|
||||
// exec(`PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm i -D -E playwright@1.28.1 --no-save && npm i -D -E @playwright/test@1.28.1 --no-save`)
|
||||
const timeStart = +new Date()
|
||||
rm('-rf', dir)
|
||||
rm('-rf', 'dist/latest')
|
||||
|
||||
mkdir('-p', dir)
|
||||
mkdir('-p', 'dist/latest')
|
||||
cp('-r', 'src/app', 'work/')
|
||||
rm('-rf', 'work/app/user-config.json')
|
||||
rm('-rf', 'work/app/localstorage.json')
|
||||
rm('-rf', 'work/app/nohup.out')
|
||||
rm('-rf', 'work/app/assets/js/index*')
|
||||
rm('-rf', 'work/app/assets/js/*.txt')
|
||||
rm('-rf', 'node_modules/cpu-features')
|
||||
|
||||
require('fs').writeFileSync(
|
||||
resolve(__dirname, '../../work/app/package.json'),
|
||||
JSON.stringify(
|
||||
pack, null, 2
|
||||
)
|
||||
)
|
||||
|
||||
exec(`cd work/app && npm i --omit=dev && cd ${cwd}`)
|
||||
rm('-rf', 'work/app/node_modules/.bin')
|
||||
// Remove axios browser/ESM builds and unnecessary files (keep only lib/ and node CJS)
|
||||
rm('-rf', 'work/app/node_modules/axios/dist/esm')
|
||||
rm('-rf', 'work/app/node_modules/axios/dist/browser')
|
||||
rm('-rf', 'work/app/node_modules/axios/dist/*.js')
|
||||
rm('-rf', 'work/app/node_modules/axios/dist/*.map')
|
||||
rm('-rf', 'work/app/node_modules/axios/dist/node/*.map')
|
||||
rm('-rf', 'work/app/node_modules/axios/index.d.cts')
|
||||
rm('-rf', 'work/app/node_modules/axios/lib')
|
||||
|
||||
// Remove cpu-features after npm prune to prevent rebuild issues
|
||||
rm('-rf', 'node_modules/cpu-features')
|
||||
rm('-rf', 'work/app/node_modules/cpu-features')
|
||||
|
||||
// Clean up node-pty platform-specific files to reduce bundle size
|
||||
if (isWin) {
|
||||
// On Windows, remove Unix-specific files
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/unixTerminal.js')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/unixTerminal.js.map')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/unixTerminal.test.js')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/unixTerminal.test.js.map')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/build/pty.target.mk')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/build/spawn-helper.target.mk')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/build/binding.Makefile')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/build/gyp-mac-tool')
|
||||
} else {
|
||||
// On Linux/Mac, remove Windows-specific files
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/conpty_console_list_agent.js')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/conpty_console_list_agent.js.map')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/windowsConoutConnection.js')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/windowsConoutConnection.js.map')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/windowsPtyAgent.js')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/windowsPtyAgent.js.map')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/windowsPtyAgent.test.js')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/windowsPtyAgent.test.js.map')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/windowsTerminal.js')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/windowsTerminal.js.map')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/windowsTerminal.test.js')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/windowsTerminal.test.js.map')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/deps/winpty')
|
||||
}
|
||||
|
||||
// Remove all test files from node-pty to reduce bundle size
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/*.test.js')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/*.test.js.map')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/testUtils.test.js')
|
||||
rm('-rf', 'work/app/node_modules/node-pty/lib/testUtils.test.js.map')
|
||||
|
||||
// yarn auto clean
|
||||
cp('-r', 'build/bin/.yarnclean', 'work/app/')
|
||||
exec(`cd work/app && yarn generate-lock-entry > yarn.lock && yarn autoclean --force && cd ${cwd}`)
|
||||
rm('-rf', 'work/app/.yarnclean')
|
||||
rm('-rf', 'work/app/package-lock.json')
|
||||
rm('-rf', 'work/app/yarn.lock')
|
||||
require('./clean-empty-folders').main()
|
||||
|
||||
const endTime = +new Date()
|
||||
echo(`done pack prepare in ${(endTime - timeStart) / 1000} s`)
|
||||
@@ -0,0 +1,28 @@
|
||||
const savedPackage = [
|
||||
'shelljs',
|
||||
'tar'
|
||||
]
|
||||
const pack = require('../../package.json')
|
||||
const fs = require('fs')
|
||||
const { resolve } = require('path')
|
||||
|
||||
delete pack.devDependencies
|
||||
pack.dependencies = savedPackage.reduce((prev, p) => {
|
||||
prev[p] = '*'
|
||||
return prev
|
||||
}, {})
|
||||
pack.scripts = {
|
||||
postinstall: 'node npm/install.js',
|
||||
postpublish: 'node bin/postpublish.js'
|
||||
}
|
||||
delete pack.langugeRepo
|
||||
delete pack.privacyNoticeLink
|
||||
delete pack.knownIssuesLink
|
||||
delete pack.sponsorLink
|
||||
delete pack.releases
|
||||
delete pack.standard
|
||||
const from = resolve(__dirname, '../../package.json')
|
||||
const to = resolve(__dirname, '../../package-bak.json')
|
||||
fs.copyFileSync(from, to)
|
||||
fs.unlinkSync(from)
|
||||
fs.writeFileSync(from, JSON.stringify(pack, null, 2))
|
||||
@@ -0,0 +1,16 @@
|
||||
const { rm, echo, cd, cp, exec } = require('shelljs')
|
||||
const { resolve } = require('path')
|
||||
const fs = require('fs')
|
||||
const pack = require('../../package.json')
|
||||
const tpack = require('../web-app/package.json')
|
||||
tpack.version = pack.version
|
||||
|
||||
const to = resolve(__dirname, '../web-app/')
|
||||
const from = resolve(__dirname, '../../src/client')
|
||||
|
||||
echo('start build electerm-react pack and publish')
|
||||
fs.writeFileSync(resolve(to, 'package.json'), JSON.stringify(tpack, null, 2))
|
||||
rm('-rf', resolve(to, 'client'))
|
||||
cp('-r', from, to)
|
||||
cd(to)
|
||||
exec(`npm-publish --token ${process.env.token} --access public`)
|
||||
@@ -0,0 +1,40 @@
|
||||
// build html
|
||||
/**
|
||||
* build common files with react module in it
|
||||
*/
|
||||
const fs = require('fs')
|
||||
const pug = require('pug')
|
||||
const { resolve } = require('path')
|
||||
const pack = require('../../package.json')
|
||||
const deepCopy = require('json-deep-copy')
|
||||
|
||||
const entryPug = resolve(
|
||||
__dirname,
|
||||
'../../src/client/views/index.pug'
|
||||
)
|
||||
const targetFilePath = resolve(
|
||||
__dirname,
|
||||
'../../work/app/assets/index.html'
|
||||
)
|
||||
const pugContent = fs.readFileSync(entryPug, 'utf-8')
|
||||
const defaultAIPreset = {
|
||||
baseURLAI: 'https://ai.electerm.org/api/ai',
|
||||
apiPathAI: '/chat/completions',
|
||||
modelAI: 'mistral-small-latest',
|
||||
authHeaderNameAI: 'Authorization: Bearer',
|
||||
id: 'ai.electerm.org',
|
||||
nameAI: 'ai.electerm.org(default free)'
|
||||
}
|
||||
|
||||
const data = {
|
||||
version: pack.version,
|
||||
siteName: pack.name,
|
||||
isDev: false,
|
||||
defaultAIPreset
|
||||
}
|
||||
const htmlContent = pug.render(pugContent, {
|
||||
filename: entryPug,
|
||||
...data,
|
||||
_global: deepCopy(data)
|
||||
})
|
||||
fs.writeFileSync(targetFilePath, htmlContent, 'utf8')
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
cd `dirname $0`
|
||||
cd ../..
|
||||
git co master
|
||||
git pull
|
||||
git pull
|
||||
git delete-branch build
|
||||
git create-branch build
|
||||
git push origin build -u
|
||||
git co -
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
const { exec, cd } = require('shelljs')
|
||||
const { resolve } = require('path')
|
||||
const p = resolve(__dirname, '../vite')
|
||||
cd(p)
|
||||
exec('npm start')
|
||||
@@ -0,0 +1,53 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
async function main () {
|
||||
const filePath = process.argv[2]
|
||||
const key = process.argv[3] || path.basename(filePath || '')
|
||||
|
||||
if (!filePath) {
|
||||
throw new Error('Usage: node build/bin/upload-file-to-r2.js <filePath> [key]')
|
||||
}
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw new Error(`File does not exist: ${filePath}`)
|
||||
}
|
||||
|
||||
const accountId = process.env.CF_R2_ACCOUNT_ID
|
||||
const bucket = process.env.CF_R2_BUCKET
|
||||
const accessKeyId = process.env.CF_R2_ACCESS_KEY_ID
|
||||
const secretAccessKey = process.env.CF_R2_SECRET_ACCESS_KEY
|
||||
|
||||
if (!accountId || !bucket || !accessKeyId || !secretAccessKey) {
|
||||
throw new Error('Missing required env vars: CF_R2_ACCOUNT_ID, CF_R2_BUCKET, CF_R2_ACCESS_KEY_ID, CF_R2_SECRET_ACCESS_KEY')
|
||||
}
|
||||
|
||||
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3')
|
||||
|
||||
const client = new S3Client({
|
||||
region: 'auto',
|
||||
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
|
||||
credentials: {
|
||||
accessKeyId,
|
||||
secretAccessKey
|
||||
}
|
||||
})
|
||||
|
||||
const fileStream = fs.createReadStream(filePath)
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
Body: fileStream,
|
||||
ContentLength: fs.statSync(filePath).size,
|
||||
ContentType: 'text/plain; charset=utf-8',
|
||||
CacheControl: 'no-store'
|
||||
})
|
||||
|
||||
await client.send(command)
|
||||
console.log(`[r2] Uploaded ${filePath} to ${bucket}/${key}`)
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
const { exec, cd } = require('shelljs')
|
||||
const { resolve } = require('path')
|
||||
const p = resolve(__dirname, '../vite')
|
||||
cd(p)
|
||||
|
||||
exec('npm run build')
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
cd `dirname $0`
|
||||
cd ../..
|
||||
git co master
|
||||
git pull
|
||||
git pull
|
||||
git delete-branch electerm-react
|
||||
git create-branch electerm-react
|
||||
git push origin electerm-react -u
|
||||
git co -
|
||||
@@ -0,0 +1,137 @@
|
||||
{
|
||||
"appId": "org.electerm.electerm",
|
||||
"directories": {
|
||||
"app": "work/app",
|
||||
"output": "dist",
|
||||
"buildResources": "node_modules/@electerm/electerm-resource/build-res"
|
||||
},
|
||||
"electronLanguages": [
|
||||
"en-US"
|
||||
],
|
||||
"asar": true,
|
||||
"artifactName": "${productName}-${version}-${os}-${arch}.${ext}",
|
||||
"win": {
|
||||
"target": [
|
||||
"tar.gz",
|
||||
"appx",
|
||||
"nsis"
|
||||
],
|
||||
"icon": "node_modules/@electerm/electerm-resource/build/icons-win.ico",
|
||||
"publish": {
|
||||
"provider": "github",
|
||||
"channel": "${env.WORKFLOW_NAME}"
|
||||
}
|
||||
},
|
||||
"protocols": [
|
||||
{
|
||||
"name": "SSH Protocol",
|
||||
"schemes": [
|
||||
"ssh"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Telnet Protocol",
|
||||
"schemes": [
|
||||
"telnet"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "RDP Protocol",
|
||||
"schemes": [
|
||||
"rdp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "VNC Protocol",
|
||||
"schemes": [
|
||||
"vnc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Serial Protocol",
|
||||
"schemes": [
|
||||
"serial"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Spice Protocol",
|
||||
"schemes": [
|
||||
"spice"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Electerm Protocol",
|
||||
"schemes": [
|
||||
"electerm"
|
||||
]
|
||||
}
|
||||
],
|
||||
"appx": {
|
||||
"applicationId": "electerm",
|
||||
"identityName": "64159ZHAOXudong.electerm",
|
||||
"publisher": "CN=9F481654-170A-434C-8259-984D2EB1FD7F",
|
||||
"publisherDisplayName": "ZHAO Xudong"
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"artifactName": "${productName}-${version}-${os}-${arch}-installer.${ext}",
|
||||
"allowToChangeInstallationDirectory": true
|
||||
},
|
||||
"mac": {
|
||||
"category": "public.app-category.developer-tools",
|
||||
"target": "dmg",
|
||||
"icon": "node_modules/@electerm/electerm-resource/build/icons.icns",
|
||||
"publish": {
|
||||
"provider": "github",
|
||||
"channel": "${env.WORKFLOW_NAME}"
|
||||
},
|
||||
"hardenedRuntime": true,
|
||||
"gatekeeperAssess": false,
|
||||
"entitlements": "./build/entitlements.plist",
|
||||
"entitlementsInherit": "./build/entitlements.plist",
|
||||
"notarize": true
|
||||
},
|
||||
"dmg": {
|
||||
"contents": [
|
||||
{
|
||||
"x": 110,
|
||||
"y": 190
|
||||
},
|
||||
{
|
||||
"x": 440,
|
||||
"y": 190,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
}
|
||||
]
|
||||
},
|
||||
"linux": {
|
||||
"category": "Development;System;TerminalEmulator",
|
||||
"icon": "node_modules/@electerm/electerm-resource/res/imgs",
|
||||
"target": [
|
||||
"rpm",
|
||||
"deb",
|
||||
"tar.gz",
|
||||
"snap"
|
||||
],
|
||||
"publish": {
|
||||
"provider": "github",
|
||||
"channel": "${env.WORKFLOW_NAME}"
|
||||
},
|
||||
"desktop": {
|
||||
"entry": {
|
||||
"StartupWMClass": "electerm"
|
||||
}
|
||||
}
|
||||
},
|
||||
"snap": {
|
||||
"confinement": "classic",
|
||||
"publish": {
|
||||
"provider": "github",
|
||||
"channel": "${env.WORKFLOW_NAME}"
|
||||
}
|
||||
},
|
||||
"deb": {
|
||||
"afterInstall": "build/bin/linux-deb-after-build.sh"
|
||||
}
|
||||
}
|
||||
@@ -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.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,5 @@
|
||||
# run `cp .sample.env .env` to create your local env
|
||||
|
||||
## development server config
|
||||
DEV_HOST=127.0.0.1
|
||||
DEV_PORT=5570
|
||||
@@ -0,0 +1,31 @@
|
||||
import { config as conf } from 'dotenv'
|
||||
import { readFileSync } from 'fs'
|
||||
import { resolve } from 'path'
|
||||
|
||||
conf()
|
||||
|
||||
export const cwd = process.cwd()
|
||||
export const env = process.env
|
||||
export const isProd = env.NODE_ENV === 'production'
|
||||
const packPath = resolve(cwd, '../../package.json')
|
||||
export const pack = JSON.parse(readFileSync(packPath).toString())
|
||||
export const version = pack.version
|
||||
export const viewPath = resolve(cwd, '../../src/client/views')
|
||||
export const staticPaths = [
|
||||
{
|
||||
dir: resolve(cwd, '../../node_modules/electerm-icons/icons'),
|
||||
path: '/icons'
|
||||
},
|
||||
{
|
||||
dir: resolve(cwd, '../../node_modules/@electerm/electerm-resource/tray-icons'),
|
||||
path: '/images'
|
||||
},
|
||||
{
|
||||
dir: resolve(cwd, '../../node_modules/@electerm/electerm-resource/res/imgs'),
|
||||
path: '/images'
|
||||
},
|
||||
{
|
||||
dir: resolve(cwd, '../../src/client/entry'),
|
||||
path: '/'
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,87 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
// import htmlPurge from 'vite-plugin-purgecss'
|
||||
import { cwd, version } from './common.js'
|
||||
import { resolve } from 'path'
|
||||
import def from './def.js'
|
||||
|
||||
function buildInput () {
|
||||
return {
|
||||
electerm: resolve(cwd, '../../src/client/entry/electerm.jsx'),
|
||||
basic: resolve(cwd, '../../src/client/entry/basic.js'),
|
||||
worker: resolve(cwd, '../../src/client/entry/worker.js')
|
||||
}
|
||||
}
|
||||
|
||||
// Custom plugin to replace window.et.isWebApp with false
|
||||
function replaceWebAppPlugin () {
|
||||
return {
|
||||
name: 'replace-webapp',
|
||||
renderChunk (code, chunk) {
|
||||
// Replace window.et.isWebApp with false in the bundled code
|
||||
const newCode = code.replace(/window\.et\.isWebApp/g, 'false')
|
||||
if (newCode !== code) {
|
||||
return {
|
||||
code: newCode,
|
||||
map: null
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react({ include: /\.(mdx|js|jsx|ts|tsx|mjs)$/ }),
|
||||
replaceWebAppPlugin()
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'ironrdp-wasm': resolve(cwd, '../../node_modules/ironrdp-wasm/pkg/rdp_client.js'),
|
||||
// @xterm/addon-ligatures bundles lru-cache@11, which calls
|
||||
// channel()/tracingChannel() from node:diagnostics_channel at import time.
|
||||
// In the renderer (browser) context Vite stubs Node builtins and the call
|
||||
// throws. lru-cache only uses it for optional metrics, so a no-op stub is
|
||||
// safe. Covers both bare `diagnostics_channel` and the `node:` prefix.
|
||||
'node:diagnostics_channel': resolve(cwd, './diagnostics-channel-stub.js'),
|
||||
diagnostics_channel: resolve(cwd, './diagnostics-channel-stub.js')
|
||||
}
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: ['ironrdp-wasm']
|
||||
},
|
||||
define: def,
|
||||
publicDir: false,
|
||||
legacy: {
|
||||
inconsistentCjsInterop: true
|
||||
},
|
||||
root: resolve(cwd, '../..'),
|
||||
build: {
|
||||
target: 'esnext',
|
||||
cssCodeSplit: false,
|
||||
codeSplitting: false,
|
||||
emptyOutDir: false,
|
||||
outDir: resolve(cwd, '../../work/app/assets'),
|
||||
rollupOptions: {
|
||||
input: buildInput(),
|
||||
output: {
|
||||
format: 'esm',
|
||||
entryFileNames: `js/[name]-${version}.js`,
|
||||
chunkFileNames: `chunk/[name]-${version}-[hash].js`,
|
||||
dir: resolve(cwd, '../../work/app/assets'),
|
||||
assetFileNames: chunkInfo => {
|
||||
const { name } = chunkInfo
|
||||
if (/\.(png|jpe?g|gif|svg|webp|ico|bmp)$/i.test(name)) {
|
||||
return `images/${name}`
|
||||
} else if (name && name.endsWith('.css')) {
|
||||
return `css/style-${version}[extname]`
|
||||
} else {
|
||||
return 'assets/[name]-[hash][extname]'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,6 @@
|
||||
import { version } from './common.js'
|
||||
|
||||
export default {
|
||||
'process.env.VER': JSON.stringify(version),
|
||||
__DEFINES__: JSON.stringify('some value')
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import logger from 'morgan'
|
||||
import { viewPath, env, staticPaths, pack, isProd, cwd } from './common.js'
|
||||
import express from 'express'
|
||||
import { createServer as createViteServer } from 'vite'
|
||||
import conf from './conf.js'
|
||||
import copy from 'json-deep-copy'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { spawn } from 'child_process'
|
||||
import multer from 'multer'
|
||||
|
||||
const devPort = env.DEV_PORT || 5570
|
||||
const host = env.DEV_HOST || '127.0.0.1'
|
||||
const h = `http://${host}:${devPort}`
|
||||
const defaultAIPreset = {
|
||||
baseURLAI: 'https://ai.electerm.org/api/ai',
|
||||
apiPathAI: '/chat/completions',
|
||||
modelAI: 'mistral-small-latest',
|
||||
authHeaderNameAI: 'Authorization: Bearer',
|
||||
id: 'ai.electerm.org',
|
||||
nameAI: 'ai.electerm.org(default free)'
|
||||
}
|
||||
|
||||
const base = {
|
||||
version: pack.version,
|
||||
isDev: !isProd,
|
||||
siteName: pack.name,
|
||||
defaultAIPreset
|
||||
}
|
||||
|
||||
function handleIndex (req, res) {
|
||||
const view = 'index'
|
||||
res.render(view, {
|
||||
...base,
|
||||
_global: copy(base)
|
||||
})
|
||||
}
|
||||
|
||||
function redirect (req, res) {
|
||||
const {
|
||||
name
|
||||
} = req.params
|
||||
const mapper = {
|
||||
electerm: '/src/client/entry/electerm.jsx',
|
||||
worker: '/src/client/entry/worker.js'
|
||||
}
|
||||
res.redirect(mapper[name])
|
||||
}
|
||||
|
||||
async function createServer () {
|
||||
const app = express()
|
||||
|
||||
// Create Vite server in middleware mode and configure the app type as
|
||||
// 'custom', disabling Vite's own HTML serving logic so parent server
|
||||
// can take control
|
||||
const vite = await createViteServer({
|
||||
...conf,
|
||||
server: {
|
||||
middlewareMode: true,
|
||||
hmr: {
|
||||
port: 30085,
|
||||
overlay: true
|
||||
}
|
||||
},
|
||||
appType: 'custom'
|
||||
})
|
||||
|
||||
app.use(
|
||||
logger('dev')
|
||||
)
|
||||
app.use(express.json())
|
||||
app.use(express.urlencoded({
|
||||
extended: true
|
||||
}))
|
||||
staticPaths.forEach(({ path, dir }) => {
|
||||
app.use(
|
||||
path,
|
||||
express.static(dir, { maxAge: '170d' })
|
||||
)
|
||||
})
|
||||
|
||||
const upload = multer({ dest: 'uploads/' })
|
||||
|
||||
app.get('/api/download', (req, res) => {
|
||||
const filePath = req.query.path
|
||||
if (!filePath) {
|
||||
return res.status(400).json({ error: 'path is required' })
|
||||
}
|
||||
try {
|
||||
const stat = fs.statSync(filePath)
|
||||
if (stat.isFile()) {
|
||||
const fileName = path.basename(filePath)
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(fileName)}"`)
|
||||
res.setHeader('Content-Type', 'application/octet-stream')
|
||||
fs.createReadStream(filePath).pipe(res)
|
||||
} else if (stat.isDirectory()) {
|
||||
const dirName = path.basename(filePath)
|
||||
const parentDir = path.dirname(filePath)
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(dirName)}.tar.gz"`)
|
||||
res.setHeader('Content-Type', 'application/gzip')
|
||||
const tar = spawn('tar', ['czf', '-', '-C', parentDir, dirName])
|
||||
tar.stdout.pipe(res)
|
||||
tar.stderr.on('data', (data) => {
|
||||
console.error('tar stderr:', data.toString())
|
||||
})
|
||||
tar.on('error', (err) => {
|
||||
console.error('tar error:', err)
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
} else {
|
||||
res.status(400).json({ error: 'path is not a file or directory' })
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('download error:', err)
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
app.post('/api/upload', upload.single('file'), (req, res) => {
|
||||
const targetDir = req.body.path
|
||||
if (!targetDir || !req.file) {
|
||||
return res.status(400).json({ error: 'path and file are required' })
|
||||
}
|
||||
try {
|
||||
const destPath = path.join(targetDir, req.file.originalname)
|
||||
fs.renameSync(req.file.path, destPath)
|
||||
res.json({ success: true, path: destPath })
|
||||
} catch (err) {
|
||||
console.error('upload error:', err)
|
||||
res.status(500).json({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
app.set('views', viewPath)
|
||||
app.set('view engine', 'pug')
|
||||
|
||||
// Use vite's connect instance as middleware. If you use your own
|
||||
// express router (express.Router()), you should use router.use
|
||||
app.use(vite.middlewares)
|
||||
app.get(['/', '/index.html'], handleIndex)
|
||||
app.get('/:dir/:name.:ext', redirect)
|
||||
app.listen(devPort, host, () => {
|
||||
console.log('cwd:', cwd)
|
||||
console.log(`server started at ${h}`)
|
||||
})
|
||||
}
|
||||
|
||||
createServer()
|
||||
@@ -0,0 +1,55 @@
|
||||
// Browser-safe stub for Node's `node:diagnostics_channel` module.
|
||||
//
|
||||
// Why this exists:
|
||||
// @xterm/addon-ligatures (beta line) bundles lru-cache@11, which imports
|
||||
// `channel`/`tracingChannel` from `node:diagnostics_channel` and calls them at
|
||||
// module-load time (for optional metrics). In the Electron renderer / Vite dev
|
||||
// server this is a browser context: Vite stubs Node builtins with a
|
||||
// `browser-external` shim that throws on use, so `channel()` is undefined and
|
||||
// the addon crashes on import.
|
||||
//
|
||||
// lru-cache only publishes metrics when `hasSubscribers` is true, which never
|
||||
// happens here (we never subscribe). So all of these can be silent no-ops.
|
||||
|
||||
function makeChannel () {
|
||||
return {
|
||||
publish () {},
|
||||
subscribe () {
|
||||
return () => {}
|
||||
},
|
||||
unsubscribe () {},
|
||||
bindStore (store) {
|
||||
return store
|
||||
},
|
||||
unbindStore () {},
|
||||
hasSubscribers: false
|
||||
}
|
||||
}
|
||||
|
||||
export function channel () {
|
||||
return makeChannel()
|
||||
}
|
||||
|
||||
export function tracingChannel () {
|
||||
const ch = makeChannel()
|
||||
return {
|
||||
start: ch,
|
||||
end: ch,
|
||||
asyncStart: ch,
|
||||
asyncEnd: ch,
|
||||
error: ch,
|
||||
trace (fn) {
|
||||
return fn()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function hasSubscribers () {
|
||||
return false
|
||||
}
|
||||
|
||||
export default {
|
||||
channel,
|
||||
tracingChannel,
|
||||
hasSubscribers
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "electerm",
|
||||
"version": "1.29.5",
|
||||
"main": "app.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "npm run c",
|
||||
"c": "node ./dev-server.js",
|
||||
"build": "cross-env NODE_ENV=production vite build --config ./conf.js"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
/client/views
|
||||
/client/entry
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) since 2017~ ZHAO Xudong <zxdong@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,31 @@
|
||||
<h1 align="center" style="padding-top: 60px;padding-bottom: 40px;">
|
||||
<a href="https://electerm.github.io/electerm">
|
||||
<img src="https://github.com/electerm/electerm-resource/raw/master/static/images/electerm.png", alt="" />
|
||||
</a>
|
||||
</h1>
|
||||
|
||||
# electerm [](https://twitter.com/intent/tweet?text=Open%20sourced%20terminal%2Fssh%2Fsftp%20client(linux%2C%20mac%2C%20win)&url=https%3A%2F%2Fgithub.com%2Felecterm%2Felecterm&hashtags=electerm,ssh,terminal,sftp)
|
||||
|
||||
[](https://github.com/electerm/electerm/releases)
|
||||
[](https://github.com/electerm/electerm/actions)
|
||||
[](https://github.com/electerm/electerm/blob/master/LICENSE)
|
||||
[](https://standardjs.com)
|
||||
[](https://snapcraft.io/electerm)
|
||||
[](https://www.microsoft.com/store/apps/9NCN7272GTFF)
|
||||
[](https://github.com/sponsors/electerm)
|
||||
|
||||
Open-sourced terminal/ssh/telnet/serialport/sftp client(linux, mac, win).
|
||||
|
||||
This is react components src for electerm
|
||||
|
||||
## Use
|
||||
|
||||
todo
|
||||
|
||||
## Change log
|
||||
|
||||
Visit [Releases](https://github.com/electerm/electerm/releases).
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@electerm/electerm-react",
|
||||
"version": "1.34.30",
|
||||
"description": "react components src for electerm",
|
||||
"main": "./client/components/main/main.jsx",
|
||||
"license": "MIT",
|
||||
"langugeRepo": "https://github.com/electerm/electerm-locales",
|
||||
"privacyNoticeLink": "https://github.com/electerm/electerm/wiki/privacy-notice",
|
||||
"knownIssuesLink": "https://github.com/electerm/electerm/wiki/Know-issues",
|
||||
"sponsorLink": "https://electerm.org/sponsor-electerm.html",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/electerm/electerm.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "ZHAO Xudong",
|
||||
"email": "zxdong@gmail.com",
|
||||
"url": "https://github.com/zxdong262"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/electerm/electerm/issues"
|
||||
},
|
||||
"homepage": "https://electerm.org",
|
||||
"releases": "https://github.com/electerm/electerm/releases",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"files": [
|
||||
"client",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user