Skip to content

Commit 4ea2bd0

Browse files
committed
feat: add desktop app v1
1 parent b33238f commit 4ea2bd0

101 files changed

Lines changed: 15029 additions & 15 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/desktop.yml

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
name: Desktop
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
- master
9+
tags:
10+
- "desktop-v*"
11+
workflow_dispatch:
12+
13+
permissions:
14+
contents: read
15+
16+
jobs:
17+
check:
18+
runs-on: ubuntu-latest
19+
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- uses: actions/setup-python@v5
24+
with:
25+
python-version: "3.11"
26+
27+
- uses: astral-sh/setup-uv@v3
28+
29+
- uses: actions/setup-node@v4
30+
with:
31+
node-version: "20"
32+
cache: npm
33+
cache-dependency-path: desktop/package-lock.json
34+
35+
- name: Install Python dependencies
36+
run: uv sync --dev
37+
38+
- name: Run Python lint
39+
run: uv run ruff check .
40+
41+
- name: Run Python tests
42+
run: uv run pytest
43+
44+
- name: Install desktop dependencies
45+
working-directory: desktop
46+
run: npm ci
47+
48+
- name: Run frontend lint
49+
working-directory: desktop
50+
run: npm run lint
51+
52+
- name: Run frontend tests
53+
working-directory: desktop
54+
run: npm run test
55+
56+
- name: Build frontend
57+
working-directory: desktop
58+
run: npm run build
59+
60+
build:
61+
needs: check
62+
if: startsWith(github.ref, 'refs/tags/desktop-v') || github.event_name == 'workflow_dispatch'
63+
permissions:
64+
contents: write
65+
strategy:
66+
fail-fast: false
67+
matrix:
68+
include:
69+
- os: macos-15
70+
target: aarch64-apple-darwin
71+
bundles: app,dmg
72+
artifact-name: nber-cli-desktop-macos-arm64
73+
release-platform: macos
74+
- os: macos-15-intel
75+
target: x86_64-apple-darwin
76+
bundles: app,dmg
77+
artifact-name: nber-cli-desktop-macos-x64
78+
release-platform: macos
79+
- os: windows-2022
80+
target: x86_64-pc-windows-msvc
81+
bundles: nsis
82+
artifact-name: nber-cli-desktop-windows-x64
83+
release-platform: windows
84+
runs-on: ${{ matrix.os }}
85+
86+
steps:
87+
- uses: actions/checkout@v4
88+
89+
- uses: actions/setup-python@v5
90+
with:
91+
python-version: "3.11"
92+
93+
- uses: astral-sh/setup-uv@v3
94+
95+
- uses: actions/setup-node@v4
96+
with:
97+
node-version: "20"
98+
cache: npm
99+
cache-dependency-path: desktop/package-lock.json
100+
101+
- uses: dtolnay/rust-toolchain@stable
102+
with:
103+
targets: ${{ matrix.target }}
104+
105+
- name: Install Python dependencies
106+
run: uv sync --dev
107+
108+
- name: Install desktop dependencies
109+
working-directory: desktop
110+
run: npm ci
111+
112+
- name: Validate macOS release signing secrets
113+
if: startsWith(github.ref, 'refs/tags/desktop-v') && runner.os == 'macOS'
114+
env:
115+
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
116+
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
117+
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
118+
APPLE_ID: ${{ secrets.APPLE_ID }}
119+
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
120+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
121+
shell: bash
122+
run: uv run python scripts/validate-desktop-signing.py --platform macos --require-signed
123+
124+
- name: Validate Windows release signing secrets
125+
if: startsWith(github.ref, 'refs/tags/desktop-v') && runner.os == 'Windows'
126+
env:
127+
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
128+
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
129+
shell: pwsh
130+
run: uv run python scripts/validate-desktop-signing.py --platform windows --require-signed
131+
132+
- name: Build sidecar
133+
run: uv run python scripts/build-sidecar.py --clean --target-triple ${{ matrix.target }}
134+
135+
- name: Import Apple Developer ID certificate
136+
if: runner.os == 'macOS'
137+
env:
138+
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
139+
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
140+
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
141+
shell: bash
142+
run: |
143+
if [ -z "$APPLE_CERTIFICATE" ]; then
144+
echo "APPLE_CERTIFICATE is not configured; building without macOS signing."
145+
exit 0
146+
fi
147+
if [ -z "$KEYCHAIN_PASSWORD" ]; then
148+
echo "KEYCHAIN_PASSWORD is required for macOS signing."
149+
exit 1
150+
fi
151+
echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12
152+
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
153+
security default-keychain -s build.keychain
154+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
155+
security set-keychain-settings -t 3600 -u build.keychain
156+
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
157+
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
158+
CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application" | head -n 1 || true)
159+
if [ -z "$CERT_INFO" ]; then
160+
echo "Developer ID Application certificate not found in keychain."
161+
exit 1
162+
fi
163+
CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}')
164+
echo "APPLE_SIGNING_IDENTITY=$CERT_ID" >> "$GITHUB_ENV"
165+
166+
- name: Import Windows code signing certificate
167+
if: runner.os == 'Windows'
168+
env:
169+
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
170+
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
171+
shell: pwsh
172+
run: |
173+
if (-not $env:WINDOWS_CERTIFICATE) {
174+
Write-Host "WINDOWS_CERTIFICATE is not configured; building without Windows signing."
175+
exit 0
176+
}
177+
$certificatePath = "$env:RUNNER_TEMP\windows-certificate.pfx"
178+
[IO.File]::WriteAllBytes($certificatePath, [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE))
179+
$securePassword = ConvertTo-SecureString $env:WINDOWS_CERTIFICATE_PASSWORD -AsPlainText -Force
180+
$certificate = Import-PfxCertificate -FilePath $certificatePath -CertStoreLocation Cert:\CurrentUser\My -Password $securePassword
181+
if (-not $certificate.Thumbprint) {
182+
throw "Imported Windows certificate did not expose a thumbprint."
183+
}
184+
"WINDOWS_CERTIFICATE_THUMBPRINT=$($certificate.Thumbprint)" | Out-File -FilePath $env:GITHUB_ENV -Append
185+
186+
- name: Prepare Tauri signing configuration
187+
env:
188+
APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }}
189+
APPLE_PROVIDER_SHORT_NAME: ${{ secrets.APPLE_PROVIDER_SHORT_NAME }}
190+
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ env.WINDOWS_CERTIFICATE_THUMBPRINT }}
191+
WINDOWS_DIGEST_ALGORITHM: ${{ secrets.WINDOWS_DIGEST_ALGORITHM }}
192+
WINDOWS_TIMESTAMP_URL: ${{ secrets.WINDOWS_TIMESTAMP_URL }}
193+
run: uv run python scripts/prepare-tauri-signing.py
194+
195+
- name: Build Tauri app
196+
working-directory: desktop
197+
env:
198+
APPLE_ID: ${{ secrets.APPLE_ID }}
199+
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
200+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
201+
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
202+
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
203+
run: npm run tauri build -- --target ${{ matrix.target }} --bundles ${{ matrix.bundles }}
204+
205+
- name: Normalize release artifact names
206+
run: uv run python scripts/normalize-desktop-artifacts.py --platform ${{ matrix.release-platform }} --target-triple ${{ matrix.target }}
207+
208+
- name: Check release artifacts
209+
run: uv run python scripts/check-desktop-release.py --platform ${{ matrix.release-platform }} --max-mb 80
210+
211+
- name: Smoke test desktop app
212+
run: uv run python scripts/smoke-desktop-app.py --install-from-package
213+
214+
- name: Check signed and notarized macOS release artifacts
215+
if: startsWith(github.ref, 'refs/tags/desktop-v') && matrix.release-platform == 'macos'
216+
run: uv run python scripts/check-desktop-release.py --platform macos --max-mb 80 --require-signed --require-notarized
217+
218+
- name: Check signed Windows release artifacts
219+
if: startsWith(github.ref, 'refs/tags/desktop-v') && matrix.release-platform == 'windows'
220+
run: uv run python scripts/check-desktop-release.py --platform ${{ matrix.release-platform }} --max-mb 80 --require-signed
221+
222+
- name: Upload desktop artifacts
223+
uses: actions/upload-artifact@v4
224+
with:
225+
name: ${{ matrix.artifact-name }}
226+
path: |
227+
desktop/src-tauri/target/**/release/bundle/**/*.dmg
228+
desktop/src-tauri/target/**/release/bundle/**/*.app
229+
desktop/src-tauri/target/**/release/bundle/**/*.exe
230+
desktop/src-tauri/target/**/release/bundle/**/*.msi
231+
232+
- name: Upload GitHub Release assets
233+
if: startsWith(github.ref, 'refs/tags/desktop-v')
234+
uses: softprops/action-gh-release@v2
235+
with:
236+
draft: true
237+
files: |
238+
desktop/src-tauri/target/**/release/bundle/**/*.dmg
239+
desktop/src-tauri/target/**/release/bundle/**/*.exe
240+
desktop/src-tauri/target/**/release/bundle/**/*.msi

