import cp from 'child_process';
import fs from 'fs';
import { mkdir, readdir, unlink } from 'fs/promises';
import { createRequire } from 'module';
import path, { dirname, join, relative } from 'path';
import process from 'process';
import { fileURLToPath, pathToFileURL } from 'url';
import { promisify } from 'util';
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'copy... Remove this comment to see the full error message
import copyTemplateDirOriginal from 'copy-template-dir';
import { findUp } from 'find-up';
import fuzzy from 'fuzzy';
import inquirer from 'inquirer';
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'node... Remove this comment to see the full error message
import fetch from 'node-fetch';
import ora from 'ora';
import { fileExistsAsync } from '../../lib/fs.js';
import { getAddons, getCurrentAddon, getSiteData } from '../../utils/addons/prepare.js';
import { NETLIFYDEVERR, NETLIFYDEVLOG, NETLIFYDEVWARN, chalk, error, log } from '../../utils/command-helpers.js';
import { getDotEnvVariables, injectEnvVariables } from '../../utils/dev.js';
import execa from '../../utils/execa.js';
import { readRepoURL, validateRepoURL } from '../../utils/read-repo-url.js';
const copyTemplateDir = promisify(copyTemplateDirOriginal);
const require = createRequire(import.meta.url);
const templatesDir = path.resolve(dirname(fileURLToPath(import.meta.url)), '../../../functions-templates');
/**
 * Ensure that there's a sub-directory in `/functions-templates` named after
 * each `value` property in this list.
 */
const languages = [
    { name: 'JavaScript', value: 'javascript' },
    { name: 'TypeScript', value: 'typescript' },
    { name: 'Go', value: 'go' },
    { name: 'Rust', value: 'rust' },
];
/**
 * prompt for a name if name not supplied
 * @param {string} argumentName
 * @param {import('commander').OptionValues} options
 * @param {string} [defaultName]
 * @returns
 */
// @ts-expect-error TS(7006) FIXME: Parameter 'argumentName' implicitly has an 'any' t... Remove this comment to see the full error message
const getNameFromArgs = async function (argumentName, options, defaultName) {
    if (options.name) {
        if (argumentName) {
            throw new Error('function name specified in both flag and arg format, pick one');
        }
        return options.name;
    }
    if (argumentName) {
        return argumentName;
    }
    const { name } = await inquirer.prompt([
        {
            name: 'name',
            message: 'Name your function:',
            default: defaultName,
            type: 'input',
            validate: (val) => Boolean(val) && /^[\w.-]+$/i.test(val),
            // make sure it is not undefined and is a valid filename.
            // this has some nuance i have ignored, eg crossenv and i18n concerns
        },
    ]);
    return name;
};
// @ts-expect-error TS(7006) FIXME: Parameter 'registry' implicitly has an 'any' type.
const filterRegistry = function (registry, input) {
    // @ts-expect-error TS(7006) FIXME: Parameter 'value' implicitly has an 'any' type.
    const temp = registry.map((value) => value.name + value.description);
    // TODO: remove once https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1394 is fixed
    // eslint-disable-next-line unicorn/no-array-method-this-argument
    const filteredTemplates = fuzzy.filter(input, temp);
    const filteredTemplateNames = new Set(filteredTemplates.map((filteredTemplate) => (input ? filteredTemplate.string : filteredTemplate)));
    return (registry
        // @ts-expect-error TS(7006) FIXME: Parameter 't' implicitly has an 'any' type.
        .filter((t) => filteredTemplateNames.has(t.name + t.description))
        // @ts-expect-error TS(7006) FIXME: Parameter 't' implicitly has an 'any' type.
        .map((t) => {
        // add the score
        // @ts-expect-error TS(2339) FIXME: Property 'score' does not exist on type 'FilterRes... Remove this comment to see the full error message
        const { score } = filteredTemplates.find((filteredTemplate) => filteredTemplate.string === t.name + t.description);
        t.score = score;
        return t;
    }));
};
/**
 * @param {string} lang
 * @param {'edge' | 'serverless'} funcType
 */
