Browse Source

[needs review] Convert tauri.js to typescript (#203)

* feat(tauri.js): move to typescript

* fix(tauri.js): properly export api as commonjs

* feat(tauri.js): convert tauricon to typescript

* fix(tauri.js/tauricon): type error

* chore(tauri.js/package): update yarn.lock

* chore(tauri.js/package): add build/pretest scripts

* refactor(tauri.js/template): remove duplicate types

* chore(tauri.js) lint-fix

* fix(tauri.js) build tauricon.ts

* chore(tauri.js) remove unused code

Co-authored-by: nothingismagick <drthompsonsmagickindustries@gmail.com>
Co-authored-by: Lucas Fernandes Nogueira <lucasfernandesnog@gmail.com>
Noah Klayman 5 năm trước cách đây
mục cha
commit
fab788b4bd
38 tập tin đã thay đổi với 1439 bổ sung516 xóa
  1. 24 11
      cli/tauri.js/.eslintrc.js
  2. 3 0
      cli/tauri.js/.gitignore
  3. 0 21
      cli/tauri.js/api/build.js
  4. 0 22
      cli/tauri.js/api/dev.js
  5. 0 9
      cli/tauri.js/api/init.js
  6. 1 1
      cli/tauri.js/bin/tauri-build.js
  7. 1 1
      cli/tauri.js/bin/tauri-dev.js
  8. 7 10
      cli/tauri.js/bin/tauri-icon.js
  9. 1 1
      cli/tauri.js/bin/tauri-init.js
  10. 0 12
      cli/tauri.js/entry.js
  11. 0 12
      cli/tauri.js/generator.js
  12. 0 36
      cli/tauri.js/helpers/app-paths.js
  13. 0 44
      cli/tauri.js/helpers/copy-templates.js
  14. 0 19
      cli/tauri.js/helpers/logger.js
  15. 0 49
      cli/tauri.js/helpers/tauri-config.js
  16. 25 5
      cli/tauri.js/package.json
  17. 26 0
      cli/tauri.js/src/api/build.ts
  18. 27 0
      cli/tauri.js/src/api/dev.ts
  19. 14 0
      cli/tauri.js/src/api/init.ts
  20. 155 87
      cli/tauri.js/src/api/tauricon.ts
  21. 16 0
      cli/tauri.js/src/entry.ts
  22. 11 0
      cli/tauri.js/src/generator.ts
  23. 29 0
      cli/tauri.js/src/helpers/app-paths.ts
  24. 56 0
      cli/tauri.js/src/helpers/copy-templates.ts
  25. 23 0
      cli/tauri.js/src/helpers/logger.ts
  26. 2 2
      cli/tauri.js/src/helpers/on-shutdown.ts
  27. 26 24
      cli/tauri.js/src/helpers/spawn.ts
  28. 59 0
      cli/tauri.js/src/helpers/tauri-config.ts
  29. 6 24
      cli/tauri.js/src/helpers/tauricon.config.ts
  30. 119 68
      cli/tauri.js/src/runner.ts
  31. 30 22
      cli/tauri.js/src/template.ts
  32. 43 0
      cli/tauri.js/src/types/config.ts
  33. 1 0
      cli/tauri.js/src/types/index.ts
  34. 5 0
      cli/tauri.js/src/types/modules.d.ts
  35. 1 1
      cli/tauri.js/test/jest/__tests__/tauricon.spec.js
  36. 16 0
      cli/tauri.js/tsconfig.json
  37. 34 0
      cli/tauri.js/webpack.config.js
  38. 678 35
      cli/tauri.js/yarn.lock

+ 24 - 11
cli/tauri.js/.eslintrc.js

@@ -1,23 +1,27 @@
 module.exports = {
+  root: true,
+
   env: {
     node: true,
     jest: true
   },
 
+  parser: '@typescript-eslint/parser',
+
   extends: [
-    'standard',
-    'plugin:lodash-template/recommended',
-    'plugin:node/recommended'
+    'standard-with-typescript',
+    'plugin:@typescript-eslint/recommended-requiring-type-checking',
+    'plugin:lodash-template/recommended'
+    // TODO: make this work with typescript
+    // 'plugin:node/recommended'
   ],
 
-  'parserOptions': {
-    'ecmaVersion': 2020
-  },
+  plugins: ['@typescript-eslint', 'node', 'security'],
 
-  'plugins': [
-    'node',
-    'security'
-  ],
+  parserOptions: {
+    tsconfigRootDir: __dirname,
+    project: './tsconfig.json'
+  },
 
   globals: {
     __statics: true,
@@ -42,6 +46,15 @@ module.exports = {
     'security/detect-non-literal-require': 'warn',
     'security/detect-object-injection': 'warn',
     'security/detect-possible-timing-attacks': 'error',
-    'security/detect-pseudoRandomBytes': 'error'
+    'security/detect-pseudoRandomBytes': 'error',
+    '@typescript-eslint/strict-boolean-expressions': 0,
+    'space-before-function-paren': [
+      'error',
+      {
+        asyncArrow: 'always',
+        anonymous: 'never',
+        named: 'never'
+      }
+    ]
   }
 }

+ 3 - 0
cli/tauri.js/.gitignore

@@ -1,3 +1,6 @@
+# Webpack output
+dist
+
 # Logs
 logs
 *.log

+ 0 - 21
cli/tauri.js/api/build.js

@@ -1,21 +0,0 @@
-module.exports = config => {
-  const { tauriDir } = require('../helpers/app-paths')
-  const merge = require('webpack-merge')
-  const Runner = require('../runner')
-  const tauri = new Runner({ modeDir: tauriDir })
-  const tauriConfig = require('../helpers/tauri-config')(
-    merge(
-      {
-        ctx: {
-          prod: true
-        }
-      },
-      config
-    )
-  )
-
-  require('../generator').generate(tauriConfig.tauri)
-  require('../entry').generate(tauriDir, tauriConfig)
-
-  return tauri.build(tauriConfig)
-}

+ 0 - 22
cli/tauri.js/api/dev.js

@@ -1,22 +0,0 @@
-module.exports = config => {
-  const { tauriDir } = require('../helpers/app-paths')
-  const Runner = require('../runner')
-  const merge = require('webpack-merge')
-  const tauri = new Runner()
-  const tauriConfig = require('../helpers/tauri-config')(
-    merge(
-      {
-        ctx: {
-          debug: true,
-          dev: true
-        }
-      },
-      config
-    )
-  )
-
-  require('../generator').generate(tauriConfig.tauri)
-  require('../entry').generate(tauriDir, tauriConfig)
-
-  return tauri.run(tauriConfig)
-}

+ 0 - 9
cli/tauri.js/api/init.js

@@ -1,9 +0,0 @@
-const { inject } = require('../template')
-
-module.exports = args => {
-  return inject(args.directory, 'all', {
-    force: args.force,
-    logging: args.logging,
-    tauriPath: args.tauriPath
-  })
-}

+ 1 - 1
cli/tauri.js/bin/tauri-build.js

@@ -20,6 +20,6 @@ if (argv.help) {
   process.exit(0)
 }
 
-const build = require('../api/build')
+const build = require('../dist/build')
 
 build({ ctx: { debug: argv.debug } })

+ 1 - 1
cli/tauri.js/bin/tauri-dev.js

@@ -19,6 +19,6 @@ if (argv.help) {
   process.exit(0)
 }
 
-const dev = require('../api/dev')
+const dev = require('../dist/dev')
 
 dev()

+ 7 - 10
cli/tauri.js/bin/tauri-icon.js

@@ -1,10 +1,5 @@
 const parseArgs = require('minimist')
-const { appDir, tauriDir } = require('../helpers/app-paths')
-const logger = require('../helpers/logger')
-const log = logger('app:tauri')
-const warn = logger('app:tauri (icon)', 'red')
-const { tauricon } = require('../api/tauricon')
-const { resolve } = require('path')
+const { tauricon } = require('../dist/tauricon')
 
 /**
  * @type {object}
@@ -51,11 +46,13 @@ if (argv.help) {
 }
 
 tauricon.make(
-  argv.i || resolve(appDir, 'app-icon.png'),
-  argv.t || resolve(tauriDir, 'icons'),
+  argv.i,
+  argv.t,
   argv.c || 'optipng'
 ).then(() => {
-  log('(tauricon) Completed')
+  // TODO: use logger module for prettier output
+  console.log('app:tauri (tauricon) Completed')
 }).catch(e => {
-  warn(e)
+  // TODO: use logger module for prettier output
+  console.error('app:tauri (icon)', e)
 })

+ 1 - 1
cli/tauri.js/bin/tauri-init.js

@@ -39,7 +39,7 @@ if (argv.help) {
   process.exit(0)
 }
 
-const init = require('../api/init')
+const init = require('../dist/init')
 
 init({
   directory: argv.d || process.cwd(),

+ 0 - 12
cli/tauri.js/entry.js

@@ -1,12 +0,0 @@
-const compileTemplate = require('lodash.template')
-const { readFileSync, writeFileSync, ensureDirSync } = require('fs-extra')
-const path = require('path')
-
-module.exports.generate = (outDir, cfg) => {
-  // this MUST be from the templates repo
-  const apiTemplate = readFileSync(path.resolve(__dirname, './templates/tauri.js'), 'utf-8')
-  const apiContent = compileTemplate(apiTemplate)(cfg)
-
-  ensureDirSync(outDir)
-  writeFileSync(path.join(outDir, 'tauri.js'), apiContent, 'utf-8')
-}

+ 0 - 12
cli/tauri.js/generator.js

@@ -1,12 +0,0 @@
-const
-  path = require('path')
-const { writeFileSync } = require('fs-extra')
-const { tauriDir } = require('./helpers/app-paths')
-
-module.exports.generate = tauriConfig => {
-  const
-    { bundle, ...cfg } = tauriConfig
-  const outDir = tauriDir
-  writeFileSync(path.join(outDir, 'config.json'), JSON.stringify(cfg))
-  writeFileSync(path.join(outDir, 'bundle.json'), JSON.stringify(bundle))
-}

+ 0 - 36
cli/tauri.js/helpers/app-paths.js

@@ -1,36 +0,0 @@
-const { existsSync } = require('fs')
-const { resolve, join, normalize, sep } = require('path')
-
-/**
- *
- * @returns {{length}|*}
- */
-function getAppDir () {
-  let dir = process.cwd()
-  let count = 0
-
-  // only go up three folders max
-  while (dir.length && dir[dir.length - 1] !== sep && count <= 2) {
-    if (existsSync(join(dir, 'tauri.conf.js'))) {
-      return dir
-    }
-    count++
-    dir = normalize(join(dir, '..'))
-  }
-
-  // just return the current directory
-  return process.cwd()
-}
-
-const appDir = getAppDir()
-const tauriDir = resolve(appDir, 'src-tauri')
-
-module.exports = {
-  appDir,
-  tauriDir,
-
-  resolve: {
-    app: dir => resolve(appDir, dir),
-    tauri: dir => resolve(tauriDir, dir)
-  }
-}

+ 0 - 44
cli/tauri.js/helpers/copy-templates.js

@@ -1,44 +0,0 @@
-// forked from https://github.com/quasarframework/quasar/blob/master/app/lib/app-extension/Extension.js
-function renderFolders ({ source, target, scope }) {
-  const
-    fs = require('fs-extra')
-  const { join, resolve } = require('path')
-  const fglob = require('fast-glob')
-  const isBinary = require('isbinaryfile').isBinaryFileSync
-  const compileTemplate = require('lodash.template')
-
-  const files = fglob.sync(['**/*'], {
-    cwd: source
-  })
-
-  for (const rawPath of files) {
-    const targetRelativePath = rawPath.split('/').map(name => {
-      // dotfiles are ignored when published to npm, therefore in templates
-      // we need to use underscore instead (e.g. "_gitignore")
-      if (name.charAt(0) === '_' && name.charAt(1) !== '_') {
-        return `.${name.slice(1)}`
-      }
-      if (name.charAt(0) === '_' && name.charAt(1) === '_') {
-        return `${name.slice(1)}`
-      }
-      return name
-    }).join('/')
-
-    const targetPath = join(target, targetRelativePath)
-    const sourcePath = resolve(source, rawPath)
-
-    fs.ensureFileSync(targetPath)
-
-    if (isBinary(sourcePath)) {
-      fs.copyFileSync(sourcePath, targetPath)
-    } else {
-      const rawContent = fs.readFileSync(sourcePath, 'utf-8')
-      const template = compileTemplate(rawContent, {
-        interpolate: /<%=([\s\S]+?)%>/g
-      })
-      fs.writeFileSync(targetPath, template(scope), 'utf-8')
-    }
-  }
-}
-
-module.exports = renderFolders

+ 0 - 19
cli/tauri.js/helpers/logger.js

@@ -1,19 +0,0 @@
-const ms = require('ms')
-const chalk = require('chalk')
-
-let prevTime
-
-module.exports = function (banner, color = 'green') {
-  return function (msg) {
-    const curr = +new Date()
-    const diff = curr - (prevTime || curr)
-
-    prevTime = curr
-
-    if (msg) {
-      console.log(` ${chalk[color](banner)} ${msg} ${chalk.green(`+${ms(diff)}`)}`)
-    } else {
-      console.log()
-    }
-  }
-}

+ 0 - 49
cli/tauri.js/helpers/tauri-config.js

@@ -1,49 +0,0 @@
-const appPaths = require('./app-paths')
-const merge = require('webpack-merge')
-const error = require('../helpers/logger')('ERROR:', 'red')
-const { existsSync } = require('fs-extra')
-
-module.exports = cfg => {
-  const pkgPath = appPaths.resolve.app('package.json')
-  const tauriConfPath = appPaths.resolve.app('tauri.conf.js')
-  if (!existsSync(pkgPath)) {
-    error('Could not find a package.json in your app\'s directory.')
-    process.exit(1)
-  }
-  if (!existsSync(tauriConfPath)) {
-    error('Could not find a tauri config (tauri.conf.js) in your app\'s directory.')
-    process.exit(1)
-  }
-  const tauriConf = require(tauriConfPath)(cfg.ctx)
-  const pkg = require(pkgPath)
-
-  const config = merge({
-    build: {},
-    ctx: {},
-    tauri: {
-      embeddedServer: {
-        active: true
-      },
-      bundle: {
-        active: true
-      },
-      whitelist: {
-        all: false
-      },
-      window: {
-        title: pkg.productName
-      },
-      security: {
-        csp: 'default-src data: filesystem: ws: http: https: \'unsafe-eval\' \'unsafe-inline\''
-      },
-      edge: {
-        active: true
-      }
-    }
-  }, tauriConf, cfg)
-
-  process.env.TAURI_DIST_DIR = appPaths.resolve.app(config.build.distDir)
-  process.env.TAURI_DIR = appPaths.tauriDir
-
-  return config
-}

+ 25 - 5
cli/tauri.js/package.json

@@ -10,9 +10,12 @@
     "url": "https://opencollective.com/tauri"
   },
   "scripts": {
+    "build": "webpack --progress",
     "test": "jest --runInBand --no-cache",
+    "pretest": "yarn build",
     "test:mac-local": "jest --runInBand",
-    "lint-fix": "eslint --fix ./bin/*.js ./*.js ./helpers/*.js",
+    "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",
     "build:tauri[rust]": "cd ../tauri && TAURI_DIST_DIR=../../test/fixture/dist TAURI_DIR=../test/fixture cargo publish --dry-run --allow-dirty"
   },
@@ -39,6 +42,7 @@
     "yarn": ">= 1.19.1"
   },
   "dependencies": {
+    "@tauri-apps/tauri-inliner": "1.13.2",
     "@tauri-apps/toml": "2.2.4",
     "chalk": "3.0.0",
     "chokidar": "3.3.1",
@@ -49,7 +53,6 @@
     "imagemin-optipng": "7.1.0",
     "imagemin-pngquant": "8.0.0",
     "imagemin-zopfli": "6.0.0",
-    "@tauri-apps/tauri-inliner": "1.13.2",
     "is-png": "2.0.0",
     "isbinaryfile": "4.0.2",
     "jsdom": "15.2.1",
@@ -64,9 +67,21 @@
     "webpack-shell-plugin": "0.5.0"
   },
   "devDependencies": {
+    "@types/cross-spawn": "^6.0.1",
+    "@types/fs-extra": "^8.0.1",
+    "@types/imagemin": "^7.0.0",
+    "@types/imagemin-optipng": "^5.2.0",
+    "@types/jsdom": "^12.2.4",
+    "@types/lodash.debounce": "^4.0.6",
+    "@types/lodash.template": "^4.4.6",
+    "@types/ms": "^0.7.31",
+    "@types/sharp": "^0.23.1",
+    "@types/webpack-merge": "^4.1.5",
+    "@typescript-eslint/eslint-plugin": "^2.12.0",
+    "@typescript-eslint/parser": "^2.12.0",
     "dotenv": "8.2.0",
     "eslint": "6.7.2",
-    "eslint-config-standard": "14.1.0",
+    "eslint-config-standard-with-typescript": "^11.0.1",
     "eslint-plugin-import": "2.19.1",
     "eslint-plugin-lodash-template": "0.15.0",
     "eslint-plugin-node": "10.0.0",
@@ -77,8 +92,13 @@
     "jest": "24.9.0",
     "jest-mock-process": "1.2.0",
     "lint-staged": "9.5.0",
-    "lockfile-lint": "3.0.4",
-    "promise": "8.0.3"
+    "lockfile-lint": "3.0.3",
+    "promise": "8.0.3",
+    "ts-loader": "6.2.1",
+    "typescript": "3.7.3",
+    "webpack": "4.41.4",
+    "webpack-cli": "3.3.10",
+    "webpack-node-externals": "1.7.2"
   },
   "husky": {
     "hooks": {

+ 26 - 0
cli/tauri.js/src/api/build.ts

@@ -0,0 +1,26 @@
+import { TauriConfig } from 'types'
+import merge from 'webpack-merge'
+import * as entry from '../entry'
+import * as generator from '../generator'
+import { tauriDir } from '../helpers/app-paths'
+import getTauriConfig from '../helpers/tauri-config'
+import Runner from '../runner'
+
+module.exports = async (config: TauriConfig): Promise<void> => {
+  const tauri = new Runner()
+  const tauriConfig = getTauriConfig(
+    merge(
+      {
+        ctx: {
+          prod: true
+        }
+      } as any,
+      config as any
+    ) as TauriConfig
+  )
+
+  generator.generate(tauriConfig.tauri)
+  entry.generate(tauriDir, tauriConfig)
+
+  return tauri.build(tauriConfig)
+}

+ 27 - 0
cli/tauri.js/src/api/dev.ts

@@ -0,0 +1,27 @@
+import { TauriConfig } from 'types'
+import merge from 'webpack-merge'
+import * as entry from '../entry'
+import * as generator from '../generator'
+import { tauriDir } from '../helpers/app-paths'
+import getTauriConfig from '../helpers/tauri-config'
+import Runner from '../runner'
+
+module.exports = async (config: TauriConfig): Promise<void> => {
+  const tauri = new Runner()
+  const tauriConfig = getTauriConfig(
+    merge(
+      {
+        ctx: {
+          debug: true,
+          dev: true
+        }
+      } as any,
+      config as any
+    ) as TauriConfig
+  )
+
+  generator.generate(tauriConfig.tauri)
+  entry.generate(tauriDir, tauriConfig)
+
+  return tauri.run(tauriConfig)
+}

+ 14 - 0
cli/tauri.js/src/api/init.ts

@@ -0,0 +1,14 @@
+import { inject } from '../template'
+
+module.exports = (args: {
+  directory: string
+  force: false | 'conf' | 'template' | 'all'
+  logging: boolean
+  tauriPath?: string
+}): boolean => {
+  return inject(args.directory, 'all', {
+    force: args.force,
+    logging: args.logging,
+    tauriPath: args.tauriPath
+  })
+}

+ 155 - 87
cli/tauri.js/api/tauricon.js → cli/tauri.js/src/api/tauricon.ts

@@ -12,31 +12,27 @@
  * @license MIT
  */
 
-const path = require('path')
-const sharp = require('sharp')
-const imagemin = require('imagemin')
-const pngquant = require('imagemin-pngquant')
-const optipng = require('imagemin-optipng')
-const zopfli = require('imagemin-zopfli')
-const png2icons = require('png2icons')
-const readChunk = require('read-chunk')
-const isPng = require('is-png')
-const logger = require('../helpers/logger')
+import { access, ensureDir, ensureFileSync, writeFileSync } from 'fs-extra'
+import imagemin, { Plugin } from 'imagemin'
+import optipng from 'imagemin-optipng'
+import pngquant from 'imagemin-pngquant'
+import zopfli from 'imagemin-zopfli'
+import isPng from 'is-png'
+import path from 'path'
+import png2icons from 'png2icons'
+import readChunk from 'read-chunk'
+import sharp from 'sharp'
+import { appDir, tauriDir } from '../helpers/app-paths'
+import logger from '../helpers/logger'
+import * as settings from '../helpers/tauricon.config'
+
 const log = logger('app:spawn')
 const warn = logger('app:spawn', 'red')
 
-const settings = require('../helpers/tauricon.config.js')
-let image = false
+let image: boolean | sharp.Sharp = false
 const spinnerInterval = false
 
-const {
-  access,
-  writeFileSync,
-  ensureDir,
-  ensureFileSync
-} = require('fs-extra')
-
-const exists = async function (file) {
+const exists = async function(file: string | Buffer): Promise<boolean> {
   try {
     await access(file)
     return true
@@ -53,7 +49,7 @@ const exists = async function (file) {
  * @param {string} src - a folder to target
  * @exits {error} if not a png, if not an image
  */
-const checkSrc = async function (src) {
+const checkSrc = async (src: string): Promise<boolean | sharp.Sharp> => {
   if (image !== false) {
     return image
   } else {
@@ -83,13 +79,17 @@ const checkSrc = async function (src) {
  * @param {object} options - a subset of the settings
  * @returns {array} folders
  */
-const uniqueFolders = function (options) {
+// TODO: proper type of options and folders
+const uniqueFolders = (options: { [index: string]: any }): any[] => {
   let folders = []
   for (const type in options) {
-    if (options[type].folder) {
-      folders.push(options[type].folder)
+    const option = options[String(type)]
+    if (option.folder) {
+      folders.push(option.folder)
     }
   }
+  // TODO: is compare argument required?
+  // eslint-disable-next-line @typescript-eslint/require-array-sort-compare
   folders = folders.sort().filter((x, i, a) => !i || x !== a[i - 1])
   return folders
 }
@@ -100,11 +100,18 @@ const uniqueFolders = function (options) {
  * @param {string} hex - hex colour
  * @returns {array} r,g,b
  */
-const hexToRgb = function (hex) {
+const hexToRgb = (
+  hex: string
+): { r: number, g: number, b: number } | undefined => {
   // https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
   // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
   const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i
-  hex = hex.replace(shorthandRegex, function (m, r, g, b) {
+  hex = hex.replace(shorthandRegex, function(
+    m: string,
+    r: string,
+    g: string,
+    b: string
+  ) {
     return r + r + g + g + b + b
   })
 
@@ -115,29 +122,29 @@ const hexToRgb = function (hex) {
       g: parseInt(result[2], 16),
       b: parseInt(result[3], 16)
     }
-    : null
+    : undefined
 }
 
 /**
  * validate image and directory
- * @param {string} src
- * @param {string} target
- * @returns {Promise<void>}
  */
-const validate = async function (src, target) {
+const validate = async (
+  src: string,
+  target: string
+): Promise<boolean | sharp.Sharp> => {
   if (target !== undefined) {
     await ensureDir(target)
   }
   return checkSrc(src)
 }
 
+// TODO: should take end param?
 /**
  * Log progress in the command line
  *
- * @param {string} msg
  * @param {boolean} end
  */
-const progress = function (msg) {
+const progress = (msg: string): void => {
   process.stdout.write(`  ${msg}                       \r`)
 }
 
@@ -149,9 +156,8 @@ const progress = function (msg) {
  *     const spinnerInterval = spinner()
  *     // later
  *     clearInterval(spinnerInterval)
- * @returns {function} - the interval object
  */
-const spinner = function () {
+const spinner = (): NodeJS.Timeout => {
   return setInterval(() => {
     process.stdout.write('/ \r')
     setTimeout(() => {
@@ -166,22 +172,21 @@ const spinner = function () {
   }, 500)
 }
 
-const tauricon = exports.tauricon = {
-  validate: async function (src, target) {
+const tauricon = (exports.tauricon = {
+  validate: async function(src: string, target: string) {
     await validate(src, target)
     return typeof image === 'object'
   },
-  version: function () {
-    return require('../package.json').version
+  version: function() {
+    return __non_webpack_require__('../package.json').version
   },
-  /**
-   *
-   * @param {string} src
-   * @param {string} target
-   * @param {string} strategy
-   * @param {object} options
-   */
-  make: async function (src, target, strategy, options) {
+  make: async function(
+    src: string = path.resolve(appDir, 'app-icon.png'),
+    target: string = path.resolve(tauriDir, 'icons'),
+    strategy: string,
+    // TODO: proper type for options
+    options: { [index: string]: any }
+  ) {
     const spinnerInterval = spinner()
     options = options || settings.options.tauri
     await this.validate(src, target)
@@ -207,14 +212,25 @@ const tauricon = exports.tauricon = {
    * @param {string} target - where to drop the images
    * @param {object} options - js object that defines path and sizes
    */
-  build: async function (src, target, options) {
+  build: async function(
+    src: string,
+    target: string,
+    // TODO: proper type for options
+    options: { [index: string]: any }
+  ) {
     await this.validate(src, target)
     const sharpSrc = sharp(src) // creates the image object
-    const buildify2 = async function (pvar) {
+    const buildify2 = async function(
+      pvar: [string, number, number]
+    ): Promise<void> {
       try {
         const pngImage = sharpSrc.resize(pvar[1], pvar[1])
         if (pvar[2]) {
-          const rgb = hexToRgb(options.background_color)
+          const rgb = hexToRgb(options.background_color) || {
+            r: undefined,
+            g: undefined,
+            b: undefined
+          }
           pngImage.flatten({
             background: { r: rgb.r, g: rgb.g, b: rgb.b, alpha: 1 }
           })
@@ -228,15 +244,19 @@ const tauricon = exports.tauricon = {
 
     let output
     const folders = uniqueFolders(options)
+    // eslint-disable-next-line @typescript-eslint/no-for-in-array
     for (const n in folders) {
+      const folder = folders[Number(n)]
       // make the folders first
-      ensureDir(`${target}${path.sep}${folders[n]}`)
+      // TODO: should this be ensureDirSync?
+      // eslint-disable-next-line @typescript-eslint/no-floating-promises
+      ensureDir(`${target}${path.sep}${folder}`)
     }
     for (const optionKey in options) {
-      const option = options[optionKey]
+      const option = options[String(optionKey)]
       // chain up the transforms
       for (const sizeKey in option.sizes) {
-        const size = option.sizes[sizeKey]
+        const size = option.sizes[String(sizeKey)]
         if (!option.splash) {
           const dest = `${target}/${option.folder}`
           if (option.infix === true) {
@@ -244,7 +264,11 @@ const tauricon = exports.tauricon = {
           } else {
             output = `${dest}${path.sep}${option.prefix}${option.suffix}`
           }
-          const pvar = [output, size, option.background]
+          const pvar: [string, number, number] = [
+            output,
+            size,
+            option.background
+          ]
           await buildify2(pvar)
         }
       }
@@ -258,10 +282,20 @@ const tauricon = exports.tauricon = {
    * @param {string} target - where to drop the images
    * @param {object} options - js object that defines path and sizes
    */
-  splash: async function (src, splashSrc, target, options) {
+  splash: async function(
+    src: string,
+    splashSrc: string,
+    target: string,
+    // TODO: proper type for options
+    options: { [index: string]: any }
+  ) {
     let output
     let block = false
-    const rgb = hexToRgb(options.background_color)
+    const rgb = hexToRgb(options.background_color) || {
+      r: undefined,
+      g: undefined,
+      b: undefined
+    }
 
     // three options
     // options: splashscreen_type [generate | overlay | pure]
@@ -280,37 +314,42 @@ const tauricon = exports.tauricon = {
         process.exit(1)
       }
       sharpSrc = sharp(src)
-      sharpSrc.extend({
-        top: 726,
-        bottom: 726,
-        left: 726,
-        right: 726,
-        background: {
-          r: rgb.r,
-          g: rgb.g,
-          b: rgb.b,
-          alpha: 1
-        }
-      })
+      sharpSrc
+        .extend({
+          top: 726,
+          bottom: 726,
+          left: 726,
+          right: 726,
+          background: {
+            r: rgb.r,
+            g: rgb.g,
+            b: rgb.b,
+            alpha: 1
+          }
+        })
         .flatten({ background: { r: rgb.r, g: rgb.g, b: rgb.b, alpha: 1 } })
     } else if (options.splashscreen_type === 'overlay') {
       sharpSrc = sharp(splashSrc)
         .flatten({ background: { r: rgb.r, g: rgb.g, b: rgb.b, alpha: 1 } })
-        .composite([{
-          input: src
-          // blend: 'multiply' <= future work, maybe just a gag
-        }])
+        .composite([
+          {
+            input: src
+            // blend: 'multiply' <= future work, maybe just a gag
+          }
+        ])
     } else if (options.splashscreen_type === 'pure') {
-      sharpSrc = sharp(splashSrc)
-        .flatten({ background: { r: rgb.r, g: rgb.g, b: rgb.b, alpha: 1 } })
+      sharpSrc = sharp(splashSrc).flatten({
+        background: { r: rgb.r, g: rgb.g, b: rgb.b, alpha: 1 }
+      })
     }
-
+    // TODO: determine if this really could be undefined
+    // @ts-ignore
     const data = await sharpSrc.toBuffer()
 
     for (const optionKey in options) {
-      const option = options[optionKey]
+      const option = options[String(optionKey)]
       for (const sizeKey in option.sizes) {
-        const size = option.sizes[sizeKey]
+        const size = option.sizes[String(sizeKey)]
         if (option.splash) {
           const dest = `${target}${path.sep}${option.folder}`
           await ensureDir(dest)
@@ -337,14 +376,22 @@ const tauricon = exports.tauricon = {
    * @param {string} strategy - which minify strategy to use
    * @param {string} mode - singlefile or batch
    */
-  minify: async function (target, options, strategy, mode) {
-    let cmd
+  minify: async function(
+    target: string,
+    // TODO: proper type for options
+    options: { [index: string]: any },
+    strategy: string,
+    mode: string
+  ) {
+    let cmd: Plugin
     const minify = settings.options.minify
     if (!minify.available.find(x => x === strategy)) {
       strategy = minify.type
     }
     switch (strategy) {
       case 'pngquant':
+        // TODO: is minify.pngquantOptions the proper format?
+        // @ts-ignore
         cmd = pngquant(minify.pngquantOptions)
         break
       case 'optipng':
@@ -355,7 +402,7 @@ const tauricon = exports.tauricon = {
         break
     }
 
-    const __minifier = async (pvar) => {
+    const __minifier = async (pvar: string[]): Promise<string | void> => {
       await imagemin([pvar[0]], {
         destination: pvar[1],
         plugins: [cmd]
@@ -365,17 +412,28 @@ const tauricon = exports.tauricon = {
     }
     switch (mode) {
       case 'singlefile':
+        // TODO: the __minifier function only accepts one arg, why is cmd passed?
+        // @ts-ignore
         await __minifier([target, path.dirname(target)], cmd)
         break
       case 'batch':
         // eslint-disable-next-line no-case-declarations
         const folders = uniqueFolders(options)
+        // eslint-disable-next-line @typescript-eslint/no-for-in-array
         for (const n in folders) {
-          log('batch minify:', folders[n])
-          await __minifier([
-            `${target}${path.sep}${folders[n]}${path.sep}*.png`,
-            `${target}${path.sep}${folders[n]}`
-          ], cmd)
+          const folder = folders[Number(n)]
+          // TODO: The log argument doesn't accept multiple args, should this be fixed?
+          // @ts-ignore
+          log('batch minify:', folder)
+          await __minifier(
+            [
+              `${target}${path.sep}${folder}${path.sep}*.png`,
+              `${target}${path.sep}${folder}`
+            ],
+            // TODO: the __minifier function only accepts one arg, why is this here?
+            // @ts-ignore
+            cmd
+          )
         }
         break
       default:
@@ -393,7 +451,13 @@ const tauricon = exports.tauricon = {
    * @param {object} options
    * @param {string} strategy
    */
-  icns: async function (src, target, options, strategy) {
+  icns: async function(
+    src: string,
+    target: string,
+    // TODO: proper type for options
+    options: { [index: string]: any },
+    strategy: string
+  ) {
     try {
       if (!image) {
         process.exit(1)
@@ -403,10 +467,14 @@ const tauricon = exports.tauricon = {
       const sharpSrc = sharp(src)
       const buf = await sharpSrc.toBuffer()
 
+      // TODO: does this need to be awaited?
+      // eslint-disable-next-line @typescript-eslint/await-thenable
       const out = await png2icons.createICNS(buf, png2icons.BICUBIC, 0)
       ensureFileSync(path.join(target, '/icon.icns'))
       writeFileSync(path.join(target, '/icon.icns'), out)
 
+      // TODO: does this need to be awaited?
+      // eslint-disable-next-line @typescript-eslint/await-thenable
       const out2 = await png2icons.createICO(buf, png2icons.BICUBIC, 0, true)
       ensureFileSync(path.join(target, '/icon.ico'))
       writeFileSync(path.join(target, '/icon.ico'), out2)
@@ -414,7 +482,7 @@ const tauricon = exports.tauricon = {
       console.error(err)
     }
   }
-}
+})
 
 if (typeof exports !== 'undefined') {
   if (typeof module !== 'undefined' && module.exports) {

+ 16 - 0
cli/tauri.js/src/entry.ts

@@ -0,0 +1,16 @@
+import { ensureDirSync, readFileSync, writeFileSync } from 'fs-extra'
+import compileTemplate from 'lodash.template'
+import path from 'path'
+import { TauriConfig } from './types/config'
+
+export const generate = (outDir: string, cfg: TauriConfig): void => {
+  // this MUST be from the templates repo
+  const apiTemplate = readFileSync(
+    path.resolve(__dirname, '../templates/tauri.js'),
+    'utf-8'
+  )
+  const apiContent = compileTemplate(apiTemplate)(cfg)
+
+  ensureDirSync(outDir)
+  writeFileSync(path.join(outDir, 'tauri.js'), apiContent, 'utf-8')
+}

+ 11 - 0
cli/tauri.js/src/generator.ts

@@ -0,0 +1,11 @@
+import { writeFileSync } from 'fs-extra'
+import path from 'path'
+import { tauriDir } from './helpers/app-paths'
+import { TauriConfig } from './types/config'
+
+export const generate = (tauriConfig: TauriConfig['tauri']): void => {
+  const { bundle, ...cfg } = tauriConfig
+  const outDir = tauriDir
+  writeFileSync(path.join(outDir, 'config.json'), JSON.stringify(cfg))
+  writeFileSync(path.join(outDir, 'bundle.json'), JSON.stringify(bundle))
+}

+ 29 - 0
cli/tauri.js/src/helpers/app-paths.ts

@@ -0,0 +1,29 @@
+import { existsSync } from 'fs'
+import { join, normalize, resolve, sep } from 'path'
+
+const getAppDir = (): string => {
+  let dir = process.cwd()
+  let count = 0
+
+  // only go up three folders max
+  while (dir.length > 0 && dir.endsWith(sep) && count <= 2) {
+    if (existsSync(join(dir, 'tauri.conf.js'))) {
+      return dir
+    }
+    count++
+    dir = normalize(join(dir, '..'))
+  }
+
+  // just return the current directory
+  return process.cwd()
+}
+
+const appDir = getAppDir()
+const tauriDir = resolve(appDir, 'src-tauri')
+
+const resolveDir = {
+  app: (dir: string) => resolve(appDir, dir),
+  tauri: (dir: string) => resolve(tauriDir, dir)
+}
+
+export { appDir, tauriDir, resolveDir as resolve }

+ 56 - 0
cli/tauri.js/src/helpers/copy-templates.ts

@@ -0,0 +1,56 @@
+// forked from https://github.com/quasarframework/quasar/blob/master/app/lib/app-extension/Extension.js
+import fglob from 'fast-glob'
+import fs from 'fs-extra'
+import { isBinaryFileSync as isBinary } from 'isbinaryfile'
+import compileTemplate from 'lodash.template'
+import { join, resolve } from 'path'
+
+const copyTemplates = ({
+  source,
+  target,
+  scope
+}: {
+  source: string
+  target: string
+  scope?: object
+}): void => {
+  const files = fglob.sync(['**/*'], {
+    cwd: source
+  })
+
+  for (const rawPath of files) {
+    const targetRelativePath = rawPath
+      .split('/')
+      .map(name => {
+        // dotfiles are ignored when published to npm, therefore in templates
+        // we need to use underscore instead (e.g. "_gitignore")
+        if (name.startsWith('_') && name.charAt(1) !== '_') {
+          return `.${name.slice(1)}`
+        }
+        if (name.startsWith('_') && name.charAt(1) === '_') {
+          return `${name.slice(1)}`
+        }
+        return name
+      })
+      .join('/')
+
+    const targetPath = join(target, targetRelativePath)
+    const sourcePath = resolve(source, rawPath)
+
+    fs.ensureFileSync(targetPath)
+
+    if (isBinary(sourcePath)) {
+      fs.copyFileSync(sourcePath, targetPath)
+    } else {
+      // eslint-disable-next-line security/detect-non-literal-fs-filename
+      const rawContent = fs.readFileSync(sourcePath, 'utf-8')
+      const template = compileTemplate(rawContent, {
+        interpolate: /<%=([\s\S]+?)%>/g
+      })
+      // eslint-disable-next-line security/detect-non-literal-fs-filename
+      fs.writeFileSync(targetPath, template(scope), 'utf-8')
+    }
+  }
+}
+
+export default copyTemplates

+ 23 - 0
cli/tauri.js/src/helpers/logger.ts

@@ -0,0 +1,23 @@
+import chalk from 'chalk'
+import ms from 'ms'
+
+let prevTime: number
+
+export default (banner: string, color: string = 'green') => {
+  return (msg?: string) => {
+    const curr = +new Date()
+    const diff = curr - (prevTime || curr)
+
+    prevTime = curr
+
+    if (msg) {
+      console.log(
+        // TODO: proper typings for color and banner
+        // @ts-ignore
+        ` ${chalk[String(color)](String(banner))} ${msg} ${chalk.green(`+${ms(diff)}`)}`
+      )
+    } else {
+      console.log()
+    }
+  }
+}

+ 2 - 2
cli/tauri.js/helpers/on-shutdown.js → cli/tauri.js/src/helpers/on-shutdown.ts

@@ -1,5 +1,5 @@
-module.exports = function (fn) {
-  const cleanup = () => {
+export default (fn: () => void): void => {
+  const cleanup = (): void => {
     try {
       fn()
     } finally {

+ 26 - 24
cli/tauri.js/helpers/spawn.js → cli/tauri.js/src/helpers/spawn.ts

@@ -1,24 +1,26 @@
-const logger = require('./logger')
+import crossSpawn from 'cross-spawn'
+import logger from './logger'
+
 const log = logger('app:spawn')
 const warn = logger('app:spawn', 'red')
-const crossSpawn = require('cross-spawn')
 
 /*
- Returns pid, takes onClose
+  Returns pid, takes onClose
  */
-module.exports.spawn = function (cmd, params, cwd, onClose) {
+export const spawn = (
+  cmd: string,
+  params: string[],
+  cwd: string,
+  onClose: (code: number) => void
+): number => {
   log(`Running "${cmd} ${params.join(' ')}"`)
   log()
 
-  const runner = crossSpawn(
-    cmd,
-    params, {
-      stdio: 'inherit',
-      stdout: 'inherit',
-      stderr: 'inherit',
-      cwd
-    }
-  )
+  // TODO: move to execa?
+  const runner = crossSpawn(cmd, params, {
+    stdio: 'inherit',
+    cwd
+  })
 
   runner.on('close', code => {
     log()
@@ -33,21 +35,21 @@ module.exports.spawn = function (cmd, params, cwd, onClose) {
 }
 
 /*
- Returns nothing, takes onFail
+  Returns nothing, takes onFail
  */
-module.exports.spawnSync = function (cmd, params, cwd, onFail) {
+export const spawnSync = (
+  cmd: string,
+  params: string[],
+  cwd: string,
+  onFail: () => void
+): void => {
   log(`[sync] Running "${cmd} ${params.join(' ')}"`)
   log()
 
-  const runner = crossSpawn.sync(
-    cmd,
-    params, {
-      stdio: 'inherit',
-      stdout: 'inherit',
-      stderr: 'inherit',
-      cwd
-    }
-  )
+  const runner = crossSpawn.sync(cmd, params, {
+    stdio: 'inherit',
+    cwd
+  })
 
   if (runner.status || runner.error) {
     warn()

+ 59 - 0
cli/tauri.js/src/helpers/tauri-config.ts

@@ -0,0 +1,59 @@
+import { existsSync } from 'fs-extra'
+import { TauriConfig } from 'types'
+import merge from 'webpack-merge'
+import logger from '../helpers/logger'
+import * as appPaths from './app-paths'
+
+const error = logger('ERROR:', 'red')
+
+export default (cfg: Partial<TauriConfig>): TauriConfig => {
+  const pkgPath = appPaths.resolve.app('package.json')
+  const tauriConfPath = appPaths.resolve.app('tauri.conf.js')
+  if (!existsSync(pkgPath)) {
+    error("Could not find a package.json in your app's directory.")
+    process.exit(1)
+  }
+  if (!existsSync(tauriConfPath)) {
+    error(
+      "Could not find a tauri config (tauri.conf.js) in your app's directory."
+    )
+    process.exit(1)
+  }
+  const tauriConf = __non_webpack_require__(tauriConfPath)(cfg.ctx)
+  const pkg = __non_webpack_require__(pkgPath)
+
+  const config = merge(
+    {
+      build: {},
+      ctx: {},
+      tauri: {
+        embeddedServer: {
+          active: true
+        },
+        bundle: {
+          active: true
+        },
+        whitelist: {
+          all: false
+        },
+        window: {
+          title: pkg.productName
+        },
+        security: {
+          csp:
+            "default-src data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
+        },
+        edge: {
+          active: true
+        }
+      }
+    } as any,
+    tauriConf,
+    cfg as any
+  ) as TauriConfig
+
+  process.env.TAURI_DIST_DIR = appPaths.resolve.app(config.build.distDir)
+  process.env.TAURI_DIR = appPaths.tauriDir
+
+  return config
+}

+ 6 - 24
cli/tauri.js/helpers/tauricon.config.js → cli/tauri.js/src/helpers/tauricon.config.ts

@@ -1,4 +1,4 @@
-exports.options = {
+export const options = {
   // folder determines in which path to drop the generated file
   // prefix is the first part of the generated file's name
   // infix adds e.g. '44x44' based on the size in sizes to the generated file's name
@@ -38,53 +38,35 @@ exports.options = {
       prefix: '',
       infix: true,
       suffix: '.png',
-      sizes: [
-        32, 128
-      ]
+      sizes: [32, 128]
     },
     linux_2x: {
       folder: '.',
       prefix: '128x128@2x',
       infix: false,
       suffix: '.png',
-      sizes: [
-        256
-      ]
+      sizes: [256]
     },
     defaults: {
       folder: '.',
       prefix: 'icon',
       infix: false,
       suffix: '.png',
-      sizes: [
-        512
-      ]
+      sizes: [512]
     },
     appx_logo: {
       folder: '.',
       prefix: 'StoreLogo',
       infix: false,
       suffix: '.png',
-      sizes: [
-        50
-      ]
+      sizes: [50]
     },
     appx_square: {
       folder: '.',
       prefix: 'Square',
       infix: true,
       suffix: 'Logo.png',
-      sizes: [
-        30,
-        44,
-        71,
-        89,
-        107,
-        142,
-        150,
-        284,
-        310
-      ]
+      sizes: [30, 44, 71, 89, 107, 142, 150, 284, 310]
     }
     // todo: look at capacitor and cordova for insight into what icons
     // we need for those distribution targets

+ 119 - 68
cli/tauri.js/runner.js → cli/tauri.js/src/runner.ts

@@ -1,30 +1,39 @@
-const
-  chokidar = require('chokidar')
-const debounce = require('lodash.debounce')
-const path = require('path')
-const { readFileSync, writeFileSync, existsSync } = require('fs-extra')
-
-const
-  { spawn } = require('./helpers/spawn')
-const onShutdown = require('./helpers/on-shutdown')
-const generator = require('./generator')
-const entry = require('./entry')
-const { appDir, tauriDir } = require('./helpers/app-paths')
-
-const logger = require('./helpers/logger')
+import Inliner from '@tauri-apps/tauri-inliner'
+import toml from '@tauri-apps/toml'
+import chokidar, { FSWatcher } from 'chokidar'
+import { existsSync, readFileSync, writeFileSync } from 'fs-extra'
+import { JSDOM } from 'jsdom'
+import debounce from 'lodash.debounce'
+import path from 'path'
+import * as entry from './entry'
+import * as generator from './generator'
+import { appDir, tauriDir } from './helpers/app-paths'
+import logger from './helpers/logger'
+import onShutdown from './helpers/on-shutdown'
+import { spawn } from './helpers/spawn'
+import getTauriConfig from './helpers/tauri-config'
+import { TauriConfig } from './types/config'
+
 const log = logger('app:tauri', 'green')
 const warn = logger('app:tauri (template)', 'red')
 
 class Runner {
-  constructor () {
+  pid: number
+  tauriWatcher?: FSWatcher
+  devPath?: string
+  killPromise?: Function
+
+  constructor() {
     this.pid = 0
-    this.tauriWatcher = null
+    this.tauriWatcher = undefined
     onShutdown(() => {
-      this.stop()
+      this.stop().catch(e => {
+        throw e
+      })
     })
   }
 
-  async run (cfg) {
+  async run(cfg: TauriConfig): Promise<void> {
     const devPath = cfg.build.devPath
 
     if (this.pid) {
@@ -40,7 +49,7 @@ class Runner {
     })
 
     const runningDevServer = devPath.startsWith('http')
-    let inlinedAssets = []
+    let inlinedAssets: string[] = []
 
     if (!runningDevServer) {
       inlinedAssets = await this.__parseHtml(cfg, path.resolve(appDir, devPath))
@@ -57,9 +66,11 @@ class Runner {
 
     const features = runningDevServer ? ['dev-server'] : []
 
-    const startDevTauri = () => {
+    const startDevTauri = async (): Promise<void> => {
       return this.__runCargoCommand({
-        cargoArgs: ['run'].concat(features.length ? ['--features', ...features] : []),
+        cargoArgs: ['run'].concat(
+          features.length ? ['--features', ...features] : []
+        ),
         dev: true
       })
     }
@@ -67,31 +78,49 @@ class Runner {
     // Start watching for tauri app changes
     // 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(appDir, 'tauri.conf.js')
-      ], {
-        watchers: {
-          chokidar: {
-            ignoreInitial: true
+      .watch(
+        [
+          path.join(tauriDir, 'src'),
+          path.join(tauriDir, 'Cargo.toml'),
+          path.join(tauriDir, 'build.rs'),
+          path.join(appDir, 'tauri.conf.js')
+        ],
+        {
+          // TODO: incorrect options?
+          // @ts-ignore
+          watchers: {
+            chokidar: {
+              ignoreInitial: true
+            }
           }
         }
-      })
-      .on('change', debounce(async (path) => {
-        await this.__stopCargo()
-        if (path.includes('tauri.conf.js')) {
-          this.run(require('./helpers/tauri-config')({ ctx: cfg.ctx }))
-        } else {
-          startDevTauri()
-        }
-      }, 1000))
+      )
+      .on(
+        'change',
+        debounce((path: string) => {
+          this.__stopCargo()
+            .then(() => {
+              if (path.includes('tauri.conf.js')) {
+                this.run(getTauriConfig({ ctx: cfg.ctx })).catch(e => {
+                  throw e
+                })
+              } else {
+                startDevTauri().catch(e => {
+                  throw e
+                })
+              }
+            })
+            .catch(err => {
+              warn(err)
+              process.exit(1)
+            })
+        }, 1000)
+      )
 
     return startDevTauri()
   }
 
-  async build (cfg) {
+  async build(cfg: TauriConfig): Promise<void> {
     this.__manipulateToml(toml => {
       this.__whitelistApi(cfg, toml)
     })
@@ -108,11 +137,16 @@ class Runner {
       cfg.tauri.embeddedServer.active ? 'embedded-server' : 'no-server'
     ]
 
-    const buildFn = target => this.__runCargoCommand({
-      cargoArgs: [cfg.tauri.bundle.active ? 'tauri-cli' : 'build', '--features', ...features]
-        .concat(cfg.ctx.debug ? [] : ['--release'])
-        .concat(target ? ['--target', target] : [])
-    })
+    const buildFn = async (target?: string): Promise<void> =>
+      this.__runCargoCommand({
+        cargoArgs: [
+          cfg.tauri.bundle.active ? 'tauri-cli' : 'build',
+          '--features',
+          ...features
+        ]
+          .concat(cfg.ctx.debug ? [] : ['--release'])
+          .concat(target ? ['--target', target] : [])
+      })
 
     if (cfg.ctx.debug || !cfg.ctx.targetName) {
       // on debug mode or if no target specified,
@@ -127,19 +161,18 @@ class Runner {
     }
   }
 
-  __parseHtml (cfg, indexDir) {
-    const Inliner = require('@tauri-apps/tauri-inliner')
-    const jsdom = require('jsdom')
-    const { JSDOM } = jsdom
-    const inlinedAssets = []
+  async __parseHtml(cfg: TauriConfig, indexDir: string): Promise<string[]> {
+    const inlinedAssets: string[] = []
 
     return new Promise((resolve, reject) => {
       const distIndexPath = path.join(indexDir, 'index.html')
       if (!existsSync(distIndexPath)) {
-        warn(`Error: cannot find index.html in "${indexDir}". Did you forget to build your web code or update the build.distDir in tauri.conf.js?`)
+        warn(
+          `Error: cannot find index.html in "${indexDir}". Did you forget to build your web code or update the build.distDir in tauri.conf.js?`
+        )
         reject(new Error('Could not find index.html in dist dir.'))
       }
-      new Inliner(distIndexPath, (err, html) => {
+      new Inliner(distIndexPath, (err: Error, html: string) => {
         if (err) {
           reject(err)
         } else {
@@ -151,6 +184,9 @@ class Runner {
           })
 
           const tauriScript = document.createElement('script')
+          // TODO: should this be read as a buffer or a utf8 string?
+          // TODO: is text the write attribute to set?
+          // @ts-ignore
           tauriScript.text = readFileSync(path.join(tauriDir, 'tauri.js'))
           document.body.insertBefore(tauriScript, document.body.firstChild)
 
@@ -162,35 +198,44 @@ class Runner {
             document.head.appendChild(cspTag)
           }
 
-          writeFileSync(path.join(indexDir, 'index.tauri.html'), dom.serialize())
+          writeFileSync(
+            path.join(indexDir, 'index.tauri.html'),
+            dom.serialize()
+          )
           resolve(inlinedAssets)
         }
-      }).on('progress', event => {
+      }).on('progress', (event: string) => {
         const match = event.match(/([\S\d]+)\.([\S\d]+)/g)
         match && inlinedAssets.push(match[0])
       })
     })
   }
 
-  stop () {
+  async stop(): Promise<void> {
     return new Promise((resolve, reject) => {
       this.tauriWatcher && this.tauriWatcher.close()
-      this.__stopCargo().then(resolve)
+      this.__stopCargo()
+        .then(resolve)
+        .catch(e => {
+          console.error(e)
+        })
     })
   }
 
-  __runCargoCommand ({
+  async __runCargoCommand({
     cargoArgs,
     extraArgs,
     dev = false
-  }) {
+  }: {
+    cargoArgs: string[]
+    extraArgs?: string[]
+    dev?: boolean
+  }): Promise<void> {
     return new Promise(resolve => {
       this.pid = spawn(
         'cargo',
 
-        extraArgs
-          ? cargoArgs.concat(['--']).concat(extraArgs)
-          : cargoArgs,
+        extraArgs ? cargoArgs.concat(['--']).concat(extraArgs) : cargoArgs,
 
         tauriDir,
 
@@ -204,7 +249,7 @@ class Runner {
 
           if (this.killPromise) {
             this.killPromise()
-            this.killPromise = null
+            this.killPromise = undefined
           } else if (dev) {
             warn()
             warn('Cargo process was killed. Exiting...')
@@ -218,7 +263,7 @@ class Runner {
     })
   }
 
-  __stopCargo () {
+  async __stopCargo(): Promise<void> {
     const pid = this.pid
 
     if (!pid) {
@@ -234,10 +279,11 @@ class Runner {
     })
   }
 
-  __manipulateToml (callback) {
-    const toml = require('@tauri-apps/toml')
+  __manipulateToml(callback: (tomlContents: object) => void): void {
     const tomlPath = path.join(tauriDir, 'Cargo.toml')
+    // TODO: should this be read as buffer or string?
     const tomlFile = readFileSync(tomlPath)
+    // @ts-ignore
     const tomlContents = toml.parse(tomlFile)
 
     callback(tomlContents)
@@ -246,13 +292,18 @@ class Runner {
     writeFileSync(tomlPath, output)
   }
 
-  __whitelistApi (cfg, tomlContents) {
+  __whitelistApi(
+    cfg: TauriConfig,
+    tomlContents: { [index: string]: any }
+  ): void {
     const tomlFeatures = []
 
     if (cfg.tauri.whitelist.all) {
       tomlFeatures.push('all-api')
     } else {
-      const whitelist = Object.keys(cfg.tauri.whitelist).filter(w => cfg.tauri.whitelist[w] === true)
+      const whitelist = Object.keys(cfg.tauri.whitelist).filter(
+        w => cfg.tauri.whitelist[String(w)] === true
+      )
       tomlFeatures.push(...whitelist)
     }
 
@@ -264,4 +315,4 @@ class Runner {
   }
 }
 
-module.exports = Runner
+export default Runner

+ 30 - 22
cli/tauri.js/template.js → cli/tauri.js/src/template.ts

@@ -1,12 +1,22 @@
-const { copySync, existsSync, removeSync } = require('fs-extra')
-const { resolve, join, normalize } = require('path')
-const copyTemplates = require('./helpers/copy-templates')
+import { copySync, existsSync, removeSync } from 'fs-extra'
+import { join, normalize, resolve } from 'path'
+import copyTemplates from './helpers/copy-templates'
+import logger from './helpers/logger'
 
-const logger = require('./helpers/logger')
 const log = logger('app:tauri', 'green')
 const warn = logger('app:tauri (template)', 'red')
 
-const injectConfFile = (injectPath, { force, logging }) => {
+interface InjectOptions {
+  force: false | InjectionType
+  logging: boolean
+  tauriPath?: string
+}
+type InjectionType = 'conf' | 'template' | 'all'
+
+const injectConfFile = (
+  injectPath: string,
+  { force, logging }: InjectOptions
+): boolean | undefined => {
   const path = join(injectPath, 'tauri.conf.js')
   if (existsSync(path) && force !== 'conf' && force !== 'all') {
     warn(`tauri.conf.js found in ${path}
@@ -15,7 +25,7 @@ const injectConfFile = (injectPath, { force, logging }) => {
   } else {
     try {
       removeSync(path)
-      copySync(resolve(__dirname, './templates/tauri.conf.js'), path)
+      copySync(resolve(__dirname, '../templates/tauri.conf.js'), path)
     } catch (e) {
       if (logging) console.log(e)
       return false
@@ -25,7 +35,10 @@ const injectConfFile = (injectPath, { force, logging }) => {
   }
 }
 
-const injectTemplate = (injectPath, { force, logging, tauriPath }) => {
+const injectTemplate = (
+  injectPath: string,
+  { force, logging, tauriPath }: InjectOptions
+): boolean | undefined => {
   const dir = normalize(join(injectPath, 'src-tauri'))
   if (existsSync(dir) && force !== 'template' && force !== 'all') {
     warn(`Tauri dir (${dir}) not empty.
@@ -33,12 +46,14 @@ Run \`tauri init --force template\` to overwrite.`)
     if (!force) return false
   }
 
-  const tauriDep = tauriPath ? `{ path = "${join('..', tauriPath, 'tauri')}" }` : null
+  const tauriDep = tauriPath
+    ? `{ path = "${join('..', tauriPath, 'tauri')}" }`
+    : null
 
   try {
     removeSync(dir)
     copyTemplates({
-      source: resolve(__dirname, './templates/src-tauri'),
+      source: resolve(__dirname, '../templates/src-tauri'),
       scope: {
         tauriDep
       },
@@ -52,16 +67,11 @@ Run \`tauri init --force template\` to overwrite.`)
   }
 }
 
-/**
- *
- * @param {string} injectPath
- * @param {string} type ['conf'|'template'|'all']
- * @param {string|boolean} [force=false] - One of[false|'conf'|'template'|'all']
- * @param {boolean} [logging=false]
- * @param {string} [tauriPath=null]
- * @returns {boolean}
- */
-const inject = (injectPath, type, { force = false, logging = false, tauriPath = null }) => {
+const inject = (
+  injectPath: string,
+  type: InjectionType,
+  { force = false, logging = false, tauriPath }: InjectOptions
+): boolean => {
   if (typeof type !== 'string' || typeof injectPath !== 'string') {
     warn('- internal error. Required params missing.')
     return false
@@ -75,6 +85,4 @@ const inject = (injectPath, type, { force = false, logging = false, tauriPath =
   return true
 }
 
-module.exports = {
-  inject
-}
+export { inject }

+ 43 - 0
cli/tauri.js/src/types/config.ts

@@ -0,0 +1,43 @@
+// TODO: Clean up types, properly mark which ones are optional
+// May need to have different types for each stage of config generation process
+
+export interface TauriConfig {
+  build: {
+    distDir: string
+    devPath: string
+  }
+  ctx: {
+    prod?: boolean
+    dev?: boolean
+    target: string
+    debug?: boolean
+    targetName: string
+  }
+  bundle: {}
+  tauri: {
+    inlinedAssets: string[]
+    devPath: string
+    embeddedServer: {
+      active: boolean
+    }
+    bundle: {
+      active: boolean
+    }
+    whitelist: {
+      all: boolean
+      [index: string]: boolean
+    }
+    window: {
+      title: string
+      width: number
+      height: number
+      resizable: boolean
+    }
+    security: {
+      csp: string
+    }
+    edge: {
+      active: boolean
+    }
+  }
+}

+ 1 - 0
cli/tauri.js/src/types/index.ts

@@ -0,0 +1 @@
+export * from './config'

+ 5 - 0
cli/tauri.js/src/types/modules.d.ts

@@ -0,0 +1,5 @@
+declare module '@tauri-apps/tauri-inliner'
+declare module 'imagemin-zopfli'
+
+// eslint-disable-next-line @typescript-eslint/camelcase
+declare const __non_webpack_require__: Function

+ 1 - 1
cli/tauri.js/test/jest/__tests__/tauricon.spec.js

@@ -1,4 +1,4 @@
-const tauricon = require('api/tauricon.js')
+const tauricon = require('~/dist/tauricon.js')
 
 describe('[CLI] tauri-icon internals', () => {
   it('tells you the version', () => {

+ 16 - 0
cli/tauri.js/tsconfig.json

@@ -0,0 +1,16 @@
+{
+  "compilerOptions": {
+    "outDir": "./dist/",
+    "strict": true,
+    "module": "commonjs",
+    "target": "es5",
+    "allowJs": true,
+    "esModuleInterop": true,
+    "moduleResolution": "node",
+    "baseUrl": ".",
+    "paths": {
+      "types": ["src/types"]
+    }
+  },
+  "include": ["src"]
+}

+ 34 - 0
cli/tauri.js/webpack.config.js

@@ -0,0 +1,34 @@
+const path = require('path')
+const nodeExternals = require('webpack-node-externals')
+
+module.exports = {
+  entry: {
+    build: './src/api/build.ts',
+    dev: './src/api/dev.ts',
+    init: './src/api/init.ts',
+    tauricon: './src/api/tauricon.ts'
+  },
+  mode: process.env.NODE_ENV || 'development',
+  devtool: 'source-map',
+  module: {
+    rules: [
+      {
+        test: /\.tsx?$/,
+        use: 'ts-loader',
+        exclude: /node_modules/
+      }
+    ]
+  },
+  node: false,
+  resolve: {
+    extensions: ['.ts', '.js']
+  },
+  output: {
+    library: 'tauri',
+    libraryTarget: 'umd',
+    filename: '[name].js',
+    path: path.resolve(__dirname, 'dist')
+  },
+  externals: [nodeExternals()],
+  target: 'node'
+}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 678 - 35
cli/tauri.js/yarn.lock


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác