feat(cli) rewrite the core CLI in Rust (#851)

This commit is contained in:
Lucas Fernandes Nogueira 2021-01-30 12:15:47 -03:00 committed by GitHub
parent 23132acf76
commit 3e8abe3764
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
87 changed files with 11436 additions and 14072 deletions

6
.changes/cli-rust.md Normal file
View File

@ -0,0 +1,6 @@
---
"tauri-cli": minor
"tauri-bundler": minor
---
The `dev` and `build` pipeline is now written in Rust.

View File

@ -139,14 +139,13 @@
}
},
"packages": {
"tauri.js": {
"path": "./cli/tauri.js",
"api": {
"path": "./api",
"manager": "javascript",
"dependencies": ["tauri"],
"assets": [
{
"path": "./cli/tauri.js/tauri-${ pkgFile.version }.tgz",
"name": "tauri.js-${ pkgFile.version }.tgz"
"path": "./api/tauri-${ pkgFile.version }.tgz",
"name": "api-${ pkgFile.version }.tgz"
}
]
},
@ -154,6 +153,22 @@
"path": "./cli/tauri-bundler",
"manager": "rust"
},
"tauri-cli": {
"path": "./cli/core",
"manager": "rust",
"dependencies": ["api", "tauri-bundler", "tauri"]
},
"tauri.js": {
"path": "./cli/tauri.js",
"manager": "javascript",
"dependencies": ["tauri-cli"],
"assets": [
{
"path": "./cli/tauri.js/tauri-${ pkgFile.version }.tgz",
"name": "tauri.js-${ pkgFile.version }.tgz"
}
]
},
"tauri-utils": {
"path": "./tauri-utils",
"manager": "rust"
@ -171,7 +186,7 @@
"tauri": {
"path": "./tauri",
"manager": "rust",
"dependencies": ["tauri-api", "tauri-updater"]
"dependencies": ["api", "tauri-api", "tauri-updater"]
}
}
}

8
.changes/tauri-api.md Normal file
View File

@ -0,0 +1,8 @@
---
"tauri.js": minor
---
The Tauri API interface is now shipped with the `@tauri-apps/api` package instead of the deprecated `tauri` package.
To use the new API package, delete the old `tauri` from your `package.json` and install the new package:
`$ yarn remove tauri && yarn add @tauri-apps/api` or `$ npm uninstall tauri && npm install @tauri-apps/api`.
And change all `import { someApi } from 'tauri/api` to `import { someApi } from '@tauri-apps/api'`.

7
.changes/tauri-cli.md Normal file
View File

@ -0,0 +1,7 @@
---
"tauri.js": minor
---
The Tauri Node.js CLI package is now `@tauri-apps/cli`.
To use the new CLI, delete the old `tauri` from your `package.json` and install the new package:
`$ yarn remove tauri && yarn add --dev @tauri-apps/cli` or `$ npm uninstall tauri && npm install --save-dev @tauri-apps/cli`.

2
.github/CODEOWNERS vendored
View File

@ -15,6 +15,8 @@
/cli/tauri-bundler/ @tauri-apps/bundler
/cli/core/ @tauri-apps/core
/cli/tauri.js/ @tauri-apps/js-cli
/tauri-update/ @tauri-apps/core

View File

@ -7,6 +7,7 @@ on:
- 'tauri/**'
- 'tauri-utils/**'
- 'tauri-api/**'
- 'cli/core/**'
jobs:
workspace_clippy_fmt_check:
@ -38,6 +39,28 @@ jobs:
command: fmt
args: --all -- --check
cli_clippy_fmt_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: rustup component add clippy
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --manifest-path ./cli/core/Cargo.toml --all-targets -- -D warnings
name: cli
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path ./cli/core/Cargo.toml --all -- --check
core_clippy_check:
runs-on: ubuntu-latest
strategy:

View File

@ -46,6 +46,30 @@ jobs:
TAURI_DIST_DIR: ${{ runner.workspace }}/tauri/tauri/examples/communication/dist
TAURI_DIR: ${{ runner.workspace }}/tauri/tauri/examples/communication/src-tauri
test-tauri-cli:
runs-on: ${{ matrix.platform }}
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v2
- name: install stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: build api
working-directory: ./api
run: yarn && yarn build
- name: build CLI
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path ./cli/core/Cargo.toml
test-tauri-js-cli:
runs-on: ${{ matrix.platform }}
@ -61,9 +85,8 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y webkit2gtk-4.0
- run: cargo install --path ./cli/tauri-bundler --force
- name: test
timeout-minutes: 15
timeout-minutes: 30
run: |
cd ./cli/tauri.js
yarn

2
.gitignore vendored
View File

@ -76,7 +76,7 @@ TODO.md
target
# lock for libs
Cargo.lock
/Cargo.lock
yarn.lock
/cli/tauri.js/test/jest/tmp

1
api/.eslintignore Normal file
View File

@ -0,0 +1 @@
/src/types/config.validator.ts

56
api/.eslintrc.js Normal file
View File

@ -0,0 +1,56 @@
module.exports = {
root: true,
env: {
node: true,
jest: true,
},
parser: "@typescript-eslint/parser",
extends: [
"standard-with-typescript",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:lodash-template/recommended",
// TODO: make this work with typescript
// 'plugin:node/recommended'
"prettier",
"prettier/@typescript-eslint",
],
plugins: ["@typescript-eslint", "node", "security"],
parserOptions: {
tsconfigRootDir: __dirname,
project: "./tsconfig.json",
},
globals: {
__statics: true,
process: true,
},
// add your custom rules here
rules: {
// allow console.log during development only
"no-console": process.env.NODE_ENV === "production" ? "error" : "off",
// allow debugger during development only
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
"no-process-exit": "off",
"security/detect-non-literal-fs-filename": "warn",
"security/detect-unsafe-regex": "error",
"security/detect-buffer-noassert": "error",
"security/detect-child-process": "warn",
"security/detect-disable-mustache-escape": "error",
"security/detect-eval-with-expression": "error",
"security/detect-no-csrf-before-method-override": "error",
"security/detect-non-literal-regexp": "error",
"security/detect-non-literal-require": "warn",
"security/detect-object-injection": "warn",
"security/detect-possible-timing-attacks": "error",
"security/detect-pseudoRandomBytes": "error",
"space-before-function-paren": "off",
"@typescript-eslint/default-param-last": "off",
"@typescript-eslint/strict-boolean-expressions": 0,
},
};

64
api/.gitignore vendored Normal file
View File

@ -0,0 +1,64 @@
# Build output
dist/
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
/.vs
.DS_Store
.Thumbs.db
*.sublime*
.idea/
debug.log
package-lock.json
.vscode/settings.json

14
api/babel.config.js Normal file
View File

@ -0,0 +1,14 @@
module.exports = {
presets: [
[
"@babel/preset-env",
{
targets: {
node: "current",
},
modules: "commonjs",
},
],
"@babel/preset-typescript",
],
};

84
api/package.json Normal file
View File

@ -0,0 +1,84 @@
{
"name": "@tauri-apps/api",
"version": "0.1.0",
"description": "Tauri API definitions",
"main": "./dist/index",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/tauri"
},
"scripts": {
"build": "rimraf ./dist && rollup -c --silent",
"prepublishOnly": "yarn build",
"lint": "eslint --ext ts \"./src/**/*.ts\"",
"lint-fix": "eslint --fix --ext ts \"./src/**/*.ts\"",
"lint:lockfile": "lockfile-lint --path yarn.lock --type yarn --validate-https --allowed-hosts npm yarn",
"format": "prettier --write --end-of-line=auto \"./**/*.{js,jsx,ts,tsx,html,css,json}\" --ignore-path .gitignore",
"format:check": "prettier --check --end-of-line=auto \"./**/*.{js,jsx,ts,tsx,html,css,json}\" --ignore-path .gitignore"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tauri-apps/tauri.git"
},
"contributors": [
"Tauri Team <team@tauri-apps.org> (https://tauri.studio)",
"Daniel Thompson-Yvetot <denjell@sfosc.org>",
"Lucas Fernandes Gonçalves Nogueira <lucas@tauri.studio>"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/tauri-apps/tauri/issues"
},
"homepage": "https://github.com/tauri-apps/tauri#readme",
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">= 10.17.0",
"npm": ">= 6.6.0",
"yarn": ">= 1.19.1"
},
"devDependencies": {
"@babel/core": "7.12.9",
"@babel/preset-env": "7.12.7",
"@babel/preset-typescript": "7.12.7",
"@rollup/plugin-babel": "5.2.2",
"@rollup/plugin-commonjs": "17.0.0",
"@rollup/plugin-json": "4.1.0",
"@rollup/plugin-node-resolve": "11.0.0",
"@rollup/plugin-sucrase": "3.1.0",
"@rollup/plugin-typescript": "8.0.0",
"@typescript-eslint/eslint-plugin": "4.9.0",
"@typescript-eslint/parser": "4.9.0",
"eslint": "7.14.0",
"eslint-config-prettier": "6.15.0",
"eslint-config-standard-with-typescript": "19.0.1",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-lodash-template": "0.19.0",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "4.2.1",
"eslint-plugin-security": "1.4.0",
"eslint-plugin-standard": "4.1.0",
"husky": "4.3.0",
"lint-staged": "10.5.3",
"lockfile-lint": "4.3.7",
"prettier": "2.2.1",
"rimraf": "3.0.2",
"rollup": "2.34.1",
"rollup-plugin-terser": "7.0.2",
"rollup-plugin-typescript2": "0.29.0",
"typescript": "4.1.2"
},
"resolutions": {
"**/lodash": ">=4.17.19"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,jsx,ts,tsx,md,html,css,json}": "prettier --write --end-of-line=auto \"./**/*.{js,jsx,ts,tsx,html,css,json}\" --ignore-path .gitignore",
"*.{ts,tsx}": "eslint --fix --ext ts ./src/**/*.ts"
}
}

107
api/rollup.config.js Normal file
View File

@ -0,0 +1,107 @@
// rollup.config.js
import { terser } from "rollup-plugin-terser";
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import sucrase from "@rollup/plugin-sucrase";
import babel, { getBabelOutputPlugin } from "@rollup/plugin-babel";
import typescript from "@rollup/plugin-typescript";
import pkg from "./package.json";
export default [
{
input: {
fs: "./src/fs.ts",
path: "./src/path.ts",
dialog: "./src/dialog.ts",
event: "./src/event.ts",
http: "./src/http.ts",
index: "./src/index.ts",
process: "./src/process.ts",
tauri: "./src/tauri.ts",
window: "./src/window.ts",
cli: "./src/cli.ts",
notification: "./src/notification.ts",
},
treeshake: true,
perf: true,
output: [
{
dir: "dist/",
entryFileNames: "[name].js",
format: "cjs",
exports: "named",
globals: {},
},
{
dir: "dist/",
entryFileNames: "[name].mjs",
format: "esm",
exports: "named",
globals: {},
},
],
plugins: [
commonjs({}),
resolve({
// pass custom options to the resolve plugin
customResolveOptions: {
moduleDirectory: "node_modules",
},
}),
typescript({
tsconfig: "./tsconfig.json",
}),
babel({
configFile: false,
presets: [["@babel/preset-env"], ["@babel/preset-typescript"]],
}),
terser(),
],
external: [
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
],
watch: {
chokidar: true,
include: "src/**",
exclude: "node_modules/**",
},
},
{
input: {
bundle: "./src/bundle.ts",
},
output: [
{
name: "__TAURI__",
dir: "dist/", // if it needs to run in the browser
entryFileNames: "tauri.bundle.umd.js",
format: "umd",
plugins: [
getBabelOutputPlugin({
presets: [["@babel/preset-env", { modules: "umd" }]],
allowAllFormats: true,
}),
terser(),
],
globals: {},
},
],
plugins: [
sucrase({
exclude: ["node_modules"],
transforms: ["typescript"],
}),
resolve({
// pass custom options to the resolve plugin
customResolveOptions: {
moduleDirectory: "node_modules",
},
}),
],
external: [
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
],
},
];

24
api/src/bundle.ts Normal file
View File

@ -0,0 +1,24 @@
import "regenerator-runtime/runtime";
import * as cli from "./cli";
import * as dialog from "./dialog";
import * as event from "./event";
import * as fs from "./fs";
import * as path from "./path";
import http from "./http";
import * as process from "./process";
import * as tauri from "./tauri";
import * as window from "./window";
import * as notification from "./notification";
export {
cli,
dialog,
event,
fs,
path,
http,
process,
tauri,
window,
notification,
};

View File

@ -1,4 +1,4 @@
import { promisified } from './tauri'
import { promisified } from "./tauri";
export interface ArgMatch {
/**
@ -6,21 +6,21 @@ export interface ArgMatch {
* boolean if flag
* string[] or null if takes multiple values
*/
value: string | boolean | string[] | null
value: string | boolean | string[] | null;
/**
* number of occurrences
*/
occurrences: number
occurrences: number;
}
export interface SubcommandMatch {
name: string
matches: CliMatches
name: string;
matches: CliMatches;
}
export interface CliMatches {
args: { [name: string]: ArgMatch }
subcommand: SubcommandMatch | null
args: { [name: string]: ArgMatch };
subcommand: SubcommandMatch | null;
}
/**
@ -28,8 +28,8 @@ export interface CliMatches {
*/
async function getMatches(): Promise<CliMatches> {
return await promisified<CliMatches>({
cmd: 'cliMatches'
})
cmd: "cliMatches",
});
}
export { getMatches }
export { getMatches };

View File

@ -1,16 +1,16 @@
import { promisified } from './tauri'
import { promisified } from "./tauri";
export interface OpenDialogOptions {
filter?: string
defaultPath?: string
multiple?: boolean
directory?: boolean
filter?: string;
defaultPath?: string;
multiple?: boolean;
directory?: boolean;
}
export type SaveDialogOptions = Pick<
OpenDialogOptions,
'filter' | 'defaultPath'
>
"filter" | "defaultPath"
>;
/**
* @name openDialog
@ -25,14 +25,14 @@ export type SaveDialogOptions = Pick<
async function open(
options: OpenDialogOptions = {}
): Promise<string | string[]> {
if (typeof options === 'object') {
Object.freeze(options)
if (typeof options === "object") {
Object.freeze(options);
}
return await promisified({
cmd: 'openDialog',
options
})
cmd: "openDialog",
options,
});
}
/**
@ -44,14 +44,14 @@ async function open(
* @returns {Promise<string>} Promise resolving to the select path
*/
async function save(options: SaveDialogOptions = {}): Promise<string> {
if (typeof options === 'object') {
Object.freeze(options)
if (typeof options === "object") {
Object.freeze(options);
}
return await promisified({
cmd: 'saveDialog',
options
})
cmd: "saveDialog",
options,
});
}
export { open, save }
export { open, save };

View File

@ -1,11 +1,11 @@
import { invoke, transformCallback } from './tauri'
import { invoke, transformCallback } from "./tauri";
export interface Event<T> {
type: string
payload: T
type: string;
payload: T;
}
export type EventCallback<T> = (event: Event<T>) => void
export type EventCallback<T> = (event: Event<T>) => void;
/**
* listen to an event from the backend
@ -19,11 +19,11 @@ function listen<T>(
once = false
): void {
invoke({
cmd: 'listen',
cmd: "listen",
event,
handler: transformCallback(handler, once),
once
})
once,
});
}
/**
@ -34,10 +34,10 @@ function listen<T>(
*/
function emit(event: string, payload?: string): void {
invoke({
cmd: 'emit',
cmd: "emit",
event,
payload
})
payload,
});
}
export { listen, emit }
export { listen, emit };

View File

@ -1,47 +1,47 @@
import { promisified } from './tauri'
import { promisified } from "./tauri";
export enum ResponseType {
JSON = 1,
Text = 2,
Binary = 3
Binary = 3,
}
export enum BodyType {
Form = 1,
File = 2,
Auto = 3
Auto = 3,
}
export type Body = object | string | BinaryType
export type Body = object | string | BinaryType;
export type HttpVerb =
| 'GET'
| 'POST'
| 'PUT'
| 'DELETE'
| 'PATCH'
| 'HEAD'
| 'OPTIONS'
| 'CONNECT'
| 'TRACE'
| "GET"
| "POST"
| "PUT"
| "DELETE"
| "PATCH"
| "HEAD"
| "OPTIONS"
| "CONNECT"
| "TRACE";
export interface HttpOptions {
method: HttpVerb
url: string
headers?: Record<string, any>
params?: Record<string, any>
body?: Body
followRedirects: boolean
maxRedirections: boolean
connectTimeout: number
readTimeout: number
timeout: number
allowCompression: boolean
responseType?: ResponseType
bodyType: BodyType
method: HttpVerb;
url: string;
headers?: Record<string, any>;
params?: Record<string, any>;
body?: Body;
followRedirects: boolean;
maxRedirections: boolean;
connectTimeout: number;
readTimeout: number;
timeout: number;
allowCompression: boolean;
responseType?: ResponseType;
bodyType: BodyType;
}
export type PartialOptions = Omit<HttpOptions, 'method' | 'url'>
export type PartialOptions = Omit<HttpOptions, "method" | "url">;
/**
* makes a HTTP request
@ -52,9 +52,9 @@ export type PartialOptions = Omit<HttpOptions, 'method' | 'url'>
*/
async function request<T>(options: HttpOptions): Promise<T> {
return await promisified({
cmd: 'httpRequest',
options: options
})
cmd: "httpRequest",
options: options,
});
}
/**
@ -67,10 +67,10 @@ async function request<T>(options: HttpOptions): Promise<T> {
*/
async function get<T>(url: string, options: PartialOptions): Promise<T> {
return await request({
method: 'GET',
method: "GET",
url,
...options
})
...options,
});
}
/**
@ -88,11 +88,11 @@ async function post<T>(
options: PartialOptions
): Promise<T> {
return await request({
method: 'POST',
method: "POST",
url,
body,
...options
})
...options,
});
}
/**
@ -110,11 +110,11 @@ async function put<T>(
options: PartialOptions
): Promise<T> {
return await request({
method: 'PUT',
method: "PUT",
url,
body,
...options
})
...options,
});
}
/**
@ -127,10 +127,10 @@ async function put<T>(
*/
async function patch<T>(url: string, options: PartialOptions): Promise<T> {
return await request({
method: 'PATCH',
method: "PATCH",
url,
...options
})
...options,
});
}
/**
@ -146,10 +146,10 @@ async function deleteRequest<T>(
options: PartialOptions
): Promise<T> {
return await request({
method: 'DELETE',
method: "DELETE",
url,
...options
})
...options,
});
}
export default {
@ -160,5 +160,5 @@ export default {
patch,
delete: deleteRequest,
ResponseType,
BodyType
}
BodyType,
};

2
api/src/index.ts Normal file
View File

@ -0,0 +1,2 @@
import * as api from "./bundle";
export default api;

35
api/src/notification.ts Normal file
View File

@ -0,0 +1,35 @@
import { promisified } from "./tauri";
export interface Options {
title: string;
body?: string;
icon?: string;
}
export type PartialOptions = Omit<Options, "title">;
export type Permission = "granted" | "denied" | "default";
async function isPermissionGranted(): Promise<boolean | null> {
if (window.Notification.permission !== "default") {
return await Promise.resolve(window.Notification.permission === "granted");
}
return await promisified({
cmd: "isNotificationPermissionGranted",
});
}
async function requestPermission(): Promise<Permission> {
return await window.Notification.requestPermission();
}
function sendNotification(options: Options | string): void {
if (typeof options === "string") {
// eslint-disable-next-line no-new
new window.Notification(options);
} else {
// eslint-disable-next-line no-new
new window.Notification(options.title, options);
}
}
export { sendNotification, requestPermission, isPermissionGranted };

View File

@ -1,5 +1,5 @@
import { promisified } from './tauri'
import { BaseDirectory } from './fs'
import { promisified } from "./tauri";
import { BaseDirectory } from "./fs";
/**
* @name appDir
@ -8,10 +8,10 @@ import { BaseDirectory } from './fs'
*/
async function appDir(): Promise<string> {
return await promisified<string>({
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.App
})
cmd: "resolvePath",
path: "",
directory: BaseDirectory.App,
});
}
/**
@ -21,10 +21,10 @@ async function appDir(): Promise<string> {
*/
async function audioDir(): Promise<string> {
return await promisified<string>({
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Audio
})
cmd: "resolvePath",
path: "",
directory: BaseDirectory.Audio,
});
}
/**
@ -34,10 +34,10 @@ async function audioDir(): Promise<string> {
*/
async function cacheDir(): Promise<string> {
return await promisified<string>({
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Cache
})
cmd: "resolvePath",
path: "",
directory: BaseDirectory.Cache,
});
}
/**
@ -47,10 +47,10 @@ async function cacheDir(): Promise<string> {
*/
async function configDir(): Promise<string> {
return await promisified<string>({
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Config
})
cmd: "resolvePath",
path: "",
directory: BaseDirectory.Config,
});
}
/**
@ -60,10 +60,10 @@ async function configDir(): Promise<string> {
*/
async function dataDir(): Promise<string> {
return await promisified<string>({
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Data
})
cmd: "resolvePath",
path: "",
directory: BaseDirectory.Data,
});
}
/**
@ -73,10 +73,10 @@ async function dataDir(): Promise<string> {
*/
async function desktopDir(): Promise<string> {
return await promisified<string>({
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Desktop
})
cmd: "resolvePath",
path: "",
directory: BaseDirectory.Desktop,
});
}
/**
@ -86,10 +86,10 @@ async function desktopDir(): Promise<string> {
*/
async function documentDir(): Promise<string> {
return await promisified<string>({
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Document
})
cmd: "resolvePath",
path: "",
directory: BaseDirectory.Document,
});
}
/**
@ -99,10 +99,10 @@ async function documentDir(): Promise<string> {
*/
async function downloadDir(): Promise<string> {
return await promisified<string>({
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Download
})
cmd: "resolvePath",
path: "",
directory: BaseDirectory.Download,
});
}
/**
@ -112,10 +112,10 @@ async function downloadDir(): Promise<string> {
*/
async function executableDir(): Promise<string> {
return await promisified<string>({
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Executable
})
cmd: "resolvePath",
path: "",
directory: BaseDirectory.Executable,
});
}
/**
@ -125,10 +125,10 @@ async function executableDir(): Promise<string> {
*/
async function fontDir(): Promise<string> {
return await promisified<string>({
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Font
})
cmd: "resolvePath",
path: "",
directory: BaseDirectory.Font,
});
}
/**
@ -138,10 +138,10 @@ async function fontDir(): Promise<string> {
*/
async function homeDir(): Promise<string> {
return await promisified<string>({
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Home
})
cmd: "resolvePath",
path: "",
directory: BaseDirectory.Home,
});
}
/**
@ -151,10 +151,10 @@ async function homeDir(): Promise<string> {
*/
async function localDataDir(): Promise<string> {
return await promisified<string>({
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.LocalData
})
cmd: "resolvePath",
path: "",
directory: BaseDirectory.LocalData,
});
}
/**
@ -164,10 +164,10 @@ async function localDataDir(): Promise<string> {
*/
async function pictureDir(): Promise<string> {
return await promisified<string>({
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Picture
})
cmd: "resolvePath",
path: "",
directory: BaseDirectory.Picture,
});
}
/**
@ -177,10 +177,10 @@ async function pictureDir(): Promise<string> {
*/
async function publicDir(): Promise<string> {
return await promisified<string>({
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Public
})
cmd: "resolvePath",
path: "",
directory: BaseDirectory.Public,
});
}
/**
@ -190,10 +190,10 @@ async function publicDir(): Promise<string> {
*/
async function resourceDir(): Promise<string> {
return await promisified<string>({
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Resource
})
cmd: "resolvePath",
path: "",
directory: BaseDirectory.Resource,
});
}
/**
@ -203,10 +203,10 @@ async function resourceDir(): Promise<string> {
*/
async function runtimeDir(): Promise<string> {
return await promisified<string>({
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Runtime
})
cmd: "resolvePath",
path: "",
directory: BaseDirectory.Runtime,
});
}
/**
@ -216,10 +216,10 @@ async function runtimeDir(): Promise<string> {
*/
async function templateDir(): Promise<string> {
return await promisified<string>({
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Template
})
cmd: "resolvePath",
path: "",
directory: BaseDirectory.Template,
});
}
/**
@ -229,10 +229,10 @@ async function templateDir(): Promise<string> {
*/
async function videoDir(): Promise<string> {
return await promisified<string>({
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Video
})
cmd: "resolvePath",
path: "",
directory: BaseDirectory.Video,
});
}
/**
@ -245,10 +245,10 @@ async function resolvePath(
directory: BaseDirectory
): Promise<string> {
return await promisified<string>({
cmd: 'resolvePath',
cmd: "resolvePath",
path,
directory
})
directory,
});
}
export {
@ -270,5 +270,5 @@ export {
runtimeDir,
templateDir,
videoDir,
resolvePath
}
resolvePath,
};

View File

@ -1,4 +1,4 @@
import { promisified } from './tauri'
import { promisified } from "./tauri";
/**
* spawns a process
@ -11,15 +11,15 @@ async function execute(
command: string,
args?: string | string[]
): Promise<string> {
if (typeof args === 'object') {
Object.freeze(args)
if (typeof args === "object") {
Object.freeze(args);
}
return await promisified({
cmd: 'execute',
cmd: "execute",
command,
args: typeof args === 'string' ? [args] : args
})
args: typeof args === "string" ? [args] : args,
});
}
export { execute }
export { execute };

View File

@ -1,31 +1,31 @@
declare global {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Window {
__TAURI_INVOKE_HANDLER__: (command: string) => void
__TAURI_INVOKE_HANDLER__: (command: string) => void;
}
}
function s4(): string {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1)
.substring(1);
}
function uid(): string {
return (
s4() +
s4() +
'-' +
"-" +
s4() +
'-' +
"-" +
s4() +
'-' +
"-" +
s4() +
'-' +
"-" +
s4() +
s4() +
s4()
)
);
}
/**
@ -34,28 +34,28 @@ function uid(): string {
* @param args
*/
function invoke(args: any): void {
window.__TAURI_INVOKE_HANDLER__(args)
window.__TAURI_INVOKE_HANDLER__(args);
}
function transformCallback(
callback?: (response: any) => void,
once = false
): string {
const identifier = uid()
const identifier = uid();
Object.defineProperty(window, identifier, {
value: (result: any) => {
if (once) {
Reflect.deleteProperty(window, identifier)
Reflect.deleteProperty(window, identifier);
}
return callback?.(result)
return callback?.(result);
},
writable: false,
configurable: true
})
configurable: true,
});
return identifier
return identifier;
}
/**
@ -68,20 +68,20 @@ function transformCallback(
async function promisified<T>(args: any): Promise<T> {
return await new Promise((resolve, reject) => {
const callback = transformCallback((e) => {
resolve(e)
Reflect.deleteProperty(window, error)
}, true)
resolve(e);
Reflect.deleteProperty(window, error);
}, true);
const error = transformCallback((e) => {
reject(e)
Reflect.deleteProperty(window, callback)
}, true)
reject(e);
Reflect.deleteProperty(window, callback);
}, true);
invoke({
callback,
error,
...args
})
})
...args,
});
});
}
export { invoke, transformCallback, promisified }
export { invoke, transformCallback, promisified };

View File

@ -1,4 +1,4 @@
import { invoke } from './tauri'
import { invoke } from "./tauri";
/**
* sets the window title
@ -7,9 +7,9 @@ import { invoke } from './tauri'
*/
function setTitle(title: string): void {
invoke({
cmd: 'setTitle',
title
})
cmd: "setTitle",
title,
});
}
/**
@ -19,9 +19,9 @@ function setTitle(title: string): void {
*/
function open(url: string): void {
invoke({
cmd: 'open',
uri: url
})
cmd: "open",
uri: url,
});
}
export { setTitle, open }
export { setTitle, open };

View File

@ -8,8 +8,8 @@
"types": ["@types"]
},
"declaration": true,
"declarationDir": "api",
"rootDir": "api-src"
"declarationDir": "dist",
"rootDir": "src"
},
"include": ["./api-src"]
"include": ["./src"]
}

View File

@ -0,0 +1,2 @@
[advisories]
ignore = ["RUSTSEC-2020-0031"]

3176
cli/core/Cargo.lock generated Executable file

File diff suppressed because it is too large Load Diff

33
cli/core/Cargo.toml Normal file
View File

@ -0,0 +1,33 @@
workspace = {}
[package]
name = "tauri-cli"
version = "0.1.0"
authors = ["Lucas Nogueira <lucas@tauri.studio>"]
edition = "2018"
[[bin]]
name = "cargo-tauri"
path = "src/main.rs"
[dependencies]
clap = { version = "3.0.0-beta.1", features = ["yaml"] }
anyhow = "1.0"
tauri-bundler = { path = "../tauri-bundler" }
colored = "2.0"
once_cell = "1.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tauri-inliner = "0.0.1"
kuchiki = "0.8"
html5ever = "0.25"
tiny_http = "0.7"
attohttpc = "0.15"
url = "2.1"
http = "0.2"
handlebars = "3.2"
notify = "4.0"
shared_child = "0.3"
toml_edit = "0.2"
convert_case = "0.4"
json-patch = "0.2"

26
cli/core/build.rs Normal file
View File

@ -0,0 +1,26 @@
use std::env;
use std::env::current_dir;
use std::error::Error;
use std::fs::{read_to_string, File};
use std::io::{BufWriter, Write};
use std::path::Path;
pub fn main() -> Result<(), Box<dyn Error>> {
let out_dir = env::var("OUT_DIR")?;
let dest_bundle_umd_path = Path::new(&out_dir).join("tauri.bundle.umd.js");
let mut bundle_umd_file = BufWriter::new(File::create(&dest_bundle_umd_path)?);
let bundle_umd_path = current_dir()?.join("../../api/dist/tauri.bundle.umd.js");
println!("cargo:rerun-if-changed={:?}", bundle_umd_path);
if let Ok(bundle_umd_js) = read_to_string(bundle_umd_path) {
write!(bundle_umd_file, "{}", bundle_umd_js)?;
} else {
write!(
bundle_umd_file,
r#"throw new Error("you are trying to use the global Tauri script but the @tauri-apps/api package wasn't compiled; run `yarn build` first")"#
)?;
}
Ok(())
}

128
cli/core/src/build.rs Normal file
View File

@ -0,0 +1,128 @@
use tauri_bundler::{
build_project,
bundle::{bundle_project, PackageType, SettingsBuilder},
};
use crate::helpers::{
app_paths::{app_dir, tauri_dir},
config::get as get_config,
execute_with_output,
manifest::rewrite_manifest,
TauriHtml,
};
use std::env::{set_current_dir, set_var};
use std::fs::read_to_string;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
#[derive(Default)]
pub struct Build {
debug: bool,
verbose: bool,
targets: Option<Vec<String>>,
config: Option<String>,
}
impl Build {
pub fn new() -> Self {
Default::default()
}
pub fn debug(mut self) -> Self {
self.debug = true;
self
}
pub fn verbose(mut self) -> Self {
self.verbose = true;
self
}
pub fn targets(mut self, targets: Vec<String>) -> Self {
self.targets = Some(targets);
self
}
pub fn config(mut self, config: String) -> Self {
self.config.replace(config);
self
}
pub fn run(self) -> crate::Result<()> {
let config = get_config(self.config.as_deref())?;
let feature = if config.tauri.embedded_server.active {
"embedded-server"
} else {
"no-server"
};
let mut settings_builder = SettingsBuilder::new().features(vec![feature.to_string()]);
if !self.debug {
settings_builder = settings_builder.release();
}
if self.verbose {
settings_builder = settings_builder.verbose();
}
if let Some(names) = self.targets {
let mut types = vec![];
for name in names {
match PackageType::from_short_name(&name) {
Some(package_type) => {
types.push(package_type);
}
None => {
return Err(anyhow::anyhow!(format!(
"Unsupported bundle format: {}",
name
)));
}
}
}
settings_builder = settings_builder.package_types(types);
}
let tauri_path = tauri_dir();
set_current_dir(&tauri_path)?;
set_var("TAURI_DIR", &tauri_path);
set_var("TAURI_DIST_DIR", tauri_path.join(&config.build.dist_dir));
rewrite_manifest(&config)?;
let index_html_path = PathBuf::from(&config.build.dist_dir).join("index.html");
let tauri_html = TauriHtml::new(&config.build.dist_dir, read_to_string(index_html_path)?)
.inliner_enabled(config.tauri.inliner.active && !config.tauri.embedded_server.active)
.global_tauri(config.build.with_global_tauri)
.generate()?;
let tauri_index_html_path = PathBuf::from(&config.build.dist_dir).join("index.tauri.html");
let mut tauri_index_html_file = File::create(tauri_index_html_path)?;
tauri_index_html_file.write_all(tauri_html.as_bytes())?;
let settings = settings_builder.build()?;
if let Some(before_build) = &config.build.before_build_command {
let mut cmd: Option<&str> = None;
let mut args: Vec<&str> = vec![];
for token in before_build.split(' ') {
if cmd.is_none() && !token.is_empty() {
cmd = Some(token);
} else {
args.push(token)
}
}
if let Some(cmd) = cmd {
let mut command = Command::new(cmd);
command.args(args).current_dir(app_dir());
execute_with_output(&mut command)?;
}
}
build_project(&settings)?;
if config.tauri.bundle.active {
bundle_project(settings)?;
}
Ok(())
}
}