// @ts-expect-error TS(7006) FIXME: Parameter 'lang' implicitly has an 'any' type.
const formatRegistryArrayForInquirer = async function (lang, funcType) {
    const folders = await readdir(path.join(templatesDir, lang), { withFileTypes: true });
    const imports = await Promise.all(folders
        .filter((folder) => Boolean(folder?.isDirectory()))
        .map(async ({ name }) => {
        try {
            const templatePath = path.join(templatesDir, lang, name, '.netlify-function-template.js');
            // @ts-expect-error TS(7036) FIXME: Dynamic import's specifier must be of type 'string... Remove this comment to see the full error message
            const template = await import(pathToFileURL(templatePath));
            return template.default;
        }
        catch {
            // noop if import fails we don't break the whole inquirer
        }
    }));
    const registry = imports
        .filter((template) => template?.functionType === funcType)
        .sort((templateA, templateB) => {
        const priorityDiff = (templateA.priority || DEFAULT_PRIORITY) - (templateB.priority || DEFAULT_PRIORITY);
        if (priorityDiff !== 0) {
            return priorityDiff;
        }
        // This branch is needed because `Array.prototype.sort` was not stable
        // until Node 11, so the original sorting order from `fs.readdirSync`
        // was not respected. We can simplify this once we drop support for
        // Node 10.
        return templateA - templateB;
    })
        .map((t) => {
        t.lang = lang;
        return {
            // confusing but this is the format inquirer wants
            name: `[${t.name}] ${t.description}`,
            value: t,
            short: `${lang}-${t.name}`,
        };
    });
    return registry;
};
/**
 * pick template from our existing templates
 * @param {import('commander').OptionValues} config
 * @param {'edge' | 'serverless'} funcType
 */
// @ts-expect-error TS(7031) FIXME: Binding element 'languageFromFlag' implicitly has ... Remove this comment to see the full error message
const pickTemplate = async function ({ language: languageFromFlag }, funcType) {
    const specialCommands = [
        new inquirer.Separator(),
        {
            name: `Clone template from GitHub URL`,
            value: 'url',
            short: 'gh-url',
        },
        {
            name: `Report issue with, or suggest a new template`,
            value: 'report',
            short: 'gh-report',
        },
        new inquirer.Separator(),
    ];
    let language = languageFromFlag;
    if (language === undefined) {
        const langs = funcType === 'edge'
            ? languages.filter((lang) => lang.value === 'javascript' || lang.value === 'typescript')
            : languages.filter(Boolean);
        const { language: languageFromPrompt } = await inquirer.prompt({
            choices: langs,
            message: 'Select the language of your function',
            name: 'language',
            type: 'list',
        });
        language = languageFromPrompt;
    }
    // @ts-expect-error TS(7034) FIXME: Variable 'templatesForLanguage' implicitly has typ... Remove this comment to see the full error message
    let templatesForLanguage;
    try {
        templatesForLanguage = await formatRegistryArrayForInquirer(language, funcType);
    }
    catch {
        throw error(`Invalid language: ${language}`);
    }
    const { chosenTemplate } = await inquirer.prompt({
        name: 'chosenTemplate',
        message: 'Pick a template',
        // @ts-expect-error TS(2769) FIXME: No overload matches this call.
        type: 'autocomplete',
        // @ts-expect-error TS(7006) FIXME: Parameter 'answersSoFar' implicitly has an 'any' t... Remove this comment to see the full error message
        source(answersSoFar, input) {
            // if Edge Functions template, don't show url option
            // @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'Separator... Remove this comment to see the full error message
            const edgeCommands = specialCommands.filter((val) => val.value !== 'url');
            const parsedSpecialCommands = funcType === 'edge' ? edgeCommands : specialCommands;
            if (!input || input === '') {
                // show separators
                // @ts-expect-error TS(7005) FIXME: Variable 'templatesForLanguage' implicitly has an ... Remove this comment to see the full error message
                return [...templatesForLanguage, ...parsedSpecialCommands];
            }
            // only show filtered results sorted by score
            // @ts-expect-error TS(7005) FIXME: Variable 'templatesForLanguage' implicitly has an ... Remove this comment to see the full error message
            const answers = [...filterRegistry(templatesForLanguage, input), ...parsedSpecialCommands].sort((answerA, answerB) => answerB.score - answerA.score);
            return answers;
        },
    });
    return chosenTemplate;
};
const DEFAULT_PRIORITY = 999;
/** @returns {Promise<'edge' | 'serverless'>} */
const selectTypeOfFunc = async () => {
    const functionTypes = [
        { name: 'Edge function (Deno)', value: 'edge' },
        { name: 'Serverless function (Node/Go/Rust)', value: 'serverless' },
    ];
    const { functionType } = await inquirer.prompt([
        {
            name: 'functionType',
            message: "Select the type of function you'd like to create",
            type: 'list',
            choices: functionTypes,
        },
    ]);
    return functionType;
};
/**
 * @param {import('../base-command.js').default} command
 */
