Merge branch 'main' into franknoirot/fix-electron-playwright

This commit is contained in:
Frank Noirot
2024-09-03 16:38:00 -04:00
committed by GitHub
21 changed files with 945 additions and 372 deletions

View File

@ -1,4 +1,4 @@
name: build-test-publish-apps name: build-publish-apps
on: on:
pull_request: pull_request:
@ -21,7 +21,7 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
prepare-json-files: prepare-files:
runs-on: ubuntu-22.04 # seperate job on Ubuntu for easy string manipulations (compared to Windows) runs-on: ubuntu-22.04 # seperate job on Ubuntu for easy string manipulations (compared to Windows)
outputs: outputs:
version: ${{ steps.export_version.outputs.version }} version: ${{ steps.export_version.outputs.version }}
@ -33,6 +33,19 @@ jobs:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'yarn' cache: 'yarn'
- run: yarn install
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
# TODO: see if we can fetch from main instead if no diff at src/wasm-lib
- name: Run build:wasm
run: "yarn build:wasm${{ env.BUILD_RELEASE == 'true' && '-dev' || ''}}"
- name: Set nightly version - name: Set nightly version
if: github.event_name == 'schedule' if: github.event_name == 'schedule'
run: | run: |
@ -42,36 +55,50 @@ jobs:
# TODO: see if we ned to add updater test URL here https://dl.zoo.dev/releases/modeling-app/updater-test/last_update.json # TODO: see if we ned to add updater test URL here https://dl.zoo.dev/releases/modeling-app/updater-test/last_update.json
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
if: ${{ github.event_name == 'schedule' || env.CUT_RELEASE_PR == 'true' }}
with: with:
name: prepared-files
path: | path: |
package.json package.json
src/wasm-lib/pkg/wasm_lib*
- id: export_version - id: export_version
run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT" run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"
build-test-app-macos: build-apps:
needs: [prepare-json-files] needs: [prepare-files]
runs-on: macos-14 strategy:
fail-fast: false
matrix:
os: [macos-14, windows-2022, ubuntu-22.04]
runs-on: ${{ matrix.os }}
env: env:
APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
CSC_FOR_PULL_REQUEST: true
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
WINDOWS_CERTIFICATE_THUMBPRINT: F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
if: github.event_name == 'schedule' name: prepared-files
- name: Copy updated .json files - name: Copy prepared files
if: github.event_name == 'schedule'
run: | run: |
ls -l artifact ls -R prepared-files
cp artifact/package.json package.json cp prepared-files/package.json package.json
cp prepared-files/src/wasm-lib/pkg/wasm_lib_bg.wasm public
mkdir src/wasm-lib/pkg
cp prepared-files/src/wasm-lib/pkg/wasm_lib* src/wasm-lib/pkg
- name: Sync node version and setup cache - name: Sync node version and setup cache
uses: actions/setup-node@v4 uses: actions/setup-node@v4
@ -81,79 +108,10 @@ jobs:
- run: yarn install - run: yarn install
- name: Setup Rust - run: yarn tronb:vite
uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: Run build:wasm
run: "yarn build:wasm${{ env.BUILD_RELEASE == 'true' && '-dev' || ''}}"
# TODO: sign the app (and updater bundle potentially)
- name: Add signing certificate
if: ${{ env.BUILD_RELEASE == 'true' }}
run: chmod +x add-osx-cert.sh && ./add-osx-cert.sh
- name: Build the app for arm64
run: "yarn electron-forge make"
- name: Build the app for x64
run: "yarn electron-forge make --arch x64"
- name: List artifacts
run: "ls -R out/make"
# TODO: add the 'Build for Mac TestFlight (nightly)' stage back
- uses: actions/upload-artifact@v3
with:
path: "out/make/*/*/*/*"
build-test-app-windows:
needs: [prepare-json-files]
runs-on: windows-2022
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v3
- name: Copy updated .json files
if: github.event_name == 'schedule'
run: |
ls -l artifact
cp artifact/package.json package.json
- name: Sync node version and setup cache
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn' # Set this to npm, yarn or pnpm.
- run: yarn install
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: Run build:wasm manually
shell: bash
env:
MODE: ${{ env.BUILD_RELEASE == 'true' && '--release' || '--debug' }}
run: |
mkdir src/wasm-lib/pkg; cd src/wasm-lib
echo "building with ${{ env.MODE }}"
npx wasm-pack build --target web --out-dir pkg ${{ env.MODE }}
cd ../../
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
- name: Prepare certificate and variables (Windows only) - name: Prepare certificate and variables (Windows only)
if: ${{ env.BUILD_RELEASE == 'true' }} if: ${{ env.BUILD_RELEASE == 'true' && matrix.os == 'windows-2022' }}
run: | run: |
echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12 echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
cat /d/Certificate_pkcs12.p12 cat /d/Certificate_pkcs12.p12
@ -168,7 +126,7 @@ jobs:
shell: bash shell: bash
- name: Setup certicate with SSM KSP (Windows only) - name: Setup certicate with SSM KSP (Windows only)
if: ${{ env.BUILD_RELEASE == 'true' }} if: ${{ env.BUILD_RELEASE == 'true' && matrix.os == 'windows-2022' }}
run: | run: |
curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi
msiexec /i smtools-windows-x64.msi /quiet /qn msiexec /i smtools-windows-x64.msi /quiet /qn
@ -178,83 +136,47 @@ jobs:
smksp_cert_sync.exe smksp_cert_sync.exe
shell: cmd shell: cmd
- name: Build the app for x64 - name: Build the app
run: "yarn electron-forge make --arch x64" run: yarn electron-builder --config ${{ env.BUILD_RELEASE && '--publish always' || '' }}
- name: Build the app for arm64 - name: List artifacts in out/
run: "yarn electron-forge make --arch arm64" run: ls -R out
- name: List artifacts - name: Prepare the tauri update bundles (macOS)
run: "ls -R out/make" if: ${{ env.BUILD_RELEASE && matrix.os == 'macos-14' }}
- name: Sign using Signtool
if: ${{ env.BUILD_RELEASE == 'true' }}
env:
THUMBPRINT: "F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D"
X64_FILE: "D:\\a\\modeling-app\\modeling-app\\out\\make\\squirrel.windows\\x64\\Zoo Modeling App-*Setup.exe"
ARM64_FILE: "D:\\a\\modeling-app\\modeling-app\\out\\make\\squirrel.windows\\arm64\\Zoo Modeling App-*Setup.exe"
run: | run: |
signtool.exe sign /sha1 ${{ env.THUMBPRINT }} /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 "${{ env.X64_FILE }}" for ARCH in arm64 x64; do
signtool.exe verify /v /pa "${{ env.X64_FILE }}" TAURI_DIR=out/tauri/$VERSION/macos
signtool.exe sign /sha1 ${{ env.THUMBPRINT }} /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 "${{ env.ARM64_FILE }}" TEMP_DIR=temp/$ARCH
signtool.exe verify /v /pa "${{ env.ARM64_FILE }}" mkdir -p $TAURI_DIR
mkdir -p $TEMP_DIR
unzip out/*-$ARCH-mac.zip -d $TEMP_DIR
tar -czvf "$TAURI_DIR/Zoo Modeling App-$ARCH.app.tar.gz" -C $TEMP_DIR "Zoo Modeling App.app"
yarn tauri signer sign "$TAURI_DIR/Zoo Modeling App-$ARCH.app.tar.gz"
done
ls -R out
- name: Prepare the tauri update bundles (Windows)
if: ${{ env.BUILD_RELEASE && matrix.os == 'windows-2022' }}
run: |
$env:TAURI_DIR="out/tauri/${env:VERSION}/nsis"
mkdir -p ${env:TAURI_DIR}
$env:OUT_FILE="${env:TAURI_DIR}/Zoo Modeling App_${env:VERSION_NO_V}_x64-setup.nsis.zip"
7z a -mm=Copy "${env:OUT_FILE}" ./out/*-x64-win.exe
yarn tauri signer sign "${env:OUT_FILE}"
ls -R out
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:
path: "out/make/*/*/*" name: out-${{ matrix.os }}
path: |
# TODO: Run e2e tests out/Zoo*.*
out/latest*.yml
out/tauri
build-test-app-ubuntu:
needs: [prepare-json-files]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v3
if: github.event_name == 'schedule'
- name: Copy updated .json files
if: github.event_name == 'schedule'
run: |
ls -l artifact
cp artifact/package.json package.json
- name: Sync node version and setup cache
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn' # Set this to npm, yarn or pnpm.
- run: yarn install
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: Run build:wasm
run: "yarn build:wasm${{ env.BUILD_RELEASE == 'true' && '-dev' || ''}}"
- name: Build the app for arm64
run: "yarn electron-forge make --arch arm64"
- name: Build the app for x64
run: "yarn electron-forge make --arch x64"
- name: List artifacts
run: "ls -R out/make"
# TODO: add the 'Build for Mac TestFlight (nightly)' stage back # TODO: add the 'Build for Mac TestFlight (nightly)' stage back
# TODO: sign the app (and updater bundle potentially) # TODO: add the updater tests back
- uses: actions/upload-artifact@v3
with:
path: "out/make/*/*/*"
publish-apps-release: publish-apps-release:
@ -262,87 +184,107 @@ jobs:
permissions: permissions:
contents: write contents: write
if: ${{ github.event_name == 'release' || github.event_name == 'schedule' }} if: ${{ github.event_name == 'release' || github.event_name == 'schedule' }}
needs: [prepare-json-files, build-test-app-macos, build-test-app-windows, build-test-app-ubuntu] needs: [prepare-files, build-apps]
env: env:
VERSION_NO_V: ${{ needs.prepare-json-files.outputs.version }} VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
VERSION: ${{ github.event_name == 'release' && format('v{0}', needs.prepare-json-files.outputs.version) || needs.prepare-json-files.outputs.version }} VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }} PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }}
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Nightly build, commit {0}', github.sha) }} NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
BUCKET_DIR: ${{ github.event_name == 'release' && 'dl.kittycad.io/releases/modeling-app' || 'dl.kittycad.io/releases/modeling-app/nightly' }} BUCKET_DIR: ${{ github.event_name == 'schedule' && 'dl.kittycad.io/releases/modeling-app/nightly' || 'dl.kittycad.io/releases/modeling-app' }}
WEBSITE_DIR: ${{ github.event_name == 'release' && 'dl.zoo.dev/releases/modeling-app' || 'dl.zoo.dev/releases/modeling-app/nightly' }} WEBSITE_DIR: ${{ github.event_name == 'schedule' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }}
BUCKET_DIR_TAURI: 'dl.kittycad.io/releases/modeling-app/tauri-compat'
WEBSITE_DIR_TAURI: 'dl.zoo.dev/releases/modeling-app/tauri-compat'
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }} URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
steps: steps:
- uses: actions/download-artifact@v3 - uses: actions/checkout@v4
- name: Generate the update static endpoint - uses: actions/download-artifact@v3
with:
name: out-windows-2022
path: out
- uses: actions/download-artifact@v3
with:
name: out-macos-14
path: out
- uses: actions/download-artifact@v3
with:
name: out-ubuntu-22.04
path: out
- name: Generate the download static endpoint
run: | run: |
ls -l artifact/*/*oo* RELEASE_DIR=https://${WEBSITE_DIR}
DARWIN_SIG=`cat artifact/macos/*.app.tar.gz.sig`
WINDOWS_X86_64_SIG=`cat artifact/msi/*x64*.msi.zip.sig`
WINDOWS_AARCH64_SIG=`cat artifact/msi/*arm64*.msi.zip.sig`
RELEASE_DIR=https://${WEBSITE_DIR}/${VERSION}
jq --null-input \ jq --null-input \
--arg version "${VERSION}" \ --arg version "${VERSION}" \
--arg pub_date "${PUB_DATE}" \ --arg pub_date "${PUB_DATE}" \
--arg notes "${NOTES}" \ --arg notes "${NOTES}" \
--arg darwin_sig "$DARWIN_SIG" \ --arg mac_arm64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-arm64-mac.dmg" \
--arg darwin_url "$RELEASE_DIR/macos/${{ env.URL_CODED_NAME }}.app.tar.gz" \ --arg mac_x64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-x64-mac.dmg" \
--arg windows_x86_64_sig "$WINDOWS_X86_64_SIG" \ --arg windows_arm64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-arm64-win.msi" \
--arg windows_x86_64_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64_en-US.msi.zip" \ --arg windows_x64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-x64-win.msi" \
--arg windows_aarch64_sig "$WINDOWS_AARCH64_SIG" \ '{
--arg windows_aarch64_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_arm64_en-US.msi.zip" \ "version": $version,
"pub_date": $pub_date,
"notes": $notes,
"platforms": {
"dmg-arm64": {
"url": $mac_arm64_url
},
"dmg-x64": {
"url": $mac_x64_url
},
"msi-arm64": {
"url": $windows_arm64_url
},
"msi-x64": {
"url": $windows_x64_url
}
}
}' > last_download.json
cat last_download.json
- name: Generate the update static endpoint for tauri
run: |
TAURI_DIR=out/tauri/$VERSION
MAC_ARM64_SIG=`cat $TAURI_DIR/macos/*-arm64.app.tar.gz.sig`
MAC_X64_SIG=`cat $TAURI_DIR/macos/*-x64.app.tar.gz.sig`
WINDOWS_SIG=`cat $TAURI_DIR/nsis/*.nsis.zip.sig`
RELEASE_DIR=https://${WEBSITE_DIR_TAURI}/${VERSION}
jq --null-input \
--arg version "${VERSION}" \
--arg pub_date "${PUB_DATE}" \
--arg notes "${NOTES}" \
--arg mac_arm64_sig "$MAC_ARM64_SIG" \
--arg mac_arm64_url "$RELEASE_DIR/macos/${{ env.URL_CODED_NAME }}-arm64.app.tar.gz" \
--arg mac_x64_sig "$MAC_X64_SIG" \
--arg mac_x64_url "$RELEASE_DIR/macos/${{ env.URL_CODED_NAME }}-x64.app.tar.gz" \
--arg windows_sig "$WINDOWS_SIG" \
--arg windows_url "$RELEASE_DIR/nsis/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64-setup.nsis.zip" \
'{ '{
"version": $version, "version": $version,
"pub_date": $pub_date, "pub_date": $pub_date,
"notes": $notes, "notes": $notes,
"platforms": { "platforms": {
"darwin-x86_64": { "darwin-x86_64": {
"signature": $darwin_sig, "signature": $mac_x64_sig,
"url": $darwin_url "url": $mac_x64_url
}, },
"darwin-aarch64": { "darwin-aarch64": {
"signature": $darwin_sig, "signature": $mac_arm64_sig,
"url": $darwin_url "url": $mac_arm64_url
}, },
"windows-x86_64": { "windows-x86_64": {
"signature": $windows_x86_64_sig, "signature": $windows_sig,
"url": $windows_x86_64_url "url": $windows_url
},
"windows-aarch64": {
"signature": $windows_aarch64_sig,
"url": $windows_aarch64_url
} }
} }
}' > last_update.json }' > last_update.json
cat last_update.json cat last_update.json
- name: Generate the download static endpoint - name: List artifacts
run: | run: "ls -R out"
RELEASE_DIR=https://${WEBSITE_DIR}/${VERSION}
jq --null-input \
--arg version "${VERSION}" \
--arg pub_date "${PUB_DATE}" \
--arg notes "${NOTES}" \
--arg darwin_url "$RELEASE_DIR/dmg/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_universal.dmg" \
--arg windows_x86_64_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64_en-US.msi" \
--arg windows_aarch64_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_arm64_en-US.msi" \
'{
"version": $version,
"pub_date": $pub_date,
"notes": $notes,
"platforms": {
"dmg-universal": {
"url": $darwin_url
},
"msi-x86_64": {
"url": $windows_x86_64_url
},
"msi-aarch64": {
"url": $windows_aarch64_url
}
}
}' > last_download.json
cat last_download.json
- name: Authenticate to Google Cloud - name: Authenticate to Google Cloud
uses: 'google-github-actions/auth@v2.1.5' uses: 'google-github-actions/auth@v2.1.5'
@ -352,20 +294,22 @@ jobs:
- name: Set up Google Cloud SDK - name: Set up Google Cloud SDK
uses: google-github-actions/setup-gcloud@v2.1.0 uses: google-github-actions/setup-gcloud@v2.1.0
with: with:
project_id: kittycadapi project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }}
- name: Upload release files to public bucket - name: Upload release files to public bucket
uses: google-github-actions/upload-cloud-storage@v2.1.3 uses: google-github-actions/upload-cloud-storage@v2.1.3
with: with:
path: artifact path: out
glob: '*/Zoo*' glob: 'Zoo*'
parent: false parent: false
destination: ${{ env.BUCKET_DIR }}/${{ env.VERSION }} destination: ${{ env.BUCKET_DIR }}
- name: Upload update endpoint to public bucket - name: Upload update endpoint to public bucket
uses: google-github-actions/upload-cloud-storage@v2.1.3 uses: google-github-actions/upload-cloud-storage@v2.1.3
with: with:
path: last_update.json path: out
glob: 'latest*'
parent: false
destination: ${{ env.BUCKET_DIR }} destination: ${{ env.BUCKET_DIR }}
- name: Upload download endpoint to public bucket - name: Upload download endpoint to public bucket
@ -374,11 +318,27 @@ jobs:
path: last_download.json path: last_download.json
destination: ${{ env.BUCKET_DIR }} destination: ${{ env.BUCKET_DIR }}
- name: Upload release files to public bucket for tauri
uses: google-github-actions/upload-cloud-storage@v2.1.1
with:
path: "out/tauri/${{ env.VERSION }}"
glob: '*/Zoo*'
parent: false
destination: ${{ env.BUCKET_DIR_TAURI }}/${{ env.VERSION }}
- name: Upload update endpoint to public bucket for tauri
uses: google-github-actions/upload-cloud-storage@v2.1.1
with:
path: last_update.json
destination: ${{ env.BUCKET_DIR }}
- name: Upload release files to Github - name: Upload release files to Github
if: ${{ github.event_name == 'release' }} if: ${{ github.event_name == 'release' }}
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
files: 'artifact/*/Zoo*' files: 'out/Zoo*'
# TODO: Add GitHub publisher
announce_release: announce_release:
needs: [publish-apps-release] needs: [publish-apps-release]