86
cli/core/src/cli.yml Normal file
View File

@ -0,0 +1,86 @@
name: cargo-tauri
bin_name: cargo
author: Lucas Nogueira <lucas@tauri.studio>
about: The Tauri command line interface.
subcommands:
- tauri:
about: Tauri CLI
subcommands:
- dev:
about: Tauri dev.
args:
- config:
short: c
long: config
about: config JSON to merge with tauri.conf.json
takes_value: true
- exit-on-panic:
short: e
long: exit-on-panic
about: Exit on panic
- build:
about: Tauri build.
args:
- debug:
short: d
long: debug
about: Builds with the debug flag
- verbose:
short: v
long: verbose
about: Enables verbose logging
- target:
short: t
long: target
about: list of target triples to build against
takes_value: true
multiple: true
- config:
short: c
long: config
about: config JSON to merge with tauri.conf.json
takes_value: true
- info:
about: Shows information about Tauri dependencies
- init:
about: Initializes a Tauri project
args:
- force:
short: f
long: force
about: Force init to overwrite [conf|template|all]
takes_value: true
- log:
short: l
long: log
about: Enables logging
- directory:
short: d
long: directory
about: Set target directory for init
takes_value: true
- tauri-path:
short: t
long: tauri-path
about: Path of the Tauri project to use (relative to the cwd)
takes_value: true
- app-name:
short: A
long: app-name
about: Name of your Tauri application
takes_value: true
- window-title:
short: W
long: window-title
about: Window title of your Tauri application
takes_value: true
- dist-dir:
short: D
long: dist-dir
about: Web assets location, relative to <project-dir>/src-tauri
takes_value: true
- dev-path:
short: P
long: dev-path
about: Url of your dev server
takes_value: true

232
cli/core/src/dev.rs Normal file
View File

@ -0,0 +1,232 @@
use crate::helpers::{
app_paths::{app_dir, tauri_dir},
config::{get as get_config, reload as reload_config, Config},
manifest::rewrite_manifest,
Logger, TauriHtml,
};
use attohttpc::{Method, RequestBuilder};
use http::header::HeaderName;
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
use shared_child::SharedChild;
use tiny_http::{Response, Server};
use url::Url;
use std::env::set_var;
use std::ffi::OsStr;
use std::process::{exit, Child, Command};
use std::sync::mpsc::{channel, Receiver};
use std::sync::{Arc, Mutex};
use std::thread::sleep;
use std::time::Duration;
struct ChildGuard(Child);
impl Drop for ChildGuard {
fn drop(&mut self) {
let _ = self.0.kill();
}
}
#[derive(Default)]
pub struct Dev {
exit_on_panic: bool,
config: Option<String>,
}
impl Dev {
pub fn new() -> Self {
Default::default()
}
pub fn config(mut self, config: String) -> Self {
self.config.replace(config);
self
}
pub fn exit_on_panic(mut self, exit_on_panic: bool) -> Self {
self.exit_on_panic = exit_on_panic;
self
}
pub fn run(self) -> crate::Result<()> {
let logger = Logger::new("tauri:dev");
let tauri_path = tauri_dir();
let config = get_config(self.config.as_deref())?;
let mut config_mut = config.clone();
let mut _guard = None;
let mut process: Arc<SharedChild>;
let new_dev_path: String;
if let Some(before_dev) = &config.build.before_dev_command {
let mut cmd: Option<&str> = None;
let mut args: Vec<&str> = vec![];
for token in before_dev.split(' ') {
if cmd.is_none() && !token.is_empty() {
cmd = Some(token);
} else {
args.push(token)
}
}
if let Some(cmd) = cmd {
logger.log(format!("Running `{}`", before_dev));
let mut command = Command::new(cmd);
command.args(args).current_dir(app_dir()).spawn()?;
_guard = Some(command);
}
}
let running_dev_server = config.build.dev_path.starts_with("http");
if running_dev_server {
let dev_path = Url::parse(&config.build.dev_path)?;
let dev_port = dev_path.port().unwrap_or(80);
let timeout = Duration::from_secs(3);
let wait_time = Duration::from_secs(30);
let mut total_time = timeout;
while RequestBuilder::new(Method::GET, &dev_path).send().is_err() {
logger.warn("Waiting for your dev server to start...");
sleep(timeout);
total_time += timeout;
if total_time == wait_time {
logger.error(format!(
"Couldn't connect to {} after {}s. Please make sure that's the URL to your dev server.",
dev_path,
total_time.as_secs()
));
exit(1);
}
}
let proxy_path = dev_path.clone();
let proxy_port = dev_port + 1;
logger.log(format!("starting dev proxy on port {}", proxy_port));
let config_ = config.clone();
std::thread::spawn(move || proxy_dev_server(&config_, &proxy_path, proxy_port));
new_dev_path = format!(
"http://{}:{}",
dev_path.host_str().expect("failed to read dev_path host"),
proxy_port
);
} else {
new_dev_path = tauri_dir()
.join(&config.build.dev_path)
.to_string_lossy()
.to_string();
}
config_mut.build.dev_path = new_dev_path.clone();
set_var("TAURI_DIR", &tauri_path);
set_var("TAURI_DIST_DIR", tauri_path.join(&config.build.dist_dir));
set_var("TAURI_CONFIG", serde_json::to_string(&config_mut)?);
rewrite_manifest(&config)?;
let (child_wait_tx, child_wait_rx) = channel();
let child_wait_rx = Arc::new(Mutex::new(child_wait_rx));
process = self.start_app(child_wait_rx.clone());
let (tx, rx) = channel();
let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap();
watcher.watch(tauri_path.join("src"), RecursiveMode::Recursive)?;
watcher.watch(tauri_path.join("Cargo.toml"), RecursiveMode::Recursive)?;
watcher.watch(tauri_path.join("tauri.conf.json"), RecursiveMode::Recursive)?;
if !running_dev_server {
watcher.watch(
config_mut.build.dev_path.to_string(),
RecursiveMode::Recursive,
)?;
}
loop {
if let Ok(event) = rx.recv() {
let event_path = match event {
DebouncedEvent::Create(path) => Some(path),
DebouncedEvent::Remove(path) => Some(path),
DebouncedEvent::Rename(_, dest) => Some(dest),
DebouncedEvent::Write(path) => Some(path),
_ => None,
};
if let Some(event_path) = event_path {
let _ = child_wait_tx.send(true);
process.kill()?;
if event_path.file_name() == Some(OsStr::new("tauri.conf.json")) {
config_mut = reload_config(Some(&serde_json::to_string(&config_mut)?))?.clone();
rewrite_manifest(&config_mut)?;
config_mut.build.dev_path = new_dev_path.clone();
set_var("TAURI_CONFIG", serde_json::to_string(&config_mut)?);
}
process = self.start_app(child_wait_rx.clone());
}
}
}
}
fn start_app(&self, child_wait_rx: Arc<Mutex<Receiver<bool>>>) -> Arc<SharedChild> {
let mut command = Command::new("cargo");
command.arg("run").current_dir(tauri_dir());
let child = SharedChild::spawn(&mut command).expect("failed to run cargo");
let child_arc = Arc::new(child);
if self.exit_on_panic {
let child_clone = child_arc.clone();
std::thread::spawn(move || {
child_clone.wait().expect("failed to wait on child");
if child_wait_rx
.lock()
.expect("failed to get child_wait_rx lock")
.try_recv()
.is_err()
{
std::process::exit(1);
}
});
}
child_arc
}
}
fn proxy_dev_server(config: &Config, dev_path: &Url, dev_port: u16) -> crate::Result<()> {
let server_url = format!(
"{}:{}",
dev_path.host_str().expect("failed to read dev_path host"),
dev_port,
);
let server = Server::http(server_url).expect("failed to create proxy server");
for request in server.incoming_requests() {
let request_url = request.url();
let mut request_builder = RequestBuilder::new(
Method::from_bytes(request.method().to_string().as_bytes()).unwrap(),
dev_path.join(&request_url)?.to_string(),
);
for header in request.headers() {
request_builder = request_builder.header(
HeaderName::from_bytes(header.field.as_str().as_bytes())?,
header.value.as_str(),
);
}
if request_url == "/" {
let response = request_builder.send()?.text()?;
let tauri_html = TauriHtml::new(&config.build.dist_dir, response)
.global_tauri(config.build.with_global_tauri)
.generate()?;
request.respond(Response::from_data(tauri_html))?;
} else {
let response = request_builder.send()?.bytes()?;
request.respond(Response::from_data(response))?;
}
}
Ok(())
}

View File

@ -0,0 +1,36 @@
use std::env::current_dir;
use std::path::PathBuf;
use once_cell::sync::Lazy;
fn get_app_dir() -> PathBuf {
let mut dir = current_dir().expect("failed to read cwd");
let mut count = 0;
// only go up three folders max
while count <= 2 {
let test_path = dir.join("src-tauri/tauri.conf.json");
if test_path.exists() {
return dir;
}
count += 1;
match dir.parent() {
Some(parent) => {
dir = parent.to_path_buf();
}
None => break,
}
}
panic!("Couldn't recognize the current folder as a Tauri project.")
}
pub fn app_dir() -> &'static PathBuf {
static APP_DIR: Lazy<PathBuf> = Lazy::new(get_app_dir);
&APP_DIR
}
pub fn tauri_dir() -> PathBuf {
app_dir().join("src-tauri")
}

View File