// @ts-expect-error TS(7006) FIXME: Parameter 'command' implicitly has an 'any' type.
const ensureEdgeFuncDirExists = function (command) {
    const { config, site } = command.netlify;
    const siteId = site.id;
    if (!siteId) {
        error(`${NETLIFYDEVERR} No site id found, please run inside a site directory or \`netlify link\``);
    }
    const functionsDir = config.build?.edge_functions ?? join(command.workingDir, 'netlify/edge-functions');
    const relFunctionsDir = relative(command.workingDir, functionsDir);
    if (!fs.existsSync(functionsDir)) {
        log(`${NETLIFYDEVLOG} Edge Functions directory ${chalk.magenta.inverse(relFunctionsDir)} does not exist yet, creating it...`);
        fs.mkdirSync(functionsDir, { recursive: true });
        log(`${NETLIFYDEVLOG} Edge Functions directory ${chalk.magenta.inverse(relFunctionsDir)} created.`);
    }
    return functionsDir;
};
/**
 * Prompts the user to choose a functions directory
 * @param {import('../base-command.js').default} command
 * @returns {Promise<string>} - functions directory or throws an error
 */
// @ts-expect-error TS(7006) FIXME: Parameter 'command' implicitly has an 'any' type.
const promptFunctionsDirectory = async (command) => {
    const { api, relConfigFilePath, site } = command.netlify;
    log(`\n${NETLIFYDEVLOG} functions directory not specified in ${relConfigFilePath} or UI settings`);
    if (!site.id) {
        error(`${NETLIFYDEVERR} No site id found, please run inside a site directory or \`netlify link\``);
    }
    const { functionsDir } = await inquirer.prompt([
        {
            type: 'input',
            name: 'functionsDir',
            message: 'Enter the path, relative to your site, where your functions should live:',
            default: 'netlify/functions',
        },
    ]);
    try {
        log(`${NETLIFYDEVLOG} updating site settings with ${chalk.magenta.inverse(functionsDir)}`);
        await api.updateSite({
            siteId: site.id,
            body: {
                build_settings: {
                    functions_dir: functionsDir,
                },
            },
        });
        log(`${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(functionsDir)} updated in site settings`);
    }
    catch {
        throw error('Error updating site settings');
    }
    return functionsDir;
};
/**
 * Get functions directory (and make it if necessary)
 * @param {import('../base-command.js').default} command
 * @returns {Promise<string>} - functions directory or throws an error
 */
