feat(android): Capacitor APK wrapper + on-demand CI build (#156)
Closes #153. - Capacitor v7 scaffold producing a sideloadable debug APK from dist/web/ - .github/workflows/build-android.yml — workflow_dispatch only; soft dependency on forgejo-stack/job-android via use_prebaked_image input (fast path ~3-5 min, fallback installs JDK + Android SDK at runtime) - Privacy hardening: allowBackup=false, FileProvider removed, Google Services plugin reference removed, usesCleartextTraffic=false, dataExtractionRules deny-all - INTERNET permission documented in docs/PRIVACY_GAPS.md (Capacitor requires it; CSP connect-src 'self' enforces no traffic) - Branded launcher icons + splash regenerated from .resources/icon.png - docs/android-apk.md updated with Q1-Q5 decisions + CI section - docs/PRIVACY_GAPS.md updated with Android <=9 Downloads/ world-readable gap - Plan: docs/superpowers/plans/2026-05-17-android-apk-and-ci.md Q1-Q5 decisions: - Q1 file output: accept Downloads/ (documented privacy gap on Android <=9) - Q2 back button: Capacitor default (close on root) - Q3 android/ placement: master - Q4 CI: in scope, workflow_dispatch only - Q5 min API: 23 (Capacitor 7 default) Review rounds 1 and 2 closed all P0 + P1 findings. Three P2 polish items split to follow-ups.
107
.github/workflows/build-android.yml
vendored
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
name: Build Android APK
|
||||
|
||||
# Manual trigger only. Per issue #153: "the action will be started
|
||||
# manually not on merge or PR." No push/pull_request/schedule.
|
||||
#
|
||||
# Security note: no untrusted inputs (issue/PR/comment body, head_ref,
|
||||
# commit messages) are referenced in any run: step. The only template
|
||||
# expansions are ${{ github.ref }} in concurrency.group (a refname) and
|
||||
# ${{ inputs.use_prebaked_image }} (a boolean dispatch input) in
|
||||
# container: and step `if:` conditions — neither is user-supplied text.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
use_prebaked_image:
|
||||
description: 'Use forgejo-stack/job-android image (fast). Uncheck if the image is not available on the runner host — workflow falls back to installing JDK + Android SDK at runtime (~3x slower).'
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
concurrency:
|
||||
group: build-android-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build-apk:
|
||||
name: Build debug APK
|
||||
runs-on: ubuntu-latest
|
||||
# Soft dependency on forgejo-stack/job-android:latest.
|
||||
#
|
||||
# When `use_prebaked_image` is true (default), the job runs inside the
|
||||
# Android-augmented image built by forgejo-stack's setup.sh — JDK 17 +
|
||||
# Android SDK are baked in, cold build is ~3-5 min.
|
||||
#
|
||||
# When `use_prebaked_image` is false, container is empty, the job runs
|
||||
# on whatever image the runner provides (forgejo-stack/job:latest on
|
||||
# this stack, GitHub-hosted runner image elsewhere), and the steps
|
||||
# below install JDK + Android SDK at runtime — cold build is ~10-15 min
|
||||
# but works on any runner that has Node + docker + apt (or already has
|
||||
# the tools, in which case setup-java / setup-android no-op).
|
||||
container: ${{ inputs.use_prebaked_image == true && 'forgejo-stack/job-android:latest' || '' }}
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build web bundle
|
||||
run: yarn build:web
|
||||
|
||||
# Toolchain bring-up — the pre-baked image already has JDK 17 +
|
||||
# Android SDK. Skip on the fast path; install on the fallback path.
|
||||
- name: Setup JDK 17
|
||||
if: ${{ inputs.use_prebaked_image != true }}
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '17'
|
||||
|
||||
- name: Setup Android SDK
|
||||
if: ${{ inputs.use_prebaked_image != true }}
|
||||
uses: android-actions/setup-android@v3
|
||||
with:
|
||||
packages: 'platforms;android-35 build-tools;35.0.0'
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: gradle-${{ runner.os }}-${{ hashFiles('android/**/*.gradle*', 'android/gradle/wrapper/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
gradle-${{ runner.os }}-
|
||||
|
||||
- name: Verify toolchain
|
||||
run: |
|
||||
java -version
|
||||
echo "ANDROID_HOME=$ANDROID_HOME"
|
||||
ls -d "$ANDROID_HOME/platforms/android-35" "$ANDROID_HOME/build-tools/35.0.0"
|
||||
|
||||
- name: Capacitor sync
|
||||
run: npx cap sync android
|
||||
|
||||
- name: Build debug APK
|
||||
working-directory: android
|
||||
run: ./gradlew assembleDebug --no-daemon --stacktrace
|
||||
|
||||
- name: Report APK info
|
||||
run: |
|
||||
ls -lh android/app/build/outputs/apk/debug/
|
||||
file android/app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: metascrub-debug-apk
|
||||
path: android/app/build/outputs/apk/debug/app-debug.apk
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
102
android/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
|
||||
|
||||
# Built application files
|
||||
*.apk
|
||||
*.aar
|
||||
*.ap_
|
||||
*.aab
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
out/
|
||||
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
||||
# release/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
# Android Studio Navigation editor temp files
|
||||
.navigation/
|
||||
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/workspace.xml
|
||||
.idea/tasks.xml
|
||||
.idea/gradle.xml
|
||||
.idea/assetWizardSettings.xml
|
||||
.idea/dictionaries
|
||||
.idea/libraries
|
||||
# Android Studio 3 in .gitignore file.
|
||||
.idea/caches
|
||||
.idea/modules.xml
|
||||
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||
.idea/navEditor.xml
|
||||
|
||||
# Keystore files — never committed (privacy-critical: a leaked release key
|
||||
# allows trojan rebuilds that masquerade as MetaScrub).
|
||||
*.jks
|
||||
*.keystore
|
||||
app/release/
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
.cxx/
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
# google-services.json
|
||||
|
||||
# Freeline
|
||||
freeline.py
|
||||
freeline/
|
||||
freeline_project_description.json
|
||||
|
||||
# fastlane
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
fastlane/test_output
|
||||
fastlane/readme.md
|
||||
|
||||
# Version control
|
||||
vcs.xml
|
||||
|
||||
# lint
|
||||
lint/intermediates/
|
||||
lint/generated/
|
||||
lint/outputs/
|
||||
lint/tmp/
|
||||
# lint/reports/
|
||||
|
||||
# Android Profiling
|
||||
*.hprof
|
||||
|
||||
# Cordova plugins for Capacitor
|
||||
capacitor-cordova-android-plugins
|
||||
|
||||
# Copied web assets
|
||||
app/src/main/assets/public
|
||||
|
||||
# Generated Config files
|
||||
app/src/main/assets/capacitor.config.json
|
||||
app/src/main/assets/capacitor.plugins.json
|
||||
app/src/main/res/xml/config.xml
|
||||
2
android/app/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/build/*
|
||||
!/build/.npmkeep
|
||||
54
android/app/build.gradle
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
namespace "com.metascrub.app"
|
||||
compileSdk rootProject.ext.compileSdkVersion
|
||||
defaultConfig {
|
||||
applicationId "com.metascrub.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "4.0.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
|
||||
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
flatDir{
|
||||
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
|
||||
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
|
||||
implementation project(':capacitor-android')
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
||||
implementation project(':capacitor-cordova-android-plugins')
|
||||
}
|
||||
|
||||
apply from: 'capacitor.build.gradle'
|
||||
|
||||
// Capacitor's Android template ships a try-catch that conditionally applies
|
||||
// `com.google.gms.google-services` when a google-services.json is present.
|
||||
// MetaScrub does not (and will not) integrate with Google services or push
|
||||
// notifications — project-direction.md §"What's NOT in scope" rules out
|
||||
// any Google dependency. The plugin reference is deleted entirely so that
|
||||
// `./gradlew dependencies` and `aapt dump permissions` are clean for F-Droid
|
||||
// reviewers and security auditors. If a future plugin truly needs FCM, add
|
||||
// it back narrowly at that time.
|
||||
19
android/app/capacitor.build.gradle
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||
|
||||
android {
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_21
|
||||
targetCompatibility JavaVersion.VERSION_21
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||
dependencies {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (hasProperty('postBuildExtras')) {
|
||||
postBuildExtras()
|
||||
}
|
||||
21
android/app/proguard-rules.pro
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.getcapacitor.myapp;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
|
||||
@Test
|
||||
public void useAppContext() throws Exception {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
|
||||
assertEquals("com.getcapacitor.app", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
41
android/app/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:fullBackupContent="false"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:usesCleartextTraffic="false">
|
||||
|
||||
<activity
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation"
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/title_activity_main"
|
||||
android:theme="@style/AppTheme.NoActionBarLaunch"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
<!-- Permissions -->
|
||||
|
||||
<!--
|
||||
INTERNET is required by Capacitor's `https://localhost/` WebView scheme
|
||||
even though the app makes zero outbound calls. The actual no-traffic
|
||||
guarantee is enforced by the CSP meta tag in vite.config.web.ts
|
||||
(connect-src 'self'). See docs/PRIVACY_GAPS.md §"Android INTERNET
|
||||
permission".
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.metascrub.app;
|
||||
|
||||
import com.getcapacitor.BridgeActivity;
|
||||
|
||||
public class MainActivity extends BridgeActivity {}
|
||||
BIN
android/app/src/main/res/drawable-land-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
android/app/src/main/res/drawable-land-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
android/app/src/main/res/drawable-land-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
android/app/src/main/res/drawable-land-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
android/app/src/main/res/drawable-land-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
android/app/src/main/res/drawable-port-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
android/app/src/main/res/drawable-port-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
android/app/src/main/res/drawable-port-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
android/app/src/main/res/drawable-port-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
android/app/src/main/res/drawable-port-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
android/app/src/main/res/drawable/splash.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
12
android/app/src/main/res/layout/activity_main.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<WebView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 6 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
7
android/app/src/main/res/values/strings.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="app_name">MetaScrub</string>
|
||||
<string name="title_activity_main">MetaScrub</string>
|
||||
<string name="package_name">com.metascrub.app</string>
|
||||
<string name="custom_url_scheme">com.metascrub.app</string>
|
||||
</resources>
|
||||
22
android/app/src/main/res/values/styles.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:background">@null</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
|
||||
<item name="android:background">@drawable/splash</item>
|
||||
</style>
|
||||
</resources>
|
||||
24
android/app/src/main/res/xml/data_extraction_rules.xml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Android 12+ backup/transfer policy. Both <cloud-backup> and <device-transfer>
|
||||
are intentionally empty so that no app data is exfiltrated via Google Drive
|
||||
auto-backup or device-to-device transfer. Pair with android:allowBackup="false"
|
||||
and android:fullBackupContent="false" in AndroidManifest.xml for full coverage
|
||||
across all supported API levels.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<exclude domain="root" />
|
||||
<exclude domain="file" />
|
||||
<exclude domain="database" />
|
||||
<exclude domain="sharedpref" />
|
||||
<exclude domain="external" />
|
||||
</cloud-backup>
|
||||
<device-transfer>
|
||||
<exclude domain="root" />
|
||||
<exclude domain="file" />
|
||||
<exclude domain="database" />
|
||||
<exclude domain="sharedpref" />
|
||||
<exclude domain="external" />
|
||||
</device-transfer>
|
||||
</data-extraction-rules>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.getcapacitor.myapp;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
|
||||
@Test
|
||||
public void addition_isCorrect() throws Exception {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
||||
29
android/build.gradle
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.7.2'
|
||||
classpath 'com.google.gms:google-services:4.4.2'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "variables.gradle"
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
3
android/capacitor.settings.gradle
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||
include ':capacitor-android'
|
||||
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
||||
22
android/gradle.properties
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
7
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
252
android/gradlew
vendored
Executable file
|
|
@ -0,0 +1,252 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
94
android/gradlew.bat
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
5
android/settings.gradle
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
include ':app'
|
||||
include ':capacitor-cordova-android-plugins'
|
||||
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
|
||||
|
||||
apply from: 'capacitor.settings.gradle'
|
||||
16
android/variables.gradle
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
ext {
|
||||
minSdkVersion = 23
|
||||
compileSdkVersion = 35
|
||||
targetSdkVersion = 35
|
||||
androidxActivityVersion = '1.9.2'
|
||||
androidxAppCompatVersion = '1.7.0'
|
||||
androidxCoordinatorLayoutVersion = '1.2.0'
|
||||
androidxCoreVersion = '1.15.0'
|
||||
androidxFragmentVersion = '1.8.4'
|
||||
coreSplashScreenVersion = '1.0.1'
|
||||
androidxWebkitVersion = '1.12.1'
|
||||
junitVersion = '4.13.2'
|
||||
androidxJunitVersion = '1.2.1'
|
||||
androidxEspressoCoreVersion = '3.6.1'
|
||||
cordovaAndroidVersion = '10.1.1'
|
||||
}
|
||||
12
capacitor.config.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import type { CapacitorConfig } from "@capacitor/cli";
|
||||
|
||||
const config: CapacitorConfig = {
|
||||
appId: "com.metascrub.app",
|
||||
appName: "MetaScrub",
|
||||
webDir: "dist/web",
|
||||
server: {
|
||||
androidScheme: "https",
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
@ -42,6 +42,46 @@ The current shipping state. Expect this table to drift; the README's Format Supp
|
|||
|
||||
---
|
||||
|
||||
## Android `INTERNET` permission — granted but never used
|
||||
|
||||
**Decided 2026-05-17 (issue #153).** The Capacitor APK declares `<uses-permission android:name="android.permission.INTERNET" />` in `AndroidManifest.xml`. Anyone running `aapt dump permissions` on the APK will see this and reasonably ask: "but the README says zero network traffic?"
|
||||
|
||||
**Why the permission is there:** Capacitor's WebView interceptor serves bundled assets via `https://localhost/` (so the renderer is in a secure context — required for WASM and service workers). Android's WebView grants asset loads against any scheme through the platform's HTTP plumbing, which requires the app to hold `INTERNET`. There is no Capacitor configuration that loads local assets without it.
|
||||
|
||||
**What actually enforces no outbound traffic:** the CSP meta tag injected by `vite.config.web.ts`:
|
||||
|
||||
```html
|
||||
<meta http-equiv="Content-Security-Policy" content="... connect-src 'self' ...">
|
||||
```
|
||||
|
||||
`connect-src 'self'` means the WebView refuses any `fetch()`, XHR, WebSocket, EventSource, or beacon to any origin other than the `https://localhost/` Capacitor scheme. The renderer has no analytics SDK, no error reporting, no auto-update check, no font CDN, and no call sites that would hit a remote origin in the first place.
|
||||
|
||||
**What you can verify yourself:**
|
||||
- `aapt dump permissions app-debug.apk` — shows only `INTERNET` (no `READ_EXTERNAL_STORAGE`, `ACCESS_NETWORK_STATE`, `ACCESS_COARSE_LOCATION`, etc.).
|
||||
- Run the APK behind a host-side firewall or mitmproxy with the device on a captive-WiFi — no outbound DNS or TCP from the app's UID after launch.
|
||||
- (Future) the no-network Playwright test (issue #67) will exercise this in CI.
|
||||
|
||||
**What MetaScrub could do (won't):** ship a WebView shim that loads assets via `content://` instead of `https://localhost/`, removing the need for `INTERNET`. The drawback is losing the secure-context guarantee (no WASM, no service worker). The trade-off isn't worth it — the CSP layer + the absence of fetch sites already gives the same outcome with a much smaller change.
|
||||
|
||||
---
|
||||
|
||||
## Android `Downloads/` — world-readable on Android ≤9
|
||||
|
||||
**Decided 2026-05-17 (issue #153).** The Capacitor APK uses the system WebView; `<a download>` invocations route through Android's DownloadManager, which writes the cleaned file to the public `Downloads/` folder. On Android 9 and below, that folder is world-readable — any other app on the device with `READ_EXTERNAL_STORAGE` (granted at install for legacy apps) can enumerate and read the cleaned file.
|
||||
|
||||
**What may leak:** the cleaned file's *existence and contents* to other installed apps with broad storage permissions. The metadata strip itself is unaffected — the file is byte-equivalent to what the user would get from the web build. The leak is "another app on the same device can read the file you just saved," not "the metadata wasn't actually stripped."
|
||||
|
||||
**Affected versions:** Android 6.0–9.0 (API 23–28). Android 10+ (API 29+) introduced scoped storage; the cleaned file is isolated to the app's Downloads entry and not world-readable.
|
||||
|
||||
**What you can do:**
|
||||
- Use Android 10 or later if available (scoped storage applies automatically).
|
||||
- Move the cleaned file to your app-private storage immediately after the strip (e.g. into Signal's media folder before sharing).
|
||||
- For users on Android ≤9 who need stronger isolation, the PWA path (Add to Home Screen + the browser's download flow) has the same behaviour — this isn't an APK-specific regression; it's a property of the platform's pre-scoped-storage filesystem model.
|
||||
|
||||
**What MetaScrub could do (deferred):** a Capacitor-aware adapter in `web_api.ts` (detectable via `typeof Capacitor !== 'undefined'`) using `@capacitor/filesystem` to write into the app's private external storage with an explicit location picker for export. This is a one-issue follow-up; not implemented in the v1 APK pass to keep the patch small.
|
||||
|
||||
---
|
||||
|
||||
## RAW unsupported
|
||||
|
||||
**Decided 2026-05-09 (issue #16).** Previously, RAW formats (CR2, CR3, NEF, ARW, RAF, ORF, DNG, RW2, X3F, and dozens of vendor variants) were processed by the bundled Perl ExifTool inside the Electron desktop build. Phase D removes that wrapper entirely; v5 ships a single WASM/pure-TS code path that does not cover proprietary RAW.
|
||||
|
|
|
|||
|
|
@ -7,6 +7,16 @@ This doc covers two questions asked together:
|
|||
|
||||
**TL;DR:** The APK path via Capacitor is viable and well-defined. The AAR path is not worth pursuing given the project's constraints — see the [AAR Feasibility](#aar-feasibility) section for the full analysis.
|
||||
|
||||
## Decisions (issue #153 v1)
|
||||
|
||||
| Q | Decision | Rationale |
|
||||
| --- | --- | --- |
|
||||
| File output path | `<a download>` → system `Downloads/` (accepted) | Documented as a known limitation on Android ≤9 in [`PRIVACY_GAPS.md`](PRIVACY_GAPS.md). Android 10+ scoped storage mitigates. A future `@capacitor/filesystem` adapter is the upgrade path. |
|
||||
| Back button | Capacitor default (close on root) | Single-screen app; no nav stack. |
|
||||
| `android/` placement | Committed to `master` | Matches project pattern (everything in one repo). ~12 MB on disk. |
|
||||
| CI | `workflow_dispatch` only | Run manually, never on push/PR. See `.github/workflows/build-android.yml` and the [§CI](#ci) section below. |
|
||||
| Min API level | 23 (Capacitor 7 default) | Covers 99%+ of devices; WASM secure-context requirement is well within range. |
|
||||
|
||||
> [!NOTE]
|
||||
> The APK path produces a **personal-distribution APK** (sideloaded, unsigned or self-signed). It is not the project's official mobile story — `project-direction.md` documents that as the PWA install path ("Add to Home Screen" from the deployed web app). Use this doc when you want a single file you can hand someone who doesn't trust hosted apps or who isn't online when they need the tool. The PWA path stays the recommended option for most users.
|
||||
|
||||
|
|
@ -250,11 +260,47 @@ Before calling the APK path shippable, decide: accept the `Downloads/` behavior
|
|||
- **Stock Android 7+:** System WebView auto-updates via Play Store's WebView component. Fine.
|
||||
- **LineageOS:** Does _not_ bundle a WebView; the user must install one manually (Chromium WebView from F-Droid, or Mulch from DivestOS repos). If the user has an outdated WebView, WASM may fail. Worth a one-time test before distributing to a LineageOS audience.
|
||||
- **Android 5–6:** API level below Capacitor 7's minimum (API 23); not supported.
|
||||
- **CI integration:** Not in scope for the initial pass. An Android toolchain job would add ~10 min to the matrix (Linux runner + `cmdline-tools` + `sdkmanager`). Defer until there's a real release cadence for the APK.
|
||||
- **CI integration:** Shipped as a manual workflow — see [§CI](#ci) below.
|
||||
- **`android/` repo decision:** The `android/` directory is ~10–15 MB on disk after `cap add android`. Whether to commit it to `master` or keep it on a separate build branch / sibling repo is worth deciding before the first PR lands.
|
||||
|
||||
---
|
||||
|
||||
## CI
|
||||
|
||||
`.github/workflows/build-android.yml` builds the debug APK on demand. Trigger it from the Forgejo Actions tab → "Build Android APK" → "Run workflow".
|
||||
|
||||
### Two execution paths
|
||||
|
||||
The workflow has a `use_prebaked_image` boolean dispatch input that toggles between a fast path and a portable fallback.
|
||||
|
||||
| Mode | Container | JDK + Android SDK | Cold time |
|
||||
| --- | --- | --- | --- |
|
||||
| `use_prebaked_image = true` (default) | `forgejo-stack/job-android:latest` | Baked into the image | ~3–5 min |
|
||||
| `use_prebaked_image = false` | Runner default | `actions/setup-java@v4` + `android-actions/setup-android@v3` | ~10–15 min |
|
||||
|
||||
The default fast path requires the `forgejo-stack/job-android:latest` image on the runner host (built by `forgejo-stack`'s `setup.sh`; see `runner-image-android/Dockerfile`). The fallback works on any runner with Node + docker + apt — GitHub-hosted runners, vanilla `act_runner`, or a forgejo-stack instance without the optional Android image built.
|
||||
|
||||
The workflow itself has a **soft** dependency on forgejo-stack: the file checks into the repo regardless, and you opt out of the pre-baked image by unchecking the dispatch input. There is no hard repo-to-infra coupling.
|
||||
|
||||
### Steps (fast path)
|
||||
|
||||
1. Checks out the branch you select.
|
||||
2. Installs Node 22 (JDK 17 + Android SDK already in the container).
|
||||
3. Runs `yarn install --frozen-lockfile` and `yarn build:web`.
|
||||
4. Syncs `dist/web/` into `android/app/src/main/assets/public/` via `npx cap sync android`.
|
||||
5. Runs `./gradlew assembleDebug --no-daemon --stacktrace`.
|
||||
6. Uploads `app-debug.apk` as a workflow artifact named `metascrub-debug-apk` (retained 30 days).
|
||||
|
||||
The fallback path additionally runs `setup-java@v4` + `android-actions/setup-android@v3` before step 4.
|
||||
|
||||
**Trigger:** `workflow_dispatch` only — no push, no PR, no schedule. The action is run by hand when an APK build is needed; it is not part of the per-PR CI matrix and does not block merges.
|
||||
|
||||
**Why not on push/PR:** Android SDK install + Gradle build add ~5–10 min per run on a cold cache, with no upside for the normal CI path (web build, lint, typecheck, unit tests, e2e are all platform-agnostic). The APK only needs to be rebuilt when there is something to distribute.
|
||||
|
||||
**Signing:** Debug APK only — sufficient for personal sideloading and forensic verification. Release signing is out of scope for this iteration (it would require a keystore committed as a CI secret); see the "Signed release APK" section above for the manual path.
|
||||
|
||||
---
|
||||
|
||||
## Files this approach adds to the repo
|
||||
|
||||
After running the one-time setup, the worktree picks up:
|
||||
|
|
|
|||
678
docs/superpowers/plans/2026-05-17-android-apk-and-ci.md
Normal file
|
|
@ -0,0 +1,678 @@
|
|||
# Android APK + On-Demand CI Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans (inline) — the user is away and asked for end-to-end execution. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Wrap `dist/web/` as a sideloadable Android APK via Capacitor v7, with a manually-triggered CI workflow that builds the debug APK and uploads it as a workflow artifact.
|
||||
|
||||
**Architecture:** Capacitor v7 generates an Android Studio project at `android/` that hosts a system WebView. The WebView serves bundled assets via `https://localhost/` (Capacitor's interceptor), so the existing JS/TS processing engine, CSP, and privacy invariants are unchanged. A `.github/workflows/build-android.yml` workflow with `workflow_dispatch` trigger only (no push/PR) builds the APK on demand and uploads via `actions/upload-artifact`.
|
||||
|
||||
**Tech Stack:** Capacitor v7 (`@capacitor/core`, `@capacitor/cli`, `@capacitor/android`), JDK 17 (Temurin), Android SDK API 35 + build-tools, Gradle (Android Gradle Plugin auto-selected by Capacitor), GitHub Actions on Forgejo runners.
|
||||
|
||||
---
|
||||
|
||||
## Q1–Q5 decisions (made up front; user is away)
|
||||
|
||||
| Q | Decision | Why |
|
||||
| --- | --- | --- |
|
||||
| Q1 — File output path | **Accept `Downloads/` behavior for v1; document Android ≤9 world-readable as a gap in `PRIVACY_GAPS.md`** | Keeps scope tight, no renderer changes, no new prod deps. Android 10+ scoped storage already mitigates this. The adapter approach (`@capacitor/filesystem`) is a separate, larger change. |
|
||||
| Q2 — Back button | **Use Capacitor default (close on root)** | App is single-screen. No nav stack to traverse. |
|
||||
| Q3 — `android/` placement | **Commit to `master`** | Project pattern: all build configs live in master. ~10–15 MB on disk is fine. Splitting across repos adds operational complexity for marginal benefit. |
|
||||
| Q4 — CI | **In scope. `workflow_dispatch` only (no push/PR/schedule trigger)** | Per user: "action will be started manually not on merge or PR." |
|
||||
| Q5 — Min API 23 | **Accept (Capacitor 7 default)** | Covers 99%+ of active devices. |
|
||||
|
||||
Decisions are reflected in `docs/android-apk.md` updates (Task 10) and in the issue #153 status update (Task 12).
|
||||
|
||||
---
|
||||
|
||||
## File structure
|
||||
|
||||
| File | Responsibility |
|
||||
| --- | --- |
|
||||
| `capacitor.config.ts` (new, root) | Capacitor app id, app name, web dir, `server.androidScheme = 'https'` |
|
||||
| `package.json` (modified) | Add `@capacitor/*` to `devDependencies`; add `android:sync` + `android:build` scripts |
|
||||
| `yarn.lock` (modified) | Lock new deps |
|
||||
| `android/` (new, generated) | Full Android Studio project (Gradle, AndroidManifest, app/, gradle wrapper) |
|
||||
| `android/.gitignore` (new, generated then audited) | Excludes `.gradle/`, `build/`, `.idea/`, `local.properties`, `app/release/` |
|
||||
| `android/app/src/main/res/values/strings.xml` (modified) | `app_name = "MetaScrub"` |
|
||||
| `android/app/build.gradle` (modified) | `versionName = "5.0.0"` aligned with `package.json` `version` |
|
||||
| `.github/workflows/build-android.yml` (new) | `workflow_dispatch` trigger; setup-node + setup-java + setup-android; `yarn build:web && npx cap sync android && gradlew assembleDebug`; `upload-artifact` |
|
||||
| `docs/android-apk.md` (modified) | Document Q1–Q5 decisions; move "CI integration" from "Known issues before shipping" to a new "CI" subsection |
|
||||
| `docs/PRIVACY_GAPS.md` (modified) | Add "Android Downloads/ folder — world-readable on Android ≤9" subsection |
|
||||
| `.gitignore` (modified, root) | Ensure `dist/`, `node_modules/` already cover `dist/web/` (no new entry needed); verify after `cap add` |
|
||||
|
||||
---
|
||||
|
||||
## Tasks
|
||||
|
||||
### Task 1: Install Capacitor v7 devDependencies
|
||||
|
||||
**Files:**
|
||||
- Modify: `package.json` (devDependencies)
|
||||
- Modify: `yarn.lock`
|
||||
|
||||
- [ ] **Step 1: Add Capacitor packages**
|
||||
|
||||
```bash
|
||||
yarn add -D @capacitor/core@^7 @capacitor/cli@^7 @capacitor/android@^7
|
||||
```
|
||||
|
||||
Expected output: ends with "Done in Xs."
|
||||
|
||||
- [ ] **Step 2: Verify CLI is reachable and on v7**
|
||||
|
||||
```bash
|
||||
npx cap --version
|
||||
```
|
||||
|
||||
Expected: `7.x.y` (any 7.x patch is fine).
|
||||
|
||||
- [ ] **Step 3: Verify devDependencies block in package.json**
|
||||
|
||||
Confirm `@capacitor/core`, `@capacitor/cli`, `@capacitor/android` all listed with `^7.` prefix; no entries leaked into `dependencies`.
|
||||
|
||||
- [ ] **Step 4: Sanity-check the existing CI is unaffected**
|
||||
|
||||
```bash
|
||||
yarn typecheck && yarn lint
|
||||
```
|
||||
|
||||
Expected: both pass with no errors. Capacitor packages do not affect the renderer build.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add package.json yarn.lock
|
||||
git commit -m "chore(android): add Capacitor v7 devDependencies (refs #153)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Initialize capacitor.config.ts
|
||||
|
||||
**Files:**
|
||||
- Create: `capacitor.config.ts`
|
||||
|
||||
- [ ] **Step 1: Run cap init non-interactively**
|
||||
|
||||
```bash
|
||||
npx cap init MetaScrub com.metascrub.app --web-dir=dist/web
|
||||
```
|
||||
|
||||
Capacitor 7 may emit a telemetry prompt. If it asks `Create config in TypeScript? (Y/n)` answer `Y` (default). If a telemetry consent question appears, answer `N` (privacy invariant §5 — we do not phone home, ever).
|
||||
|
||||
Expected output: "✔ Creating capacitor.config.ts in ..." plus the path.
|
||||
|
||||
- [ ] **Step 2: Inspect the generated config**
|
||||
|
||||
```bash
|
||||
cat capacitor.config.ts
|
||||
```
|
||||
|
||||
Expected: a `CapacitorConfig` object with `appId: 'com.metascrub.app'`, `appName: 'MetaScrub'`, `webDir: 'dist/web'`.
|
||||
|
||||
- [ ] **Step 3: Add `server.androidScheme: 'https'` explicitly**
|
||||
|
||||
Final content of `capacitor.config.ts`:
|
||||
|
||||
```typescript
|
||||
import type { CapacitorConfig } from '@capacitor/cli';
|
||||
|
||||
const config: CapacitorConfig = {
|
||||
appId: 'com.metascrub.app',
|
||||
appName: 'MetaScrub',
|
||||
webDir: 'dist/web',
|
||||
server: {
|
||||
androidScheme: 'https',
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
`server.androidScheme: 'https'` is the Capacitor 7 default; setting it explicitly guards against silent regression. Without `https://`, WASM instantiation will fail (Chrome enforces secure-context for WASM and service workers).
|
||||
|
||||
- [ ] **Step 4: Verify Prettier formatting (tabs, not spaces)**
|
||||
|
||||
```bash
|
||||
yarn prettier --check capacitor.config.ts
|
||||
```
|
||||
|
||||
Expected: pass. If it fails, run `yarn prettier --write capacitor.config.ts`.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add capacitor.config.ts
|
||||
git commit -m "chore(android): add capacitor.config.ts with https scheme (refs #153)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Build web bundle (needed before cap add android)
|
||||
|
||||
**Files:** None modified.
|
||||
|
||||
- [ ] **Step 1: Build the web bundle**
|
||||
|
||||
```bash
|
||||
yarn build:web
|
||||
```
|
||||
|
||||
Expected: output ends with "✓ built in Xs". A `dist/web/index.html` is produced along with `dist/web/assets/` and `dist/web/sw.js`.
|
||||
|
||||
- [ ] **Step 2: Verify the output exists**
|
||||
|
||||
```bash
|
||||
ls dist/web/index.html dist/web/assets dist/web/sw.js
|
||||
```
|
||||
|
||||
Expected: all three present.
|
||||
|
||||
(No commit — this is a build artifact, ignored by `.gitignore`.)
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Add Android platform
|
||||
|
||||
**Files:**
|
||||
- Create: `android/` (entire Android Studio project tree, ~10–15 MB on disk)
|
||||
|
||||
- [ ] **Step 1: Generate the Android project**
|
||||
|
||||
```bash
|
||||
npx cap add android
|
||||
```
|
||||
|
||||
This downloads the Capacitor Android template and copies `dist/web/` into `android/app/src/main/assets/public/`. Expected output ends with "Sync finished in Xs".
|
||||
|
||||
- [ ] **Step 2: Verify the project tree was created**
|
||||
|
||||
```bash
|
||||
ls android/app/src/main/AndroidManifest.xml \
|
||||
android/app/build.gradle \
|
||||
android/build.gradle \
|
||||
android/settings.gradle \
|
||||
android/gradlew \
|
||||
android/.gitignore
|
||||
```
|
||||
|
||||
Expected: all six files exist.
|
||||
|
||||
- [ ] **Step 3: Verify the web assets were synced**
|
||||
|
||||
```bash
|
||||
ls android/app/src/main/assets/public/index.html
|
||||
```
|
||||
|
||||
Expected: file exists (copied from `dist/web/`).
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Audit `.gitignore` for android/
|
||||
|
||||
**Files:**
|
||||
- Modify (verify and patch): `android/.gitignore`
|
||||
|
||||
Capacitor generates an `android/.gitignore` automatically. We verify it covers all the entries listed in `docs/android-apk.md` and patch if missing.
|
||||
|
||||
- [ ] **Step 1: Inspect Capacitor's generated gitignore**
|
||||
|
||||
```bash
|
||||
cat android/.gitignore
|
||||
```
|
||||
|
||||
The Capacitor 7 template ships with most of the right entries. Required entries:
|
||||
|
||||
```
|
||||
.gradle/
|
||||
build/
|
||||
app/build/
|
||||
.idea/
|
||||
local.properties
|
||||
captures/
|
||||
*.iml
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
app/release/
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Patch `android/.gitignore` if any required entry is missing**
|
||||
|
||||
If any of the entries above are missing, append them. Specifically `app/release/` (release keystores) is the security-critical one — never commit a signing keystore.
|
||||
|
||||
- [ ] **Step 3: Verify nothing in `android/build/` or `android/.gradle/` would be committed**
|
||||
|
||||
```bash
|
||||
git status android/ | head -20
|
||||
git check-ignore -v android/app/build/ android/.gradle/ android/local.properties 2>/dev/null || echo "(some paths don't exist yet, that's fine)"
|
||||
```
|
||||
|
||||
Expected: `git status` does not show entries inside `android/.gradle/` or `android/app/build/` (or those paths don't exist yet).
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Set app metadata (name + version)
|
||||
|
||||
**Files:**
|
||||
- Modify: `android/app/src/main/res/values/strings.xml`
|
||||
- Modify: `android/app/build.gradle` (for `versionName`)
|
||||
|
||||
- [ ] **Step 1: Inspect generated strings.xml**
|
||||
|
||||
```bash
|
||||
cat android/app/src/main/res/values/strings.xml
|
||||
```
|
||||
|
||||
Capacitor sets `<string name="app_name">MetaScrub</string>` based on the `cap init` name. If it instead shows `com.metascrub.app` (the appId fallback), patch it.
|
||||
|
||||
- [ ] **Step 2: Confirm `app_name`, `title_activity_main`, and `package_name` are all MetaScrub-flavoured**
|
||||
|
||||
Final strings.xml should look approximately like:
|
||||
|
||||
```xml
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="app_name">MetaScrub</string>
|
||||
<string name="title_activity_main">MetaScrub</string>
|
||||
<string name="package_name">com.metascrub.app</string>
|
||||
<string name="custom_url_scheme">com.metascrub.app</string>
|
||||
</resources>
|
||||
```
|
||||
|
||||
If any value is wrong, edit the file directly.
|
||||
|
||||
- [ ] **Step 3: Align `versionName` in `android/app/build.gradle` with `package.json` `version`**
|
||||
|
||||
`package.json` currently has `"version": "4.0.0"`. Open `android/app/build.gradle`, find the `defaultConfig` block, and set:
|
||||
|
||||
```
|
||||
versionCode 1
|
||||
versionName "4.0.0"
|
||||
```
|
||||
|
||||
(versionCode is monotonic; this is the first APK build.)
|
||||
|
||||
- [ ] **Step 4: Verify the manifest references these**
|
||||
|
||||
```bash
|
||||
grep -E "applicationId|versionCode|versionName" android/app/build.gradle
|
||||
```
|
||||
|
||||
Expected: `applicationId "com.metascrub.app"`, `versionCode 1`, `versionName "4.0.0"`.
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Commit the android/ tree
|
||||
|
||||
**Files:**
|
||||
- Stage: `android/` (everything not gitignored)
|
||||
- Stage: `capacitor.config.ts` (already committed in Task 2, no-op)
|
||||
|
||||
- [ ] **Step 1: Verify what would be staged**
|
||||
|
||||
```bash
|
||||
git status android/ | head -40
|
||||
```
|
||||
|
||||
Expected: a list of files including `android/build.gradle`, `android/app/build.gradle`, `android/app/src/main/AndroidManifest.xml`, the gradle wrapper jar, etc. Should NOT include `.gradle/` or `build/`.
|
||||
|
||||
- [ ] **Step 2: Stage the android/ tree**
|
||||
|
||||
```bash
|
||||
git add android/
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Quick check of the staged file count**
|
||||
|
||||
```bash
|
||||
git diff --staged --name-only | wc -l
|
||||
```
|
||||
|
||||
Expected: somewhere between ~30 and ~80 files. If wildly larger (hundreds+), something escaped `.gitignore` — pause and inspect.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "feat(android): add Capacitor Android project (refs #153)
|
||||
|
||||
- Capacitor 7 generated android/ Gradle project (API 23 min, API 35 target)
|
||||
- App id: com.metascrub.app; display name: MetaScrub
|
||||
- versionCode 1 / versionName 4.0.0 (matches package.json)
|
||||
- .gitignore excludes .gradle/, build/, .idea/, local.properties, release keystores"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8: Add package.json scripts
|
||||
|
||||
**Files:**
|
||||
- Modify: `package.json` `scripts`
|
||||
|
||||
- [ ] **Step 1: Add two helper scripts**
|
||||
|
||||
Add after the existing `"build:web:standalone"` line:
|
||||
|
||||
```json
|
||||
"android:sync": "yarn build:web && npx cap sync android",
|
||||
"android:build": "yarn android:sync && cd android && ./gradlew assembleDebug",
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify JSON is still valid**
|
||||
|
||||
```bash
|
||||
node -e "JSON.parse(require('fs').readFileSync('package.json'))"
|
||||
```
|
||||
|
||||
Expected: no output (success).
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add package.json
|
||||
git commit -m "chore(android): add android:sync + android:build npm scripts (refs #153)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 9: Add `.github/workflows/build-android.yml`
|
||||
|
||||
**Files:**
|
||||
- Create: `.github/workflows/build-android.yml`
|
||||
|
||||
- [ ] **Step 1: Write the workflow file**
|
||||
|
||||
Full content of `.github/workflows/build-android.yml`:
|
||||
|
||||
```yaml
|
||||
name: Build Android APK
|
||||
|
||||
# Manual trigger only. Per issue #153: "the action will be started
|
||||
# manually not on merge or PR." No push/pull_request/schedule.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: build-android-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build-apk:
|
||||
name: Build debug APK
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build web bundle
|
||||
run: yarn build:web
|
||||
|
||||
- name: Setup JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '17'
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
with:
|
||||
packages: 'platforms;android-35 build-tools;35.0.0'
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: gradle-${{ runner.os }}-${{ hashFiles('android/**/*.gradle*', 'android/gradle/wrapper/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
gradle-${{ runner.os }}-
|
||||
|
||||
- name: Capacitor sync
|
||||
run: npx cap sync android
|
||||
|
||||
- name: Build debug APK
|
||||
working-directory: android
|
||||
run: ./gradlew assembleDebug --no-daemon --stacktrace
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: metascrub-debug-apk
|
||||
path: android/app/build/outputs/apk/debug/app-debug.apk
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
```
|
||||
|
||||
Notes for reviewers:
|
||||
- `workflow_dispatch` only — no auto-trigger.
|
||||
- `setup-android@v3` accepts SDK licenses non-interactively and installs the requested packages.
|
||||
- `assembleDebug` produces an unsigned debug APK (fine for sideloading; release signing needs a keystore that lives outside CI for now).
|
||||
- Artifact retention is 30 days (default) — adjust later if needed.
|
||||
|
||||
- [ ] **Step 2: Verify YAML parses**
|
||||
|
||||
```bash
|
||||
yarn dlx js-yaml .github/workflows/build-android.yml >/dev/null && echo OK || echo "YAML PARSE FAILED"
|
||||
```
|
||||
|
||||
If `yarn dlx js-yaml` is not available, use Python:
|
||||
|
||||
```bash
|
||||
python3 -c "import yaml,sys; yaml.safe_load(open('.github/workflows/build-android.yml')); print('OK')"
|
||||
```
|
||||
|
||||
Expected: `OK`.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add .github/workflows/build-android.yml
|
||||
git commit -m "ci(android): add workflow_dispatch APK build (refs #153)
|
||||
|
||||
Manual trigger only — per issue #153, the action is run on demand
|
||||
rather than on each merge/PR. Builds dist/web/, syncs into Capacitor,
|
||||
runs gradlew assembleDebug, uploads app-debug.apk as a workflow artifact."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 10: Update docs/android-apk.md with decisions
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/android-apk.md`
|
||||
|
||||
- [ ] **Step 1: Add a "Decisions" subsection near the top**
|
||||
|
||||
Insert after the TL;DR (around line 14, just before "Why an APK..."):
|
||||
|
||||
```markdown
|
||||
## Decisions (issue #153 v1)
|
||||
|
||||
| Q | Decision | Rationale |
|
||||
| --- | --- | --- |
|
||||
| File output path | `<a download>` → system `Downloads/` (accepted) | Documented as a known limitation on Android ≤9 in [`PRIVACY_GAPS.md`](PRIVACY_GAPS.md). Android 10+ scoped storage mitigates. A future `@capacitor/filesystem` adapter is the upgrade path. |
|
||||
| Back button | Capacitor default (close on root) | Single-screen app; no nav stack. |
|
||||
| `android/` placement | Committed to `master` | Matches project pattern (everything in one repo). ~12 MB on disk. |
|
||||
| CI | `workflow_dispatch` only | Run manually, never on push/PR. See `.github/workflows/build-android.yml`. |
|
||||
| Min API level | 23 (Capacitor 7 default) | Covers 99%+ of devices; WASM secure-context requirement is well within range. |
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Replace the "CI integration" line under "Known issues before shipping" with a "CI" section**
|
||||
|
||||
Find the bullet that reads:
|
||||
```
|
||||
- **CI integration:** Not in scope for the initial pass. An Android toolchain job would add ~10 min to the matrix (Linux runner + `cmdline-tools` + `sdkmanager`). Defer until there's a real release cadence for the APK.
|
||||
```
|
||||
|
||||
Replace it with a forward-reference to the new section:
|
||||
|
||||
```
|
||||
- **CI integration:** Shipped as a manual workflow — see [§CI](#ci) below.
|
||||
```
|
||||
|
||||
Then add a new section before "## Files this approach adds to the repo":
|
||||
|
||||
```markdown
|
||||
## CI
|
||||
|
||||
`.github/workflows/build-android.yml` builds the debug APK on demand. Trigger it from the Forgejo Actions tab → "Build Android APK" → "Run workflow". The workflow:
|
||||
|
||||
1. Checks out the branch you select.
|
||||
2. Installs Node 22 and JDK 17.
|
||||
3. Runs `yarn install --frozen-lockfile` and `yarn build:web`.
|
||||
4. Installs Android SDK (API 35 + build-tools 35.0.0) via `android-actions/setup-android@v3`.
|
||||
5. Syncs `dist/web/` into `android/app/src/main/assets/public/` via `npx cap sync android`.
|
||||
6. Runs `./gradlew assembleDebug --no-daemon --stacktrace`.
|
||||
7. Uploads `app-debug.apk` as a workflow artifact named `metascrub-debug-apk` (retained 30 days).
|
||||
|
||||
**Trigger:** `workflow_dispatch` only — no push, no PR, no schedule. The action is run by hand when an APK build is needed; it is not part of the per-PR CI matrix and does not block merges.
|
||||
|
||||
**Why not on push/PR:** Android SDK install + Gradle build add ~5–10 min per run on a cold cache, with no upside for the normal CI path (web build, lint, typecheck, unit tests, e2e are all platform-agnostic). The APK only needs to be rebuilt when there is something to distribute.
|
||||
|
||||
**Signing:** Debug APK only — sufficient for personal sideloading and forensic verification. Release signing is out of scope for this iteration (would require keystore + CI secret); see the "Signed release APK" section above for the manual path.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify the doc still reads coherently**
|
||||
|
||||
```bash
|
||||
yarn prettier --check docs/android-apk.md
|
||||
```
|
||||
|
||||
If prettier is configured for markdown, expect pass. If not, no-op.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/android-apk.md
|
||||
git commit -m "docs(android): document Q1-Q5 decisions and add CI section (refs #153)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 11: Update PRIVACY_GAPS.md with Android Downloads/ note
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/PRIVACY_GAPS.md`
|
||||
|
||||
- [ ] **Step 1: Add a new top-level section near the xattr entry**
|
||||
|
||||
Insert after the `## macOS extended file attributes (xattr) — lost in Phase G` block (ends around line 41), before `## RAW unsupported`:
|
||||
|
||||
```markdown
|
||||
## Android `Downloads/` — world-readable on Android ≤9
|
||||
|
||||
**Decided 2026-05-17 (issue #153).** The Capacitor APK uses the system WebView; `<a download>` invocations route through Android's DownloadManager, which writes the cleaned file to the public `Downloads/` folder. On Android 9 and below, that folder is world-readable — any other app on the device with `READ_EXTERNAL_STORAGE` (granted at install for legacy apps) can enumerate and read the cleaned file.
|
||||
|
||||
**What may leak:** the cleaned file's *existence and contents* to other installed apps with broad storage permissions. The metadata strip itself is unaffected — the file is byte-equivalent to what the user would get from the web build. The leak is "another app on the same device can read the file you just saved," not "the metadata wasn't actually stripped."
|
||||
|
||||
**Affected versions:** Android 6.0–9.0 (API 23–28). Android 10+ (API 29+) introduced scoped storage; the cleaned file is isolated to the app's Downloads entry and not world-readable.
|
||||
|
||||
**What you can do:**
|
||||
- Use Android 10 or later if available (scoped storage applies automatically).
|
||||
- Move the cleaned file to your app-private storage immediately after the strip (e.g. into Signal's media folder before sharing).
|
||||
- For users on Android ≤9 who need stronger isolation, the PWA path (Add to Home Screen + the browser's download flow) has the same behavior — this isn't an APK-specific regression; it's a property of the platform's pre-scoped-storage filesystem model.
|
||||
|
||||
**What MetaScrub could do (deferred):** a Capacitor-aware adapter in `web_api.ts` (detectable via `typeof Capacitor !== 'undefined'`) using `@capacitor/filesystem` to write into the app's private external storage with an explicit location picker for export. This is a one-issue follow-up; not implemented in the v1 APK pass to keep the patch small.
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/PRIVACY_GAPS.md
|
||||
git commit -m "docs(privacy): document Android Downloads/ world-readable gap on Android <=9 (refs #153)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 12: Push branch + open PR + update issue
|
||||
|
||||
**Files:** None modified.
|
||||
|
||||
- [ ] **Step 1: Push the branch to forgejo**
|
||||
|
||||
```bash
|
||||
git push -u forgejo feat/android-apk-issue-153
|
||||
```
|
||||
|
||||
Expected: success, returns the new remote ref.
|
||||
|
||||
- [ ] **Step 2: Open a PR via Forgejo API**
|
||||
|
||||
```bash
|
||||
TOKEN=$(docker inspect forgejo-stack-forgejo-mcp-1 --format '{{range .Config.Env}}{{println .}}{{end}}' | grep FORGEJOMCP_TOKEN | cut -d= -f2)
|
||||
|
||||
curl -s -X POST \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
http://localhost:3000/api/v1/repos/forgejo_admin/exifcleaner-web/pulls \
|
||||
-d '{
|
||||
"title": "feat(android): Capacitor APK wrapper + on-demand CI build",
|
||||
"body": "Closes #153.\n\n## What\n\n- Capacitor v7 scaffold producing a sideloadable debug APK from the existing `dist/web/` bundle\n- `.github/workflows/build-android.yml` — manually-triggered (`workflow_dispatch` only) CI workflow that builds the APK and uploads it as a workflow artifact\n- Docs updated with Q1-Q5 decisions; PRIVACY_GAPS.md updated with the Android ≤ 9 Downloads/ world-readable gap\n\n## Decisions (Q1-Q5 from issue #153)\n\n- **Q1 (file output):** Accept system `Downloads/` for v1; document as a privacy gap on Android ≤ 9\n- **Q2 (back button):** Capacitor default (close on root)\n- **Q3 (android/ placement):** Committed to `master`\n- **Q4 (CI):** In scope; `workflow_dispatch` only (per founder instruction: \"started manually not on merge or PR\")\n- **Q5 (API 23):** Accepted\n\n## How to test\n\n1. Merge this PR.\n2. From the Forgejo Actions UI, dispatch the **Build Android APK** workflow against `master`.\n3. Download the `metascrub-debug-apk` artifact and sideload onto a real Android device.\n4. Verify: file picker works, JPEG/PNG/PDF strip works, cleaned file lands in Downloads/.\n5. Verify no network traffic during processing (mitmproxy or platform-side firewall).\n\nFull approach + AAR feasibility verdict in `docs/android-apk.md`.",
|
||||
"head": "feat/android-apk-issue-153",
|
||||
"base": "master"
|
||||
}' \
|
||||
-o /tmp/pr-created.json \
|
||||
&& jq -r '"PR #\(.number): \(.html_url)"' /tmp/pr-created.json
|
||||
```
|
||||
|
||||
Expected: `PR #N: http://localhost:3000/.../pulls/N`.
|
||||
|
||||
- [ ] **Step 3: Add a status comment to issue #153**
|
||||
|
||||
```bash
|
||||
curl -s -X POST \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
http://localhost:3000/api/v1/repos/forgejo_admin/exifcleaner-web/issues/153/comments \
|
||||
-d '{
|
||||
"body": "Implementation PR opened. Q1-Q5 decisions made up front (see PR description). CI is now in scope as a manually-triggered `workflow_dispatch` workflow per founder direction."
|
||||
}' \
|
||||
-o /tmp/comment.json \
|
||||
&& jq -r '"comment id \(.id)"' /tmp/comment.json
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Print final summary**
|
||||
|
||||
Echo PR URL and the workflow path for the user to dispatch when they're back.
|
||||
|
||||
---
|
||||
|
||||
## Self-review notes
|
||||
|
||||
After writing this plan, run the self-review checklist before execution:
|
||||
|
||||
1. **Spec coverage** — every "Done looks like" checkbox in issue #153 maps to a task above?
|
||||
- `capacitor.config.ts` + `android/` committed with gitignore → Tasks 2, 5, 7
|
||||
- `androidScheme: 'https'` set → Task 2
|
||||
- Build command produces APK → Task 9 (verified in CI; no local Java/Android SDK available)
|
||||
- APK installs on real device → out of scope for this PR (user verifies post-merge)
|
||||
- Back button behavior → Q2 decision documented (Task 10)
|
||||
- File output path decision → Q1 decision documented (Tasks 10, 11)
|
||||
- Privacy re-verified (CSP unchanged) → no `vite.config.web.ts` change; CSP unchanged. Confirmed by audit during Task 2.
|
||||
- `docs/android-apk.md` updated → Task 10
|
||||
- CI in scope → Task 9
|
||||
2. **Placeholder scan** — no TBD/TODO; all commands literal; no "similar to Task N" references.
|
||||
3. **Type consistency** — npm script names (`android:sync`, `android:build`) reused consistently; workflow file path used consistently.
|
||||
4. **Scope check** — single subsystem (Android wrapper + its CI). One PR.
|
||||
|
||||
---
|
||||
|
||||
## Out of scope (explicitly deferred)
|
||||
|
||||
- iOS (`npx cap add ios`) — separate story
|
||||
- Release-signed APK — needs keystore as CI secret; do after first manual debug build is verified
|
||||
- `@capacitor/filesystem` adapter for `Downloads/` privacy mitigation — documented in PRIVACY_GAPS.md as a known gap with the follow-up path noted
|
||||
- Splash screen / icon polish (`@capacitor/splash-screen`)
|
||||
- F-Droid submission
|
||||
- Real-device install verification — the user runs this post-merge with the artifact built by CI
|
||||
|
|
@ -13,6 +13,8 @@
|
|||
"build": "vite build --config vite.config.web.ts",
|
||||
"build:web": "vite build --config vite.config.web.ts",
|
||||
"build:web:standalone": "vite build --config vite.config.web.standalone.ts",
|
||||
"android:sync": "yarn build:web && npx cap sync android",
|
||||
"android:build": "yarn android:sync && cd android && ./gradlew assembleDebug",
|
||||
"preview": "vite preview --config vite.config.web.ts",
|
||||
"preview:web": "vite preview --config vite.config.web.ts",
|
||||
"typecheck": "tsc --noEmit",
|
||||
|
|
@ -34,6 +36,9 @@
|
|||
"react-dom": "^19.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/android": "^7",
|
||||
"@capacitor/cli": "^7",
|
||||
"@capacitor/core": "^7",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@types/node": "^22",
|
||||
"@types/react": "^19.2.0",
|
||||
|
|
|
|||
96
scripts/generate_android_assets.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate Android launcher icons + splash screens from .resources/icon.png.
|
||||
|
||||
Run from repo root: `python3 scripts/generate_android_assets.py`.
|
||||
|
||||
Replaces Capacitor's default placeholders with the MetaScrub icon. Pure stdlib
|
||||
+ Pillow (already a Python dev dep we get from the system; no project-level
|
||||
addition).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
SOURCE_ICON = REPO_ROOT / ".resources/icon.png"
|
||||
RES = REPO_ROOT / "android/app/src/main/res"
|
||||
|
||||
LAUNCHER_SIZES = {
|
||||
"mdpi": 48,
|
||||
"hdpi": 72,
|
||||
"xhdpi": 96,
|
||||
"xxhdpi": 144,
|
||||
"xxxhdpi": 192,
|
||||
}
|
||||
|
||||
SPLASH_PORTRAIT = {
|
||||
"mdpi": (320, 480),
|
||||
"hdpi": (480, 800),
|
||||
"xhdpi": (720, 1280),
|
||||
"xxhdpi": (960, 1600),
|
||||
"xxxhdpi": (1280, 1920),
|
||||
}
|
||||
|
||||
SPLASH_BG = (26, 26, 26, 255)
|
||||
SPLASH_ICON_FRACTION = 0.30
|
||||
|
||||
|
||||
def write_launcher_icons(source: Image.Image) -> None:
|
||||
for density, size in LAUNCHER_SIZES.items():
|
||||
out_dir = RES / f"mipmap-{density}"
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
square = source.resize((size, size), Image.LANCZOS)
|
||||
square.save(out_dir / "ic_launcher.png", "PNG")
|
||||
|
||||
round_icon = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
||||
mask = Image.new("L", (size, size), 0)
|
||||
ImageDraw.Draw(mask).ellipse((0, 0, size - 1, size - 1), fill=255)
|
||||
round_icon.paste(square, (0, 0), mask)
|
||||
round_icon.save(out_dir / "ic_launcher_round.png", "PNG")
|
||||
|
||||
|
||||
def write_splashes(source: Image.Image) -> None:
|
||||
for density, (w, h) in SPLASH_PORTRAIT.items():
|
||||
for orientation, (sw, sh) in (("port", (w, h)), ("land", (h, w))):
|
||||
out_dir = RES / f"drawable-{orientation}-{density}"
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
canvas = Image.new("RGBA", (sw, sh), SPLASH_BG)
|
||||
icon_side = int(min(sw, sh) * SPLASH_ICON_FRACTION)
|
||||
icon = source.resize((icon_side, icon_side), Image.LANCZOS)
|
||||
canvas.paste(icon, ((sw - icon_side) // 2, (sh - icon_side) // 2), icon)
|
||||
canvas.save(out_dir / "splash.png", "PNG")
|
||||
|
||||
|
||||
def write_drawable_splash(source: Image.Image) -> None:
|
||||
out = RES / "drawable" / "splash.png"
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
canvas = Image.new("RGBA", (480, 800), SPLASH_BG)
|
||||
icon_side = int(480 * SPLASH_ICON_FRACTION)
|
||||
icon = source.resize((icon_side, icon_side), Image.LANCZOS)
|
||||
canvas.paste(icon, ((480 - icon_side) // 2, (800 - icon_side) // 2), icon)
|
||||
canvas.save(out, "PNG")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not SOURCE_ICON.exists():
|
||||
raise SystemExit(f"missing source icon: {SOURCE_ICON}")
|
||||
|
||||
source = Image.open(SOURCE_ICON).convert("RGBA")
|
||||
if source.size != (1024, 1024):
|
||||
print(f"warning: source is {source.size}, expected 1024x1024")
|
||||
|
||||
write_launcher_icons(source)
|
||||
write_splashes(source)
|
||||
write_drawable_splash(source)
|
||||
|
||||
print("Generated launcher icons (mipmap-*/{ic_launcher,ic_launcher_round}.png)")
|
||||
print("Generated splash screens (drawable-{port,land}-*/splash.png + drawable/splash.png)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
475
yarn.lock
|
|
@ -832,6 +832,41 @@
|
|||
"@babel/helper-string-parser" "^7.27.1"
|
||||
"@babel/helper-validator-identifier" "^7.28.5"
|
||||
|
||||
"@capacitor/android@^7":
|
||||
version "7.6.5"
|
||||
resolved "https://registry.yarnpkg.com/@capacitor/android/-/android-7.6.5.tgz#6b43f00dcbdccad9602d4ba84ffd4eacd8611bc9"
|
||||
integrity sha512-FCNSo5bWkqCIuNt2OgG6IUsJJaYlghzbzlFf4IiHkFtir18FCyqHVGHszvtXB+npT7HQ9JQOZ4MQRDsgczKEGQ==
|
||||
|
||||
"@capacitor/cli@^7":
|
||||
version "7.6.5"
|
||||
resolved "https://registry.yarnpkg.com/@capacitor/cli/-/cli-7.6.5.tgz#12138d3e1303f6580bd89b73d2341bd0b314feee"
|
||||
integrity sha512-nlhNoWZIL7YcV1JgNP8+p7WAVIG+zwsY20ZQJ0zd+2lLeCx9MijfELLhLzMCq33c2pILwOvLww8HUqZ5uYCvHg==
|
||||
dependencies:
|
||||
"@ionic/cli-framework-output" "^2.2.8"
|
||||
"@ionic/utils-subprocess" "^3.0.1"
|
||||
"@ionic/utils-terminal" "^2.3.5"
|
||||
commander "^12.1.0"
|
||||
debug "^4.4.0"
|
||||
env-paths "^2.2.0"
|
||||
fs-extra "^11.2.0"
|
||||
kleur "^4.1.5"
|
||||
native-run "^2.0.3"
|
||||
open "^8.4.0"
|
||||
plist "^3.1.0"
|
||||
prompts "^2.4.2"
|
||||
rimraf "^6.0.1"
|
||||
semver "^7.6.3"
|
||||
tar "^7.5.3"
|
||||
tslib "^2.8.1"
|
||||
xml2js "^0.6.2"
|
||||
|
||||
"@capacitor/core@^7":
|
||||
version "7.6.5"
|
||||
resolved "https://registry.yarnpkg.com/@capacitor/core/-/core-7.6.5.tgz#6a33094bfcd2dc2e6c09cf86e2ab844eac2e4c39"
|
||||
integrity sha512-JWyxD9LaK1oY588B/khJIG3Ec4NHI/Ki4v1b4XzqPNt2a27au0F2AhLTwYexV4Bp3Ruxf+8oYqxFAC8/7sGBWA==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
"@dependents/detective-less@^5.0.1":
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@dependents/detective-less/-/detective-less-5.0.1.tgz#e6c5b502f0d26a81da4170c1ccd848a6eaa68470"
|
||||
|
|
@ -970,11 +1005,102 @@
|
|||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz#0eaf705c941a218a43dba8e09f1df1d6cd2f1f17"
|
||||
integrity sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==
|
||||
|
||||
"@ionic/cli-framework-output@^2.2.8":
|
||||
version "2.2.8"
|
||||
resolved "https://registry.yarnpkg.com/@ionic/cli-framework-output/-/cli-framework-output-2.2.8.tgz#29d541acc7773a6aaceec5f3b079937fbcef5402"
|
||||
integrity sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g==
|
||||
dependencies:
|
||||
"@ionic/utils-terminal" "2.3.5"
|
||||
debug "^4.0.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@ionic/utils-array@2.1.6":
|
||||
version "2.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@ionic/utils-array/-/utils-array-2.1.6.tgz#eee863be945ee1a28b9a10ff16fdea776fa18c22"
|
||||
integrity sha512-0JZ1Zkp3wURnv8oq6Qt7fMPo5MpjbLoUoa9Bu2Q4PJuSDWM8H8gwF3dQO7VTeUj3/0o1IB1wGkFWZZYgUXZMUg==
|
||||
dependencies:
|
||||
debug "^4.0.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@ionic/utils-fs@3.1.7", "@ionic/utils-fs@^3.1.7":
|
||||
version "3.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@ionic/utils-fs/-/utils-fs-3.1.7.tgz#e0d41225272c346846867e88a0b84b1a4ee9d9c9"
|
||||
integrity sha512-2EknRvMVfhnyhL1VhFkSLa5gOcycK91VnjfrTB0kbqkTFCOXyXgVLI5whzq7SLrgD9t1aqos3lMMQyVzaQ5gVA==
|
||||
dependencies:
|
||||
"@types/fs-extra" "^8.0.0"
|
||||
debug "^4.0.0"
|
||||
fs-extra "^9.0.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@ionic/utils-object@2.1.6":
|
||||
version "2.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@ionic/utils-object/-/utils-object-2.1.6.tgz#c0259bf925b6c12663d06f6bc1703e5dcb565e6d"
|
||||
integrity sha512-vCl7sl6JjBHFw99CuAqHljYJpcE88YaH2ZW4ELiC/Zwxl5tiwn4kbdP/gxi2OT3MQb1vOtgAmSNRtusvgxI8ww==
|
||||
dependencies:
|
||||
debug "^4.0.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@ionic/utils-process@2.1.12":
|
||||
version "2.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@ionic/utils-process/-/utils-process-2.1.12.tgz#17b05d66201859fe11f53b47be22b85aa90b9556"
|
||||
integrity sha512-Jqkgyq7zBs/v/J3YvKtQQiIcxfJyplPgECMWgdO0E1fKrrH8EF0QGHNJ9mJCn6PYe2UtHNS8JJf5G21e09DfYg==
|
||||
dependencies:
|
||||
"@ionic/utils-object" "2.1.6"
|
||||
"@ionic/utils-terminal" "2.3.5"
|
||||
debug "^4.0.0"
|
||||
signal-exit "^3.0.3"
|
||||
tree-kill "^1.2.2"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@ionic/utils-stream@3.1.7":
|
||||
version "3.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@ionic/utils-stream/-/utils-stream-3.1.7.tgz#224f8c99012aa54e7dbf59950de903b6a61cd811"
|
||||
integrity sha512-eSELBE7NWNFIHTbTC2jiMvh1ABKGIpGdUIvARsNPMNQhxJB3wpwdiVnoBoTYp+5a6UUIww4Kpg7v6S7iTctH1w==
|
||||
dependencies:
|
||||
debug "^4.0.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@ionic/utils-subprocess@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@ionic/utils-subprocess/-/utils-subprocess-3.0.1.tgz#561608fecf432c28fd80f94c1563dc0d092581b7"
|
||||
integrity sha512-cT4te3AQQPeIM9WCwIg8ohroJ8TjsYaMb2G4ZEgv9YzeDqHZ4JpeIKqG2SoaA3GmVQ3sOfhPM6Ox9sxphV/d1A==
|
||||
dependencies:
|
||||
"@ionic/utils-array" "2.1.6"
|
||||
"@ionic/utils-fs" "3.1.7"
|
||||
"@ionic/utils-process" "2.1.12"
|
||||
"@ionic/utils-stream" "3.1.7"
|
||||
"@ionic/utils-terminal" "2.3.5"
|
||||
cross-spawn "^7.0.3"
|
||||
debug "^4.0.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@ionic/utils-terminal@2.3.5", "@ionic/utils-terminal@^2.3.4", "@ionic/utils-terminal@^2.3.5":
|
||||
version "2.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@ionic/utils-terminal/-/utils-terminal-2.3.5.tgz#a48465f40496ee8f29c6d92e4506d5f19762ac3c"
|
||||
integrity sha512-3cKScz9Jx2/Pr9ijj1OzGlBDfcmx7OMVBt4+P1uRR0SSW4cm1/y3Mo4OY3lfkuaYifMNBW8Wz6lQHbs1bihr7A==
|
||||
dependencies:
|
||||
"@types/slice-ansi" "^4.0.0"
|
||||
debug "^4.0.0"
|
||||
signal-exit "^3.0.3"
|
||||
slice-ansi "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
tslib "^2.0.1"
|
||||
untildify "^4.0.0"
|
||||
wrap-ansi "^7.0.0"
|
||||
|
||||
"@isaacs/cliui@^9.0.0":
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-9.0.0.tgz#4d0a3f127058043bf2e7ee169eaf30ed901302f3"
|
||||
integrity sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==
|
||||
|
||||
"@isaacs/fs-minipass@^4.0.0":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32"
|
||||
integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==
|
||||
dependencies:
|
||||
minipass "^7.0.4"
|
||||
|
||||
"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5":
|
||||
version "0.3.13"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f"
|
||||
|
|
@ -1426,6 +1552,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
|
||||
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
|
||||
|
||||
"@types/fs-extra@^8.0.0":
|
||||
version "8.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.5.tgz#33aae2962d3b3ec9219b5aca2555ee00274f5927"
|
||||
integrity sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/node@*", "@types/node@>=20.0.0":
|
||||
version "25.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-25.8.0.tgz#d13033397d1c186876bed4c9b9d7f3f962097eb3"
|
||||
|
|
@ -1457,6 +1590,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975"
|
||||
integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==
|
||||
|
||||
"@types/slice-ansi@^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/slice-ansi/-/slice-ansi-4.0.0.tgz#eb40dfbe3ac5c1de61f6bcb9ed471f54baa989d6"
|
||||
integrity sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ==
|
||||
|
||||
"@types/trusted-types@^2.0.2":
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
|
||||
|
|
@ -1636,6 +1774,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.31.tgz#83276e1d450fea7d20dd15c3bbafbea5aada122d"
|
||||
integrity sha512-nBxuiuS9Lj5bPkPbWogPUnjxxWpkRniX7e5UBQDWl6Fsf4roq9wwV+cR7ezQ4zXswNvPIlsdj1slcLB7XCsRAw==
|
||||
|
||||
"@xmldom/xmldom@^0.9.10":
|
||||
version "0.9.10"
|
||||
resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.9.10.tgz#a0ad5a26fe8aa996310870726e1704977f769dee"
|
||||
integrity sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==
|
||||
|
||||
acorn@^8.15.0:
|
||||
version "8.16.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a"
|
||||
|
|
@ -1656,7 +1799,12 @@ ansi-regex@^5.0.0:
|
|||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
|
||||
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
|
||||
|
||||
ansi-styles@^4.1.0:
|
||||
ansi-regex@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
|
||||
|
||||
ansi-styles@^4.0.0, ansi-styles@^4.1.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
|
||||
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
|
||||
|
|
@ -1704,6 +1852,11 @@ ast-module-types@^6.0.1:
|
|||
resolved "https://registry.yarnpkg.com/ast-module-types/-/ast-module-types-6.0.1.tgz#4b4ca0251c57b815bab62604dcb22f8c903e2523"
|
||||
integrity sha512-WHw67kLXYbZuHTmcdbIrVArCq5wxo6NEuj3hiYAWr8mwJeC+C2mMCIBIWCiDoCye/OF/xelc+teJ1ERoWmnEIA==
|
||||
|
||||
astral-regex@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
|
||||
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
|
||||
|
||||
async-function@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b"
|
||||
|
|
@ -1760,7 +1913,7 @@ balanced-match@^4.0.2:
|
|||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a"
|
||||
integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==
|
||||
|
||||
base64-js@^1.3.1:
|
||||
base64-js@^1.3.1, base64-js@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
|
|
@ -1775,6 +1928,11 @@ baseline-browser-mapping@^2.9.0:
|
|||
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz#3e508c43c46d961eb4d7d2e5b8d1dd0f9ee4f488"
|
||||
integrity sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==
|
||||
|
||||
big-integer@1.6.x:
|
||||
version "1.6.52"
|
||||
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85"
|
||||
integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==
|
||||
|
||||
bl@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
|
||||
|
|
@ -1784,6 +1942,13 @@ bl@^4.1.0:
|
|||
inherits "^2.0.4"
|
||||
readable-stream "^3.4.0"
|
||||
|
||||
bplist-parser@^0.3.2:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.3.2.tgz#3ac79d67ec52c4c107893e0237eb787cbacbced7"
|
||||
integrity sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==
|
||||
dependencies:
|
||||
big-integer "1.6.x"
|
||||
|
||||
brace-expansion@^2.0.1:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.3.tgz#0493338bdd58e319b1039c67cf7ee439892c01d9"
|
||||
|
|
@ -1827,6 +1992,11 @@ browserslist@^4.28.1:
|
|||
node-releases "^2.0.36"
|
||||
update-browserslist-db "^1.2.3"
|
||||
|
||||
buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||
|
|
@ -1913,6 +2083,11 @@ check-error@^2.1.1:
|
|||
resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.3.tgz#2427361117b70cca8dc89680ead32b157019caf5"
|
||||
integrity sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==
|
||||
|
||||
chownr@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4"
|
||||
integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==
|
||||
|
||||
cli-cursor@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
|
||||
|
|
@ -1984,7 +2159,7 @@ core-util-is@~1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
|
||||
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
|
||||
|
||||
cross-spawn@^7.0.6:
|
||||
cross-spawn@^7.0.3, cross-spawn@^7.0.6:
|
||||
version "7.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
|
||||
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
|
||||
|
|
@ -2030,6 +2205,13 @@ data-view-byte-offset@^1.0.1:
|
|||
es-errors "^1.3.0"
|
||||
is-data-view "^1.0.1"
|
||||
|
||||
debug@^4.0.0, debug@^4.3.4, debug@^4.3.6, debug@^4.4.0, debug@^4.4.1, debug@^4.4.3:
|
||||
version "4.4.3"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
|
||||
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
debug@^4.1.0, debug@^4.3.1:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
|
||||
|
|
@ -2037,13 +2219,6 @@ debug@^4.1.0, debug@^4.3.1:
|
|||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
debug@^4.3.4, debug@^4.3.6, debug@^4.4.1, debug@^4.4.3:
|
||||
version "4.4.3"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
|
||||
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
deep-eql@^5.0.1:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341"
|
||||
|
|
@ -2075,6 +2250,11 @@ define-data-property@^1.0.1, define-data-property@^1.1.4:
|
|||
es-errors "^1.3.0"
|
||||
gopd "^1.0.1"
|
||||
|
||||
define-lazy-prop@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
|
||||
integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==
|
||||
|
||||
define-properties@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c"
|
||||
|
|
@ -2196,6 +2376,18 @@ electron-to-chromium@^1.5.328:
|
|||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.349.tgz#9b9c6a6d84d1107557c18a9336099ce0ee890e5b"
|
||||
integrity sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==
|
||||
|
||||
elementtree@^0.1.7:
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/elementtree/-/elementtree-0.1.7.tgz#9ac91be6e52fb6e6244c4e54a4ac3ed8ae8e29c0"
|
||||
integrity sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==
|
||||
dependencies:
|
||||
sax "1.1.4"
|
||||
|
||||
emoji-regex@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||
|
||||
enhanced-resolve@^5.20.0:
|
||||
version "5.20.1"
|
||||
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz#eeeb3966bea62c348c40a0cc9e7912e2557d0be0"
|
||||
|
|
@ -2209,6 +2401,11 @@ entities@^7.0.1:
|
|||
resolved "https://registry.yarnpkg.com/entities/-/entities-7.0.1.tgz#26e8a88889db63417dcb9a1e79a3f1bc92b5976b"
|
||||
integrity sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==
|
||||
|
||||
env-paths@^2.2.0:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
|
||||
integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
|
||||
|
||||
es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9:
|
||||
version "1.24.2"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.2.tgz#2dbd38c180735ee983f77585140a2706a963ed9a"
|
||||
|
|
@ -2415,6 +2612,13 @@ fast-uri@^3.0.1:
|
|||
resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.2.tgz#8af3d4fc9d3e71b11572cc2673b514a7d1a8c8ec"
|
||||
integrity sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==
|
||||
|
||||
fd-slicer@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||
integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
fdir@^6.5.0:
|
||||
version "6.5.0"
|
||||
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350"
|
||||
|
|
@ -2466,7 +2670,16 @@ foreground-child@^3.3.1:
|
|||
cross-spawn "^7.0.6"
|
||||
signal-exit "^4.0.1"
|
||||
|
||||
fs-extra@^9.0.1:
|
||||
fs-extra@^11.2.0:
|
||||
version "11.3.5"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.5.tgz#07a44eff40bea53e719909a532f91a23bf0769ff"
|
||||
integrity sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==
|
||||
dependencies:
|
||||
graceful-fs "^4.2.0"
|
||||
jsonfile "^6.0.1"
|
||||
universalify "^2.0.0"
|
||||
|
||||
fs-extra@^9.0.0, fs-extra@^9.0.1:
|
||||
version "9.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
|
||||
integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
|
||||
|
|
@ -2576,6 +2789,15 @@ glob@^11.0.1:
|
|||
package-json-from-dist "^1.0.0"
|
||||
path-scurry "^2.0.0"
|
||||
|
||||
glob@^13.0.3:
|
||||
version "13.0.6"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-13.0.6.tgz#078666566a425147ccacfbd2e332deb66a2be71d"
|
||||
integrity sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==
|
||||
dependencies:
|
||||
minimatch "^10.2.2"
|
||||
minipass "^7.1.3"
|
||||
path-scurry "^2.0.2"
|
||||
|
||||
globalthis@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236"
|
||||
|
|
@ -2681,6 +2903,11 @@ inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3:
|
|||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
ini@^4.1.1:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.3.tgz#4c359675a6071a46985eb39b14e4a2c0ec98a795"
|
||||
integrity sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==
|
||||
|
||||
ini@~1.3.0:
|
||||
version "1.3.8"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
|
||||
|
|
@ -2759,6 +2986,11 @@ is-date-object@^1.0.5, is-date-object@^1.1.0:
|
|||
call-bound "^1.0.2"
|
||||
has-tostringtag "^1.0.2"
|
||||
|
||||
is-docker@^2.0.0, is-docker@^2.1.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
|
||||
integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
|
||||
|
||||
is-finalizationregistry@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90"
|
||||
|
|
@ -2766,6 +2998,11 @@ is-finalizationregistry@^1.1.0:
|
|||
dependencies:
|
||||
call-bound "^1.0.3"
|
||||
|
||||
is-fullwidth-code-point@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
|
||||
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
||||
|
||||
is-generator-function@^1.0.10:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.2.tgz#ae3b61e3d5ea4e4839b90bad22b02335051a17d5"
|
||||
|
|
@ -2906,6 +3143,13 @@ is-weakset@^2.0.3:
|
|||
call-bound "^1.0.3"
|
||||
get-intrinsic "^1.2.6"
|
||||
|
||||
is-wsl@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
|
||||
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
|
||||
dependencies:
|
||||
is-docker "^2.0.0"
|
||||
|
||||
isarray@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
|
||||
|
|
@ -2986,6 +3230,16 @@ jszip@^3.10.1:
|
|||
readable-stream "~2.3.6"
|
||||
setimmediate "^1.0.5"
|
||||
|
||||
kleur@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
|
||||
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
|
||||
|
||||
kleur@^4.1.5:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
|
||||
integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
|
||||
|
||||
leven@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
|
||||
|
|
@ -3100,11 +3354,18 @@ minimist@^1.2.5:
|
|||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
|
||||
minipass@^7.1.2:
|
||||
minipass@^7.0.4, minipass@^7.1.2, minipass@^7.1.3:
|
||||
version "7.1.3"
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b"
|
||||
integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==
|
||||
|
||||
minizlib@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.1.0.tgz#6ad76c3a8f10227c9b51d1c9ac8e30b27f5a251c"
|
||||
integrity sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==
|
||||
dependencies:
|
||||
minipass "^7.1.2"
|
||||
|
||||
module-definition@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/module-definition/-/module-definition-6.0.1.tgz#47e73144cc5a9aa31f3380166fddf8e962ccb2e4"
|
||||
|
|
@ -3137,6 +3398,23 @@ nanoid@^3.3.11:
|
|||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
|
||||
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
|
||||
|
||||
native-run@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/native-run/-/native-run-2.0.3.tgz#b1ceb39c8d24abf4916c07ae9aad5a9fbcd31a02"
|
||||
integrity sha512-U1PllBuzW5d1gfan+88L+Hky2eZx+9gv3Pf6rNBxKbORxi7boHzqiA6QFGSnqMem4j0A9tZ08NMIs5+0m/VS1Q==
|
||||
dependencies:
|
||||
"@ionic/utils-fs" "^3.1.7"
|
||||
"@ionic/utils-terminal" "^2.3.4"
|
||||
bplist-parser "^0.3.2"
|
||||
debug "^4.3.4"
|
||||
elementtree "^0.1.7"
|
||||
ini "^4.1.1"
|
||||
plist "^3.1.0"
|
||||
split2 "^4.2.0"
|
||||
through2 "^4.0.2"
|
||||
tslib "^2.6.2"
|
||||
yauzl "^2.10.0"
|
||||
|
||||
node-releases@^2.0.27:
|
||||
version "2.0.27"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e"
|
||||
|
|
@ -3183,6 +3461,15 @@ onetime@^5.1.0:
|
|||
dependencies:
|
||||
mimic-fn "^2.1.0"
|
||||
|
||||
open@^8.4.0:
|
||||
version "8.4.2"
|
||||
resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9"
|
||||
integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==
|
||||
dependencies:
|
||||
define-lazy-prop "^2.0.0"
|
||||
is-docker "^2.1.1"
|
||||
is-wsl "^2.2.0"
|
||||
|
||||
ora@^5.4.1:
|
||||
version "5.4.1"
|
||||
resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18"
|
||||
|
|
@ -3207,7 +3494,7 @@ own-keys@^1.0.1:
|
|||
object-keys "^1.1.1"
|
||||
safe-push-apply "^1.0.0"
|
||||
|
||||
package-json-from-dist@^1.0.0:
|
||||
package-json-from-dist@^1.0.0, package-json-from-dist@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505"
|
||||
integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==
|
||||
|
|
@ -3232,7 +3519,7 @@ path-parse@^1.0.7:
|
|||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||
|
||||
path-scurry@^2.0.0:
|
||||
path-scurry@^2.0.0, path-scurry@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.2.tgz#6be0d0ee02a10d9e0de7a98bae65e182c9061f85"
|
||||
integrity sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==
|
||||
|
|
@ -3260,6 +3547,11 @@ pdf-lib@^1.17.1:
|
|||
pako "^1.0.11"
|
||||
tslib "^1.11.1"
|
||||
|
||||
pend@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
|
||||
|
||||
picocolors@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
|
||||
|
|
@ -3294,6 +3586,15 @@ playwright@1.58.2:
|
|||
optionalDependencies:
|
||||
fsevents "2.3.2"
|
||||
|
||||
plist@^3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/plist/-/plist-3.1.1.tgz#fa6099e1e3cf6ea180258ebe6378ea3878c2c841"
|
||||
integrity sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==
|
||||
dependencies:
|
||||
"@xmldom/xmldom" "^0.9.10"
|
||||
base64-js "^1.5.1"
|
||||
xmlbuilder "^15.1.1"
|
||||
|
||||
pluralize@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
|
||||
|
|
@ -3379,6 +3680,14 @@ process-nextick-args@~2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
|
||||
|
||||
prompts@^2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
|
||||
integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==
|
||||
dependencies:
|
||||
kleur "^3.0.3"
|
||||
sisteransi "^1.0.5"
|
||||
|
||||
punycode@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
|
|
@ -3416,7 +3725,7 @@ react@^19.2.0:
|
|||
resolved "https://registry.yarnpkg.com/react/-/react-19.2.4.tgz#438e57baa19b77cb23aab516cf635cd0579ee09a"
|
||||
integrity sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==
|
||||
|
||||
readable-stream@^3.4.0:
|
||||
readable-stream@3, readable-stream@^3.4.0:
|
||||
version "3.6.2"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
|
||||
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
|
||||
|
|
@ -3550,6 +3859,14 @@ restore-cursor@^3.1.0:
|
|||
onetime "^5.1.0"
|
||||
signal-exit "^3.0.2"
|
||||
|
||||
rimraf@^6.0.1:
|
||||
version "6.1.3"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-6.1.3.tgz#afbee236b3bd2be331d4e7ce4493bac1718981af"
|
||||
integrity sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==
|
||||
dependencies:
|
||||
glob "^13.0.3"
|
||||
package-json-from-dist "^1.0.1"
|
||||
|
||||
rollup@^4.43.0:
|
||||
version "4.57.1"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.57.1.tgz#947f70baca32db2b9c594267fe9150aa316e5a88"
|
||||
|
|
@ -3664,6 +3981,16 @@ sass-lookup@^6.1.0:
|
|||
commander "^12.1.0"
|
||||
enhanced-resolve "^5.20.0"
|
||||
|
||||
sax@1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.1.4.tgz#74b6d33c9ae1e001510f179a91168588f1aedaa9"
|
||||
integrity sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==
|
||||
|
||||
sax@>=0.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.6.0.tgz#da59637629307b97e7c4cb28e080a7bc38560d5b"
|
||||
integrity sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==
|
||||
|
||||
scheduler@^0.27.0:
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd"
|
||||
|
|
@ -3674,6 +4001,11 @@ semver@^6.3.1:
|
|||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
||||
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
||||
|
||||
semver@^7.6.3:
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.0.tgz#ed0661039fcbcda2ce71f01fa6adbefaa77040df"
|
||||
integrity sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==
|
||||
|
||||
semver@^7.7.3:
|
||||
version "7.7.4"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a"
|
||||
|
|
@ -3782,11 +4114,30 @@ signal-exit@^3.0.2:
|
|||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
|
||||
integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
|
||||
|
||||
signal-exit@^3.0.3:
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
||||
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
|
||||
|
||||
signal-exit@^4.0.1:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
|
||||
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
|
||||
|
||||
sisteransi@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
|
||||
integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
|
||||
|
||||
slice-ansi@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
|
||||
integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
astral-regex "^2.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
|
||||
smob@^1.0.0:
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/smob/-/smob-1.6.1.tgz#930607366738545aee542a93e03e47b54e0303e0"
|
||||
|
|
@ -3817,6 +4168,11 @@ source-map@^0.8.0-beta.0:
|
|||
dependencies:
|
||||
whatwg-url "^7.0.0"
|
||||
|
||||
split2@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4"
|
||||
integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==
|
||||
|
||||
stackback@0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b"
|
||||
|
|
@ -3842,6 +4198,15 @@ stream-to-array@^2.3.0:
|
|||
dependencies:
|
||||
any-promise "^1.1.0"
|
||||
|
||||
string-width@^4.1.0:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string.prototype.matchall@^4.0.12:
|
||||
version "4.0.12"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz#6c88740e49ad4956b1332a911e949583a275d4c0"
|
||||
|
|
@ -3923,6 +4288,13 @@ strip-ansi@^6.0.0:
|
|||
dependencies:
|
||||
ansi-regex "^5.0.0"
|
||||
|
||||
strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-bom@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
|
||||
|
|
@ -3969,6 +4341,17 @@ tapable@^2.3.0:
|
|||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.2.tgz#86755feabad08d82a26b891db044808c6ad00f15"
|
||||
integrity sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==
|
||||
|
||||
tar@^7.5.3:
|
||||
version "7.5.15"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.15.tgz#afe6d1316cddf614a566e3813e42fe01aed46fee"
|
||||
integrity sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==
|
||||
dependencies:
|
||||
"@isaacs/fs-minipass" "^4.0.0"
|
||||
chownr "^3.0.0"
|
||||
minipass "^7.1.2"
|
||||
minizlib "^3.1.0"
|
||||
yallist "^5.0.0"
|
||||
|
||||
temp-dir@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e"
|
||||
|
|
@ -3994,6 +4377,13 @@ terser@^5.17.4:
|
|||
commander "^2.20.0"
|
||||
source-map-support "~0.5.20"
|
||||
|
||||
through2@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764"
|
||||
integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==
|
||||
dependencies:
|
||||
readable-stream "3"
|
||||
|
||||
tinybench@^2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b"
|
||||
|
|
@ -4049,6 +4439,11 @@ tr46@^1.0.1:
|
|||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
tree-kill@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
|
||||
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
|
||||
|
||||
ts-api-utils@^2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz#4acd4a155e22734990a5ed1fe9e97f113bcb37c1"
|
||||
|
|
@ -4078,6 +4473,11 @@ tslib@^1.11.1:
|
|||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.0.1, tslib@^2.1.0, tslib@^2.6.2, tslib@^2.8.1:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
|
||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||
|
||||
type-fest@^0.16.0:
|
||||
version "0.16.0"
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860"
|
||||
|
|
@ -4193,6 +4593,11 @@ universalify@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
|
||||
integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
|
||||
|
||||
untildify@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b"
|
||||
integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==
|
||||
|
||||
upath@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"
|
||||
|
|
@ -4540,12 +4945,52 @@ workbox-window@7.4.1, workbox-window@^7.4.1:
|
|||
"@types/trusted-types" "^2.0.2"
|
||||
workbox-core "7.4.1"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
ws@^8.18.3:
|
||||
version "8.20.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.20.1.tgz#91a9ae2b312ccf98e0a85ec499b48cef45ab0ddb"
|
||||
integrity sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==
|
||||
|
||||
xml2js@^0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499"
|
||||
integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==
|
||||
dependencies:
|
||||
sax ">=0.6.0"
|
||||
xmlbuilder "~11.0.0"
|
||||
|
||||
xmlbuilder@^15.1.1:
|
||||
version "15.1.1"
|
||||
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5"
|
||||
integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==
|
||||
|
||||
xmlbuilder@~11.0.0:
|
||||
version "11.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
|
||||
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
|
||||
|
||||
yallist@^3.0.2:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
|
||||
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
|
||||
|
||||
yallist@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533"
|
||||
integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==
|
||||
|
||||
yauzl@^2.10.0:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||
integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.1.0"
|
||||
|
|
|
|||