@ -0,0 +1,426 @@
use json_patch::merge;
use once_cell::sync::OnceCell;
use serde::de::{Deserializer, Error as DeError, Visitor};
use serde::ser::Serializer;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::collections::HashMap;
use std::fs::File;
use std::io::BufReader;
static CONFIG: OnceCell<Config> = OnceCell::new();
/// The window configuration object.
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
#[serde(tag = "window", rename_all = "camelCase")]
pub struct WindowConfig {
/// The window width.
#[serde(default = "default_width")]
pub width: i32,
/// The window height.
#[serde(default = "default_height")]
pub height: i32,
/// Whether the window is resizable or not.
#[serde(default = "default_resizable")]
pub resizable: bool,
/// The window title.
#[serde(default = "default_title")]
pub title: String,
/// Whether the window starts as fullscreen or not.
#[serde(default)]
pub fullscreen: bool,
}
fn default_width() -> i32 {
800
}
fn default_height() -> i32 {
600
}
fn default_resizable() -> bool {
true
}
fn default_title() -> String {
"Tauri App".to_string()
}
fn default_window() -> WindowConfig {
WindowConfig {
width: default_width(),
height: default_height(),
resizable: default_resizable(),
title: default_title(),
fullscreen: false,
}
}
/// The embedded server port.
#[derive(PartialEq, Clone, Debug, Deserialize, Serialize)]
pub enum Port {
/// Port with a numeric value.
Value(u16),
/// Random port.
Random,
}
/// The embeddedServer configuration object.
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
#[serde(tag = "embeddedServer", rename_all = "camelCase")]
pub struct EmbeddedServerConfig {
/// Whether the embeddedServer is active or not
pub active: bool,
/// The embedded server host.
#[serde(default = "default_host")]
pub host: String,
/// The embedded server port.
/// If it's `random`, we'll generate one at runtime.
#[serde(
default = "default_port",
deserialize_with = "port_deserializer",
serialize_with = "port_serializer"
)]
pub port: Port,
}
fn default_host() -> String {
"http://127.0.0.1".to_string()
}
fn port_serializer<S>(x: &Port, s: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
match x {
Port::Random => s.serialize_str("random"),
Port::Value(val) => s.serialize_u16(*val),
}
}
fn port_deserializer<'de, D>(deserializer: D) -> Result<Port, D::Error>
where
D: Deserializer<'de>,
{
struct PortDeserializer;
impl<'de> Visitor<'de> for PortDeserializer {
type Value = Port;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a port number or the 'random' string")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: DeError,
{
if value != "random" {
Err(DeError::custom(
"expected a 'random' string or a port number",
))
} else {
Ok(Port::Random)
}
}
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: DeError,
{
Ok(Port::Value(value as u16))
}
}
deserializer.deserialize_any(PortDeserializer {})
}
fn default_port() -> Port {
Port::Random
}
fn default_embedded_server() -> EmbeddedServerConfig {
EmbeddedServerConfig {
active: false,
host: default_host(),
port: default_port(),
}
}
/// A CLI argument definition
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct CliArg {
/// The short version of the argument, without the preceding -.
///
/// NOTE: Any leading - characters will be stripped, and only the first non - character will be used as the short version.
pub short: Option<char>,
/// The unique argument name
pub name: String,
/// The argument description which will be shown on the help information.
/// Typically, this is a short (one line) description of the arg.
pub description: Option<String>,
/// The argument long description which will be shown on the help information.
/// Typically this a more detailed (multi-line) message that describes the argument.
pub long_description: Option<String>,
/// Specifies that the argument takes a value at run time.
///
/// NOTE: values for arguments may be specified in any of the following methods
/// - Using a space such as -o value or --option value
/// - Using an equals and no space such as -o=value or --option=value
/// - Use a short and no space such as -ovalue
pub takes_value: Option<bool>,
/// Specifies that the argument may appear more than once.
///
/// - For flags, this results in the number of occurrences of the flag being recorded.
/// For example -ddd or -d -d -d would count as three occurrences.
/// - For options there is a distinct difference in multiple occurrences vs multiple values.
/// For example, --opt val1 val2 is one occurrence, but two values. Whereas --opt val1 --opt val2 is two occurrences.
pub multiple: Option<bool>,
///
pub multiple_occurrences: Option<bool>,
///
pub number_of_values: Option<u64>,
/// Specifies a list of possible values for this argument.
/// At runtime, the CLI verifies that only one of the specified values was used, or fails with an error message.
pub possible_values: Option<Vec<String>>,
/// Specifies the minimum number of values for this argument.
/// For example, if you had a -f <file> argument where you wanted at least 2 'files',
/// you would set `minValues: 2`, and this argument would be satisfied if the user provided, 2 or more values.
pub min_values: Option<u64>,
/// Specifies the maximum number of values are for this argument.
/// For example, if you had a -f <file> argument where you wanted up to 3 'files',
/// you would set .max_values(3), and this argument would be satisfied if the user provided, 1, 2, or 3 values.
pub max_values: Option<u64>,
/// Sets whether or not the argument is required by default.
///
/// - Required by default means it is required, when no other conflicting rules have been evaluated
/// - Conflicting rules take precedence over being required.
pub required: Option<bool>,
/// Sets an arg that override this arg's required setting
/// i.e. this arg will be required unless this other argument is present.
pub required_unless: Option<String>,
/// Sets args that override this arg's required setting
/// i.e. this arg will be required unless all these other arguments are present.
pub required_unless_all: Option<Vec<String>>,
/// Sets args that override this arg's required setting
/// i.e. this arg will be required unless at least one of these other arguments are present.
pub required_unless_one: Option<Vec<String>>,
/// Sets a conflicting argument by name
/// i.e. when using this argument, the following argument can't be present and vice versa.
pub conflicts_with: Option<String>,
/// The same as conflictsWith but allows specifying multiple two-way conflicts per argument.
pub conflicts_with_all: Option<Vec<String>>,
/// Tets an argument by name that is required when this one is present
/// i.e. when using this argument, the following argument must be present.
pub requires: Option<String>,
/// Sts multiple arguments by names that are required when this one is present
/// i.e. when using this argument, the following arguments must be present.
pub requires_all: Option<Vec<String>>,
/// Allows a conditional requirement with the signature [arg, value]
/// the requirement will only become valid if `arg`'s value equals `${value}`.
pub requires_if: Option<Vec<String>>,
/// Allows specifying that an argument is required conditionally with the signature [arg, value]
/// the requirement will only become valid if the `arg`'s value equals `${value}`.
pub required_if: Option<Vec<String>>,
/// Requires that options use the --option=val syntax
/// i.e. an equals between the option and associated value.
pub require_equals: Option<bool>,
/// The positional argument index, starting at 1.
///
/// The index refers to position according to other positional argument.
/// It does not define position in the argument list as a whole. When utilized with multiple=true,
/// only the last positional argument may be defined as multiple (i.e. the one with the highest index).
pub index: Option<u64>,
}
/// The CLI root command definition.
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
#[serde(tag = "cli", rename_all = "camelCase")]
pub struct CliConfig {
description: Option<String>,
long_description: Option<String>,
before_help: Option<String>,
after_help: Option<String>,
args: Option<Vec<CliArg>>,
subcommands: Option<HashMap<String, CliConfig>>,
}
#[allow(dead_code)]
impl CliConfig {
/// List of args for the command
pub fn args(&self) -> Option<&Vec<CliArg>> {
self.args.as_ref()
}
/// List of subcommands of this command
pub fn subcommands(&self) -> Option<&HashMap<String, CliConfig>> {
self.subcommands.as_ref()
}
/// Command description which will be shown on the help information.
pub fn description(&self) -> Option<&String> {
self.description.as_ref()
}
/// Command long description which will be shown on the help information.
pub fn long_description(&self) -> Option<&String> {
self.description.as_ref()
}
/// Adds additional help information to be displayed in addition to auto-generated help.
/// This information is displayed before the auto-generated help information.
/// This is often used for header information.
pub fn before_help(&self) -> Option<&String> {
self.before_help.as_ref()
}
/// Adds additional help information to be displayed in addition to auto-generated help.
/// This information is displayed after the auto-generated help information.
/// This is often used to describe how to use the arguments, or caveats to be noted.
pub fn after_help(&self) -> Option<&String> {
self.after_help.as_ref()
}
}
/// The bundler configuration object.
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
#[serde(tag = "bundle", rename_all = "camelCase")]
pub struct BundleConfig {
#[serde(default)]
pub active: bool,
/// The bundle identifier.
pub identifier: String,
}
fn default_bundle() -> BundleConfig {
BundleConfig {
active: false,
identifier: String::from(""),
}
}
/// The Tauri configuration object.
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
#[serde(tag = "tauri", rename_all = "camelCase")]
pub struct TauriConfig {
/// The window configuration.
#[serde(default = "default_window")]
pub window: WindowConfig,
/// The embeddedServer configuration.
#[serde(default = "default_embedded_server")]
pub embedded_server: EmbeddedServerConfig,
/// The CLI configuration.
#[serde(default)]
pub cli: Option<CliConfig>,
/// The bundler configuration.
#[serde(default = "default_bundle")]
pub bundle: BundleConfig,
#[serde(default)]
pub inliner: InlinerConfig,
#[serde(default)]
pub allowlist: HashMap<String, bool>,
}
#[derive(Default, PartialEq, Clone, Deserialize, Serialize, Debug)]
pub struct InlinerConfig {
pub active: bool,
}
/// The Build configuration object.
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
#[serde(tag = "build", rename_all = "camelCase")]
pub struct BuildConfig {
/// the devPath config.
#[serde(default = "default_dev_path")]
pub dev_path: String,
#[serde(default = "default_dist_dir")]
pub dist_dir: String,
pub before_dev_command: Option<String>,
pub before_build_command: Option<String>,
#[serde(default)]
pub with_global_tauri: bool,
}
fn default_dev_path() -> String {
"".to_string()
}
fn default_dist_dir() -> String {
"../dist".to_string()
}
type JsonObject = HashMap<String, JsonValue>;
/// The tauri.conf.json mapper.
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Config {
/// The Tauri configuration.
#[serde(default = "default_tauri")]
pub tauri: TauriConfig,
/// The build configuration.
#[serde(default = "default_build")]
pub build: BuildConfig,
/// The plugins config.
#[serde(default)]
pub plugins: HashMap<String, JsonObject>,
}
fn default_tauri() -> TauriConfig {
TauriConfig {
window: default_window(),
embedded_server: default_embedded_server(),
cli: None,
bundle: default_bundle(),
inliner: Default::default(),
allowlist: Default::default(),
}
}
fn default_build() -> BuildConfig {
BuildConfig {
dev_path: default_dev_path(),
dist_dir: default_dist_dir(),
before_dev_command: None,
before_build_command: None,
with_global_tauri: false,
}
}
/// Gets the static parsed config from `tauri.conf.json`.
fn get_internal(merge_config: Option<&str>, reload: bool) -> crate::Result<&'static Config> {
if let Some(config) = CONFIG.get() {
if !reload {
return Ok(config);
}
}
let path = super::app_paths::tauri_dir().join("tauri.conf.json");
let file = File::open(path)?;
let buf = BufReader::new(file);
let mut config: JsonValue = serde_json::from_reader(buf)?;
if let Some(merge_config) = merge_config {
let merge_config: JsonValue = serde_json::from_str(&merge_config)?;
merge(&mut config, &merge_config);
}
let config = serde_json::from_value(config)?;
CONFIG
.set(config)
.map_err(|_| anyhow::anyhow!("failed to set CONFIG"))?;
let config = CONFIG.get().unwrap();
Ok(config)
}
pub fn get(merge_config: Option<&str>) -> crate::Result<&'static Config> {
get_internal(merge_config, false)
}
pub fn reload(merge_config: Option<&str>) -> crate::Result<&'static Config> {
get_internal(merge_config, true)
}

View File

@ -0,0 +1,35 @@
use colored::Colorize;
pub struct Logger<'a> {
context: &'a str,
}
impl<'a> Logger<'a> {
pub fn new(context: &'a str) -> Self {
Self { context }
}
pub fn log(&self, message: impl AsRef<str>) {
println!(
"{} {}",
format!("[{}]", self.context).green().bold(),
message.as_ref()
);
}
pub fn warn(&self, message: impl AsRef<str>) {
println!(
"{} {}",
format!("[{}]", self.context).yellow().bold(),
message.as_ref()
);
}
pub fn error(&self, message: impl AsRef<str>) {
println!(
"{} {}",
format!("[{}]", self.context).red().bold(),
message.as_ref()
);
}
}

View File

@ -0,0 +1,65 @@
use super::{app_paths::tauri_dir, config::Config};
use convert_case::{Case, Casing};
use toml_edit::{Array, Document, Value};
use std::fs::File;
use std::io::{Read, Write};
pub fn rewrite_manifest(config: &Config) -> crate::Result<()> {
let manifest_path = tauri_dir().join("Cargo.toml");
let mut manifest_str = String::new();
let mut manifest_file = File::open(&manifest_path)?;
manifest_file.read_to_string(&mut manifest_str)?;
let mut manifest: Document = manifest_str.parse::<Document>()?;
let dependencies = manifest
.as_table_mut()
.entry("dependencies")
.as_table_mut()
.expect("manifest dependencies isn't a table");
let entry = dependencies.entry("tauri");
let tauri = entry.as_value_mut();
if let Some(tauri) = tauri {
let mut features: Array = Default::default();
let allowlist = &config.tauri.allowlist;
if *allowlist.get("all").unwrap_or(&false) {
features.push("all-api".to_string()).unwrap();
} else {
for (feature, enabled) in allowlist.iter() {
if *enabled {
features.push(feature.to_case(Case::Kebab)).unwrap();
}
}
}
if config.tauri.cli.is_some() {
features.push("cli".to_string()).unwrap();
}
match tauri {
Value::InlineTable(tauri_def) => {
let manifest_features =
tauri_def.get_or_insert("features", Value::Array(Default::default()));
*manifest_features = Value::Array(features);
}
_ => {
return Err(anyhow::anyhow!(
"Unsupported tauri dependency format on Cargo.toml"
))
}
}
let mut manifest_file = File::create(&manifest_path)?;
manifest_file.write_all(
manifest
.to_string_in_original_order()
.replace(r#"" ,features =["#, r#"", features = ["#)
.as_bytes(),
)?;
manifest_file.flush()?;
}
Ok(())
}

View File

@ -0,0 +1,33 @@
pub mod app_paths;
pub mod config;
mod logger;
pub mod manifest;
mod tauri_html;
pub use logger::Logger;
pub use tauri_html::TauriHtml;
use std::io::{BufRead, BufReader};
use std::process::{Command, Stdio};
pub fn execute_with_output(cmd: &mut Command) -> crate::Result<()> {
let mut child = cmd
.stdout(Stdio::piped())
.spawn()
.expect("failed to spawn command");
{
let stdout = child.stdout.as_mut().expect("Failed to get stdout handle");
let reader = BufReader::new(stdout);
for line in reader.lines() {
println!("{}", line.expect("Failed to get line"));
}
}
let status = child.wait()?;
if status.success() {
Ok(())
} else {
Err(anyhow::anyhow!("command failed"))
}
}

View File

@ -0,0 +1,107 @@
use handlebars::Handlebars;
use html5ever::{interface::QualName, namespace_url, ns, LocalName};
use kuchiki::{traits::*, NodeRef};
use once_cell::sync::Lazy;
use tauri_inliner::inline_html_string;
use std::collections::BTreeMap;
use std::path::PathBuf;
fn handlebars() -> &'static Handlebars<'static> {
static HANDLEBARS: Lazy<Handlebars> = Lazy::new(|| {
let mut handlebars = Handlebars::new();
handlebars
.register_template_string(
"mutation-observer.js",
include_str!("../templates/mutation-observer.js"),
)
.map_err(|e| e.to_string())
.expect("Failed to setup handlebar template");
handlebars
});
&HANDLEBARS
}
pub struct TauriHtml {
original: String,
html_dir: PathBuf,
inliner_enabled: bool,
global_tauri: bool,
}
impl TauriHtml {
pub fn new(html_dir: impl Into<PathBuf>, html: String) -> Self {
Self {
original: html,
html_dir: html_dir.into(),
inliner_enabled: false,
global_tauri: false,
}
}
pub fn inliner_enabled(mut self, enabled: bool) -> Self {
self.inliner_enabled = enabled;
self
}
pub fn global_tauri(mut self, global_tauri: bool) -> Self {
self.global_tauri = global_tauri;
self
}
pub fn generate(self) -> crate::Result<String> {
let html = if self.inliner_enabled {
inline_html_string(&self.original, self.html_dir, Default::default())?
} else {
self.original
};
let document = kuchiki::parse_html().one(html);
let body = document.select_first("body");
let head = document.select_first("head");
let handlebars = handlebars();
if let Ok(ref head) = head {
let mut data = BTreeMap::new();
data.insert("target".to_string(), "head".to_string());
let head_mutation_observer_script =
create_script_element(&handlebars.render("mutation-observer.js", &data)?);
head.as_node().prepend(head_mutation_observer_script);
}
if let Ok(ref body) = body {
let mut data = BTreeMap::new();
data.insert("target".to_string(), "body".to_string());
let body_mutation_observer_script =
create_script_element(&handlebars.render("mutation-observer.js", &data)?);
body.as_node().prepend(body_mutation_observer_script);
}
if let Ok(target) = if head.is_ok() { head } else { body } {
let tauri_script = create_script_element(include_str!("../templates/tauri.js"));
target.as_node().prepend(tauri_script);
if self.global_tauri {
let global_api_script = create_script_element(include_str!(concat!(
env!("OUT_DIR"),
"/tauri.bundle.umd.js"
)));
target.as_node().prepend(global_api_script);
}
}
let new_html = document.to_string();
Ok(new_html)
}
}
fn create_script_element(content: &str) -> NodeRef {
let script = NodeRef::new_element(
QualName::new(None, ns!(html), LocalName::from("script")),
None,
);
script.append(NodeRef::new_text(content));
script
}

12
cli/core/src/info.rs Normal file
View File

@ -0,0 +1,12 @@
#[derive(Default)]
pub struct Info;
impl Info {
pub fn new() -> Self {
Default::default()
}
pub fn run(self) -> crate::Result<()> {
unimplemented!()
}
}

91
cli/core/src/init.rs Normal file
View File

@ -0,0 +1,91 @@
#![allow(dead_code)]
use std::convert::TryFrom;
use std::path::PathBuf;
pub enum ForceType {
All,
Config,
Template,
}
impl TryFrom<&str> for ForceType {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value.to_lowercase().as_str() {
"all" => Ok(Self::All),
"conf" => Ok(Self::Config),
"template" => Ok(Self::Template),
_ => Err(anyhow::anyhow!("Invalid `force` value.")),
}
}
}
pub struct Init {
force: Option<ForceType>,
directory: PathBuf,
tauri_path: Option<PathBuf>,
app_name: Option<String>,
window_title: Option<String>,
dist_dir: Option<String>,
dev_path: Option<String>,
}
impl Default for Init {
fn default() -> Self {
Self {
force: None,
directory: std::env::current_dir().expect("failed to read cwd"),
tauri_path: None,
app_name: None,
window_title: None,
dist_dir: None,
dev_path: None,
}
}
}
impl Init {
pub fn new() -> Self {
Default::default()
}
pub fn force(mut self, force: ForceType) -> Self {
self.force = Some(force);
self
}
pub fn directory(mut self, directory: impl Into<PathBuf>) -> Self {
self.directory = directory.into();
self
}
pub fn tauri_path(mut self, tauri_path: impl Into<PathBuf>) -> Self {
self.tauri_path = Some(tauri_path.into());
self
}
pub fn app_name(mut self, app_name: impl Into<String>) -> Self {
self.app_name = Some(app_name.into());
self
}
pub fn window_title(mut self, window_title: impl Into<String>) -> Self {
self.window_title = Some(window_title.into());
self
}
pub fn dist_dir(mut self, dist_dir: impl Into<String>) -> Self {
self.dist_dir = Some(dist_dir.into());
self
}
pub fn dev_path(mut self, dev_path: impl Into<String>) -> Self {
self.dev_path = Some(dev_path.into());
self
}
pub fn run(self) -> crate::Result<()> {
unimplemented!()
}
}

109
cli/core/src/main.rs Normal file
View File

