Some checks failed
deploy-site / deploy (push) Failing after 14m16s
The released installer could not start: it carried a better-sqlite3 build for the host Node runtime instead of Electron, so the app died immediately with a module version mismatch when it opened its database. Packaging now proves the Electron build of every runtime-sensitive native module before an installer or archive exists, and installers are produced only from that verified tree, so the mistake cannot pass silently. The release pipelines run the same check. The default local model also pointed at a retired model: a *.gguf name that Ollama cannot serve, while the settings, onboarding, and guide screens recommended an older model. All of them now use the model the service code already preferred.
509 lines
26 KiB
YAML
509 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
|
|
- node scripts/ci/verify-native-abi.mjs
|
|
- 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
|
|
- node scripts/ci/verify-native-abi.mjs
|
|
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.
|
|
# ────────────────────────────────────────────────────────────────────
|