mobileBuild.ts 8.9 KB

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