@ -0,0 +1,109 @@
pub use anyhow::Result;
use clap::{crate_version, load_yaml, App, AppSettings, ArgMatches};
use std::convert::TryInto;
mod build;
mod dev;
mod helpers;
mod info;
mod init;
pub use helpers::Logger;
fn init_command(matches: &ArgMatches) -> Result<()> {
let force = matches.value_of("force");
let directory = matches.value_of("directory");
let tauri_path = matches.value_of("tauri_path");
let app_name = matches.value_of("app_name");
let window_title = matches.value_of("window_title");
let dist_dir = matches.value_of("dist_dir");
let dev_path = matches.value_of("dev_path");
let mut init_runner = init::Init::new();
if let Some(force) = force {
init_runner = init_runner.force(force.try_into()?);
}
if let Some(directory) = directory {
init_runner = init_runner.directory(directory);
}
if let Some(tauri_path) = tauri_path {
init_runner = init_runner.tauri_path(tauri_path);
}
if let Some(app_name) = app_name {
init_runner = init_runner.app_name(app_name);
}
if let Some(window_title) = window_title {
init_runner = init_runner.window_title(window_title);
}
if let Some(dist_dir) = dist_dir {
init_runner = init_runner.dist_dir(dist_dir);
}
if let Some(dev_path) = dev_path {
init_runner = init_runner.directory(dev_path);
}
init_runner.run()
}
fn dev_command(matches: &ArgMatches) -> Result<()> {
let exit_on_panic = matches.is_present("exit-on-panic");
let config = matches.value_of("config");
let mut dev_runner = dev::Dev::new().exit_on_panic(exit_on_panic);
if let Some(config) = config {
dev_runner = dev_runner.config(config.to_string());
}
dev_runner.run()
}
fn build_command(matches: &ArgMatches) -> Result<()> {
let debug = matches.is_present("debug");
let verbose = matches.is_present("verbose");
let targets = matches.values_of_lossy("target");
let config = matches.value_of("config");
let mut build_runner = build::Build::new();
if debug {
build_runner = build_runner.debug();
}
if verbose {
build_runner = build_runner.verbose();
}
if let Some(targets) = targets {
build_runner = build_runner.targets(targets);
}
if let Some(config) = config {
build_runner = build_runner.config(config.to_string());
}
build_runner.run()
}
fn info_command() -> Result<()> {
info::Info::new().run()
}
fn main() -> Result<()> {
let yaml = load_yaml!("cli.yml");
let app = App::from(yaml)
.version(crate_version!())
.setting(AppSettings::ArgRequiredElseHelp)
.setting(AppSettings::GlobalVersion)
.setting(AppSettings::SubcommandRequired);
let app_matches = app.get_matches();
let matches = app_matches.subcommand_matches("tauri").unwrap();
if let Some(matches) = matches.subcommand_matches("init") {
init_command(&matches)?;
} else if let Some(matches) = matches.subcommand_matches("dev") {
dev_command(&matches)?;
} else if let Some(matches) = matches.subcommand_matches("build") {
build_command(&matches)?;
} else if matches.subcommand_matches("info").is_some() {
info_command()?;
}
Ok(())
}

View File

@ -23,11 +23,11 @@
})
})
<% if (target === 'body') { %>
{{#if (eq target "body")}}
var target = document.documentElement
<% } else { %>
{{ else }}
var target = document.head
<% } %>
{{/if}}
observer.observe(target, {
childList: true,

View File

@ -296,4 +296,4 @@ if (!String.prototype.startsWith) {
message: message
})
}
})()
})()

@ -1 +0,0 @@
Subproject commit 55fd9104fa4da3b7afb96a16bb1ed8378b028a5a

1803
cli/tauri-bundler/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
workspace = { }
workspace = {}
[package]
name = "tauri-bundler"
@ -34,7 +34,7 @@ serde = { version = "1.0", features = [ "derive" ] }
strsim = "0.10.0"
tar = "0.4"
target_build_utils = "0.3"
term = "0.7.0"
termcolor = "1.1.2"
toml = "0.5.8"
uuid = { version = "0.8", features = [ "v5" ] }
walkdir = "2"
@ -56,4 +56,8 @@ tempfile = "3"
[[bin]]
name = "cargo-tauri-bundler"
path = "src/main.rs"
path = "src/bin.rs"
[lib]
name = "tauri_bundler"
path = "src/lib.rs"

View File