alembic.ini

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
[alembic]
2+
script_location = src/nber_cli/migrations
3+
prepend_sys_path = .
4+
path_separator = os
5+
sqlalchemy.url =
6+
7+
[post_write_hooks]
8+
9+
[loggers]
10+
keys = root,sqlalchemy,alembic
11+
12+
[handlers]
13+
keys = console
14+
15+
[formatters]
16+
keys = generic
17+
18+
[logger_root]
19+
level = WARNING
20+
handlers = console
21+
qualname =
22+
23+
[logger_sqlalchemy]
24+
level = WARNING
25+
handlers =
26+
qualname = sqlalchemy.engine
27+
28+
[logger_alembic]
29+
level = INFO
30+
handlers =
31+
qualname = alembic
32+
33+
[handler_console]
34+
class = StreamHandler
35+
args = (sys.stderr,)
36+
level = NOTSET
37+
formatter = generic
38+
39+
[formatter_generic]
40+
format = %(levelname)-5.5s [%(name)s] %(message)s
41+
datefmt = %H:%M:%S

desktop/.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?

desktop/.oxlintrc.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"$schema": "./node_modules/oxlint/configuration_schema.json",
3+
"plugins": ["react", "typescript", "oxc"],
4+
"rules": {
5+
"react/rules-of-hooks": "error",
6+
"react/only-export-components": ["warn", { "allowConstantExport": true }]
7+
}
8+
}

0 commit comments

Comments
 (0)