in packages/MSBot/src/msbot-clone-services.ts [148:891]
async function processConfiguration(): Promise<void> {
if (!args.sdkVersion) {
args.sdkVersion = "v4";
}
if (!args.sdkLanguage) {
if (fs.existsSync("package.json")) {
args.sdkLanguage = "Node";
} else {
args.sdkLanguage = "CSharp";
}
}
if (!args.groupName) {
args.groupName = args.name;
}
if ((<any>args)['proj-file']) {
args.projFile = (<string>(<any>args)['proj-file']);
console.log(args.projFile);
}
else if ((<any>args)['code-dir']) {
args.codeDir = (<string>(<any>args)['code-dir']);
console.log(args.codeDir);
}
if (!args.projFile && !args.codeDir) {
let files = fs.readdirSync('.');
for (let file of files) {
if (path.extname(file) == '.csproj') {
args.projFile = file;
break;
}
}
}
if (!args.searchSku) {
args.searchSku = "basic";
}
// verify az command exists and is correct version
await checkAzBotServiceVersion();
if (args.projFile) {
await checkDotNetRequirement();
}
let recipe = <BotRecipe> await fsExtra.readJson(path.join(args.folder, `bot.recipe`));
// get subscription account data
let command: string = `az account show `;
if (args.subscriptionId) {
command += `--subscription ${args.subscriptionId}`;
}
let azAccount = await runCommand(command, `Fetching subscription account`);
args.subscriptionName = azAccount.name;
args.subscriptionId = azAccount.id;
args.tenantId = azAccount.tenantId;
try {
// pass 0 - tell the user what are going to create
if (!args.quiet) {
let bot = 0;
let appInsights = 0;
let storage = 0;
let sitePlan = 0;
console.log("The following services will be created by this operation:");
const table = new Table({
// don't use lines for table
chars: {
'top': '', 'top-mid': '', 'top-left': '', 'top-right': '',
'bottom': '', 'bottom-mid': '', 'bottom-left': '', 'bottom-right': '',
'left': '', 'left-mid': '', 'right': '', 'right-mid': '',
'mid': '', 'mid-mid': '', 'middle': ''
},
head: [chalk.default.bold('Service'), chalk.default.bold('Location'), chalk.default.bold('SKU'), chalk.default.bold("Resource Group")],
colWidths: [40, 20, 20, 20],
style: { 'padding-left': 1, 'padding-right': 1 },
wordWrap: true
});
let hasSitePlan = false;
let rows = [];
for (let resource of recipe.resources) {
switch (resource.type) {
case ServiceTypes.AppInsights:
let appInsightsRegion = regionToAppInsightRegionNameMap[args.location];
if (appInsightsRegion) {
rows.push([`Azure AppInsights Service`, `${appInsightsRegion}`, `F0`, args.groupName]);
}
break;
case ServiceTypes.BlobStorage:
rows.push(['Azure Blob Storage Service', `${args.location}`, 'Standard_LRS', args.groupName]);
break;
case ServiceTypes.Bot:
rows.push([`Azure Bot Service Registration`, `Global`, ``, args.groupName]);
if (!hasSitePlan) {
rows.push([`Azure App Site Plan`, `${args.location}`, `S1`, args.groupName]);
hasSitePlan = true;
}
rows.push([`Azure WebApp Service (Bot)`, `${args.location}`, ``, args.groupName]);
break;
case ServiceTypes.CosmosDB:
rows.push([`Azure CosmosDB Service`, `${args.location}`, `1 write region`, args.groupName]);
break;
case ServiceTypes.Endpoint:
break;
case ServiceTypes.File:
break;
case ServiceTypes.Generic:
break;
case ServiceTypes.Dispatch:
case ServiceTypes.Luis:
if (!args.luisAuthoringKey) {
throw new Error('missing --luisAuthoringKey argument');
}
if (!args.luisPublishRegion) {
args.luisPublishRegion = luisPublishRegions.find((value) => value == args.location);
if (!args.luisPublishRegion) {
args.luisPublishRegion = regionToLuisPublishRegionMap[args.location];
}
}
if (resource.type == ServiceTypes.Dispatch)
rows.push([`Azure LUIS Cognitive Service (Dispatch)`, `${args.luisPublishRegion}`, `S0`, args.groupName]);
else
rows.push([`Azure LUIS Cognitive Service`, `${args.luisPublishRegion}`, `S0`, args.groupName]);
break;
case ServiceTypes.QnA:
rows.push([`Azure QnA Maker Service`, `westus`, `S0`, args.groupName]);
if (!hasSitePlan) {
rows.push([`Azure App Site Plan`, `${args.location}`, `S1`, args.groupName]);
hasSitePlan = true;
}
rows.push([`Azure WebApp Service (QnA)`, `${args.location}`, ``, args.groupName]);
rows.push([`Azure Search Service`, `${regionToSearchRegionMap[args.location]}`, args.searchSku, args.groupName]);
break;
default:
break;
}
}
rows.sort((a, b) => a[0].localeCompare(b[0]));
for (let row of rows)
table.push(row);
await logAsync(table.toString());
console.log(`Resources will be created in subscription: ${chalk.default.bold(azAccount.name)} (${azAccount.id})`);
if (!args.force) {
const answer = readline.question(`Would you like to perform this operation? [y/n]`);
if (answer == "no" || answer == "n") {
// remove orphaned bot file if it exists
if (fs.existsSync(args.name + '.bot')) fs.unlinkSync(args.name + '.bot');
console.log("Canceling the operation");
process.exit(1);
}
}
}
// pass 1 - create bot if we are going to need one. This will create
// * group
// * sitePlan
// * site
// * appInsights
// * storage
// create group
let azGroup: any;
let azBot: IBotService | undefined;
let azSitePlan: any;
let storageInfo;
let azQnaSubscription: any;
let azLuisSubscription: any;
let azAppInsights: any;
let azBotExtended: any;
let azBotEndpoint: IEndpointService | undefined;
// create group if not created yet
azGroup = await createGroup();
for (let resource of recipe.resources) {
if (resource.type == ServiceTypes.Bot) {
if (!azBot) {
azBot = await createBot();
azBotEndpoint = <IEndpointService><any>azBot;
azBotExtended = await runCommand(`az bot show -g ${args.groupName} -n ${args.name} --subscription ${args.subscriptionId}`,
`Fetching bot extended information [${args.name}]`);
// fetch co-created resources so we can get blob and appinsights data
let azGroupResources = await runCommand(`az resource list -g ${azGroup.name} --subscription ${args.subscriptionId}`,
`Fetching co-created resources [${args.name}]`);
let botWebSite;
for (let resource of azGroupResources) {
switch (resource.type) {
case "microsoft.insights/components":
azAppInsights = resource;
break;
case "Microsoft.Storage/storageAccounts":
storageInfo = resource;
break;
case "Microsoft.Web/serverfarms":
azSitePlan = resource;
break;
case "Microsoft.Web/sites":
// the website for the bot does have the bot name in it (qna host does)
if (resource.name.indexOf('-qnahost') < 0)
botWebSite = resource;
break;
}
}
// get appSettings from botAppSite (specifically to get secret)
if (botWebSite) {
if(args.encryption){
let botAppSettings = await runCommand(`az webapp config appsettings list -g ${args.groupName} -n ${botWebSite.name} --subscription ${args.subscriptionId}`,
`Fetching bot website appsettings [${args.name}]`);
for (let setting of botAppSettings) {
if (setting.name == "botFileSecret") {
args.secret = setting.value;
break;
}
}
if (!args.secret) {
args.secret = BotConfiguration.generateKey();
// set the appsetting
await runCommand(`az webapp config appsettings set -g ${args.groupName} -n ${botWebSite.name} --settings botFileSecret="${args.secret}" --subscription ${args.subscriptionId}`,
`Setting bot website appsettings secret [${args.name}]`);
}
}
} else {
throw new Error('botsite was not found');
}
config.services.push(new BotService({
type: ServiceTypes.Bot,
id: resource.id,
name: azBot.name,
tenantId: args.tenantId,
subscriptionId: args.subscriptionId,
resourceGroup: args.groupName,
serviceName: azBot.name,
appId: azBot.appId
}));
await config.save();
}
}
}
// pass 2 - create LUIS and QNA cognitive service subscriptions (and hosting services)
for (let resource of recipe.resources) {
switch (resource.type) {
case ServiceTypes.Luis:
case ServiceTypes.Dispatch:
if (!azLuisSubscription) {
if (!args.luisAuthoringRegion) {
if (regionToLuisAuthoringRegionMap.hasOwnProperty(args.location))
args.luisAuthoringRegion = regionToLuisAuthoringRegionMap[args.location];
else
throw new Error(`${args.location} does not have a valid luisAuthoringRegion. Pass --luisAuthoringRegion to tell us which region you are in`);
}
if (!args.luisPublishRegion) {
args.luisPublishRegion = luisPublishRegions.find((value) => value == args.location);
if (!args.luisPublishRegion) {
args.luisPublishRegion = regionToLuisAuthoringRegionMap[args.location];
}
}
// create luis subscription
let luisCogsName = `${args.name}-LUIS`;
azLuisSubscription = await runCommand(`az cognitiveservices account create -g ${azGroup.name} --kind LUIS -n "${luisCogsName}" --location ${args.luisPublishRegion} --sku S0 --yes --subscription ${args.subscriptionId}`,
`Creating LUIS Cognitive Service [${luisCogsName}]`);
// get keys
let luisKeys = await runCommand(`az cognitiveservices account keys list -g ${azGroup.name} -n "${luisCogsName}" --subscription ${args.subscriptionId}`,
`Fetching LUIS Keys [${luisCogsName}]`);
args.luisSubscriptionKey = luisKeys.key1;
}
break;
case ServiceTypes.QnA:
if (!azQnaSubscription) {
if (!azSitePlan) {
azSitePlan = await runCommand(`az appservice plan create -g ${args.groupName} --sku s1 --name ${args.name} --subscription ${args.subscriptionId}`,
`Creating site plan [${args.name}]`);
}
// create qnaMaker service in resource group
// we have a group, and app service,
// provision search instance
let searchName = args.name.toLowerCase() + '-search';
let searchResult = await runCommand(`az search service create -g ${azGroup.name} -n "${searchName}" --location ${regionToSearchRegionMap[args.location]} --sku ${args.searchSku} --subscription ${args.subscriptionId}`,
`Creating Azure Search Service [${searchName}]`);
// get search keys
let searchKeys = await runCommand(`az search admin-key show -g ${azGroup.name} --service-name "${searchName}" --subscription ${args.subscriptionId}`,
`Fetching Azure Search Service keys [${searchName}]`);
// create qna host service
let qnaHostName = args.name + '-qnahost';
let createQnaWeb = await runCommand(`az webapp create -g ${azGroup.name} -n ${qnaHostName} --plan ${args.name} --subscription ${args.subscriptionId}`,
`Creating QnA Maker host web service [${qnaHostName}]`);
// configure qna web service settings
command = `az webapp config appsettings set -g ${azGroup.name} -n ${qnaHostName} --subscription ${args.subscriptionId} --settings `;
command += `"AzureSearchName=${searchName}" `;
command += `AzureSearchAdminKey=${searchKeys.primaryKey} `;
command += `PrimaryEndpointKey=${qnaHostName}-PrimaryEndpointKey `;
command += `SecondaryEndpointKey=${qnaHostName}-SecondaryEndpointKey `;
command += `DefaultAnswer="No good match found in KB." `;
command += `QNAMAKER_EXTENSION_VERSION="latest"`;
await runCommand(command, `Configuring QnA Maker host web service settings [${qnaHostName}]`);
await runCommand(`az webapp cors add -g ${azGroup.name} -n ${qnaHostName} -a "*" --subscription ${args.subscriptionId}`,
`Configuring QnA Maker host web service CORS [${qnaHostName}]`);
// create qnamaker account
let qnaAccountName = args.name + '-QnAMaker';
command = `az cognitiveservices account create -g ${azGroup.name} --kind QnAMaker -n "${qnaAccountName}" --sku S0 --subscription ${args.subscriptionId} `;
// NOTE: currently qnamaker is only available in westus
command += `--location westus --yes `;
command += `--api-properties qnaRuntimeEndpoint=https://${qnaHostName}.azurewebsites.net`;
azQnaSubscription = await runCommand(command, `Creating QnA Maker Cognitive Service [${qnaAccountName}]`);
// get qna subscriptionKey
let azQnaKeys = await runCommand(`az cognitiveservices account keys list -g ${azGroup.name} -n "${qnaAccountName}" --subscription ${args.subscriptionId}`,
`Fetching QnA Maker Cognitive Service [${qnaAccountName}]`);
args.qnaSubscriptionKey = azQnaKeys.key1;
}
break;
default:
if (!args.location) {
throw new Error('missing --location argument');
}
break;
}
}
// pass 3- create the actual services
for (let resource of recipe.resources) {
switch (resource.type) {
case ServiceTypes.AppInsights:
{
// this was created via az bot create, hook it up
if (azAppInsights && resource.id) {
let appInsights = await getAppInsightsService(azAppInsights);
appInsights.id = resource.id; // keep original id
config.services.push(appInsights);
} else {
console.warn("WARNING: No bot appInsights plan was created because no bot was created");
}
await config.save();
}
break;
case ServiceTypes.BlobStorage:
{
// this was created via az bot create, get the connection string and then hook it up
if (!storageInfo) {
let storageName: string = `${azGroup.name.toLowerCase() + generateShortId()}storage`;
storageInfo = await runCommand(`az storage account create -g ${azGroup.name} -n "${storageName}" --location ${args.location} --sku Standard_LRS --subscription ${args.subscriptionId}`,
`Creating Azure Blob Storage [${storageName}]`);
}
if (storageInfo) {
let blobConnection = await runCommand(`az storage account show-connection-string -g ${azGroup.name} -n "${storageInfo.name}" --subscription ${args.subscriptionId}`,
`Fetching Azure Blob Storage connection string [${args.name}]`);
let blobResource = <IBlobResource>resource;
config.services.push(new BlobStorageService({
type: ServiceTypes.BlobStorage,
id: resource.id,
name: storageInfo.name,
serviceName: storageInfo.name,
tenantId: args.tenantId,
subscriptionId: args.subscriptionId,
resourceGroup: args.groupName,
connectionString: blobConnection.connectionString,
container: blobResource.container
}));
}
await config.save();
}
break;
case ServiceTypes.Bot:
{
// already created
}
break;
case ServiceTypes.CosmosDB:
{
let cosmosResource = <ICosmosDBResource>resource;
let cosmosName = `${args.name.toLowerCase()}`;
// az cosmosdb create --n name -g Group1
let cosmosDb = await runCommand(`az cosmosdb create -n ${cosmosName} -g ${azGroup.name} --subscription ${args.subscriptionId}`,
`Creating Azure CosmosDB account [${cosmosName}] ${chalk.default.italic.yellow(`(Please be patient, this may take 5 minutes)`)}`);
// get keys
let cosmosDbKeys = await runCommand(`az cosmosdb list-keys -g ${azGroup.name} -n ${cosmosName} --subscription ${args.subscriptionId}`,
`Fetching Azure CosmosDB account keys [${args.name}]`);
// az cosmosdb database create -n clonebot1cosmosdb --key <key> -d db1 --url-connection https://clonebot1cosmosdb.documents.azure.com:443/
await runCommand(`az cosmosdb database create -g ${azGroup.name} -n ${cosmosName} --key ${cosmosDbKeys.primaryMasterKey} -d ${cosmosResource.database} --url-connection https://${cosmosName}.documents.azure.com:443/ --subscription ${args.subscriptionId}`,
`Creating Azure CosmosDB database [${cosmosResource.database}]`);
// az cosmosdb collection create -n clonebot1cosmosdb --key <key> -d db1 --url-connection https://clonebot1cosmosdb.documents.azure.com:443/ --collection-name collection
await runCommand(`az cosmosdb collection create -g ${azGroup.name} -n ${cosmosName} --key ${cosmosDbKeys.primaryMasterKey} -d ${cosmosResource.database} --url-connection https://${cosmosName}.documents.azure.com:443/ --collection-name ${cosmosResource.collection} --subscription ${args.subscriptionId}`,
`Creating Azure CosmosDB collection [${cosmosResource.collection}]`);
// get connection string is broken
// command = `az cosmosdb list-connection-strings -g ${azGroup.name} -n ${args.name}`;
// logCommand(args, `Fetching cosmosdb connection strings ${cosmosResource.collection}`, command);
// p = await exec(command);
// let connections = JSON.parse(p.stdout);
// register it as a service
config.services.push(new CosmosDbService({
type: ServiceTypes.CosmosDB,
id: cosmosResource.id,
name: cosmosName,
serviceName: cosmosName,
tenantId: args.tenantId,
subscriptionId: args.subscriptionId,
resourceGroup: args.groupName,
endpoint: `https://${cosmosName}.documents.azure.com:443/`,
key: cosmosDbKeys.primaryMasterKey,
database: cosmosResource.database,
collection: cosmosResource.collection,
}));
}
await config.save();
break;
case ServiceTypes.Endpoint:
{
let urlResource = <IUrlResource>resource;
if (urlResource.url && urlResource.url.indexOf('localhost') > 0) {
// add localhost record as is, but add appId/password
config.services.push(new EndpointService({
type: ServiceTypes.Endpoint,
id: resource.id,
name: resource.name,
appId: (azBotEndpoint) ? azBotEndpoint.appId : '',
appPassword: (azBotEndpoint) ? azBotEndpoint.appPassword : '',
endpoint: urlResource.url
}));
} else {
// merge oldUrl and new Url hostname
let oldUrl = new url.URL(urlResource.url);
if (azBotEndpoint) {
let azUrl = new url.URL(azBotEndpoint.endpoint);
oldUrl.hostname = azUrl.hostname;
config.services.push(new EndpointService({
type: ServiceTypes.Endpoint,
id: resource.id,
name: resource.name,
appId: azBotEndpoint.appId,
appPassword: azBotEndpoint.appPassword,
endpoint: oldUrl.href
}));
if (oldUrl != azUrl) {
// TODO update bot service record with merged url
}
} else {
console.warn("There is no cloud endpoint to because there is no bot created");
}
}
await config.save();
}
break;
case ServiceTypes.File:
{
let fileResource = <IFileResource>resource;
config.services.push(new FileService({
type: ServiceTypes.File,
id: fileResource.id,
name: fileResource.name,
path: fileResource.path,
}));
await config.save();
}
break;
case ServiceTypes.Generic:
{
let genericResource = <IGenericResource>resource;
config.services.push(new GenericService({
type: ServiceTypes.Generic,
id: genericResource.id,
name: genericResource.name,
url: genericResource.url,
configuration: genericResource.configuration,
}));
await config.save();
}
break;
case ServiceTypes.Dispatch:
{
let luisService = await importAndTrainLuisApp(resource);
let dispatchResource = <IDispatchResource>resource;
let dispatchService: IDispatchService = Object.assign({ serviceIds: dispatchResource.serviceIds, }, luisService);
(<any>dispatchService).type = ServiceTypes.Dispatch;
dispatchService.id = resource.id; // keep same resource id
config.services.push(new DispatchService(dispatchService));
await config.save();
}
break;
case ServiceTypes.Luis:
{
if (!commandExistsSync('luis')) {
console.error(chalk.default.redBright(`Unable to find LUIS CLI. Please install via npm i -g luis-apis and try again. \n\nSee https://aka.ms/msbot-clone-services for pre-requisites.`))
showErrorHelp();
}
let luisService = await importAndTrainLuisApp(resource);
luisService.id = `${resource.id}`; // keep same resource id
config.services.push(luisService);
await config.save();
}
break;
case ServiceTypes.QnA:
{
if (!commandExistsSync('qnamaker')) {
console.error(chalk.default.redBright(`Unable to find QnAMaker CLI. Please install via npm i -g qnamaker and try again. \n\nSee https://aka.ms/msbot-clone-services for pre-requisites.`))
showErrorHelp();
}
let qnaPath = path.join(args.folder, `${resource.id}.qna`);
let kbName = resource.name;
// These values pretty much gaurantee success. We can decrease them if the QnA backend gets faster
const initialDelaySeconds = 30;
let retryAttemptsRemaining = 3;
let retryDelaySeconds = 15;
const retryDelayIncrease = 30;
console.log(`Waiting ${initialDelaySeconds} seconds for QnAMaker backend to finish setting up...`);
await sleep(initialDelaySeconds);
let svc;
while (retryAttemptsRemaining >= 0) {
try {
svc = await runCommand(`qnamaker create kb --subscriptionKey ${args.qnaSubscriptionKey} --name "${kbName}" --in ${qnaPath} --wait --msbot`,
`Creating QnA Maker KB [${kbName}]`);
break;
} catch (err) {
// Helpful error messages are mostly in err.stderr, when available. err.stderr can't be parsed to JSON, so we have to search for substrings
if (err.stderr) {
const generalFailedString = `"operationState\": \"Failed\"`;
const invalidSubscriptionString = `Access denied due to invalid subscription key`;
// Usually the first failure
if (err.stderr.includes(invalidSubscriptionString)) {
console.warn(chalk.default.yellowBright(`QnAMaker backend still generating API keys. Waiting ${retryDelaySeconds} seconds before trying again. ${retryAttemptsRemaining} attempts remaining.`));
// Usually the remaining, non-breaking failures
} else if (err.stderr.includes(generalFailedString)) {
console.warn(chalk.default.yellowBright(`QnAMaker backend not ready. Waiting ${retryDelaySeconds} seconds before trying again. ${retryAttemptsRemaining} attempts remaining.`));
} else {
console.error(chalk.default.yellowBright(`QnAMaker doesn't seem to be working. Waiting ${retryDelaySeconds} seconds before trying again. ${retryAttemptsRemaining} attempts remaining.`));
}
}
retryAttemptsRemaining--;
await sleep(retryDelaySeconds);
retryDelaySeconds += retryDelayIncrease;
if (retryAttemptsRemaining < 0) {
console.error(chalk.default.redBright(`Unable to create QnA KB.`));
showErrorHelp();
} else {
continue;
}
}
}
let service = new QnaMakerService(svc);
service.id = `${resource.id}`; // keep id
service.name = kbName;
config.services.push(service);
await config.save();
// publish
try {
await runCommand(`qnamaker publish kb --subscriptionKey ${service.subscriptionKey} --kbId ${service.kbId} --hostname ${service.hostname} --endpointKey ${service.endpointKey}`,
`Publishing QnA Maker KB [${kbName}]`);
} catch (err) {
console.warn(err.message || err);
}
}
break;
default:
break;
}
}
// hook up appinsights and blob storage if it hasn't been already
if (azBot) {
let hasBot = false;
let hasBlob = false;
let hasAppInsights = false;
for (let service of config.services) {
switch (service.type) {
case ServiceTypes.AppInsights:
hasAppInsights = true;
break;
case ServiceTypes.BlobStorage:
hasBlob = true;
break;
case ServiceTypes.Bot:
hasBot = true;
break;
}
}
if (!hasBot) {
// created via az bot create, register the result
config.connectService(new BotService({
name: azBot.name,
tenantId: args.tenantId,
subscriptionId: args.subscriptionId,
resourceGroup: args.groupName,
serviceName: azBot.name,
appId: azBot.appId
}));
}
if (azBotEndpoint) {
// add endpoint
config.connectService(new EndpointService({
type: ServiceTypes.Endpoint,
name: "production",
appId: azBotEndpoint.appId,
appPassword: azBotEndpoint.appPassword,
endpoint: azBotEndpoint.endpoint
}));
await config.save();
}
if (!hasAppInsights) {
if (azAppInsights) {
let appInsights = await getAppInsightsService(azAppInsights);
config.connectService(appInsights);
} else {
console.warn("WARNING: No bot appInsights plan was created because no bot was created");
}
await config.save();
}
if (!hasBlob && storageInfo) {
// this was created via az bot create, get the connection string and then hook it up
let blobConnection = await runCommand(`az storage account show-connection-string -g ${azGroup.name} -n "${storageInfo.name}" --subscription ${args.subscriptionId}`,
`Fetching storage connection string [${storageInfo.name}]`);
config.connectService(new BlobStorageService({
name: storageInfo.name,
serviceName: storageInfo.name,
tenantId: args.tenantId,
subscriptionId: args.subscriptionId,
resourceGroup: args.groupName,
connectionString: blobConnection.connectionString,
container: null
}));
await config.save();
}
}
console.log(`${config.getPath()} created.`);
if (args.secret) {
console.log(`\nThe secret used to decrypt ${config.getPath()} is:`);
console.log(chalk.default.magentaBright(args.secret));
console.log(chalk.default.yellowBright(`\nNOTE: This secret is not recoverable and you should save it in a safe place according to best security practices.`));
console.log(chalk.default.yellowBright(` Copy this secret and use it to open the ${config.getPath()} the first time.`));
await config.save(args.secret);
}
if (azBot) {
// update local safe settings
await updateLocalSafeSettings(azBot);
// publish local bot code to web service
await publishBot(azBot);
// show emulator url with secret if exist
let fullPath = path.resolve(process.cwd(), config.getPath());
let productionEndpoint = config.findServiceByNameOrId('production');
let botFileUrl = `bfemulator://bot.open?path=${encodeURIComponent(fullPath)}`;
if (args.secret) {
botFileUrl += `&secret=${encodeURIComponent(args.secret)}`;
}
if (productionEndpoint) {
botFileUrl += `&id=${productionEndpoint.id}`;
}
console.log('To open this bot file in emulator:');
console.log(chalk.default.cyanBright(botFileUrl));
// auto launch emulator with url so it can memorize the secret so you don't need to remember it. <whew!>
process.env.ELECTRON_NO_ATTACH_CONSOLE = "true";
opn(botFileUrl, { wait: false });
}
console.log(`Done.`);
} catch (error) {
if (error.message) {
let lines = error.message.split('\n');
let message = '';
for (let line of lines) {
// trim to copyright symbol, help from inner process command line args is inappropriate
if (line.indexOf('©') > 0)
break;
message += line;
}
throw new Error(message);
}
throw new Error(error);
}
}