mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-12-24 19:25:12 +03:00
feat(cli) rewrite the core CLI in Rust (#851)
This commit is contained in:
parent
23132acf76
commit
3e8abe3764
6
.changes/cli-rust.md
Normal file
6
.changes/cli-rust.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
"tauri-cli": minor
|
||||
"tauri-bundler": minor
|
||||
---
|
||||
|
||||
The `dev` and `build` pipeline is now written in Rust.
|
@ -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
8
.changes/tauri-api.md
Normal 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
7
.changes/tauri-cli.md
Normal 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
2
.github/CODEOWNERS
vendored
@ -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
|
||||
|
23
.github/workflows/core-lint-fmt.yml
vendored
23
.github/workflows/core-lint-fmt.yml
vendored
@ -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:
|
||||
|
27
.github/workflows/test-core.yml
vendored
27
.github/workflows/test-core.yml
vendored
@ -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
2
.gitignore
vendored
@ -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
1
api/.eslintignore
Normal file
@ -0,0 +1 @@
|
||||
/src/types/config.validator.ts
|
56
api/.eslintrc.js
Normal file
56
api/.eslintrc.js
Normal 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
64
api/.gitignore
vendored
Normal 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
14
api/babel.config.js
Normal 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
84
api/package.json
Normal 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
107
api/rollup.config.js
Normal 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
24
api/src/bundle.ts
Normal 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,
|
||||
};
|
@ -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 };
|
@ -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 };
|
@ -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 };
|
@ -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
2
api/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import * as api from "./bundle";
|
||||
export default api;
|
35
api/src/notification.ts
Normal file
35
api/src/notification.ts
Normal 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 };
|
@ -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,
|
||||
};
|
@ -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 };
|
@ -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 };
|
@ -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 };
|
@ -8,8 +8,8 @@
|
||||
"types": ["@types"]
|
||||
},
|
||||
"declaration": true,
|
||||
"declarationDir": "api",
|
||||
"rootDir": "api-src"
|
||||
"declarationDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["./api-src"]
|
||||
"include": ["./src"]
|
||||
}
|
2
cli/core/.cargo/audit.toml
Normal file
2
cli/core/.cargo/audit.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[advisories]
|
||||
ignore = ["RUSTSEC-2020-0031"]
|
3176
cli/core/Cargo.lock
generated
Executable file
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
33
cli/core/Cargo.toml
Normal 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
26
cli/core/build.rs
Normal 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
128
cli/core/src/build.rs
Normal 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
86
cli/core/src/cli.yml
Normal 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
232
cli/core/src/dev.rs
Normal 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(())
|
||||
}
|
36
cli/core/src/helpers/app_paths.rs
Normal file
36
cli/core/src/helpers/app_paths.rs
Normal 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")
|
||||
}
|
426
cli/core/src/helpers/config.rs
Normal file
426
cli/core/src/helpers/config.rs
Normal 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)
|
||||
}
|
35
cli/core/src/helpers/logger.rs
Normal file
35
cli/core/src/helpers/logger.rs
Normal 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()
|
||||
);
|
||||
}
|
||||
}
|
65
cli/core/src/helpers/manifest.rs
Normal file
65
cli/core/src/helpers/manifest.rs
Normal 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(())
|
||||
}
|
33
cli/core/src/helpers/mod.rs
Normal file
33
cli/core/src/helpers/mod.rs
Normal 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"))
|
||||
}
|
||||
}
|
107
cli/core/src/helpers/tauri_html.rs
Normal file
107
cli/core/src/helpers/tauri_html.rs
Normal 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
12
cli/core/src/info.rs
Normal 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
91
cli/core/src/init.rs
Normal 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
109
cli/core/src/main.rs
Normal 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(())
|
||||
}
|
@ -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,
|
1
cli/deno
1
cli/deno
@ -1 +0,0 @@
|
||||
Subproject commit 55fd9104fa4da3b7afb96a16bb1ed8378b028a5a
|
1803
cli/tauri-bundler/Cargo.lock
generated
Normal file
1803
cli/tauri-bundler/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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<()> {
|
||||
|
@ -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(¤t_dir)?;
|
||||
let tauri_config = super::tauri_config::get();
|
||||
|
||||
@ -363,7 +362,12 @@ impl Settings {
|
||||
}
|
||||
};
|
||||
let workspace_dir = Settings::get_workspace_dir(¤t_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(
|
||||
|
@ -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),
|
||||
|
32
cli/tauri-bundler/src/lib.rs
Normal file
32
cli/tauri-bundler/src/lib.rs
Normal 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(())
|
||||
}
|
2
cli/tauri.js/.gitignore
vendored
2
cli/tauri.js/.gitignore
vendored
@ -1,7 +1,5 @@
|
||||
# Build output
|
||||
/dist
|
||||
/api
|
||||
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
@ -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
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
import * as api from './bundle'
|
||||
export default api
|
@ -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 }
|
@ -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()
|
@ -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()
|
@ -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' ||
|
||||
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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 || {})
|
||||
]
|
||||
}
|
||||
]
|
@ -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)
|
||||
}
|
||||
}
|
31
cli/tauri.js/src/api/cli.ts
Normal file
31
cli/tauri.js/src/api/cli.ts
Normal 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)
|
@ -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)
|
||||
}
|
||||
}
|
79
cli/tauri.js/src/helpers/rust-cli.ts
Normal file
79
cli/tauri.js/src/helpers/rust-cli.ts
Normal 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 }
|
||||
}
|
@ -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
|
||||
|
@ -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
|
@ -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, '/')
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
}
|
1
cli/tauri.js/src/types/modules.d.ts
vendored
1
cli/tauri.js/src/types/modules.d.ts
vendored
@ -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
|
||||
|
@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -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
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -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']
|
||||
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
@ -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" ]
|
||||
|
@ -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=
|
@ -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
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
3055
tauri/examples/communication/dist/index.tauri.html
vendored
3055
tauri/examples/communication/dist/index.tauri.html
vendored
File diff suppressed because one or more lines are too long
1871
tauri/examples/communication/src-tauri/Cargo.lock
generated
Normal file
1871
tauri/examples/communication/src-tauri/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
1683
tauri/examples/rollup/src-tauri/Cargo.lock
generated
Executable file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user