#!/usr/bin/env node
* Copyright (c) 2022-2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const fs = require('fs');
const path = require('path');
const program = require('commander');
const inquirer = require('inquirer');
const { Platform, platform } = require('./src/cli/ace-check/platform');
const { create, repairProject, createProject } = require('./src/cli/ace-create/project');
const { createModule, repairModule } = require('./src/cli/ace-create/module');
const { createAbility, repairAbility } = require('./src/cli/ace-create/ability');
const { setConfig } = require('./src/cli/ace-config');
const check = require('./src/cli/ace-check');
const { devices, getDeviceID, showValidDevice, getDeviceType } = require('./src/cli/ace-devices');
const compiler = require('./src/cli/ace-build/ace-compiler');
const { build } = require('./src/cli/ace-build');
const { install } = require('./src/cli/ace-install');
const uninstall = require('./src/cli/ace-uninstall');
const { log } = require('./src/cli/ace-log');
const launch = require('./src/cli/ace-launch');
const run = require('./src/cli/ace-run');
const clean = require('./src/cli/ace-clean');
const test = require('./src/cli/ace-test');
const { getAbsolutePath, validOptions, checkProjectType,
isSupportedOperatorType, getCreatedPlatforms } = require('./src/cli/util');
const { aceHelp, commandHelp, subcommandHelp, unknownOptions, unknownCommands } = require('./src/cli/ace-help');
const { getProjectInfo, getTempPath } = require('./src/cli/ace-create/util');
const { getShowSdkVersion } = require('./src/cli/util/index');
const { modifyModules, modifyProject } = require('./src/cli/ace-modify');
const { analysisProject } = require('./src/cli/ace-analysis/index');
process.env.toolsPath = process.env.toolsPath || path.join(__dirname, '../');
globalThis.templatePath = path.join(__dirname, '..', 'templates');
let createdPlatformsCache;
const commandsSort = {
'Application': [],
'Device': [],
'Environment': [],
'Project': [],
};
const subCommands = ['apk', 'hap', 'ios'];
const installCommands = ['apk', 'hap', 'hsp', 'ios'];
const aceCommands = ['build', 'check', 'clean', 'config', 'create', 'devices', 'help',
'install', 'launch', 'log', 'new', 'run', 'test', 'uninstall', 'modify', 'analysis'];
parseCommander();
function getCreatedPlatformsCached(projectDir = process.cwd()) {
if (!createdPlatformsCache) {
createdPlatformsCache = getCreatedPlatforms(projectDir);
}
return createdPlatformsCache;
}
function normalizePlatformChoice(choice) {
const trimmed = (choice || '').toString().trim();
if (trimmed === '' || trimmed === '1') {
return 'both';
}
if (trimmed === '2') {
return 'android';
}
if (trimmed === '3') {
return 'ios';
}
return 'both';
}
function promptTargetPlatforms() {
return inquirer.prompt([{
name: 'platform',
type: 'input',
message: 'Please select the project platform (1: Default(Android, iOS); 2: Android; 3: iOS):',
validate(val) {
if (val === '' || val === undefined) {
return true;
}
const trimmed = val.toString().trim();
if (['1', '2', '3'].includes(trimmed)) {
return true;
}
return 'input must be 1, 2, 3 or empty for default.';
},
}]).then(answers => normalizePlatformChoice(answers.platform));
}
function parseCommander() {
program.configureHelp({
showGlobalOptions: true,
});
program.addHelpText('before', 'Manage your ArkUI cross-platform app development.');
program.addHelpText('before',
`
Common commands:
ace create
Create a new ArkUI cross-platform project in the specified directory.
ace run
Run your ArkUI cross-platform application on an attached device or in an emulator.
`);
program.version(require('../package').version);
program.usage('<command> [arguments]').name('ace').addHelpCommand(false);
program.option('-d, --device <device>', 'Input device id to specify the device to do something.');
program.unknownOption = () => unknownOptions();
program.on('command:*', unknownCommand => unknownCommands(unknownCommand));
parseBuild();
parseCheck();
parseClean();
parseConfig();
parseCreate();
parseDevices();
parseHelp();
parseInstall();
parseLaunch();
parseLog();
parseNew();
parseRun();
parseTest();
parseUninstall();
parseModify();
parseAnalysis();
const userInputCommand = process.argv.slice(2);
if (userInputCommand.length === 0 || ['help', '--help', '-h'].includes(userInputCommand[0]) && userInputCommand.length === 1) {
program.outputHelp(() => aceHelp(commandsSort));
} else {
if (!aceCommands.includes(userInputCommand[0]) && process.argv.some(arg => ['--help', '-h'].includes(arg))) {
unknownCommands(userInputCommand[0]);
}
program.addHelpText('afterAll', `\nRun "ace help" to see global options.`);
program.parse(process.argv);
}
}
function isProjectNameValid(name) {
const regex = /^[a-zA-Z][a-zA-Z0-9_]*$/;
if (name.length < 1 || name.length > 200) {
return false;
}
return regex.test(name);
}
function isBundleNameValid(name) {
const regex = /^[a-z][a-z0-9]*(\.[a-z0-9_]+)+$/;
if (name.length < 7 || name.length > 128) {
return false;
}
return regex.test(name);
}
function parseHelp() {
const helpCmd = program.command('help', { hidden: true })
.usage('[arguments]')
.description(`help.`)
.addHelpCommand(false)
.addHelpText('afterAll', `\nRun "ace help" to see global options.`);
aceCommands.forEach(subcommand => {
helpCmd.command(subcommand, { hidden: true });
});
helpCmd.on('command:*', unknownCommand => unknownCommands(unknownCommand));
}
function parseCreate() {
const createCmd = program.command('create [outputDir]', { hidden: true })
.usage('<output directory>')
.option('-t, --template [type]', '[app (default), library, plugin_napi]')
.description(`Create a new ArkUI cross-platform project.`)
.action((outputDir, cmd) => {
if (outputDir === undefined) {
console.error('\x1B[31m%s\x1B[0m', 'No option specified for the output directory.');
return false;
}
const initInfo = {};
initInfo.currentProjectPath = outputDir;
const absolutePath = getAbsolutePath(outputDir);
const projectName = path.basename(absolutePath);
if (!cmd.template) {
initInfo.template = 'app';
} else if (cmd.template === 'app' || cmd.template === 'library' || cmd.template === 'plugin_napi') {
initInfo.template = cmd.template;
} else {
console.error('\x1B[31m%s\x1B[0m', `Failed to create the project.` +
`Invalid template type ${cmd.template}, choose from the app, library or plugin_napi options.`);
return false;
}
if (fs.existsSync(absolutePath)) {
if (!fs.existsSync(path.join(absolutePath, '.projectInfo'))) {
console.error('\x1B[31m%s\x1B[0m', 'This project already exist, ' +
"but the project information file doesn't exist and can't be repaired.");
return false;
} else {
const currentProjectTemplate = checkProjectType(absolutePath);
if (initInfo.template !== currentProjectTemplate) {
console.log('\x1B[31m%s\x1B[0m', `The requested template type ${initInfo.template}
doesn't match the existing template type of ${currentProjectTemplate}.`);
return false;
}
inquirer.prompt([{
name: 'repair',
type: 'input',
message: `The project already exists. Do you want to repair the project (y / n):`,
validate(val) {
if (val.toLowerCase() !== 'y' && val.toLowerCase() !== 'n') {
return 'Please enter y / n!';
} else {
return true;
}
},
}]).then(answers => {
if (answers.repair.toLowerCase() === 'y') {
const projectInfo = getProjectInfo(absolutePath);
const projectTempPath = getTempPath(outputDir);
const isRepair = true;
const createdPlatforms = getCreatedPlatformsCached();
let targetPlatforms = 'both';
if (createdPlatforms.includes('android') && createdPlatforms.includes('ios')) {
targetPlatforms = 'both';
} else if (createdPlatforms.includes('android')) {
targetPlatforms = 'android';
} else if (createdPlatforms.includes('ios')) {
targetPlatforms = 'ios';
}
createProject(absolutePath, projectTempPath, projectInfo.bundleName, projectName, projectInfo.runtimeOS,
initInfo.template, initInfo.currentProjectPath, projectInfo.compileSdkVersion, targetPlatforms, isRepair);
repairModule(globalThis.templatePath, absolutePath, projectTempPath);
repairAbility(globalThis.templatePath, absolutePath, projectTempPath);
repairProject(absolutePath, outputDir);
} else {
console.error('Failed to repair project, preserve existing project.');
}
});
return true;
}
}
if (!isProjectNameValid(projectName)) {
console.log('The project dir must contain 1 to 200 characters, start with a ' +
'letter, and include only letters, digits and underscores (_)');
return false;
}
inquirer.prompt([{
name: 'project',
type: 'input',
message: `Enter the project name(${projectName}):`,
validate(val) {
if (val === '') {
val = projectName;
}
if (!isProjectNameValid(val)) {
return 'The project name must contain 1 to 200 characters, start with a ' +
'letter, and include only letters, digits and underscores (_)';
}
return true;
},
}]).then(answers => {
initInfo.platform = '';
initInfo.project = answers.project || projectName;
initInfo.projectAbsolutePath = absolutePath;
initInfo.outputDir = outputDir;
inquirer.prompt([{
name: 'bundleName',
type: 'input',
message: `Enter the bundleName (com.example.${initInfo.project.toLowerCase()}):`,
validate(val) {
if (val === '') {
val = `com.example.${initInfo.project.toLowerCase()}`;
}
if (!isBundleNameValid(val)) {
return 'The bundle name must contain 7 to 128 characters,start with a letter,and include ' +
'only lowercase letters, digits,underscores(_) and at least one separator(.).';
}
return true;
},
}]).then(answers => {
initInfo.bundleName = answers.bundleName ? answers.bundleName.toLowerCase() :
`com.example.${initInfo.project.toLowerCase()}`;
inquirer.prompt([{
name: 'runtimeOS',
type: 'input',
message: 'Enter the runtimeOS (1: OpenHarmony; 2: HarmonyOS):',
validate(val) {
if (val === '1' || val === '2') {
return true;
} else {
return 'input must be an integer: 1 or 2.';
}
},
}]).then(answers => {
initInfo.runtimeOS = answers.runtimeOS;
let sdkVersionShowMap = getShowSdkVersion();
let sdkChooseStr = '';
sdkVersionShowMap.forEach((value, key) => {
if (sdkChooseStr !== '') {
sdkChooseStr = sdkChooseStr + '; ';
}
sdkChooseStr = sdkChooseStr + key + ': ' + value;
});
let messageStr = 'Please select the Complie SDk (' + sdkChooseStr + '):';
inquirer.prompt([{
name: 'Complie SDk',
type: 'input',
message: messageStr,
validate(val) {
if (sdkVersionShowMap.has(val)) {
return true;
} else {
return getChooseSdkVersionTip(sdkVersionShowMap);
}
},
}]).then(answers => {
initInfo.sdkVersion = sdkVersionShowMap.get(answers['Complie SDk']);
promptTargetPlatforms().then(targetPlatforms => {
initInfo.platform = targetPlatforms;
if (platform === Platform.MacOS && (initInfo.platform === 'ios' || initInfo.platform === 'both')) {
inquirer.prompt([{
name: 'integrationApproach',
type: 'input',
message: 'Please select the Framework integration approach (1: Native Manual; 2: CocoaPods):',
validate(val) {
if (val === '1' || val === '2') {
return true;
} else {
return 'input must be an integer: 1 or 2.';
}
},
}]).then(answers => {
initInfo.integrationApproach = answers.integrationApproach;
create(initInfo);
});
} else {
create(initInfo);
}
});
});
});
});
});
});
if (process.argv[2] === 'help' && process.argv[3] === 'create') {
commandHelp(createCmd);
}
createCmd.unknownOption = () => unknownOptions();
commandsSort['Project'].push(program.commands[program.commands.length - 1]);
}
function getChooseSdkVersionTip(sdkVersionShowMap) {
let sdkChooseStr = '';
sdkVersionShowMap.forEach((value, key) => {
if (sdkChooseStr === '') {
sdkChooseStr = sdkChooseStr + key;
} else {
sdkChooseStr = sdkChooseStr + ' or ' + key;
}
});
return 'input must be an integer: ' + sdkChooseStr + '.';
}
function isValidSocketPort(port) {
if (port === undefined || port === null || port === '') {
return false;
}
if (port === true) {
return true;
}
const portStr = String(port).trim();
if (!/^\d+$/.test(portStr)) {
return false;
}
const portNumber = Number(portStr);
return Number.isInteger(portNumber) && portNumber >= 1 && portNumber <= 65535;
}
function parseNew() {
const newCmd = program.command('new', { hidden: true })
.usage('<subCommand> [arguments]')
.description(`Create a new ability or module for your project.`)
.addHelpCommand(false)
.on('--help', () => {
if (process.argv.some(option => ['help', '--help', '-h'].includes(option)) || process.argv.length === 3) {
console.log(`
Available subcommands:
ability Create a new ability.
module Create a new module.`);
}
});
const newArgs = ['ability', 'module'];
newArgs.forEach(subcommand => {
const newSubcommand = newCmd.command(subcommand, { hidden: true });
newSubcommand
.action((cmd) => {
if (subcommand === 'module') {
createModule();
} else if (subcommand === 'ability') {
createAbility();
}
});
if (process.argv[2] === 'help' && process.argv[3] === 'new') {
subcommandHelp(newCmd, newArgs, subcommand, newSubcommand);
}
newSubcommand.unknownOption = () => unknownOptions();
});
newCmd.on('command:*', unknownCommand => unknownCommands(unknownCommand));
newCmd.unknownOption = () => unknownOptions();
commandsSort['Project'].push(program.commands[program.commands.length - 1]);
}
function parseCheck() {
const checkCmd = program.command('check', { hidden: true })
.usage('[arguments]')
.option('-v, --v', 'Show details.')
.description(`Show information about the installed tools.`)
.action((cmd) => {
check(cmd);
});
checkCmd.unknownOption = () => unknownOptions();
if (process.argv[2] === 'help' && process.argv[3] === 'check') {
commandHelp(checkCmd);
}
commandsSort['Environment'].push(program.commands[program.commands.length - 1]);
}
function parseDevices() {
const devicesCmd = program.command('devices', { hidden: true })
.usage('[arguments]')
.description(`List the connected devices.`)
.action(() => {
devices();
});
if (process.argv[2] === 'help' && process.argv[3] === 'devices') {
commandHelp(devicesCmd);
}
devicesCmd.unknownOption = () => unknownOptions();
commandsSort['Device'].push(program.commands[program.commands.length - 1]);
}
function parseConfig() {
const configCmd = program.command('config', { hidden: true })
.usage('[arguments]')
.option('--android-sdk [Android SDK]', 'Android SDK path.')
.option('--android-studio-path [Android Studio Path]', 'Android Studio path.')
.option('--arkui-x-sdk [ArkUI-X SDK]', 'ArkUI-X Sdk path.')
.option('--build-dir [Build Dir]', 'Build directory.')
.option('--deveco-studio-path [DevEco Studio Path]', 'DevEco Studio path.')
.option('--harmonyos-sdk [HarmonyOS SDK]', 'HarmonyOS SDK path.')
.option('--java-sdk [Java SDK]', 'Java SDK path.')
.option('--nodejs-dir [Nodejs Dir]', 'Node.js path.')
.option('--ohpm-dir [Ohpm Dir]', 'Ohpm path.')
.option('--openharmony-sdk [OpenHarmony SDK]', 'OpenHarmony SDK path.')
.option('--source-dir [Source Dir]', 'Source path.')
.option('--command-line-tools-path [command line tools Dir]', 'Command Line Tools path.(only linux)')
.description(`Configure ArkUI cross-platform settings.`)
.action((cmd) => {
if (Object.keys(cmd).length !== 0) {
setConfig({
'android-sdk': cmd.androidSdk,
'android-studio-path': cmd.androidStudioPath,
'arkui-x-sdk': cmd.arkuiXSdk,
'build-dir': cmd.buildDir,
'deveco-studio-path': cmd.devecoStudioPath,
'harmonyos-sdk': cmd.harmonyosSdk,
'java-sdk': cmd.javaSdk,
'nodejs-dir': cmd.nodejsDir,
'ohpm-dir': cmd.ohpmDir,
'openharmony-sdk': cmd.openharmonySdk,
'source-dir': cmd.sourceDir,
'command-line-tools-path': cmd.commandLineToolsPath,
});
} else {
console.log('Please use ace config with options :' + `
--android-sdk [Android SDK]
--android-studio-path [Android Studio Path]
--arkui-x-sdk [ArkUI-X SDK]
--build-dir [Build Dir]
--deveco-studio-path [DevEco Studio Path]
--harmonyos-sdk [HarmonyOS SDK]
--java-sdk [Java SDK]
--nodejs-dir [Nodejs Dir]
--ohpm-dir [Ohpm Dir]
--openharmony-sdk [OpenHarmony SDK]
--source-dir [Source Dir]
--command-line-tools-path [Command Line Tools Path(only linux)]`);
}
});
if (process.argv[2] === 'help' && process.argv[3] === 'config') {
commandHelp(configCmd);
}
configCmd.unknownOption = () => unknownOptions();
commandsSort['Environment'].push(program.commands[program.commands.length - 1]);
}
function parseBuild() {
const buildCmd = program.command('build', { hidden: true })
.addHelpCommand(false)
.usage('<subCommand> [arguments]')
.description(`Build an executable app or install a bundle.`)
.on('--help', () => {
if (process.argv.some(option => ['help', '--help', '-h'].includes(option)) || process.argv.length === 3) {
console.log(`
Available subcommands:
aab Build an Android App Bundle file from your app.
aar Build a repository containing an AAR and a POM file.
apk Build an Android APK file from your app.
bundle Build an ArkUI cross-platform assets directory from your app.
hap Build a HarmonyOS/Openharmony HAP file from your app.
hsp Build a HarmonyOS/Openharmony HSP file from your app.
ios Build an iOS APP file from your app.
ios-framework Build an iOS framework.
ios-xcframework Build an iOS XCFramework.`);
}
});
const buildArgs = ['aab', 'aar', 'apk', 'bundle', 'hap', 'hsp', 'ios', 'ios-framework', 'ios-xcframework'];
buildArgs.forEach(subcommand => {
const buildSubcommand = buildCmd.command(subcommand, { hidden: true }).usage('[arguments]');
if (subcommand !== 'hsp') {
buildSubcommand
.option('-r, --release', 'Build a release version of your app.')
.option('--debug', 'Build a debug version of your app.')
.option('--profile', 'Build a version of your app specialized for performance profiling.');
} else {
buildSubcommand.option('--target [moduleName]', 'name of module to be built');
}
if (subcommand === 'ios') {
buildSubcommand
.option('--nosign', 'Build without sign.')
.option('--analyze', 'analyze/diff package size')
.option('-s, --simulator', 'Build for iOS Simulator.');
}
if (subcommand === 'ios-framework' || subcommand === 'ios-xcframework') {
buildSubcommand
.option('-s, --simulator', 'Build for iOS Simulator.');
}
if (['apk', 'aab', 'aar', 'bundle'].includes(subcommand)) {
buildSubcommand
.option('--target-platform <platform>', 'The target platform for which the package is compiled ' +
'[arm, arm64, x86_64]. stub.an is packaged by default and only for arm64 or x86_64 targets.')
.option(
'--shrink',
'Disable copying and packaging stub.an into Android assets. ' +
'By default stub.an is packaged only for arm64 and x86_64 targets.'
);
}
if (subcommand === 'apk') {
buildSubcommand
.option('--analyze', 'analyze/diff package size');
}
if (subcommand === 'hap') {
buildSubcommand
.option('--analyze', 'analyze/diff package size')
.option('--target [moduleName]', 'name of module to be built');
}
if (process.argv[2] === 'help' && process.argv[3] === 'build') {
subcommandHelp(buildCmd, buildArgs, subcommand, buildSubcommand);
}
buildSubcommand
.action((cmd) => {
cmd.simulator = cmd.simulator && platform === Platform.MacOS;
if ((cmd.release && cmd.debug) || (cmd.release && cmd.profile) || (cmd.profile && cmd.debug)) {
console.error('\x1B[31m%s\x1B[0m', 'Multiple build models are not allowed to exist at the same time.');
return false;
}
if (cmd.targetPlatform && (subcommand === 'apk' || subcommand === 'aab' ||
subcommand === 'aar' || subcommand === 'bundle')) {
const errValue = validOptions(cmd.targetPlatform, ['arm', 'arm64', 'x86_64']);
if (errValue) {
console.error('\x1B[31m%s\x1B[0m', `"${errValue}" is an invalid value, please input arm, arm64, x86_64.`);
return false;
}
}
if (subcommand === 'hap' || subcommand === 'hsp' || subcommand === 'bundle') {
compiler(subcommand, cmd);
} else if (subcommand === 'apk' || subcommand === 'ios' || subcommand === 'aar' ||
subcommand === 'ios-framework' || subcommand === 'ios-xcframework' || subcommand === 'aab') {
const createdPlatforms = getCreatedPlatformsCached();
if (!isSupportedOperatorType(createdPlatforms, subcommand)) {
return false;
}
build(subcommand, cmd);
}
});
buildSubcommand.unknownOption = () => unknownOptions();
});
buildCmd.on('command:*', unknownCommand => unknownCommands(unknownCommand));
buildCmd.unknownOption = () => unknownOptions();
commandsSort['Project'].push(program.commands[program.commands.length - 1]);
}
function parseInstall() {
const installCmd = program.command('install [fileType]', { hidden: true })
.usage('[arguments]')
.option('--target [moduleName]', 'Specifies the name of the module to install.')
.option('--path <path>', 'Specifies the path of the package to install.')
.description(`Install an ArkUI cross-platform app on an attached device.`)
.on('--help', () => {
if (!installCommands.some(sub => process.argv.includes(sub))) {
console.log(`
Available subcommands:
apk Install an Android APK on an attached device.
hap Install a HarmonyOS/OpenHarmony HAP on an attached device.
hsp Install a HarmonyOS/OpenHarmony HSP on an attached device.
ios Install an iOS APP on an attached device.`);
}
})
.action((fileType, options, cmd) => {
if (options.path && options.target) {
console.error('\x1B[31m%s\x1B[0m', 'The option "path" and "target" do not support to exist at the same time.');
return false;
}
execCmd(fileType, options, cmd, install);
});
if (process.argv[2] === 'help' && process.argv[3] === 'install') {
commandHelp(installCmd);
}
installCmd.unknownOption = () => unknownOptions();
commandsSort['Application'].push(program.commands[program.commands.length - 1]);
}
function parseUninstall() {
const uninstallCmd = program.command('uninstall [fileType]', { hidden: true })
.usage('[arguments]')
.option('--bundle <bundleName>', 'Specifies the name of the bundle to be uninstalled..')
.description(`Uninstall an ArkUI cross-platform app on an attached device.`)
.on('--help', () => {
if (!subCommands.some(sub => process.argv.includes(sub))) {
console.log(`
Available subcommands:
apk Uninstall an Android APK an attached device.
hap Uninstall a HarmonyOS/Openharmony HAP an attached device.
ios Uninstall an iOS APP an attached device.`);
}
})
.action((fileType, options, cmd) => {
if (!options.bundle) {
console.log(`Please input bundleName with --bundle.`);
return false;
}
execCmd(fileType, options.bundle, cmd, uninstall);
});
if (process.argv[2] === 'help' && process.argv[3] === 'uninstall') {
commandHelp(uninstallCmd);
}
uninstallCmd.unknownOption = () => unknownOptions();
commandsSort['Application'].push(program.commands[program.commands.length - 1]);
}
function parseRun() {
const runCmd = program.command('run [fileType]', { hidden: true })
.usage('[arguments]')
.description(`Run your ArkUI cross-platform app on an attached device.`)
.option('--target [moduleName]', 'Specifies the name of the module to install.')
.option('-r, --release', 'Run a release version of your app.')
.option('--debug', 'Run a debug version of your app.')
.option('--profile', 'Run a version of your app specialized for performance profiling.')
.on('--help', () => {
if (!subCommands.some(sub => process.argv.includes(sub))) {
console.log(`
Available subcommands:
apk Run an Android APK to your device.
hap Run a HarmonyOS/Openharmony HAP to your device.
ios Run an iOS APP to your device.`);
}
})
.action((fileType, options, cmd) => {
if ((options.release && options.debug) || (options.release && options.profile) || (options.profile && options.debug)) {
console.error('\x1B[31m%s\x1B[0m', 'Multiple run models are not allowed to exist at the same time.');
return false;
}
execCmd(fileType, options, cmd, run);
});
if (process.argv[2] === 'help' && process.argv[3] === 'run') {
commandHelp(runCmd);
}
runCmd.unknownOption = () => unknownOptions();
commandsSort['Application'].push(program.commands[program.commands.length - 1]);
}
function parseLaunch() {
const launchCmd = program.command('launch [fileType]', { hidden: true })
.usage('[arguments]')
.option('--target [moduleName]', 'Name of module to be launched.')
.description(`Launch your ArkUI cross-platform app on an attached device.`)
.on('--help', () => {
if (!subCommands.some(sub => process.argv.includes(sub))) {
console.log(`
Available subcommands:
apk Launch an Android APK to your device.
hap Launch a HarmonyOS/Openharmony HAP to your device.
ios Launch an iOS APP to your device.`);
}
})
.action((fileType, options, cmd) => {
execCmd(fileType, options, cmd, launch);
});
if (process.argv[2] === 'help' && process.argv[3] === 'launch') {
commandHelp(launchCmd);
}
launchCmd.unknownOption = () => unknownOptions();
commandsSort['Application'].push(program.commands[program.commands.length - 1]);
}
function parseLog() {
const logCmd = program.command('log [fileType]', { hidden: true })
.usage('[arguments]')
.description(`Show log output for running ArkUI cross-platform apps.`)
.on('--help', () => {
if (!subCommands.some(sub => process.argv.includes(sub))) {
console.log(`
Available subcommands:
apk Show log output for running the APK.
hap Show log output for running the HAP.
ios Show log output for running the APP.`);
}
})
.action((fileType, options, cmd) => {
execCmd(fileType, '', cmd, log);
});
if (process.argv[2] === 'help' && process.argv[3] === 'log') {
commandHelp(logCmd);
}
logCmd.unknownOption = () => unknownOptions();
commandsSort['Application'].push(program.commands[program.commands.length - 1]);
}
function parseModify() {
const ModifyCmd = program.command('modify', { hidden: true })
.usage('[arguments]')
.description(`Modify HarmonyOS project to ArkUI-X project structre.`)
.option('--project', 'modify current project all modules.')
.option('--modules', 'modify specified modules')
.action((modifyType) => {
let platforms = 'both';
if (fs.existsSync(path.join(process.cwd(), './.arkui-x/arkui-x-config.json5'))) {
const createdPlatforms = getCreatedPlatformsCached();
platforms = createdPlatforms.length === 2 ? 'both' : createdPlatforms[0];
parseModifyByPlatform(modifyType, platforms);
} else {
promptTargetPlatforms().then(targetPlatforms => {
platforms = targetPlatforms;
parseModifyByPlatform(modifyType, platforms);
});
}
});
if (process.argv[2] === 'help' && process.argv[3] === 'modify') {
commandHelp(ModifyCmd);
}
ModifyCmd.unknownOption = () => unknownOptions();
commandsSort['Project'].push(program.commands[program.commands.length - 1]);
}
function inquirerModify(answers, platforms) {
const modifyNumber = parseInt(answers.repair, 10);
inquirer.prompt([{
name: 'repair',
type: 'input',
message: `Enter the modify module name(Multiple modules can be entered and separated by ","):`,
validate(val) {
if (val === '') {
return 'Input is empty,Please enter modify module name!';
}
const modules = val.split(',');
if (modifyNumber !== modules.length) {
return 'Incorrect number of mudules to be modified';
} else {
return true;
}
},
}]).then(answersModules => {
const modules = answersModules.repair.split(',');
let modifyModulesArray = [];
for (let i = 0; i < modules.length; i++) {
modifyModulesArray.push(modules[i].trim());
}
modifyModules(modifyModulesArray, platforms);
});
}
function parseModifyByPlatform(modifyType, platforms) {
if (modifyType.project) {
modifyProject(platforms);
} else if (modifyType.modules) {
inquirer.prompt([{
name: 'repair',
type: 'input',
message: `Enter the number of modules to be modified:`,
validate(val) {
const number = parseInt(val, 10);
if (val === '' || isNaN(number) || number <= 0 || number > 10) {
return 'Please enter a number 1 - 10!';
} else {
return true;
}
},
}]).then(answers => {
inquirerModify(answers, platforms);
});
} else {
console.log('Please input modify type with --project or --modules.` ');
}
}
function parseAnalysis() {
const analysisCmd = program.command('analysis', { hidden: true })
.usage('[arguments]')
.description(`Analysis the interfaces that do not support cross-platform.`)
.option('--sdk [sdkPath]', 'Specifies the HarmonyOS sdk path of the project use.')
.option('--buildlog [buildLogPath]', 'Specifies the build log path.')
.action((options) => {
analysisProject(options.sdk, options.buildlog);
});
if (process.argv[2] === 'help' && process.argv[3] === 'analysis') {
commandHelp(analysisCmd);
}
analysisCmd.unknownOption = () => unknownOptions();
commandsSort.Project.push(program.commands[program.commands.length - 1]);
}
function parseClean() {
const cleanCmd = program.command('clean', { hidden: true })
.usage('[arguments]')
.description(`Delete the build/ directories.`)
.action(() => {
clean();
});
if (process.argv[2] === 'help' && process.argv[3] === 'clean') {
commandHelp(cleanCmd);
}
cleanCmd.unknownOption = () => unknownOptions();
commandsSort['Project'].push(program.commands[program.commands.length - 1]);
}
function parseTest() {
const testCmd = program.command('test [fileType]', { hidden: true })
.usage('[arguments]')
.option('--b [bundleName]', 'Specifies the name of the bundle to test.')
.option('--class [class]', 'Specifies the name of the class to test.')
.option('--m [testModuleName]', 'Specifies the name of the module to test.')
.option('--path [path]', 'Specifies the path of the package to install and test.')
.option('--skipInstall', 'Specifies the name of the app you want to skip installation. Works only when the app has been installed.')
.option('--target [moduleName]', 'Specifies the name of the module to install.')
.option('--socket [port]', 'Specifies the socket port, default is 8017.')
.option('--timeout [timeout]', 'Specifies the timeout time.')
.option('--unittest [testRunner]', 'Specifies the name of the test runner.')
.description(`Run ArkUI cross-platform unit tests for the current project.`)
.on('--help', () => {
if (!['apk', 'ios'].some(sub => process.argv.includes(sub))) {
console.log(`
Available subcommands:
apk Run APK unit tests for the current project.
ios Run APP unit tests for the current project.`);
}
})
.action((fileType, options, cmd) => {
if (!options.b) {
console.log("No bundle name specified. Use '--b <testBundleName>' to specify one.");
return false;
}
if (!options.m) {
console.log("test moduleName not found, please use '--m <testModuleName>'");
return false;
}
if (!options.unittest) {
console.log("test unittest not found, please use '--unittest <unittest>'");
return false;
}
if (options.path !== undefined && options.path.length == 0) {
console.log('please input the correct path of install file');
return false;
}
if (options.socket !== undefined && !isValidSocketPort(options.socket)) {
console.log("Invalid socket port. If you use '--socket', provide an integer between 1 and 65535," +
' or use the default 8017.');
return false;
}
if (fileType === 'apk' || fileType === 'ios') {
options.test = 'test';
options.debug = true;
if (!cmd.parent._optionValues.device) {
chooseDevice(fileType, options, test);
} else {
test(fileType, cmd.parent._optionValues.device, options);
}
} else {
console.log(`Please use ace test with subCommand : apk or ios.`);
}
});
if (process.argv[2] === 'help' && process.argv[3] === 'test') {
commandHelp(testCmd);
}
testCmd.unknownOption = () => unknownOptions();
commandsSort['Application'].push(program.commands[program.commands.length - 1]);
}
function execCmd(fileType, options, cmd, func) {
const fileTypeList = func.name === 'install' ? installCommands : subCommands;
if (fileType && !fileTypeList.includes(fileType)) {
console.error('\x1B[31m%s\x1B[0m', `Could not find a command named "${process.argv.slice(3)}".\n\n`);
console.error('\x1B[31m%s\x1B[0m', `Run 'ace help <command>' for available ACE Tools commands and options.`);
return false;
}
const createdPlatforms = getCreatedPlatformsCached();
if (!isSupportedOperatorType(createdPlatforms, fileType)) {
return false;
}
if (!cmd.parent._optionValues.device) {
chooseDevice(fileType, options, func);
} else {
if (!fileType) {
fileType = getDeviceType(cmd.parent._optionValues.device);
}
func(fileType, cmd.parent._optionValues.device, options);
}
}
function chooseDevice(fileType, options, func) {
const mapDevice = showValidDevice(fileType);
if (!mapDevice) {
return false;
} else if (mapDevice.size === 1) {
const inputDevice = getDeviceID(mapDevice.get('0'));
if (!fileType) {
fileType = getDeviceType(inputDevice);
}
func(fileType, inputDevice, options);
} else {
inquirer.prompt([{
name: 'ID',
type: 'input',
message: 'Please choose one (or "q" to quit):',
validate(val) {
if (val.toLowerCase() === 'q' || val.toLowerCase() === 'quit') {
return true;
}
if (mapDevice.get(val)) {
return true;
} else {
return `choose 1 ~ ${mapDevice.size}.`;
}
},
}]).then(answers => {
const id = answers.ID;
if (id === 'q' || id === 'quit') {
return false;
}
const inputDevice = getDeviceID(mapDevice.get(id));
if (!fileType) {
fileType = getDeviceType(inputDevice);
}
func(fileType, inputDevice, options);
});
}
}