info.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. import toml from '@tauri-apps/toml'
  2. import chalk from 'chalk'
  3. import fs from 'fs'
  4. import os from 'os'
  5. import path from 'path'
  6. import { appDir, tauriDir } from '../helpers/app-paths'
  7. import { sync as spawn } from 'cross-spawn'
  8. import { TauriConfig } from './../types/config'
  9. import { CargoLock, CargoManifest } from '../types/cargo'
  10. import nonWebpackRequire from '../helpers/non-webpack-require'
  11. import packageJson from '../../package.json'
  12. import getScriptVersion from '../helpers/get-script-version'
  13. import {
  14. semverLt,
  15. getNpmLatestVersion,
  16. getCrateLatestVersion
  17. } from './dependency-manager/util'
  18. async function crateLatestVersion(name: string): Promise<string | undefined> {
  19. try {
  20. return await getCrateLatestVersion(name)
  21. } catch {
  22. return undefined
  23. }
  24. }
  25. interface DirInfo {
  26. path: string
  27. name: string
  28. type?: 'folder' | 'file'
  29. children?: DirInfo[]
  30. }
  31. /* eslint-disable security/detect-non-literal-fs-filename */
  32. function dirTree(filename: string, recurse = true): DirInfo {
  33. const stats = fs.lstatSync(filename)
  34. const info: DirInfo = {
  35. path: filename,
  36. name: path.basename(filename)
  37. }
  38. if (stats.isDirectory()) {
  39. info.type = 'folder'
  40. if (recurse) {
  41. info.children = fs.readdirSync(filename).map(function (child: string) {
  42. return dirTree(filename + '/' + child, false)
  43. })
  44. }
  45. } else {
  46. info.type = 'file'
  47. }
  48. return info
  49. }
  50. function getVersion(
  51. command: string,
  52. args: string[] = [],
  53. formatter?: (output: string) => string
  54. ): string {
  55. const version = getScriptVersion(command, args)
  56. if (version === null) {
  57. return chalk.red('Not installed')
  58. } else {
  59. return chalk.green(formatter === undefined ? version : formatter(version))
  60. }
  61. }
  62. interface Info {
  63. section?: boolean
  64. key: string
  65. value?: string
  66. suffix?: string
  67. }
  68. function printInfo(info: Info): void {
  69. const suffix = info.suffix ? ` ${info.suffix}` : ''
  70. console.log(
  71. `${info.section ? '\n' : ''}${info.key}${
  72. info.value === undefined ? '' : ' - ' + info.value
  73. }${suffix}`
  74. )
  75. }
  76. interface Version {
  77. section?: boolean
  78. key: string
  79. version?: string | null
  80. targetVersion?: string
  81. }
  82. function printVersion(info: Version): void {
  83. const outdated =
  84. info.version &&
  85. info.targetVersion &&
  86. semverLt(info.version, info.targetVersion)
  87. console.log(
  88. `${info.section ? '\n' : ''}${info.key}${
  89. info.version
  90. ? ' - ' + chalk.green(info.version)
  91. : chalk.red('Not installed')
  92. }` +
  93. (outdated && info.targetVersion
  94. ? ` (${chalk.red('outdated, latest: ' + info.targetVersion)})`
  95. : '')
  96. )
  97. }
  98. function readTomlFile<T extends CargoLock | CargoManifest>(
  99. filepath: string
  100. ): T | null {
  101. try {
  102. const file = fs.readFileSync(filepath).toString()
  103. return (toml.parse(file) as unknown) as T
  104. } catch (_) {
  105. return null
  106. }
  107. }
  108. async function printAppInfo(tauriDir: string): Promise<void> {
  109. printInfo({ key: 'App', section: true })
  110. const lockPath = path.join(tauriDir, 'Cargo.lock')
  111. const lock = readTomlFile<CargoLock>(lockPath)
  112. const lockPackages = lock
  113. ? lock.package.filter((pkg) => pkg.name === 'tauri')
  114. : []
  115. const manifestPath = path.join(tauriDir, 'Cargo.toml')
  116. const manifest = readTomlFile<CargoManifest>(manifestPath)
  117. let tauriVersion
  118. const foundTauriVersions = []
  119. if (manifest && lock && lockPackages.length === 1) {
  120. // everything looks good
  121. foundTauriVersions.push(lockPackages[0].version)
  122. tauriVersion = chalk.green(lockPackages[0].version)
  123. } else if (lock && lockPackages.length === 1) {
  124. // good lockfile, but no manifest - will cause problems building
  125. foundTauriVersions.push(lockPackages[0].version)
  126. tauriVersion = `${chalk.green(lockPackages[0].version)} (${chalk.red(
  127. 'no manifest'
  128. )})`
  129. } else {
  130. // we found multiple/none `tauri` packages in the lockfile, or
  131. // no manifest. in both cases we want more info on the manifest
  132. const manifestVersion = (): string => {
  133. const tauri = manifest?.dependencies.tauri
  134. if (tauri) {
  135. if (typeof tauri === 'string') {
  136. foundTauriVersions.push(tauri)
  137. return chalk.yellow(tauri)
  138. } else if (tauri.version) {
  139. foundTauriVersions.push(tauri.version)
  140. return chalk.yellow(tauri.version)
  141. } else if (tauri.path) {
  142. const manifestPath = path.resolve(tauriDir, tauri.path, 'Cargo.toml')
  143. const manifestContent = readTomlFile<CargoManifest>(manifestPath)
  144. let pathVersion = manifestContent?.package.version
  145. pathVersion = pathVersion
  146. ? chalk.yellow(pathVersion)
  147. : chalk.red(pathVersion)
  148. return `path:${tauri.path} [${pathVersion}]`
  149. }
  150. } else {
  151. return chalk.red('no manifest')
  152. }
  153. return chalk.red('unknown manifest')
  154. }
  155. let lockVersion
  156. if (lock && lockPackages.length > 0) {
  157. lockVersion = chalk.yellow(lockPackages.map((p) => p.version).join(', '))
  158. } else if (lock && lockPackages.length === 0) {
  159. lockVersion = chalk.red('unknown lockfile')
  160. } else {
  161. lockVersion = chalk.red('no lockfile')
  162. }
  163. tauriVersion = `${manifestVersion()} (${chalk.yellow(lockVersion)})`
  164. }
  165. const tauriVersionString = foundTauriVersions.reduce(
  166. (old, current) => (semverLt(old, current) ? current : old),
  167. '0.0.0'
  168. )
  169. const latestTauriCore = await crateLatestVersion('tauri')
  170. printInfo({
  171. key: ' tauri.rs',
  172. value: tauriVersion,
  173. suffix:
  174. tauriVersionString !== '0.0.0' &&
  175. latestTauriCore &&
  176. semverLt(tauriVersionString, latestTauriCore)
  177. ? `(${chalk.red('outdated, latest: ' + latestTauriCore)})`
  178. : undefined
  179. })
  180. try {
  181. const configPath = path.join(tauriDir, 'tauri.conf.json')
  182. const config = nonWebpackRequire(configPath) as TauriConfig
  183. printInfo({
  184. key: ' build-type',
  185. value: config.tauri.bundle?.active ? 'bundle' : 'build'
  186. })
  187. printInfo({
  188. key: ' CSP',
  189. value: config.tauri.security ? config.tauri.security.csp : 'unset'
  190. })
  191. printInfo({
  192. key: ' distDir',
  193. value: config.build
  194. ? chalk.green(config.build.distDir)
  195. : chalk.red('unset')
  196. })
  197. printInfo({
  198. key: ' devPath',
  199. value: config.build
  200. ? chalk.green(config.build.devPath)
  201. : chalk.red('unset')
  202. })
  203. } catch (_) {}
  204. }
  205. module.exports = async () => {
  206. printInfo({
  207. key: 'Operating System',
  208. value: chalk.green(
  209. `${os.type()}(${os.release()}) - ${os.platform()}/${os.arch()}`
  210. ),
  211. section: true
  212. })
  213. if (os.platform() === 'win32') {
  214. const { stdout } = spawn('REG', [
  215. 'QUERY',
  216. 'HKEY_CLASSES_root\\AppX3xxs313wwkfjhythsb8q46xdsq8d2cvv\\Application',
  217. '/v',
  218. 'ApplicationName'
  219. ])
  220. const match = /{(\S+)}/g.exec(stdout.toString())
  221. if (match) {
  222. const edgeString = match[1]
  223. printInfo({
  224. key: 'Microsoft Edge',
  225. value: edgeString.split('?')[0].replace('Microsoft.MicrosoftEdge_', '')
  226. })
  227. }
  228. }
  229. printInfo({ key: 'Node.js environment', section: true })
  230. printVersion({
  231. key: ' Node.js',
  232. version: process.version.slice(1),
  233. targetVersion: packageJson.engines.node.replace('>= ', '')
  234. })
  235. printVersion({
  236. key: ' tauri.js',
  237. version: packageJson.version,
  238. targetVersion: await getNpmLatestVersion('tauri')
  239. })
  240. printInfo({ key: 'Rust environment', section: true })
  241. printInfo({
  242. key: ' rustc',
  243. value: getVersion('rustc', [], (output) => output.split(' ')[1])
  244. })
  245. printInfo({
  246. key: ' cargo',
  247. value: getVersion('cargo', [], (output) => output.split(' ')[1])
  248. })
  249. printVersion({
  250. key: ' tauri-bundler',
  251. version: getScriptVersion('cargo', ['tauri-bundler']),
  252. targetVersion: await crateLatestVersion('tauri-bundler')
  253. })
  254. printInfo({ key: 'Global packages', section: true })
  255. printInfo({ key: ' NPM', value: getVersion('npm') })
  256. printInfo({ key: ' yarn', value: getVersion('yarn') })
  257. printInfo({ key: 'App directory structure', section: true })
  258. const tree = dirTree(appDir)
  259. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
  260. for (const artifact of tree.children || []) {
  261. if (artifact.type === 'folder') {
  262. console.log(`/${artifact.name}`)
  263. }
  264. }
  265. await printAppInfo(tauriDir)
  266. }
  267. /* eslint-enable security/detect-non-literal-fs-filename */