// @ts-expect-error TS(7006) FIXME: Parameter 'command' implicitly has an 'any' type.
const ensureFunctionDirExists = async function (command) {
    const { config } = command.netlify;
    const functionsDirHolder = config.functionsDirectory || join(command.workingDir, await promptFunctionsDirectory(command));
    const relFunctionsDirHolder = relative(command.workingDir, functionsDirHolder);
    if (!fs.existsSync(functionsDirHolder)) {
        log(`${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(relFunctionsDirHolder)} does not exist yet, creating it...`);
        await mkdir(functionsDirHolder, { recursive: true });
        log(`${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(relFunctionsDirHolder)} created`);
    }
    return functionsDirHolder;
};
/**
 * Download files from a given GitHub URL
 * @param {import('../base-command.js').default} command
 * @param {import('commander').OptionValues} options
 * @param {string} argumentName
 * @param {string} functionsDir
 */
// @ts-expect-error TS(7006) FIXME: Parameter 'command' implicitly has an 'any' type.
const downloadFromURL = async function (command, options, argumentName, functionsDir) {
    const folderContents = await readRepoURL(options.url);
    const [functionName] = options.url.split('/').slice(-1);
    const nameToUse = await getNameFromArgs(argumentName, options, functionName);
    const fnFolder = path.join(functionsDir, nameToUse);
    if (fs.existsSync(`${fnFolder}.js`) && fs.lstatSync(`${fnFolder}.js`).isFile()) {
        log(`${NETLIFYDEVWARN}: A single file version of the function ${nameToUse} already exists at ${fnFolder}.js. Terminating without further action.`);
        process.exit(1);
    }
    try {
        await mkdir(fnFolder, { recursive: true });
    }
    catch {
        // Ignore
    }
    await Promise.all(
    // @ts-expect-error TS(7031) FIXME: Binding element 'downloadUrl' implicitly has an 'a... Remove this comment to see the full error message
    folderContents.map(async ({ download_url: downloadUrl, name }) => {
        try {
            const res = await fetch(downloadUrl);
            const finalName = path.basename(name, '.js') === functionName ? `${nameToUse}.js` : name;
            const dest = fs.createWriteStream(path.join(fnFolder, finalName));
            res.body.pipe(dest);
        }
        catch (error_) {
            throw new Error(`Error while retrieving ${downloadUrl} ${error_}`);
        }
    }));
    log(`${NETLIFYDEVLOG} Installing dependencies for ${nameToUse}...`);
    cp.exec('npm i', { cwd: path.join(functionsDir, nameToUse) }, () => {
        log(`${NETLIFYDEVLOG} Installing dependencies for ${nameToUse} complete `);
    });
    // read, execute, and delete function template file if exists
    const fnTemplateFile = path.join(fnFolder, '.netlify-function-template.js');
    if (await fileExistsAsync(fnTemplateFile)) {
        const { default: { addons = [], onComplete }, } = await import(pathToFileURL(fnTemplateFile).href);
        await installAddons(command, addons, path.resolve(fnFolder));
        await handleOnComplete({ command, onComplete });
        // delete
        await unlink(fnTemplateFile);
    }
};
/**
 * Takes a list of existing packages and a list of packages required by a
 * function, and returns the packages from the latter that aren't present
 * in the former. The packages are returned as an array of strings with the
 * name and version range (e.g. '@netlify/functions@0.1.0').
 */
const getNpmInstallPackages = (existingPackages = {}, neededPackages = {}) => Object.entries(neededPackages)
    // @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
    .filter(([name]) => existingPackages[name] === undefined)
    .map(([name, version]) => `${name}@${version}`);
/**
 * When installing a function's dependencies, we first try to find a site-level
 * `package.json` file. If we do, we look for any dependencies of the function
 * that aren't already listed as dependencies of the site and install them. If
 * we don't do this check, we may be upgrading the version of a module used in
 * another part of the project, which we don't want to do.
 */
