diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c7ae56c --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,158 @@ +name: build + +on: push + +jobs: + build: + runs-on: ${{ fromJson('{"linux":"ubuntu-22.04","mac":"macos-13","win":"windows-2022"}')[matrix.os] }} + continue-on-error: false + + strategy: + fail-fast: false + matrix: + os: [linux, win, mac] + arch: [x64] + include: + - os: win + arch: ia32 + - os: mac + arch: arm64 + + steps: + # Check the secrets required for signing app on macOS. + - name: Check secrets + id: check-secrets + if: ${{ matrix.os == 'mac' && startsWith(github.ref, 'refs/tags/') }} + shell: bash + env: + BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} + P12_PASSWORD: ${{ secrets.P12_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + run: | + if [ -z $BUILD_CERTIFICATE_BASE64 ] || [ -z $P12_PASSWORD ] || [ -z $KEYCHAIN_PASSWORD ]; then + echo '::warning title=Must set BUILD_CERTIFICATE_BASE64/P12_PASSWORD/KEYCHAIN_PASSWORD secrets for signing app on macOS::Read more at https://docs.github.com/en/actions/deployment/deploying-xcode-applications/installing-an-apple-certificate-on-macos-runners-for-xcode-development' + exit 0 + fi + if [ -z $APPLE_TEAM_ID ]; then + echo '::warning title=Must set APPLE_TEAM_ID secret for signing app on macOS::Read more at https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/' + exit 0 + fi + if [ -z $APPLE_ID ] || [ -z $APPLE_PASSWORD ]; then + echo '::warning title=Must set APPLE_ID/APPLE_PASSWORD secrets for notarizing app on macOS::Read more at https://github.com/lando/notarize-action' + exit 0 + fi + echo '::set-output name=mac-sign::true' + + - name: Checkout + uses: actions/checkout@v4 + + - name: Use Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Set package version + shell: bash + run: | + # Set the version field in package.json to git tag name. + npm config set git-tag-version=false + npm version $(git describe --tags) || true + # Use production icons. + npm pkg set 'build.icons.mac'='assets/build/icon.icns' + npm pkg set 'build.icons.win'='assets/build/icon.ico' + + - name: Build + env: + # Without this the CI job will encounter GitHub API rate limit due to + # the fetch-yode module pulling yode versions. + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: bash + run: | + set -e + npm install + npm publish --dry-run + + - name: Create Distribution + if: ${{ !steps.check-secrets.outputs.mac-sign }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: npm run dist -- --arch ${{ matrix.arch }} + + - name: Install the Apple Certificate + if: steps.check-secrets.outputs.mac-sign + env: + # https://docs.github.com/en/actions/deployment/deploying-xcode-applications/installing-an-apple-certificate-on-macos-runners-for-xcode-development + BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} + P12_PASSWORD: ${{ secrets.P12_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + # Create variables. + CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + + # Import certificate from secrets. + echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH + + # Create temporary keychain. + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + + # Import certificate to keychain. + security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + + - name: Create App Bundle + if: steps.check-secrets.outputs.mac-sign + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + run: npm run build -- --arch ${{ matrix.arch }} --identity "$APPLE_TEAM_ID" + + - name: Notarize Build + if: steps.check-secrets.outputs.mac-sign + uses: lando/notarize-action@v2 + with: + product-path: out/Boilerplate.app + appstore-connect-username: ${{ secrets.APPLE_ID }} + appstore-connect-password: ${{ secrets.APPLE_PASSWORD }} + appstore-connect-team-id: ${{ secrets.APPLE_TEAM_ID }} + + - name: Create Distribution (macOS notarized build) + if: steps.check-secrets.outputs.mac-sign + run: | + VERSION=`node -e "process.stdout.write(require('./package.json').version)"` + xcrun stapler staple out/Boilerplate.app + ditto -c -k out boilerplate-v$VERSION-darwin-${{ matrix.arch }}.zip + + - name: Upload Binary Files + uses: actions/upload-artifact@v4 + with: + name: dist-${{ matrix.os }}-${{ matrix.arch }} + path: '*.zip' + retention-days: 1 + + release: + if: startsWith(github.ref, 'refs/tags/') + needs: [build] + runs-on: ubuntu-latest + permissions: + # Needed by action-gh-release. + contents: write + + steps: + - name: Download Files + uses: actions/download-artifact@v4 + with: + merge-multiple: true + + - name: Release + uses: softprops/action-gh-release@v2 + with: + draft: true + name: Boilerplate ${{ github.ref_name }} + generate_release_notes: true + files: '*.zip' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6eab8aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# TypeScript compiled files +/dist/ +/types/**/*.d.ts + +# Everything else should keep same with .npmignore +*.swp +*.zip + +yarn.lock +package-lock.json +npm-debug.log +yarn-error.log +/node_modules/ +/out/ diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..7245402 --- /dev/null +++ b/.npmignore @@ -0,0 +1,15 @@ +# Unused source files +/src/ +/types/ +.github +tsconfig.json + +# Everything else should keep same with .gitignore +*.swp +*.zip + +yarn.lock +package-lock.json +npm-debug.log +/node_modules/ +/out/ diff --git a/READMD.md b/READMD.md new file mode 100644 index 0000000..835998d --- /dev/null +++ b/READMD.md @@ -0,0 +1,29 @@ +# boilerplate-nodejs + +A Node.js template of the [Yue library](https://github.com/yue/yue) for building +cross-platform desktop apps with system webview and native GUI widgets. + +## Features + +* Build desktop apps with Node.js, system webview and native UI. +* Generate single-file executables for Linux/Windows. +* Generate signed app bundle for macOS. +* Add Node.js bindings to web pages. +* Custom protocols in webview. + +## How to use + +1. Clone the project. +2. Put your web app in `app/`. +3. `npm install` to install dependencies. +4. `npm start` to start the app. +5. `npm run dist` to create distribution. + +## Docs + +* [Sign mac app](https://github.com/yue/boilerplate-js/blob/main/docs/sign-mac-app.md) +* [Yue documents (external link)](http://libyue.com/docs/latest/js/) + +## License + +Public domain. diff --git a/assets/build/entitlements.plist b/assets/build/entitlements.plist new file mode 100644 index 0000000..9a279dc --- /dev/null +++ b/assets/build/entitlements.plist @@ -0,0 +1,12 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-library-validation + + + diff --git a/assets/build/icon-dev.icns b/assets/build/icon-dev.icns new file mode 100644 index 0000000..a4e155f Binary files /dev/null and b/assets/build/icon-dev.icns differ diff --git a/assets/build/icon-dev.ico b/assets/build/icon-dev.ico new file mode 100644 index 0000000..a6ddb0c Binary files /dev/null and b/assets/build/icon-dev.ico differ diff --git a/assets/build/icon.icns b/assets/build/icon.icns new file mode 100644 index 0000000..f2ef541 Binary files /dev/null and b/assets/build/icon.icns differ diff --git a/assets/build/icon.ico b/assets/build/icon.ico new file mode 100644 index 0000000..814c554 Binary files /dev/null and b/assets/build/icon.ico differ diff --git a/docs/sign-mac-app.md b/docs/sign-mac-app.md new file mode 100644 index 0000000..4bd9e95 --- /dev/null +++ b/docs/sign-mac-app.md @@ -0,0 +1,17 @@ +# Sign mac app + +This template has a GitHub workflow that signs and the notorizes the app bundle, +and you need to set following secrects in repo/orgnaization to make it work. + +## `BUILD_CERTIFICATE_BASE64`/`P12_PASSWORD`/`KEYCHAIN_PASSWORD` + +https://docs.github.com/en/actions/deployment/deploying-xcode-applications/installing-an-apple-certificate-on-macos-runners-for-xcode-development + +## `APPLE_TEAM_ID` + +https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/ + +## `APPLE_ID`/`APPLE_PASSWORD` + +https://github.com/lando/notarize-action +https://support.apple.com/en-us/102654 diff --git a/package.json b/package.json new file mode 100644 index 0000000..e4885c8 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "boilerplate", + "private": true, + "version": "0.0.1-dev", + "main": "dist/gui-main.js", + "build": { + "appId": "org.yue.boilerplate", + "productName": "Boilerplate", + "copyright": "Copyright © 2024 Yue. All rights reserved.", + "unpackDir": "assets/icons", + "ignore": [ + "assets/build" + ], + "entitlements": "assets/build/entitlements.plist", + "icons": { + "mac": "assets/build/icon-dev.icns", + "win": "assets/build/icon-dev.ico" + } + }, + "scripts": { + "prepack": "tsc", + "start": "yode node_modules/ts-node/dist/bin.js src/gui-main.ts", + "build": "yackage build out", + "dist": "yackage dist out" + }, + "engines": { + "node": ">=20.0.0" + }, + "dependencies": { + "fetch-yode": "1.x", + "gui": "0.15.1" + }, + "devDependencies": { + "@types/node": "20.x", + "ts-node": "10.9.2", + "typescript": "5.4.3", + "yackage": "0.9.x" + } +} diff --git a/src/gui-main.ts b/src/gui-main.ts new file mode 100644 index 0000000..5efc919 --- /dev/null +++ b/src/gui-main.ts @@ -0,0 +1,23 @@ +#!/usr/bin/env node + +import gui from 'gui'; + +// Check if it is Yode. +if (!process.versions.yode) + throw new Error('Can only run under Yode runtime.'); + +// Show GUI when ready. +if (process.platform == 'darwin') { + gui.lifetime.onReady = guiMain; +} else { + guiMain(); +} + +// Create main window. +let mainWindow: gui.Window; +function guiMain() { + mainWindow = gui.Window.create({}); + mainWindow.setContentSize({width: 400, height: 400}); + mainWindow.center(); + mainWindow.activate(); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..3744e78 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationDir": "types", + "baseUrl": "src", + "outDir": "dist", + "allowJs": true, + "module": "commonjs", + "esModuleInterop": true, + "target": "es2022", + "lib": ["es2022", "dom"] + }, + "include": ["src/**/*"] +}