public function actionApp($app, $repo = null)
    {
        // root of the dev repo
        $base = \dirname(\dirname(__DIR__));
        $appDir = "$base/apps/$app";

        if (!file_exists($appDir)) {
            if (empty($repo)) {
                if (isset($this->apps[$app])) {
                    $repo = $this->apps[$app];
                    if ($this->useHttp) {
                        $repo = str_replace('git@github.com:', 'https://github.com/', $repo);
                    }
                } else {
                    $this->stderr("Repo argument is required for app '$app'.\n", Console::FG_RED);
                    return 1;
                }
            }

            $this->stdout("cloning application repo '$app' from '$repo'...\n", Console::BOLD);
            passthru('git clone ' . escapeshellarg($repo) . ' ' . $appDir);
            $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
        }

        // cleanup
        $this->stdout("cleaning up application '$app' vendor directory...\n", Console::BOLD);
        $this->cleanupVendorDir($appDir);
        $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);

        // composer update
        $this->stdout("updating composer for app '$app'...\n", Console::BOLD);
        chdir($appDir);
        $command = 'composer update --prefer-dist';
        if ($this->composerNoProgress) {
            $command .= ' --no-progress';
        }
        passthru($command);
        $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);

        // link directories
        $this->stdout("linking framework and extensions to '$app' app vendor dir...\n", Console::BOLD);
        $this->linkFrameworkAndExtensions($appDir, $base);
        $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);

        return 0;
    }