// @ts-expect-error TS(7031) FIXME: Binding element 'functionPackageJson' implicitly h... Remove this comment to see the full error message
const installDeps = async ({ functionPackageJson, functionPath, functionsDir }) => {
    const { dependencies: functionDependencies, devDependencies: functionDevDependencies } = require(functionPackageJson);
    const sitePackageJson = await findUp('package.json', { cwd: functionsDir });
    const npmInstallFlags = ['--no-audit', '--no-fund'];
    // If there is no site-level `package.json`, we fall back to the old behavior
    // of keeping that file in the function directory and running `npm install`
    // from there.
    if (!sitePackageJson) {
        await execa('npm', ['i', ...npmInstallFlags], { cwd: functionPath });
        return;
    }
    const { dependencies: siteDependencies, devDependencies: siteDevDependencies } = require(sitePackageJson);
    const dependencies = getNpmInstallPackages(siteDependencies, functionDependencies);
    const devDependencies = getNpmInstallPackages(siteDevDependencies, functionDevDependencies);
    const npmInstallPath = path.dirname(sitePackageJson);
    if (dependencies.length !== 0) {
        await execa('npm', ['i', ...dependencies, '--save', ...npmInstallFlags], { cwd: npmInstallPath });
    }
    if (devDependencies.length !== 0) {
        await execa('npm', ['i', ...devDependencies, '--save-dev', ...npmInstallFlags], { cwd: npmInstallPath });
    }
    // We installed the function's dependencies in the site-level `package.json`,
    // so there's no reason to keep the one copied over from the template.
    fs.unlinkSync(functionPackageJson);
    // Similarly, if the template has a `package-lock.json` file, we delete it.
    try {
        const functionPackageLock = path.join(functionPath, 'package-lock.json');
        fs.unlinkSync(functionPackageLock);
    }
    catch {
        // no-op
    }
};
/**
 * no --url flag specified, pick from a provided template
 * @param {import('../base-command.js').default} command
 * @param {import('commander').OptionValues} options
 * @param {string} argumentName
 * @param {string} functionsDir Absolute path of the functions directory
 * @param {'edge' | 'serverless'} funcType
 */
