"use strict";
/*
 * @adonisjs/core
 *
 * (c) Harminder Virk <virk@adonisjs.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.App = void 0;
const ace_1 = require("@adonisjs/ace");
const cliui_1 = require("@poppinss/cliui");
const helpers_1 = require("@poppinss/utils/build/helpers");
const Kernel_1 = require("../../Kernel");
const utils_1 = require("../../../utils");
const GenerateManifest_1 = require("../GenerateManifest");
/**
 * A local list of assembler commands. We need this, so that when assembler
 * is not installed (probably in production) and someone is trying to
 * build the project by running `serve` or `build`, we should give
 * them a better descriptive error.
 *
 * Also, do note that at times this list will be stale, but we get it back
 * in sync over time.
 */
const ASSEMBLER_COMMANDS = [
    'build',
    'serve',
    'invoke',
    'make:command',
    'make:controller',
    'make:exception',
    'make:listener',
    'make:middleware',
    'make:prldfile',
    'make:provider',
    'make:validator',
    'make:view',
];
/**
 * Exposes the API to execute app commands registered under
 * the manifest file.
 */
class App {
    /**
     * Returns a boolean if mentioned command is an assembler
     * command
     */
    get isAssemblerCommand() {
        return ASSEMBLER_COMMANDS.includes(this.commandName);
    }
    /**
     * Source root always points to the compiled source
     * code.
     */
    constructor(appRoot) {
        this.appRoot = appRoot;
        /**
         * Reference to the app kernel
         */
        this.kernel = new Kernel_1.AppKernel(this.appRoot, 'console');
        /**
         * Reference to the ace kernel
         */
        this.ace = new ace_1.Kernel(this.kernel.application);
    }
    /**
     * Print commands help
     */
    printHelp(value, command) {
        if (!value) {
            return;
        }
        this.ace.printHelp(command, [GenerateManifest_1.GenerateManifest.getManifestJSON()]);
        process.exit(0);
    }
    /**
     * Print framework version
     */
    printVersion(value) {
        if (!value) {
            return;
        }
        const appVersion = this.kernel.application.version;
        const adonisVersion = this.kernel.application.adonisVersion;
        let assemblerVersion = 'Not Installed';
        try {
            assemblerVersion = require((0, helpers_1.resolveFrom)(this.appRoot, '@adonisjs/assembler/package.json')).version;
        }
        catch (error) { }
        (0, cliui_1.sticker)()
            .heading('node ace --version')
            .add(`App version: ${cliui_1.logger.colors.cyan(appVersion ? appVersion.version : 'NA')}`)
            .add(`Framework version: ${cliui_1.logger.colors.cyan(adonisVersion ? adonisVersion.version : 'NA')}`)
            .add(`Assembler version: ${cliui_1.logger.colors.cyan(assemblerVersion)}`)
            .render();
        process.exit(0);
    }
    /**
     * Invoked before command source will be read from the
     * disk
     */
    async onFind(command) {
        if (!command) {
            return;
        }
        /**
         * Register ts hook when
         *
         * - Haven't registered it already
         * - Is a typescript project
         * - Is not an assembler command
         */
        if (!this.isAssemblerCommand) {
            this.kernel.registerTsCompilerHook();
        }
        /**
         * Only main command can load the application or switch
         * the environment.
         *
         * If a sub-command needs application, then the main command
         * should set "loadApp" to true as well.
         */
        if (command.commandName === this.commandName || command.aliases.includes(this.commandName)) {
            /**
             * Switch environment before wiring the app
             */
            if (command.settings.environment) {
                this.kernel.application.switchEnvironment(command.settings.environment);
            }
            if (command.settings.loadApp) {
                /**
                 * Set ace instance within the container, so that the underlying
                 * commands or the app can access it from the container
                 */
                this.kernel.application.container.singleton('Adonis/Core/Ace', () => this.ace);
                await this.kernel.boot();
            }
        }
    }
    /**
     * Invoked before command is about to run.
     */
    async onRun() {
        if (this.kernel.hasBooted) {
            await this.kernel.start();
        }
    }
    /**
     * Hooks into ace lifecycle events to conditionally
     * load the app.
     */
    registerAceHooks() {
        this.ace.before('find', async (command) => this.onFind(command));
        this.ace.before('run', async () => this.onRun());
    }
    /**
     * Adding flags
     */
    registerAceFlags() {
        /**
         * Showing help including core commands
         */
        this.ace.flag('help', async (value, _, command) => this.printHelp(value, command), {
            alias: 'h',
        });
        /**
         * Showing app and AdonisJs version
         */
        this.ace.flag('version', async (value) => this.printVersion(value), { alias: 'v' });
    }
    /**
     * Load commands using manifest loader
     */
    async loadCommands() {
        await (0, utils_1.loadAceCommands)(this.kernel.application, this.ace);
    }
    /**
     * Handle application command
     */
    async handle(argv) {
        try {
            /**
             * Manifest files to load
             */
            await this.loadCommands();
            /**
             * Define ace hooks to wire the application (if required)
             */
            this.registerAceHooks();
            /**
             * Define global flags
             */
            this.registerAceFlags();
            /**
             * Print help when no arguments have been passed
             */
            if (!argv.length) {
                this.printHelp(true);
                return;
            }
            /**
             * Hold reference to the command name. We will use this to decide whether
             * or not to exit the process forcefully after the command has been
             * executed
             */
            this.commandName = argv[0];
            /**
             * Listen for the exit signal on ace kernel
             */
            this.ace.onExit(async () => {
                if (this.kernel.hasBooted) {
                    await this.kernel.close();
                }
                if (!this.ace.error) {
                    process.exit(this.ace.exitCode);
                }
                return this.kernel
                    .handleError(this.ace.error)
                    .finally(() => process.exit(this.ace.exitCode));
            });
            /**
             * Handle command
             */
            await this.ace.handle(argv);
        }
        catch (error) {
            if (!error) {
                process.exit(1);
            }
            this.kernel.handleError(error).finally(() => process.exit(1));
        }
    }
}
exports.App = App;
