chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
# CONTRIBUTING
|
||||
|
||||
> First off, thank you for considering contributing to electerm.
|
||||
|
||||
> electerm is an open source project and I love to receive contributions from our community — you! There are many ways to contribute, from writing tutorials or blog posts, improving the documentation, submitting bug reports and feature requests or writing code which can be incorporated into electerm itself.
|
||||
|
||||
> Working on your first Pull Request? You can learn how from this *free* series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
|
||||
|
||||
Basic process:
|
||||
|
||||
> 1. Create your own fork of the code
|
||||
> 2. Do the changes in your fork
|
||||
> 3. If you like the change and think the project could use it:
|
||||
* Be sure you have followed the code style for the project by running `npm run lint`.
|
||||
* Send a pull request
|
||||
|
||||
> Want to report a issue or give some suggestions?
|
||||
[Submit a issue](https://github.com/electerm/electerm/issues/new)
|
||||
@@ -0,0 +1,3 @@
|
||||
github: electerm
|
||||
ko_fi: zhaoxudong
|
||||
custom: https://paypal.me/zhaoxudongPS
|
||||
@@ -0,0 +1 @@
|
||||
## 👉 [Please follow one of these issue templates](https://github.com/electerm/electerm/issues/new/choose) 👈
|
||||
@@ -0,0 +1,62 @@
|
||||
name: "\U0001F41B Bug report(提交bug)"
|
||||
description: Create a report to help us improve
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for reporting an issue.
|
||||
|
||||
This issue tracker is for bugs and issues found within Electerm.
|
||||
If you require more general support please start a discussion on our discussion board https://github.com/electerm/electerm/discussions
|
||||
|
||||
Please fill in as much of the form below as you're able.
|
||||
|
||||
Before continue, make sure you are using the latest release: check https://github.com/electerm/electerm/releases for latest release, also please
|
||||
|
||||
仅用来追踪bug, 如果有其他的讨论请移步讨论区 https://github.com/electerm/electerm/discussions, 请务必确保问题是基于最新版本: https://github.com/electerm/electerm/releases
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Electerm Version and download file extension(Electerm版本和下载文件后缀)
|
||||
description: |
|
||||
- electerm-xx.xx.xx-linux-amd64.deb
|
||||
- electerm-xx.xx.xx-linux-amd64.snap
|
||||
- electerm-xx.xx.xx-linux-x64.tar.gz
|
||||
- electerm-xx.xx.xx-linux-x86_64.rpm
|
||||
- electerm-xx.xx.xx-mac-arm64.dmg
|
||||
- electerm-xx.xx.xx-mac-x64.dmg
|
||||
- electerm-xx.xx.xx-win-x64-installer.exe
|
||||
- electerm-xx.xx.xx-win-x64-loose.tar.gz
|
||||
- electerm-xx.xx.xx-win-x64.appx
|
||||
- electerm-xx.xx.xx-win-x64.tar.gz
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Platform detail (平台详情)
|
||||
description: |
|
||||
windows 7/8/10/11, mac os(ARM or x64), or linux
|
||||
UNIX: output of `uname -a`
|
||||
Windows: output of `"$([Environment]::OSVersion | ForEach-Object VersionString) $(if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" })"` in PowerShell console
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What steps will reproduce the bug?(重现问题的详细步骤)
|
||||
description: Enter details about your bug, preferably a simple code snippet that can be run using `node` directly without installing third-party dependencies.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What should have happened?(期望的结果)
|
||||
description: If possible please provide screenshots.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Would this happen in other terminal app(是否能够在其他同类软件重现这个问题)
|
||||
description: really important.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional information(其他任何相关信息)
|
||||
description: Tell us anything else you think we should know.
|
||||
@@ -0,0 +1,10 @@
|
||||
name: "💥 Proposal / Feature (建议和新功能)"
|
||||
description: Propose a non-trivial change or new feature for electerm
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What feature you'd like to see or proposal(期望什么新功能/特性或者建议)
|
||||
description: A clear and concise description of what the proposal or feature is.(请尽可能详细描述,以便帮助其他人能够理解)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: ⁉️ Need help with Electerm?
|
||||
url: https://github.com/electerm/electerm/discussions
|
||||
about: Please start a discussion before opening a bug.
|
||||
@@ -0,0 +1,8 @@
|
||||
# Electerm Community Code of Conduct
|
||||
|
||||
1. **Be Respectful**: Treat everyone with kindness. No harassment or discrimination.
|
||||
2. **Collaborate Constructively**: Critique ideas, not people.
|
||||
3. **Support Others**: Help newcomers and value diverse perspectives.
|
||||
4. **Report Responsibly**: Flag issues via [GitHub Issues](https://github.com/electerm/electerm/issues) or contact maintainers privately.
|
||||
|
||||
We foster an open, welcoming environment for all contributors.
|
||||
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
@@ -0,0 +1,24 @@
|
||||
name: Trigger Website Update
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
trigger-website-update:
|
||||
runs-on: ubuntu-latest
|
||||
environment: build
|
||||
strategy:
|
||||
matrix:
|
||||
repository:
|
||||
- electerm/electerm.org
|
||||
- electerm/repos
|
||||
- electerm/electerm-data-tool
|
||||
steps:
|
||||
- name: Trigger ${{ matrix.repository }} repository workflow
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.PAT_REPO_TOKEN }}
|
||||
repository: ${{ matrix.repository }}
|
||||
event-type: electerm-release
|
||||
@@ -0,0 +1,26 @@
|
||||
name: electerm-react-publish
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
branches: [ electerm-react ]
|
||||
jobs:
|
||||
release-npm:
|
||||
runs-on: ubuntu-24.04
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip npm]') }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup node and npm
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
registry-url: https://registry.npmjs.org/
|
||||
- run: npm config set legacy-peer-deps true
|
||||
- run: npm install shelljs
|
||||
- run: npm install -g @jsdevtools/npm-publish
|
||||
- name: build
|
||||
env:
|
||||
token: ${{secrets.NPM_TOKEN}}
|
||||
run: node build/bin/publish-electerm-react.js
|
||||
@@ -0,0 +1,99 @@
|
||||
# Simplified workflow using custom electerm-builder Docker image
|
||||
name: linux-arm-legacy-docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, build-only, linux-arm-legacy, legacy-build ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
container: zxdong262/electerm-builder-legacy:latest # This will be our custom image
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip build]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip legacy]') && !contains(github.event.head_commit.message, '[skip linux-arm-legacy]') }}
|
||||
|
||||
steps:
|
||||
- name: Clone repository to /tmp and setup
|
||||
run: |
|
||||
cd /tmp
|
||||
git clone https://github.com/electerm/electerm.git electerm_build
|
||||
cd electerm_build
|
||||
git checkout ${{ github.sha }}
|
||||
|
||||
# Configure npm to use /tmp for cache and global packages
|
||||
mkdir -p /tmp/.npm /tmp/.npm-global
|
||||
npm config set cache /tmp/.npm
|
||||
npm config set prefix /tmp/.npm-global
|
||||
export PATH="/tmp/.npm-global/bin:$PATH"
|
||||
echo "PATH=/tmp/.npm-global/bin:$PATH" >> $GITHUB_ENV
|
||||
echo "npm_config_cache=/tmp/.npm" >> $GITHUB_ENV
|
||||
echo "npm_config_prefix=/tmp/.npm-global" >> $GITHUB_ENV
|
||||
|
||||
- name: Verify pre-installed versions
|
||||
working-directory: /tmp/electerm_build
|
||||
run: |
|
||||
echo "=== Pre-installed versions ==="
|
||||
node --version
|
||||
npm --version
|
||||
yarn --version
|
||||
python3 --version
|
||||
python --version
|
||||
gcc --version
|
||||
g++ --version
|
||||
fpm --version
|
||||
|
||||
- name: Clear electron-builder cache
|
||||
working-directory: /tmp/electerm_build
|
||||
run: rm -rf ~/.cache/electron-builder
|
||||
|
||||
- name: ci init
|
||||
working-directory: /tmp/electerm_build
|
||||
run: npm config set legacy-peer-deps true
|
||||
|
||||
- name: Update electron version and remove package-lock
|
||||
working-directory: /tmp/electerm_build
|
||||
run: |
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json'));
|
||||
pkg.devDependencies.electron = '22.3.27';
|
||||
pkg.devDependencies['electron-builder'] = '26.0.20';
|
||||
pkg.devDependencies['@electron/rebuild'] = '3.7.2';
|
||||
pkg.dependencies['node-pty'] = '0.10.1';
|
||||
pkg.dependencies.serialport = '10.5.0';
|
||||
pkg.devDependencies.vite = '4';
|
||||
pkg.devDependencies['@vitejs/plugin-react'] = '4.7.0';
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
|
||||
"
|
||||
rm -f package-lock.json
|
||||
|
||||
- name: Install npm dependencies
|
||||
working-directory: /tmp/electerm_build
|
||||
run: npm i && npm i -S @electron/rebuild@3.7.2
|
||||
|
||||
- name: Install R2 dependencies if needed
|
||||
working-directory: /tmp/electerm_build
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
run: npm i -D @aws-sdk/client-s3
|
||||
|
||||
- name: Build application
|
||||
working-directory: /tmp/electerm_build
|
||||
run: npm run b
|
||||
|
||||
- name: Run pb
|
||||
working-directory: /tmp/electerm_build
|
||||
run: npm run pb
|
||||
|
||||
- name: Build legacy packages
|
||||
working-directory: /tmp/electerm_build
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_NUMBER: ${{ secrets.BUILD_NUMBER }}
|
||||
USE_SYSTEM_FPM: true
|
||||
WORKFLOW_NAME: ${{ github.workflow }}
|
||||
CF_R2_ACCOUNT_ID: ${{ secrets.CF_R2_ACCOUNT_ID }}
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
CF_R2_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
CF_R2_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
run: node build/bin/build-linux-arm-legacy
|
||||
@@ -0,0 +1,111 @@
|
||||
# Alternative workflow using native ARM64 runners
|
||||
name: linux-arm-native
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, build-only, linux-arm ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip build]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip arm-linux]') && !contains(github.event.head_commit.message, '[skip linux3]') }}
|
||||
env:
|
||||
# Force electron-builder to use the system fpm (ARM native) instead of downloading x86 prebuilt bundle
|
||||
USE_SYSTEM_FPM: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js 16.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
python3-dev \
|
||||
build-essential \
|
||||
libnss3-dev \
|
||||
libsecret-1-dev \
|
||||
fakeroot \
|
||||
rpm \
|
||||
libgtk-3-0 \
|
||||
libnotify4 \
|
||||
libnss3 \
|
||||
libxss1 \
|
||||
libxtst6 \
|
||||
xdg-utils \
|
||||
libatspi2.0-0 \
|
||||
libuuid1 \
|
||||
libsecret-1-0 \
|
||||
libappindicator3-1 \
|
||||
ruby-full
|
||||
|
||||
- name: Install FPM
|
||||
run: sudo gem install fpm
|
||||
|
||||
- name: Verify glibc version
|
||||
run: ldd --version
|
||||
|
||||
- name: Verify fpm
|
||||
run: |
|
||||
which fpm
|
||||
fpm --version
|
||||
|
||||
- name: Install yarn
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: ci init
|
||||
run: npm config set legacy-peer-deps true
|
||||
|
||||
- name: Clear electron-builder cache
|
||||
run: rm -rf ~/.cache/electron-builder
|
||||
|
||||
- name: Update electron version and remove package-lock
|
||||
run: |
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json'));
|
||||
pkg.devDependencies.electron = '22.3.27';
|
||||
pkg.devDependencies['electron-builder'] = '26.0.20';
|
||||
pkg.devDependencies['@electron/rebuild'] = '3.7.2';
|
||||
pkg.dependencies['node-pty'] = '1.1.0-beta14';
|
||||
pkg.dependencies.serialport = '13.0.0';
|
||||
pkg.devDependencies.vite = '4';
|
||||
pkg.devDependencies['@vitejs/plugin-react'] = '4.7.0';
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
|
||||
"
|
||||
rm -f package-lock.json
|
||||
|
||||
- name: Install npm dependencies
|
||||
run: npm i && npm i -S @electron/rebuild@3.7.2
|
||||
|
||||
- name: Install R2 dependencies if needed
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
run: npm i -D @aws-sdk/client-s3
|
||||
|
||||
- name: Build application
|
||||
run: npm run b
|
||||
|
||||
- name: Run pb
|
||||
run: npm run pb
|
||||
|
||||
- name: Build ARM64 packages
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CF_R2_ACCOUNT_ID: ${{ secrets.CF_R2_ACCOUNT_ID }}
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
CF_R2_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
CF_R2_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
BUILD_NUMBER: ${{ secrets.BUILD_NUMBER }}
|
||||
WORKFLOW_NAME: ${{ github.workflow }}
|
||||
run: node build/bin/build-linux-arm
|
||||
@@ -0,0 +1,57 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: linux-deb-tar
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, build-only, linux-deb-tar ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-24.04
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip build]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip linux]') && !contains(github.event.head_commit.message, '[skip linux1]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 24.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24.x
|
||||
# - name: xvfb
|
||||
# uses: GabrielBB/xvfb-action@v1
|
||||
|
||||
# before_script:
|
||||
# - run: export DISPLAY=:99.0
|
||||
# - run: sh -e /etc/init.d/xvfb start &
|
||||
# - run: sleep 3
|
||||
|
||||
# before_install:
|
||||
- run: npm config set legacy-peer-deps true
|
||||
- run: npm install -g yarn
|
||||
- run: npm i
|
||||
|
||||
- name: Install R2 dependencies if needed
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
run: npm i -D @aws-sdk/client-s3
|
||||
|
||||
# script:
|
||||
- run: npm run b
|
||||
|
||||
- name: Run pb
|
||||
run: npm run pb
|
||||
|
||||
- name: build
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_NUMBER: ${{ secrets.BUILD_NUMBER }}
|
||||
WORKFLOW_NAME: ${{ github.workflow }}
|
||||
CF_R2_ACCOUNT_ID: ${{ secrets.CF_R2_ACCOUNT_ID }}
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
CF_R2_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
CF_R2_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
|
||||
run: node build/bin/build-linux-deb-tar
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
# Simplified workflow using custom electerm-builder Docker image
|
||||
name: linux-legacy-docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, build-only, linux-legacy, legacy-build ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: zxdong262/electerm-builder-legacy:latest # This will be our custom image
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip build]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip legacy]') && !contains(github.event.head_commit.message, '[skip linux-legacy]') }}
|
||||
|
||||
steps:
|
||||
- name: Clone repository to /tmp and setup
|
||||
run: |
|
||||
cd /tmp
|
||||
git clone https://github.com/electerm/electerm.git electerm_build
|
||||
cd electerm_build
|
||||
git checkout ${{ github.sha }}
|
||||
|
||||
# Configure npm to use /tmp for cache and global packages
|
||||
mkdir -p /tmp/.npm /tmp/.npm-global
|
||||
npm config set cache /tmp/.npm
|
||||
npm config set prefix /tmp/.npm-global
|
||||
export PATH="/tmp/.npm-global/bin:$PATH"
|
||||
echo "PATH=/tmp/.npm-global/bin:$PATH" >> $GITHUB_ENV
|
||||
echo "npm_config_cache=/tmp/.npm" >> $GITHUB_ENV
|
||||
echo "npm_config_prefix=/tmp/.npm-global" >> $GITHUB_ENV
|
||||
|
||||
- name: Verify pre-installed versions
|
||||
working-directory: /tmp/electerm_build
|
||||
run: |
|
||||
echo "=== Pre-installed versions ==="
|
||||
node --version
|
||||
npm --version
|
||||
yarn --version
|
||||
python3 --version
|
||||
python --version
|
||||
gcc --version
|
||||
g++ --version
|
||||
fpm --version
|
||||
|
||||
- name: Clear electron-builder cache
|
||||
working-directory: /tmp/electerm_build
|
||||
run: rm -rf ~/.cache/electron-builder
|
||||
|
||||
- name: ci init
|
||||
working-directory: /tmp/electerm_build
|
||||
run: npm config set legacy-peer-deps true
|
||||
|
||||
- name: Update electron version and remove package-lock
|
||||
working-directory: /tmp/electerm_build
|
||||
run: |
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json'));
|
||||
pkg.devDependencies.electron = '22.3.27';
|
||||
pkg.devDependencies['electron-builder'] = '26.0.20';
|
||||
pkg.devDependencies['@electron/rebuild'] = '3.7.2';
|
||||
pkg.dependencies['node-pty'] = '0.10.1';
|
||||
pkg.dependencies.serialport = '10.5.0';
|
||||
pkg.devDependencies.vite = '4';
|
||||
pkg.devDependencies['@vitejs/plugin-react'] = '4.7.0';
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
|
||||
fs.unlinkSync('package-lock.json');
|
||||
"
|
||||
|
||||
- name: Install npm dependencies
|
||||
working-directory: /tmp/electerm_build
|
||||
run: npm i && npm i -S @electron/rebuild@3.7.2
|
||||
|
||||
- name: Install R2 dependencies if needed
|
||||
working-directory: /tmp/electerm_build
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
run: npm i -D @aws-sdk/client-s3
|
||||
|
||||
- name: Verify updated dependencies
|
||||
working-directory: /tmp/electerm_build
|
||||
run: |
|
||||
echo "Verifying updated dependencies:"
|
||||
npm list electron --depth=0
|
||||
npm list @electron/rebuild --depth=0
|
||||
npm list node-pty --depth=0
|
||||
npm list serialport --depth=0
|
||||
npm list vite --depth=0
|
||||
|
||||
- name: Build application
|
||||
working-directory: /tmp/electerm_build
|
||||
run: npm run b
|
||||
|
||||
- name: Run pb
|
||||
working-directory: /tmp/electerm_build
|
||||
run: npm run pb
|
||||
|
||||
- name: Build legacy packages
|
||||
working-directory: /tmp/electerm_build
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_NUMBER: ${{ secrets.BUILD_NUMBER }}
|
||||
USE_SYSTEM_FPM: true
|
||||
WORKFLOW_NAME: ${{ github.workflow }}
|
||||
CF_R2_ACCOUNT_ID: ${{ secrets.CF_R2_ACCOUNT_ID }}
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
CF_R2_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
CF_R2_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
run: node build/bin/build-linux-legacy
|
||||
@@ -0,0 +1,142 @@
|
||||
# Build electerm for loong64 (LoongArch64) Linux - Legacy (old world)
|
||||
#
|
||||
# This workflow:
|
||||
# 1. Downloads GCC 8 cross-compiler from Loongnix for old-world ABI
|
||||
# 2. Builds x64 version with legacy deps to get the asar
|
||||
# 3. Downloads electron loong64 from Loongnix
|
||||
# 4. Cross-compile native modules for loong64 with legacy deps
|
||||
# 5. Merges x64 asar with loong64 electron and native modules
|
||||
# 6. Uploads tar.gz to GitHub release draft
|
||||
# 7. Builds deb package and uploads to GitHub release draft
|
||||
# 8. Optionally uploads to R2
|
||||
|
||||
name: linux-loong64-legacy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, build-only, linux-loong64-legacy ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip build]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip linux]') && !contains(github.event.head_commit.message, '[skip loong64]') && !contains(github.event.head_commit.message, '[skip legacy]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js 20.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Install loong64 cross-compiler and GCC 8 from Loongnix
|
||||
run: |
|
||||
sudo add-apt-repository -y universe
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-14-loongarch64-linux-gnu g++-14-loongarch64-linux-gnu binutils-loongarch64-linux-gnu || \
|
||||
sudo apt-get install -y gcc-loongarch64-linux-gnu g++-loongarch64-linux-gnu binutils-loongarch64-linux-gnu || true
|
||||
which loongarch64-linux-gnu-gcc 2>/dev/null || sudo ln -sf /usr/bin/loongarch64-linux-gnu-gcc-14 /usr/bin/loongarch64-linux-gnu-gcc || true
|
||||
which loongarch64-linux-gnu-g++ 2>/dev/null || sudo ln -sf /usr/bin/loongarch64-linux-gnu-g++-14 /usr/bin/loongarch64-linux-gnu-g++ || true
|
||||
|
||||
# Download GCC 8 from Loongnix for old-world ABI support
|
||||
# The Loongnix FTP server is unreliable and drops connections, so we retry with resume
|
||||
echo "Downloading GCC 8 cross-compiler from Loongnix..."
|
||||
URL="https://ftp.loongnix.cn/toolchain/gcc/release/loongarch/gcc8/loongson-gnu-toolchain-8.3-x86_64-loongarch64-linux-gnu-rc1.6.tar.xz"
|
||||
for i in 1 2 3 4 5; do
|
||||
echo "Attempt $i..."
|
||||
curl -L --retry 3 --retry-delay 5 -C - -o /tmp/gcc8-loong64.tar.xz "$URL" && break
|
||||
echo "Attempt $i failed, retrying..."
|
||||
sleep 10
|
||||
done
|
||||
sudo mkdir -p /opt/gcc8-loong64
|
||||
sudo tar xJf /tmp/gcc8-loong64.tar.xz -C /opt/gcc8-loong64 --strip-components=1
|
||||
echo "/opt/gcc8-loong64/bin" >> $GITHUB_PATH
|
||||
export PATH="/opt/gcc8-loong64/bin:$PATH"
|
||||
loongarch64-linux-gnu-gcc --version
|
||||
|
||||
- run: rm package-lock.json
|
||||
- run: npm config set legacy-peer-deps true
|
||||
|
||||
- name: Update package.json with legacy deps
|
||||
run: |
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json'));
|
||||
pkg.devDependencies.electron = '22.3.27';
|
||||
pkg.devDependencies['electron-builder'] = '26.0.20';
|
||||
pkg.devDependencies['@electron/rebuild'] = '3.7.2';
|
||||
pkg.dependencies['node-pty'] = '1.1.0';
|
||||
pkg.dependencies.serialport = '10.5.0';
|
||||
pkg.devDependencies.vite = '4';
|
||||
pkg.devDependencies['@vitejs/plugin-react'] = '4.7.0';
|
||||
console.log('Version:', pkg.version);
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
|
||||
"
|
||||
|
||||
- name: Install npm dependencies
|
||||
run: |
|
||||
npm config set legacy-peer-deps true
|
||||
npm install -g yarn
|
||||
npm i && npm i -S @electron/rebuild@3.7.2
|
||||
|
||||
- name: Verify updated dependencies
|
||||
run: |
|
||||
echo "Verifying updated dependencies:"
|
||||
npm list electron --depth=0
|
||||
npm list @electron/rebuild --depth=0
|
||||
npm list node-pty --depth=0
|
||||
npm list serialport --depth=0
|
||||
npm list vite --depth=0
|
||||
|
||||
- name: Build loong64 legacy
|
||||
env:
|
||||
WORKFLOW_NAME: linux-loong64-legacy
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_NUMBER: ${{ secrets.BUILD_NUMBER }}
|
||||
run: bash build/bin/build-linux-loong64-legacy.sh
|
||||
|
||||
- name: Verify output
|
||||
run: |
|
||||
ls -la dist-loong64-legacy/*.tar.gz dist-loong64-legacy/*.deb
|
||||
file dist-loong64-legacy/electerm-*/electerm
|
||||
echo "=== Native modules ==="
|
||||
find dist-loong64-legacy/electerm-* -name "*.node" -exec sh -c 'echo "--- $1 ---"; file "$1"; readelf -h "$1" 2>/dev/null | grep -E "Machine|Class"; readelf -d "$1" 2>/dev/null | grep NEEDED' _ {} \;
|
||||
echo "=== pty.node symbols (forkpty/openpty) ==="
|
||||
find dist-loong64-legacy/electerm-* -name "pty.node" -exec readelf -s {} \; 2>/dev/null | grep -iE "forkpty|openpty|pty" || true
|
||||
echo "=== Build logs ==="
|
||||
cat /tmp/node-pty-build.log 2>/dev/null | tail -50 || echo "No node-pty build log"
|
||||
cat /tmp/serialport-build.log 2>/dev/null | tail -30 || echo "No serialport build log"
|
||||
|
||||
# - name: Upload build logs
|
||||
# if: always()
|
||||
# uses: actions/upload-artifact@v4
|
||||
# with:
|
||||
# name: build-logs
|
||||
# path: /tmp/*-build.log
|
||||
# retention-days: 7
|
||||
|
||||
- name: Upload to R2
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
env:
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
run: |
|
||||
mkdir -p ~/.aws
|
||||
cat > ~/.aws/credentials << EOF
|
||||
[default]
|
||||
aws_access_key_id = ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
aws_secret_access_key = ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
EOF
|
||||
cat > ~/.aws/config << EOF
|
||||
[default]
|
||||
region = auto
|
||||
endpoint_url = ${{ secrets.CF_R2_ENDPOINT }}
|
||||
EOF
|
||||
for file in $(find dist-loong64-legacy \( -name "*.tar.gz" -o -name "*.deb" \) -type f); do
|
||||
filename=$(basename "$file")
|
||||
aws s3 cp "$file" "s3://${CF_R2_BUCKET}/${filename}" \
|
||||
--acl public-read \
|
||||
--cache-control "max-age=31536000"
|
||||
echo "Uploaded: ${filename}"
|
||||
done
|
||||
@@ -0,0 +1,94 @@
|
||||
# Build electerm for loong64 (LoongArch64) Linux
|
||||
#
|
||||
# This workflow:
|
||||
# 1. Builds x64 version with electron v39.2.7 to get the asar
|
||||
# 2. Downloads electron loong64 v39.2.7
|
||||
# 3. Cross-compiles native modules for loong64
|
||||
# 4. Merges x64 asar with loong64 electron and native modules
|
||||
# 5. Uploads tar.gz to GitHub release draft
|
||||
# 6. Builds deb package and uploads to GitHub release draft
|
||||
# 7. Optionally uploads to R2
|
||||
|
||||
name: linux-loong64
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, build-only, linux-loong64 ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip build]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip linux]') && !contains(github.event.head_commit.message, '[skip loong64]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js 24.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24.x
|
||||
|
||||
- name: Install loong64 cross-compiler
|
||||
run: |
|
||||
sudo add-apt-repository -y universe
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-14-loongarch64-linux-gnu g++-14-loongarch64-linux-gnu binutils-loongarch64-linux-gnu || \
|
||||
sudo apt-get install -y gcc-loongarch64-linux-gnu g++-loongarch64-linux-gnu binutils-loongarch64-linux-gnu
|
||||
which loongarch64-linux-gnu-gcc 2>/dev/null || sudo ln -sf /usr/bin/loongarch64-linux-gnu-gcc-14 /usr/bin/loongarch64-linux-gnu-gcc || true
|
||||
which loongarch64-linux-gnu-g++ 2>/dev/null || sudo ln -sf /usr/bin/loongarch64-linux-gnu-g++-14 /usr/bin/loongarch64-linux-gnu-g++ || true
|
||||
|
||||
- run: rm package-lock.json
|
||||
- run: npm config set legacy-peer-deps true
|
||||
- name: Update electron version to v39.2.7
|
||||
run: |
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json'));
|
||||
pkg.devDependencies.electron = '39.2.7';
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
||||
console.log('Electron version set to 39.2.7');
|
||||
"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm config set legacy-peer-deps true
|
||||
npm install -g yarn
|
||||
npm i
|
||||
|
||||
- name: Build loong64
|
||||
env:
|
||||
WORKFLOW_NAME: linux-loong64
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_NUMBER: ${{ secrets.BUILD_NUMBER }}
|
||||
run: bash build/bin/build-linux-loong64.sh
|
||||
|
||||
- name: Verify output
|
||||
run: |
|
||||
ls -la dist-loong64/*.tar.gz dist-loong64/*.deb
|
||||
file dist-loong64/electerm-*/electerm
|
||||
find dist-loong64/electerm-* -name "*.node" -exec file {} \; | grep LoongArch
|
||||
|
||||
- name: Upload to R2
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
env:
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
run: |
|
||||
mkdir -p ~/.aws
|
||||
cat > ~/.aws/credentials << EOF
|
||||
[default]
|
||||
aws_access_key_id = ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
aws_secret_access_key = ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
EOF
|
||||
cat > ~/.aws/config << EOF
|
||||
[default]
|
||||
region = auto
|
||||
endpoint_url = ${{ secrets.CF_R2_ENDPOINT }}
|
||||
EOF
|
||||
for file in $(find dist-loong64 \( -name "*.tar.gz" -o -name "*.deb" \) -type f); do
|
||||
filename=$(basename "$file")
|
||||
aws s3 cp "$file" "s3://${CF_R2_BUCKET}/${filename}" \
|
||||
--acl public-read \
|
||||
--cache-control "max-age=31536000"
|
||||
echo "Uploaded: ${filename}"
|
||||
done
|
||||
@@ -0,0 +1,70 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: linux-rpm-snap
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, build-only, linux-rpm-snap ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-24.04
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip build]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip linux]') && !contains(github.event.head_commit.message, '[skip linux2]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 24.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24.x
|
||||
|
||||
# - name: xvfb
|
||||
# uses: GabrielBB/xvfb-action@v1
|
||||
|
||||
- name: Install rpm
|
||||
run: sudo apt-get install rpm -y
|
||||
|
||||
- name: Install Snapcraft
|
||||
uses: samuelmeuli/action-snapcraft@master
|
||||
|
||||
# before_script:
|
||||
# - run: export DISPLAY=:99.0
|
||||
# - run: sh -e /etc/init.d/xvfb start &
|
||||
# - run: sleep 3
|
||||
|
||||
# before_install:
|
||||
- run: npm config set legacy-peer-deps true
|
||||
- run: npm install -g yarn
|
||||
- run: npm i
|
||||
# - run: npm i -D -E electron-builder@24.9.1
|
||||
|
||||
- name: Install R2 dependencies if needed
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
run: npm i -D @aws-sdk/client-s3
|
||||
|
||||
# script:
|
||||
- run: npm run b
|
||||
|
||||
- name: Run pb
|
||||
run: npm run pb
|
||||
|
||||
- name: build
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_NUMBER: ${{ secrets.BUILD_NUMBER }}
|
||||
WORKFLOW_NAME: ${{ github.workflow }}
|
||||
CF_R2_ACCOUNT_ID: ${{ secrets.CF_R2_ACCOUNT_ID }}
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
CF_R2_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
CF_R2_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
|
||||
run: node build/bin/build-linux-rpm-snap
|
||||
|
||||
- name: upload to snap store
|
||||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_TOKEN }}
|
||||
run: snapcraft upload --release=edge dist/*.snap
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: mac-10
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, build-only, mac-10, legacy-build ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: macos-15-intel
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip build]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip mac]') && !contains(github.event.head_commit.message, '[skip mac-10]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16.x
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
# before_install:
|
||||
- run: npm config set legacy-peer-deps true
|
||||
- run: npm install -g yarn
|
||||
- name: Update electron version and remove package-lock
|
||||
run: |
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json'));
|
||||
pkg.devDependencies.electron = '22.3.27';
|
||||
pkg.devDependencies['electron-builder'] = '26.0.20';
|
||||
pkg.devDependencies['@electron/rebuild'] = '3.7.2';
|
||||
pkg.dependencies['node-pty'] = '1.1.0-beta14';
|
||||
pkg.dependencies.serialport = '13.0.0';
|
||||
pkg.devDependencies.vite = '4';
|
||||
pkg.devDependencies['@vitejs/plugin-react'] = '4.7.0';
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
|
||||
fs.unlinkSync('package-lock.json');
|
||||
"
|
||||
- run: npm i && npm i -S @electron/rebuild@3.7.2
|
||||
|
||||
- name: Install R2 dependencies if needed
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
run: npm i -D @aws-sdk/client-s3
|
||||
|
||||
# script:
|
||||
- run: npm run b
|
||||
|
||||
- name: Run pb
|
||||
run: npm run pb
|
||||
|
||||
- name: build
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_NUMBER: ${{ secrets.BUILD_NUMBER }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLEID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLEIDPASS }}
|
||||
APPLE_TEAM_ID: ${{secrets.APPLE_TEAM_ID}}
|
||||
WORKFLOW_NAME: ${{ github.workflow }}
|
||||
CF_R2_ACCOUNT_ID: ${{ secrets.CF_R2_ACCOUNT_ID }}
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
CF_R2_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
CF_R2_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
run: node build/bin/build-mac-10
|
||||
@@ -0,0 +1,57 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: mac-arm
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, build-only, mac-arm ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: macos-15
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip build]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip mac]') && !contains(github.event.head_commit.message, '[skip arm]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 24.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24.x
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
# before_install:
|
||||
- run: pip install setuptools
|
||||
- run: npm config set legacy-peer-deps true
|
||||
- run: npm install -g yarn
|
||||
- run: npm i
|
||||
|
||||
- name: Install R2 dependencies if needed
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
run: npm i -D @aws-sdk/client-s3
|
||||
|
||||
# script:
|
||||
- run: npm run b
|
||||
|
||||
- name: Run pb
|
||||
run: npm run pb
|
||||
|
||||
- name: build
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_NUMBER: ${{ secrets.BUILD_NUMBER }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLEID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLEIDPASS }}
|
||||
APPLE_TEAM_ID: ${{secrets.APPLE_TEAM_ID}}
|
||||
WORKFLOW_NAME: ${{ github.workflow }}
|
||||
CF_R2_ACCOUNT_ID: ${{ secrets.CF_R2_ACCOUNT_ID }}
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
CF_R2_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
CF_R2_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
run: node build/bin/build-mac-arm
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: mac-x64-test-1
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, test-only, test1 ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: macos-15-intel
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip test]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip mac]') && !contains(github.event.head_commit.message, '[skip test1]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 24.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24.15.0
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
# before_install:
|
||||
- run: pip install setuptools
|
||||
- run: npm config set legacy-peer-deps true
|
||||
- run: npm install -g yarn
|
||||
- run: npm i
|
||||
- run: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm i -D -E playwright@1.49.1 && npm i -D -E @playwright/test@1.49.1
|
||||
|
||||
# script:
|
||||
- run: npm run b
|
||||
|
||||
- name: Run pb
|
||||
run: npm run pb
|
||||
- name: test
|
||||
uses: GabrielBB/xvfb-action@v1
|
||||
with:
|
||||
run: npm run test1
|
||||
env:
|
||||
NODE_TEST: 1
|
||||
TEST_HOST: ${{ secrets.TEST_HOST }}
|
||||
TEST_USER: ${{ secrets.TEST_USER_1 }}
|
||||
TEST_PASS: ${{ secrets.TEST_PASS_1 }}
|
||||
GIST_TOKEN: ${{ secrets.GIST_TOKEN }}
|
||||
GIST_ID: ${{ secrets.GIST_ID }}
|
||||
GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }}
|
||||
GITEE_ID: ${{ secrets.GITEE_ID }}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: mac-x64-test-2
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, test-only ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: macos-15-intel
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip test]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip mac]') && !contains(github.event.head_commit.message, '[skip test2]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 24.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24.15.0
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
# before_install:
|
||||
- run: pip install setuptools
|
||||
- run: npm config set legacy-peer-deps true
|
||||
- run: npm install -g yarn
|
||||
- run: npm i
|
||||
- run: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm i -D -E playwright@1.49.1 && npm i -D -E @playwright/test@1.49.1
|
||||
|
||||
# script:
|
||||
- run: npm run b
|
||||
|
||||
- name: Run pb
|
||||
run: npm run pb
|
||||
|
||||
- name: test
|
||||
uses: GabrielBB/xvfb-action@v1
|
||||
with:
|
||||
run: npm run test2
|
||||
env:
|
||||
NODE_TEST: 1
|
||||
TEST_HOST: ${{ secrets.TEST_HOST }}
|
||||
TEST_USER: ${{ secrets.TEST_USER_2 }}
|
||||
TEST_PASS: ${{ secrets.TEST_PASS_2 }}
|
||||
GIST_TOKEN: ${{ secrets.GIST_TOKEN }}
|
||||
GIST_ID: ${{ secrets.GIST_ID }}
|
||||
GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }}
|
||||
GITEE_ID: ${{ secrets.GITEE_ID }}
|
||||
CUSTOM_SYNC_URL: ${{ secrets.CUSTOM_SYNC_URL }}
|
||||
CUSTOM_SYNC_USER: ${{ secrets.CUSTOM_SYNC_USER }}
|
||||
CUSTOM_SYNC_SECRET: ${{ secrets.CUSTOM_SYNC_SECRET }}
|
||||
CLOUD_TOKEN: ${{ secrets.CLOUD_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: mac-x64-test-3
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, test-only, test3 ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: macos-15-intel
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip test]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip mac]') && !contains(github.event.head_commit.message, '[skip test3]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 24.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24.15.0
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
# before_install:
|
||||
- run: pip install setuptools
|
||||
- run: npm config set legacy-peer-deps true
|
||||
- run: npm install -g yarn
|
||||
- run: npm i
|
||||
- run: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm i -D -E playwright@1.49.1 && npm i -D -E @playwright/test@1.49.1
|
||||
|
||||
# script:
|
||||
- run: npm run b
|
||||
|
||||
- name: Run pb
|
||||
run: npm run pb
|
||||
|
||||
- name: test
|
||||
uses: GabrielBB/xvfb-action@v1
|
||||
with:
|
||||
run: npm run test3
|
||||
env:
|
||||
NODE_TEST: 1
|
||||
TEST_HOST: ${{ secrets.TEST_HOST }}
|
||||
TEST_USER: ${{ secrets.TEST_USER_3 }}
|
||||
TEST_PASS: ${{ secrets.TEST_PASS_3 }}
|
||||
GIST_TOKEN: ${{ secrets.GIST_TOKEN }}
|
||||
GIST_ID: ${{ secrets.GIST_ID }}
|
||||
GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }}
|
||||
GITEE_ID: ${{ secrets.GITEE_ID }}
|
||||
CUSTOM_SYNC_URL: ${{ secrets.CUSTOM_SYNC_URL }}
|
||||
CUSTOM_SYNC_USER: ${{ secrets.CUSTOM_SYNC_USER }}
|
||||
CUSTOM_SYNC_SECRET: ${{ secrets.CUSTOM_SYNC_SECRET }}
|
||||
CLOUD_TOKEN: ${{ secrets.CLOUD_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: mac-x64
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, build-only, mac-x64 ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: macos-15-intel
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip build]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip mac]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 24.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24.x
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
# before_install:
|
||||
- run: npm config set legacy-peer-deps true
|
||||
- run: npm install -g yarn
|
||||
- run: npm i
|
||||
|
||||
- name: Install R2 dependencies if needed
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
run: npm i -D @aws-sdk/client-s3
|
||||
|
||||
# script:
|
||||
- run: npm run b
|
||||
|
||||
- name: Run pb
|
||||
run: npm run pb
|
||||
|
||||
- name: build
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_NUMBER: ${{ secrets.BUILD_NUMBER }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLEID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLEIDPASS }}
|
||||
APPLE_TEAM_ID: ${{secrets.APPLE_TEAM_ID}}
|
||||
WORKFLOW_NAME: ${{ github.workflow }}
|
||||
CF_R2_ACCOUNT_ID: ${{ secrets.CF_R2_ACCOUNT_ID }}
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
CF_R2_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
CF_R2_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
run: node build/bin/build-mac
|
||||
|
||||
@@ -0,0 +1,504 @@
|
||||
# Upload APPX to Microsoft Store after a new release is published.
|
||||
# Downloads the .appx artifact from the GitHub release and submits
|
||||
# a new Store submission via the Partner Center Ingestion API.
|
||||
|
||||
name: ms-store-upload
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
push:
|
||||
branches: [ ms ]
|
||||
|
||||
jobs:
|
||||
upload-to-store:
|
||||
runs-on: windows-2022
|
||||
environment: build
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js 24.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24.x
|
||||
|
||||
- name: Install R2 dependencies if store upload may run
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
run: npm i -D @aws-sdk/client-s3
|
||||
|
||||
- name: Determine release tag
|
||||
id: tag
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
$tag = "${{ github.event.release.tag_name }}"
|
||||
if (-not $tag) {
|
||||
Write-Host "No release tag from event, fetching latest release..."
|
||||
$tag = (gh release view --repo "${{ github.repository }}" --json tagName -q '.tagName')
|
||||
if (-not $tag) { throw 'Could not determine release tag.' }
|
||||
}
|
||||
Write-Host "Release tag: $tag"
|
||||
"tag_name=$tag" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Download APPX from release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
$tag = "${{ steps.tag.outputs.tag_name }}"
|
||||
New-Item -ItemType Directory -Force -Path dist | Out-Null
|
||||
gh release download "$tag" --repo "${{ github.repository }}" --pattern '*.appx' -D dist
|
||||
Write-Host 'Downloaded APPX files:'
|
||||
Get-ChildItem -Path dist -Filter '*.appx' -Recurse | Format-Table FullName, Length
|
||||
|
||||
- name: Setup Microsoft Store Developer CLI
|
||||
uses: microsoft/microsoft-store-apppublisher@v1.1
|
||||
|
||||
- name: Configure Microsoft Store Developer CLI
|
||||
env:
|
||||
MS_TENANT_ID: ${{ secrets.MS_TENANT_ID }}
|
||||
MS_SELLER_ID: ${{ secrets.MS_SELLER_ID }}
|
||||
MS_CLIENT_ID: ${{ secrets.MS_CLIENT_ID }}
|
||||
MS_CLIENT_SECRET: ${{ secrets.MS_CLIENT_SECRET }}
|
||||
run: msstore reconfigure --tenantId "$env:MS_TENANT_ID" --sellerId "$env:MS_SELLER_ID" --clientId "$env:MS_CLIENT_ID" --clientSecret "$env:MS_CLIENT_SECRET"
|
||||
shell: pwsh
|
||||
|
||||
- name: Debug Microsoft Store app access
|
||||
continue-on-error: true
|
||||
env:
|
||||
MS_PRODUCT_ID: ${{ secrets.MS_PRODUCT_ID }}
|
||||
MS_APP_ID: ${{ secrets.MS_APP_ID }}
|
||||
MSSTORE_LOG_FILE: dist/msstore-logs/msstore-${{ github.run_id }}-${{ github.run_attempt }}.log
|
||||
run: |
|
||||
function Get-VisibleProductIds([string] $appsListOutput) {
|
||||
$cleanOutput = [regex]::Replace($appsListOutput, '\x1B\[[0-9;?]*[ -/]*[@-~]', '')
|
||||
return @(
|
||||
$cleanOutput -split "`r?`n" |
|
||||
Where-Object { $_.Trim().StartsWith('│') } |
|
||||
ForEach-Object {
|
||||
$columns = $_.Trim().Split('│') | ForEach-Object { $_.Trim() }
|
||||
if ($columns.Count -ge 5) {
|
||||
$productId = $columns[2]
|
||||
$packageId = $columns[4]
|
||||
if ($productId -and $productId -ne 'ProductId' -and $packageId -and $packageId -ne 'PackageId') {
|
||||
$productId
|
||||
}
|
||||
}
|
||||
} |
|
||||
Select-Object -Unique
|
||||
)
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $env:MSSTORE_LOG_FILE) | Out-Null
|
||||
Set-Content -Path $env:MSSTORE_LOG_FILE -Value "=== Microsoft Store debug log $(Get-Date -Format o) ==="
|
||||
|
||||
function Write-StoreLog([string] $message) {
|
||||
Add-Content -Path $env:MSSTORE_LOG_FILE -Value $message
|
||||
}
|
||||
|
||||
$msstoreVersion = & msstore --version 2>&1 | Out-String
|
||||
Write-Host "msstore CLI version: $msstoreVersion"
|
||||
Write-StoreLog "msstore CLI version: $msstoreVersion"
|
||||
|
||||
$configuredProductId = "$env:MS_PRODUCT_ID".Trim()
|
||||
if (-not $configuredProductId) {
|
||||
$configuredProductId = "$env:MS_APP_ID".Trim()
|
||||
}
|
||||
|
||||
if ($configuredProductId) {
|
||||
Write-Host "Configured Store Product ID: $configuredProductId"
|
||||
} else {
|
||||
Write-Host 'Configured Store Product ID: <empty>'
|
||||
}
|
||||
|
||||
Write-Host 'Package identifiers in this repo are not valid Store Product IDs:'
|
||||
Write-Host ' Electron appId: org.electerm.electerm'
|
||||
Write-Host ' APPX applicationId: electerm'
|
||||
Write-Host ' APPX identityName: 64159ZHAOXudong.electerm'
|
||||
Write-Host 'Accessible Microsoft Store packaged apps for this account:'
|
||||
Write-StoreLog "Configured Store Product ID: $configuredProductId"
|
||||
Write-StoreLog 'Package identifiers in this repo are not valid Store Product IDs:'
|
||||
Write-StoreLog ' Electron appId: org.electerm.electerm'
|
||||
Write-StoreLog ' APPX applicationId: electerm'
|
||||
Write-StoreLog ' APPX identityName: 64159ZHAOXudong.electerm'
|
||||
Write-StoreLog 'Accessible Microsoft Store packaged apps for this account:'
|
||||
|
||||
Write-StoreLog ''
|
||||
Write-StoreLog "=== msstore apps list $(Get-Date -Format o) ==="
|
||||
$appsListOutput = (& msstore apps list 2>&1 | Tee-Object -FilePath $env:MSSTORE_LOG_FILE -Append | Out-String)
|
||||
$appsListExitCode = $LASTEXITCODE
|
||||
$global:LASTEXITCODE = 0
|
||||
Write-Host $appsListOutput
|
||||
Write-StoreLog "msstore apps list exit code: $appsListExitCode"
|
||||
|
||||
$productIds = Get-VisibleProductIds $appsListOutput
|
||||
|
||||
if ($appsListExitCode -ne 0) {
|
||||
Write-Host "msstore apps list exited with code $appsListExitCode. This usually means the configured Entra app can authenticate but does not have Partner Center access to packaged apps for this seller account."
|
||||
} elseif ($productIds.Count -eq 0) {
|
||||
Write-Host 'msstore apps list succeeded but no packaged Product IDs were detected. This usually means no packaged Store apps are visible to this account.'
|
||||
} elseif (-not $configuredProductId) {
|
||||
if ($productIds.Count -eq 1) {
|
||||
Write-Host "Exactly one packaged Product ID is visible: $($productIds[0])"
|
||||
} else {
|
||||
Write-Host "Multiple packaged Product IDs are visible: $($productIds -join ', ')"
|
||||
}
|
||||
} elseif ($configuredProductId -notin $productIds) {
|
||||
Write-Host "Configured Store Product ID '$configuredProductId' was not found in the accessible app list."
|
||||
Write-Host "Visible Product IDs: $($productIds -join ', ')"
|
||||
} else {
|
||||
Write-Host "Configured Store Product ID '$configuredProductId' is visible to this account."
|
||||
}
|
||||
shell: pwsh
|
||||
|
||||
- name: Upload APPX to Microsoft Store and submit for certification
|
||||
env:
|
||||
MS_PRODUCT_ID: ${{ secrets.MS_PRODUCT_ID }}
|
||||
MS_APP_ID: ${{ secrets.MS_APP_ID }}
|
||||
MS_TENANT_ID: ${{ secrets.MS_TENANT_ID }}
|
||||
MS_CLIENT_ID: ${{ secrets.MS_CLIENT_ID }}
|
||||
MS_CLIENT_SECRET: ${{ secrets.MS_CLIENT_SECRET }}
|
||||
MSSTORE_LOG_FILE: dist/msstore-logs/msstore-${{ github.run_id }}-${{ github.run_attempt }}.log
|
||||
run: |
|
||||
# ── Initialise log file ─────────────────────────────────────────────────
|
||||
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $env:MSSTORE_LOG_FILE) | Out-Null
|
||||
Set-Content -Path $env:MSSTORE_LOG_FILE -Value "=== ms-store-upload log $(Get-Date -Format o) ==="
|
||||
|
||||
function Write-StoreLog([string] $message) {
|
||||
Add-Content -Path $env:MSSTORE_LOG_FILE -Value $message
|
||||
}
|
||||
|
||||
# Retry wrapper for Invoke-RestMethod with exponential backoff.
|
||||
# The Partner Center Ingestion API is behind Azure Front Door and can
|
||||
# return transient 504 / 502 / 503 responses under load.
|
||||
function Invoke-WithRetry {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)] [scriptblock] $Action,
|
||||
[string] $Description = 'API call',
|
||||
[int] $MaxRetries = 5,
|
||||
[int] $BaseDelaySec = 10
|
||||
)
|
||||
for ($i = 0; $i -lt $MaxRetries; $i++) {
|
||||
try {
|
||||
return & $Action
|
||||
} catch {
|
||||
$statusCode = $null
|
||||
if ($_.Exception.Response) {
|
||||
$statusCode = [int]$_.Exception.Response.StatusCode
|
||||
}
|
||||
$isTransient = $false
|
||||
if ($statusCode -in 408, 429, 500, 502, 503, 504) { $isTransient = $true }
|
||||
if ($_.Exception.Message -match 'timed?\s*out|gateway|service unavailable|connection was closed|unable to connect') { $isTransient = $true }
|
||||
$attempt = $i + 1
|
||||
if ($isTransient -and $attempt -lt $MaxRetries) {
|
||||
$delay = $BaseDelaySec * [Math]::Pow(2, $i)
|
||||
Write-Host " ⚠ ${Description}: transient error (HTTP $statusCode) on attempt $attempt/$MaxRetries — retrying in ${delay}s..."
|
||||
Write-StoreLog "${Description}: transient error (HTTP $statusCode) attempt $attempt/$MaxRetries, retry in ${delay}s. $($_.Exception.Message)"
|
||||
Start-Sleep -Seconds $delay
|
||||
} else {
|
||||
Write-StoreLog "${Description}: FAILED on attempt $attempt (HTTP $statusCode). $($_.Exception.Message)"
|
||||
throw
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-VisibleProductIds([string] $appsListOutput) {
|
||||
$cleanOutput = [regex]::Replace($appsListOutput, '\x1B\[[0-9;?]*[ -/]*[@-~]', '')
|
||||
return @(
|
||||
$cleanOutput -split "`r?`n" |
|
||||
Where-Object { $_.Trim().StartsWith('│') } |
|
||||
ForEach-Object {
|
||||
$columns = $_.Trim().Split('│') | ForEach-Object { $_.Trim() }
|
||||
if ($columns.Count -ge 5) {
|
||||
$productId = $columns[2]
|
||||
$packageId = $columns[4]
|
||||
if ($productId -and $productId -ne 'ProductId' -and $packageId -and $packageId -ne 'PackageId') {
|
||||
$productId
|
||||
}
|
||||
}
|
||||
} |
|
||||
Select-Object -Unique
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
$appx = Get-ChildItem -Path dist -Filter '*.appx' -Recurse | Select-Object -First 1
|
||||
if (-not $appx) { throw 'No .appx file found under dist/' }
|
||||
|
||||
Write-StoreLog ''
|
||||
Write-StoreLog "=== publish preflight $(Get-Date -Format o) ==="
|
||||
Write-StoreLog "APPX file: $($appx.FullName)"
|
||||
|
||||
# Soft preflight: enumerate accessible packaged apps.
|
||||
# 'msstore apps list' can fail on its own (e.g. a CLI regression pulled in by
|
||||
# 'version: latest') even when the Entra app and the configured Product ID are
|
||||
# perfectly valid. The actual submission below calls the Dev Center Ingestion API
|
||||
# directly and does not depend on this command, so when MS_PRODUCT_ID is explicitly
|
||||
# configured we trust it and proceed instead of aborting the whole build.
|
||||
$appsListOutput = (& msstore apps list 2>&1 | Tee-Object -FilePath $env:MSSTORE_LOG_FILE -Append | Out-String)
|
||||
$appsListExitCode = $LASTEXITCODE
|
||||
$global:LASTEXITCODE = 0
|
||||
Write-StoreLog "msstore apps list exit code: $appsListExitCode"
|
||||
$productIds = Get-VisibleProductIds $appsListOutput
|
||||
|
||||
$productId = "$env:MS_PRODUCT_ID".Trim()
|
||||
if (-not $productId) {
|
||||
$productId = "$env:MS_APP_ID".Trim()
|
||||
}
|
||||
|
||||
if (-not $productId) {
|
||||
# No explicitly configured id: we must rely on the app list.
|
||||
if ($appsListExitCode -eq 0 -and $productIds.Count -eq 1) {
|
||||
$productId = $productIds[0]
|
||||
Write-Host "Resolved MS Product ID from account app list: $productId"
|
||||
} else {
|
||||
$visibleProductIds = if ($productIds.Count) { $productIds -join ', ' } else { 'none detected' }
|
||||
throw "Missing MS_PRODUCT_ID or MS_APP_ID secret and could not resolve a single Product ID from 'msstore apps list' (exit $appsListExitCode). Visible Product IDs for this account: $visibleProductIds"
|
||||
}
|
||||
} elseif ($appsListExitCode -ne 0 -or $productIds.Count -eq 0) {
|
||||
Write-Host "WARNING: 'msstore apps list' failed (exit $appsListExitCode) but MS_PRODUCT_ID is explicitly configured; proceeding with the configured Product ID."
|
||||
Write-StoreLog "WARNING: 'msstore apps list' failed (exit $appsListExitCode); proceeding with configured Product ID: $productId"
|
||||
} elseif ($productId -notin $productIds) {
|
||||
Write-Host "WARNING: Configured Store Product ID '$productId' was not found in the accessible app list; proceeding with the configured value anyway."
|
||||
Write-StoreLog "WARNING: Configured Store Product ID '$productId' not in accessible app list; proceeding."
|
||||
} else {
|
||||
Write-Host "Configured Store Product ID '$productId' is visible to this account."
|
||||
}
|
||||
|
||||
$knownWrongAppIds = @(
|
||||
'org.electerm.electerm',
|
||||
'electerm',
|
||||
'64159ZHAOXudong.electerm'
|
||||
)
|
||||
if ($knownWrongAppIds -contains $productId) {
|
||||
throw "Configured store identifier '$productId' is a package identifier. Use the Microsoft Store Partner Center product id instead."
|
||||
}
|
||||
|
||||
Write-Host "Resolved publish Product ID: $productId"
|
||||
Write-StoreLog "Resolved publish Product ID: $productId"
|
||||
|
||||
# ── Direct Ingestion API publish ───────────────────────────────────────
|
||||
# msstore publish copies listing features verbatim from the live submission.
|
||||
# If any locale has >20 features the Ingestion API rejects the update.
|
||||
# We bypass msstore publish and call the API directly so we can cap features.
|
||||
|
||||
Write-StoreLog ''
|
||||
Write-StoreLog "=== direct-api publish $(Get-Date -Format o) ==="
|
||||
|
||||
# 1. Get OAuth token for the Partner Center Ingestion API
|
||||
$tokenParts = @(
|
||||
"grant_type=client_credentials",
|
||||
"client_id=$([uri]::EscapeDataString($env:MS_CLIENT_ID))",
|
||||
"client_secret=$([uri]::EscapeDataString($env:MS_CLIENT_SECRET))",
|
||||
"scope=$([uri]::EscapeDataString('https://manage.devcenter.microsoft.com/.default'))"
|
||||
)
|
||||
$tokenResp = Invoke-WithRetry -Description 'Get OAuth token' {
|
||||
Invoke-RestMethod `
|
||||
-Uri "https://login.microsoftonline.com/$env:MS_TENANT_ID/oauth2/v2.0/token" `
|
||||
-Method Post -ContentType 'application/x-www-form-urlencoded' `
|
||||
-Body ($tokenParts -join '&')
|
||||
}
|
||||
$token = $tokenResp.access_token
|
||||
Write-StoreLog "OAuth token acquired."
|
||||
$h = @{ Authorization = "Bearer $token" }
|
||||
$apiBase = "https://manage.devcenter.microsoft.com/v1.0/my/applications/$productId"
|
||||
|
||||
# 2. Get app info; delete any pending submission
|
||||
Write-Host "Getting app info..."
|
||||
$appInfo = Invoke-WithRetry -Description 'Get app info' { Invoke-RestMethod -Uri $apiBase -Headers $h }
|
||||
Write-StoreLog "App info fetched. lastPublishedSubmission: $($appInfo.lastPublishedApplicationSubmission.id)"
|
||||
if ($appInfo.pendingApplicationSubmission.id) {
|
||||
$pendingId = $appInfo.pendingApplicationSubmission.id
|
||||
Write-Host "Deleting pending submission $pendingId ..."
|
||||
Write-StoreLog "Deleting pending submission: $pendingId"
|
||||
Invoke-WithRetry -Description 'Delete pending submission' { Invoke-RestMethod -Uri "$apiBase/submissions/$pendingId" -Method Delete -Headers $h } | Out-Null
|
||||
Write-Host "Deleted pending submission."
|
||||
Write-StoreLog "Deleted pending submission."
|
||||
}
|
||||
|
||||
# 3. Fetch last published submission to use as the body for POST /submissions.
|
||||
# The API does NOT auto-clone — it requires the full submission object.
|
||||
$lastPubSubId = $appInfo.lastPublishedApplicationSubmission.id
|
||||
if (-not $lastPubSubId) { throw 'No published submission found to clone from.' }
|
||||
Write-Host "Fetching published submission $lastPubSubId as template..."
|
||||
Write-StoreLog "Fetching published submission: $lastPubSubId"
|
||||
$pubSub = Invoke-WithRetry -Description 'Fetch published submission template' { Invoke-RestMethod -Uri "$apiBase/submissions/$lastPubSubId" -Headers $h }
|
||||
|
||||
# 4. Create new submission using the published submission data as the request body.
|
||||
# A 504/502 gateway timeout can still result in the submission being created
|
||||
# server-side. A naive retry would then fail with HTTP 409 Conflict
|
||||
# (InvalidState) because the Ingestion API only allows one pending
|
||||
# submission at a time. After any failure we re-fetch app info and reuse
|
||||
# the pending submission if one now exists instead of blindly retrying.
|
||||
Write-Host "Creating new submission..."
|
||||
Write-StoreLog "Creating new submission (POST with published data as body)..."
|
||||
$pubSubBytes = [System.Text.Encoding]::UTF8.GetBytes(($pubSub | ConvertTo-Json -Depth 30 -Compress))
|
||||
$newSub = $null
|
||||
$maxCreateRetries = 5
|
||||
$submissionReady = $false
|
||||
for ($i = 0; $i -lt $maxCreateRetries -and -not $submissionReady; $i++) {
|
||||
try {
|
||||
$newSub = Invoke-RestMethod -Uri "$apiBase/submissions" -Method Post -Headers $h -ContentType 'application/json' -Body $pubSubBytes
|
||||
$submissionReady = $true
|
||||
} catch {
|
||||
$statusCode = $null
|
||||
if ($_.Exception.Response) {
|
||||
$statusCode = [int]$_.Exception.Response.StatusCode
|
||||
}
|
||||
$errBody = ''
|
||||
if ($_.ErrorDetails -and $_.ErrorDetails.Message) {
|
||||
$errBody = $_.ErrorDetails.Message
|
||||
}
|
||||
$attempt = $i + 1
|
||||
$isRecoverable = ($statusCode -in 409, 502, 503, 504) -or
|
||||
($errBody -match 'Conflict|InvalidState') -or
|
||||
($_.Exception.Message -match 'timed?\s*out|gateway|service unavailable|connection was closed')
|
||||
if ($isRecoverable -and $attempt -lt $maxCreateRetries) {
|
||||
$delay = 10 * [Math]::Pow(2, $i)
|
||||
Write-Host " ⚠ Create new submission: HTTP $statusCode on attempt $attempt/$maxCreateRetries — checking for pending submission in ${delay}s..."
|
||||
Write-StoreLog "Create new submission: HTTP $statusCode attempt $attempt/$maxCreateRetries, retry in ${delay}s. $errBody"
|
||||
Start-Sleep -Seconds $delay
|
||||
# Re-fetch app info — the failed POST may have created a pending submission.
|
||||
# Use Invoke-WithRetry because the API may be experiencing widespread 504s.
|
||||
try {
|
||||
$appInfoRetry = Invoke-WithRetry -Description 'Re-fetch app info for pending submission' { Invoke-RestMethod -Uri $apiBase -Headers $h }
|
||||
if ($appInfoRetry.pendingApplicationSubmission.id) {
|
||||
$pendingSubId = $appInfoRetry.pendingApplicationSubmission.id
|
||||
Write-Host " Found pending submission $pendingSubId — reusing it."
|
||||
Write-StoreLog "Reusing existing pending submission: $pendingSubId"
|
||||
$pendingSub = Invoke-WithRetry -Description 'Fetch pending submission details' { Invoke-RestMethod -Uri "$apiBase/submissions/$pendingSubId" -Headers $h }
|
||||
$newSub = [PSCustomObject]@{
|
||||
id = $pendingSubId
|
||||
fileUploadUrl = $pendingSub.fileUploadUrl
|
||||
}
|
||||
$submissionReady = $true
|
||||
}
|
||||
} catch {
|
||||
Write-Host " Could not re-fetch app info or submission: $($_.Exception.Message)"
|
||||
Write-StoreLog "Re-fetch failed: $($_.Exception.Message)"
|
||||
}
|
||||
} else {
|
||||
Write-StoreLog "Create new submission: FAILED on attempt $attempt (HTTP $statusCode). $errBody"
|
||||
throw
|
||||
}
|
||||
}
|
||||
}
|
||||
if (-not $newSub) { throw 'Failed to create new submission after retries.' }
|
||||
$subId = $newSub.id
|
||||
$fileUploadUrl = $newSub.fileUploadUrl
|
||||
Write-Host "Submission ID: $subId"
|
||||
Write-StoreLog "Submission ID: $subId"
|
||||
|
||||
# 5. Get full data for the new submission
|
||||
$subData = Invoke-WithRetry -Description 'Get new submission data' { Invoke-RestMethod -Uri "$apiBase/submissions/$subId" -Headers $h }
|
||||
|
||||
# 6. Replace packages: mark existing for deletion, add new one as PendingUpload
|
||||
$newPackages = @()
|
||||
foreach ($pkg in @($subData.applicationPackages)) {
|
||||
$pkg.fileStatus = 'PendingDelete'
|
||||
$newPackages += $pkg
|
||||
}
|
||||
$newPackages += [PSCustomObject]@{ fileName = $appx.Name; fileStatus = 'PendingUpload' }
|
||||
$subData.applicationPackages = $newPackages
|
||||
Write-StoreLog "applicationPackages: $($newPackages.Count) entries (last = new PendingUpload)."
|
||||
|
||||
# Auto-publish after certification passes.
|
||||
$subData.targetPublishMode = 'Immediate'
|
||||
|
||||
# 7. Push updated submission metadata
|
||||
Write-Host "Updating submission metadata..."
|
||||
Write-StoreLog "PUT submission metadata..."
|
||||
$updateBodyBytes = [System.Text.Encoding]::UTF8.GetBytes(($subData | ConvertTo-Json -Depth 30 -Compress))
|
||||
Invoke-WithRetry -Description 'PUT submission metadata' {
|
||||
Invoke-RestMethod -Uri "$apiBase/submissions/$subId" -Method Put -Headers $h `
|
||||
-ContentType 'application/json' -Body $updateBodyBytes
|
||||
} | Out-Null
|
||||
Write-Host "Submission metadata updated."
|
||||
Write-StoreLog "Submission metadata updated."
|
||||
|
||||
# 8. Create ZIP and upload to Azure Blob SAS URL
|
||||
# (SAS URL carries its own auth — do not include the Bearer token here)
|
||||
$zipPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'electerm-appx-upload.zip')
|
||||
if (Test-Path $zipPath) { Remove-Item $zipPath }
|
||||
Compress-Archive -Path $appx.FullName -DestinationPath $zipPath
|
||||
$zipSizeMB = [Math]::Round((Get-Item $zipPath).Length / 1MB, 1)
|
||||
Write-Host "Uploading package ZIP ($zipSizeMB MB)..."
|
||||
Write-StoreLog "Uploading package ZIP ($zipSizeMB MB) to Azure Blob SAS URL..."
|
||||
$blobHeaders = @{ 'x-ms-blob-type' = 'BlockBlob'; 'Content-Type' = 'application/octet-stream' }
|
||||
Invoke-WithRetry -Description 'Upload package ZIP to Azure Blob' { Invoke-RestMethod -Uri $fileUploadUrl -Method Put -Headers $blobHeaders -InFile $zipPath } | Out-Null
|
||||
Write-Host "Package uploaded."
|
||||
Write-StoreLog "Package uploaded."
|
||||
|
||||
# 9. Commit submission to trigger certification.
|
||||
# targetPublishMode = 'Immediate' means it will go live automatically after passing certification.
|
||||
Write-Host "Committing submission to trigger certification..."
|
||||
Write-StoreLog "Committing submission (Immediate publish mode)..."
|
||||
$commitResp = Invoke-WithRetry -Description 'Commit submission' { Invoke-RestMethod -Uri "$apiBase/submissions/$subId/commit" -Method Post -Headers $h -ContentType 'application/json' }
|
||||
Write-Host "Submission committed. Certification will start shortly."
|
||||
Write-StoreLog "Commit response status: $($commitResp.status)"
|
||||
if ($commitResp.statusDetails) {
|
||||
$errors = @($commitResp.statusDetails.errors)
|
||||
$warnings = @($commitResp.statusDetails.warnings)
|
||||
if ($errors.Count -gt 0) {
|
||||
foreach ($e in $errors) {
|
||||
Write-Host " ERROR: $($e.code) - $($e.details)"
|
||||
Write-StoreLog " Commit error: $($e.code) - $($e.details)"
|
||||
}
|
||||
throw "Submission commit returned $($errors.Count) error(s)."
|
||||
}
|
||||
if ($warnings.Count -gt 0) {
|
||||
foreach ($w in $warnings) {
|
||||
Write-Host " WARNING: $($w.code) - $($w.details)"
|
||||
Write-StoreLog " Commit warning: $($w.code) - $($w.details)"
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-StoreLog "Done (committed, Immediate mode). SubmissionId: $subId"
|
||||
|
||||
# 10. Verify submission status after commit
|
||||
Write-Host "Verifying submission status..."
|
||||
Start-Sleep -Seconds 10
|
||||
$subStatus = Invoke-WithRetry -Description 'Check submission status' { Invoke-RestMethod -Uri "$apiBase/submissions/$subId" -Headers $h }
|
||||
Write-Host "Submission status: $($subStatus.status)"
|
||||
Write-StoreLog "Post-commit submission status: $($subStatus.status)"
|
||||
if ($subStatus.status -eq 'CommitFailed') {
|
||||
$errDetails = if ($subStatus.statusDetails.errors) { $subStatus.statusDetails.errors | ForEach-Object { "$($_.code): $($_.details)" } | Out-String } else { 'no details' }
|
||||
throw "Submission commit failed. Status: $($subStatus.status). Details: $errDetails"
|
||||
}
|
||||
|
||||
} catch {
|
||||
$errMsg = $_.ToString()
|
||||
$errDetail = $_.Exception.Message
|
||||
$errStack = $_.ScriptStackTrace
|
||||
Write-Host "ERROR: $errMsg"
|
||||
Write-StoreLog ''
|
||||
Write-StoreLog "=== FATAL ERROR $(Get-Date -Format o) ==="
|
||||
Write-StoreLog "Message : $errMsg"
|
||||
Write-StoreLog "Detail : $errDetail"
|
||||
Write-StoreLog "Stack :"
|
||||
Write-StoreLog "$errStack"
|
||||
throw $_
|
||||
}
|
||||
shell: pwsh
|
||||
|
||||
- name: Upload Microsoft Store raw log to R2
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
env:
|
||||
MSSTORE_LOG_FILE: dist/msstore-logs/msstore-${{ github.run_id }}-${{ github.run_attempt }}.log
|
||||
CF_R2_ACCOUNT_ID: ${{ secrets.CF_R2_ACCOUNT_ID }}
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
CF_R2_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
CF_R2_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
run: |
|
||||
if (-not (Test-Path $env:MSSTORE_LOG_FILE)) {
|
||||
Write-Host "Microsoft Store raw log file was not created: $env:MSSTORE_LOG_FILE"
|
||||
exit 0
|
||||
}
|
||||
|
||||
$key = "store-logs/ms-store-upload/${{ github.run_id }}-${{ github.run_attempt }}.log"
|
||||
node build/bin/upload-file-to-r2.js "$env:MSSTORE_LOG_FILE" "$key"
|
||||
shell: pwsh
|
||||
@@ -0,0 +1,24 @@
|
||||
name: publish-to-npm
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
branches: [ test-npm ]
|
||||
jobs:
|
||||
release-npm:
|
||||
runs-on: ubuntu-latest
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip npm]') }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup node and npm
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
registry-url: https://registry.npmjs.org/
|
||||
- run: node build/bin/prepublish.js
|
||||
- run: npm config set legacy-peer-deps true && npm i
|
||||
- uses: JS-DevTools/npm-publish@v3
|
||||
with:
|
||||
token: ${{secrets.NPM_TOKEN}}
|
||||
@@ -0,0 +1,52 @@
|
||||
name: Sync Master to Gitee
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sync-master:
|
||||
runs-on: ubuntu-latest
|
||||
environment: build
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup SSH
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.GITEE_PRIVATE_SSH_TOKEN }}" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
ssh-keyscan gitee.com >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config --global user.name "ZHAO Xudong"
|
||||
git config --global user.email "zxdong@gmail.com"
|
||||
git remote add gitee git@gitee.com:github-zxdong262/electerm.git
|
||||
|
||||
- name: Push to Gitee
|
||||
run: git push -f gitee HEAD:master
|
||||
|
||||
- name: Update version.html in gh-pages
|
||||
run: |
|
||||
# Get version from package.json using standard grep and sed
|
||||
VERSION=$(curl -s https://raw.githubusercontent.com/electerm/electerm/master/package.json | grep '"version":' | sed 's/.*"version": "\([^"]*\).*/\1/')
|
||||
|
||||
# Checkout gh-pages branch
|
||||
git fetch origin gh-pages:gh-pages
|
||||
git checkout gh-pages
|
||||
|
||||
# Update version.html
|
||||
echo "v$VERSION" > version.html
|
||||
|
||||
# Commit and push changes
|
||||
git add version.html
|
||||
git commit -m "Update version to v$VERSION"
|
||||
git push origin gh-pages
|
||||
|
||||
# Also push to Gitee
|
||||
git push gitee gh-pages
|
||||
@@ -0,0 +1,59 @@
|
||||
name: upload-release-assets-to-r2
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
branches: [ r2 ]
|
||||
jobs:
|
||||
upload-to-r2:
|
||||
runs-on: ubuntu-latest
|
||||
environment: build
|
||||
steps:
|
||||
- name: Download all release assets
|
||||
run: |
|
||||
TAG_NAME="${{ github.event.release.tag_name || github.event.inputs.tag_name }}"
|
||||
mkdir -p ./release-assets
|
||||
gh release download "$TAG_NAME" --repo "${{ github.repository }}" -D ./release-assets
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
- name: List downloaded file
|
||||
run: ls -la ./release-assets
|
||||
|
||||
- name: Configure AWS credentials for R2
|
||||
run: |
|
||||
mkdir -p ~/.aws
|
||||
cat > ~/.aws/credentials << 'EOF'
|
||||
[default]
|
||||
aws_access_key_id = ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
aws_secret_access_key = ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
EOF
|
||||
cat > ~/.aws/config << 'EOF'
|
||||
[default]
|
||||
region = auto
|
||||
endpoint_url = ${{ secrets.CF_R2_ENDPOINT }}
|
||||
EOF
|
||||
|
||||
- name: Delete existing r folder
|
||||
env:
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
run: |
|
||||
aws s3 rm "s3://${CF_R2_BUCKET}/r/" --recursive
|
||||
|
||||
- name: Upload all release assets to R2
|
||||
env:
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
run: |
|
||||
for file in $(find ./release-assets -type f); do
|
||||
rel_path="${file#./release-assets/}"
|
||||
aws s3 cp "$file" "s3://${CF_R2_BUCKET}/r/${rel_path}" \
|
||||
--acl public-read \
|
||||
--cache-control "max-age=31536000"
|
||||
echo "Uploaded: ${rel_path}"
|
||||
done
|
||||
|
||||
- name: Verify uploads
|
||||
env:
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
run: |
|
||||
aws s3 ls "s3://${CF_R2_BUCKET}/r/" --recursive
|
||||
@@ -0,0 +1,55 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: win-appx
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, build-only, win-appx ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: windows-2022
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip build]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip win]') && !contains(github.event.head_commit.message, '[skip appx]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 24.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24.x
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.7' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
- run: npm config set legacy-peer-deps true
|
||||
- run: npm install -g node-gyp
|
||||
|
||||
# before_install:
|
||||
# - run: npm install --global --production windows-build-tools --vs2015
|
||||
- run: npm install -g yarn
|
||||
- run: npm un node-pty serialport
|
||||
- run: npm i
|
||||
- run: npm i -S -E node-pty@1.1.0-beta14
|
||||
- run: npm i -S -E serialport@13.0.0
|
||||
|
||||
- name: Install R2 dependencies if store upload may run
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
run: npm i -D @aws-sdk/client-s3
|
||||
|
||||
# script:
|
||||
- run: npm run b
|
||||
|
||||
- name: Run pb
|
||||
run: npm run pb
|
||||
|
||||
- name: build
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_NUMBER: ${{ secrets.BUILD_NUMBER }}
|
||||
WORKFLOW_NAME: ${{ github.workflow }}
|
||||
|
||||
run: node build/bin/build-win-appx
|
||||
@@ -0,0 +1,68 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: win-arm
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, build-only, win-arm ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: windows-11-arm
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip build]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip win]') && !contains(github.event.head_commit.message, '[skip win-arm]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 24.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24.x
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- run: npm config set legacy-peer-deps true
|
||||
- run: npm install -g node-gyp
|
||||
|
||||
# before_install:
|
||||
# - run: npm install --global --production windows-build-tools --vs2015
|
||||
- run: npm install -g yarn
|
||||
- run: npm un node-pty serialport
|
||||
- run: npm i
|
||||
- run: npm i -S -E node-pty@1.1.0-beta14
|
||||
- run: npm i -S -E serialport@13.0.0
|
||||
|
||||
- name: Install R2 dependencies if needed
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
run: npm i -D @aws-sdk/client-s3
|
||||
|
||||
# script:
|
||||
- run: npm run b
|
||||
|
||||
- name: Run pb
|
||||
run: npm run pb
|
||||
|
||||
# check work/app/node_modules size in windows arm
|
||||
- name: Check node_modules size
|
||||
shell: powershell
|
||||
run: |
|
||||
$size = (Get-ChildItem -Path "work/app/node_modules" -Recurse | Measure-Object -Property Length -Sum).Sum
|
||||
$sizeInMB = [math]::Round($size / 1MB, 2)
|
||||
$sizeInGB = [math]::Round($size / 1GB, 2)
|
||||
Write-Host "work/app/node_modules size: $sizeInMB MB ($sizeInGB GB)"
|
||||
|
||||
- name: build
|
||||
if: ${{ !contains(github.event.head_commit.message, 'skip build') }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_NUMBER: ${{ secrets.BUILD_NUMBER }}
|
||||
WORKFLOW_NAME: ${{ github.workflow }}
|
||||
CF_R2_ACCOUNT_ID: ${{ secrets.CF_R2_ACCOUNT_ID }}
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
CF_R2_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
CF_R2_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
run: node build/bin/build-win-arm
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: win-loose
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, build-only, win-loose ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: windows-2022
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip build]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip win]') && !contains(github.event.head_commit.message, '[skip loose]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 24.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24.x
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.7' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
- run: npm config set legacy-peer-deps true
|
||||
- run: npm install -g node-gyp
|
||||
|
||||
# before_install:
|
||||
# - run: npm install --global --production windows-build-tools --vs2015
|
||||
- run: npm install -g yarn
|
||||
- run: npm un node-pty serialport
|
||||
- run: npm i
|
||||
- run: npm i -S -E node-pty@1.1.0-beta14
|
||||
- run: npm i -S -E serialport@13.0.0
|
||||
|
||||
- name: Install R2 dependencies if needed
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
run: npm i -D @aws-sdk/client-s3
|
||||
|
||||
# script:
|
||||
- run: npm run b
|
||||
|
||||
- name: Run pb
|
||||
run: npm run pb
|
||||
|
||||
- name: build
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_NUMBER: ${{ secrets.BUILD_NUMBER }}
|
||||
WORKFLOW_NAME: ${{ github.workflow }}
|
||||
CF_R2_ACCOUNT_ID: ${{ secrets.CF_R2_ACCOUNT_ID }}
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
CF_R2_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
CF_R2_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
|
||||
run: node build/bin/build-win-loose
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: win-arm-nsis
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, build-only, win-nsis-arm ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: windows-11-arm
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip build]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip win]') && !contains(github.event.head_commit.message, '[skip win-nsis-arm]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 24.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24.x
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- run: npm config set legacy-peer-deps true
|
||||
- run: npm install -g node-gyp
|
||||
|
||||
# before_install:
|
||||
# - run: npm install --global --production windows-build-tools --vs2015
|
||||
- run: npm install -g yarn
|
||||
- run: npm un node-pty serialport
|
||||
- run: npm i
|
||||
- run: npm i -S -E node-pty@1.1.0-beta14
|
||||
- run: npm i -S -E serialport@13.0.0
|
||||
|
||||
- name: Install R2 dependencies if needed
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
run: npm i -D @aws-sdk/client-s3
|
||||
|
||||
# script:
|
||||
- run: npm run b
|
||||
|
||||
- name: Run pb
|
||||
run: npm run pb
|
||||
|
||||
# check work/app/node_modules size in windows arm
|
||||
- name: Check node_modules size
|
||||
shell: powershell
|
||||
run: |
|
||||
$size = (Get-ChildItem -Path "work/app/node_modules" -Recurse | Measure-Object -Property Length -Sum).Sum
|
||||
$sizeInMB = [math]::Round($size / 1MB, 2)
|
||||
$sizeInGB = [math]::Round($size / 1GB, 2)
|
||||
Write-Host "work/app/node_modules size: $sizeInMB MB ($sizeInGB GB)"
|
||||
|
||||
- name: build
|
||||
if: ${{ !contains(github.event.head_commit.message, 'skip build') }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_NUMBER: ${{ secrets.BUILD_NUMBER }}
|
||||
WORKFLOW_NAME: ${{ github.workflow }}
|
||||
CF_R2_ACCOUNT_ID: ${{ secrets.CF_R2_ACCOUNT_ID }}
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
CF_R2_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
CF_R2_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
run: node build/bin/build-win-nsis-arm
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: win-nsis
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, build-only, win-nsis ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: windows-2022
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip build]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip win]') && !contains(github.event.head_commit.message, '[skip nsis]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 24.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24.x
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.7' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
- run: npm config set legacy-peer-deps true
|
||||
- run: npm install -g node-gyp
|
||||
|
||||
# before_install:
|
||||
# - run: npm install --global --production windows-build-tools --vs2015
|
||||
- run: npm install -g yarn
|
||||
- run: npm un node-pty serialport
|
||||
- run: npm i
|
||||
- run: npm i -S -E node-pty@1.1.0-beta14
|
||||
- run: npm i -S -E serialport@13.0.0
|
||||
|
||||
- name: Install R2 dependencies if needed
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
run: npm i -D @aws-sdk/client-s3
|
||||
|
||||
# script:
|
||||
- run: npm run b
|
||||
|
||||
- name: Run pb
|
||||
run: npm run pb
|
||||
|
||||
- name: build
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_NUMBER: ${{ secrets.BUILD_NUMBER }}
|
||||
WORKFLOW_NAME: ${{ github.workflow }}
|
||||
CF_R2_ACCOUNT_ID: ${{ secrets.CF_R2_ACCOUNT_ID }}
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
CF_R2_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
CF_R2_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
|
||||
run: node build/bin/build-win-nsis
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: win-portable
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, build-only, win-portable ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: windows-2022
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip build]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip win]') && !contains(github.event.head_commit.message, '[skip portable]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 24.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24.x
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.7' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
- run: npm config set legacy-peer-deps true
|
||||
- run: npm install -g node-gyp
|
||||
|
||||
# before_install:
|
||||
# - run: npm install --global --production windows-build-tools --vs2015
|
||||
- run: npm install -g yarn
|
||||
- run: npm un node-pty serialport
|
||||
- run: npm i
|
||||
- run: npm i -S -E node-pty@1.1.0-beta14
|
||||
- run: npm i -S -E serialport@13.0.0
|
||||
|
||||
- name: Install R2 dependencies if needed
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
run: npm i -D @aws-sdk/client-s3
|
||||
|
||||
# script:
|
||||
- run: npm run b
|
||||
|
||||
- name: Run pb
|
||||
run: npm run pb
|
||||
|
||||
- name: build
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_NUMBER: ${{ secrets.BUILD_NUMBER }}
|
||||
WORKFLOW_NAME: ${{ github.workflow }}
|
||||
CF_R2_ACCOUNT_ID: ${{ secrets.CF_R2_ACCOUNT_ID }}
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
CF_R2_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
CF_R2_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
run: node build/bin/build-win-portable
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: win-tar
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, build-only, win-tar ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: windows-2022
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip build]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip win]') && !contains(github.event.head_commit.message, '[skip win-tar]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 24.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24.x
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.7' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
- run: npm config set legacy-peer-deps true
|
||||
- run: npm install -g node-gyp
|
||||
|
||||
# before_install:
|
||||
# - run: npm install --global --production windows-build-tools --vs2015
|
||||
- run: npm install -g yarn
|
||||
- run: npm un node-pty serialport
|
||||
- run: npm i
|
||||
- run: npm i -S -E node-pty@1.1.0-beta14
|
||||
- run: npm i -S -E serialport@13.0.0
|
||||
|
||||
- name: Install R2 dependencies if needed fix
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
run: npm i -D @aws-sdk/client-s3
|
||||
|
||||
# script:
|
||||
- run: npm run b
|
||||
|
||||
- name: Run pb
|
||||
run: npm run pb
|
||||
|
||||
- name: build
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_NUMBER: ${{ secrets.BUILD_NUMBER }}
|
||||
WORKFLOW_NAME: ${{ github.workflow }}
|
||||
CF_R2_ACCOUNT_ID: ${{ secrets.CF_R2_ACCOUNT_ID }}
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
CF_R2_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
CF_R2_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
run: node build/bin/build-win-tar
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: win7-tar
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ build, test, build-only, win7-tar, legacy-build ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: windows-2022
|
||||
environment: build
|
||||
if: ${{ !contains(github.event.head_commit.message, '[skip build]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip win]') && !contains(github.event.head_commit.message, '[skip win7-tar]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.7' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
- run: npm config set legacy-peer-deps true
|
||||
- run: npm install -g node-gyp
|
||||
|
||||
# before_install :
|
||||
# - run: npm install --global --production windows-build-tools --vs2015
|
||||
- run: npm install -g yarn
|
||||
- name: Update electron version and remove package-lock
|
||||
run: |
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json'));
|
||||
pkg.devDependencies.electron = '22.3.27';
|
||||
pkg.devDependencies['electron-builder'] = '26.0.20';
|
||||
pkg.devDependencies['@electron/rebuild'] = '3.7.2';
|
||||
pkg.dependencies['node-pty'] = '1.1.0-beta14';
|
||||
pkg.dependencies.serialport = '13.0.0';
|
||||
pkg.devDependencies.vite = '4';
|
||||
pkg.devDependencies['@vitejs/plugin-react'] = '4.7.0';
|
||||
pkg.dependencies['os-locale-s'] = '1.0.8';
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
|
||||
fs.unlinkSync('package-lock.json');
|
||||
"
|
||||
- run: npm i
|
||||
- run: npm i -S @electron/rebuild@3.7.2
|
||||
|
||||
- name: Install R2 dependencies if needed
|
||||
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
|
||||
run: npm i -D @aws-sdk/client-s3
|
||||
|
||||
# script:
|
||||
- run: npm run b
|
||||
|
||||
- name: Run pb
|
||||
run: npm run pb
|
||||
|
||||
- name: build
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_NUMBER: ${{ secrets.BUILD_NUMBER }}
|
||||
WORKFLOW_NAME: ${{ github.workflow }}
|
||||
CF_R2_ACCOUNT_ID: ${{ secrets.CF_R2_ACCOUNT_ID }}
|
||||
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
|
||||
CF_R2_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
CF_R2_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
run: node build/bin/build-win7-tar
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
name: Publish to WinGet
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
push:
|
||||
branches: [ test-winget ]
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-24.04
|
||||
environment: build
|
||||
steps:
|
||||
- uses: vedantmgoyal9/winget-releaser@main
|
||||
with:
|
||||
identifier: electerm.electerm
|
||||
installers-regex: 'win-x64-installer.exe$'
|
||||
max-versions-to-keep: 30
|
||||
token: ${{ secrets.WINGET_TOKEN }}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
.DS_Store
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# custom
|
||||
/temp/
|
||||
/dist/
|
||||
/work/
|
||||
/version
|
||||
/*.txt
|
||||
/build/web-app/client
|
||||
/*.tar
|
||||
/*.gz
|
||||
.vscode
|
||||
/test-results
|
||||
|
||||
ftp-root
|
||||
*.asc
|
||||
/electron-builder.json
|
||||
|
||||
/dist-loong64
|
||||
/work-loong64
|
||||
/.claude
|
||||
/AGENTS.md
|
||||
/.workbuddy
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
# run `cp .sample.env .env` to create your local env
|
||||
|
||||
## test env
|
||||
|
||||
## test ssh ip or host, required
|
||||
TEST_HOST=0.0.0.0
|
||||
|
||||
## test ssh username, required
|
||||
TEST_USER=xxx
|
||||
|
||||
## test ssh port, optional, default 22
|
||||
# TEST_PORT=22
|
||||
|
||||
## test ssh password, required
|
||||
TEST_PASS=xxx
|
||||
|
||||
@@ -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,229 @@
|
||||
<h1 align="center" style="padding-top: 60px;padding-bottom: 40px;">
|
||||
<a href="https://electerm.org">
|
||||
<img src="https://github.com/electerm/electerm-resource/raw/master/static/images/electerm.png", alt="" />
|
||||
</a>
|
||||
</h1>
|
||||
|
||||
[](https://github.com/electerm/electerm/releases)
|
||||
[](https://github.com/electerm/electerm/actions)
|
||||
[](https://github.com/electerm/electerm/blob/master/LICENSE)
|
||||
[](https://snapcraft.io/electerm)
|
||||
[](https://www.microsoft.com/store/apps/9NCN7272GTFF)
|
||||
[](https://github.com/sponsors/electerm)
|
||||
[](https://atomgit.com/electerm/electerm)
|
||||
|
||||
[](README.md) [](README_cn.md)
|
||||
|
||||
Open-sourced terminal/ssh/sftp/telnet/serialport/RDP/VNC/Spice/ftp client(linux, mac, win).
|
||||
|
||||
- [electerm.org](https://electerm.org): Homepage, downloads, videos, etc
|
||||
- [electerm-web](https://github.com/electerm/electerm-web): Web app version running in browser(including mobile device)
|
||||
- [electerm-web-docker](https://github.com/electerm/electerm-web-docker): Docker image for electerm-web
|
||||
- [electerm online](https://cloud.electerm.org): Public free online electerm app
|
||||
- [electerm demo](https://demo.electerm.org): Online demo of electerm
|
||||
- [electerm AI](https://ai.electerm.org): Free AI for electerm users
|
||||
- [electerm deb repo](https://repos.electerm.org/deb): Debian repo of electerm
|
||||
- [electerm rpm repo](https://repos.electerm.org/rpm): RPM repo of electerm
|
||||
|
||||
## Atlas Cloud
|
||||
|
||||
<div align="center">
|
||||
<a href="https://www.atlascloud.ai/?utm_source=github&utm_medium=link&utm_campaign=electerm">
|
||||
<img src="https://github.com/electerm/electerm-resource/blob/master/static/images/atlas-cloud.png?raw=true" alt="Atlas Cloud" width="200" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
[Atlas Cloud](https://www.atlascloud.ai/?utm_source=github&utm_medium=link&utm_campaign=electerm) provides OpenAI-compatible AI APIs and model access for AI-powered workflows in electerm.
|
||||
|
||||
[](https://www.digitalocean.com/?refcode=c10bcb28b846&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge)
|
||||
|
||||
[](https://oss-directory.vercel.app)
|
||||
|
||||
<div align="center">
|
||||
<img src="https://github.com/electerm/electerm-resource/raw/master/static/images/electerm.gif", alt="" />
|
||||
</div>
|
||||
|
||||
## Features
|
||||
|
||||
- Works as a terminal/file manager or ssh/sftp/ftp/telnet/serialport/RDP/VNC/Spice client
|
||||
- Support Window 7+(X64/ARM64), Mac OS 10.15+(x64/arm64), Linux(x64/arm64/Loong64), even old Linux with glibc 2.17+ like UOS/Kylin/Ubuntu 18.04 etc
|
||||
- Global hotkey to toggle window visibility (similar to guake, default is `ctrl + 2`)
|
||||
- Multi platform(linux, mac, win)
|
||||
- 🇺🇸 🇨🇳 🇧🇷 🇷🇺 🇪🇸 🇫🇷 🇹🇷 🇭🇰 🇯🇵 🇸🇦 🇩🇪 🇰🇷 🇮🇩 🇵🇱 Multi-language support([electerm-locales](https://github.com/electerm/electerm-locales), contributions/fixes welcome)
|
||||
- Double click to directly edit (small) remote files.
|
||||
- Auth with publicKey + password.
|
||||
- Support Zmodem(rz, sz).
|
||||
- Support ssh tunnel.
|
||||
- Support [Trzsz](https://github.com/trzsz/trzsz)(trz/tsz), similar to rz/sz, and compatible with tmux.
|
||||
- Transparent window(Mac, win).
|
||||
- Terminal background image.
|
||||
- Global/session proxy.
|
||||
- Quick commands
|
||||
- UI/terminal theme
|
||||
- Sync bookmarks/themes/quick commands to github/gitee secret gist/webdav/custom server/electerm cloud
|
||||
- Quick input to one or all terminals.
|
||||
- AI assistant integration (supporting [DeepSeek](https://www.deepseek.com), OpenAI, and any other AI APIs) to help with command suggestions, script writing, and explaining selected terminal content, create bookmarks
|
||||
- MCP (Model Context Protocol) widget for AI assistants and external tools integration - see [MCP Widget Usage Guide](https://github.com/electerm/electerm/wiki/MCP-Widget-Usage-Guide)
|
||||
- Deep link support: Open connections with URLs like `telnet://192.168.2.31:34554` or `ssh://user@host:22` - see [Deep link support wiki](https://github.com/electerm/electerm/wiki/Deep-link-support)
|
||||
- Command line usage: check [wiki](https://github.com/electerm/electerm/wiki/Command-line-usage)
|
||||
|
||||
## Install
|
||||
|
||||
- For Mac user: `brew install --cask electerm`
|
||||
- With snap: `sudo snap install electerm --classic`
|
||||
- For some Linux distribution, you can find it from OS default App store(Ubuntu, Deepin, Mint...).
|
||||
- For some linux OS, the `rpm`, `deb`, or `snap` release may not work, you can try the `tar.gz` or `.appImage` release.
|
||||
- For Windows users, you can install it from [windows store](https://www.microsoft.com/store/apps/9NCN7272GTFF), command-line installer [winget](https://github.com/microsoft/winget-cli) and [scoop](https://github.com/lukesampson/scoop) is also recommended:
|
||||
|
||||
```powershell
|
||||
# winget https://github.com/microsoft/winget-cli
|
||||
winget install electerm.electerm
|
||||
|
||||
# scoop https://github.com/lukesampson/scoop
|
||||
scoop bucket add dorado https://github.com/chawyehsu/dorado
|
||||
scoop install dorado/electerm
|
||||
```
|
||||
|
||||
- Install from Debian repository (for Debian/Ubuntu-based systems) with `apt` command
|
||||
|
||||
Check [https://repos.electerm.org/deb](https://repos.electerm.org/deb)
|
||||
|
||||
- Install from npm
|
||||
|
||||
```bash
|
||||
npm i -g electerm
|
||||
|
||||
```
|
||||
|
||||
## Upgrade
|
||||
|
||||
- Auto upgrade: When a new version is released, you will get an upgrade notification after you start electerm again. You can then click the upgrade button to upgrade.
|
||||
- Download: Just download the latest edition, reinstall.
|
||||
- Npm: If you install from npm, just run `npm i -g electerm` again.
|
||||
- If use Snap or some other distribution system, these systems may provide upgrades.
|
||||
|
||||
## Known issues
|
||||
|
||||
[https://github.com/electerm/electerm/wiki/Know-issues](https://github.com/electerm/electerm/wiki/Know-issues)
|
||||
|
||||
## Troubleshoot
|
||||
|
||||
[https://github.com/electerm/electerm/wiki/Troubleshoot](https://github.com/electerm/electerm/wiki/Troubleshoot)
|
||||
|
||||
## Discussion
|
||||
|
||||
[](https://discord.gg/855W7g8EVd)
|
||||
|
||||
[Discussion board](https://github.com/electerm/electerm/discussions)
|
||||
|
||||

|
||||
|
||||
## Support
|
||||
|
||||
Would love to hear from you, please tell me what you think, [submit an issue](https://github.com/electerm/electerm/issues), [Start a new discussion](https://github.com/electerm/electerm/discussions/new), [create/fix language files](https://github.com/electerm/electerm-locales) or create pull requests, all welcome.
|
||||
|
||||
## Sponsor this project
|
||||
|
||||
github sponsor
|
||||
|
||||
[https://github.com/sponsors/electerm](https://github.com/sponsors/electerm)
|
||||
|
||||
kofi
|
||||
|
||||
[https://ko-fi.com/zhaoxudong](https://ko-fi.com/zhaoxudong)
|
||||
|
||||
wechat donate
|
||||
|
||||
[](https://github.com/electerm)
|
||||
|
||||
TRON TRN20
|
||||
|
||||
[]
|
||||
|
||||
Address: TXk3pQNmQu1vihH76RaEFnK9wg13x4LLCZ
|
||||
|
||||
## Dev
|
||||
|
||||
```bash
|
||||
# May only works in Linux
|
||||
# needs nodejs/npm, suggest using nvm to install nodejs/npm
|
||||
# with nodejs 24.x
|
||||
|
||||
git clone git@github.com:electerm/electerm.git
|
||||
cd electerm
|
||||
npm config set legacy-peer-deps true
|
||||
npm i
|
||||
|
||||
# start vite dev server, requires port 5570
|
||||
npm start
|
||||
|
||||
# in a separate terminal session run app
|
||||
npm run app
|
||||
|
||||
# code format check
|
||||
npm run lint
|
||||
|
||||
# code format fix
|
||||
npm run fix
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
```bash
|
||||
npm run b
|
||||
npm run prepare-test
|
||||
cp .sample.env .env
|
||||
|
||||
# edit .env, fill your test host/username/password, may only works in mac OS
|
||||
npm run test
|
||||
```
|
||||
|
||||
## Test build
|
||||
|
||||
```bash
|
||||
# May only works in Linux
|
||||
# Install yarn first(to do yarn autoclean)
|
||||
# See https://yarnpkg.com/en/docs/install
|
||||
|
||||
# Build linux only with -l
|
||||
npm i
|
||||
npm run b
|
||||
npm run pb
|
||||
./node_modules/.bin/electron-builder --linux tar.gz
|
||||
# or replace tar.gz to rpm/deb/AppImage
|
||||
# check dist/ folder
|
||||
|
||||
# build for linux arm/
|
||||
./node_modules/.bin/electron-builder --linux --arm64
|
||||
```
|
||||
|
||||
## Video guide
|
||||
|
||||
- [https://electerm.org/videos](https://electerm.org/videos)
|
||||
|
||||
## Change log
|
||||
|
||||
Visit [Releases](https://github.com/electerm/electerm/releases).
|
||||
|
||||
## Contact author
|
||||
|
||||
[zxdong@gmail.com](mailto:zxdong@gmail.com)
|
||||
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Star History
|
||||
|
||||
<p>
|
||||
<a href="https://www.star-history.com/electerm/electerm"><img src="https://api.star-history.com/badge?repo=electerm/electerm" alt="Star History Rank" /></a>
|
||||
</p>
|
||||
|
||||
<a href="https://www.star-history.com/?repos=electerm%2Felecterm&type=date&legend=top-left">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=electerm/electerm&type=date&legend=top-left&sealed_token=DvPlttPaLtq6RPIWRANYX08-8ZJyrJalhombrkDcg1IwWOMPMIplgi85q0FiwrbhE3lRIi_yxvOWdell731CGQvzlfJMt2Sa6VfCvQ80BwEtQ6RpPchEiw" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=electerm/electerm&type=date&legend=top-left&sealed_token=DvPlttPaLtq6RPIWRANYX08-8ZJyrJalhombrkDcg1IwWOMPMIplgi85q0FiwrbhE3lRIi_yxvOWdell731CGQvzlfJMt2Sa6VfCvQ80BwEtQ6RpPchEiw" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=electerm/electerm&type=date&legend=top-left&sealed_token=DvPlttPaLtq6RPIWRANYX08-8ZJyrJalhombrkDcg1IwWOMPMIplgi85q0FiwrbhE3lRIi_yxvOWdell731CGQvzlfJMt2Sa6VfCvQ80BwEtQ6RpPchEiw" />
|
||||
</picture>
|
||||
</a>
|
||||
@@ -0,0 +1,7 @@
|
||||
# WeHub 来源说明
|
||||
|
||||
- 原始项目:`electerm/electerm`
|
||||
- 原始仓库:https://github.com/electerm/electerm
|
||||
- 导入方式:上游默认分支的最新快照
|
||||
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
|
||||
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
<h1 align="center" style="padding-top: 60px;padding-bottom: 40px;">
|
||||
<a href="https://electerm.org">
|
||||
<img src="https://github.com/electerm/electerm-resource/raw/master/static/images/electerm.png", alt="" />
|
||||
</a>
|
||||
</h1>
|
||||
|
||||
[](https://github.com/electerm/electerm/releases)
|
||||
[](https://github.com/electerm/electerm/actions)
|
||||
[](https://github.com/electerm/electerm/blob/master/LICENSE)
|
||||
[](https://snapcraft.io/electerm)
|
||||
[](https://www.microsoft.com/store/apps/9NCN7272GTFF)
|
||||
[](https://github.com/sponsors/electerm)
|
||||
[](https://atomgit.com/electerm/electerm)
|
||||
|
||||
[](README.md) [](README_cn.md)
|
||||
|
||||
|
||||
开源终端/ssh/telnet/serialport/RDP/VNC/Spice/sftp/ftp客户端(linux, mac, win)。
|
||||
|
||||
- [electerm.org](https://electerm.org): 主页,下载,视频等
|
||||
- [electerm-web](https://github.com/electerm/electerm-web): 运行于浏览器(支持移动设备)的web app版本
|
||||
- [electerm-web-docker](https://github.com/electerm/electerm-web-docker): electerm-web的docker镜像
|
||||
- [electerm online](https://cloud.electerm.org): 公共免费在线electerm应用
|
||||
- [electerm demo](https://demo.electerm.org): 在线演示
|
||||
- [electerm AI](https://ai.electerm.org): 免费为 electerm 用户提供 AI
|
||||
- [electerm deb repo](https://repos.electerm.org/deb): Debian repo of electerm
|
||||
- [electerm rpm repo](https://repos.electerm.org/rpm): RPM repo of electerm
|
||||
|
||||
## Atlas Cloud
|
||||
|
||||
<div align="center">
|
||||
<a href="https://www.atlascloud.ai/?utm_source=github&utm_medium=link&utm_campaign=electerm">
|
||||
<img src="https://github.com/electerm/electerm-resource/blob/master/static/images/atlas-cloud.png?raw=true" alt="Atlas Cloud" width="200" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
[Atlas Cloud](https://www.atlascloud.ai/?utm_source=github&utm_medium=link&utm_campaign=electerm) 提供与 OpenAI 兼容的 AI API 和模型访问,用于在 electerm 中实现 AI 驱动的工作流程。
|
||||
|
||||
[](https://www.digitalocean.com/?refcode=c10bcb28b846&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge)
|
||||
|
||||
[](https://oss-directory.vercel.app)
|
||||
|
||||
<div align="center">
|
||||
<img src="https://github.com/electerm/electerm-resource/raw/master/static/images/electerm.gif", alt="" />
|
||||
</div>
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 支持ssh,telnet,serialport,RDP,VNC,Spice,本地和远程文件管理,sftp/ftp文件传输,以及作为本地终端使用
|
||||
- 支持Window 7+(X64/ARM64), Mac OS 10.15+(x64/arm64), Linux(x64/arm64/Loong64), 以及Linux with glibc 2.17+ like UOS/Kylin/Ubuntu 18.04 etc
|
||||
- 全局快捷键切换隐藏显示窗口(类似guake, 默认快捷键`ctrl + 2`)
|
||||
- 多平台支持(linux, mac, win)
|
||||
- 🇺🇸 🇨🇳 🇧🇷 🇷🇺 🇪🇸 🇫🇷 🇹🇷 🇭🇰 🇯🇵 🇸🇦 🇩🇪 🇰🇷 🇮🇩 🇵🇱 多国语言支持([electerm-locales](https://github.com/electerm/electerm-locales), 欢迎提交代码)
|
||||
- 双击直接编辑远程文件.
|
||||
- 支持密码或者密匙登录.
|
||||
- 支持Zmodem(rz, sz).
|
||||
- 支持ssh隧道
|
||||
- 支持[Trzsz](https://github.com/trzsz/trzsz)(trz/tsz), 类似rz/sz, 兼容tmux.
|
||||
- 支持透明窗口(Mac, win).
|
||||
- 支持设置终端背景图片.
|
||||
- 支持代理服务器.
|
||||
- 支持预设快捷命令
|
||||
- 支持主题
|
||||
- 支持同步书签等数据到github/gitee私人gist, webdav/custom server/electerm cloud
|
||||
- 支持快速输入命令到一个或者多个终端
|
||||
- AI助手集成(支持[DeepSeek](https://www.deepseek.com)、OpenAI等AI API),协助命令建议、脚本编写、以及解释所选终端内容, 创建书签
|
||||
- MCP (Model Context Protocol) 组件,用于AI助手和外部工具集成 - 详见 [MCP Widget Usage Guide](https://github.com/electerm/electerm/wiki/MCP-Widget-Usage-Guide)
|
||||
- 支持命令行使用: 请参阅[wiki](https://github.com/electerm/electerm/wiki/Command-line-usage)
|
||||
- 深度链接支持: 使用 `telnet://192.168.2.31:34554` 或 `ssh://user@host:22` 等URL打开连接 - 详见 [深度链接支持 wiki](https://github.com/electerm/electerm/wiki/Deep-link-support)
|
||||
|
||||
|
||||
## 安装
|
||||
|
||||
- Mac OS用户: `brew install --cask electerm`
|
||||
- Snap: `sudo snap install electerm --classic`
|
||||
- 一些Linux发行版的内置软件商店(Ubuntu, Deepin, Mint...).
|
||||
- 不支持`rpm`, `deb`, or `snap` 的Linux发行版可以尝试 `tar.gz`版本.
|
||||
- Windows用户可以从[windows store](https://www.microsoft.com/store/apps/9NCN7272GTFF), 命令行安装工具 [winget](https://github.com/microsoft/winget-cli),以及[scoop](https://github.com/lukesampson/scoop) :
|
||||
|
||||
```powershell
|
||||
# winget https://github.com/microsoft/winget-cli
|
||||
winget install electerm.electerm
|
||||
|
||||
# scoop https://github.com/lukesampson/scoop
|
||||
scoop bucket add dorado https://github.com/chawyehsu/dorado
|
||||
scoop install dorado/electerm
|
||||
```
|
||||
|
||||
- 从Debian软件源安装 (适用于Debian/Ubuntu系统) 使用 `apt` 命令
|
||||
|
||||
查看 [https://repos.electerm.org/deb](https://repos.electerm.org/deb)
|
||||
|
||||
- 从npm安装
|
||||
|
||||
```bash
|
||||
npm i -g electerm
|
||||
```
|
||||
|
||||
## 升级
|
||||
|
||||
- 自动升级: 点击新版本的升级按钮.
|
||||
- 下载: 重新下载最新版安装.
|
||||
- Npm: 如果是从npm安装 再次运行`npm i -g electerm` 即可.
|
||||
- 如果从Linux发行版软件商店或者Snap安装,可能有自动升级机制.
|
||||
|
||||
## 已知问题
|
||||
|
||||
[https://github.com/electerm/electerm/wiki/Know-issues](https://github.com/electerm/electerm/wiki/Know-issues)
|
||||
|
||||
## 疑难解答
|
||||
|
||||
[https://github.com/electerm/electerm/wiki/Troubleshoot](https://github.com/electerm/electerm/wiki/Troubleshoot)
|
||||
|
||||
## 讨论区
|
||||
|
||||
[](https://discord.gg/855W7g8EVd)
|
||||
|
||||
[Discussion board](https://github.com/electerm/electerm/discussions)
|
||||
|
||||

|
||||
|
||||
## 支持
|
||||
|
||||
欢迎[提交问题/建议](https://github.com/electerm/electerm/issues), [展开讨论](https://github.com/electerm/electerm/discussions/new), [修复或者创建语言文件](https://github.com/electerm/electerm-locales)或者贡献代码。
|
||||
|
||||
## 赞助项目
|
||||
|
||||
github sponsor
|
||||
|
||||
[https://github.com/sponsors/electerm](https://github.com/sponsors/electerm)
|
||||
|
||||
kofi
|
||||
|
||||
[https://ko-fi.com/zhaoxudong](https://ko-fi.com/zhaoxudong)
|
||||
|
||||
微信赞赏码
|
||||
|
||||
[](https://github.com/electerm)
|
||||
|
||||
TRON TRN20
|
||||
|
||||
[]
|
||||
|
||||
地址: TXk3pQNmQu1vihH76RaEFnK9wg13x4LLCZ
|
||||
|
||||
## 开发
|
||||
|
||||
```bash
|
||||
# May only works in Linux
|
||||
# needs nodejs/npm, suggest using nvm to install nodejs/npm
|
||||
# with nodejs 24.x
|
||||
|
||||
git clone git@github.com:electerm/electerm.git
|
||||
cd electerm
|
||||
npm config set legacy-peer-deps true
|
||||
npm i
|
||||
|
||||
# start vite dev server, requires port 5570
|
||||
npm start
|
||||
|
||||
# in a separate terminal session run app
|
||||
npm run app
|
||||
|
||||
# code format check
|
||||
npm run lint
|
||||
|
||||
# code format fix
|
||||
npm run fix
|
||||
```
|
||||
|
||||
## 测试
|
||||
|
||||
```bash
|
||||
npm run b
|
||||
npm run prepare-test
|
||||
cp .sample.env .env
|
||||
|
||||
# edit .env, fill your test host/username/password, may only works in mac OS
|
||||
npm run test
|
||||
```
|
||||
|
||||
## 测试构建
|
||||
|
||||
```bash
|
||||
# May only works in Linux
|
||||
# Install yarn first(to do yarn autoclean)
|
||||
# See https://yarnpkg.com/en/docs/install
|
||||
|
||||
# Build linux only with -l
|
||||
npm i
|
||||
npm run b
|
||||
npm run pb
|
||||
./node_modules/.bin/electron-builder --linux tar.gz
|
||||
# or replace tar.gz to rpm/deb/AppImage
|
||||
# check dist/ folder
|
||||
|
||||
# build for linux arm/
|
||||
./node_modules/.bin/electron-builder --linux --arm64
|
||||
```
|
||||
|
||||
## 使用视频
|
||||
|
||||
- [https://electerm.org/videos](https://electerm.org/videos)
|
||||
|
||||
## 变更历史
|
||||
|
||||
Visit [Releases](https://github.com/electerm/electerm/releases).
|
||||
|
||||
## 联系作者
|
||||
|
||||
[zxdong@gmail.com](mailto:zxdong@gmail.com)
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT
|
||||
|
||||
## 收藏历史
|
||||
|
||||
<p>
|
||||
<a href="https://www.star-history.com/electerm/electerm"><img src="https://api.star-history.com/badge?repo=electerm/electerm" alt="Star History Rank" /></a>
|
||||
</p>
|
||||
|
||||
<a href="https://www.star-history.com/?repos=electerm%2Felecterm&type=date&legend=top-left">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=electerm/electerm&type=date&legend=top-left&sealed_token=DvPlttPaLtq6RPIWRANYX08-8ZJyrJalhombrkDcg1IwWOMPMIplgi85q0FiwrbhE3lRIi_yxvOWdell731CGQvzlfJMt2Sa6VfCvQ80BwEtQ6RpPchEiw" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=electerm/electerm&type=date&legend=top-left&sealed_token=DvPlttPaLtq6RPIWRANYX08-8ZJyrJalhombrkDcg1IwWOMPMIplgi85q0FiwrbhE3lRIi_yxvOWdell731CGQvzlfJMt2Sa6VfCvQ80BwEtQ6RpPchEiw" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=electerm/electerm&type=date&legend=top-left&sealed_token=DvPlttPaLtq6RPIWRANYX08-8ZJyrJalhombrkDcg1IwWOMPMIplgi85q0FiwrbhE3lRIi_yxvOWdell731CGQvzlfJMt2Sa6VfCvQ80BwEtQ6RpPchEiw" />
|
||||
</picture>
|
||||
</a>
|
||||
@@ -0,0 +1,6 @@
|
||||
## **Reporting Security Issues**
|
||||
|
||||
|
||||
If you believe you have found a security vulnerability in electerm, we encourage you to report it immediately.
|
||||
|
||||
We review all legitimate reports and work quickly to fix any issues to ensure the safety, reliability, and stability of our services
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user