@ -1,41 +1,14 @@
mod bundle;
mod error;
pub use error::{Error, Result};
use crate::bundle::{bundle_project, check_icons, PackageType, Settings};
use tauri_bundler::{
build_project,
bundle::{bundle_project, check_icons, print_error, PackageType, SettingsBuilder},
};
pub use tauri_bundler::{Error, Result};
use clap::{crate_version, App, AppSettings, Arg, SubCommand};
#[cfg(windows)]
use runas::Command;
use std::env;
use std::process;
// Runs `cargo build` to make sure the binary file is up-to-date.
fn build_project_if_unbuilt(settings: &Settings) -> crate::Result<()> {
let mut args = vec!["build".to_string()];
if let Some(triple) = settings.target_triple() {
args.push(format!("--target={}", triple));
}
if settings.is_release_build() {
args.push("--release".to_string());
}
if let Some(features) = settings.build_features() {
args.push(format!("--features={}", features.join(" ")));
}
let status = process::Command::new("cargo").args(args).status()?;
if !status.success() {
return Err(crate::Error::GenericError(format!(
"Result of `cargo build` operation was unsuccessful: {}",
status
)));
}
Ok(())
}
// Runs the CLI.
fn run() -> crate::Result<()> {
@ -98,59 +71,51 @@ fn run() -> crate::Result<()> {
.long("version")
.short("v")
.help("Read the version of the bundler"),
).arg(
Arg::with_name("verbose")
.long("verbose")
.help("Enable verbose output"),
),
)
.get_matches();
#[cfg(windows)]
{
if let Ok(tauri_config) = crate::bundle::tauri_config::get() {
if tauri_config.tauri.embedded_server.active {
let exempt_output = std::process::Command::new("CheckNetIsolation")
.args(&vec!["LoopbackExempt", "-s"])
.output()
.expect("failed to read LoopbackExempt -s");
if !exempt_output.status.success() {
panic!("Failed to execute CheckNetIsolation LoopbackExempt -s");
}
let output_str = String::from_utf8_lossy(&exempt_output.stdout).to_lowercase();
if !output_str.contains("win32webviewhost_cw5n1h2txyewy") {
println!("Running Loopback command");
Command::new("powershell")
.args(&[
"CheckNetIsolation LoopbackExempt -a -n=\"Microsoft.Win32WebViewHost_cw5n1h2txyewy\"",
])
.force_prompt(true)
.status()
.expect("failed to run Loopback command");
}
}
}
}
if let Some(m) = m.subcommand_matches("tauri-bundler") {
if m.is_present("version") {
if let Some(matches) = m.subcommand_matches("tauri-bundler") {
if matches.is_present("version") {
println!("{}", crate_version!());
} else {
let output_paths = env::current_dir()
.map_err(From::from)
.and_then(|d| Settings::new(d, m))
let mut settings_builder = SettingsBuilder::new();
if let Some(names) = matches.values_of("format") {
let mut types = vec![];
for name in names {
match PackageType::from_short_name(name) {
Some(package_type) => {
types.push(package_type);
}
None => {
return Err(crate::Error::GenericError(format!(
"Unsupported bundle format: {}",
name
)));
}
}
}
settings_builder = settings_builder.package_types(types);
}
if let Some(triple) = matches.value_of("target") {
settings_builder = settings_builder.target(triple.to_string());
}
if let Some(features) = matches.values_of_lossy("features") {
settings_builder = settings_builder.features(features);
}
settings_builder
.build()
.and_then(|s| {
if check_icons(&s)? {
build_project_if_unbuilt(&s)?;
build_project(&s)?;
Ok(s)
} else {
Err(crate::Error::IconPathError)
}
})
.and_then(bundle_project)?;
bundle::print_finished(&output_paths)?;
}
}
Ok(())
@ -158,6 +123,6 @@ fn run() -> crate::Result<()> {
fn main() {
if let Err(error) = run() {
bundle::print_error(&error.into()).expect("Failed to call print error in main");
print_error(&error.into()).expect("Failed to call print error in main");
}
}

View File

@ -15,9 +15,15 @@ pub mod tauri_config;
#[cfg(target_os = "windows")]
mod wix;
#[cfg(windows)]
use std::process::Command;
#[cfg(windows)]
use tauri_config::get as get_tauri_config;
pub use self::common::print_error;
pub use self::common::print_info;
pub use self::common::{print_error, print_finished};
pub use self::settings::{PackageType, Settings};
pub use self::settings::{PackageType, Settings, SettingsBuilder};
use common::print_finished;
use std::path::PathBuf;
@ -56,6 +62,36 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<PathBuf>> {
settings.copy_resources(settings.project_out_directory())?;
settings.copy_binaries(settings.project_out_directory())?;
#[cfg(windows)]
{
if let Ok(tauri_config) = get_tauri_config() {
if tauri_config.tauri.embedded_server.active {
let exempt_output = Command::new("CheckNetIsolation")
.args(&vec!["LoopbackExempt", "-s"])
.output()
.expect("failed to read LoopbackExempt -s");
if !exempt_output.status.success() {
panic!("Failed to execute CheckNetIsolation LoopbackExempt -s");
}
let output_str = String::from_utf8_lossy(&exempt_output.stdout).to_lowercase();
if !output_str.contains("win32webviewhost_cw5n1h2txyewy") {
println!("Running Loopback command");
runas::Command::new("powershell")
.args(&[
"CheckNetIsolation LoopbackExempt -a -n=\"Microsoft.Win32WebViewHost_cw5n1h2txyewy\"",
])
.force_prompt(true)
.status()
.expect("failed to run Loopback command");
}
}
}
}
print_finished(&paths)?;
Ok(paths)
}

View File

@ -4,6 +4,7 @@ use std::fs::{self, File};
use std::io::{self, BufRead, BufReader, BufWriter, Write};
use std::path::{Component, Path, PathBuf};
use std::process::{Command, Stdio};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
/// Returns true if the path has a filename indicating that it is a high-desity
/// "retina" icon. Specifically, returns true the the file stem ends with
@ -161,108 +162,57 @@ pub fn print_finished(output_paths: &[PathBuf]) -> crate::Result<()> {
Ok(())
}
/// Safely adds the terminal attribute to the terminal output.
/// If the terminal doesn't support the attribute, does nothing.
fn safe_term_attr<T: term::Terminal + ?Sized>(
output: &mut T,
attr: term::Attr,
) -> term::Result<()> {
if output.supports_attr(attr) {
output.attr(attr)
} else {
Ok(())
}
}
/// Prints a formatted bundle progress to stderr.
fn print_progress(step: &str, msg: &str) -> crate::Result<()> {
if let Some(mut output) = term::stderr() {
safe_term_attr(&mut *output, term::Attr::Bold)?;
output.fg(term::color::GREEN)?;
write!(output, " {}", step)?;
output.reset()?;
writeln!(output, " {}", msg)?;
output.flush()?;
Ok(())
} else {
let mut output = io::stderr();
write!(output, " {}", step)?;
writeln!(output, " {}", msg)?;
output.flush()?;
Ok(())
}
let mut output = StandardStream::stderr(ColorChoice::Always);
let _ = output.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true));
write!(output, " {}", step)?;
output.reset()?;
writeln!(output, " {}", msg)?;
output.flush()?;
Ok(())
}
/// Prints a warning message to stderr, in the same format that `cargo` uses.
pub fn print_warning(message: &str) -> crate::Result<()> {
if let Some(mut output) = term::stderr() {
safe_term_attr(&mut *output, term::Attr::Bold)?;
output.fg(term::color::YELLOW)?;
write!(output, "warning:")?;
output.reset()?;
writeln!(output, " {}", message)?;
output.flush()?;
Ok(())
} else {
let mut output = io::stderr();
write!(output, "warning:")?;
writeln!(output, " {}", message)?;
output.flush()?;
Ok(())
}
let mut output = StandardStream::stderr(ColorChoice::Always);
let _ = output.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)).set_bold(true));
write!(output, "warning:")?;
output.reset()?;
writeln!(output, " {}", message)?;
output.flush()?;
Ok(())
}
/// Prints a Info message to stderr.
pub fn print_info(message: &str) -> crate::Result<()> {
if let Some(mut output) = term::stderr() {
safe_term_attr(&mut *output, term::Attr::Bold)?;
output.fg(term::color::GREEN)?;
write!(output, "info:")?;
output.reset()?;
writeln!(output, " {}", message)?;
output.flush()?;
Ok(())
} else {
let mut output = io::stderr();
write!(output, "info:")?;
writeln!(output, " {}", message)?;
output.flush()?;
Ok(())
}
let mut output = StandardStream::stderr(ColorChoice::Always);
let _ = output.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true));
write!(output, "info:")?;
output.reset()?;
writeln!(output, " {}", message)?;
output.flush()?;
Ok(())
}
/// Prints an error to stderr, in the same format that `cargo` uses.
pub fn print_error(error: &anyhow::Error) -> crate::Result<()> {
if let Some(mut output) = term::stderr() {
safe_term_attr(&mut *output, term::Attr::Bold)?;
output.fg(term::color::RED)?;
write!(output, "error:")?;
output.reset()?;
safe_term_attr(&mut *output, term::Attr::Bold)?;
writeln!(output, " {}", error)?;
output.reset()?;
for cause in error.chain().skip(1) {
writeln!(output, " Caused by: {}", cause)?;
}
// Add Backtrace once its stable.
// if let Some(backtrace) = error.backtrace() {
// writeln!(output, "{:?}", backtrace)?;
// }
output.flush()?;
std::process::exit(1)
} else {
let mut output = io::stderr();
write!(output, "error:")?;
writeln!(output, " {}", error)?;
for cause in error.chain().skip(1) {
writeln!(output, " Caused by: {}", cause)?;
}
// if let Some(backtrace) = error.backtrace() {
// writeln!(output, "{:?}", backtrace)?;
// }
output.flush()?;
std::process::exit(1)
let mut output = StandardStream::stderr(ColorChoice::Always);
let _ = output.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true));
write!(output, "error:")?;
output.reset()?;
let _ = output.set_color(ColorSpec::new().set_bold(true));
writeln!(output, " {}", error)?;
output.reset()?;
for cause in error.chain().skip(1) {
writeln!(output, " Caused by: {}", cause)?;
}
// Add Backtrace once its stable.
// if let Some(backtrace) = error.backtrace() {
// writeln!(output, "{:?}", backtrace)?;
// }
output.flush()?;
std::process::exit(1)
}
pub fn execute_with_verbosity(cmd: &mut Command, settings: &Settings) -> crate::Result<()> {

View File

@ -2,7 +2,6 @@ use super::category::AppCategory;
use crate::bundle::common;
use crate::bundle::platform::target_triple;
use clap::ArgMatches;
use serde::Deserialize;
use target_build_utils::TargetInfo;
@ -164,14 +163,12 @@ struct BundleSettings {
/// The `metadata` section of the package configuration.
///
/// # Example Cargo.toml
/// ```
/// [package]
/// name = "..."
///
/// [package.metadata.bundle]
/// identifier = "..."
/// ...other properties from BundleSettings
/// ```
#[derive(Clone, Debug, Deserialize)]
struct MetadataSettings {
/// the bundle settings of the package.
@ -307,50 +304,52 @@ struct CargoConfig {
build: Option<CargoBuildConfig>,
}
impl Settings {
#[derive(Default)]
pub struct SettingsBuilder {
target_triple: Option<String>,
release: bool,
verbose: bool,
package_types: Option<Vec<PackageType>>,
features: Option<Vec<String>>,
}
impl SettingsBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn target(mut self, target_triple: String) -> Self {
self.target_triple = Some(target_triple);
self
}
pub fn release(mut self) -> Self {
self.release = true;
self
}
pub fn verbose(mut self) -> Self {
self.verbose = true;
self
}
pub fn package_types(mut self, package_types: Vec<PackageType>) -> Self {
self.package_types = Some(package_types);
self
}
pub fn features(mut self, features: Vec<String>) -> Self {
self.features = Some(features);
self
}
/// Builds a Settings from the CLI args.
///
/// Package settings will be read from Cargo.toml.
///
/// Bundle settings will be read from from $TAURI_DIR/tauri.conf.json if it exists and fallback to Cargo.toml's [package.metadata.bundle].
pub fn new(current_dir: PathBuf, matches: &ArgMatches<'_>) -> crate::Result<Self> {
let package_types = match matches.values_of("format") {
Some(names) => {
let mut types = vec![];
for name in names {
match PackageType::from_short_name(name) {
Some(package_type) => {
types.push(package_type);
}
None => {
return Err(crate::Error::GenericError(format!(
"Unsupported bundle format: {}",
name
)));
}
}
}
Some(types)
}
None => None,
};
let is_release = matches.is_present("release");
let is_verbose = matches.is_present("verbose");
let target = match matches.value_of("target") {
Some(triple) => Some((triple.to_string(), TargetInfo::from_str(triple)?)),
None => None,
};
let features = if matches.is_present("features") {
Some(
matches
.values_of("features")
.expect("Couldn't get the features")
.map(|s| s.to_string())
.collect(),
)
} else {
None
};
pub fn build(self) -> crate::Result<Settings> {
let current_dir = std::env::current_dir()?;
let cargo_settings = CargoSettings::load(&current_dir)?;
let tauri_config = super::tauri_config::get();
@ -363,7 +362,12 @@ impl Settings {
}
};
let workspace_dir = Settings::get_workspace_dir(&current_dir);
let target_dir = Settings::get_target_dir(&workspace_dir, &target, is_release)?;
let target = if let Some(target_triple) = self.target_triple {
Some((target_triple.clone(), TargetInfo::from_str(&target_triple)?))
} else {
None
};
let target_dir = Settings::get_target_dir(&workspace_dir, &target, self.release)?;
let bundle_settings = match tauri_config {
Ok(config) => merge_settings(BundleSettings::default(), config.tauri.bundle),
Err(e) => {
@ -438,17 +442,19 @@ impl Settings {
Ok(Settings {
package,
package_types,
package_types: self.package_types,
target,
features,
is_release,
is_verbose,
features: self.features,
is_release: self.release,
is_verbose: self.verbose,
project_out_directory: target_dir,
binaries,
bundle_settings,
})
}
}
impl Settings {
/// This function determines where 'target' dir is and suffixes it with 'release' or 'debug'
/// to determine where the compiled binary will be located.
fn get_target_dir(

View File

@ -1,15 +1,6 @@
use thiserror::Error as DeriveError;
use {
glob, handlebars, image, serde_json, std::io, std::num, std::path, target_build_utils, term,
toml, walkdir,
};
#[cfg(windows)]
use {attohttpc, regex};
#[cfg(not(target_os = "linux"))]
use {hex, zip};
use std::{io, num, path};
#[derive(Debug, DeriveError)]
pub enum Error {
@ -26,8 +17,6 @@ pub enum Error {
#[error("`{0}`")]
TargetError(#[from] target_build_utils::Error),
#[error("`{0}`")]
TermError(#[from] term::Error),
#[error("`{0}`")]
TomlError(#[from] toml::de::Error),
#[error("`{0}`")]
WalkdirError(#[from] walkdir::Error),

View File

@ -0,0 +1,32 @@
pub mod bundle;
mod error;
pub use error::{Error, Result};
use bundle::Settings;
use std::process;
// Runs `cargo build` to make sure the binary file is up-to-date.
pub fn build_project(settings: &Settings) -> crate::Result<()> {
let mut args = vec!["build".to_string()];
if let Some(triple) = settings.target_triple() {
args.push(format!("--target={}", triple));
}
if settings.is_release_build() {
args.push("--release".to_string());
}
if let Some(features) = settings.build_features() {
args.push(format!("--features={}", features.join(" ")));
}
let status = process::Command::new("cargo").args(args).status()?;
if !status.success() {
return Err(crate::Error::GenericError(format!(
"Result of `cargo build` operation was unsuccessful: {}",
status
)));
}
Ok(())
}

View File

@ -1,7 +1,5 @@
# Build output
/dist
/api
# Logs
logs

View File

@ -1,24 +0,0 @@
import 'regenerator-runtime/runtime'
import * as cli from './cli'
import * as dialog from './dialog'
import * as event from './event'
import * as fs from './fs'
import * as path from './path'
import http from './http'
import * as process from './process'
import * as tauri from './tauri'
import * as window from './window'
import * as notification from './notification'
export {
cli,
dialog,
event,
fs,
path,
http,
process,
tauri,
window,
notification
}

View File

@ -1,2 +0,0 @@
import * as api from './bundle'
export default api

View File

@ -1,35 +0,0 @@
import { promisified } from './tauri'
export interface Options {
title: string
body?: string
icon?: string
}
export type PartialOptions = Omit<Options, 'title'>
export type Permission = 'granted' | 'denied' | 'default'
async function isPermissionGranted(): Promise<boolean | null> {
if (window.Notification.permission !== 'default') {
return await Promise.resolve(window.Notification.permission === 'granted')
}
return await promisified({
cmd: 'isNotificationPermissionGranted'
})
}
async function requestPermission(): Promise<Permission> {
return await window.Notification.requestPermission()
}
function sendNotification(options: Options | string): void {
if (typeof options === 'string') {
// eslint-disable-next-line no-new
new window.Notification(options)
} else {
// eslint-disable-next-line no-new
new window.Notification(options.title, options)
}
}
export { sendNotification, requestPermission, isPermissionGranted }

View File

@ -1,40 +0,0 @@
const parseArgs = require('minimist')
const argv = parseArgs(process.argv.slice(2), {
alias: {
h: 'help',
d: 'debug',
t: 'target',
v: 'verbose'
},
boolean: ['h', 'd', 'v']
})
if (argv.help) {
console.log(`
Description
Tauri build.
Usage
$ tauri build
Options
--help, -h Displays this message
--debug, -d Builds with the debug flag
--target, -t Comma-separated list of target triples to build against
--verbose, -v Enable verbose logging
`)
process.exit(0)
}
async function run() {
const build = require('../dist/api/build')
await build({
ctx: {
debug: argv.debug,
target: argv.target
},
verbose: argv.verbose
}).promise
}
run()

View File

@ -1,33 +0,0 @@
const parseArgs = require('minimist')
const argv = parseArgs(process.argv.slice(2), {
alias: {
h: 'help',
e: 'exit-on-panic'
},
boolean: ['h', 'e']
})
if (argv.help) {
console.log(`
Description
Tauri dev.
Usage
$ tauri dev
Options
--help, -h Displays this message
`)
process.exit(0)
}
async function run() {
const dev = require('../dist/api/dev')
await dev({
ctx: {
exitOnPanic: argv['exit-on-panic']
}
}).promise
}
run()

View File

@ -1,8 +1,10 @@
#!/usr/bin/env node
const cmds = ['create', 'init', 'dev', 'build', 'help', 'icon', 'info', 'deps']
const cmds = ['create', 'init', 'help', 'icon', 'info', 'deps']
const rustCliCmds = ['dev', 'build']
const cmd = process.argv[2]
/**
* @description This is the bootstrapper that in turn calls subsequent
* Tauri Commands
@ -15,6 +17,15 @@ const tauri = function (command) {
command = command[0]
}
if (rustCliCmds.includes(command)) {
const { runOnRustCli } = require('../dist/helpers/rust-cli')
if (process.argv && !process.env.test) {
process.argv.splice(0, 3)
}
runOnRustCli(command, process.argv || [])
return
}
if (
!command ||
command === '-h' ||

View File

@ -31,6 +31,7 @@ module.exports = {
moduleFileExtensions: ['ts', 'js', 'json'],
moduleNameMapper: {
'^~/(.*)$': '<rootDir>/$1',
'^dist/(.*)$': '<rootDir>/dist/$1',
'^bin/(.*)$': '<rootDir>/bin/$1',
'^helpers/(.*)$': '<rootDir>/src/helpers/$1',
'^api/(.*)$': '<rootDir>/src/api/$1',
@ -39,9 +40,7 @@ module.exports = {
'../../package.json': '<rootDir>/package.json'
},
transform: {
'templates[\\\\/](tauri|mutation-observer).js':
'./test/jest/raw-loader-transformer.js',
'api[\\\\/]tauri.bundle.umd.js': './test/jest/raw-loader-transformer.js',
'templates[\\\\/]tauri.js': './test/jest/raw-loader-transformer.js',
'\\.(js|ts)$': 'babel-jest'
}
}

View File

@ -1,7 +1,7 @@
{
"name": "tauri",
"name": "@tauri-apps/cli",
"version": "0.14.1",
"description": "Multi-binding collection of libraries and templates for building Tauri apps",
"description": "Command line interface for building Tauri apps",
"bin": {
"tauri": "./bin/tauri.js"
},
@ -10,20 +10,18 @@
"url": "https://opencollective.com/tauri"
},
"scripts": {
"build": "yarn build:api && yarn build:webpack",
"build:webpack": "rimraf ./dist && yarn build:typevalidators && webpack --progress",
"build:typevalidators": "node ./build/type-validators",
"build:api": "rimraf ./api && rollup -c --silent",
"build-release": "yarn build:api && rimraf ./dist && yarn build:typevalidators && webpack",
"build": "rimraf ./dist && yarn build:typevalidators && webpack --progress",
"build-release": "rimraf ./dist && yarn build:typevalidators && webpack",
"test": "jest --runInBand --no-cache --testPathIgnorePatterns=\"(build|dev)\"",
"pretest": "yarn build",
"prepublishOnly": "yarn build-release",
"test:local": "jest --runInBand",
"lint": "eslint --ext ts \"./src/**/*.ts\" \"./api-src/**/*.ts\"",
"lint-fix": "eslint --fix --ext ts \"./src/**/*.ts\" \"./api-src/**/*.ts\"",
"lint": "eslint --ext ts \"./src/**/*.ts\"",
"lint-fix": "eslint --fix --ext ts \"./src/**/*.ts\"",
"lint:lockfile": "lockfile-lint --path yarn.lock --type yarn --validate-https --allowed-hosts npm yarn",
"format": "prettier --write --end-of-line=auto !./**/mutation-observer.js !./**/config.validator.js \"./**/*.{js,jsx,ts,tsx,html,css,json}\" --ignore-path .gitignore",
"format:check": "prettier --check --end-of-line=auto !./**/mutation-observer.js \"./**/*.{js,jsx,ts,tsx,html,css,json}\" --ignore-path .gitignore",
"format": "prettier --write --end-of-line=auto !./**/config.validator.js !./**/config.schema.json \"./**/*.{js,jsx,ts,tsx,html,css,json}\" --ignore-path .gitignore",
"format:check": "prettier --check --end-of-line=auto !./**/config.validator.js !./**/config.schema.json \"./**/*.{js,jsx,ts,tsx,html,css,json}\" --ignore-path .gitignore",
"build:tauri[rust]": "cd ../tauri && TAURI_DIST_DIR=../../test/fixture/dist TAURI_DIR=../test/fixture cargo publish --dry-run --allow-dirty"
},
"repository": {
@ -49,7 +47,6 @@
"yarn": ">= 1.19.1"
},
"dependencies": {
"@tauri-apps/tauri-inliner": "1.14.1",
"@tauri-apps/toml": "2.2.4",
"chalk": "4.1.0",
"chokidar": "3.5.1",
@ -80,12 +77,6 @@
"@babel/core": "7.12.10",
"@babel/preset-env": "7.12.11",
"@babel/preset-typescript": "7.12.7",
"@rollup/plugin-babel": "5.2.2",
"@rollup/plugin-commonjs": "17.0.0",
"@rollup/plugin-json": "4.1.0",
"@rollup/plugin-node-resolve": "11.1.0",
"@rollup/plugin-sucrase": "3.1.0",
"@rollup/plugin-typescript": "8.1.0",
"@types/cross-spawn": "6.0.2",
"@types/fs-extra": "9.0.6",
"@types/http-proxy": "1.17.5",
@ -121,9 +112,6 @@
"promise": "8.1.0",
"raw-loader": "4.0.2",
"rimraf": "3.0.2",
"rollup": "2.38.0",
"rollup-plugin-terser": "7.0.2",
"rollup-plugin-typescript2": "0.29.0",
"toml-loader": "1.0.0",
"ts-loader": "8.0.14",
"tslib": "2.1.0",
@ -142,7 +130,7 @@
}
},
"lint-staged": {
"*.{js,jsx,ts,tsx,md,html,css,json}": "prettier --write --end-of-line=auto !./**/mutation-observer.js !./**/config.validator.js \"./**/*.{js,jsx,ts,tsx,html,css,json}\" --ignore-path .gitignore",
"*.{ts,tsx}": "eslint --fix --ext ts ./src/**/*.ts ./api-src/**/*.ts"
"*.{js,jsx,ts,tsx,md,html,css,json}": "prettier --write --end-of-line=auto !./**/config.validator.js !./**/config.schema.json \"./**/*.{js,jsx,ts,tsx,html,css,json}\" --ignore-path .gitignore",
"*.{ts,tsx}": "eslint --fix --ext ts ./src/**/*.ts"
}
}

View File

@ -1,107 +0,0 @@
// rollup.config.js
import { terser } from 'rollup-plugin-terser'
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import sucrase from '@rollup/plugin-sucrase'
import babel, { getBabelOutputPlugin } from '@rollup/plugin-babel'
import typescript from '@rollup/plugin-typescript'
import pkg from './package.json'
export default [
{
input: {
fs: './api-src/fs.ts',
path: './api-src/path.ts',
dialog: './api-src/dialog.ts',
event: './api-src/event.ts',
http: './api-src/http.ts',
index: './api-src/index.ts',
process: './api-src/process.ts',
tauri: './api-src/tauri.ts',
window: './api-src/window.ts',
cli: './api-src/cli.ts',
notification: './api-src/notification.ts'
},
treeshake: true,
perf: true,
output: [
{
dir: 'api/',
entryFileNames: '[name].js',
format: 'cjs',
exports: 'named',
globals: {}
},
{
dir: 'api/',
entryFileNames: '[name].mjs',
format: 'esm',
exports: 'named',
globals: {}
}
],
plugins: [
commonjs({}),
resolve({
// pass custom options to the resolve plugin
customResolveOptions: {
moduleDirectory: 'node_modules'
}
}),
typescript({
tsconfig: './tsconfig.api.json'
}),
babel({
configFile: false,
presets: [['@babel/preset-env'], ['@babel/preset-typescript']]
}),
terser()
],
external: [
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {})
],
watch: {
chokidar: true,
include: 'api-src/**',
exclude: 'node_modules/**'
}
},
{
input: {
bundle: './api-src/bundle.ts'
},
output: [
{
name: '__TAURI__',
dir: 'api/', // if it needs to run in the browser
entryFileNames: 'tauri.bundle.umd.js',
format: 'umd',
plugins: [
getBabelOutputPlugin({
presets: [['@babel/preset-env', { modules: 'umd' }]],
allowAllFormats: true
}),
terser()
],
globals: {}
}
],
plugins: [
sucrase({
exclude: ['node_modules'],
transforms: ['typescript']
}),
resolve({
// pass custom options to the resolve plugin
customResolveOptions: {
moduleDirectory: 'node_modules'
}
})
],
external: [
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {})
]
}
]

View File

@ -1,28 +0,0 @@
import { TauriConfig } from 'types'
import { merge } from 'webpack-merge'
import Runner from '../runner'
import getTauriConfig from '../helpers/tauri-config'
interface BuildResult {
promise: Promise<void>
runner: Runner
}
module.exports = (config: TauriConfig): BuildResult => {
const tauri = new Runner()
const tauriConfig = getTauriConfig(
merge(
{
ctx: {
prod: true
}
} as any,
config as any
) as TauriConfig
)
return {
runner: tauri,
promise: tauri.build(tauriConfig)
}
}

View File

@ -0,0 +1,31 @@
import { runOnRustCli } from '../helpers/rust-cli'
interface Args {
[key: string]: string | Object
}
function runCliCommand(
command: string,
args: Args
): { pid: number; promise: Promise<void> } {
const argsArray = []
for (const argName in args) {
const argValue = args[argName]
if (argValue === false) {
continue
}
argsArray.push(`--${argName}`)
if (argValue === true) {
continue
}
argsArray.push(
typeof argValue === 'string' ? argValue : JSON.stringify(argValue)
)
}
return runOnRustCli(command, argsArray)
}
export const dev = (args: Args): { pid: number; promise: Promise<void> } =>
runCliCommand('dev', args)
export const build = (args: Args): { pid: number; promise: Promise<void> } =>
runCliCommand('build', args)

View File

@ -1,49 +0,0 @@
import { TauriConfig } from 'types'
import { merge } from 'webpack-merge'
import Runner from '../runner'
import getTauriConfig from '../helpers/tauri-config'
import logger from '../helpers/logger'
import chalk from 'chalk'
import { platform } from 'os'
import { resolve } from 'path'
import { sync as spawnSync } from 'cross-spawn'
const error = logger('tauri:dev', chalk.red)
interface DevResult {
promise: Promise<void>
runner: Runner
}
module.exports = (config: TauriConfig): DevResult => {
if (platform() === 'win32') {
const child = spawnSync('powershell', [
resolve(__dirname, '../../scripts/is-admin.ps1')
])
const response = String(child.output[1]).replace('\n', '').trim()
if (response === 'True') {
error(
"Administrator privileges detected. Tauri doesn't work when running as admin, see https://github.com/Boscop/web-view/issues/96"
)
process.exit(1)
}
}
const tauri = new Runner()
const tauriConfig = getTauriConfig(
merge(
{
ctx: {
debug: true,
dev: true
}
} as any,
config as any
) as TauriConfig
)
return {
runner: tauri,
promise: tauri.run(tauriConfig)
}
}

View File

@ -0,0 +1,79 @@
import { existsSync } from 'fs'
import { resolve, join } from 'path'
import { spawnSync, spawn } from './spawn'
import { CargoManifest } from '../types/cargo'
const currentTauriCliVersion = (): string => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-member-access
const tauriCliManifest = require('../../../core/Cargo.toml') as CargoManifest
return tauriCliManifest.package.version
}
export function runOnRustCli(
command: string,
args: string[]
): { pid: number; promise: Promise<void> } {
const targetPath = resolve(__dirname, '../..')
const targetCliPath = join(targetPath, 'bin/cargo-tauri')
let resolveCb: () => void
let rejectCb: () => void
let pid: number
const promise = new Promise<void>((resolve, reject) => {
resolveCb = resolve
rejectCb = () => reject(new Error())
})
const onClose = (code: number, pid: number): void => {
if (code === 0) {
resolveCb()
} else {
rejectCb()
}
}
if (existsSync(targetCliPath)) {
pid = spawn(
targetCliPath,
['tauri', command, ...args],
process.cwd(),
onClose
)
} else {
if (existsSync(resolve(targetPath, '../tauri-bundler'))) {
// running local CLI
const cliPath = resolve(targetPath, '../core')
spawnSync('cargo', ['build', '--release'], cliPath)
const localCliPath = resolve(
targetPath,
'../core/target/release/cargo-tauri'
)
pid = spawn(
localCliPath,
['tauri', command, ...args],
process.cwd(),
onClose
)
} else {
spawnSync(
'cargo',
[
'install',
'--root',
targetPath,
'tauri-cli',
'--version',
currentTauriCliVersion()
],
process.cwd()
)
pid = spawn(
targetCliPath,
['tauri', command, ...args],
process.cwd(),
onClose
)
}
}
return { pid, promise }
}

View File

@ -32,7 +32,7 @@ export const spawn = (
}
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
onClose && onClose(code, runner.pid)
onClose && onClose(code ?? 0, runner.pid)
})
return runner.pid

View File

@ -1,613 +0,0 @@
import Inliner from '@tauri-apps/tauri-inliner'
import toml, { JsonMap } from '@tauri-apps/toml'
import chokidar, { FSWatcher } from 'chokidar'
import { existsSync, readFileSync, writeFileSync } from 'fs-extra'
import { JSDOM } from 'jsdom'
import { debounce, template } from 'lodash'
import path from 'path'
import http from 'http'
import * as net from 'net'
import os from 'os'
import { findClosestOpenPort } from './helpers/net'
import { tauriDir, appDir } from './helpers/app-paths'
import logger from './helpers/logger'
import onShutdown from './helpers/on-shutdown'
import { spawn, spawnSync } from './helpers/spawn'
import { exec, ChildProcess } from 'child_process'
import { TauriConfig } from './types/config'
import { CargoManifest } from './types/cargo'
import getTauriConfig from './helpers/tauri-config'
import httpProxy from 'http-proxy'
import isReachable from 'is-reachable'
import chalk from 'chalk'
const log = logger('app:tauri')
const warn = logger('app:tauri (runner)', chalk.red)
const WATCHER_INTERVAL = 1000
class Runner {
pid: number
rewritingToml: boolean = false
tauriWatcher?: FSWatcher
devPath?: string
killPromise?: Function
beforeDevProcess?: ChildProcess
devServer?: net.Server
constructor() {
this.pid = 0
this.tauriWatcher = undefined
onShutdown(() => {
this.stop().catch((e) => {
throw e
})
})
}
async run(cfg: TauriConfig): Promise<void> {
let devPath = cfg.build.devPath
if (this.pid) {
if (this.devPath !== devPath) {
await this.stop()
}
}
if (!this.beforeDevProcess && cfg.build.beforeDevCommand) {
log('Running `' + cfg.build.beforeDevCommand + '`')
const ls = exec(
cfg.build.beforeDevCommand,
{
cwd: appDir,
env: process.env
},
(error) => {
if (error) {
process.exit(1)
}
}
)
ls.stderr?.pipe(process.stderr)
ls.stdout?.pipe(process.stdout)
this.beforeDevProcess = ls
let devTryCount = 0
const devTryTimeout = 3000
while (!(await isReachable(devPath))) {
log('Waiting for your dev server to start...')
await new Promise((resolve) => setTimeout(resolve, devTryTimeout))
devTryCount++
if (devTryCount === 10) {
warn(
`Couldn't connect to ${devPath} after ${
(devTryTimeout * devTryCount) / 1000
}s. Please make sure that's the URL to your dev server.`
)
process.exit(1)
}
}
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const cargoManifest = (this.__getManifest() as any) as CargoManifest
this.__allowlistApi(cfg, cargoManifest)
this.__rewriteManifest((cargoManifest as unknown) as toml.JsonMap)
const runningDevServer = devPath.startsWith('http')
let inlinedAssets: string[] = []
if (runningDevServer) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this
const devUrl = new URL(devPath)
const proxy = httpProxy.createProxyServer({
ws: true,
target: {
host: devUrl.hostname,
port: devUrl.port
},
selfHandleResponse: true
})
proxy.on(
'proxyRes',
(
proxyRes: http.IncomingMessage,
req: http.IncomingMessage,
res: http.ServerResponse
) => {
if (req.url === '/') {
const body: Uint8Array[] = []
proxyRes.on('data', (chunk: Uint8Array) => {
body.push(chunk)
})
proxyRes.on('end', () => {
const bodyStr = body.join('')
const indexDir = os.tmpdir()
writeFileSync(path.join(indexDir, 'index.html'), bodyStr)
self
.__parseHtml(cfg, indexDir, false)
.then(({ html }) => {
const headers: { [key: string]: string } = {}
if (proxyRes.headers['content-type']) {
headers['content-type'] = proxyRes.headers['content-type']
} else {
const charsetMatch = /charset="(\S+)"/g.exec(bodyStr)
if (charsetMatch) {
headers[
'content-type'
] = `'text/html; charset=${charsetMatch[1]}`
}
}
res.writeHead(200, headers)
res.end(html)
})
.catch((err) => {
res.writeHead(500, JSON.stringify(err))
res.end()
})
})
} else {
if (proxyRes.statusCode) {
res = res.writeHead(proxyRes.statusCode, proxyRes.headers)
}
proxyRes.pipe(res)
}
}
)
proxy.on(
'error',
(error: Error, _: http.IncomingMessage, res: http.ServerResponse) => {
if (error.message?.includes('ECONNREFUSED')) {
warn(
`Connection refused to ${devUrl.protocol}//${devUrl.host}. Did you start your dev server? Usually that's done with a \`dev\` or \`serve\` NPM script.`
)
} else {
console.error(error)
}
res.writeHead(500, error.message)
}
)
const proxyServer = http.createServer((req, res) => {
delete req.headers['accept-encoding']
proxy.web(req, res)
})
proxyServer.on('upgrade', (req, socket, head) => {
proxy.ws(req, socket, head)
})
const port = await findClosestOpenPort(
parseInt(devUrl.port) + 1,
devUrl.hostname
)
const devServer = proxyServer.listen(port)
this.devServer = devServer
devPath = `${devUrl.protocol}//localhost:${port}`
cfg.build.devPath = devPath
process.env.TAURI_CONFIG = JSON.stringify(cfg)
} else {
inlinedAssets = (await this.__parseHtml(cfg, devPath)).inlinedAssets
}
process.env.TAURI_INLINED_ASSETS = inlinedAssets.join('|')
this.devPath = devPath
const startDevTauri = async (): Promise<void> => {
return await this.__runCargoCommand({
cargoArgs: ['run'],
dev: true,
exitOnPanic: cfg.ctx.exitOnPanic
})
}
// Start watching for tauri app changes
// eslint-disable-next-line security/detect-non-literal-fs-filename
let tauriPaths: string[] = []
if (
typeof cargoManifest.dependencies.tauri !== 'string' &&
cargoManifest.dependencies.tauri.path
) {
const tauriPath = path.resolve(
tauriDir,
cargoManifest.dependencies.tauri.path
)
tauriPaths = [
tauriPath,
`${tauriPath}-api`,
`${tauriPath}-updater`,
`${tauriPath}-utils`
]
}
if (!this.tauriWatcher) {
// eslint-disable-next-line security/detect-non-literal-fs-filename
this.tauriWatcher = chokidar
.watch(
[
path.join(tauriDir, 'src'),
path.join(tauriDir, 'Cargo.toml'),
path.join(tauriDir, 'build.rs'),
path.join(tauriDir, 'tauri.conf.json'),
...tauriPaths
].concat(runningDevServer ? [] : [devPath]),
{
ignoreInitial: true,
ignored: [
runningDevServer ? null : path.join(devPath, 'index.tauri.html')
].concat(path.join(tauriDir, 'target'))
}
)
.on(
'change',
debounce((changedPath: string) => {
if (
this.rewritingToml &&
changedPath.startsWith(path.join(tauriDir, 'Cargo.toml'))
) {
return
}
;(this.pid ? this.__stopCargo() : Promise.resolve())
.then(() => {
const shouldTriggerRun =
changedPath.includes('tauri.conf.json') ||
changedPath.startsWith(devPath)
if (shouldTriggerRun) {
this.run(getTauriConfig({ ctx: cfg.ctx })).catch((e) => {
throw e
})
} else {
startDevTauri().catch((e) => {
throw e
})
}
})
.catch((err) => {
warn(err)
process.exit(1)
})
}, WATCHER_INTERVAL)
)
}
return startDevTauri()
}
async build(cfg: TauriConfig): Promise<void> {
if (cfg.build.beforeBuildCommand) {
const [command, ...args] = cfg.build.beforeBuildCommand.split(' ')
spawnSync(command, args, appDir)
}
const cargoManifest = this.__getManifest()
this.__allowlistApi(cfg, (cargoManifest as unknown) as CargoManifest)
this.__rewriteManifest(cargoManifest)
const inlinedAssets = (await this.__parseHtml(cfg, cfg.build.distDir))
.inlinedAssets
process.env.TAURI_INLINED_ASSETS = inlinedAssets.join('|')
const features = [
cfg.tauri.embeddedServer.active ? 'embedded-server' : 'no-server'
]
const buildFn = async (target?: string): Promise<void> =>
await this.__runCargoCommand({
cargoArgs: [
cfg.tauri.bundle.active ? 'tauri-bundler' : 'build',
'--features',
...features,
...(cfg.tauri.bundle.active &&
Array.isArray(cfg.tauri.bundle.targets) &&
cfg.tauri.bundle.targets.length
? ['--format'].concat(cfg.tauri.bundle.targets)
: [])
]
.concat(cfg.ctx.debug ? [] : ['--release'])
.concat(cfg.verbose ? ['--verbose'] : [])
.concat(target ? ['--target', target] : [])
})
if (!cfg.ctx.target) {
// if no target specified,
// build only for the current platform
await buildFn()
} else {
const targets = cfg.ctx.target.split(',')
for (const target of targets) {
await buildFn(target)
}
}
}
async __parseHtml(
cfg: TauriConfig,
indexDir: string,
inlinerEnabled = cfg.tauri.inliner.active
): Promise<{ inlinedAssets: string[]; html: string }> {
const inlinedAssets: string[] = []
return await new Promise((resolve, reject) => {
const indexPath = path.join(indexDir, 'index.html')
if (!existsSync(indexPath)) {
warn(
`Error: cannot find index.html in "${indexDir}". Did you forget to build your web code or update the build.distDir in tauri.conf.json?`
)
reject(new Error('Could not find index.html in dist dir.'))
}
const originalHtml = readFileSync(indexPath).toString()
const rewriteHtml = (
html: string,
interceptor?: (dom: JSDOM) => void
): string => {
const dom = new JSDOM(html)
const document = dom.window.document
if (interceptor !== undefined) {
interceptor(dom)
}
if (
!(
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
(
(cfg.ctx.dev && cfg.build.devPath.startsWith('http')) ||
cfg.tauri.embeddedServer.active
)
/* eslint-enable */
)
) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-member-access
const mutationObserverTemplate = require('../templates/mutation-observer')
.default
const compiledMutationObserver = template(mutationObserverTemplate)
const bodyMutationObserverScript = document.createElement('script')
bodyMutationObserverScript.text = compiledMutationObserver({
target: 'body',
inlinedAssets: JSON.stringify(inlinedAssets)
})
document.body.insertBefore(
bodyMutationObserverScript,
document.body.firstChild
)
const headMutationObserverScript = document.createElement('script')
headMutationObserverScript.text = compiledMutationObserver({
target: 'head',
inlinedAssets: JSON.stringify(inlinedAssets)
})
document.head.insertBefore(
headMutationObserverScript,
document.head.firstChild
)
}
const tauriScript = document.createElement('script')
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-member-access
tauriScript.text = require('../templates/tauri.js').default
document.head.insertBefore(tauriScript, document.head.firstChild)
if (cfg.build.withGlobalTauri) {
const tauriUmdScript = document.createElement('script')
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-member-access
tauriUmdScript.text = require('../api/tauri.bundle.umd').default
document.head.insertBefore(tauriUmdScript, document.head.firstChild)
}
const csp = cfg.tauri.security.csp
if (csp) {
const cspTag = document.createElement('meta')
cspTag.setAttribute('http-equiv', 'Content-Security-Policy')
cspTag.setAttribute('content', csp)
document.head.appendChild(cspTag)
}
const newHtml = dom.serialize()
writeFileSync(path.join(indexDir, 'index.tauri.html'), newHtml)
return newHtml
}
const domInterceptor = cfg.tauri.embeddedServer.active
? undefined
: (dom: JSDOM) => {
const document = dom.window.document
if (!cfg.ctx.dev) {
document
.querySelectorAll('link')
.forEach((link: HTMLLinkElement) => {
link.removeAttribute('rel')
link.removeAttribute('as')
})
}
}
if (
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
(!cfg.ctx.dev && cfg.tauri.embeddedServer.active) ||
!inlinerEnabled
) {
const html = rewriteHtml(originalHtml, domInterceptor)
resolve({ inlinedAssets, html })
} else {
const cwd = process.cwd()
process.chdir(indexDir) // the inliner requires this to properly work
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
const inliner = new Inliner(
{ source: originalHtml },
(err: Error, html: string) => {
process.chdir(cwd) // reset CWD
if (err) {
reject(err)
} else {
const rewrittenHtml = rewriteHtml(html, domInterceptor)
resolve({ inlinedAssets, html: rewrittenHtml })
}
}
)
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
inliner.on('progress', (event: string) => {
const match = event.match(/([\S\d]+)\.([\S\d]+)/g)
match && inlinedAssets.push(match[0])
})
}
})
}
async stop(): Promise<void> {
return await new Promise((resolve, reject) => {
this.devServer?.close()
this.tauriWatcher?.close().catch(reject)
this.__stopCargo().then(resolve).catch(reject)
})
}
async __runCargoCommand({
cargoArgs,
extraArgs,
dev = false,
exitOnPanic = true
}: {
cargoArgs: string[]
extraArgs?: string[]
dev?: boolean
exitOnPanic?: boolean
}): Promise<void> {
return await new Promise((resolve, reject) => {
this.pid = spawn(
'cargo',
extraArgs ? cargoArgs.concat(['--']).concat(extraArgs) : cargoArgs,
tauriDir,
(code, pid) => {
if (this.killPromise) {
this.killPromise()
this.killPromise = undefined
resolve()
return
}
if (pid !== this.pid) {
resolve()
return
}
if (dev && !exitOnPanic && code === 101) {
this.pid = 0
resolve()
return
}
if (code) {
warn()
warn('⚠️ [FAIL] Cargo CLI has failed')
warn()
reject(
new Error('Cargo failed with status code ' + code.toString())
)
process.exit(1)
} else if (!dev) {
resolve()
}
if (dev) {
warn()
warn('Cargo process was killed. Exiting...')
warn()
process.exit(0)
}
resolve()
}
)
if (dev) {
resolve()
}
})
}
async __stopCargo(): Promise<void> {
if (!this.pid) {
return await Promise.resolve()
}
log('Shutting down tauri process...')
return await new Promise((resolve, reject) => {
this.killPromise = resolve
try {
process.kill(this.pid)
this.pid = 0
} catch (e) {
reject(e)
}
})
}
__getManifestPath(): string {
return path.join(tauriDir, 'Cargo.toml')
}
__getManifest(): JsonMap {
const tomlPath = this.__getManifestPath()
const tomlFile = readFileSync(tomlPath).toString()
const cargoManifest = toml.parse(tomlFile)
return cargoManifest
}
__rewriteManifest(cargoManifest: JsonMap): void {
const tomlPath = this.__getManifestPath()
const output = toml.stringify(cargoManifest)
this.rewritingToml = true
writeFileSync(tomlPath, output)
setTimeout(() => {
this.rewritingToml = false
}, WATCHER_INTERVAL * 2)
}
__allowlistApi(cfg: TauriConfig, manifest: CargoManifest): void {
const tomlFeatures = []
if (cfg.tauri.allowlist.all) {
tomlFeatures.push('all-api')
} else {
const toKebabCase = (value: string): string => {
return value
.replace(/([a-z])([A-Z])/g, '$1-$2')
.replace(/\s+/g, '-')
.toLowerCase()
}
const allowlist = Object.keys(cfg.tauri.allowlist).filter(
(w) => cfg.tauri.allowlist[String(w)]
)
tomlFeatures.push(...allowlist.map(toKebabCase))
}
if (cfg.tauri.cli) {
tomlFeatures.push('cli')
}
if (typeof manifest.dependencies.tauri === 'string') {
manifest.dependencies.tauri = {
version: manifest.dependencies.tauri,
features: tomlFeatures
}
} else {
manifest.dependencies.tauri.features = tomlFeatures
}
}
}
export default Runner

View File

@ -1,6 +1,6 @@
import { CargoManifest } from './../types/cargo'
import { existsSync, removeSync, writeFileSync } from 'fs-extra'
import { join, normalize, resolve } from 'path'
import { join, normalize, resolve, isAbsolute } from 'path'
import { TauriConfig } from 'types'
import { merge } from 'webpack-merge'
import copyTemplates from '../helpers/copy-templates'
@ -67,10 +67,9 @@ Run \`tauri init --force template\` to overwrite.`)
}
const resolveTauriPath = (tauriPath: string): string => {
const resolvedPath =
tauriPath.startsWith('/') || /^\S:/g.test(tauriPath)
? join(tauriPath, 'tauri') // we received a full path as argument
: join('..', tauriPath, 'tauri') // we received a relative path
const resolvedPath = isAbsolute(tauriPath)
? join(tauriPath, 'tauri') // we received a full path as argument
: join('..', tauriPath, 'tauri') // we received a relative path
return resolvedPath.replace(/\\/g, '/')
}

View File

@ -147,7 +147,9 @@
"type": "boolean"
}
},
"required": ["name"],
"required": [
"name"
],
"type": "object"
},
"CliConfig": {
@ -213,7 +215,10 @@
"type": "boolean"
}
},
"required": ["devPath", "distDir"],
"required": [
"devPath",
"distDir"
],
"type": "object"
}
},
@ -275,7 +280,9 @@
"type": "boolean"
}
},
"required": ["all"],
"required": [
"all"
],
"type": "object"
},
"bundle": {
@ -379,7 +386,10 @@
"description": "the bundle targets, currently supports [\"deb\", \"osx\", \"msi\", \"appimage\", \"dmg\"] or \"all\""
}
},
"required": ["icon", "identifier"],
"required": [
"icon",
"identifier"
],
"type": "object"
},
"cli": {
@ -398,7 +408,9 @@
"port": {
"anyOf": [
{
"enum": ["random"],
"enum": [
"random"
],
"type": "string"
},
{
@ -450,7 +462,9 @@
"type": "number"
}
},
"required": ["title"],
"required": [
"title"
],
"type": "object"
}
},
@ -469,6 +483,10 @@
"type": "boolean"
}
},
"required": ["build", "ctx", "tauri"],
"required": [
"build",
"ctx",
"tauri"
],
"type": "object"
}
}

