"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var deploy_command_exports = {};
__export(deploy_command_exports, {
  DeployCommand: () => DeployCommand
});
module.exports = __toCommonJS(deploy_command_exports);
var sdk = __toESM(require("@botpress/sdk"));
var import_chalk = __toESM(require("chalk"));
var fs = __toESM(require("fs"));
var import_semver = __toESM(require("semver"));
var apiUtils = __toESM(require("../api"));
var errors = __toESM(require("../errors"));
var tables = __toESM(require("../tables"));
var utils = __toESM(require("../utils"));
var import_build_command = require("./build-command");
var import_project_command = require("./project-command");
class DeployCommand extends import_project_command.ProjectCommand {
  async run() {
    const api = await this.ensureLoginAndCreateClient(this.argv);
    if (!this.argv.noBuild) {
      await this._runBuild();
    }
    const { projectType, resolveProjectDefinition } = this.readProjectDefinitionFromFS();
    if (projectType === "integration") {
      const projectDef = await resolveProjectDefinition();
      return this._deployIntegration(api, projectDef.definition);
    }
    if (projectType === "interface") {
      const projectDef = await resolveProjectDefinition();
      return this._deployInterface(api, projectDef.definition);
    }
    if (projectType === "plugin") {
      const projectDef = await resolveProjectDefinition();
      return this._deployPlugin(api, projectDef.definition);
    }
    if (projectType === "bot") {
      const projectDef = await resolveProjectDefinition();
      return this._deployBot(api, projectDef.definition, this.argv.botId, this.argv.createNewBot);
    }
    throw new errors.UnsupportedProjectType();
  }
  async _runBuild() {
    return new import_build_command.BuildCommand(this.api, this.prompt, this.logger, this.argv).setProjectContext(this.projectContext).run();
  }
  get _visibility() {
    if (this.argv.public && this.argv.visibility === "private") {
      this.logger.warn('The --public flag is deprecated. Please use "--visibility public" instead.');
      return "public";
    }
    if (this.argv.public && this.argv.visibility !== "private") {
      this.logger.warn("The --public flag and --visibility option are both present. Ignoring the --public flag...");
    }
    return this.argv.visibility;
  }
  async _deployIntegration(api, integrationDef) {
    const res = await this._manageWorkspaceHandle(api, integrationDef);
    if (!res) return;
    const { integration: updatedIntegrationDef, workspaceId } = res;
    integrationDef = updatedIntegrationDef;
    if (workspaceId) {
      api = api.switchWorkspace(workspaceId);
    }
    const { name, version } = integrationDef;
    if (integrationDef.icon && !integrationDef.icon.toLowerCase().endsWith(".svg")) {
      throw new errors.BotpressCLIError("Icon must be an SVG file");
    }
    if (integrationDef.readme && !integrationDef.readme.toLowerCase().endsWith(".md")) {
      throw new errors.BotpressCLIError("Readme must be a Markdown file");
    }
    const integration = await api.findPublicOrPrivateIntegration({ type: "name", name, version });
    if (integration && integration.workspaceId !== api.workspaceId) {
      throw new errors.BotpressCLIError(
        `Public integration ${name} v${version} is already deployed in another workspace.`
      );
    }
    if (integration && integration.visibility !== "private" && !api.isBotpressWorkspace) {
      throw new errors.BotpressCLIError(
        `Integration ${name} v${version} is already deployed publicly and cannot be updated. Please bump the version.`
      );
    }
    let message;
    if (integration) {
      this.logger.warn("Integration already exists. If you decide to deploy, it will override the existing one.");
      message = `Are you sure you want to override integration ${name} v${version}?`;
    } else {
      message = `Are you sure you want to deploy integration ${name} v${version}?`;
    }
    const confirm = await this.prompt.confirm(message);
    if (!confirm) {
      this.logger.log("Aborted");
      return;
    }
    this.logger.debug("Preparing integration request body...");
    const createBody = {
      ...await this.prepareCreateIntegrationBody(integrationDef),
      ...await this.prepareIntegrationDependencies(integrationDef, api),
      visibility: this._visibility
    };
    const startedMessage = `Deploying integration ${import_chalk.default.bold(name)} v${version}...`;
    const successMessage = "Integration deployed";
    if (integration) {
      const updateBody = apiUtils.prepareUpdateIntegrationBody(
        {
          id: integration.id,
          ...createBody
        },
        integration
      );
      const { secrets: knownSecrets } = integration;
      updateBody.secrets = await this.promptSecrets(integrationDef, this.argv, { knownSecrets });
      this._detectDeprecatedFeatures(integrationDef, { allowDeprecated: true });
      const line = this.logger.line();
      line.started(startedMessage);
      if (this.argv.dryRun) {
        this.logger.log("Dry-run mode is active. Simulating integration update...");
        await api.client.validateIntegrationUpdate(updateBody).catch((thrown) => {
          throw errors.BotpressCLIError.wrap(thrown, `Could not update integration "${name}"`);
        });
      } else {
        await api.client.updateIntegration(updateBody).catch((thrown) => {
          throw errors.BotpressCLIError.wrap(thrown, `Could not update integration "${name}"`);
        });
      }
      line.success(successMessage);
    } else {
      this.logger.debug(`looking for previous version of integration "${name}"`);
      const previousVersion = await api.findPreviousIntegrationVersion({ type: "name", name, version });
      if (previousVersion) {
        this.logger.debug(`previous version found: ${previousVersion.version}`);
      } else {
        this.logger.debug("no previous version found");
      }
      const knownSecrets = previousVersion?.secrets;
      createBody.secrets = await this.promptSecrets(integrationDef, this.argv, { knownSecrets });
      this._detectDeprecatedFeatures(integrationDef, {
        allowDeprecated: this._allowDeprecatedFeatures(integrationDef, previousVersion)
      });
      const line = this.logger.line();
      line.started(startedMessage);
      if (this.argv.dryRun) {
        this.logger.log("Dry-run mode is active. Simulating integration creation...");
        await api.client.validateIntegrationCreation(createBody).catch((thrown) => {
          throw errors.BotpressCLIError.wrap(thrown, `Could not create integration "${name}"`);
        });
      } else {
        await api.client.createIntegration(createBody).catch((thrown) => {
          throw errors.BotpressCLIError.wrap(thrown, `Could not create integration "${name}"`);
        });
      }
      line.success(successMessage);
    }
  }
  async _deployInterface(api, interfaceDeclaration) {
    if (this._visibility === "unlisted") {
      throw new errors.BotpressCLIError(
        'Unlisted visibility is not supported for interfaces. Please use "public" or "private".'
      );
    }
    if (interfaceDeclaration.icon && !interfaceDeclaration.icon.toLowerCase().endsWith(".svg")) {
      throw new errors.BotpressCLIError("Icon must be an SVG file");
    }
    if (interfaceDeclaration.readme && !interfaceDeclaration.readme.toLowerCase().endsWith(".md")) {
      throw new errors.BotpressCLIError("Readme must be a Markdown file");
    }
    const { name, version } = interfaceDeclaration;
    const intrface = await api.findPublicOrPrivateInterface({ type: "name", name, version });
    let message;
    if (intrface) {
      this.logger.warn("Interface already exists. If you decide to deploy, it will override the existing one.");
      message = `Are you sure you want to override interface ${name} v${version}?`;
    } else {
      message = `Are you sure you want to deploy interface ${name} v${version}?`;
    }
    const confirm = await this.prompt.confirm(message);
    if (!confirm) {
      this.logger.log("Aborted");
      return;
    }
    const icon = await this.readProjectFile(interfaceDeclaration.icon, "base64");
    const readme = await this.readProjectFile(interfaceDeclaration.readme, "base64");
    if (this._visibility !== "public") {
      this.logger.warn(
        'You are currently publishing a private interface, which cannot be used by integrations and plugins. To fix this, change the visibility to "public"'
      );
    }
    const createBody = {
      ...await apiUtils.prepareCreateInterfaceBody(interfaceDeclaration),
      public: this._visibility === "public",
      icon,
      readme
    };
    const startedMessage = `Deploying interface ${import_chalk.default.bold(name)} v${version}...`;
    const successMessage = "Interface deployed";
    if (intrface) {
      const updateBody = apiUtils.prepareUpdateInterfaceBody(
        {
          id: intrface.id,
          ...createBody
        },
        intrface
      );
      const line = this.logger.line();
      line.started(startedMessage);
      if (this.argv.dryRun) {
        this.logger.warn("Dry-run mode is not supported for interface updates. Skipping deployment...");
      } else {
        await api.client.updateInterface(updateBody).catch((thrown) => {
          throw errors.BotpressCLIError.wrap(thrown, `Could not update interface "${name}"`);
        });
      }
      line.success(successMessage);
    } else {
      const line = this.logger.line();
      line.started(startedMessage);
      if (this.argv.dryRun) {
        this.logger.warn("Dry-run mode is not supported for interface creation. Skipping deployment...");
      } else {
        await api.client.createInterface(createBody).catch((thrown) => {
          throw errors.BotpressCLIError.wrap(thrown, `Could not create interface "${name}"`);
        });
      }
      line.success(successMessage);
    }
  }
  async _deployPlugin(api, pluginDef) {
    if (this._visibility === "unlisted") {
      throw new errors.BotpressCLIError(
        'Unlisted visibility is not supported for plugins. Please use "public" or "private".'
      );
    }
    const codeCJS = await fs.promises.readFile(this.projectPaths.abs.outFileCJS, "utf-8");
    const codeESM = await fs.promises.readFile(this.projectPaths.abs.outFileESM, "utf-8");
    const { name, version } = pluginDef;
    if (pluginDef.icon && !pluginDef.icon.toLowerCase().endsWith(".svg")) {
      throw new errors.BotpressCLIError("Icon must be an SVG file");
    }
    if (pluginDef.readme && !pluginDef.readme.toLowerCase().endsWith(".md")) {
      throw new errors.BotpressCLIError("Readme must be a Markdown file");
    }
    const plugin = await api.findPublicOrPrivatePlugin({ type: "name", name, version });
    let message;
    if (plugin) {
      this.logger.warn("Plugin already exists. If you decide to deploy, it will override the existing one.");
      message = `Are you sure you want to override plugin ${name} v${version}?`;
    } else {
      message = `Are you sure you want to deploy plugin ${name} v${version}?`;
    }
    const confirm = await this.prompt.confirm(message);
    if (!confirm) {
      this.logger.log("Aborted");
      return;
    }
    this.logger.debug("Preparing plugin request body...");
    const icon = await this.readProjectFile(pluginDef.icon, "base64");
    const readme = await this.readProjectFile(pluginDef.readme, "base64");
    const createBody = {
      ...await apiUtils.prepareCreatePluginBody(pluginDef),
      ...await this.preparePluginDependencies(pluginDef, api),
      public: this._visibility === "public",
      icon,
      readme,
      code: {
        node: codeCJS,
        browser: codeESM
      }
    };
    const startedMessage = `Deploying plugin ${import_chalk.default.bold(name)} v${version}...`;
    const successMessage = "Plugin deployed";
    if (plugin) {
      const updateBody = apiUtils.prepareUpdatePluginBody(
        {
          id: plugin.id,
          ...createBody
        },
        plugin
      );
      const line = this.logger.line();
      line.started(startedMessage);
      if (this.argv.dryRun) {
        this.logger.warn("Dry-run mode is not supported for plugin updates. Skipping deployment...");
      } else {
        await api.client.updatePlugin(updateBody).catch((thrown) => {
          throw errors.BotpressCLIError.wrap(thrown, `Could not update plugin "${name}"`);
        });
      }
      line.success(successMessage);
    } else {
      const line = this.logger.line();
      line.started(startedMessage);
      if (this.argv.dryRun) {
        this.logger.warn("Dry-run mode is not supported for plugin creation. Skipping deployment...");
      } else {
        await api.client.createPlugin(createBody).catch((thrown) => {
          throw errors.BotpressCLIError.wrap(thrown, `Could not create plugin "${name}"`);
        });
      }
      line.success(successMessage);
    }
  }
  _allowDeprecatedFeatures(integrationDef, previousVersion) {
    if (this.argv.allowDeprecated) {
      return true;
    }
    if (!previousVersion) {
      return false;
    }
    const versionDiff = import_semver.default.diff(integrationDef.version, previousVersion.version);
    if (!versionDiff) {
      return false;
    }
    return utils.semver.releases.lt(versionDiff, "major");
  }
  _detectDeprecatedFeatures(integrationDef, opts = {}) {
    const deprecatedFields = [];
    const { user, channels } = integrationDef;
    if (user?.creation?.enabled) {
      deprecatedFields.push("user.creation");
    }
    for (const [channelName, channel] of Object.entries(channels ?? {})) {
      if (channel?.conversation?.creation?.enabled) {
        deprecatedFields.push(`channels.${channelName}.creation`);
      }
    }
    if (!deprecatedFields.length) {
      return;
    }
    const errorMessage = `The following fields of the integration's definition are deprecated: ${deprecatedFields.join(
      ", "
    )}`;
    if (opts.allowDeprecated) {
      this.logger.warn(errorMessage);
    } else {
      throw new errors.BotpressCLIError(errorMessage);
    }
  }
  async _deployBot(api, botDefinition, argvBotId, argvCreateNew) {
    if (this.argv.dryRun) {
      this.logger.warn("Dry-run mode is not supported for bot deployments. Skipping deployment...");
      return;
    }
    const outfile = this.projectPaths.abs.outFileCJS;
    const code = await fs.promises.readFile(outfile, "utf-8");
    let bot;
    if (argvBotId && argvCreateNew) {
      throw new errors.BotpressCLIError("Cannot specify both --botId and --createNew");
    } else if (argvCreateNew) {
      const confirm = await this.prompt.confirm("Are you sure you want to create a new bot ?");
      if (!confirm) {
        this.logger.log("Aborted");
        return;
      }
      bot = await this._createNewBot(api);
    } else {
      bot = await this._getExistingBot(api, argvBotId);
      const confirm = await this.prompt.confirm(`Are you sure you want to deploy the bot "${bot.name}"?`);
      if (!confirm) {
        this.logger.log("Aborted");
        return;
      }
    }
    const line = this.logger.line();
    line.started(`Deploying bot ${import_chalk.default.bold(bot.name)}...`);
    const updateBotBody = apiUtils.prepareUpdateBotBody(
      {
        ...await apiUtils.prepareCreateBotBody(botDefinition),
        ...await this.prepareBotDependencies(botDefinition, api),
        id: bot.id,
        code
      },
      bot
    );
    const { bot: updatedBot } = await api.client.updateBot(updateBotBody).catch((thrown) => {
      throw errors.BotpressCLIError.wrap(thrown, `Could not update bot "${bot.name}"`);
    });
    this.validateIntegrationRegistration(
      updatedBot,
      (failedIntegrations) => this.logger.warn(
        `Some integrations failed to register:
${Object.entries(failedIntegrations).map(([key, int]) => `\u2022 ${key}: ${int.statusReason}`).join("\n")}`
      )
    );
    const tablesPublisher = new tables.TablesPublisher({ api, logger: this.logger, prompt: this.prompt });
    await tablesPublisher.deployTables({ botId: updatedBot.id, botDefinition });
    line.success("Bot deployed");
    await this.displayIntegrationUrls({ api, bot: updatedBot });
  }
  async _createNewBot(api) {
    const line = this.logger.line();
    const { bot: createdBot } = await api.client.createBot({}).catch((thrown) => {
      throw errors.BotpressCLIError.wrap(thrown, "Could not create bot");
    });
    line.success(`Bot created with ID "${createdBot.id}" and name "${createdBot.name}"`);
    await this.projectCache.set("botId", createdBot.id);
    return createdBot;
  }
  async _getExistingBot(api, botId) {
    const promptedBotId = await this.projectCache.sync("botId", botId, async (defaultId) => {
      const userBots = await api.listAllPages(api.client.listBots, (r) => r.bots).catch((thrown) => {
        throw errors.BotpressCLIError.wrap(thrown, "Could not fetch existing bots");
      });
      if (!userBots.length) {
        throw new errors.NoBotsFoundError();
      }
      const initial = userBots.find((bot) => bot.id === defaultId);
      const prompted = await this.prompt.select("Which bot do you want to deploy?", {
        initial: initial && { title: initial.name, value: initial.id },
        choices: userBots.map((bot) => ({ title: bot.name, value: bot.id }))
      });
      if (!prompted) {
        throw new errors.ParamRequiredError("Bot Id");
      }
      return prompted;
    });
    const { bot: fetchedBot } = await api.client.getBot({ id: promptedBotId }).catch((thrown) => {
      throw errors.BotpressCLIError.wrap(thrown, "Could not get bot info");
    });
    return fetchedBot;
  }
  async _manageWorkspaceHandle(api, integration) {
    const { name: localName, workspaceHandle: localHandle } = this._parseIntegrationName(integration.name);
    if (!localHandle && api.isBotpressWorkspace) {
      this.logger.debug("Botpress workspace detected; workspace handle omitted");
      return { integration };
    }
    const { handle: remoteHandle, name: workspaceName } = await api.getWorkspace().catch((thrown) => {
      throw errors.BotpressCLIError.wrap(thrown, "Could not fetch workspace");
    });
    if (localHandle && remoteHandle) {
      let workspaceId = void 0;
      if (localHandle !== remoteHandle) {
        const remoteWorkspace = await api.findWorkspaceByHandle(localHandle).catch((thrown) => {
          throw errors.BotpressCLIError.wrap(thrown, "Could not list workspaces");
        });
        if (!remoteWorkspace) {
          throw new errors.BotpressCLIError(
            `The integration handle "${localHandle}" is not associated with any of your workspaces.`
          );
        }
        this.logger.warn(
          `Your are logged in to workspace "${workspaceName}" but integration handle "${localHandle}" belongs to "${remoteWorkspace.name}".`
        );
        const confirmUseAlternateWorkspace = await this.prompt.confirm(
          "Do you want to deploy integration on this workspace instead?"
        );
        if (!confirmUseAlternateWorkspace) {
          throw new errors.BotpressCLIError(
            `Cannot deploy integration with handle "${localHandle}" on workspace "${workspaceName}"`
          );
        }
        workspaceId = remoteWorkspace.id;
      }
      return { integration, workspaceId };
    }
    const workspaceHandleIsMandatoryMsg = "Cannot deploy integration without workspace handle";
    if (!localHandle && remoteHandle) {
      const confirmAddHandle = await this.prompt.confirm(
        `Your current workspace handle is "${remoteHandle}". Do you want to use the name "${remoteHandle}/${localName}"?`
      );
      if (!confirmAddHandle) {
        this.logger.log("Aborted");
        return;
      }
      const newName2 = `${remoteHandle}/${localName}`;
      return { integration: new sdk.IntegrationDefinition({ ...integration, name: newName2 }) };
    }
    if (localHandle && !remoteHandle) {
      const { available } = await api.client.checkHandleAvailability({ handle: localHandle }).catch((thrown) => {
        throw errors.BotpressCLIError.wrap(thrown, "Could not check handle availability");
      });
      if (!available) {
        throw new errors.BotpressCLIError(`Handle "${localHandle}" is not yours and is not available`);
      }
      const confirmClaimHandle = await this.prompt.confirm(
        `Handle "${localHandle}" is available. Do you want to claim it for your workspace ${workspaceName}?`
      );
      if (!confirmClaimHandle) {
        throw new errors.BotpressCLIError(workspaceHandleIsMandatoryMsg);
      }
      await api.updateWorkspace({ handle: localHandle }).catch((thrown) => {
        throw errors.BotpressCLIError.wrap(thrown, `Could not claim handle "${localHandle}"`);
      });
      this.logger.success(`Handle "${localHandle}" is now yours!`);
      return { integration };
    }
    this.logger.warn("It seems you don't have a workspace handle yet.");
    let claimedHandle = void 0;
    do {
      const prompted = await this.prompt.text("Please enter a workspace handle");
      if (!prompted) {
        throw new errors.BotpressCLIError(workspaceHandleIsMandatoryMsg);
      }
      const { available, suggestions } = await api.client.checkHandleAvailability({ handle: prompted });
      if (!available) {
        this.logger.warn(`Handle "${prompted}" is not available. Suggestions: ${suggestions.join(", ")}`);
        continue;
      }
      claimedHandle = prompted;
      await api.updateWorkspace({ handle: claimedHandle }).catch((thrown) => {
        throw errors.BotpressCLIError.wrap(thrown, `Could not claim handle "${claimedHandle}"`);
      });
    } while (!claimedHandle);
    this.logger.success(`Handle "${claimedHandle}" is yours!`);
    const newName = `${claimedHandle}/${localName}`;
    return { integration: new sdk.IntegrationDefinition({ ...integration, name: newName }) };
  }
  _parseIntegrationName = (integrationName) => {
    const parts = integrationName.split("/");
    if (parts.length > 2) {
      throw new errors.BotpressCLIError(
        `Invalid integration name "${integrationName}": a single forward slash is allowed`
      );
    }
    if (parts.length === 2) {
      const [workspaceHandle, name2] = parts;
      return { name: name2, workspaceHandle };
    }
    const [name] = parts;
    return { name };
  };
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  DeployCommand
});
//# sourceMappingURL=deploy-command.js.map
