"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 project_command_exports = {};
__export(project_command_exports, {
  ProjectCommand: () => ProjectCommand,
  ProjectDefinitionContext: () => ProjectDefinitionContext
});
module.exports = __toCommonJS(project_command_exports);
var import_chalk = __toESM(require("chalk"));
var import_fs = __toESM(require("fs"));
var import_lodash = __toESM(require("lodash"));
var import_semver = __toESM(require("semver"));
var apiUtils = __toESM(require("../api"));
var codegen = __toESM(require("../code-generation"));
var consts = __toESM(require("../consts"));
var errors = __toESM(require("../errors"));
var import_sdk = require("../sdk");
var utils = __toESM(require("../utils"));
var import_global_command = require("./global-command");
class ProjectPaths extends utils.path.PathStore {
  constructor(argv) {
    const absWorkDir = utils.path.absoluteFrom(utils.path.cwd(), argv.workDir);
    super({
      workDir: absWorkDir,
      ...import_lodash.default.mapValues(consts.fromWorkDir, (p) => utils.path.absoluteFrom(absWorkDir, p))
    });
  }
}
class ProjectDefinitionContext {
  _codeCache = /* @__PURE__ */ new Map();
  _buildContext = new utils.esbuild.BuildEntrypointContext();
  getOrResolveDefinition(code) {
    const definition = this._codeCache.get(code);
    if (definition) {
      return definition;
    }
    const result = utils.require.requireJsCode(code);
    this._codeCache.set(code, result.default);
    return result.default;
  }
  rebuildEntrypoint(...args) {
    return this._buildContext.rebuild(...args);
  }
}
class ProjectCommand extends import_global_command.GlobalCommand {
  projectContext = new ProjectDefinitionContext();
  setProjectContext(projectContext) {
    this.projectContext = projectContext;
    return this;
  }
  async bootstrap() {
    await super.bootstrap();
    await this._notifyUpdateSdk();
  }
  get projectPaths() {
    return new ProjectPaths(this.argv);
  }
  get projectCache() {
    return new utils.cache.FSKeyValueCache(this.projectPaths.abs.projectCacheFile);
  }
  _readProjectType(projectPaths) {
    const abs = projectPaths.abs;
    if (import_fs.default.existsSync(abs.integrationDefinition)) {
      return "integration";
    }
    if (import_fs.default.existsSync(abs.interfaceDefinition)) {
      return "interface";
    }
    if (import_fs.default.existsSync(abs.botDefinition)) {
      return "bot";
    }
    if (import_fs.default.existsSync(abs.pluginDefinition)) {
      return "plugin";
    }
    throw new errors.UnsupportedProjectType();
  }
  readProjectDefinitionFromFS() {
    try {
      const type = this._readProjectType(this.projectPaths);
      if (type === "integration") {
        return {
          projectType: "integration",
          resolveProjectDefinition: async () => ({
            type: "integration",
            ...await this._readIntegrationDefinitionFromFS(this.projectPaths)
          })
        };
      }
      if (type === "plugin") {
        return {
          projectType: "plugin",
          resolveProjectDefinition: async () => ({
            type: "plugin",
            ...await this._readPluginDefinitionFromFS(this.projectPaths)
          })
        };
      }
      if (type === "interface") {
        return {
          projectType: "interface",
          resolveProjectDefinition: async () => ({
            type: "interface",
            ...await this._readInterfaceDefinitionFromFS(this.projectPaths)
          })
        };
      }
      if (type === "bot") {
        return {
          projectType: "bot",
          resolveProjectDefinition: async () => ({
            type: "bot",
            ...await this._readBotDefinitionFromFS(this.projectPaths)
          })
        };
      }
    } catch (thrown) {
      throw errors.BotpressCLIError.wrap(thrown, "Error while reading project definition");
    }
    throw new errors.ProjectDefinitionNotFoundError(this.projectPaths.abs.workDir);
  }
  async _readIntegrationDefinitionFromFS(projectPaths) {
    const abs = projectPaths.abs;
    const rel = projectPaths.rel("workDir");
    if (!import_fs.default.existsSync(abs.integrationDefinition)) {
      throw new errors.BotpressCLIError("Could not read integration definition");
    }
    const bpLintDisabled = await this._isBpLintDisabled(abs.integrationDefinition);
    const { outputFiles } = await this.projectContext.rebuildEntrypoint({
      absWorkingDir: abs.workDir,
      entrypoint: rel.integrationDefinition
    });
    const artifact = outputFiles?.[0];
    if (!artifact) {
      throw new errors.BotpressCLIError("Could not read integration definition");
    }
    const definition = this.projectContext.getOrResolveDefinition(artifact.text);
    (0, import_sdk.validateIntegrationDefinition)(definition);
    return { definition, bpLintDisabled };
  }
  async _readInterfaceDefinitionFromFS(projectPaths) {
    const abs = projectPaths.abs;
    const rel = projectPaths.rel("workDir");
    if (!import_fs.default.existsSync(abs.interfaceDefinition)) {
      throw new errors.BotpressCLIError("Could not read interface definition");
    }
    const bpLintDisabled = await this._isBpLintDisabled(abs.interfaceDefinition);
    const { outputFiles } = await this.projectContext.rebuildEntrypoint({
      absWorkingDir: abs.workDir,
      entrypoint: rel.interfaceDefinition
    });
    const artifact = outputFiles?.[0];
    if (!artifact) {
      throw new errors.BotpressCLIError("Could not read interface definition");
    }
    const definition = this.projectContext.getOrResolveDefinition(artifact.text);
    return { definition, bpLintDisabled };
  }
  async _readBotDefinitionFromFS(projectPaths) {
    const abs = projectPaths.abs;
    const rel = projectPaths.rel("workDir");
    if (!import_fs.default.existsSync(abs.botDefinition)) {
      throw new errors.BotpressCLIError("Could not read bot definition");
    }
    const bpLintDisabled = await this._isBpLintDisabled(abs.botDefinition);
    const { outputFiles } = await this.projectContext.rebuildEntrypoint({
      absWorkingDir: abs.workDir,
      entrypoint: rel.botDefinition
    });
    const artifact = outputFiles?.[0];
    if (!artifact) {
      throw new errors.BotpressCLIError("Could not read bot definition");
    }
    const definition = this.projectContext.getOrResolveDefinition(artifact.text);
    (0, import_sdk.validateBotDefinition)(definition);
    return { definition, bpLintDisabled };
  }
  async _readPluginDefinitionFromFS(projectPaths) {
    const abs = projectPaths.abs;
    const rel = projectPaths.rel("workDir");
    if (!import_fs.default.existsSync(abs.pluginDefinition)) {
      throw new errors.BotpressCLIError("Could not read plugin definition");
    }
    const bpLintDisabled = await this._isBpLintDisabled(abs.pluginDefinition);
    const { outputFiles } = await this.projectContext.rebuildEntrypoint({
      absWorkingDir: abs.workDir,
      entrypoint: rel.pluginDefinition
    });
    const artifact = outputFiles?.[0];
    if (!artifact) {
      throw new errors.BotpressCLIError("Could not read plugin definition");
    }
    const definition = this.projectContext.getOrResolveDefinition(artifact.text);
    return { definition, bpLintDisabled };
  }
  async _isBpLintDisabled(definitionPath) {
    const tsContent = await import_fs.default.promises.readFile(definitionPath, "utf-8");
    const regex = /\/\* bplint-disable \*\//;
    return regex.test(tsContent);
  }
  async displayIntegrationUrls({ api, bot }) {
    if (!import_lodash.default.keys(bot.integrations).length) {
      this.logger.debug("No integrations in bot");
      return;
    }
    const integrationDefinitions = await utils.records.mapValuesAsync(
      bot.integrations,
      async (integration) => api.getPublicOrPrivateIntegration({
        type: "id",
        id: integration.id
      })
    );
    this.logger.log("Integrations:");
    for (const [alias, integration] of Object.entries(bot.integrations)) {
      if (integration.enabled) {
        this.logger.log(`${alias}:`, { prefix: { symbol: "\u2192", indent: 2 } });
      } else {
        this.logger.log(`${alias} ${import_chalk.default.italic("(disabled)")}:`, {
          prefix: { symbol: "\u2192", indent: 2 }
        });
      }
      const integrationDefinition = integrationDefinitions[alias];
      const linkTemplateScript = integrationDefinition ? this._getLinkTemplateScript({ integration, integrationDefinition }) : void 0;
      this._displayWebhookUrl({ integration, integrationDefinition, linkTemplateScript });
      if (!integrationDefinition) {
        this.logger.debug(
          `No integration definition for integration ${alias} (${integration.name}, ${integration.id}), skipping OAuth or Sandbox links`
        );
        this.logger.line().commit();
        continue;
      }
      const isSandbox = integration.configurationType === "sandbox" && !!integrationDefinition.sandbox?.identifierExtractScript;
      const showLink = !!linkTemplateScript && (isSandbox || !!integrationDefinition.identifier?.extractScript);
      if (showLink && isSandbox) {
        await this._displaySandboxLinkAndCode({ integration, alias, bot, api, linkTemplateScript });
      } else if (showLink) {
        this._displayAuthorizationLink({ integration, api, linkTemplateScript });
      }
      this.logger.line().commit();
    }
  }
  _getLinkTemplateScript({
    integration,
    integrationDefinition
  }) {
    const config2 = integration.configurationType === null ? integrationDefinition?.configuration : integrationDefinition?.configurations[integration.configurationType];
    return config2?.identifier?.linkTemplateScript;
  }
  _displayWebhookUrl({
    integration,
    integrationDefinition,
    linkTemplateScript
  }) {
    const needsWebhook = !(integrationDefinition?.identifier && linkTemplateScript);
    const logFn = (needsWebhook ? this.logger.log : this.logger.debug).bind(this.logger);
    if (integration.enabled) {
      logFn(`${import_chalk.default.bold("Webhook")}: ${integration.webhookUrl}`, {
        prefix: { symbol: "\u25CF", indent: 4 }
      });
    } else {
      logFn(`Webhook: ${integration.webhookUrl}`, {
        prefix: { symbol: "\u25CB", indent: 4 }
      });
    }
  }
  _displayAuthorizationLink({
    integration,
    api,
    linkTemplateScript
  }) {
    const authorizationLink = this._getAuthorizationLink({ integration, api, linkTemplateScript });
    const isAuthorized = !!integration.identifier;
    const authorizationStatus = integration.identifier ? "Authorized \u2713" : "Authorize";
    if (integration.enabled && isAuthorized) {
      this.logger.log(`${import_chalk.default.bold(authorizationStatus)} : ${authorizationLink}`, {
        prefix: { symbol: "\u25CF", indent: 4 }
      });
    } else {
      this.logger.log(`${authorizationStatus}: ${authorizationLink}`, {
        prefix: { symbol: "\u25CB", indent: 4 }
      });
    }
  }
  _getLinkTemplateArgs({ integration, api }) {
    let env;
    if (api.url.includes(consts.stagingBotpressDomain)) {
      env = "preview";
    } else if (api.url.includes(consts.productionBotpressDomain)) {
      env = "production";
    } else {
      env = "development";
    }
    return {
      env,
      webhookId: integration.webhookId,
      webhookUrl: api.url.replace("api", "webhook")
    };
  }
  _getAuthorizationLink({
    integration,
    api,
    linkTemplateScript
  }) {
    return utils.vrl.getStringResult({
      code: linkTemplateScript,
      data: this._getLinkTemplateArgs({ integration, api })
    });
  }
  _getSandboxLink({
    shareableId,
    integration,
    api,
    linkTemplateScript
  }) {
    return utils.vrl.getStringResult({
      code: linkTemplateScript,
      data: { shareableId, ...this._getLinkTemplateArgs({ integration, api }) }
    });
  }
  async _displaySandboxLinkAndCode({
    integration,
    alias,
    bot,
    api,
    linkTemplateScript
  }) {
    const shareableId = await api.getOrGenerateShareableId(bot.id, integration.id, alias);
    const sandboxLink = this._getSandboxLink({ shareableId, integration, api, linkTemplateScript });
    const sandboxInstruction = `Send '${shareableId}' to ${sandboxLink}`;
    if (integration.enabled) {
      this.logger.log(`${import_chalk.default.bold("Sandbox")}: ${sandboxInstruction}`, {
        prefix: { symbol: "\u25CF", indent: 4 }
      });
    } else {
      this.logger.log(`Sandbox: ${sandboxInstruction}`, {
        prefix: { symbol: "\u25CB", indent: 4 }
      });
    }
  }
  async promptSecrets(integrationDef, argv, opts = {}) {
    const formatEnv = opts.formatEnv ?? false;
    const knownSecrets = opts.knownSecrets ?? [];
    const { secrets: secretDefinitions } = integrationDef;
    if (!secretDefinitions) {
      return {};
    }
    const secretArgv = this._parseArgvSecrets(argv.secrets);
    const invalidSecret = Object.keys(secretArgv).find((s) => !secretDefinitions[s]);
    if (invalidSecret) {
      throw new errors.BotpressCLIError(`Secret ${invalidSecret} is not defined in integration definition`);
    }
    const values = {};
    for (const [secretName, { optional }] of Object.entries(secretDefinitions)) {
      const argvSecret = secretArgv[secretName];
      if (argvSecret) {
        this.logger.debug(`Using secret "${secretName}" from argv`);
        values[secretName] = argvSecret;
        continue;
      }
      const alreadyKnown = knownSecrets.includes(secretName);
      let mode;
      if (alreadyKnown) {
        mode = "already set";
      } else if (optional) {
        mode = "optional";
      } else {
        mode = "required";
      }
      const prompted = await this.prompt.text(`Enter value for secret "${secretName}" (${mode})`);
      if (prompted) {
        values[secretName] = prompted;
        continue;
      }
      if (alreadyKnown) {
        this.logger.log(`Secret "${secretName}" is unchanged`);
      } else if (optional) {
        this.logger.warn(`Secret "${secretName}" is unassigned`);
      } else {
        throw new errors.BotpressCLIError(`Secret "${secretName}" is required`);
      }
    }
    for (const secretName of knownSecrets) {
      const isDefined = secretName in secretDefinitions;
      if (isDefined) {
        continue;
      }
      const prompted = await this.prompt.confirm(`Secret "${secretName}" was removed. Do you wish to delete it?`);
      if (prompted) {
        this.logger.log(`Deleting secret "${secretName}"`, { prefix: { symbol: "\xD7", fg: "red" } });
        values[secretName] = null;
      }
    }
    if (!formatEnv) {
      return values;
    }
    const envVariables = import_lodash.default.mapKeys(values, (_v, k) => codegen.secretEnvVariableName(k));
    return envVariables;
  }
  async prepareCreateIntegrationBody(integrationDef) {
    const partialBody = await apiUtils.prepareCreateIntegrationBody(integrationDef);
    let code = void 0;
    if (import_fs.default.existsSync(this.projectPaths.abs.outFileCJS)) {
      code = await this.readProjectFile(this.projectPaths.abs.outFileCJS);
    }
    const icon = await this.readProjectFile(integrationDef.icon, "base64");
    const readme = await this.readProjectFile(integrationDef.readme, "base64");
    const extractScript = await this.readProjectFile(integrationDef.identifier?.extractScript);
    const fallbackHandlerScript = await this.readProjectFile(integrationDef.identifier?.fallbackHandlerScript);
    return {
      ...partialBody,
      code,
      icon,
      readme,
      identifier: {
        extractScript,
        fallbackHandlerScript
      },
      configuration: integrationDef.configuration ? {
        schema: await utils.schema.mapZodToJsonSchema(integrationDef.configuration, {
          useLegacyZuiTransformer: integrationDef.__advanced?.useLegacyZuiTransformer
        }),
        identifier: {
          required: integrationDef.configuration.identifier?.required,
          linkTemplateScript: await this.readProjectFile(
            integrationDef.configuration.identifier?.linkTemplateScript
          )
        }
      } : void 0,
      configurations: integrationDef.configurations ? await utils.records.mapValuesAsync(integrationDef.configurations, async (configuration) => ({
        title: configuration.title,
        description: configuration.description,
        schema: await utils.schema.mapZodToJsonSchema(configuration, {
          useLegacyZuiTransformer: integrationDef.__advanced?.useLegacyZuiTransformer
        }),
        identifier: {
          required: configuration.identifier?.required,
          linkTemplateScript: await this.readProjectFile(configuration.identifier?.linkTemplateScript)
        }
      })) : void 0
    };
  }
  async prepareBotDependencies(botDef, api) {
    const integrations = await this._fetchDependencies(
      botDef.integrations ?? {},
      ({ name, version }) => api.getPublicOrPrivateIntegration({ type: "name", name, version })
    );
    const plugins = await this._fetchDependencies(
      botDef.plugins ?? {},
      async ({ name, version }) => await api.getPublicOrPrivatePlugin({ type: "name", name, version })
    );
    const pluginsWithBackingIntegrations = await utils.records.mapValuesAsync(plugins, async (plugin) => ({
      ...plugin,
      interfaces: await this._fetchDependencies(
        plugin.interfaces ?? {},
        async (interfaceExtension) => await api.getPublicOrPrivateIntegration({ ...interfaceExtension, type: "name" })
      )
    }));
    return {
      integrations: utils.records.mapValues(
        integrations,
        ({ enabled, configurationType, configuration, disabledChannels, id }) => ({
          enabled,
          configurationType,
          configuration,
          disabledChannels,
          integrationId: id
        })
      ),
      plugins: utils.records.mapValues(pluginsWithBackingIntegrations, (plugin) => ({
        ...plugin,
        interfaces: utils.records.mapValues(plugin.interfaces ?? {}, (iface) => ({
          ...iface,
          integrationId: iface.id
        }))
      }))
    };
  }
  async prepareIntegrationDependencies(integrationDef, api) {
    const interfaces = await this._fetchDependencies(
      integrationDef.interfaces ?? {},
      ({ name, version }) => api.getPublicInterface({ type: "name", name, version })
    );
    return { interfaces };
  }
  async preparePluginDependencies(pluginDef, api) {
    const integrations = await this._fetchDependencies(
      pluginDef.integrations ?? {},
      ({ name, version }) => api.getPublicOrPrivateIntegration({ type: "name", name, version })
    );
    const interfaces = await this._fetchDependencies(
      pluginDef.interfaces ?? {},
      ({ name, version }) => api.getPublicInterface({ type: "name", name, version })
    );
    return {
      dependencies: {
        integrations,
        interfaces
      }
    };
  }
  _fetchDependencies = async (deps, fetcher) => {
    const isRemote = (dep) => dep.id !== void 0;
    return utils.records.mapValuesAsync(deps, async (dep) => {
      if (isRemote(dep)) {
        return dep;
      }
      const { id } = await fetcher(dep);
      return { ...dep, id };
    });
  };
  readProjectFile = async (filePath, encoding = "utf-8") => {
    if (!filePath) {
      return void 0;
    }
    const absoluteFilePath = utils.path.absoluteFrom(this.projectPaths.abs.workDir, filePath);
    return import_fs.default.promises.readFile(absoluteFilePath, encoding).catch((thrown) => {
      throw errors.BotpressCLIError.wrap(thrown, `Could not read file "${absoluteFilePath}"`);
    });
  };
  _parseArgvSecrets(argvSecrets) {
    const parsed = {};
    for (const secret of argvSecrets) {
      const [key, value] = utils.string.splitOnce(secret, "=");
      if (!value) {
        throw new errors.BotpressCLIError(
          `Secret "${key}" is missing a value. Expected format: "SECRET_NAME=secretValue"`
        );
      }
      parsed[key] = value;
    }
    return parsed;
  }
  _notifyUpdateSdk = async () => {
    try {
      this.logger.debug("Checking if sdk is up to date");
      const { workDir } = this.projectPaths.abs;
      const projectPkgJson = await utils.pkgJson.readPackageJson(workDir);
      if (!projectPkgJson) {
        this.logger.debug(`Could not find package.json at "${workDir}"`);
        return;
      }
      const sdkPackageName = "@botpress/sdk";
      const actualSdkVersion = utils.pkgJson.findDependency(projectPkgJson, sdkPackageName);
      if (!actualSdkVersion) {
        this.logger.debug(`Could not find dependency "${sdkPackageName}" in project package.json`);
        return;
      }
      if (actualSdkVersion.startsWith("workspace:")) {
        return;
      }
      const actualCleanedSdkVersion = import_semver.default.valid(import_semver.default.coerce(actualSdkVersion));
      if (!actualCleanedSdkVersion) {
        this.logger.debug(`Invalid sdk version "${actualSdkVersion}" in project package.json`);
        return;
      }
      const cliPkgJson = await this.readPkgJson();
      const expectedSdkVersion = utils.pkgJson.findDependency(cliPkgJson, sdkPackageName);
      if (!expectedSdkVersion) {
        this.logger.debug(`Could not find dependency "${sdkPackageName}" in cli package.json`);
        return;
      }
      const expectedCleanedSdkVersion = import_semver.default.valid(import_semver.default.coerce(expectedSdkVersion));
      if (!expectedCleanedSdkVersion) {
        this.logger.debug(`Invalid sdk version "${expectedSdkVersion}" in cli package.json`);
        return;
      }
      if (import_semver.default.eq(actualCleanedSdkVersion, expectedCleanedSdkVersion)) {
        return;
      }
      const diff = import_semver.default.diff(actualCleanedSdkVersion, expectedCleanedSdkVersion);
      if (!diff) {
        this.logger.debug(`Could not compare versions "${actualCleanedSdkVersion}" and "${expectedCleanedSdkVersion}"`);
        return;
      }
      const errorMsg = `Project SDK version is "${actualCleanedSdkVersion}", but expected "${expectedCleanedSdkVersion}"`;
      if (utils.semver.releases.lt(diff, "minor")) {
        this.logger.debug(`${errorMsg}. This may cause compatibility issues.`);
        return;
      }
      this.logger.warn(import_chalk.default.bold(`${errorMsg}. This will cause compatibility issues.`));
    } catch (thrown) {
      const err = errors.BotpressCLIError.map(thrown);
      this.logger.debug(`Failed to check if sdk is up to date: ${err.message}`);
    }
  };
  validateIntegrationRegistration(updatedBot, onFailCallback) {
    let failedIntegrations = {};
    for (const [integrationName, integration] of Object.entries(updatedBot.integrations)) {
      if (integration.status === "registration_failed") {
        failedIntegrations = { ...failedIntegrations, [integrationName]: integration };
      }
    }
    if (Object.keys(failedIntegrations).length > 0) {
      onFailCallback(failedIntegrations);
    }
  }
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  ProjectCommand,
  ProjectDefinitionContext
});
//# sourceMappingURL=project-command.js.map
