code2app 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -0
- package/assets/icon-source.png +0 -0
- package/bin/cli.js +248 -0
- package/dist/index.html +24 -0
- package/docs/APP_STORES.md +129 -0
- package/docs/BUILDING.md +134 -0
- package/docs/LOCAL_APPS.md +116 -0
- package/docs/MOBILE.md +78 -0
- package/docs/OAUTH.md +76 -0
- package/package.json +66 -0
- package/profiles/README.md +56 -0
- package/profiles/example.json +37 -0
- package/scripts/build-sidecar.mjs +80 -0
- package/scripts/configure.mjs +222 -0
- package/scripts/generate-icons.mjs +53 -0
- package/scripts/generate-placeholder-icons.mjs +186 -0
- package/scripts/lib/png.mjs +141 -0
- package/scripts/profile.mjs +147 -0
- package/src-tauri/Cargo.lock +5832 -0
- package/src-tauri/Cargo.toml +47 -0
- package/src-tauri/build.rs +3 -0
- package/src-tauri/capabilities/default.json +15 -0
- package/src-tauri/capabilities/remote.json +17 -0
- package/src-tauri/icons/128x128.png +0 -0
- package/src-tauri/icons/128x128@2x.png +0 -0
- package/src-tauri/icons/32x32.png +0 -0
- package/src-tauri/icons/64x64.png +0 -0
- package/src-tauri/icons/Square107x107Logo.png +0 -0
- package/src-tauri/icons/Square142x142Logo.png +0 -0
- package/src-tauri/icons/Square150x150Logo.png +0 -0
- package/src-tauri/icons/Square284x284Logo.png +0 -0
- package/src-tauri/icons/Square30x30Logo.png +0 -0
- package/src-tauri/icons/Square310x310Logo.png +0 -0
- package/src-tauri/icons/Square44x44Logo.png +0 -0
- package/src-tauri/icons/Square71x71Logo.png +0 -0
- package/src-tauri/icons/Square89x89Logo.png +0 -0
- package/src-tauri/icons/StoreLogo.png +0 -0
- package/src-tauri/icons/icon.icns +0 -0
- package/src-tauri/icons/icon.ico +0 -0
- package/src-tauri/icons/icon.png +0 -0
- package/src-tauri/src/generated_config.rs +22 -0
- package/src-tauri/src/lib.rs +212 -0
- package/src-tauri/src/main.rs +6 -0
- package/src-tauri/tauri.conf.json +84 -0
- package/vitest.config.mjs +35 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "native-app-wrapper"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Tauri wrapper that packages a website or a bundled CLI as a native app"
|
|
5
|
+
authors = ["OpenSourceAGI"]
|
|
6
|
+
edition = "2021"
|
|
7
|
+
rust-version = "1.77.2"
|
|
8
|
+
|
|
9
|
+
[lib]
|
|
10
|
+
name = "native_app_wrapper_lib"
|
|
11
|
+
crate-type = ["staticlib", "cdylib", "rlib"]
|
|
12
|
+
|
|
13
|
+
[build-dependencies]
|
|
14
|
+
tauri-build = { version = "2", features = [] }
|
|
15
|
+
|
|
16
|
+
[dependencies]
|
|
17
|
+
tauri = { version = "2", features = [] }
|
|
18
|
+
tauri-plugin-opener = "2"
|
|
19
|
+
tauri-plugin-deep-link = "2"
|
|
20
|
+
# Backs the `sidecar_output` command: local-mode apps bundle a CLI next to the
|
|
21
|
+
# binary and read its stdout instead of reimplementing it in Rust.
|
|
22
|
+
tauri-plugin-shell = "2"
|
|
23
|
+
serde = { version = "1", features = ["derive"] }
|
|
24
|
+
serde_json = "1"
|
|
25
|
+
url = "2"
|
|
26
|
+
urlencoding = "2"
|
|
27
|
+
|
|
28
|
+
# Desktop-only plugins: neither single-instance (used to route a second
|
|
29
|
+
# deep-link launch's argv into the already-running app) nor the updater apply
|
|
30
|
+
# to the mobile targets, which have their own OS-level equivalents (Android/
|
|
31
|
+
# iOS both already dedupe app launches, and app-store updates replace the
|
|
32
|
+
# updater's job on mobile).
|
|
33
|
+
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
|
34
|
+
tauri-plugin-single-instance = "2"
|
|
35
|
+
tauri-plugin-updater = "2"
|
|
36
|
+
# Backs the fullscreen toggle: a profile can open the window in true OS
|
|
37
|
+
# fullscreen, which has no title bar to click a restore button on, so an
|
|
38
|
+
# OS-level shortcut is the only reliable way out regardless of what the wrapped
|
|
39
|
+
# page's own JS does.
|
|
40
|
+
tauri-plugin-global-shortcut = "2"
|
|
41
|
+
|
|
42
|
+
[profile.release]
|
|
43
|
+
panic = "abort"
|
|
44
|
+
codegen-units = 1
|
|
45
|
+
lto = true
|
|
46
|
+
opt-level = "s"
|
|
47
|
+
strip = true
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../gen/schemas/desktop-schema.json",
|
|
3
|
+
"identifier": "default",
|
|
4
|
+
"description": "Baseline permissions for the wrapper's own bundled window content and Rust side. Capabilities with no `remote` block apply only to locally bundled content, so nothing here is reachable from a wrapped website — that gets the separate, narrower `remote` capability.",
|
|
5
|
+
"windows": ["main"],
|
|
6
|
+
"permissions": [
|
|
7
|
+
"core:default",
|
|
8
|
+
"core:event:default",
|
|
9
|
+
"core:window:allow-set-focus",
|
|
10
|
+
"core:window:allow-unminimize",
|
|
11
|
+
"opener:default",
|
|
12
|
+
"opener:allow-open-url",
|
|
13
|
+
"deep-link:default"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../gen/schemas/remote-schema.json",
|
|
3
|
+
"identifier": "remote",
|
|
4
|
+
"description": "Scoped IPC access for the wrapped site itself (the profile's trustedOrigins), so its login page can ask the wrapper to open the system browser for OAuth. Nothing else is exposed to remote content.",
|
|
5
|
+
"windows": [
|
|
6
|
+
"main"
|
|
7
|
+
],
|
|
8
|
+
"remote": {
|
|
9
|
+
"urls": [
|
|
10
|
+
"https://example.com/*",
|
|
11
|
+
"https://*.example.com/*"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"permissions": [
|
|
15
|
+
"opener:allow-open-url"
|
|
16
|
+
]
|
|
17
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Generated by scripts/configure.mjs from profiles/example.json. Do not edit by hand.
|
|
2
|
+
|
|
3
|
+
/// Custom URL scheme registered with the OS for the OAuth handoff, or ""
|
|
4
|
+
/// when the profile doesn't use one.
|
|
5
|
+
pub const DEEP_LINK_SCHEME: &str = "exampleapp";
|
|
6
|
+
|
|
7
|
+
/// Origin the main window loads in remote mode, or "" in local mode.
|
|
8
|
+
pub const APP_URL: &str = "https://example.com";
|
|
9
|
+
|
|
10
|
+
/// Name of the bundled CLI exposed through the `sidecar_output` command,
|
|
11
|
+
/// or "" when the profile bundles none.
|
|
12
|
+
pub const SIDECAR_NAME: &str = "";
|
|
13
|
+
|
|
14
|
+
/// The fixed argument list that sidecar is always invoked with. Fixed, and
|
|
15
|
+
/// not passed in from the frontend, so the webview can't turn the bundled
|
|
16
|
+
/// binary into an arbitrary-argument process spawner.
|
|
17
|
+
pub const SIDECAR_ARGS: &[&str] = &[];
|
|
18
|
+
|
|
19
|
+
/// Whether the profile configured auto-updates. False means the updater
|
|
20
|
+
/// plugin is never registered — registering it without a `pubkey` in
|
|
21
|
+
/// tauri.conf.json panics at startup rather than staying inert.
|
|
22
|
+
pub const UPDATER_ENABLED: bool = false;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// Generic Tauri wrapper. It packages one of two things as a native app,
|
|
2
|
+
// depending on the active profile's `mode` (see ../profiles/README.md):
|
|
3
|
+
//
|
|
4
|
+
// remote — the main window loads `generated_config::APP_URL` directly, so the
|
|
5
|
+
// live site is the app's entire UI. The only thing the wrapper adds
|
|
6
|
+
// beyond the window is an OAuth handoff: providers like Google
|
|
7
|
+
// refuse to run their login flow inside an embedded webview, so
|
|
8
|
+
// sign-in happens in the system browser and comes back through the
|
|
9
|
+
// `<scheme>://auth-callback?token=...` deep link handled below.
|
|
10
|
+
// See ../docs/OAUTH.md for the full round trip.
|
|
11
|
+
//
|
|
12
|
+
// local — the main window loads the frontend bundled in ../dist, and the
|
|
13
|
+
// app's data comes from a CLI bundled alongside it as a Tauri
|
|
14
|
+
// sidecar, reachable through the `sidecar_output` command. This is
|
|
15
|
+
// how a command-line tool ships as a GUI app with no runtime (no
|
|
16
|
+
// Node, no interpreter) installed on the user's machine.
|
|
17
|
+
// See ../docs/LOCAL_APPS.md.
|
|
18
|
+
//
|
|
19
|
+
// Everything that differs between apps is generated into generated_config.rs by
|
|
20
|
+
// scripts/configure.mjs, so this file never needs editing to package a
|
|
21
|
+
// different app.
|
|
22
|
+
|
|
23
|
+
mod generated_config;
|
|
24
|
+
|
|
25
|
+
use tauri::{Manager, Url};
|
|
26
|
+
use tauri_plugin_deep_link::DeepLinkExt;
|
|
27
|
+
|
|
28
|
+
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
29
|
+
pub fn run() {
|
|
30
|
+
let mut builder = tauri::Builder::default();
|
|
31
|
+
|
|
32
|
+
// Neither single-instance nor the updater apply on Android/iOS: the OS
|
|
33
|
+
// already dedupes app launches there, and store updates replace what
|
|
34
|
+
// tauri-plugin-updater does on desktop.
|
|
35
|
+
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
36
|
+
{
|
|
37
|
+
builder = builder.plugin(tauri_plugin_single_instance::init(|app, argv, _cwd| {
|
|
38
|
+
// On Windows/Linux, clicking a deep link while the app is already
|
|
39
|
+
// running launches a *second* process instead of firing the
|
|
40
|
+
// `on_open_url` event in the first one — single-instance forwards
|
|
41
|
+
// that second launch's argv here instead, so the callback still
|
|
42
|
+
// needs to be pulled out of argv and handled the same way.
|
|
43
|
+
if !generated_config::DEEP_LINK_SCHEME.is_empty() {
|
|
44
|
+
let scheme_prefix = format!("{}://", generated_config::DEEP_LINK_SCHEME);
|
|
45
|
+
if let Some(url) = argv.iter().find(|arg| arg.starts_with(&scheme_prefix)) {
|
|
46
|
+
handle_deep_link(app, url);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if let Some(window) = app.get_webview_window("main") {
|
|
50
|
+
let _ = window.set_focus();
|
|
51
|
+
let _ = window.unminimize();
|
|
52
|
+
}
|
|
53
|
+
}));
|
|
54
|
+
// Only when the profile configured updates. tauri-plugin-updater reads
|
|
55
|
+
// `plugins.updater.pubkey` during initialization and *panics* if it
|
|
56
|
+
// isn't there, so registering it unconditionally would crash every app
|
|
57
|
+
// that hasn't set up an update endpoint — which is the default.
|
|
58
|
+
if generated_config::UPDATER_ENABLED {
|
|
59
|
+
builder = builder.plugin(tauri_plugin_updater::Builder::new().build());
|
|
60
|
+
}
|
|
61
|
+
builder = builder.plugin(fullscreen_toggle_plugin());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
builder
|
|
65
|
+
.plugin(tauri_plugin_opener::init())
|
|
66
|
+
.plugin(tauri_plugin_shell::init())
|
|
67
|
+
.plugin(tauri_plugin_deep_link::init())
|
|
68
|
+
.invoke_handler(tauri::generate_handler![sidecar_output])
|
|
69
|
+
.setup(|app| {
|
|
70
|
+
if !generated_config::DEEP_LINK_SCHEME.is_empty() {
|
|
71
|
+
let handle = app.handle().clone();
|
|
72
|
+
app.deep_link().on_open_url(move |event| {
|
|
73
|
+
for url in event.urls() {
|
|
74
|
+
handle_deep_link(&handle, url.as_str());
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Desktop platforms must register the scheme with the OS at
|
|
79
|
+
// runtime (the tauri.conf.json `plugins.deep-link.desktop.schemes`
|
|
80
|
+
// entry declares it for the installer; this call is what actually
|
|
81
|
+
// wires it up in dev / for unpackaged runs). Android/iOS pick the
|
|
82
|
+
// scheme up from the generated project's manifest/Info.plist
|
|
83
|
+
// instead, produced by `tauri android init` / `tauri ios init`
|
|
84
|
+
// reading the same tauri.conf.json — calling register_all() there
|
|
85
|
+
// is a no-op.
|
|
86
|
+
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
87
|
+
{
|
|
88
|
+
let _ = app.deep_link().register_all();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
Ok(())
|
|
93
|
+
})
|
|
94
|
+
.run(tauri::generate_context!())
|
|
95
|
+
.expect("error while running the native app wrapper");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// Runs the bundled sidecar CLI and returns its stdout, for local-mode apps
|
|
99
|
+
/// whose frontend is a view over a command-line tool.
|
|
100
|
+
///
|
|
101
|
+
/// Two things are deliberately not parameters.
|
|
102
|
+
///
|
|
103
|
+
/// The argument list is fixed at `generated_config::SIDECAR_ARGS`, so a page can
|
|
104
|
+
/// ask for the snapshot the app is built around but can't turn the bundled
|
|
105
|
+
/// binary into a general-purpose process spawner. An app that needs several
|
|
106
|
+
/// invocations should add several commands with several fixed argument lists,
|
|
107
|
+
/// not open this one up.
|
|
108
|
+
///
|
|
109
|
+
/// And the caller is checked to be locally bundled content. Tauri already
|
|
110
|
+
/// rejects app-defined commands coming from a remote origin unless a capability
|
|
111
|
+
/// explicitly grants them, so this is a second line of defense rather than the
|
|
112
|
+
/// only one — but it is the line that doesn't depend on nobody ever adding this
|
|
113
|
+
/// command to `capabilities/remote.json`, which is exactly the mistake that
|
|
114
|
+
/// would hand a wrapped website a process spawner.
|
|
115
|
+
#[tauri::command]
|
|
116
|
+
async fn sidecar_output(window: tauri::WebviewWindow) -> Result<String, String> {
|
|
117
|
+
use tauri_plugin_shell::ShellExt;
|
|
118
|
+
|
|
119
|
+
if generated_config::SIDECAR_NAME.is_empty() {
|
|
120
|
+
return Err("this app bundles no sidecar (the profile has no \"sidecar\" block)".into());
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Tauri serves bundled content from tauri://localhost, or
|
|
124
|
+
// http://tauri.localhost on Windows. Anything else is a real website.
|
|
125
|
+
let url = window.url().map_err(|e| e.to_string())?;
|
|
126
|
+
let is_bundled_content =
|
|
127
|
+
url.scheme() == "tauri" || url.host_str() == Some("tauri.localhost");
|
|
128
|
+
if !is_bundled_content {
|
|
129
|
+
return Err("sidecar_output is only callable from the app's bundled frontend".into());
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let output = window
|
|
133
|
+
.app_handle()
|
|
134
|
+
.shell()
|
|
135
|
+
.sidecar(generated_config::SIDECAR_NAME)
|
|
136
|
+
.map_err(|e| format!("sidecar \"{}\" is not bundled: {e}", generated_config::SIDECAR_NAME))?
|
|
137
|
+
.args(generated_config::SIDECAR_ARGS)
|
|
138
|
+
.output()
|
|
139
|
+
.await
|
|
140
|
+
.map_err(|e| format!("sidecar failed to run: {e}"))?;
|
|
141
|
+
|
|
142
|
+
if !output.status.success() {
|
|
143
|
+
return Err(format!(
|
|
144
|
+
"sidecar exited with {}: {}",
|
|
145
|
+
output.status.code().unwrap_or(-1),
|
|
146
|
+
String::from_utf8_lossy(&output.stderr).trim(),
|
|
147
|
+
));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// F11 / Ctrl+Shift+F toggles the main window's fullscreen state. A profile can
|
|
154
|
+
/// open the window in true OS fullscreen (`window.fullscreen`), which on most
|
|
155
|
+
/// platforms hides the title bar entirely, so this is the only way out that
|
|
156
|
+
/// doesn't depend on the wrapped page's own JS or a click target that isn't
|
|
157
|
+
/// there.
|
|
158
|
+
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
159
|
+
fn fullscreen_toggle_plugin<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
|
160
|
+
use tauri_plugin_global_shortcut::ShortcutState;
|
|
161
|
+
|
|
162
|
+
tauri_plugin_global_shortcut::Builder::new()
|
|
163
|
+
.with_shortcuts(["F11", "CmdOrCtrl+Shift+F"])
|
|
164
|
+
.expect("fullscreen shortcuts are valid accelerator strings")
|
|
165
|
+
.with_handler(|app, _shortcut, event| {
|
|
166
|
+
if event.state() != ShortcutState::Pressed {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if let Some(window) = app.get_webview_window("main") {
|
|
170
|
+
let is_fullscreen = window.is_fullscreen().unwrap_or(false);
|
|
171
|
+
let _ = window.set_fullscreen(!is_fullscreen);
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
.build()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// Handles `<scheme>://auth-callback?token=...`. The token is a one-time login
|
|
178
|
+
/// token the site minted from the session the system browser just established
|
|
179
|
+
/// with the OAuth provider. Verifying it is a same-origin POST, not something a
|
|
180
|
+
/// plain navigation can hit — so instead of calling that endpoint from Rust,
|
|
181
|
+
/// this navigates the window to `/auth/native-callback` (same origin as the
|
|
182
|
+
/// site already loaded here), which spends the token client-side. That POST
|
|
183
|
+
/// happens same-origin to this webview, so the resulting session cookie lands
|
|
184
|
+
/// in *this* webview's cookie jar — the whole reason the token exists, since
|
|
185
|
+
/// the system browser's cookies and this window's cookies are different jars
|
|
186
|
+
/// that can't otherwise see each other's session. See ../docs/OAUTH.md.
|
|
187
|
+
fn handle_deep_link(app: &tauri::AppHandle, raw_url: &str) {
|
|
188
|
+
let Ok(parsed) = Url::parse(raw_url) else {
|
|
189
|
+
return;
|
|
190
|
+
};
|
|
191
|
+
let is_auth_callback = parsed.host_str() == Some("auth-callback")
|
|
192
|
+
|| parsed.path().trim_start_matches('/') == "auth-callback";
|
|
193
|
+
if !is_auth_callback {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
let Some((_, token)) = parsed.query_pairs().find(|(key, _)| key == "token") else {
|
|
197
|
+
return;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
let Some(window) = app.get_webview_window("main") else {
|
|
201
|
+
return;
|
|
202
|
+
};
|
|
203
|
+
let callback_url = format!(
|
|
204
|
+
"{}/auth/native-callback?token={}",
|
|
205
|
+
generated_config::APP_URL,
|
|
206
|
+
urlencoding::encode(&token),
|
|
207
|
+
);
|
|
208
|
+
if let Ok(url) = Url::parse(&callback_url) {
|
|
209
|
+
let _ = window.navigate(url);
|
|
210
|
+
let _ = window.set_focus();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://schema.tauri.app/config/2",
|
|
3
|
+
"productName": "Example App",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"identifier": "com.example.app",
|
|
6
|
+
"build": {
|
|
7
|
+
"frontendDist": "../dist"
|
|
8
|
+
},
|
|
9
|
+
"app": {
|
|
10
|
+
"withGlobalTauri": true,
|
|
11
|
+
"windows": [
|
|
12
|
+
{
|
|
13
|
+
"label": "main",
|
|
14
|
+
"title": "Example App",
|
|
15
|
+
"url": "https://example.com",
|
|
16
|
+
"width": 1280,
|
|
17
|
+
"height": 800,
|
|
18
|
+
"minWidth": 480,
|
|
19
|
+
"minHeight": 480,
|
|
20
|
+
"resizable": true,
|
|
21
|
+
"fullscreen": false,
|
|
22
|
+
"center": true
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"security": {
|
|
26
|
+
"capabilities": [
|
|
27
|
+
"default",
|
|
28
|
+
"remote"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"bundle": {
|
|
33
|
+
"active": true,
|
|
34
|
+
"targets": "all",
|
|
35
|
+
"publisher": "Example, Inc.",
|
|
36
|
+
"copyright": "Example, Inc.",
|
|
37
|
+
"category": "Productivity",
|
|
38
|
+
"shortDescription": "One-line description shown in store listings.",
|
|
39
|
+
"longDescription": "One-line description shown in store listings.",
|
|
40
|
+
"icon": [
|
|
41
|
+
"icons/32x32.png",
|
|
42
|
+
"icons/128x128.png",
|
|
43
|
+
"icons/128x128@2x.png",
|
|
44
|
+
"icons/icon.icns",
|
|
45
|
+
"icons/icon.ico"
|
|
46
|
+
],
|
|
47
|
+
"windows": {
|
|
48
|
+
"nsis": {
|
|
49
|
+
"installMode": "currentUser"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"macOS": {
|
|
53
|
+
"minimumSystemVersion": "10.15"
|
|
54
|
+
},
|
|
55
|
+
"linux": {
|
|
56
|
+
"deb": {
|
|
57
|
+
"depends": []
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"android": {
|
|
61
|
+
"minSdkVersion": 24
|
|
62
|
+
},
|
|
63
|
+
"iOS": {
|
|
64
|
+
"minimumSystemVersion": "14.0"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"plugins": {
|
|
68
|
+
"deep-link": {
|
|
69
|
+
"desktop": {
|
|
70
|
+
"schemes": [
|
|
71
|
+
"exampleapp"
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
"mobile": [
|
|
75
|
+
{
|
|
76
|
+
"scheme": [
|
|
77
|
+
"exampleapp"
|
|
78
|
+
],
|
|
79
|
+
"appLink": false
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/// <reference types="vitest/config" />
|
|
2
|
+
import { defineConfig } from "vitest/config";
|
|
3
|
+
|
|
4
|
+
// Per-package Vitest setup. Coverage is written to this package's own
|
|
5
|
+
// ./coverage/lcov.info so Codecov can flag it under "native-app-wrapper".
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
test: {
|
|
8
|
+
environment: "node",
|
|
9
|
+
include: ["{test,tests,src}/**/*.{test,spec}.{js,mjs,ts,tsx}"],
|
|
10
|
+
exclude: ["**/node_modules/**", "**/dist/**"],
|
|
11
|
+
// Infrastructure is in place before every package has a suite.
|
|
12
|
+
passWithNoTests: true,
|
|
13
|
+
coverage: {
|
|
14
|
+
provider: "v8",
|
|
15
|
+
reporter: ["text", "json", "lcov"],
|
|
16
|
+
reportsDirectory: "./coverage",
|
|
17
|
+
reportOnFailure: true,
|
|
18
|
+
include: [
|
|
19
|
+
"bin/**/*.{js,mjs}",
|
|
20
|
+
"scripts/**/*.{js,mjs}",
|
|
21
|
+
],
|
|
22
|
+
exclude: [
|
|
23
|
+
"**/node_modules/**",
|
|
24
|
+
"**/dist/**",
|
|
25
|
+
"**/build/**",
|
|
26
|
+
"**/coverage/**",
|
|
27
|
+
"**/demo/**",
|
|
28
|
+
"**/*.d.ts",
|
|
29
|
+
"**/*.config.*",
|
|
30
|
+
"**/*.{test,spec}.*",
|
|
31
|
+
"**/*.template.*",
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|