View File

@ -1,24 +0,0 @@
#!/usr/bin/env sh
# From https://dev.to/rwwagner90/signing-electron-apps-with-github-actions-4cof
KEY_CHAIN=build.keychain
CERTIFICATE_P12=certificate.p12
# Recreate the certificate from the secure environment variable
echo $APPLE_CERTIFICATE | base64 --decode > $CERTIFICATE_P12
#create a keychain
security create-keychain -p actions $KEY_CHAIN
# Make the keychain the default so identities are found
security default-keychain -s $KEY_CHAIN
# Unlock the keychain
security unlock-keychain -p actions $KEY_CHAIN
security import $CERTIFICATE_P12 -k $KEY_CHAIN -P $APPLE_CERTIFICATE_PASSWORD -T /usr/bin/codesign;
security set-key-partition-list -S apple-tool:,apple: -s -k actions $KEY_CHAIN
# remove certs
rm -fr *.p12

View File

@ -96,33 +96,49 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
} }
// deselect line tool // deselect line tool
await page.getByTestId('line').click() const btnLine = page.getByTestId('line')
const btnLineAriaPressed = await btnLine.getAttribute('aria-pressed')
if (btnLineAriaPressed === 'true') {
await btnLine.click()
}
await page.waitForTimeout(100)
const line1 = await u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`, 0) const line1 = await u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`, 0)
if (openPanes.includes('code')) { if (openPanes.includes('code')) {
await expect await expect
.poll(async () => u.getGreatestPixDiff(line1, TEST_COLORS.WHITE)) .poll(async () => u.getGreatestPixDiff(line1, TEST_COLORS.WHITE))
.toBeLessThan(3) .toBeLessThan(3)
await page.waitForTimeout(100)
await expect await expect
.poll(() => u.getGreatestPixDiff(line1, [249, 249, 249])) .poll(async () => u.getGreatestPixDiff(line1, [249, 249, 249]))
.toBeLessThan(3) .toBeLessThan(3)
await page.waitForTimeout(100)
} }
// click between first two clicks to get center of the line // click between first two clicks to get center of the line
await page.mouse.click(startXPx + PUR * 15, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 15, 500 - PUR * 10)
await page.waitForTimeout(100) await page.waitForTimeout(100)
if (openPanes.includes('code')) { if (openPanes.includes('code')) {
expect(await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)).toBeLessThan(3) await expect(
await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)
).toBeLessThan(3)
await expect(await u.getGreatestPixDiff(line1, [0, 0, 255])).toBeLessThan(3) await expect(await u.getGreatestPixDiff(line1, [0, 0, 255])).toBeLessThan(3)
} }
// hold down shift // hold down shift
await page.keyboard.down('Shift') await page.keyboard.down('Shift')
await page.waitForTimeout(100)
// click between the latest two clicks to get center of the line // click between the latest two clicks to get center of the line
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 20) await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 20)
await page.waitForTimeout(100)
// selected two lines therefore there should be two cursors // selected two lines therefore there should be two cursors
if (openPanes.includes('code')) { if (openPanes.includes('code')) {
await expect(page.locator('.cm-cursor')).toHaveCount(2) await expect(page.locator('.cm-cursor')).toHaveCount(2)
await page.waitForTimeout(100)
} }
await page.getByRole('button', { name: 'Length: open menu' }).click() await page.getByRole('button', { name: 'Length: open menu' }).click()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 33 KiB

65
electron-builder.yml Normal file
View File

@ -0,0 +1,65 @@
appId: dev.zoo.modeling-app
directories:
output: out
buildResources: assets
files:
- .vite/**
mac:
category: public.app-category.developer-tools
artifactName: "${productName}-${version}-${arch}-${os}.${ext}"
target:
- target: dmg
arch:
- x64
- arm64
- target: zip
arch:
- x64
- arm64
notarize:
teamId: 92H8YB3B95
win:
artifactName: "${productName}-${version}-${arch}-${os}.${ext}"
target:
- target: nsis
arch:
- x64
- arm64
- target: msi
arch:
- x64
- arm64
signingHashAlgorithms:
- sha256
sign: "./sign-win.js"
publisherName: "KittyCAD Inc" # needs to be exactly like on Digicert
icon: "assets/icon.ico"
msi:
oneClick: false
perMachine: true
nsis:
oneClick: false
perMachine: true
allowElevation: true
license: "LICENSE"
installerIcon: "assets/icon.ico"
include: "./installer.nsh"
linux:
artifactName: "${productName}-${version}-${arch}-${os}.${ext}"
target:
- target: appImage
arch:
- x64
- arm64
publish:
- provider: generic
url: https://dl.zoo.dev/releases/modeling-app/test/electron-builder
channel: latest

8
installer.nsh Normal file
View File

@ -0,0 +1,8 @@
!macro preInit
SetRegView 64
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App"
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App"
SetRegView 32
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App"
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App"
!macroend

View File

@ -39,6 +39,7 @@
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"decamelize": "^6.0.0", "decamelize": "^6.0.0",
"electron-squirrel-startup": "^1.0.1", "electron-squirrel-startup": "^1.0.1",
"electron-updater": "^6.2.1",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"html2canvas-pro": "^1.5.8", "html2canvas-pro": "^1.5.8",
"isomorphic-fetch": "^3.0.0", "isomorphic-fetch": "^3.0.0",
@ -97,7 +98,9 @@
"tron:package": "electron-forge package", "tron:package": "electron-forge package",
"tron:make": "electron-forge make", "tron:make": "electron-forge make",
"tron:publish": "electron-forge publish", "tron:publish": "electron-forge publish",
"tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep=@electron" "tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep=@electron",
"tronb:vite": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts",
"tronb:package": "electron-builder --config electron-builder.yml"
}, },
"prettier": { "prettier": {
"trailingComma": "es5", "trailingComma": "es5",
@ -134,6 +137,7 @@
"@iarna/toml": "^2.2.5", "@iarna/toml": "^2.2.5",
"@lezer/generator": "^1.7.1", "@lezer/generator": "^1.7.1",
"@playwright/test": "^1.46.1", "@playwright/test": "^1.46.1",
"@tauri-apps/cli": "^2.0.0-rc.9",
"@testing-library/jest-dom": "^5.14.1", "@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^15.0.2", "@testing-library/react": "^15.0.2",
"@types/d3-force": "^3.0.10", "@types/d3-force": "^3.0.10",
@ -150,7 +154,6 @@
"@types/three": "^0.163.0", "@types/three": "^0.163.0",
"@types/ua-parser-js": "^0.7.39", "@types/ua-parser-js": "^0.7.39",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@types/wait-on": "^5.3.4",
"@types/wicg-file-system-access": "^2023.10.5", "@types/wicg-file-system-access": "^2023.10.5",
"@types/ws": "^8.5.10", "@types/ws": "^8.5.10",
"@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/eslint-plugin": "^5.0.0",
@ -161,6 +164,8 @@
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"d3-force": "^3.0.0", "d3-force": "^3.0.0",
"electron": "^32.0.1", "electron": "^32.0.1",
"electron-builder": "^24.13.3",
"electron-notarize": "^1.2.2",
"eslint": "^8.0.1", "eslint": "^8.0.1",
"eslint-config-react-app": "^7.0.1", "eslint-config-react-app": "^7.0.1",
"eslint-plugin-css-modules": "^2.12.0", "eslint-plugin-css-modules": "^2.12.0",
@ -185,7 +190,6 @@
"vite-tsconfig-paths": "^4.3.2", "vite-tsconfig-paths": "^4.3.2",
"vitest": "^1.6.0", "vitest": "^1.6.0",
"vitest-webgl-canvas-mock": "^1.1.0", "vitest-webgl-canvas-mock": "^1.1.0",
"wait-on": "^7.2.0",
"wasm-pack": "^0.13.0", "wasm-pack": "^0.13.0",
"ws": "^8.17.0", "ws": "^8.17.0",
"yarn": "^1.22.22" "yarn": "^1.22.22"

38
sign-win.js Normal file
View File

@ -0,0 +1,38 @@
// From https://github.com/OpenBuilds/OpenBuilds-CONTROL/blob/4800540ffaa517925fc2cff26670809efa341ffe/signWin.js
const { execSync } = require('node:child_process')
exports.default = async (configuration) => {
if (!process.env.SM_API_KEY) {
console.error(
'Signing using signWin.js script: failed: SM_API_KEY ENV VAR NOT FOUND'
)
return
}
if (!process.env.WINDOWS_CERTIFICATE_THUMBPRINT) {
console.error(
'Signing using signWin.js script: failed: FINGERPRINT ENV VAR NOT FOUND'
)
return
}
if (!configuration.path) {
throw new Error(
`Signing using signWin.js script: failed: TARGET PATH NOT FOUND`
)
}
try {
execSync(
`smctl sign --fingerprint="${
process.env.WINDOWS_CERTIFICATE_THUMBPRINT
}" --input "${String(configuration.path)}"`,
{
stdio: 'inherit',
}
)
console.log('Signing using signWin.js script: successful')
} catch (error) {
console.error('Signing using signWin.js script: failed:', error)
}
}

View File

@ -8,8 +8,10 @@ import { Issuer } from 'openid-client'
import { Bonjour, Service } from 'bonjour-service' import { Bonjour, Service } from 'bonjour-service'
// @ts-ignore: TS1343 // @ts-ignore: TS1343
import * as kittycad from '@kittycad/lib/import' import * as kittycad from '@kittycad/lib/import'
import electronUpdater, { type AppUpdater } from 'electron-updater'
import minimist from 'minimist' import minimist from 'minimist'
import getCurrentProjectFile from 'lib/getCurrentProjectFile' import getCurrentProjectFile from 'lib/getCurrentProjectFile'
import os from 'node:os'
let mainWindow: BrowserWindow | null = null let mainWindow: BrowserWindow | null = null
@ -22,8 +24,20 @@ if (!process.env.NODE_ENV)
console.warn( console.warn(
'*FOX SCREAM* process.env.NODE_ENV is not explicitly set!, defaulting to production' '*FOX SCREAM* process.env.NODE_ENV is not explicitly set!, defaulting to production'
) )
// Default prod values
// dotenv override when present
dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] }) dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] })
console.log(process.env)
process.env.VITE_KC_API_WS_MODELING_URL ??=
'wss://api.zoo.dev/ws/modeling/commands'
process.env.VITE_KC_API_BASE_URL ??= 'https://api.zoo.dev'
process.env.VITE_KC_SITE_BASE_URL ??= 'https://zoo.dev'
process.env.VITE_KC_SKIP_AUTH ??= 'false'
process.env.VITE_KC_CONNECTION_TIMEOUT_MS ??= '15000'
// Handle creating/removing shortcuts on Windows when installing/uninstalling. // Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) { if (require('electron-squirrel-startup')) {
app.quit() app.quit()
@ -59,7 +73,7 @@ const createWindow = (): BrowserWindow => {
preload: path.join(__dirname, './preload.js'), preload: path.join(__dirname, './preload.js'),
}, },
icon: path.resolve(process.cwd(), 'assets', 'icon.png'), icon: path.resolve(process.cwd(), 'assets', 'icon.png'),
frame: false, frame: os.platform() !== 'darwin',
titleBarStyle: 'hiddenInset', titleBarStyle: 'hiddenInset',
}) })
@ -191,6 +205,36 @@ ipcMain.handle('find_machine_api', () => {
}) })
}) })
export function getAutoUpdater(): AppUpdater {
// Using destructuring to access autoUpdater due to the CommonJS module of 'electron-updater'.
// It is a workaround for ESM compatibility issues, see https://github.com/electron-userland/electron-builder/issues/7976.
const { autoUpdater } = electronUpdater
return autoUpdater
}
export async function checkForUpdates(autoUpdater: AppUpdater) {
// TODO: figure out how to get the update modal back
const result = await autoUpdater.checkForUpdatesAndNotify()
console.log(result)
}
app.on('ready', async () => {
const autoUpdater = getAutoUpdater()
checkForUpdates(autoUpdater)
const fifteenMinutes = 15 * 60 * 1000
setInterval(() => {
checkForUpdates(autoUpdater)
}, fifteenMinutes)
autoUpdater.on('update-available', (info) => {
console.log('update-available', info)
})
autoUpdater.on('update-downloaded', (info) => {
console.log('update-downloaded', info)
})
})
ipcMain.handle('loadProjectAtStartup', async () => { ipcMain.handle('loadProjectAtStartup', async () => {
// If we are in development mode, we don't want to load a project at // If we are in development mode, we don't want to load a project at
// startup. // startup.

View File

@ -132,9 +132,9 @@ dependencies = [
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.81" version = "0.1.82"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -13,7 +13,7 @@ keywords = ["kcl", "KittyCAD", "CAD"]
[dependencies] [dependencies]
anyhow = { version = "1.0.86", features = ["backtrace"] } anyhow = { version = "1.0.86", features = ["backtrace"] }
async-recursion = "1.1.1" async-recursion = "1.1.1"
async-trait = "0.1.81" async-trait = "0.1.82"
base64 = "0.22.1" base64 = "0.22.1"
chrono = "0.4.38" chrono = "0.4.38"
clap = { version = "4.5.16", default-features = false, optional = true, features = ["std", "derive"] } clap = { version = "4.5.16", default-features = false, optional = true, features = ["std", "derive"] }

View File

@ -304,7 +304,7 @@ async fn inner_get_next_adjacent_edge(tag: TagIdentifier, args: Args) -> Result<
let resp = args let resp = args
.send_modeling_cmd( .send_modeling_cmd(
uuid::Uuid::new_v4(), uuid::Uuid::new_v4(),
ModelingCmd::Solid3DGetPrevAdjacentEdge { ModelingCmd::Solid3DGetNextAdjacentEdge {
edge_id: tagged_path.id, edge_id: tagged_path.id,
object_id: tagged_path.sketch_group, object_id: tagged_path.sketch_group,
face_id, face_id,
@ -312,7 +312,7 @@ async fn inner_get_next_adjacent_edge(tag: TagIdentifier, args: Args) -> Result<
) )
.await?; .await?;
let kittycad::types::OkWebSocketResponseData::Modeling { let kittycad::types::OkWebSocketResponseData::Modeling {
modeling_response: kittycad::types::OkModelingCmdResponse::Solid3DGetPrevAdjacentEdge { data: ajacent_edge }, modeling_response: kittycad::types::OkModelingCmdResponse::Solid3DGetNextAdjacentEdge { data: ajacent_edge },
} = &resp } = &resp
else { else {
return Err(KclError::Engine(KclErrorDetails { return Err(KclError::Engine(KclErrorDetails {
@ -386,7 +386,7 @@ async fn inner_get_previous_adjacent_edge(tag: TagIdentifier, args: Args) -> Res
let resp = args let resp = args
.send_modeling_cmd( .send_modeling_cmd(
uuid::Uuid::new_v4(), uuid::Uuid::new_v4(),
ModelingCmd::Solid3DGetNextAdjacentEdge { ModelingCmd::Solid3DGetPrevAdjacentEdge {
edge_id: tagged_path.id, edge_id: tagged_path.id,
object_id: tagged_path.sketch_group, object_id: tagged_path.sketch_group,
face_id, face_id,
@ -394,7 +394,7 @@ async fn inner_get_previous_adjacent_edge(tag: TagIdentifier, args: Args) -> Res
) )
.await?; .await?;
let kittycad::types::OkWebSocketResponseData::Modeling { let kittycad::types::OkWebSocketResponseData::Modeling {
modeling_response: kittycad::types::OkModelingCmdResponse::Solid3DGetNextAdjacentEdge { data: ajacent_edge }, modeling_response: kittycad::types::OkModelingCmdResponse::Solid3DGetPrevAdjacentEdge { data: ajacent_edge },
} = &resp } = &resp
else { else {
return Err(KclError::Engine(KclErrorDetails { return Err(KclError::Engine(KclErrorDetails {

View File

@ -56,10 +56,10 @@ const bracketBody = bs
|> fillet({ |> fillet({
radius: radius, radius: radius,
tags: [ tags: [
getNextAdjacentEdge(bs.sketchGroup.tags.edge7), getPreviousAdjacentEdge(bs.sketchGroup.tags.edge7),
getNextAdjacentEdge(bs.sketchGroup.tags.edge2), getPreviousAdjacentEdge(bs.sketchGroup.tags.edge2),
getNextAdjacentEdge(bs.sketchGroup.tags.edge3), getPreviousAdjacentEdge(bs.sketchGroup.tags.edge3),
getNextAdjacentEdge(bs.sketchGroup.tags.edge6) getPreviousAdjacentEdge(bs.sketchGroup.tags.edge6)
] ]
}, %) }, %)

View File

@ -56,10 +56,10 @@ const bracketBody = bs
|> fillet({ |> fillet({
radius: radius, radius: radius,
tags: [ tags: [
getNextAdjacentEdge(bs.tags.edge7), getPreviousAdjacentEdge(bs.tags.edge7),
getNextAdjacentEdge(bs.tags.edge2), getPreviousAdjacentEdge(bs.tags.edge2),
getNextAdjacentEdge(bs.tags.edge3), getPreviousAdjacentEdge(bs.tags.edge3),
getNextAdjacentEdge(bs.tags.edge6) getPreviousAdjacentEdge(bs.tags.edge6)
] ]
}, %) }, %)
@ -89,7 +89,7 @@ const tabsR = startSketchOn(tabPlane)
radius: holeDiam / 2, radius: holeDiam / 2,
tags: [ tags: [
getNextAdjacentEdge(edge12), getNextAdjacentEdge(edge12),
getNextAdjacentEdge(edge13) getNextAdjacentEdge(edge11)
] ]
}, %) }, %)
|> patternLinear3d({ |> patternLinear3d({

View File

@ -1287,7 +1287,7 @@ capScrew([0, 0.5, 0], 50, 37.5, 50, 25)
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn kcl_test_bracket_with_fillets_ensure_fail_on_flush_source_ranges() { async fn kcl_test_bracket_with_fillets() {
let code = r#"// Shelf Bracket let code = r#"// Shelf Bracket
// This is a shelf bracket made out of 6061-T6 aluminum sheet metal. The required thickness is calculated based on a point load of 300 lbs applied to the end of the shelf. There are two brackets holding up the shelf, so the moment experienced is divided by 2. The shelf is 1 foot long from the wall. // This is a shelf bracket made out of 6061-T6 aluminum sheet metal. The required thickness is calculated based on a point load of 300 lbs applied to the end of the shelf. There are two brackets holding up the shelf, so the moment experienced is divided by 2. The shelf is 1 foot long from the wall.
@ -1328,11 +1328,7 @@ const bracket = startSketchOn('XY')
"#; "#;
let result = execute_and_snapshot(code, UnitLength::Mm).await; let result = execute_and_snapshot(code, UnitLength::Mm).await;
assert!(result.is_err()); assert!(result.is_ok());
assert_eq!(
result.err().unwrap().to_string(),
r#"engine: KclErrorDetails { source_ranges: [SourceRange([1329, 1430])], message: "Modeling command failed: [ApiError { error_code: BadRequest, message: \"Fillet failed\" }]" }"#
);
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
@ -1536,13 +1532,13 @@ const bracket = startSketchOn('XY')
|> fillet({ |> fillet({
radius: filletR, radius: filletR,
tags: [ tags: [
getPreviousAdjacentEdge(innerEdge) getNextAdjacentEdge(innerEdge)
] ]
}, %) }, %)
|> fillet({ |> fillet({
radius: filletR + thickness, radius: filletR + thickness,
tags: [ tags: [
getPreviousAdjacentEdge(outerEdge) getNextAdjacentEdge(outerEdge)
] ]
}, %) }, %)

View File

@ -49,7 +49,13 @@ export function getDefineKeys(names: string[]) {
export function getBuildDefine(env: ConfigEnv<'build'>) { export function getBuildDefine(env: ConfigEnv<'build'>) {
const { command, forgeConfig } = env const { command, forgeConfig } = env
const names = forgeConfig.renderer const renderer = (forgeConfig && forgeConfig.renderer) ?? [
{
name: 'main_window',
config: 'vite.renderer.config.ts',
},
]
const names = renderer
.filter(({ name }) => name != null) .filter(({ name }) => name != null)
.map(({ name }) => name!) .map(({ name }) => name!)
const defineKeys = getDefineKeys(names) const defineKeys = getDefineKeys(names)

View File

@ -67,7 +67,7 @@ export default defineConfig((env) => {
}, },
build: { build: {
lib: { lib: {
entry: forgeConfigSelf.entry!, entry: forgeConfigSelf?.entry ?? 'src/main.ts',
fileName: () => '[name].js', fileName: () => '[name].js',
formats: ['cjs'], formats: ['cjs'],
}, },

View File

@ -20,7 +20,7 @@ export default defineConfig((env) => {
base: './', base: './',
build: { build: {
lib: { lib: {
entry: forgeConfigSelf.entry!, entry: forgeConfigSelf?.entry ?? 'src/preload.ts',
fileName: () => '[name].js', fileName: () => '[name].js',
formats: ['cjs'], formats: ['cjs'],
}, },

View File

@ -9,7 +9,7 @@ import { lezer } from '@lezer/generator/rollup'
export default defineConfig((env) => { export default defineConfig((env) => {
const forgeEnv = env as ConfigEnv<'renderer'> const forgeEnv = env as ConfigEnv<'renderer'>
const { root, mode, forgeConfigSelf } = forgeEnv const { root, mode, forgeConfigSelf } = forgeEnv
const name = forgeConfigSelf.name ?? '' const name = forgeConfigSelf?.name ?? 'main_window'
return { return {
root, root,

652
yarn.lock

File diff suppressed because it is too large Load Diff