Local dictation had never produced a transcript on an installed build. The engine itself was healthy; every connection to it was broken. Installed builds shipped no speech engine at all: the packaging config had no entry for the faster-whisper sidecar and no pipeline step built one, so the app always fell back to a system Python without the runtime. Development was broken too, because the sidecar and SoX paths were resolved against the Vite output directory instead of the app root, which also meant recording failed with a SoX ENOENT. On hosts where localhost resolves only to IPv6, every local request was refused outright, which silently disabled both local transcription and the local LLM. The sidecar is now built and bundled (including the Silero VAD data it needs), gated by a packaging check that fails when the engine or its data is missing. Paths are discovered from the app root and fail loudly when the engine is absent. Local engine URLs are normalized to the IPv4 loopback, decoding is tuned so repeated hallucinations cannot compound (the same transcript now takes about a fifth of the time), the engine is warmed up at startup, and holding the hotkey now shows the text forming live in the recording tip.
507 lines
26 KiB
YAML
507 lines
26 KiB
YAML
# .gitlab-ci.yml
|
|
# GitLab CI/CD Pipeline for D3RO Voice
|
|
|
|
stages:
|
|
- validate
|
|
- test
|
|
- build
|
|
- e2e
|
|
- package
|
|
- publish
|
|
- deploy
|
|
|
|
variables:
|
|
NODE_VERSION: "24.19.0"
|
|
PACKAGE_NAME: "d3ro-voice"
|
|
|
|
default:
|
|
image: node:24.19.0-bookworm
|
|
tags:
|
|
- build-linux-x64
|
|
before_script:
|
|
- npm ci
|
|
|
|
# ────────────────────────────────────────────────────────────────────
|
|
# Validate & Lint
|
|
# ────────────────────────────────────────────────────────────────────
|
|
lint-and-typecheck:
|
|
stage: validate
|
|
script:
|
|
- npm run version:check
|
|
- if [ -n "${CI_COMMIT_TAG:-}" ]; then node scripts/ci/sync-version.mjs --check --tag "$CI_COMMIT_TAG"; fi
|
|
- npm run release:metadata:test
|
|
- npm run security:secrets:test
|
|
- npm run security:secrets
|
|
- npm run release:mobile:boundary:test
|
|
- npm run release:mobile:config:test
|
|
- npm run release:mobile:build-config:test
|
|
- npm run release:play:assets
|
|
- npm run lint
|
|
- npm run typecheck
|
|
rules:
|
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
- if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "develop"'
|
|
- if: '$CI_COMMIT_TAG'
|
|
|
|
# ────────────────────────────────────────────────────────────────────
|
|
# Unit & Integration Tests
|
|
# ────────────────────────────────────────────────────────────────────
|
|
test-unit:
|
|
stage: test
|
|
script:
|
|
- npm test
|
|
coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
|
|
artifacts:
|
|
when: always
|
|
reports:
|
|
junit: junit.xml
|
|
rules:
|
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
- if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "develop"'
|
|
- if: '$CI_COMMIT_TAG'
|
|
|
|
api-server-tests:
|
|
stage: test
|
|
image: mcr.microsoft.com/dotnet/sdk:10.0.302-noble
|
|
before_script:
|
|
- . scripts/ci/bootstrap-linux-toolchain.sh dotnet
|
|
cache:
|
|
key: linux-dotnet-sdk-10.0.302
|
|
paths:
|
|
- .ci-tools/dotnet-sdk-10.0.302/
|
|
script:
|
|
- dotnet restore apps/api-server.Tests/D3ROVoice.Api.Tests.csproj
|
|
- dotnet test apps/api-server.Tests/D3ROVoice.Api.Tests.csproj --configuration Release --no-restore -p:StaticWebAssetsEnabled=false
|
|
rules:
|
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
- if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "develop"'
|
|
- if: '$CI_COMMIT_TAG'
|
|
|
|
edge-functions-quality:
|
|
stage: test
|
|
image: denoland/deno:2.8.1
|
|
before_script:
|
|
- . scripts/ci/bootstrap-linux-toolchain.sh deno
|
|
cache:
|
|
key: linux-deno-2.8.1
|
|
paths:
|
|
- .ci-tools/deno-2.8.1/
|
|
script:
|
|
- for entrypoint in server/supabase/functions/*/index.ts; do deno check --config server/supabase/functions/deno.json "$entrypoint"; done
|
|
- deno test --config server/supabase/functions/deno.json --allow-read --allow-env server/supabase/functions
|
|
rules:
|
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
- if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "develop"'
|
|
- if: '$CI_COMMIT_TAG'
|
|
|
|
mobile-quality:
|
|
stage: test
|
|
before_script:
|
|
- npm ci
|
|
- npm --prefix apps/mobile-rn ci --workspaces=false
|
|
script:
|
|
- npm --prefix apps/mobile-rn run lint
|
|
- npm --prefix apps/mobile-rn run typecheck
|
|
- npm --prefix apps/mobile-rn run test:ci
|
|
rules:
|
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
- if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "develop"'
|
|
- if: '$CI_COMMIT_TAG'
|
|
|
|
mobile-android:
|
|
stage: build
|
|
image: reactnativecommunity/react-native-android@sha256:24ca7ab5a70ec0b78a81bdc5eeea5924c2531531d53971b6f2321aff08446c36
|
|
needs:
|
|
- mobile-quality
|
|
before_script:
|
|
- . scripts/ci/bootstrap-linux-toolchain.sh android
|
|
- curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" -o /tmp/node.tar.xz
|
|
- echo '14b342e71204f811bde6153be8e04b62aef63c236fef92b55f9c83154b409647 /tmp/node.tar.xz' | sha256sum -c -
|
|
- mkdir -p /tmp/node24
|
|
- tar -xJf /tmp/node.tar.xz -C /tmp/node24 --strip-components=1
|
|
- export PATH="/tmp/node24/bin:$PATH"
|
|
- node --version | grep -Fx "v${NODE_VERSION}"
|
|
- export D3RO_VERSION_NAME="0.0.0-e2e.${CI_PIPELINE_IID}"
|
|
- export D3RO_VERSION_CODE="${CI_PIPELINE_IID}"
|
|
- npm ci
|
|
- npm --prefix apps/mobile-rn ci --workspaces=false
|
|
cache:
|
|
key: linux-android-15859902-jdk-17.0.20.1
|
|
paths:
|
|
- .ci-tools/android-sdk/
|
|
- .ci-tools/microsoft-jdk-17.0.20.1/
|
|
script:
|
|
- node scripts/ci/prepare-whisper-model.mjs
|
|
- cd apps/mobile-rn/android
|
|
- ./gradlew :app:assembleDebug :app:assembleDebugAndroidTest :app:assembleE2e -PreactNativeArchitectures=arm64-v8a,x86_64 --no-daemon
|
|
- cd "$CI_PROJECT_DIR"
|
|
- node scripts/ci/verify-mobile-build-config.mjs debug > apps/mobile-rn/android/app/build/outputs/debug-build-config.json
|
|
- node scripts/ci/verify-mobile-build-config.mjs e2e > apps/mobile-rn/android/app/build/outputs/e2e-build-config.json
|
|
- node scripts/ci/verify-android-artifact.mjs --mode debug --apk apps/mobile-rn/android/app/build/outputs/apk/debug/app-debug.apk > apps/mobile-rn/android/app/build/outputs/debug-artifact-evidence.json
|
|
- node scripts/ci/verify-android-artifact.mjs --mode e2e --apk apps/mobile-rn/android/app/build/outputs/apk/e2e/app-e2e.apk --expected-version-name "$D3RO_VERSION_NAME" --expected-version-code "$D3RO_VERSION_CODE" > apps/mobile-rn/android/app/build/outputs/e2e-artifact-evidence.json
|
|
- sha256sum apps/mobile-rn/android/app/build/outputs/apk/debug/app-debug.apk apps/mobile-rn/android/app/build/outputs/apk/e2e/app-e2e.apk > apps/mobile-rn/android/app/build/outputs/android-ci.sha256
|
|
artifacts:
|
|
when: always
|
|
expire_in: 1 day
|
|
paths:
|
|
- apps/mobile-rn/android/app/build/outputs/apk/debug/app-debug.apk
|
|
- apps/mobile-rn/android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk
|
|
- apps/mobile-rn/android/app/build/outputs/apk/e2e/app-e2e.apk
|
|
- apps/mobile-rn/android/app/build/outputs/android-ci.sha256
|
|
- apps/mobile-rn/android/app/build/outputs/*-build-config.json
|
|
- apps/mobile-rn/android/app/build/outputs/*-artifact-evidence.json
|
|
rules:
|
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
- if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "develop"'
|
|
- if: '$CI_COMMIT_TAG'
|
|
|
|
mobile-emulator-e2e:
|
|
stage: e2e
|
|
image: reactnativecommunity/react-native-android@sha256:24ca7ab5a70ec0b78a81bdc5eeea5924c2531531d53971b6f2321aff08446c36
|
|
tags:
|
|
- android-emulator
|
|
- kvm
|
|
needs:
|
|
- job: mobile-android
|
|
artifacts: true
|
|
before_script:
|
|
- . scripts/ci/bootstrap-linux-toolchain.sh android
|
|
cache:
|
|
key: linux-android-15859902-jdk-17.0.20.1
|
|
paths:
|
|
- .ci-tools/android-sdk/
|
|
- .ci-tools/microsoft-jdk-17.0.20.1/
|
|
script:
|
|
- sdkmanager "platform-tools" "emulator" "platforms;android-35" "system-images;android-35;google_apis;x86_64" >/dev/null
|
|
- echo no | avdmanager create avd --force --name d3ro_ci_api35 --package "system-images;android-35;google_apis;x86_64"
|
|
- emulator -avd d3ro_ci_api35 -no-window -noaudio -no-boot-anim -gpu swiftshader_indirect -camera-back none &
|
|
- adb wait-for-device
|
|
- timeout 180 bash -c 'until [[ "$(adb shell getprop sys.boot_completed 2>/dev/null | tr -d "\r")" == "1" ]]; do sleep 2; done'
|
|
- curl -fsSL https://github.com/mobile-dev-inc/maestro/releases/download/cli-2.7.0/maestro.zip -o /tmp/maestro.zip
|
|
- echo 'a4ccab6b604617e7aef6db4f885666056eabe5cfa32befaa3bc994041b8fcbb5 /tmp/maestro.zip' | sha256sum -c -
|
|
- unzip -q /tmp/maestro.zip -d /tmp/maestro
|
|
- export PATH="/tmp/maestro/maestro/bin:$PATH"
|
|
- bash scripts/ci/run-mobile-csprng-instrumentation.sh apps/mobile-rn/android/app/build/outputs/apk/debug/app-debug.apk apps/mobile-rn/android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk
|
|
- bash scripts/ci/run-mobile-emulator-gate.sh apps/mobile-rn/android/app/build/outputs/apk/e2e/app-e2e.apk
|
|
artifacts:
|
|
when: always
|
|
expire_in: 7 days
|
|
reports:
|
|
junit: apps/mobile-rn/.maestro/*.junit.xml
|
|
paths:
|
|
- apps/mobile-rn/.maestro/*.junit.xml
|
|
- apps/mobile-rn/.maestro-output/
|
|
rules:
|
|
- if: '$D3RO_ANDROID_EMULATOR_RUNNER == "true" && $CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
- if: '$D3RO_ANDROID_EMULATOR_RUNNER == "true" && ($CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "develop")'
|
|
- if: '$D3RO_ANDROID_EMULATOR_RUNNER == "true" && $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
|
|
|
|
# ────────────────────────────────────────────────────────────────────
|
|
# Build Workspaces
|
|
# ────────────────────────────────────────────────────────────────────
|
|
build-workspaces:
|
|
stage: build
|
|
script:
|
|
- npm run build --workspace=@d3ro/desktop
|
|
- npm run build --workspace=@d3ro/admin
|
|
artifacts:
|
|
paths:
|
|
- apps/desktop/out/
|
|
- apps/admin/.next/
|
|
expire_in: 1 day
|
|
rules:
|
|
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
- if: '$CI_COMMIT_TAG'
|
|
|
|
# ────────────────────────────────────────────────────────────────────
|
|
# Package Windows (Windows Runner)
|
|
# ────────────────────────────────────────────────────────────────────
|
|
package-windows:
|
|
stage: package
|
|
tags:
|
|
- build-win-x64
|
|
before_script:
|
|
- npm ci
|
|
script:
|
|
- |
|
|
$ErrorActionPreference = 'Stop'
|
|
if ([string]::IsNullOrWhiteSpace($env:WIN_CSC_PFX_FILE) -or -not (Test-Path -LiteralPath $env:WIN_CSC_PFX_FILE -PathType Leaf)) {
|
|
throw 'WIN_CSC_PFX_FILE must be a protected GitLab file variable containing the production code-signing PFX.'
|
|
}
|
|
if ((Get-Item -LiteralPath $env:WIN_CSC_PFX_FILE).Length -le 0) {
|
|
throw 'WIN_CSC_PFX_FILE is empty.'
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($env:WIN_CSC_KEY_PASSWORD)) {
|
|
throw 'WIN_CSC_KEY_PASSWORD must be a protected, masked GitLab variable.'
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($env:WIN_CSC_EXPECTED_SIGNER_SUBJECT)) {
|
|
throw 'WIN_CSC_EXPECTED_SIGNER_SUBJECT must be a protected GitLab variable.'
|
|
}
|
|
if ($env:WIN_CSC_EXPECTED_SIGNER_SUBJECT -match '(?i)Everything2EverythingDev') {
|
|
throw 'The local Everything2EverythingDev certificate is not a production signing identity.'
|
|
}
|
|
$env:CSC_LINK = (Resolve-Path -LiteralPath $env:WIN_CSC_PFX_FILE).Path
|
|
$env:CSC_KEY_PASSWORD = $env:WIN_CSC_KEY_PASSWORD
|
|
- node scripts/ci/sync-version.mjs --check --tag "$CI_COMMIT_TAG"
|
|
# 로컬 STT는 faster-whisper 사이드카에 의존한다. 이 번들이 빠지면 설치본에서
|
|
# 전사가 전혀 동작하지 않으므로, 패키징 전에 반드시 빌드하고 검증한다.
|
|
- npm run sidecar:setup --workspace=@d3ro/desktop
|
|
- npm run sidecar:build --workspace=@d3ro/desktop
|
|
- node scripts/ci/verify-sidecar-bundle.mjs
|
|
- npm run build --workspace=@d3ro/desktop
|
|
- cd apps/desktop
|
|
- npx electron-builder --win --x64 --config electron-builder.yml
|
|
- cd ../..
|
|
- $releaseVersion = node -p "require('./release/product-version.json').version"
|
|
- '& scripts/ci/verify-windows-release-artifact.ps1 -ExpectedVersion $releaseVersion -ExpectedSignerSubject $env:WIN_CSC_EXPECTED_SIGNER_SUBJECT -ReleaseDirectory "apps/desktop/release/$releaseVersion"'
|
|
artifacts:
|
|
name: "d3ro-voice-windows-$CI_COMMIT_TAG"
|
|
paths:
|
|
- apps/desktop/release/*/*.exe
|
|
- apps/desktop/release/*/*.blockmap
|
|
- apps/desktop/release/*/latest.yml
|
|
expire_in: 7 days
|
|
rules:
|
|
- if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+.*$/'
|
|
|
|
# ────────────────────────────────────────────────────────────────────
|
|
# Package macOS (macOS Runner)
|
|
# ────────────────────────────────────────────────────────────────────
|
|
package-macos:
|
|
stage: package
|
|
tags:
|
|
- build-mac-arm64
|
|
before_script:
|
|
- npm ci
|
|
script:
|
|
- node scripts/ci/sync-version.mjs --check --tag "$CI_COMMIT_TAG"
|
|
# 로컬 STT(faster-whisper 사이드카) 번들 — 누락 시 설치본 전사 불가.
|
|
- npm run sidecar:setup --workspace=@d3ro/desktop
|
|
- npm run sidecar:build --workspace=@d3ro/desktop
|
|
- node scripts/ci/verify-sidecar-bundle.mjs
|
|
- npm run build --workspace=@d3ro/desktop
|
|
- cd apps/desktop
|
|
- npx electron-builder --mac --arm64 --config electron-builder.yml
|
|
artifacts:
|
|
name: "d3ro-voice-macos-$CI_COMMIT_TAG"
|
|
paths:
|
|
- apps/desktop/release/*/*.dmg
|
|
- apps/desktop/release/*/*.zip
|
|
- apps/desktop/release/*/*.blockmap
|
|
- apps/desktop/release/*/latest-mac.yml
|
|
expire_in: 7 days
|
|
rules:
|
|
- if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+.*$/'
|
|
|
|
# Protected, manually approved Play Console candidate/stable handoff. The main
|
|
# branch path only creates restricted artifacts; publish-release remains tag-only.
|
|
# Configure the three *_FILE variables below as protected GitLab file variables
|
|
# scoped to the mobile-production-release environment. All other credentials
|
|
# must be protected, masked, and hidden project/group variables.
|
|
mobile-production-release:
|
|
stage: package
|
|
image: reactnativecommunity/react-native-android@sha256:24ca7ab5a70ec0b78a81bdc5eeea5924c2531531d53971b6f2321aff08446c36
|
|
needs:
|
|
- job: lint-and-typecheck
|
|
artifacts: false
|
|
- job: test-unit
|
|
artifacts: false
|
|
- job: api-server-tests
|
|
artifacts: false
|
|
- job: edge-functions-quality
|
|
artifacts: false
|
|
- job: mobile-quality
|
|
artifacts: false
|
|
- job: mobile-android
|
|
artifacts: false
|
|
- job: mobile-emulator-e2e
|
|
artifacts: false
|
|
environment:
|
|
name: mobile-production-release
|
|
action: prepare
|
|
before_script: []
|
|
script:
|
|
- |
|
|
set -euo pipefail
|
|
. scripts/ci/bootstrap-linux-toolchain.sh android
|
|
curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" -o /tmp/node.tar.xz
|
|
echo '14b342e71204f811bde6153be8e04b62aef63c236fef92b55f9c83154b409647 /tmp/node.tar.xz' | sha256sum -c -
|
|
mkdir -p /tmp/node24
|
|
tar -xJf /tmp/node.tar.xz -C /tmp/node24 --strip-components=1
|
|
export PATH="/tmp/node24/bin:$PATH"
|
|
node --version | grep -Fx "v${NODE_VERSION}"
|
|
git fetch --no-tags origin main:refs/remotes/origin/main
|
|
git merge-base --is-ancestor "$CI_COMMIT_SHA" origin/main
|
|
test "$CI_COMMIT_SHA" = "$(git rev-parse HEAD)"
|
|
test -z "$(git status --porcelain --untracked-files=all)"
|
|
test "${CI_COMMIT_REF_PROTECTED:-}" = "true"
|
|
SOURCE_TREE_SHA="$(git rev-parse "${CI_COMMIT_SHA}^{tree}")"
|
|
VERSION_NAME="$(node -p "require('./release/product-version.json').version")"
|
|
VERSION_CODE="$(node -p "require('./release/product-version.json').androidVersionCode")"
|
|
ANDROID_UPLOAD_CERT_SHA256="$(node -p "require('./release/android-release-identity.json').uploadCertificateSha256")"
|
|
ADMOB_APP_ID="$(node -p "require('./release/android-release-identity.json').adMobAppId")"
|
|
ADMOB_BANNER_UNIT_ID="$(node -p "require('./release/android-release-identity.json').adMobBannerUnitId")"
|
|
ADMOB_REWARDED_UNIT_ID="$(node -p "require('./release/android-release-identity.json').adMobRewardedUnitId")"
|
|
if [ -n "${CI_COMMIT_TAG:-}" ]; then
|
|
test -z "${CI_COMMIT_BRANCH:-}"
|
|
test "$CI_COMMIT_TAG" = "v${VERSION_NAME}"
|
|
test "$(git rev-parse "refs/tags/${CI_COMMIT_TAG}^{commit}")" = "$CI_COMMIT_SHA"
|
|
RELEASE_GIT_REF="refs/tags/${CI_COMMIT_TAG}"
|
|
else
|
|
test "${CI_COMMIT_BRANCH:-}" = "main"
|
|
test "$CI_COMMIT_SHA" = "$(git rev-parse origin/main)"
|
|
RELEASE_GIT_REF="refs/heads/main"
|
|
fi
|
|
echo "$VERSION_NAME" | grep -Eq '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
|
|
echo "$VERSION_CODE" | grep -Eq '^[1-9][0-9]{0,9}$'
|
|
test "$VERSION_CODE" -le 2100000000
|
|
test -s "$ANDROID_RELEASE_KEYSTORE_FILE"
|
|
test -s "$ANDROID_GOOGLE_SERVICES_JSON_FILE"
|
|
test -s "$ANDROID_RELEASE_EVIDENCE_PRIVATE_KEY_FILE"
|
|
install -m 600 "$ANDROID_RELEASE_KEYSTORE_FILE" apps/mobile-rn/android/app/release.keystore
|
|
install -m 600 "$ANDROID_GOOGLE_SERVICES_JSON_FILE" apps/mobile-rn/android/app/google-services.json
|
|
install -m 600 "$ANDROID_RELEASE_EVIDENCE_PRIVATE_KEY_FILE" apps/mobile-rn/android/app/release-evidence-private.pem
|
|
test -n "$ANDROID_RELEASE_STORE_PASSWORD"
|
|
test -n "$ANDROID_RELEASE_KEY_ALIAS"
|
|
test -n "$ANDROID_RELEASE_KEY_PASSWORD"
|
|
test -n "$D3RO_FIREBASE_EXPECTED_PROJECT_ID"
|
|
test -n "$D3RO_FIREBASE_EXPECTED_PROJECT_NUMBER"
|
|
test -n "$D3RO_FIREBASE_EXPECTED_MOBILESDK_APP_ID"
|
|
npm ci
|
|
npm --prefix apps/mobile-rn ci --workspaces=false
|
|
npm run security:secrets:test
|
|
npm run security:secrets
|
|
npm run release:mobile:boundary:test
|
|
npm run release:mobile:config:test
|
|
npm run release:mobile:build-config:test
|
|
npm run release:play:assets
|
|
npm --prefix apps/mobile-rn run lint
|
|
npm --prefix apps/mobile-rn run typecheck
|
|
npm --prefix apps/mobile-rn run test:ci
|
|
node scripts/ci/prepare-whisper-model.mjs
|
|
curl --fail --silent --show-error --location --output /tmp/bundletool.jar https://github.com/google/bundletool/releases/download/1.18.3/bundletool-all-1.18.3.jar
|
|
echo 'a099cfa1543f55593bc2ed16a70a7c67fe54b1747bb7301f37fdfd6d91028e29 /tmp/bundletool.jar' | sha256sum -c -
|
|
export D3RO_RELEASE_STORE_FILE="$CI_PROJECT_DIR/apps/mobile-rn/android/app/release.keystore"
|
|
export D3RO_RELEASE_STORE_PASSWORD="$ANDROID_RELEASE_STORE_PASSWORD"
|
|
export D3RO_RELEASE_KEY_ALIAS="$ANDROID_RELEASE_KEY_ALIAS"
|
|
export D3RO_RELEASE_KEY_PASSWORD="$ANDROID_RELEASE_KEY_PASSWORD"
|
|
export D3RO_ADMOB_APP_ID="$ADMOB_APP_ID"
|
|
export D3RO_ADMOB_BANNER_UNIT_ID="$ADMOB_BANNER_UNIT_ID"
|
|
export D3RO_ADMOB_REWARDED_UNIT_ID="$ADMOB_REWARDED_UNIT_ID"
|
|
export D3RO_VERSION_NAME="$VERSION_NAME"
|
|
export D3RO_VERSION_CODE="$VERSION_CODE"
|
|
PLAY_APP_SIGNING_CERT_SHA256="$(node -p "require('./release/android-release-identity.json').playAppSigningCertificateSha256")"
|
|
npm run release:mobile:config
|
|
cd apps/mobile-rn/android
|
|
./gradlew :app:assembleRelease :app:bundleRelease -PreactNativeArchitectures=arm64-v8a --no-daemon
|
|
cd "$CI_PROJECT_DIR"
|
|
APK=apps/mobile-rn/android/app/build/outputs/apk/release/app-release.apk
|
|
AAB=apps/mobile-rn/android/app/build/outputs/bundle/release/app-release.aab
|
|
test -f "$APK"
|
|
test -f "$AAB"
|
|
node scripts/ci/verify-mobile-build-config.mjs release > apps/mobile-rn/android/app/build/outputs/release-build-config.json
|
|
node scripts/ci/create-mobile-release-evidence.mjs \
|
|
--apk "$APK" \
|
|
--aab "$AAB" \
|
|
--bundletool /tmp/bundletool.jar \
|
|
--repository "$CI_PROJECT_PATH" \
|
|
--commit-sha "$CI_COMMIT_SHA" \
|
|
--tree-sha "$SOURCE_TREE_SHA" \
|
|
--git-ref "$RELEASE_GIT_REF" \
|
|
--workflow-identity "gitlab-ci/mobile-production-release" \
|
|
--run-id "$CI_JOB_ID" \
|
|
--run-attempt "1" \
|
|
--runner-identity "$CI_RUNNER_ID:$CI_RUNNER_REVISION" \
|
|
--expected-admob-app-id "$D3RO_ADMOB_APP_ID" \
|
|
--expected-upload-cert-sha256 "$ANDROID_UPLOAD_CERT_SHA256" \
|
|
--expected-version-name "$D3RO_VERSION_NAME" \
|
|
--expected-version-code "$D3RO_VERSION_CODE" \
|
|
--private-key apps/mobile-rn/android/app/release-evidence-private.pem \
|
|
--snapshot-dir apps/mobile-rn/android/app/build/outputs/release-snapshot
|
|
VERIFIER_SHA256="$(sha256sum scripts/ci/verify-android-artifact.mjs | awk '{print $1}')"
|
|
BUNDLETOOL_SHA256="a099cfa1543f55593bc2ed16a70a7c67fe54b1747bb7301f37fdfd6d91028e29"
|
|
node scripts/ci/prepare-mobile-release-publication.mjs \
|
|
--source-root apps/mobile-rn/android/app/build/outputs/release-snapshot \
|
|
--apk apps/mobile-rn/android/app/build/outputs/release-snapshot/app-release.apk \
|
|
--aab apps/mobile-rn/android/app/build/outputs/release-snapshot/app-release.aab \
|
|
--evidence apps/mobile-rn/android/app/build/outputs/release-snapshot/release-artifact-evidence.json \
|
|
--public-key release/mobile-release-evidence-public.pem \
|
|
--destination-dir apps/mobile-rn/android/app/build/outputs/release-publication \
|
|
--expected-admob-app-id "$D3RO_ADMOB_APP_ID" \
|
|
--expected-upload-cert-sha256 "$ANDROID_UPLOAD_CERT_SHA256" \
|
|
--expected-version-name "$D3RO_VERSION_NAME" \
|
|
--expected-version-code "$D3RO_VERSION_CODE" \
|
|
--expected-repository "$CI_PROJECT_PATH" \
|
|
--expected-commit-sha "$CI_COMMIT_SHA" \
|
|
--expected-tree-sha "$SOURCE_TREE_SHA" \
|
|
--expected-git-ref "$RELEASE_GIT_REF" \
|
|
--expected-workflow-identity "gitlab-ci/mobile-production-release" \
|
|
--expected-run-id "$CI_JOB_ID" \
|
|
--expected-run-attempt "1" \
|
|
--expected-runner-identity "$CI_RUNNER_ID:$CI_RUNNER_REVISION" \
|
|
--expected-verifier-sha256 "$VERIFIER_SHA256" \
|
|
--expected-bundletool-sha256 "$BUNDLETOOL_SHA256"
|
|
node scripts/ci/verify-android-app-links.mjs \
|
|
--expected-play-app-signing-cert-sha256 "$PLAY_APP_SIGNING_CERT_SHA256" \
|
|
--forbidden-upload-cert-sha256 "$ANDROID_UPLOAD_CERT_SHA256" \
|
|
> apps/mobile-rn/android/app/build/outputs/release-app-links-evidence.json
|
|
sha256sum \
|
|
apps/mobile-rn/android/app/build/outputs/release-publication/app-release.apk \
|
|
apps/mobile-rn/android/app/build/outputs/release-publication/app-release.aab \
|
|
> apps/mobile-rn/android/app/build/outputs/release-publication/SHA256SUMS.txt
|
|
cache:
|
|
key: linux-android-15859902-jdk-17.0.20.1
|
|
paths:
|
|
- .ci-tools/android-sdk/
|
|
- .ci-tools/microsoft-jdk-17.0.20.1/
|
|
after_script:
|
|
- rm -f apps/mobile-rn/android/app/release.keystore apps/mobile-rn/android/app/google-services.json apps/mobile-rn/android/app/release-evidence-private.pem
|
|
artifacts:
|
|
access: maintainer
|
|
expire_in: 7 days
|
|
paths:
|
|
- apps/mobile-rn/android/app/build/outputs/release-publication/app-release.aab
|
|
- apps/mobile-rn/android/app/build/outputs/release-publication/android-release-evidence.json
|
|
- apps/mobile-rn/android/app/build/outputs/release-publication/android-publication-manifest.json
|
|
- apps/mobile-rn/android/app/build/outputs/release-publication/SHA256SUMS.txt
|
|
- apps/mobile-rn/android/app/build/outputs/release-snapshot/release-artifact-verification.json
|
|
- apps/mobile-rn/android/app/build/outputs/release-build-config.json
|
|
- apps/mobile-rn/android/app/build/outputs/release-app-links-evidence.json
|
|
manual_confirmation: '검증된 production AAB를 생성해 Maintainer 전용 Play Console handoff로 보낼까요?'
|
|
rules:
|
|
- if: '$D3RO_MOBILE_PRODUCTION_RELEASE_ENABLED == "true" && $D3RO_ANDROID_EMULATOR_RUNNER == "true" && $CI_COMMIT_REF_PROTECTED == "true" && $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
|
|
when: manual
|
|
allow_failure: false
|
|
- if: '$D3RO_MOBILE_PRODUCTION_RELEASE_ENABLED == "true" && $D3RO_ANDROID_EMULATOR_RUNNER == "true" && $CI_COMMIT_REF_PROTECTED == "true" && $CI_COMMIT_BRANCH == "main"'
|
|
when: manual
|
|
allow_failure: false
|
|
- when: never
|
|
|
|
# ────────────────────────────────────────────────────────────────────
|
|
# Publish Release (GitLab Package Registry + Release Page)
|
|
# ────────────────────────────────────────────────────────────────────
|
|
publish-release:
|
|
stage: publish
|
|
image: node:24.19.0-bookworm
|
|
needs:
|
|
- job: package-windows
|
|
artifacts: true
|
|
- job: package-macos
|
|
artifacts: true
|
|
optional: true
|
|
- job: mobile-production-release
|
|
artifacts: false
|
|
optional: true
|
|
script:
|
|
# canonical: Forgejo feed + release hub. legacy mirror: GitLab registry.
|
|
- node scripts/ci/publish-forgejo-release.mjs
|
|
- node scripts/ci/publish-gitlab-release.mjs
|
|
rules:
|
|
- if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+.*$/'
|
|
|
|
# ────────────────────────────────────────────────────────────────────
|
|
# Admin NAS deployment remains disabled until an immutable image, authenticated
|
|
# remote target, health check, and rollback path are configured. Do not revive
|
|
# the former DinD job: it only changed an ephemeral CI daemon and referenced a
|
|
# non-existent compose file, so it never deployed the NAS.
|
|
# ────────────────────────────────────────────────────────────────────
|