mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
Support Linux packaging via electron-builder (#371)
* Add linux makers * missed some * remove eu-strip * blah * add description * remove v from version * add exec name * use bin * add bin to both * test flatpak * test adding dev dependencies * remove flatpak for now * add command to flatten directory structure * update package info * save rpm info * save work * add bin to packagerConfig * save work * okay let's see what happens * iterate array * test more * remove large * test * test * remove linux arm * test addl targets * add quiet to zip * revert dir flatten * remove pacman * add s3 bucket to electron-builder config * make dir * only copy artifacts * don't merge * zip recurse * blah * replace with electronupdater * make generic * fix * fix stuff * Update build-helper.yml * test fix * fix path * remove tree * messed up comment * remove touch * add platform name to artifact * remove license * remove forge * cleanup builder config * switch artifact name order * Remove darwin restriction on autoupdate * try adding back pacman * fix license * remove pacman again * rewrite scripts * add binary paths to builder * clean up * Update scripts * update interval and readme * remove flatpak and snap dependencies for now * upload with a wildcard * fix paths for addl binaries * add back blockmap * update release path * add newline * remove forge config * 2 small fixes - remove double cd for waveshell building, and remove GOARCH for wavesrv binary in dev mode
This commit is contained in:
parent
1979e401d4
commit
4ef921bdd1
42
.github/workflows/build-helper.yml
vendored
42
.github/workflows/build-helper.yml
vendored
@ -11,15 +11,11 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- platform: "darwin"
|
||||
arch: "x64"
|
||||
runner: "macos-latest"
|
||||
scripthaus: "build-package"
|
||||
- platform: "darwin"
|
||||
arch: "arm64"
|
||||
arch: "universal"
|
||||
runner: "macos-latest-xlarge"
|
||||
scripthaus: "build-package"
|
||||
- platform: "linux"
|
||||
arch: "x64"
|
||||
arch: "amd64"
|
||||
runner: "ubuntu-latest"
|
||||
scripthaus: "build-package-linux"
|
||||
runs-on: ${{ matrix.runner }}
|
||||
@ -29,6 +25,11 @@ jobs:
|
||||
with:
|
||||
repository: scripthaus-dev/scripthaus
|
||||
path: scripthaus
|
||||
- name: Install Linux Build Dependencies
|
||||
if: matrix.platform == 'linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install rpm
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{env.GO_VERSION}}
|
||||
@ -36,7 +37,8 @@ jobs:
|
||||
wavesrv/go.sum
|
||||
waveshell/go.sum
|
||||
scripthaus/go.sum
|
||||
- run: |
|
||||
- name: Install Scripthaus
|
||||
run: |
|
||||
go work use ./scripthaus;
|
||||
cd scripthaus;
|
||||
go get ./...;
|
||||
@ -45,16 +47,21 @@ jobs:
|
||||
with:
|
||||
node-version: ${{env.NODE_VERSION}}
|
||||
cache: "yarn"
|
||||
- id: set-version
|
||||
- name: Set Version
|
||||
id: set-version
|
||||
run: |
|
||||
VERSION=$(node -e 'console.log(require("./version.js"))')
|
||||
echo "WAVETERM_VERSION=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
- run: yarn --frozen-lockfile
|
||||
- run: ./scripthaus/scripthaus run ${{ matrix.scripthaus }}
|
||||
- name: Install Yarn Dependencies
|
||||
run: yarn --frozen-lockfile
|
||||
- name: Build ${{ matrix.platform }}/${{ matrix.arch }}
|
||||
run: ./scripthaus/scripthaus run ${{ matrix.scripthaus }}
|
||||
env:
|
||||
GOARCH: ${{ matrix.arch }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: waveterm-build-${{ matrix.platform }}-${{ matrix.arch }}
|
||||
path: out/make/zip/${{ matrix.platform }}/${{ matrix.arch }}/*.zip
|
||||
name: waveterm-build-${{ matrix.platform }}
|
||||
path: make/*.* # only upload files from the make directory, not subdirectories
|
||||
retention-days: 2
|
||||
upload:
|
||||
name: "Upload Builds"
|
||||
@ -65,10 +72,13 @@ jobs:
|
||||
with:
|
||||
merge-multiple: true
|
||||
path: buildtemp
|
||||
- run: |
|
||||
echo "${{ needs.runbuild.outputs.WAVETERM_VERSION }}" > buildtemp/version.txt
|
||||
- run: (cd buildtemp; zip ../waveterm-builds.zip *)
|
||||
- run: aws s3 cp waveterm-builds.zip s3://waveterm-github-artifacts/
|
||||
- name: Set `version.txt`
|
||||
run: |
|
||||
echo "${{ needs.runbuild.outputs.WAVETERM_VERSION }}" >> buildtemp/version.txt
|
||||
- name: Zip Builds
|
||||
run: (cd buildtemp; zip -q ../waveterm-builds.zip *)
|
||||
- name: Upload to S3
|
||||
run: aws s3 cp waveterm-builds.zip s3://waveterm-github-artifacts/
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: "${{ secrets.S3_USERID }}"
|
||||
AWS_SECRET_ACCESS_KEY: "${{ secrets.S3_SECRETKEY }}"
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -20,3 +20,5 @@ temp.sql
|
||||
.idea/
|
||||
test/
|
||||
.vscode/
|
||||
make/
|
||||
waveterm-builds.zip
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
# Wave Terminal
|
||||
|
||||
A open-source, cross-platform, AI-integrated, modern terminal for seamless workflows.
|
||||
An open-source, cross-platform, AI-integrated, modern terminal for seamless workflows.
|
||||
|
||||
Wave isn't just another terminal emulator; it's a rethink on how terminals are built. Wave combines command line with the power of the open web to help veteran CLI users and new developers alike.
|
||||
|
||||
|
67
buildres/README.md
Normal file
67
buildres/README.md
Normal file
@ -0,0 +1,67 @@
|
||||
# Building for release
|
||||
|
||||
## Build Helper workflow
|
||||
|
||||
Our release builds are managed by the "Build Helper" GitHub Action, which is defined
|
||||
in [`build-helper.yml`](../.github/workflows/build-helper.yml).
|
||||
|
||||
Under the hood, this will call the `build-package` and `build-package-linux` scripts in
|
||||
[`scripthaus.md`](../scripthaus.md), which will build the Electron codebase using
|
||||
WebPack and then the `wavesrv` and `mshell` binaries, then it will call `electron-builder`
|
||||
to generate the distributable app packages. The configuration for `electron-builder`
|
||||
is [`electron-builder.config.js`](../electron-builder.config.js).
|
||||
|
||||
We are working to fully automate the building of release artifacts. For now,
|
||||
manual steps are still required to sign and notarize the macOS artifacts. The
|
||||
Linux artifacts do not require additional modification before being published.
|
||||
|
||||
## Local signing and notarizing for macOS
|
||||
|
||||
The [`prepare-macos.sh`](./prepare-macos.sh) script will download the latest build
|
||||
artifacts from S3 and sign and notarize the macOS binaries within it. It will then
|
||||
generate a DMG and a new ZIP archive with the new signed app.
|
||||
|
||||
This will call a few different JS scripts to perform more complicated operations.
|
||||
[`osx-sign.js`](./osx-sign.js) and [`osx-notarize.js`](./osx-notarize.js) call
|
||||
underlying Electron APIs to sign and notarize the package.
|
||||
[`update-latest-mac.js`](./update-latest-mac.js) will then update the `latest-mac.yml`
|
||||
file with the SHA512 checksum and file size of the new signed and notarized installer. This
|
||||
is important for the `electron-updater` auto-update mechanism to then find and validate new releases.
|
||||
|
||||
## Uploading release artifacts for distribution
|
||||
|
||||
### Upload script
|
||||
|
||||
Once the build has been fully validated and is ready to be released, the
|
||||
[`upload-release.sh`](./upload-release.sh) script is then used to grab the completed
|
||||
artifacts and upload them to the `dl.waveterm.dev` S3 bucket for distribution.
|
||||
|
||||
### Homebrew
|
||||
|
||||
Homebrew currently requires a manual bump of the version, but now that we have auto-updates,
|
||||
we should add our cask to the list of apps that can be automatically bumped.
|
||||
|
||||
### Linux
|
||||
|
||||
We do not currently submit the Linux packages to any of the package repositories. We
|
||||
are working on addressing this in the near future.
|
||||
|
||||
## `electron-build` configuration
|
||||
|
||||
Most of our configuration is fairly standard. The main exception to this is that we exclude
|
||||
our Go binaries from the ASAR archive that Electron generates. ASAR files cannot be executed
|
||||
by NodeJS because they are not seen as files and therefore cannot be executed via a Shell
|
||||
command. More information can be found
|
||||
[here](https://www.electronjs.org/docs/latest/tutorial/asar-archives#executing-binaries-inside-asar-archive).
|
||||
|
||||
We also exclude most of our `node_modules` from packaging, as WebPack handles packaging
|
||||
of any dependencies for us. The one exception is `monaco-editor`.
|
||||
|
||||
## Automatic updates
|
||||
|
||||
Thanks to `electron-updater`, we are able to provide automatic app updates for macOS and Linux,
|
||||
as long as the app was distributed as a DMG, AppImage, RPM, or DEB file.
|
||||
|
||||
With each release, `latest-mac.yml` and `latest-linux.yml` files will be produced that point to the
|
||||
newest release. These also include file sizes and checksums to aid in validating the packages. The app
|
||||
will check these files in our S3 bucket every hour to see if a new version is available.
|
@ -1,20 +0,0 @@
|
||||
const eu = require("@electron/universal");
|
||||
const path = require("path");
|
||||
|
||||
const x64Path = path.resolve(__dirname, "temp", "x64", "Wave.app");
|
||||
const arm64Path = path.resolve(__dirname, "temp", "arm64", "Wave.app");
|
||||
const outPath = path.resolve(__dirname, "temp", "Wave.app");
|
||||
|
||||
console.log("building universal package");
|
||||
console.log("x64 path", x64Path);
|
||||
console.log("arm64 path", arm64Path);
|
||||
console.log("output path", outPath);
|
||||
|
||||
(async () => {
|
||||
await eu.makeUniversalApp({
|
||||
x64AppPath: x64Path,
|
||||
arm64AppPath: arm64Path,
|
||||
outAppPath: outPath,
|
||||
});
|
||||
console.log("created macos universal app");
|
||||
})();
|
@ -1,97 +0,0 @@
|
||||
#!/bin/bash
|
||||
# This script is used to build the universal app for macOS
|
||||
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
|
||||
rm -f *.zip *.dmg
|
||||
ZIP_DIR=$SCRIPT_DIR/zip
|
||||
rm -rf $ZIP_DIR
|
||||
mkdir $ZIP_DIR
|
||||
TEMP_DIR=$SCRIPT_DIR/temp
|
||||
rm -rf $TEMP_DIR
|
||||
mkdir $TEMP_DIR
|
||||
BUILDS_DIR=$SCRIPT_DIR/builds
|
||||
rm -rf $BUILDS_DIR
|
||||
|
||||
# Download the builds zip
|
||||
aws s3 cp s3://waveterm-github-artifacts/waveterm-builds.zip .
|
||||
BUILDS_ZIP=waveterm-builds.zip
|
||||
if ! [ -f $BUILDS_ZIP ]; then
|
||||
echo "no $BUILDS_ZIP found";
|
||||
exit 1;
|
||||
fi
|
||||
echo "unzipping $BUILDS_ZIP"
|
||||
unzip -q $BUILDS_ZIP -d $BUILDS_DIR
|
||||
rm $BUILDS_ZIP
|
||||
|
||||
# Ensure we have exactly one of each build
|
||||
find_build()
|
||||
{
|
||||
local BUILD_DIR=$1
|
||||
local BUILD_PATTERN=$2
|
||||
local BUILD_PATH=$(find $BUILD_DIR -type f -iname "$BUILD_PATTERN")
|
||||
local NUM_MATCHES=$(echo $BUILD_PATH | wc -l)
|
||||
if [ "0" -eq "$NUM_MATCHES" ]; then
|
||||
echo "no $BUILD_NAME found in $BUILD_DIR"
|
||||
exit 1
|
||||
elif [ "1" -lt "$NUM_MATCHES" ]; then
|
||||
echo "multiple $BUILD_NAME found in $BUILD_DIR"
|
||||
exit 1
|
||||
fi
|
||||
echo $BUILD_PATH
|
||||
}
|
||||
|
||||
X64_ZIP=$(find_build $BUILDS_DIR "Wave-darwin-x64-*.zip")
|
||||
ARM64_ZIP=$(find_build $BUILDS_DIR "Wave-darwin-arm64-*.zip")
|
||||
set -e
|
||||
|
||||
echo "unzipping zip files"
|
||||
unzip -q $X64_ZIP -d $TEMP_DIR/x64
|
||||
unzip -q $ARM64_ZIP -d $TEMP_DIR/arm64
|
||||
rm $ARM64_ZIP $X64_ZIP
|
||||
|
||||
# Create universal app and sign and notarize it
|
||||
TEMP_WAVE_DIR_ARM=$TEMP_DIR/x64/Wave.app
|
||||
TEMP_WAVE_DIR_X64=$TEMP_DIR/arm64/Wave.app
|
||||
TEMP_WAVE_DIR_UNIVERSAL=$TEMP_DIR/Wave.app
|
||||
lipo -create -output $TEMP_DIR/wavesrv $TEMP_WAVE_DIR_X64/Contents/Resources/app/bin/wavesrv $TEMP_WAVE_DIR_ARM/Contents/Resources/app/bin/wavesrv
|
||||
rm -rf $TEMP_WAVE_DIR_ARM/Contents/Resources/app
|
||||
mv $TEMP_WAVE_DIR_X64/Contents/Resources/app $TEMP_DIR
|
||||
cp $TEMP_DIR/wavesrv $TEMP_DIR/app/bin/wavesrv
|
||||
mkdir $TEMP_WAVE_DIR_ARM/Contents/Resources/app
|
||||
mkdir $TEMP_WAVE_DIR_X64/Contents/Resources/app
|
||||
node $SCRIPT_DIR/build-universal.js
|
||||
rm -rf $TEMP_WAVE_DIR_UNIVERSAL/Contents/Resources/app
|
||||
mv $TEMP_DIR/app $TEMP_WAVE_DIR_UNIVERSAL/Contents/Resources/app
|
||||
node $SCRIPT_DIR/osx-sign.js
|
||||
DEBUG=electron-notarize node $SCRIPT_DIR/osx-notarize.js
|
||||
echo "universal app creation success (build/sign/notarize)"
|
||||
|
||||
UVERSION=$(cat $BUILDS_DIR/version.txt)
|
||||
DMG_NAME="waveterm-macos-universal-${UVERSION}.dmg"
|
||||
ZIP_NAME="Wave-macos-universal-${UVERSION}.zip"
|
||||
|
||||
echo "creating universal zip"
|
||||
ditto $TEMP_WAVE_DIR_UNIVERSAL $ZIP_DIR/Wave.app
|
||||
cd $ZIP_DIR
|
||||
zip -9yqr $ZIP_NAME Wave.app
|
||||
mv $ZIP_NAME $BUILDS_DIR/
|
||||
cd $SCRIPT_DIR
|
||||
|
||||
# Expects create-dmg repo to be cloned in the same parent directory as the waveterm repo.
|
||||
echo "creating universal dmg"
|
||||
$SCRIPT_DIR/../../create-dmg/create-dmg \
|
||||
--volname "WaveTerm" \
|
||||
--window-pos 200 120 \
|
||||
--window-size 600 300 \
|
||||
--icon-size 100 \
|
||||
--icon "Wave.app" 200 130 \
|
||||
--hide-extension "Wave.app" \
|
||||
--app-drop-link 400 125 \
|
||||
$DMG_NAME \
|
||||
"$TEMP_WAVE_DIR_UNIVERSAL"
|
||||
echo "success, created $DMG_NAME"
|
||||
mv $DMG_NAME $BUILDS_DIR/
|
||||
spctl -a -vvv -t install $TEMP_WAVE_DIR_UNIVERSAL/
|
||||
|
||||
rm -rf $TEMP_DIR $ZIP_DIR
|
46
buildres/generate-hash.js
Normal file
46
buildres/generate-hash.js
Normal file
@ -0,0 +1,46 @@
|
||||
// Usage: node generate-hash.js <path-to-installer>
|
||||
// Example: node generate-hash.js ./make/Wave-0.0.1.dmg
|
||||
// This script will generate a hash of the installer file, as defined by electron-builder.
|
||||
// Courtesy of https://github.com/electron-userland/electron-builder/issues/3913#issuecomment-504698845
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const crypto = require("crypto");
|
||||
|
||||
/**
|
||||
* Generate a hash of a file, as defined by electron-builder
|
||||
* @param {string} file - Path to the file
|
||||
* @param {string} algorithm - Hash algorithm to use
|
||||
* @param {string} encoding - Encoding to use
|
||||
* @returns {Promise<string>} - The hash of the file
|
||||
*/
|
||||
async function hashFile(file, algorithm = "sha512", encoding = "base64") {
|
||||
return new Promise((resolve, reject) => {
|
||||
const hash = crypto.createHash(algorithm);
|
||||
hash.on("error", reject).setEncoding(encoding);
|
||||
fs.createReadStream(file, {
|
||||
highWaterMark: 1024 * 1024,
|
||||
/* better to use more memory but hash faster */
|
||||
})
|
||||
.on("error", reject)
|
||||
.on("end", () => {
|
||||
hash.end();
|
||||
resolve(hash.read());
|
||||
})
|
||||
.pipe(hash, {
|
||||
end: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
const installerPath = path.resolve(process.cwd(), process.argv[2]);
|
||||
(async () => {
|
||||
const hash = await hashFile(installerPath);
|
||||
console.log(`hash of ${installerPath}: ${hash}`);
|
||||
})();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
hashFile,
|
||||
};
|
@ -1,18 +1,37 @@
|
||||
// Notarize the Wave.app for macOS
|
||||
|
||||
const { notarize } = require("@electron/notarize");
|
||||
const path = require("path");
|
||||
|
||||
console.log("running osx-notarize");
|
||||
const waveAppPath = path.resolve(__dirname, "temp", "Wave.app");
|
||||
|
||||
notarize({
|
||||
appPath: waveAppPath,
|
||||
tool: "notarytool",
|
||||
keychainProfile: "notarytool-creds",
|
||||
})
|
||||
.then(() => {
|
||||
console.log("notarize success");
|
||||
/**
|
||||
* Notarize the Wave.app for macOS
|
||||
* @param {string} waveAppPath - Path to the Wave.app
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function notarizeApp(waveAppPath) {
|
||||
return notarize({
|
||||
appPath: waveAppPath,
|
||||
tool: "notarytool",
|
||||
keychainProfile: "notarytool-creds",
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log("notarize error", e);
|
||||
process.exit(1);
|
||||
});
|
||||
.then(() => {
|
||||
console.log("notarize success");
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log("notarize error", e);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
console.log("running osx-notarize");
|
||||
const waveAppPath = path.resolve(__dirname, "temp", "Wave.app");
|
||||
(async () => {
|
||||
await notarizeApp(waveAppPath);
|
||||
console.log("notarization complete");
|
||||
})();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
notarizeApp,
|
||||
};
|
||||
|
@ -1,22 +1,45 @@
|
||||
// Sign the app and binaries for macOS
|
||||
|
||||
const { signAsync } = require("@electron/osx-sign");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
console.log("running osx-sign");
|
||||
const waveAppPath = path.resolve(__dirname, "temp", "Wave.app");
|
||||
signAsync({
|
||||
app: waveAppPath,
|
||||
binaries: [
|
||||
waveAppPath + "/Contents/Resources/app/bin/wavesrv",
|
||||
waveAppPath + "/Contents/Resources/app/bin/mshell/mshell-v0.4-linux.amd64",
|
||||
waveAppPath + "/Contents/Resources/app/bin/mshell/mshell-v0.4-linux.arm64",
|
||||
waveAppPath + "/Contents/Resources/app/bin/mshell/mshell-v0.4-darwin.amd64",
|
||||
waveAppPath + "/Contents/Resources/app/bin/mshell/mshell-v0.4-darwin.arm64",
|
||||
],
|
||||
})
|
||||
.then(() => {
|
||||
console.log("signing success");
|
||||
/**
|
||||
* Sign the app and binaries for macOS
|
||||
* @param {string} waveAppPath - Path to the Wave.app
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function signApp(waveAppPath) {
|
||||
const binDirPath = path.resolve(waveAppPath, "Contents", "Resources", "app.asar.unpacked", "bin");
|
||||
const binFilePaths = fs
|
||||
.readdirSync(binDirPath, { recursive: true, withFileTypes: true })
|
||||
.filter((f) => f.isFile())
|
||||
.map((f) => path.resolve(binDirPath, f.path, f.name));
|
||||
console.log("waveAppPath", waveAppPath);
|
||||
console.log("binDirPath", binDirPath);
|
||||
console.log("binFilePaths", binFilePaths);
|
||||
return signAsync({
|
||||
app: waveAppPath,
|
||||
binaries: binFilePaths,
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log("signing error", e);
|
||||
process.exit(1);
|
||||
});
|
||||
.then(() => {
|
||||
console.log("signing success");
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log("signing error", e);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
console.log("running osx-sign");
|
||||
const waveAppPath = path.resolve(__dirname, "temp", "Wave.app");
|
||||
(async () => {
|
||||
await signApp(waveAppPath);
|
||||
console.log("signing complete");
|
||||
})();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
signApp,
|
||||
};
|
||||
|
88
buildres/prepare-macos.sh
Normal file
88
buildres/prepare-macos.sh
Normal file
@ -0,0 +1,88 @@
|
||||
#!/bin/bash
|
||||
# This script is used to sign and notarize the universal app for macOS
|
||||
|
||||
# Gets the directory of the script
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
|
||||
# Remove old files and dirs, create new ones
|
||||
rm -f *.zip *.dmg
|
||||
ZIP_DIR=$SCRIPT_DIR/zip
|
||||
rm -rf $ZIP_DIR
|
||||
mkdir $ZIP_DIR
|
||||
TEMP_DIR=$SCRIPT_DIR/temp
|
||||
rm -rf $TEMP_DIR
|
||||
mkdir $TEMP_DIR
|
||||
BUILDS_DIR=$SCRIPT_DIR/builds
|
||||
rm -rf $BUILDS_DIR
|
||||
|
||||
# Download the builds zip
|
||||
aws s3 cp s3://waveterm-github-artifacts/waveterm-builds.zip .
|
||||
BUILDS_ZIP=waveterm-builds.zip
|
||||
if ! [ -f $BUILDS_ZIP ]; then
|
||||
echo "no $BUILDS_ZIP found";
|
||||
exit 1;
|
||||
fi
|
||||
echo "unzipping $BUILDS_ZIP"
|
||||
unzip -q $BUILDS_ZIP -d $BUILDS_DIR
|
||||
rm $BUILDS_ZIP
|
||||
|
||||
# Finds a file in a directory matching a filename pattern. Ensures there is exactly one match.
|
||||
find_file()
|
||||
{
|
||||
local FILE_DIR=$1
|
||||
local FILE_PATTERN=$2
|
||||
local FILE_PATH=$(find $FILE_DIR -type f -iname "$FILE_PATTERN")
|
||||
local NUM_MATCHES=$(echo $FILE_PATH | wc -l)
|
||||
if [ "0" -eq "$NUM_MATCHES" ]; then
|
||||
echo "no $FILE_PATTERN found in $FILE_DIR"
|
||||
exit 1
|
||||
elif [ "1" -lt "$NUM_MATCHES" ]; then
|
||||
echo "multiple $FILE_PATTERN found in $FILE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
echo $FILE_PATH
|
||||
}
|
||||
|
||||
# Unzip Mac build
|
||||
MAC_ZIP=$(find_file $BUILDS_DIR "Wave-darwin-universal-*.zip")
|
||||
unzip -q $MAC_ZIP -d $TEMP_DIR
|
||||
|
||||
# Sign and notarize the app
|
||||
node $SCRIPT_DIR/osx-sign.js
|
||||
DEBUG=electron-notarize node $SCRIPT_DIR/osx-notarize.js
|
||||
|
||||
# Zip and move
|
||||
echo "creating universal zip"
|
||||
ZIP_NAME=$(basename $MAC_ZIP)
|
||||
TEMP_WAVE_DIR_UNIVERSAL=$TEMP_DIR/Wave.app
|
||||
ditto $TEMP_WAVE_DIR_UNIVERSAL $ZIP_DIR/Wave.app
|
||||
cd $ZIP_DIR
|
||||
zip -9yqr $ZIP_NAME Wave.app
|
||||
mv $ZIP_NAME $BUILDS_DIR/
|
||||
cd $SCRIPT_DIR
|
||||
|
||||
# Create a dmg
|
||||
# Expects create-dmg repo to be cloned in the same parent directory as the waveterm repo.
|
||||
echo "creating universal dmg"
|
||||
DMG_NAME=$(echo $ZIP_NAME | sed 's/\.zip/\.dmg/')
|
||||
$SCRIPT_DIR/../../create-dmg/create-dmg \
|
||||
--volname "WaveTerm" \
|
||||
--window-pos 200 120 \
|
||||
--window-size 600 300 \
|
||||
--icon-size 100 \
|
||||
--icon "Wave.app" 200 130 \
|
||||
--hide-extension "Wave.app" \
|
||||
--app-drop-link 400 125 \
|
||||
$DMG_NAME \
|
||||
"$TEMP_WAVE_DIR_UNIVERSAL"
|
||||
echo "success, created $DMG_NAME"
|
||||
mv $DMG_NAME $BUILDS_DIR/
|
||||
spctl -a -vvv -t install $TEMP_WAVE_DIR_UNIVERSAL/
|
||||
|
||||
# Update latest-mac.yml
|
||||
echo "updating latest-mac.yml"
|
||||
LATEST_MAC_YML=$BUILDS_DIR/latest-mac.yml
|
||||
node $SCRIPT_DIR/update-latest-mac.js $MAC_ZIP $LATEST_MAC_YML
|
||||
|
||||
# Clean up
|
||||
rm -rf $TEMP_DIR $ZIP_DIR
|
@ -1,50 +0,0 @@
|
||||
## MacOS Universal Build Notes
|
||||
|
||||
This doesn't work out of the box and doesn't seem to be well documented anywhere.
|
||||
The basic idea is that we have to create separate x64 and a arm64 builds and
|
||||
then link them together using @electron/universal. Seems easy, but in
|
||||
practice it isn't.
|
||||
|
||||
(1) The separate x64 and arm64 builds *cannot* be signed (osx-sign). This
|
||||
makes sense because once we lipo the executables together they need to be
|
||||
resigned (their SHA sums will change). If you accidentally sign them
|
||||
@electron/universal will also refuse to work.
|
||||
|
||||
(2) We already deal with architecture specific builds with Go for wavesrv and
|
||||
waveshell. This upsets @electron/universal as well since these are *binaries*
|
||||
and we don't want to lipo them together.
|
||||
|
||||
(3) Small differences in waveterm.js. The non-executable files must be
|
||||
*identical*. Well, that's a problem when we inject build times into the files.
|
||||
Other small differences can also happen (like different go toolchains, etc.).
|
||||
|
||||
(4) ASAR builds. By default if there are differences in the "app" folder
|
||||
@electron/universal plays some neat tricks to separate out the x64 from the
|
||||
arm64 code using a app.asar stub. That's great for standard electron builds
|
||||
where the entrypoint is hardcoded to index.js. Ours isn't so this doesn't work.
|
||||
|
||||
(5) ASAR builds and unpacked files. I don't know all the details here, but
|
||||
for Wave to work we have to have some files unpacked (not in ASAR format).
|
||||
The reason is that we execute them directly (e.g. wavesrv and waveshell), they
|
||||
aren't just loaded by electron.
|
||||
|
||||
(6) Ignoring and skipping files in @electron/universal is hard because
|
||||
it just takes one minimatch pattern.
|
||||
|
||||
---
|
||||
|
||||
## Solution
|
||||
|
||||
1. Create unsigned builds on x64 and arm64
|
||||
2. Move the builds to the core build machine and *extract* their "app" directories.
|
||||
In theory because we aren't using any native node modules they function the same in
|
||||
both environments.
|
||||
3. Run @electron/universal on the two unsigned builds (without their app directories).
|
||||
4. lipo wavesrv from x64 and arm64 manually to create a universal wavesrv binary.
|
||||
5. Copy our extracted "app" directory (with the newly created universal "wavesrv")
|
||||
back into the universal Wave.app created by @electron/universal.
|
||||
6. Manually run osx-sign to sign the new universal build (make sure to
|
||||
pass the wavesrv and waveshell programs as extra binaries).
|
||||
7. Manually create the new universal dmg (using create-dmg).
|
||||
|
||||
|
40
buildres/update-latest-mac.js
Normal file
40
buildres/update-latest-mac.js
Normal file
@ -0,0 +1,40 @@
|
||||
// Updates the latest-mac.yml file with the signed and notarized version of the latest installer
|
||||
// Usage: node update-latest.js <path-to-installer>
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const { hashFile } = require("./generate-hash");
|
||||
const yaml = require("yaml");
|
||||
|
||||
/**
|
||||
* Updates the latest-mac.yml file with the signed and notarized version of the latest installer
|
||||
* @param {string} installerPath - Path to the installer
|
||||
* @param {string} ymlPath - Path to the latest-mac.yml file
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function updateLatestMac(installerPath, ymlPath) {
|
||||
const hash = (await hashFile(installerPath)).trim();
|
||||
const size = fs.statSync(installerPath).size;
|
||||
const yml = yaml.parse(fs.readFileSync(ymlPath, "utf8"));
|
||||
for (const file of yml.files) {
|
||||
if (file.url === path.basename(installerPath)) {
|
||||
file.sha512 = hash;
|
||||
file.size = size;
|
||||
}
|
||||
}
|
||||
yml.sha512 = hash;
|
||||
fs.writeFileSync(ymlPath, yaml.stringify(yml));
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
const installerPath = path.resolve(process.cwd(), process.argv[2]);
|
||||
const ymlPath = path.resolve(process.cwd(), process.argv[3]);
|
||||
(async () => {
|
||||
await updateLatestMac(installerPath, ymlPath);
|
||||
console.log("latest-mac.yml updated");
|
||||
})();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
updateLatestMac,
|
||||
};
|
@ -1,13 +1,13 @@
|
||||
#!/bin/bash
|
||||
# This script is used to upload signed and notarized releases to S3 and update the Electron auto-update release feeds.
|
||||
|
||||
# Gets the directory of the script
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
|
||||
BUILDS_DIR=$SCRIPT_DIR/builds
|
||||
TEMP2_DIR=$SCRIPT_DIR/temp2
|
||||
|
||||
MAIN_RELEASE_PATH="dl.waveterm.dev/build"
|
||||
AUTOUPDATE_RELEASE_PATH="dl.waveterm.dev/autoupdate"
|
||||
AUTOUPDATE_RELEASE_PATH="dl.waveterm.dev/releases"
|
||||
|
||||
# Copy the builds to the temp2 directory
|
||||
echo "Copying builds to temp2"
|
||||
@ -22,64 +22,13 @@ if [ -z "$UVERSION" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find the DMG file
|
||||
echo "Finding DMG"
|
||||
DMG=$(find $TEMP2_DIR -type f -iname "*.dmg")
|
||||
# Ensure there is only one
|
||||
NUM_DMGS=$(echo $DMG | wc -l)
|
||||
if [ "0" -eq "$NUM_DMGS" ]; then
|
||||
echo "no DMG found in $TEMP2_DIR"
|
||||
exit 1
|
||||
elif [ "1" -lt "$NUM_DMGS" ]; then
|
||||
echo "multiple DMGs found in $TEMP2_DIR"
|
||||
exit 1
|
||||
fi
|
||||
# Remove files we don't want to upload
|
||||
rm $TEMP2_DIR/version.txt
|
||||
rm $TEMP2_DIR/builder-*.yml
|
||||
|
||||
# Find the Mac zip
|
||||
echo "Finding Mac zip"
|
||||
MAC_ZIP=$(find $TEMP2_DIR -type f -iname "*mac*.zip")
|
||||
# Ensure there is only one
|
||||
NUM_MAC_ZIPS=$(echo $MAC_ZIP | wc -l)
|
||||
if [ "0" -eq "$NUM_MAC_ZIPS" ]; then
|
||||
echo "no Mac zip found in $TEMP2_DIR"
|
||||
exit 1
|
||||
elif [ "1" -lt "$NUM_MAC_ZIPS" ]; then
|
||||
echo "multiple Mac zips found in $TEMP2_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find the Linux zips
|
||||
echo "Finding Linux zips"
|
||||
LINUX_ZIPS=$(find $TEMP2_DIR -type f -iname "*linux*.zip")
|
||||
# Ensure there is at least one
|
||||
NUM_LINUX_ZIPS=$(echo $LINUX_ZIPS | wc -l)
|
||||
if [ "0" -eq "$NUM_LINUX_ZIPS" ]; then
|
||||
echo "no Linux zips found in $TEMP2_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Upload the DMG
|
||||
echo "Uploading DMG"
|
||||
DMG_NAME=$(basename $DMG)
|
||||
aws s3 cp $DMG s3:/$MAIN_RELEASE_PATH/$DMG_NAME
|
||||
|
||||
# Upload the Linux zips
|
||||
echo "Uploading Linux zips"
|
||||
for LINUX_ZIP in $LINUX_ZIPS; do
|
||||
LINUX_ZIP_NAME=$(basename $LINUX_ZIP)
|
||||
aws s3 cp $LINUX_ZIP s3://$MAIN_RELEASE_PATH/$LINUX_ZIP_NAME
|
||||
done
|
||||
|
||||
# Upload the autoupdate Mac zip
|
||||
echo "Uploading Mac zip"
|
||||
MAC_ZIP_NAME=$(basename $MAC_ZIP)
|
||||
aws s3 cp $MAC_ZIP s3://$AUTOUPDATE_RELEASE_PATH/$MAC_ZIP_NAME
|
||||
|
||||
# Update the autoupdate feeds
|
||||
echo "Updating autoupdate feeds"
|
||||
RELEASES_CONTENTS="{\"name\":\"$UVERSION\",\"notes\":\"\",\"url\":\"https://$AUTOUPDATE_RELEASE_PATH/$MAC_ZIP_NAME\"}"
|
||||
aws s3 cp - s3://$AUTOUPDATE_RELEASE_PATH/darwin/arm64/RELEASES.json <<< $RELEASES_CONTENTS
|
||||
aws s3 cp - s3://$AUTOUPDATE_RELEASE_PATH/darwin/x64/RELEASES.json <<< $RELEASES_CONTENTS
|
||||
# Upload the artifacts
|
||||
echo "Uploading build artifacts to $AUTOUPDATE_RELEASE_PATH"
|
||||
aws s3 cp $TEMP2_DIR/ s3://$AUTOUPDATE_RELEASE_PATH/ --recursive
|
||||
|
||||
# Clean up
|
||||
echo "Cleaning up"
|
||||
|
84
electron-builder.config.js
Normal file
84
electron-builder.config.js
Normal file
@ -0,0 +1,84 @@
|
||||
const pkg = require("./package.json");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
/**
|
||||
* @type {import('electron-builder').Configuration}
|
||||
* @see https://www.electron.build/configuration/configuration
|
||||
*/
|
||||
const config = {
|
||||
appId: pkg.build.appId,
|
||||
productName: pkg.productName,
|
||||
artifactName: "${productName}-${platform}-${arch}-${version}.${ext}",
|
||||
npmRebuild: false,
|
||||
nodeGypRebuild: false,
|
||||
electronCompile: false,
|
||||
files: [
|
||||
{
|
||||
from: "./dist",
|
||||
to: "./dist",
|
||||
filter: ["**/*"],
|
||||
},
|
||||
{
|
||||
from: "./public",
|
||||
to: "./public",
|
||||
filter: ["**/*"],
|
||||
},
|
||||
{
|
||||
from: "./bin",
|
||||
to: "./bin",
|
||||
filter: ["**/*"],
|
||||
},
|
||||
{
|
||||
from: ".",
|
||||
to: ".",
|
||||
filter: ["package.json"],
|
||||
},
|
||||
"!**/node_modules/**${/*}", // Ignore node_modules by default
|
||||
{
|
||||
from: "./node_modules",
|
||||
to: "./node_modules",
|
||||
filter: ["monaco-editor/min/**/*"], // This is the only module we want to include
|
||||
},
|
||||
],
|
||||
directories: {
|
||||
output: "make",
|
||||
},
|
||||
asarUnpack: ["bin/**/*"],
|
||||
mac: {
|
||||
target: {
|
||||
target: "zip",
|
||||
arch: "universal",
|
||||
},
|
||||
icon: "public/waveterm.icns",
|
||||
category: "public.app-category.developer-tools",
|
||||
minimumSystemVersion: "10.15.0",
|
||||
binaries: fs
|
||||
.readdirSync("bin", { recursive: true, withFileTypes: true })
|
||||
.filter((f) => f.isFile())
|
||||
.map((f) => path.resolve(f.path, f.name)),
|
||||
},
|
||||
linux: {
|
||||
executableName: pkg.productName,
|
||||
category: "TerminalEmulator",
|
||||
icon: "public/waveterm.icns",
|
||||
target: ["zip", "deb", "rpm", "AppImage"],
|
||||
synopsis: pkg.description,
|
||||
description: null,
|
||||
desktop: {
|
||||
Name: pkg.productName,
|
||||
Comment: pkg.description,
|
||||
Keywords: "developer;terminal;emulator;",
|
||||
category: "Development;Utility;",
|
||||
},
|
||||
},
|
||||
appImage: {
|
||||
license: "LICENSE",
|
||||
},
|
||||
publish: {
|
||||
provider: "generic",
|
||||
url: "https://waveterm-test-autoupdate.s3.us-west-2.amazonaws.com/autoupdate",
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
@ -1,64 +0,0 @@
|
||||
var AllowedFirstParts = {
|
||||
"package.json": true,
|
||||
dist: true,
|
||||
public: true,
|
||||
node_modules: true,
|
||||
bin: true,
|
||||
};
|
||||
|
||||
var AllowedNodeModules = {
|
||||
"monaco-editor": true,
|
||||
};
|
||||
|
||||
var modCache = {};
|
||||
|
||||
function ignoreFn(path) {
|
||||
let parts = path.split("/");
|
||||
if (parts.length <= 1) {
|
||||
return false;
|
||||
}
|
||||
let firstPart = parts[1];
|
||||
if (!AllowedFirstParts[firstPart]) {
|
||||
return true;
|
||||
}
|
||||
if (firstPart == "node_modules") {
|
||||
if (parts.length <= 2) {
|
||||
return false;
|
||||
}
|
||||
if (parts.length > 3) {
|
||||
if (parts[3] == "build") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
let nodeModule = parts[2];
|
||||
if (!modCache[nodeModule]) {
|
||||
modCache[nodeModule] = true;
|
||||
}
|
||||
if (!AllowedNodeModules[nodeModule]) {
|
||||
return true;
|
||||
}
|
||||
if (nodeModule == "monaco-editor" && parts.length >= 4 && parts[3] != "min") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
packagerConfig: {
|
||||
ignore: ignoreFn,
|
||||
files: [
|
||||
"package.json",
|
||||
"dist/*",
|
||||
"public/*",
|
||||
],
|
||||
icon: "public/waveterm.icns",
|
||||
},
|
||||
rebuildConfig: {},
|
||||
makers: [
|
||||
{
|
||||
name: "@electron-forge/maker-zip",
|
||||
platforms: ["darwin", "linux"],
|
||||
},
|
||||
],
|
||||
};
|
32
package.json
32
package.json
@ -1,10 +1,22 @@
|
||||
{
|
||||
"name": "waveterm",
|
||||
"author": "Command Line Inc",
|
||||
"author": {
|
||||
"name": "Command Line Inc",
|
||||
"email": "info@commandline.dev"
|
||||
},
|
||||
"productName": "Wave",
|
||||
"version": "v0.6.1",
|
||||
"description": "An open-source, cross-platform, AI-integrated, modern terminal for seamless workflows.",
|
||||
"version": "0.6.1",
|
||||
"main": "dist/emain.js",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/wavetermdev/waveterm"
|
||||
},
|
||||
"homepage": "https://waveterm.dev",
|
||||
"build": {
|
||||
"appId": "dev.commandline.waveterm"
|
||||
},
|
||||
"dependencies": {
|
||||
"@monaco-editor/react": "^4.5.1",
|
||||
"@table-nav/core": "^0.0.7",
|
||||
@ -18,6 +30,7 @@
|
||||
"dayjs": "^1.11.3",
|
||||
"dompurify": "^3.0.2",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"electron-updater": "^6.1.8",
|
||||
"framer-motion": "^10.16.16",
|
||||
"mobx": "6.12",
|
||||
"mobx-react": "^7.5.0",
|
||||
@ -50,13 +63,6 @@
|
||||
"@babel/preset-env": "^7.18.2",
|
||||
"@babel/preset-react": "^7.23.3",
|
||||
"@babel/preset-typescript": "^7.17.12",
|
||||
"@electron-forge/cli": "^7.2.0",
|
||||
"@electron-forge/maker-deb": "^7.2.0",
|
||||
"@electron-forge/maker-rpm": "^7.2.0",
|
||||
"@electron-forge/maker-snap": "^7.2.0",
|
||||
"@electron-forge/maker-squirrel": "^7.2.0",
|
||||
"@electron-forge/maker-zip": "^7.2.0",
|
||||
"@electron/rebuild": "^3.4.0",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@types/classnames": "^2.3.1",
|
||||
"@types/electron": "^1.6.10",
|
||||
@ -72,6 +78,7 @@
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"css-loader": "^6.7.1",
|
||||
"electron": "29.0.1",
|
||||
"electron-builder": "^24.12.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"http-server": "^14.1.1",
|
||||
"less": "^4.1.2",
|
||||
@ -87,7 +94,10 @@
|
||||
"webpack-bundle-analyzer": "^4.10.1",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.9.1",
|
||||
"webpack-merge": "^5.8.0"
|
||||
"webpack-merge": "^5.8.0",
|
||||
"yaml": "^2.4.0"
|
||||
},
|
||||
"scripts": {}
|
||||
"scripts": {
|
||||
"postinstall": "electron-builder install-app-deps"
|
||||
}
|
||||
}
|
||||
|
@ -44,13 +44,21 @@ rm -rf bin/
|
||||
rm -rf build/
|
||||
node_modules/.bin/webpack --env prod
|
||||
WAVESRV_VERSION=$(node -e 'console.log(require("./version.js"))')
|
||||
WAVESHELL_VERSION=v0.4
|
||||
GO_LDFLAGS="-s -w -X main.BuildTime=$(date +'%Y%m%d%H%M')"
|
||||
(cd waveshell; CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-v0.4-darwin.amd64 main-waveshell.go)
|
||||
(cd waveshell; CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-v0.4-darwin.arm64 main-waveshell.go)
|
||||
(cd waveshell; CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-v0.4-linux.amd64 main-waveshell.go)
|
||||
(cd waveshell; CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-v0.4-linux.arm64 main-waveshell.go)
|
||||
(cd wavesrv; CGO_ENABLED=1 go build -tags "osusergo,netgo,sqlite_omit_load_extension" -ldflags "-X main.BuildTime=$(date +'%Y%m%d%H%M') -X main.WaveVersion=$WAVESRV_VERSION" -o ../bin/wavesrv ./cmd)
|
||||
node_modules/.bin/electron-forge make
|
||||
function buildWaveShell {
|
||||
(cd waveshell; CGO_ENABLED=0 GOOS=$1 GOARCH=$2 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-$WAVESHELL_VERSION-$1.$2 main-waveshell.go)
|
||||
}
|
||||
function buildWaveSrv {
|
||||
(cd wavesrv; CGO_ENABLED=1 GOARCH=$1 go build -tags "osusergo,netgo,sqlite_omit_load_extension" -ldflags "-X main.BuildTime=$(date +'%Y%m%d%H%M') -X main.WaveVersion=$WAVESRV_VERSION" -o ../bin/wavesrv.$1 ./cmd)
|
||||
}
|
||||
buildWaveShell darwin amd64
|
||||
buildWaveShell darwin arm64
|
||||
buildWaveShell linux amd64
|
||||
buildWaveShell linux arm64
|
||||
buildWaveSrv arm64
|
||||
buildWaveSrv amd64
|
||||
yarn run electron-builder -c electron-builder.config.js -m -p never
|
||||
```
|
||||
|
||||
```bash
|
||||
@ -61,20 +69,21 @@ rm -rf bin/
|
||||
rm -rf build/
|
||||
node_modules/.bin/webpack --env prod
|
||||
WAVESRV_VERSION=$(node -e 'console.log(require("./version.js"))')
|
||||
WAVESHELL_VERSION=v0.4
|
||||
GO_LDFLAGS="-s -w -X main.BuildTime=$(date +'%Y%m%d%H%M')"
|
||||
(cd waveshell; CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-v0.4-darwin.amd64 main-waveshell.go)
|
||||
(cd waveshell; CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-v0.4-darwin.arm64 main-waveshell.go)
|
||||
(cd waveshell; CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-v0.4-linux.amd64 main-waveshell.go)
|
||||
(cd waveshell; CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-v0.4-linux.arm64 main-waveshell.go)
|
||||
# adds -extldflags=-static, *only* on linux (macos does not support fully static binaries) to avoid a glibc dependency
|
||||
(cd wavesrv; CGO_ENABLED=1 go build -tags "osusergo,netgo,sqlite_omit_load_extension" -ldflags "-linkmode 'external' -extldflags=-static $GO_LDFLAGS -X main.WaveVersion=$WAVESRV_VERSION" -o ../bin/wavesrv ./cmd)
|
||||
node_modules/.bin/electron-forge make
|
||||
```
|
||||
|
||||
```bash
|
||||
# @scripthaus command open-electron-package
|
||||
# @scripthaus cd :playbook
|
||||
open out/Wave-darwin-x64/Wave.app
|
||||
function buildWaveShell {
|
||||
(cd waveshell; CGO_ENABLED=0 GOOS=$1 GOARCH=$2 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-$WAVESHELL_VERSION-$1.$2 main-waveshell.go)
|
||||
}
|
||||
function buildWaveSrv {
|
||||
# adds -extldflags=-static, *only* on linux (macos does not support fully static binaries) to avoid a glibc dependency
|
||||
(cd wavesrv; CGO_ENABLED=1 GOARCH=$1 go build -tags "osusergo,netgo,sqlite_omit_load_extension" -ldflags "-linkmode 'external' -extldflags=-static $GO_LDFLAGS -X main.WaveVersion=$WAVESRV_VERSION" -o ../bin/wavesrv.$1 ./cmd)
|
||||
}
|
||||
buildWaveShell darwin amd64
|
||||
buildWaveShell darwin arm64
|
||||
buildWaveShell linux amd64
|
||||
buildWaveShell linux arm64
|
||||
buildWaveSrv $GOARCH
|
||||
yarn run electron-builder -c electron-builder.config.js -l -p never
|
||||
```
|
||||
|
||||
```bash
|
||||
@ -87,12 +96,15 @@ CGO_ENABLED=1 go build -tags "osusergo,netgo,sqlite_omit_load_extension" -ldflag
|
||||
```bash
|
||||
# @scripthaus command fullbuild-waveshell
|
||||
set -e
|
||||
cd waveshell
|
||||
WAVESHELL_VERSION=v0.4
|
||||
GO_LDFLAGS="-s -w -X main.BuildTime=$(date +'%Y%m%d%H%M')"
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-v0.4-linux.amd64 main-waveshell.go
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-v0.4-linux.arm64 main-waveshell.go
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-v0.4-darwin.amd64 main-waveshell.go
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-v0.4-darwin.arm64 main-waveshell.go
|
||||
function buildWaveShell {
|
||||
(cd waveshell; CGO_ENABLED=0 GOOS=$1 GOARCH=$2 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-$WAVESHELL_VERSION-$1.$2 main-waveshell.go)
|
||||
}
|
||||
buildWaveShell darwin amd64
|
||||
buildWaveShell darwin arm64
|
||||
buildWaveShell linux amd64
|
||||
buildWaveShell linux arm64
|
||||
```
|
||||
|
||||
```bash
|
||||
|
@ -13,7 +13,6 @@ import { compareLoose } from "semver";
|
||||
import { ReactComponent as AppsIcon } from "@/assets/icons/apps.svg";
|
||||
import { ReactComponent as WorkspacesIcon } from "@/assets/icons/workspaces.svg";
|
||||
import { ReactComponent as SettingsIcon } from "@/assets/icons/settings.svg";
|
||||
import { ReactComponent as WaveLogoWord } from "@/assets/wave-logo_horizontal-coloronblack.svg";
|
||||
import { ReactComponent as WaveLogo } from "@/assets/waveterm-logo.svg";
|
||||
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
@ -176,36 +175,20 @@ class MainSideBar extends React.Component<MainSideBarProps, {}> {
|
||||
*/
|
||||
@boundMethod
|
||||
getUpdateAppBanner(): React.ReactNode {
|
||||
if (GlobalModel.platform == "darwin") {
|
||||
const status = GlobalModel.appUpdateStatus.get();
|
||||
if (status == "ready") {
|
||||
return (
|
||||
<SideBarItem
|
||||
key="update-ready"
|
||||
className="update-banner"
|
||||
frontIcon={<i className="fa-sharp fa-regular fa-circle-up icon" />}
|
||||
contents="Click to Install Update"
|
||||
onClick={() => GlobalModel.installAppUpdate()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const status = GlobalModel.appUpdateStatus.get();
|
||||
if (status == "ready") {
|
||||
return (
|
||||
<SideBarItem
|
||||
key="update-ready"
|
||||
className="update-banner"
|
||||
frontIcon={<i className="fa-sharp fa-regular fa-circle-up icon" />}
|
||||
contents="Click to Install Update"
|
||||
onClick={() => GlobalModel.installAppUpdate()}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
const clientData = this.props.clientData;
|
||||
if (!clientData?.clientopts.noreleasecheck && !isBlank(clientData?.releaseinfo?.latestversion)) {
|
||||
if (compareLoose(appconst.VERSION, clientData.releaseinfo.latestversion) < 0) {
|
||||
return (
|
||||
<SideBarItem
|
||||
key="update-available"
|
||||
className="update-banner"
|
||||
frontIcon={<i className="fa-sharp fa-regular fa-circle-up icon" />}
|
||||
contents="Update Available"
|
||||
onClick={() => openLink("https://www.waveterm.dev/download?ref=upgrade")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getSessions() {
|
||||
|
@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as electron from "electron";
|
||||
import { autoUpdater } from "electron-updater";
|
||||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import fetch from "node-fetch";
|
||||
@ -69,9 +70,10 @@ function log(...msg: any[]) {
|
||||
console.log = log;
|
||||
console.log(
|
||||
sprintf(
|
||||
"waveterm-app starting, WAVETERM_HOME=%s, apppath=%s arch=%s/%s",
|
||||
"waveterm-app starting, WAVETERM_HOME=%s, electronpath=%s gopath=%s arch=%s/%s",
|
||||
waveHome,
|
||||
getAppBasePath(),
|
||||
getElectronAppBasePath(),
|
||||
getGoAppBasePath(),
|
||||
unamePlatform,
|
||||
unameArch
|
||||
)
|
||||
@ -130,31 +132,49 @@ function checkPromptMigrate() {
|
||||
}
|
||||
}
|
||||
|
||||
// for dev, this is just the waveterm directory
|
||||
// for prod, this is .../Wave.app/Contents/Resources/app
|
||||
function getAppBasePath() {
|
||||
/**
|
||||
* Gets the base path to the Electron app resources. For dev, this is the root of the project. For packaged apps, this is the app.asar archive.
|
||||
* @returns The base path of the Electron application
|
||||
*/
|
||||
function getElectronAppBasePath(): string {
|
||||
return path.dirname(__dirname);
|
||||
}
|
||||
|
||||
function getBaseHostPort() {
|
||||
/**
|
||||
* Gets the base path to the Go backend. If the app is packaged as an asar, the path will be in a separate unpacked directory.
|
||||
* @returns The base path of the Go backend
|
||||
*/
|
||||
function getGoAppBasePath(): string {
|
||||
const appDir = getElectronAppBasePath();
|
||||
if (appDir.endsWith(".asar")) {
|
||||
return `${appDir}.unpacked`;
|
||||
} else {
|
||||
return appDir;
|
||||
}
|
||||
}
|
||||
|
||||
function getBaseHostPort(): string {
|
||||
if (isDev) {
|
||||
return DevServerEndpoint;
|
||||
}
|
||||
return ProdServerEndpoint;
|
||||
}
|
||||
|
||||
function getWaveSrvPath() {
|
||||
return path.join(getAppBasePath(), "bin", "wavesrv");
|
||||
function getWaveSrvPath(): string {
|
||||
if (isDev) {
|
||||
return path.join(getGoAppBasePath(), "bin", "wavesrv");
|
||||
}
|
||||
return path.join(getGoAppBasePath(), "bin", `wavesrv.${unameArch}`);
|
||||
}
|
||||
|
||||
function getWaveSrvCmd() {
|
||||
function getWaveSrvCmd(): string {
|
||||
const waveSrvPath = getWaveSrvPath();
|
||||
const waveHome = getWaveHomeDir();
|
||||
const logFile = path.join(waveHome, "wavesrv.log");
|
||||
return `"${waveSrvPath}" >> "${logFile}" 2>&1`;
|
||||
}
|
||||
|
||||
function getWaveSrvCwd() {
|
||||
function getWaveSrvCwd(): string {
|
||||
return getWaveHomeDir();
|
||||
}
|
||||
|
||||
@ -162,7 +182,7 @@ function ensureDir(dir: fs.PathLike) {
|
||||
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
||||
}
|
||||
|
||||
function readAuthKey() {
|
||||
function readAuthKey(): string {
|
||||
const homeDir = getWaveHomeDir();
|
||||
const authKeyFileName = path.join(homeDir, AuthKeyFile);
|
||||
if (!fs.existsSync(authKeyFileName)) {
|
||||
@ -259,7 +279,7 @@ electron.Menu.setApplicationMenu(menu);
|
||||
|
||||
let MainWindow: Electron.BrowserWindow | null = null;
|
||||
|
||||
function getMods(input: any) {
|
||||
function getMods(input: any): object {
|
||||
return { meta: input.meta, shift: input.shift, ctrl: input.control, alt: input.alt };
|
||||
}
|
||||
|
||||
@ -291,7 +311,7 @@ function shFrameNavHandler(event: Electron.Event<Electron.WebContentsWillFrameNa
|
||||
console.log("frame navigation canceled");
|
||||
}
|
||||
|
||||
function createMainWindow(clientData: ClientDataType | null) {
|
||||
function createMainWindow(clientData: ClientDataType | null): Electron.BrowserWindow {
|
||||
const bounds = calcBounds(clientData);
|
||||
setKeyUtilPlatform(platform());
|
||||
const win = new electron.BrowserWindow({
|
||||
@ -305,11 +325,11 @@ function createMainWindow(clientData: ClientDataType | null) {
|
||||
transparent: true,
|
||||
icon: unamePlatform == "linux" ? "public/logos/wave-logo-dark.png" : undefined,
|
||||
webPreferences: {
|
||||
preload: path.join(getAppBasePath(), DistDir, "preload.js"),
|
||||
preload: path.join(getElectronAppBasePath(), DistDir, "preload.js"),
|
||||
},
|
||||
});
|
||||
const indexHtml = isDev ? "index-dev.html" : "index.html";
|
||||
win.loadFile(path.join(getAppBasePath(), "public", indexHtml));
|
||||
win.loadFile(path.join(getElectronAppBasePath(), "public", indexHtml));
|
||||
win.webContents.on("before-input-event", (e, input) => {
|
||||
const waveEvent = adaptFromElectronKeyEvent(input);
|
||||
if (win.isFocused()) {
|
||||
@ -450,7 +470,7 @@ function mainResizeHandler(_: any, win: Electron.BrowserWindow) {
|
||||
});
|
||||
}
|
||||
|
||||
function calcBounds(clientData: ClientDataType) {
|
||||
function calcBounds(clientData: ClientDataType): Electron.Rectangle {
|
||||
const primaryDisplay = electron.screen.getPrimaryDisplay();
|
||||
const pdBounds = primaryDisplay.bounds;
|
||||
const size = { x: 100, y: 100, width: pdBounds.width - 200, height: pdBounds.height - 200 };
|
||||
@ -572,7 +592,7 @@ function readLastLinesOfFile(filePath: string, lineCount: number) {
|
||||
});
|
||||
}
|
||||
|
||||
function getContextMenu(): any {
|
||||
function getContextMenu(): electron.Menu {
|
||||
const menu = new electron.Menu();
|
||||
const menuItem = new electron.MenuItem({ label: "Testing", click: () => console.log("click testing!") });
|
||||
menu.append(menuItem);
|
||||
@ -585,7 +605,7 @@ function getFetchHeaders() {
|
||||
};
|
||||
}
|
||||
|
||||
async function getClientDataPoll(loopNum: number) {
|
||||
async function getClientDataPoll(loopNum: number): Promise<ClientDataType | null> {
|
||||
const lastTime = loopNum >= 6;
|
||||
const cdata = await getClientData(!lastTime, loopNum);
|
||||
if (lastTime || cdata != null) {
|
||||
@ -595,7 +615,7 @@ async function getClientDataPoll(loopNum: number) {
|
||||
return getClientDataPoll(loopNum + 1);
|
||||
}
|
||||
|
||||
async function getClientData(willRetry: boolean, retryNum: number) {
|
||||
async function getClientData(willRetry: boolean, retryNum: number): Promise<ClientDataType | null> {
|
||||
const url = new URL(getBaseHostPort() + "/api/get-client-data");
|
||||
const fetchHeaders = getFetchHeaders();
|
||||
return fetch(url, { headers: fetchHeaders })
|
||||
@ -634,13 +654,13 @@ function runWaveSrv() {
|
||||
pReject = argReject;
|
||||
});
|
||||
const envCopy = { ...process.env };
|
||||
envCopy[WaveAppPathVarName] = getAppBasePath();
|
||||
envCopy[WaveAppPathVarName] = getGoAppBasePath();
|
||||
if (isDev) {
|
||||
envCopy[WaveDevVarName] = "1";
|
||||
}
|
||||
const waveSrvCmd = getWaveSrvCmd();
|
||||
console.log("trying to run local server", waveSrvCmd);
|
||||
const proc = child_process.spawn("bash", ["-c", waveSrvCmd], {
|
||||
const proc = child_process.execFile("bash", ["-c", waveSrvCmd], {
|
||||
cwd: getWaveSrvCwd(),
|
||||
env: envCopy,
|
||||
});
|
||||
@ -787,8 +807,6 @@ function setAppUpdateStatus(status: string) {
|
||||
* @returns The interval at which the auto-updater checks for updates
|
||||
*/
|
||||
function initUpdater(): NodeJS.Timeout {
|
||||
const { autoUpdater } = electron;
|
||||
|
||||
if (isDev) {
|
||||
console.log("skipping auto-updater in dev mode");
|
||||
return null;
|
||||
@ -803,12 +821,6 @@ function initUpdater(): NodeJS.Timeout {
|
||||
serverType = "json";
|
||||
}
|
||||
|
||||
autoUpdater.setFeedURL({
|
||||
url: feedURL,
|
||||
headers: { "User-Agent": "Wave Auto-Update" },
|
||||
serverType,
|
||||
});
|
||||
|
||||
autoUpdater.removeAllListeners();
|
||||
|
||||
autoUpdater.on("error", (err) => {
|
||||
@ -828,10 +840,10 @@ function initUpdater(): NodeJS.Timeout {
|
||||
console.log("update-not-available");
|
||||
});
|
||||
|
||||
autoUpdater.on("update-downloaded", (event, releaseNotes, releaseName, releaseDate, updateURL) => {
|
||||
console.log("update-downloaded", [event, releaseNotes, releaseName, releaseDate, updateURL]);
|
||||
availableUpdateReleaseName = releaseName;
|
||||
availableUpdateReleaseNotes = releaseNotes;
|
||||
autoUpdater.on("update-downloaded", (event) => {
|
||||
console.log("update-downloaded", [event]);
|
||||
availableUpdateReleaseName = event.releaseName;
|
||||
availableUpdateReleaseNotes = event.releaseNotes as string | null;
|
||||
|
||||
// Display the update banner and create a system notification
|
||||
setAppUpdateStatus("ready");
|
||||
@ -847,7 +859,7 @@ function initUpdater(): NodeJS.Timeout {
|
||||
|
||||
// check for updates right away and keep checking later
|
||||
autoUpdater.checkForUpdates();
|
||||
return setInterval(autoUpdater.checkForUpdates, 600000); // 10 minutes in ms
|
||||
return setInterval(autoUpdater.checkForUpdates, 3600000); // 1 hour in ms
|
||||
}
|
||||
|
||||
/**
|
||||
@ -863,7 +875,7 @@ async function installAppUpdate() {
|
||||
};
|
||||
|
||||
await electron.dialog.showMessageBox(MainWindow, dialogOpts).then(({ response }) => {
|
||||
if (response === 0) electron.autoUpdater.quitAndInstall();
|
||||
if (response === 0) autoUpdater.quitAndInstall();
|
||||
});
|
||||
}
|
||||
|
||||
@ -896,19 +908,12 @@ function configureAutoUpdater(enabled: boolean) {
|
||||
}
|
||||
autoUpdateLock = true;
|
||||
|
||||
// only configure auto-updater on macOS
|
||||
if (unamePlatform == "darwin") {
|
||||
if (enabled && autoUpdateInterval == null) {
|
||||
try {
|
||||
console.log("configuring auto updater");
|
||||
autoUpdateInterval = initUpdater();
|
||||
} catch (e) {
|
||||
console.log("error configuring auto updater", e.toString());
|
||||
}
|
||||
} else if (autoUpdateInterval != null) {
|
||||
console.log("user has disabled auto-updates, stopping updater");
|
||||
clearInterval(autoUpdateInterval);
|
||||
autoUpdateInterval = null;
|
||||
if (enabled && autoUpdateInterval == null) {
|
||||
try {
|
||||
console.log("configuring auto updater");
|
||||
autoUpdateInterval = initUpdater();
|
||||
} catch (e) {
|
||||
console.log("error configuring auto updater", e.toString());
|
||||
}
|
||||
}
|
||||
autoUpdateLock = false;
|
||||
|
Loading…
Reference in New Issue
Block a user