mobileBuild.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import os from 'node:os'
  2. import { Listr } from 'listr2'
  3. import { config as initDE } from 'dotenv'
  4. import { logLogo } from './logo'
  5. import { downloadFile } from './download'
  6. import { config, currentPath } from './config'
  7. import {
  8. fileExistsAsync,
  9. removeDir,
  10. createDir,
  11. runCommand,
  12. ensureDownloadedAndExtracted,
  13. renameWithOverwrite,
  14. } from './utils'
  15. initDE()
  16. logLogo()
  17. // ----------------------------
  18. // Main Listr Tasks definition
  19. // ----------------------------
  20. await new Listr(
  21. [
  22. {
  23. title: 'Rebuilding Nuxt.js Sources',
  24. task: () =>
  25. new Listr(
  26. [
  27. {
  28. title: 'Removing old build directories',
  29. task: async () => {
  30. await Promise.all([removeDir('.nuxt'), removeDir('.output')])
  31. },
  32. },
  33. {
  34. title: 'Generating static build using Nuxt.js',
  35. task: async (_, task) => {
  36. await runCommand('nuxt', ['generate'], { task })
  37. },
  38. },
  39. ],
  40. { concurrent: false },
  41. ),
  42. },
  43. {
  44. title: 'Configuring Capacitor Platforms',
  45. task: () =>
  46. new Listr(
  47. [
  48. {
  49. title: 'Removing outdated mobile platform directories',
  50. task: async () => {
  51. await Promise.all([removeDir('android'), removeDir('ios')])
  52. },
  53. },
  54. {
  55. title: 'Adding Capacitor mobile platforms',
  56. task: async (_, task) => {
  57. await runCommand('npx', ['cap', 'add', 'android'], { task })
  58. await runCommand('npx', ['cap', 'add', 'ios'], { task })
  59. await runCommand('npx', ['cap', 'sync'], { task })
  60. },
  61. },
  62. ],
  63. { concurrent: false },
  64. ),
  65. },
  66. {
  67. title: 'Assembling Android Application',
  68. task: () =>
  69. new Listr(
  70. [
  71. {
  72. title: 'Verifying Java SE 20 Installation',
  73. task: async (ctx, subTask) => {
  74. subTask.title = 'Checking Java installation'
  75. if (!(await fileExistsAsync(config.JAVA_BIN))) {
  76. const systemConfig: Partial<
  77. Record<NodeJS.Platform, Partial<Record<NodeJS.Architecture, string>>>
  78. > = {
  79. linux: {
  80. x64: `https://download.oracle.com/java/${config.JAVA_VERSION.split('.')[0]}/archive/jdk-${config.JAVA_VERSION}_linux-x64_bin.tar.gz`,
  81. arm64: `https://download.oracle.com/java/${config.JAVA_VERSION.split('.')[0]}/archive/jdk-${config.JAVA_VERSION}_linux-aarch64_bin.tar.gz`,
  82. },
  83. // TODO: add support for win32
  84. }
  85. const platform = os.platform() as keyof typeof systemConfig
  86. const arch = os.arch()
  87. const url = systemConfig[platform]?.[arch]
  88. if (!url) {
  89. throw new Error(
  90. 'Your system platform is not supported for Java installation',
  91. )
  92. }
  93. await createDir(config.JAVA_DIR)
  94. await ensureDownloadedAndExtracted(
  95. config.JAVA_BIN,
  96. url,
  97. `jdk-${config.JAVA_VERSION}.tar.gz`,
  98. [
  99. `rm -rf ${config.JAVA_DIR}/* && mkdir -p ${config.JAVA_DIR}`,
  100. `tar -xzf jdk-${config.JAVA_VERSION}.tar.gz -C ${config.JAVA_DIR}`,
  101. `rm jdk-${config.JAVA_VERSION}.tar.gz`,
  102. ],
  103. { task: subTask },
  104. )
  105. subTask.title = 'Java installed'
  106. }
  107. else {
  108. subTask.title = 'Java already installed'
  109. }
  110. },
  111. },
  112. {
  113. title: 'Verifying Android SDK Installation',
  114. task: async (ctx, subTask) => {
  115. const toolsCheck = config.ANDROID_TOOLS.map(tool =>
  116. fileExistsAsync(`${config.SDK_DIR}/${tool.replace(';', '/')}/package.xml`),
  117. )
  118. const sdkInstalled = (await Promise.all(toolsCheck)).every(Boolean)
  119. // Save the result for later tasks.
  120. ctx.isSdkInstalled = sdkInstalled
  121. subTask.title = sdkInstalled
  122. ? 'Android SDK is installed'
  123. : 'Android SDK not found, installation required'
  124. },
  125. },
  126. {
  127. title: 'Installing Android Command Line Tools',
  128. task: async (ctx, subTask) => {
  129. if (ctx.isSdkInstalled) {
  130. subTask.skip('Command Line Tools already installed')
  131. return
  132. }
  133. if (!(await fileExistsAsync(config.CMD_TOOLS_ARCHIVE))) {
  134. await downloadFile(
  135. subTask,
  136. 'https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip',
  137. config.CMD_TOOLS_ARCHIVE,
  138. )
  139. }
  140. subTask.title = 'Unzipping archive'
  141. await runCommand(
  142. 'bash',
  143. ['-c', `"rm -rf ${config.CMD_TOOLS_DIR} && unzip ${config.CMD_TOOLS_ARCHIVE}"`],
  144. { task: subTask },
  145. )
  146. subTask.title = 'Android Command Line Tools installed'
  147. },
  148. },
  149. {
  150. title: 'Installing Additional Android SDK Components',
  151. skip: ctx =>
  152. ctx.isSdkInstalled ? 'Additional tools already installed' : false,
  153. task: async (ctx, subTask) => {
  154. subTask.title = 'Accepting Android licenses'
  155. await createDir(config.SDK_DIR)
  156. await runCommand('bash', ['-c', `"chmod +x ./${config.CMD_TOOLS_DIR}/bin/sdkmanager"`], {
  157. task: subTask,
  158. disableOutput: true,
  159. })
  160. await runCommand(
  161. 'bash',
  162. [
  163. '-c',
  164. `"yes | ./${config.CMD_TOOLS_DIR}/bin/sdkmanager --sdk_root=${currentPath}/${config.SDK_DIR.slice(2)} --licenses"`,
  165. ],
  166. { task: subTask, disableOutput: true },
  167. )
  168. for (const tool of config.ANDROID_TOOLS) {
  169. subTask.title = `Installing: ${tool}`
  170. await runCommand(
  171. 'bash',
  172. [
  173. '-c',
  174. `"./${config.CMD_TOOLS_DIR}/bin/sdkmanager --sdk_root=${currentPath}/${config.SDK_DIR.slice(2)} --install '${tool}'"`,
  175. ],
  176. { task: subTask, disableOutput: true },
  177. )
  178. }
  179. subTask.title = 'Additional Android SDK components installed'
  180. },
  181. },
  182. {
  183. title: 'Compiling Android Project using Gradle',
  184. task: async (ctx, subTask) => {
  185. subTask.title = 'Gradle is building the project, please wait'
  186. await runCommand('bash', ['-c', '"cd android && ./gradlew assembleDebug"'], {
  187. task: subTask,
  188. maxOutputLines: 3,
  189. })
  190. renameWithOverwrite(`${currentPath}/android/app/build/outputs/apk/debug/app-debug.apk`, `${currentPath}/${process.env.APP_VERSION}.apk`)
  191. subTask.title = `APK built at: ${currentPath}/${process.env.APP_VERSION}.apk`
  192. },
  193. },
  194. {
  195. title: `Deploying APK to Connected Android Device ${!process.env.ANDROID_DEVICE_ID ? '[Will be skipped]' : ''}`,
  196. skip: () => !process.env.ANDROID_DEVICE_ID,
  197. task: async (ctx, subTask) => {
  198. const adb = `${config.SDK_DIR}/platform-tools/adb`
  199. const launchAppCommand = 'shell monkey -p app.hapticx.procollege -c android.intent.category.LAUNCHER 1'.split(' ')
  200. subTask.title = 'Checking connected devices'
  201. await runCommand(adb, ['devices'], { task: subTask })
  202. subTask.title = 'Installing APK on device'
  203. await runCommand(
  204. adb,
  205. [
  206. '-s',
  207. process.env.ANDROID_DEVICE_ID,
  208. 'install',
  209. './android/app/build/outputs/apk/debug/app-debug.apk',
  210. ],
  211. { task: subTask },
  212. )
  213. subTask.title = 'Launching the application'
  214. await runCommand(adb, ['-s', process.env.ANDROID_DEVICE_ID, ...launchAppCommand])
  215. subTask.title = `App launched on device: ${process.env.ANDROID_DEVICE_ID}`
  216. },
  217. },
  218. ],
  219. { concurrent: false },
  220. ),
  221. },
  222. ],
  223. {
  224. concurrent: false,
  225. rendererOptions: {
  226. collapseSubtasks: false,
  227. formatOutput: 'wrap',
  228. },
  229. },
  230. ).run()