// @ts-expect-error TS(7006) FIXME: Parameter 'command' implicitly has an 'any' type.
// eslint-disable-next-line max-params
const scaffoldFromTemplate = async function (command, options, argumentName, functionsDir, funcType) {
    // pull the rest of the metadata from the template
    const chosenTemplate = await pickTemplate(options, funcType);
    if (chosenTemplate === 'url') {
        const { chosenUrl } = await inquirer.prompt([
            {
                name: 'chosenUrl',
                message: 'URL to clone: ',
                type: 'input',
                validate: (/** @type {string} */ val) => Boolean(validateRepoURL(val)),
                // make sure it is not undefined and is a valid filename.
                // this has some nuance i have ignored, eg crossenv and i18n concerns
            },
        ]);
        options.url = chosenUrl.trim();
        try {
            await downloadFromURL(command, options, argumentName, functionsDir);
        }
        catch (error_) {
            error(`$${NETLIFYDEVERR} Error downloading from URL: ${options.url}`);
            // @ts-expect-error TS(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message
            error(error_);
            process.exit(1);
        }
    }
    else if (chosenTemplate === 'report') {
        log(`${NETLIFYDEVLOG} Open in browser: https://github.com/netlify/cli/issues/new`);
    }
    else {
        const { addons = [], lang, name: templateName, onComplete } = chosenTemplate;
        const pathToTemplate = path.join(templatesDir, lang, templateName);
        if (!fs.existsSync(pathToTemplate)) {
            throw new Error(`There isn't a corresponding directory to the selected name. Template '${templateName}' is misconfigured`);
        }
        const name = await getNameFromArgs(argumentName, options, templateName);
        log(`${NETLIFYDEVLOG} Creating function ${chalk.cyan.inverse(name)}`);
        const functionPath = ensureFunctionPathIsOk(functionsDir, name);
        const vars = { name };
        let functionPackageJson;
        // These files will not be part of the log message because they'll likely
        // be removed before the command finishes.
        const omittedFromOutput = new Set(['.netlify-function-template.js', 'package.json', 'package-lock.json']);
        const createdFiles = await copyTemplateDir(pathToTemplate, functionPath, vars);
        // @ts-expect-error TS(7006) FIXME: Parameter 'filePath' implicitly has an 'any' type.
        createdFiles.forEach((filePath) => {
            const filename = path.basename(filePath);
            if (!omittedFromOutput.has(filename)) {
                log(`${NETLIFYDEVLOG} ${chalk.greenBright('Created')} ${filePath}`);
            }
            fs.chmodSync(path.resolve(filePath), TEMPLATE_PERMISSIONS);
            if (filePath.includes('package.json')) {
                functionPackageJson = path.resolve(filePath);
            }
        });
        // delete function template file that was copied over by copydir
        await unlink(path.join(functionPath, '.netlify-function-template.js'));
        // npm install
        if (functionPackageJson !== undefined) {
            const spinner = ora({
                text: `Installing dependencies for ${name}`,
                spinner: 'moon',
            }).start();
            await installDeps({ functionPackageJson, functionPath, functionsDir });
            spinner.succeed(`Installed dependencies for ${name}`);
        }
        if (funcType === 'edge') {
            registerEFInToml(name, command.netlify);
        }
        await installAddons(command, addons, path.resolve(functionPath));
        await handleOnComplete({ command, onComplete });
        log();
        log(chalk.greenBright(`Function created!`));
        if (lang == 'rust') {
            log(chalk.green(`Please note that Rust functions require setting the NETLIFY_EXPERIMENTAL_BUILD_RUST_SOURCE environment variable to 'true' on your site.`));
        }
    }
};
const TEMPLATE_PERMISSIONS = 0o777;
// @ts-expect-error TS(7031) FIXME: Binding element 'addonName' implicitly has an 'any... Remove this comment to see the full error message
const createFunctionAddon = async function ({ addonName, addons, api, siteData, siteId }) {
    try {
        const addon = getCurrentAddon({ addons, addonName });
        if (addon && addon.id) {
            log(`The "${addonName} add-on" already exists for ${siteData.name}`);
            return false;
        }
        await api.createServiceInstance({
            siteId,
            addon: addonName,
            body: { config: {} },
        });
        log(`Add-on "${addonName}" created for ${siteData.name}`);
        return true;
    }
    catch (error_) {
        // @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
        error(error_.message);
    }
};
/**
 *
 * @param {object} config
 * @param {import('../base-command.js').default} config.command
 * @param {(command: import('../base-command.js').default) => any} config.onComplete
 */
// @ts-expect-error TS(7031) FIXME: Binding element 'command' implicitly has an 'any' ... Remove this comment to see the full error message
const handleOnComplete = async ({ command, onComplete }) => {
    const { config } = command.netlify;
    if (onComplete) {
        const env = await getDotEnvVariables({
            devConfig: { ...config.dev },
            env: command.netlify.cachedConfig.env,
            site: command.netlify.site,
        });
        injectEnvVariables(env);
        await onComplete.call(command);
    }
};
/**
 *
 * @param {object} config
 * @param {*} config.addonCreated
 * @param {*} config.addonDidInstall
 * @param {import('../base-command.js').default} config.command
 * @param {string} config.fnPath
 */
// @ts-expect-error TS(7031) FIXME: Binding element 'addonCreated' implicitly has an '... Remove this comment to see the full error message
const handleAddonDidInstall = async ({ addonCreated, addonDidInstall, command, fnPath }) => {
    const { config } = command.netlify;
    if (!addonCreated || !addonDidInstall) {
        return;
    }
    const { confirmPostInstall } = await inquirer.prompt([
        {
            type: 'confirm',
            name: 'confirmPostInstall',
            message: `This template has an optional setup script that runs after addon install. This can be helpful for first time users to try out templates. Run the script?`,
            default: false,
        },
    ]);
    if (!confirmPostInstall) {
        return;
    }
    await injectEnvVariables({
        devConfig: { ...config.dev },
        env: command.netlify.cachedConfig.env,
        site: command.netlify.site,
    });
    addonDidInstall(fnPath);
};
/**
 *
 * @param {import('../base-command.js').default} command
 * @param {*} functionAddons
 * @param {*} fnPath
 * @returns
 */
// @ts-expect-error TS(7006) FIXME: Parameter 'command' implicitly has an 'any' type.
const installAddons = async function (command, functionAddons, fnPath) {
    if (functionAddons.length === 0) {
        return;
    }
    const { api, site } = command.netlify;
    const siteId = site.id;
    if (!siteId) {
        log('No site id found, please run inside a site directory or `netlify link`');
        return false;
    }
    log(`${NETLIFYDEVLOG} checking Netlify APIs...`);
    const [siteData, siteAddons] = await Promise.all([getSiteData({ api, siteId }), getAddons({ api, siteId })]);
    // @ts-expect-error TS(7031) FIXME: Binding element 'addonDidInstall' implicitly has a... Remove this comment to see the full error message
    const arr = functionAddons.map(async ({ addonDidInstall, addonName }) => {
        log(`${NETLIFYDEVLOG} installing addon: ${chalk.yellow.inverse(addonName)}`);
        try {
            const addonCreated = await createFunctionAddon({
                api,
                addons: siteAddons,
                siteId,
                addonName,
                siteData,
            });
            await handleAddonDidInstall({ addonCreated, addonDidInstall, command, fnPath });
        }
        catch (error_) {
            // @ts-expect-error TS(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message
            error(`${NETLIFYDEVERR} Error installing addon: `, error_);
        }
    });
    return Promise.all(arr);
};
/**
 *
 * @param {string} funcName
 * @param {import('../types.js').NetlifyOptions} options
 */
// @ts-expect-error TS(7006) FIXME: Parameter 'funcName' implicitly has an 'any' type.
const registerEFInToml = async (funcName, options) => {
    const { configFilePath, relConfigFilePath } = options;
    if (!fs.existsSync(configFilePath)) {
        log(`${NETLIFYDEVLOG} \`${relConfigFilePath}\` file does not exist yet. Creating it...`);
    }
    let { funcPath } = await inquirer.prompt([
        {
            type: 'input',
            name: 'funcPath',
            message: `What route do you want your edge function to be invoked on?`,
            default: '/test',
            validate: (val) => Boolean(val),
            // Make sure route isn't undefined and is valid
            // Todo: add more validation?
        },
    ]);
    // Make sure path begins with a '/'
    if (funcPath[0] !== '/') {
        funcPath = `/${funcPath}`;
    }
    const functionRegister = `\n\n[[edge_functions]]\nfunction = "${funcName}"\npath = "${funcPath}"`;
    try {
        fs.promises.appendFile(configFilePath, functionRegister);
        log(`${NETLIFYDEVLOG} Function '${funcName}' registered for route \`${funcPath}\`. To change, edit your \`${relConfigFilePath}\` file.`);
    }
    catch {
        error(`${NETLIFYDEVERR} Unable to register function. Please check your \`${relConfigFilePath}\` file.`);
    }
};
/**
 * we used to allow for a --dir command,
 * but have retired that to force every scaffolded function to be a directory
 * @param {string} functionsDir
 * @param {string} name
 * @returns
 */
// @ts-expect-error TS(7006) FIXME: Parameter 'functionsDir' implicitly has an 'any' t... Remove this comment to see the full error message
const ensureFunctionPathIsOk = function (functionsDir, name) {
    const functionPath = path.join(functionsDir, name);
    if (fs.existsSync(functionPath)) {
        log(`${NETLIFYDEVLOG} Function ${functionPath} already exists, cancelling...`);
        process.exit(1);
    }
    return functionPath;
};
export const functionsCreate = async (name, options, command) => {
    const functionType = await selectTypeOfFunc();
    const functionsDir = functionType === 'edge' ? await ensureEdgeFuncDirExists(command) : await ensureFunctionDirExists(command);
    /* either download from URL or scaffold from template */
    const mainFunc = options.url ? downloadFromURL : scaffoldFromTemplate;
    await mainFunc(command, options, name, functionsDir, functionType);
};