View File

@ -1,4 +1,3 @@
declare module '@tauri-apps/tauri-inliner'
declare module 'imagemin-zopfli'
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars

View File

@ -5,9 +5,9 @@ const distDir = path.join(appDir, 'dist')
const spawn = require('helpers/spawn').spawn
function runBuildTest(tauriConfig) {
function runBuildTest(args) {
fixtureSetup.initJest('app')
const build = require('api/build')
const { build } = require('dist/api/cli')
return new Promise(async (resolve, reject) => {
try {
let success = false
@ -19,10 +19,10 @@ function runBuildTest(tauriConfig) {
// wait for the app process to be killed
setTimeout(resolve, 2000)
})
const result = build(tauriConfig)
await result.promise
process.chdir(appDir)
await build(args).promise
const artifactFolder = tauriConfig.ctx.debug ? 'debug' : 'release'
const artifactFolder = args.debug ? 'debug' : 'release'
const artifactPath = path.resolve(
appDir,
`src-tauri/target/${artifactFolder}/app`
@ -73,16 +73,16 @@ describe('Tauri Build', () => {
${'no-server'} | ${'release'}
`('works with the $mode $flag mode', ({ mode, flag }) => {
return runBuildTest({
build,
ctx: {
debug: flag === 'debug'
},
tauri: {
allowlist: {
all: true
},
embeddedServer: {
active: mode === 'embedded-server'
debug: flag === 'debug',
config: {
build,
tauri: {
allowlist: {
all: true
},
embeddedServer: {
active: mode === 'embedded-server'
}
}
}
})

View File

@ -30,15 +30,16 @@ function startDevServer() {
function runDevTest(tauriConfig) {
fixtureSetup.initJest('app')
const dev = require('api/dev')
const { dev } = require('dist/api/cli')
return new Promise(async (resolve, reject) => {
try {
const { promise, runner } = dev(tauriConfig)
process.chdir(path.join(fixtureSetup.fixtureDir, 'app'))
const { promise, pid } = dev({ config: tauriConfig })
const isRunning = require('is-running')
let success = false
const checkIntervalId = setInterval(async () => {
if (!isRunning(runner.pid) && !success) {
if (!isRunning(pid) && !success) {
const failedCommands = Object.keys(responses)
.filter((k) => responses[k] === null)
.join(', ')
@ -52,7 +53,7 @@ function runDevTest(tauriConfig) {
// wait for the app process to be killed
setTimeout(async () => {
try {
await runner.stop()
process.kill(pid)
} catch {}
resolve()
}, 2000)
@ -82,10 +83,6 @@ describe('Tauri Dev', () => {
build: {
...build,
devPath: url
},
ctx: {
debug: true,
dev: true
}
})

View File

@ -26,17 +26,6 @@ describe('[CLI] tauri.js', () => {
jest.clearAllMocks()
})
it('will pass on an available command', async () => {
jest.spyOn(console, 'log')
jest.mock('fs')
try {
tauri('init')
} catch {}
expect(console.log.mock.calls[0][0].split('.')[0]).toBe(
'[tauri]: running init'
)
jest.clearAllMocks()
})
it('gets you help', async () => {
jest.spyOn(console, 'log')
const tests = ['--help', '-h', 'invalid command']

View File

@ -25,16 +25,16 @@ describe('[CLI] tauri.js template', () => {
const manifestFile = readFileSync(manifestPath).toString()
writeFileSync(manifestPath, `workspace = { }\n\n${manifestFile}`)
const build = require('api/build')
const { build } = require('dist/api/cli')
await build({
tauri: {
bundle: {
targets: ['deb', 'osx', 'msi', 'appimage'] // we can't bundle dmg on CI so we remove it here
config: {
tauri: {
bundle: {
targets: ['deb', 'osx', 'msi', 'appimage'] // we can't bundle dmg on CI so we remove it here
}
}
}
}).promise.then(() => {
writeFileSync('a.b', 'finished')
process.chdir(cwd)
})
}).promise
process.chdir(cwd)
})
})

View File

@ -27,7 +27,7 @@ serde_derive = "1.0"
tiny_http = "0.7"
phf = "0.8.0"
includedir = "0.6.0"
tauri = { path = "../../../../../../../tauri", features = [ "all-api" ] }
tauri = { path = "../../../../../../../tauri", features =["all-api"]}
[features]
embedded-server = [ "tauri/embedded-server" ]

View File

@ -1,381 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
accepts@~1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
dependencies:
mime-types "~2.1.24"
negotiator "0.6.2"
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
body-parser@1.19.0:
version "1.19.0"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
dependencies:
bytes "3.1.0"
content-type "~1.0.4"
debug "2.6.9"
depd "~1.1.2"
http-errors "1.7.2"
iconv-lite "0.4.24"
on-finished "~2.3.0"
qs "6.7.0"
raw-body "2.4.0"
type-is "~1.6.17"
bytes@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
content-disposition@0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
dependencies:
safe-buffer "5.1.2"
content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
cookie@0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
cors@^2.8.5:
version "2.8.5"
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
dependencies:
object-assign "^4"
vary "^1"
debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
destroy@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
etag@~1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
express@^4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
dependencies:
accepts "~1.3.7"
array-flatten "1.1.1"
body-parser "1.19.0"
content-disposition "0.5.3"
content-type "~1.0.4"
cookie "0.4.0"
cookie-signature "1.0.6"
debug "2.6.9"
depd "~1.1.2"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
finalhandler "~1.1.2"
fresh "0.5.2"
merge-descriptors "1.0.1"
methods "~1.1.2"
on-finished "~2.3.0"
parseurl "~1.3.3"
path-to-regexp "0.1.7"
proxy-addr "~2.0.5"
qs "6.7.0"
range-parser "~1.2.1"
safe-buffer "5.1.2"
send "0.17.1"
serve-static "1.14.1"
setprototypeof "1.1.1"
statuses "~1.5.0"
type-is "~1.6.18"
utils-merge "1.0.1"
vary "~1.1.2"
finalhandler@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
dependencies:
debug "2.6.9"
encodeurl "~1.0.2"
escape-html "~1.0.3"
on-finished "~2.3.0"
parseurl "~1.3.3"
statuses "~1.5.0"
unpipe "~1.0.0"
forwarded@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
fresh@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
http-errors@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
dependencies:
depd "~1.1.2"
inherits "2.0.3"
setprototypeof "1.1.1"
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
http-errors@~1.7.2:
version "1.7.3"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
dependencies:
depd "~1.1.2"
inherits "2.0.4"
setprototypeof "1.1.1"
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
iconv-lite@0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
inherits@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
inherits@2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
ipaddr.js@1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
mime-db@1.42.0:
version "1.42.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac"
integrity sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==
mime-types@~2.1.24:
version "2.1.25"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.25.tgz#39772d46621f93e2a80a856c53b86a62156a6437"
integrity sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==
dependencies:
mime-db "1.42.0"
mime@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
ms@2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
negotiator@0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
object-assign@^4:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
on-finished@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
dependencies:
ee-first "1.1.1"
parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
proxy-addr@~2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34"
integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==
dependencies:
forwarded "~0.1.2"
ipaddr.js "1.9.0"
qs@6.7.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
range-parser@~1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
raw-body@2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
dependencies:
bytes "3.1.0"
http-errors "1.7.2"
iconv-lite "0.4.24"
unpipe "1.0.0"
safe-buffer@5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
"safer-buffer@>= 2.1.2 < 3":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
send@0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
dependencies:
debug "2.6.9"
depd "~1.1.2"
destroy "~1.0.4"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
fresh "0.5.2"
http-errors "~1.7.2"
mime "1.6.0"
ms "2.1.1"
on-finished "~2.3.0"
range-parser "~1.2.1"
statuses "~1.5.0"
serve-static@1.14.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
dependencies:
encodeurl "~1.0.2"
escape-html "~1.0.3"
parseurl "~1.3.3"
send "0.17.1"
setprototypeof@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
toidentifier@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
type-is@~1.6.17, type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
dependencies:
media-typer "0.3.0"
mime-types "~2.1.24"
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
utils-merge@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
vary@^1, vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=

View File

@ -4,8 +4,7 @@ const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
entry: {
'api/build': './src/api/build.ts',
'api/dev': './src/api/dev.ts',
'api/cli': './src/api/cli.ts',
'api/init': './src/api/init.ts',
'api/recipes': './src/api/recipes/index.ts',
'api/recipes/install': './src/api/recipes/install.ts',
@ -13,7 +12,8 @@ module.exports = {
'api/info': './src/api/info.ts',
'api/dependency-manager': './src/api/dependency-manager/index.ts',
'helpers/tauri-config': './src/helpers/tauri-config.ts',
'helpers/spawn': './src/helpers/spawn.ts'
'helpers/spawn': './src/helpers/spawn.ts',
'helpers/rust-cli': './src/helpers/rust-cli.ts'
},
mode: process.env.NODE_ENV || 'development',
devtool: 'source-map',
@ -69,7 +69,7 @@ function schemaParser(schemaName, content) {
if (line === `export const ${schemaName} = {`) {
output.push('{')
} else if (output.length) {
if (line === '};') {
if (line === '}') {
output.push('}')
break
}
@ -77,5 +77,7 @@ function schemaParser(schemaName, content) {
}
}
return output.join('\n')
const json = output.join('\n')
const object = eval(`(${json})`)
return JSON.stringify(object, null, 2)
}

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,6 @@ git clone --recursive git@github.com:tauri-apps/examples.git \
|| (cd examples && git pull origin dev; cd ..) # always prepare up-to-date examples in case it's already available
cargo build
cargo install --path cli/tauri-bundler --force
cargo install cargo-web # used by example rust/yew
cd cli/tauri.js
@ -46,7 +45,6 @@ $env:TAURI_DIR = Resolve-Path $src_path
# build and install everything Rust related.
cargo build
cargo install --path cli\tauri-bundler --force
cargo install cargo-web
# install the tauri Node CLI and transpile the TS version of the API.

View File

@ -34,6 +34,17 @@
},
"rebaseConflictedPrs": false
},
{
"enabled": true,
"paths": ["cli/core/**"],
"groupName": "Tauri CLI",
"groupSlug": "allTauriCLI",
"commitMessagePrefix": "chore(deps)",
"lockFileMaintenance": {
"enabled": true
},
"rebaseConflictedPrs": false
},
{
"enabled": true,
"paths": ["cli/tauri-bundler/**"],
@ -55,6 +66,17 @@
"enabled": true
},
"rebaseConflictedPrs": false
},
{
"enabled": true,
"paths": ["api/**"],
"groupName": "Tauri API Definitions",
"groupSlug": "allTauriAPIDefinitions",
"commitMessagePrefix": "chore(deps)",
"lockFileMaintenance": {
"enabled": true
},
"rebaseConflictedPrs": false
}
]
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,7 @@ icon = [
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = [ "derive" ] }
tauri = { path = "../../..", features = [ "all-api", "cli" ] }
tauri = { path = "../../..", features =["all-api", "cli"]}
[target."cfg(windows)".build-dependencies]
winres = "0.1"

1683
tauri/examples/rollup/src-tauri/Cargo.lock generated Executable file

File diff suppressed because it is too large Load Diff