|
@@ -1,209 +1,235 @@
|
|
-import { exec } from 'node:child_process'
|
|
|
|
-import { promisify } from 'node:util'
|
|
|
|
-import path from 'node:path'
|
|
|
|
-import fs from 'node:fs'
|
|
|
|
|
|
+import os from 'node:os'
|
|
import { Listr } from 'listr2'
|
|
import { Listr } from 'listr2'
|
|
-import consola from 'consola'
|
|
|
|
-import { config } from 'dotenv'
|
|
|
|
|
|
+import { config as initDE } from 'dotenv'
|
|
import { logLogo } from './logo'
|
|
import { logLogo } from './logo'
|
|
import { downloadFile } from './download'
|
|
import { downloadFile } from './download'
|
|
-import { doesFileExist } from './fileExists'
|
|
|
|
-
|
|
|
|
-config()
|
|
|
|
-
|
|
|
|
-// yes | sdkmanager --sdk_root=/home/horanchikk/Documents/Sdk --licenses
|
|
|
|
-// sdkmanager --sdk_root=/home/horanchikk/Documents/Sdk --install "emulator" "build-tools;34.0.0" "build-tools;35.0.0" "platforms;android-34" "platform-tools"
|
|
|
|
-// export PATH=/home/horanchikk/Documents/jdk-20/bin:$PATH && ionic capacitor run android --list
|
|
|
|
-
|
|
|
|
-const execAsync = promisify(exec)
|
|
|
|
-const currentPath = path.resolve()
|
|
|
|
-
|
|
|
|
-async function runCommand(
|
|
|
|
- command: string,
|
|
|
|
- task?: {
|
|
|
|
- output: string
|
|
|
|
- },
|
|
|
|
- disableStdout: boolean | undefined = false,
|
|
|
|
-) {
|
|
|
|
- try {
|
|
|
|
- const { stdout, stderr } = await execAsync(command)
|
|
|
|
-
|
|
|
|
- if (!disableStdout) {
|
|
|
|
- if (stderr) {
|
|
|
|
- if (task) {
|
|
|
|
- task.output = stderr
|
|
|
|
- }
|
|
|
|
- else { consola.log(stderr) }
|
|
|
|
- }
|
|
|
|
- else {
|
|
|
|
- if (task) {
|
|
|
|
- task.output = stdout
|
|
|
|
- }
|
|
|
|
- else { consola.log(stdout) }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- catch (error) {
|
|
|
|
- if (!disableStdout) {
|
|
|
|
- if (task) {
|
|
|
|
- task.output = String(error)
|
|
|
|
- }
|
|
|
|
- else {
|
|
|
|
- consola.log(String(error))
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
|
|
+import { config, currentPath } from './config'
|
|
|
|
+import {
|
|
|
|
+ fileExistsAsync,
|
|
|
|
+ removeDir,
|
|
|
|
+ createDir,
|
|
|
|
+ runCommand,
|
|
|
|
+ ensureDownloadedAndExtracted,
|
|
|
|
+} from './utils'
|
|
|
|
|
|
|
|
+initDE()
|
|
logLogo()
|
|
logLogo()
|
|
|
|
|
|
-const ANDROID_DEVICE_ID = process.env.ANDROID_DEVICE_ID
|
|
|
|
-const tools = ['emulator', 'build-tools;34.0.0', 'build-tools;35.0.0', 'platforms;android-34', 'platform-tools']
|
|
|
|
-let isSdkInstalled = true
|
|
|
|
-
|
|
|
|
-for (const tool of tools) {
|
|
|
|
- if (!doesFileExist(`./sdk/${tool.replace(';', '/')}/package.xml`)) {
|
|
|
|
- isSdkInstalled = false
|
|
|
|
- break
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-try {
|
|
|
|
- await new Listr(
|
|
|
|
- [
|
|
|
|
- {
|
|
|
|
- title: 'Building nuxt',
|
|
|
|
- task: (_, task): Listr =>
|
|
|
|
- task.newListr(
|
|
|
|
- [
|
|
|
|
- {
|
|
|
|
- title: 'Removing old build',
|
|
|
|
- task: async (_) => {
|
|
|
|
- fs.rmSync('./.nuxt', { recursive: true, force: true })
|
|
|
|
- fs.rmSync('./.output', { recursive: true, force: true })
|
|
|
|
- },
|
|
|
|
|
|
+// ----------------------------
|
|
|
|
+// Main Listr Tasks definition
|
|
|
|
+// ----------------------------
|
|
|
|
+await new Listr(
|
|
|
|
+ [
|
|
|
|
+ {
|
|
|
|
+ title: 'Rebuilding Nuxt.js Sources',
|
|
|
|
+ task: () =>
|
|
|
|
+ new Listr(
|
|
|
|
+ [
|
|
|
|
+ {
|
|
|
|
+ title: 'Removing old build directories',
|
|
|
|
+ task: async () => {
|
|
|
|
+ await Promise.all([removeDir('.nuxt'), removeDir('.output')])
|
|
},
|
|
},
|
|
- {
|
|
|
|
- title: 'Re-building nuxt sources',
|
|
|
|
- task: async () => {
|
|
|
|
- await runCommand('nuxt generate', task)
|
|
|
|
- },
|
|
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ title: 'Generating static build using Nuxt.js',
|
|
|
|
+ task: async (_, task) => {
|
|
|
|
+ await runCommand('nuxt', ['generate'], { task })
|
|
},
|
|
},
|
|
- ],
|
|
|
|
- ),
|
|
|
|
- },
|
|
|
|
- {
|
|
|
|
- title: 'Building capacitor',
|
|
|
|
- task: (_, task): Listr =>
|
|
|
|
- task.newListr(
|
|
|
|
- [
|
|
|
|
- {
|
|
|
|
- title: 'Clearing old mobile builds',
|
|
|
|
- task: async (_) => {
|
|
|
|
- fs.rmSync('./android', { recursive: true, force: true })
|
|
|
|
- fs.rmSync('./ios', { recursive: true, force: true })
|
|
|
|
- },
|
|
|
|
|
|
+ },
|
|
|
|
+ ],
|
|
|
|
+ { concurrent: false },
|
|
|
|
+ ),
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ title: 'Configuring Capacitor Platforms',
|
|
|
|
+ task: () =>
|
|
|
|
+ new Listr(
|
|
|
|
+ [
|
|
|
|
+ {
|
|
|
|
+ title: 'Removing outdated mobile platform directories',
|
|
|
|
+ task: async () => {
|
|
|
|
+ await Promise.all([removeDir('android'), removeDir('ios')])
|
|
},
|
|
},
|
|
- {
|
|
|
|
- title: 'Adding mobile sources',
|
|
|
|
- task: async (_, task) => {
|
|
|
|
- await runCommand('npx cap add android', task)
|
|
|
|
- await runCommand('npx cap add ios', task)
|
|
|
|
- await runCommand('npx cap sync', task)
|
|
|
|
- },
|
|
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ title: 'Adding Capacitor mobile platforms',
|
|
|
|
+ task: async (_, task) => {
|
|
|
|
+ await runCommand('npx', ['cap', 'add', 'android'], { task })
|
|
|
|
+ await runCommand('npx', ['cap', 'add', 'ios'], { task })
|
|
|
|
+ await runCommand('npx', ['cap', 'sync'], { task })
|
|
},
|
|
},
|
|
- ],
|
|
|
|
- ),
|
|
|
|
- },
|
|
|
|
- {
|
|
|
|
- title: `Android app`,
|
|
|
|
- task: (_, task): Listr =>
|
|
|
|
- task.newListr(
|
|
|
|
- [
|
|
|
|
- {
|
|
|
|
- title: 'Download Java SE 20',
|
|
|
|
- task: async (_, task) => {
|
|
|
|
- task.title = 'Checking java'
|
|
|
|
- if (!doesFileExist('./jdk/jdk-20/bin/java')) {
|
|
|
|
- await downloadFile(task, 'https://download.java.net/openjdk/jdk20/ri/openjdk-20+36_linux-x64_bin.tar.gz', 'jdk-20.tar.gz')
|
|
|
|
- task.title = 'Unzipping archive'
|
|
|
|
- await runCommand('rm -rf ./jdk && mkdir jdk && tar -xzf jdk-20.tar.gz -C jdk && rm jdk-20.tar.gz')
|
|
|
|
- task.title = 'Java installed'
|
|
|
|
|
|
+ },
|
|
|
|
+ ],
|
|
|
|
+ { concurrent: false },
|
|
|
|
+ ),
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ title: 'Assembling Android Application',
|
|
|
|
+ task: () =>
|
|
|
|
+ new Listr(
|
|
|
|
+ [
|
|
|
|
+ {
|
|
|
|
+ title: 'Verifying Java SE 20 Installation',
|
|
|
|
+ task: async (ctx, subTask) => {
|
|
|
|
+ subTask.title = 'Checking Java installation'
|
|
|
|
+ if (!(await fileExistsAsync(config.JAVA_BIN))) {
|
|
|
|
+ const systemConfig: Partial<
|
|
|
|
+ Record<NodeJS.Platform, Partial<Record<NodeJS.Architecture, string>>>
|
|
|
|
+ > = {
|
|
|
|
+ linux: {
|
|
|
|
+ x64: 'https://download.oracle.com/java/20/archive/jdk-20.0.2_linux-x64_bin.tar.gz',
|
|
|
|
+ arm64: 'https://download.oracle.com/java/20/archive/jdk-20.0.2_linux-aarch64_bin.tar.gz',
|
|
|
|
+ },
|
|
|
|
+ // TODO: add support for win32
|
|
|
|
+ // win32: {
|
|
|
|
+ // x64: 'https://download.oracle.com/java/20/archive/jdk-20.0.2_windows-x64_bin.zip',
|
|
|
|
+ // },
|
|
}
|
|
}
|
|
- else {
|
|
|
|
- task.title = 'Java already installed'
|
|
|
|
|
|
+ const platform = os.platform() as keyof typeof systemConfig
|
|
|
|
+ const arch = os.arch()
|
|
|
|
+ const url = systemConfig[platform]?.[arch]
|
|
|
|
+ if (!url) {
|
|
|
|
+ throw new Error(
|
|
|
|
+ 'Your system platform is not supported for Java installation',
|
|
|
|
+ )
|
|
}
|
|
}
|
|
- },
|
|
|
|
|
|
+ await createDir(config.JAVA_DIR)
|
|
|
|
+ await ensureDownloadedAndExtracted(
|
|
|
|
+ config.JAVA_BIN,
|
|
|
|
+ url,
|
|
|
|
+ `jdk-${config.JAVA_VERSION}.tar.gz`,
|
|
|
|
+ [
|
|
|
|
+ `rm -rf ${config.JAVA_DIR}/* && mkdir -p ${config.JAVA_DIR}`,
|
|
|
|
+ `tar -xzf jdk-${config.JAVA_VERSION}.tar.gz -C ${config.JAVA_DIR}`,
|
|
|
|
+ `rm jdk-${config.JAVA_VERSION}.tar.gz`,
|
|
|
|
+ ],
|
|
|
|
+ { task: subTask },
|
|
|
|
+ )
|
|
|
|
+ subTask.title = 'Java installed'
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ subTask.title = 'Java already installed'
|
|
|
|
+ }
|
|
},
|
|
},
|
|
- {
|
|
|
|
- title: 'Download Command Line Tools for Android',
|
|
|
|
- task: async (_, task) => {
|
|
|
|
- if (isSdkInstalled) {
|
|
|
|
- task.title = 'Command Line Tools already installed'
|
|
|
|
- }
|
|
|
|
- else {
|
|
|
|
- await downloadFile(task, 'https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip', 'cmdline-tools.zip')
|
|
|
|
-
|
|
|
|
- task.title = 'Unzipping archive'
|
|
|
|
- await runCommand('rm -rf ./cmdline-tools && unzip cmdline-tools.zip && rm cmdline-tools.zip')
|
|
|
|
-
|
|
|
|
- task.title = 'Command Line Tools successfully installed'
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ title: 'Verifying Android SDK Installation',
|
|
|
|
+ task: async (ctx, subTask) => {
|
|
|
|
+ const toolsCheck = config.ANDROID_TOOLS.map(tool =>
|
|
|
|
+ fileExistsAsync(`${config.SDK_DIR}/${tool.replace(';', '/')}/package.xml`),
|
|
|
|
+ )
|
|
|
|
+ const sdkInstalled = (await Promise.all(toolsCheck)).every(Boolean)
|
|
|
|
+ // Save the result for later tasks.
|
|
|
|
+ ctx.isSdkInstalled = sdkInstalled
|
|
|
|
+ subTask.title = sdkInstalled
|
|
|
|
+ ? 'Android SDK is installed'
|
|
|
|
+ : 'Android SDK not found, installation required'
|
|
},
|
|
},
|
|
- {
|
|
|
|
- title: 'Download additional tools for Android',
|
|
|
|
- task: async (_, task) => {
|
|
|
|
- if (isSdkInstalled) {
|
|
|
|
- task.title = 'Additional tools already installed'
|
|
|
|
- }
|
|
|
|
- else {
|
|
|
|
- task.title = 'While you using this application, you accepting all Android licenses'
|
|
|
|
-
|
|
|
|
- await runCommand(`yes | ./cmdline-tools/bin/sdkmanager --sdk_root=${currentPath}/sdk --licenses`)
|
|
|
|
-
|
|
|
|
- for (const tool of tools) {
|
|
|
|
- task.title = `Downloading ${tool}`
|
|
|
|
- await runCommand(`./cmdline-tools/bin/sdkmanager --sdk_root=${currentPath}/sdk --install "${tool}"`)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- task.title = 'Additional tools successfully installed'
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ title: 'Installing Android Command Line Tools',
|
|
|
|
+ task: async (ctx, subTask) => {
|
|
|
|
+ if (ctx.isSdkInstalled) {
|
|
|
|
+ subTask.skip('Command Line Tools already installed')
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if (!(await fileExistsAsync(config.CMD_TOOLS_ARCHIVE))) {
|
|
|
|
+ await downloadFile(
|
|
|
|
+ subTask,
|
|
|
|
+ 'https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip',
|
|
|
|
+ config.CMD_TOOLS_ARCHIVE,
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+ subTask.title = 'Unzipping archive'
|
|
|
|
+ await runCommand(
|
|
|
|
+ 'bash',
|
|
|
|
+ ['-c', `"rm -rf ${config.CMD_TOOLS_DIR} && unzip ${config.CMD_TOOLS_ARCHIVE}"`],
|
|
|
|
+ { task: subTask },
|
|
|
|
+ )
|
|
|
|
+ subTask.title = 'Android Command Line Tools installed'
|
|
},
|
|
},
|
|
- {
|
|
|
|
- title: `Setup local env ${!ANDROID_DEVICE_ID || ANDROID_DEVICE_ID.length === 0 ? '[Will be skipped]' : ''}`,
|
|
|
|
- skip: !ANDROID_DEVICE_ID || ANDROID_DEVICE_ID.length === 0,
|
|
|
|
- task: async (_, task) => {
|
|
|
|
- await runCommand(`export PATH=${currentPath}/jdk/jdk-20/bin:$PATH && export ANDROID_SDK_ROOT=${currentPath}/sdk`)
|
|
|
|
- task.title = 'ENV successfully setuped'
|
|
|
|
- },
|
|
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ title: 'Installing Additional Android SDK Components',
|
|
|
|
+ skip: ctx =>
|
|
|
|
+ ctx.isSdkInstalled ? 'Additional tools already installed' : false,
|
|
|
|
+ task: async (ctx, subTask) => {
|
|
|
|
+ subTask.title = 'Accepting Android licenses'
|
|
|
|
+ await createDir(config.SDK_DIR)
|
|
|
|
+ await runCommand('bash', ['-c', `"chmod +x ./${config.CMD_TOOLS_DIR}/bin/sdkmanager"`], {
|
|
|
|
+ task: subTask,
|
|
|
|
+ disableOutput: true,
|
|
|
|
+ })
|
|
|
|
+ await runCommand(
|
|
|
|
+ 'bash',
|
|
|
|
+ [
|
|
|
|
+ '-c',
|
|
|
|
+ `"yes | ./${config.CMD_TOOLS_DIR}/bin/sdkmanager --sdk_root=${currentPath}/${config.SDK_DIR.slice(2)} --licenses"`,
|
|
|
|
+ ],
|
|
|
|
+ { task: subTask, disableOutput: true },
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ for (const tool of config.ANDROID_TOOLS) {
|
|
|
|
+ subTask.title = `Installing: ${tool}`
|
|
|
|
+ await runCommand(
|
|
|
|
+ 'bash',
|
|
|
|
+ [
|
|
|
|
+ '-c',
|
|
|
|
+ `"./${config.CMD_TOOLS_DIR}/bin/sdkmanager --sdk_root=${currentPath}/${config.SDK_DIR.slice(2)} --install '${tool}'"`,
|
|
|
|
+ ],
|
|
|
|
+ { task: subTask, disableOutput: true },
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+ subTask.title = 'Additional Android SDK components installed'
|
|
},
|
|
},
|
|
- {
|
|
|
|
- title: `Build android project ${!ANDROID_DEVICE_ID || ANDROID_DEVICE_ID.length === 0 ? '[Will be skipped]' : ''}`,
|
|
|
|
- skip: !ANDROID_DEVICE_ID || ANDROID_DEVICE_ID.length === 0,
|
|
|
|
- task: async (_, task) => {
|
|
|
|
- task.title = 'Gradle is building project, please wait'
|
|
|
|
- await runCommand('pnpm --package=@capacitor/cli dlx cap build android', task)
|
|
|
|
- task.title = 'Gradle building is finished'
|
|
|
|
- },
|
|
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ title: 'Compiling Android Project using Gradle',
|
|
|
|
+ task: async (ctx, subTask) => {
|
|
|
|
+ subTask.title = 'Gradle is building the project, please wait'
|
|
|
|
+ await runCommand('bash', ['-c', '"cd android && ./gradlew assembleDebug"'], {
|
|
|
|
+ task: subTask,
|
|
|
|
+ maxOutputLines: 3,
|
|
|
|
+ })
|
|
|
|
+ subTask.title = `APK built at: ${currentPath}/android/app/build/outputs/apk/debug/app-debug.apk`
|
|
},
|
|
},
|
|
- {
|
|
|
|
- title: `Run apk ${!ANDROID_DEVICE_ID || ANDROID_DEVICE_ID.length === 0 ? '[Will be skipped]' : ''}`,
|
|
|
|
- skip: !ANDROID_DEVICE_ID || ANDROID_DEVICE_ID.length === 0,
|
|
|
|
- task: async (_, task) => {
|
|
|
|
- task.title = `Running app on device ${ANDROID_DEVICE_ID}`
|
|
|
|
- await runCommand(`pnpm --package=@capacitor/cli dlx cap run android --no-sync --target ${ANDROID_DEVICE_ID}`, task)
|
|
|
|
- task.title = `Launched at ${ANDROID_DEVICE_ID}`
|
|
|
|
- },
|
|
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ title: `Deploying APK to Connected Android Device ${!process.env.ANDROID_DEVICE_ID ? '[Will be skipped]' : ''}`,
|
|
|
|
+ skip: () => !process.env.ANDROID_DEVICE_ID,
|
|
|
|
+ task: async (ctx, subTask) => {
|
|
|
|
+ const adb = `${config.SDK_DIR}/platform-tools/adb`
|
|
|
|
+ const launchAppCommand = 'shell monkey -p app.hapticx.procollege -c android.intent.category.LAUNCHER 1'.split(' ')
|
|
|
|
+
|
|
|
|
+ subTask.title = 'Checking connected devices'
|
|
|
|
+ await runCommand(adb, ['devices'], { task: subTask })
|
|
|
|
+ subTask.title = 'Installing APK on device'
|
|
|
|
+ await runCommand(
|
|
|
|
+ adb,
|
|
|
|
+ [
|
|
|
|
+ '-s',
|
|
|
|
+ process.env.ANDROID_DEVICE_ID,
|
|
|
|
+ 'install',
|
|
|
|
+ './android/app/build/outputs/apk/debug/app-debug.apk',
|
|
|
|
+ ],
|
|
|
|
+ { task: subTask },
|
|
|
|
+ )
|
|
|
|
+ subTask.title = 'Launching the application'
|
|
|
|
+ await runCommand(adb, ['-s', process.env.ANDROID_DEVICE_ID, ...launchAppCommand])
|
|
|
|
+ subTask.title = `App launched on device: ${process.env.ANDROID_DEVICE_ID}`
|
|
},
|
|
},
|
|
- ],
|
|
|
|
- ),
|
|
|
|
- },
|
|
|
|
- ],
|
|
|
|
- { concurrent: false, rendererOptions: { collapseSubtasks: false } },
|
|
|
|
- ).run()
|
|
|
|
-}
|
|
|
|
-catch (e) {
|
|
|
|
- console.error(e)
|
|
|
|
-}
|
|
|
|
|
|
+ },
|
|
|
|
+ ],
|
|
|
|
+ { concurrent: false },
|
|
|
|
+ ),
|
|
|
|
+ },
|
|
|
|
+ ],
|
|
|
|
+ {
|
|
|
|
+ concurrent: false,
|
|
|
|
+ rendererOptions: {
|
|
|
|
+ collapseSubtasks: false,
|
|
|
|
+ formatOutput: 'wrap',
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+).run()
|