This repository was archived by the owner on Jul 9, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 374
fix: during publish, do not copy project into a build folder #6729
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
49de3cf
do not copy project into a build folder
benbrown 839df65
Merge branch 'main' into benbrown/nomoveproject
benbrown 381d2e2
* during publish, do NOT write the settings file to disk
benbrown 1750e5e
Merge branch 'benbrown/nomoveproject' of https://github.com/microsoft…
benbrown 8e1b7b4
Merge branch 'main' into benbrown/nomoveproject
benbrown 84d37b0
clean out code used for doing builds in temp folders
benbrown 5043e98
Copy manifests into wwwroot/manifests as part of publish process
benbrown 139261f
Merge branch 'benbrown/nomoveproject' of https://github.com/microsoft…
benbrown b0b31d2
Merge branch 'main' into benbrown/nomoveproject
benbrown d7103df
Merge branch 'main' into benbrown/nomoveproject
benbrown 2f17f1a
Merge branch 'main' into benbrown/nomoveproject
benbrown 7c21d14
Merge branch 'main' into benbrown/nomoveproject
benbrown fd08641
Merge branch 'main' into benbrown/nomoveproject
cwhitten 2ad27b3
Merge branch 'main' into benbrown/nomoveproject
benbrown 56d6dd7
fix lint
benbrown 2718fe4
Merge branch 'benbrown/nomoveproject' of https://github.com/microsoft…
benbrown e40000f
Merge branch 'main' into benbrown/nomoveproject
cwhitten File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,10 +9,12 @@ import * as rp from 'request-promise'; | |
| import archiver from 'archiver'; | ||
| import { AzureBotService } from '@azure/arm-botservice'; | ||
| import { TokenCredentials } from '@azure/ms-rest-js'; | ||
| import { composeRenderFunction } from '@uifabric/utilities'; | ||
|
|
||
| import { BotProjectDeployConfig, BotProjectDeployLoggerType } from './types'; | ||
| import { build, publishLuisToPrediction } from './luisAndQnA'; | ||
| import { AzurePublishErrors, createCustomizeError, stringifyError } from './utils/errorHandler'; | ||
| import { copyDir } from './utils/copyDir'; | ||
| import { KeyVaultApi } from './keyvaultHelper/keyvaultApi'; | ||
| import { KeyVaultApiConfig } from './keyvaultHelper/keyvaultApiConfig'; | ||
|
|
||
|
|
@@ -114,13 +116,24 @@ export class BotProjectDeploy { | |
| // this returns a pathToArtifacts where the deployable version lives. | ||
| const pathToArtifacts = await this.runtime.buildDeploy(this.projPath, project, settings, profileName); | ||
|
|
||
| // COPY MANIFESTS TO wwwroot/manifests | ||
| // eslint-disable-next-line security/detect-non-literal-fs-filename | ||
| if (await project.fileStorage.exists(path.join(pathToArtifacts, 'manifests'))) { | ||
| await copyDir( | ||
| path.join(pathToArtifacts, 'manifests'), | ||
| project.fileStorage, | ||
| path.join(pathToArtifacts, 'wwwroot', 'manifests'), | ||
| project.fileStorage | ||
| ); | ||
| } | ||
|
|
||
| // STEP 4: ZIP THE ASSETS | ||
| // Build a zip file of the project | ||
| this.logger({ | ||
| status: BotProjectDeployLoggerType.DEPLOY_INFO, | ||
| message: 'Creating build artifact...', | ||
| }); | ||
| await this.zipDirectory(pathToArtifacts, this.zipPath); | ||
| await this.zipDirectory(pathToArtifacts, settings, this.zipPath); | ||
| this.logger({ | ||
| status: BotProjectDeployLoggerType.DEPLOY_INFO, | ||
| message: 'Build artifact ready!', | ||
|
|
@@ -146,7 +159,7 @@ export class BotProjectDeploy { | |
| } | ||
| } | ||
|
|
||
| private async zipDirectory(source: string, out: string) { | ||
| private async zipDirectory(source: string, settings: any, out: string) { | ||
| console.log(`Zip the files in ${source} into a zip file ${out}`); | ||
| try { | ||
| const archive = archiver('zip', { zlib: { level: 9 } }); | ||
|
|
@@ -157,12 +170,15 @@ export class BotProjectDeploy { | |
| .glob('**/*', { | ||
| cwd: source, | ||
| dot: true, | ||
| ignore: ['**/code.zip'], // , 'node_modules/**/*' | ||
| ignore: ['**/code.zip', '**/settings/appsettings.json'], | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is where the magic happens as we exclude appsettings from the zip... |
||
| }) | ||
| .on('error', (err) => reject(err)) | ||
| .pipe(stream); | ||
|
|
||
| stream.on('close', () => resolve()); | ||
|
|
||
| // write the merged settings to the deploy artifact | ||
| archive.append(JSON.stringify(settings, null, 2), { name: 'settings/appsettings.json' }); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and then write it directly into the zip here! |
||
| archive.finalize(); | ||
| }); | ||
| } catch (error) { | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,7 @@ import path from 'path'; | |
|
|
||
| import formatMessage from 'format-message'; | ||
| import md5 from 'md5'; | ||
| import { copy, rmdir, emptyDir, readJson, pathExists, writeJson, mkdirSync, writeFileSync } from 'fs-extra'; | ||
| import { readJson, pathExists, writeJson } from 'fs-extra'; | ||
| import { Debugger } from 'debug'; | ||
| import { | ||
| IBotProject, | ||
|
|
@@ -26,7 +26,7 @@ import { BotProjectProvision } from './provision'; | |
| import { BackgroundProcessManager } from './backgroundProcessManager'; | ||
| import { ProvisionConfig } from './provision'; | ||
| import schema from './schema'; | ||
| import { stringifyError, AzurePublishErrors, createCustomizeError } from './utils/errorHandler'; | ||
| import { stringifyError } from './utils/errorHandler'; | ||
| import { ProcessStatus } from './types'; | ||
|
|
||
| // This option controls whether the history is serialized to a file between sessions with Composer | ||
|
|
@@ -52,12 +52,6 @@ interface PublishConfig { | |
| [key: string]: any; | ||
| } | ||
|
|
||
| interface ResourceType { | ||
| key: string; | ||
| // other keys TBD | ||
| [key: string]: any; | ||
| } | ||
|
|
||
| interface ProvisionHistoryItem { | ||
| profileName: string; | ||
| jobId: string; // use for unique each provision | ||
|
|
@@ -111,32 +105,6 @@ export default async (composer: IExtensionRegistration): Promise<void> => { | |
| this.bundleId = bundleId; | ||
| } | ||
|
|
||
| private baseRuntimeFolder = process.env.AZURE_PUBLISH_PATH || path.resolve(__dirname, `../../publishBots`); | ||
|
|
||
| /*******************************************************************************************************************************/ | ||
| /* These methods generate all the necessary paths to various files */ | ||
| /*******************************************************************************************************************************/ | ||
|
|
||
| private getRuntimeTemplateMode = (runtimeKey?: string): string => { | ||
| // The "mode" is kept the same for backward compatibility with original folder names | ||
| const { runtimeType } = parseRuntimeKey(runtimeKey); | ||
| return runtimeType === 'functions' ? 'azurefunctions' : 'azurewebapp'; | ||
| }; | ||
|
|
||
| // path to working folder containing all the assets | ||
| private getRuntimeFolder = (key: string) => { | ||
| return path.resolve(this.baseRuntimeFolder, `${key}`); | ||
| }; | ||
|
|
||
| // path to the runtime code inside the working folder | ||
| private getProjectFolder = (key: string, template: string) => { | ||
| return path.resolve(this.baseRuntimeFolder, `${key}/${template}`); | ||
| }; | ||
|
|
||
| // path to the declarative assets | ||
| private getBotFolder = (key: string, template: string) => | ||
| path.resolve(this.getProjectFolder(key, template), 'ComposerDialogs'); | ||
|
|
||
| /*******************************************************************************************************************************/ | ||
| /* These methods deal with the publishing history displayed in the Composer UI */ | ||
| /*******************************************************************************************************************************/ | ||
|
|
@@ -182,84 +150,6 @@ export default async (composer: IExtensionRegistration): Promise<void> => { | |
| /* These methods implement the publish actions */ | ||
| /*******************************************************************************************************************************/ | ||
| /** | ||
| * Prepare a bot to be built and deployed by copying the runtime and declarative assets into a temporary folder | ||
| * @param project | ||
| * @param settings | ||
| * @param srcTemplate | ||
| * @param resourcekey | ||
| */ | ||
| private init = async (project: any, srcTemplate: string, resourcekey: string, runtime: any) => { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This section of code is where we set up the temporary folders for handling non-ejected bots. BYE! |
||
| try { | ||
| // point to the declarative assets (possibly in remote storage) | ||
| const botFiles = project.getProject().files; | ||
|
|
||
| const mode = this.getRuntimeTemplateMode(runtime?.key); | ||
|
|
||
| // include both pre-release and release identifiers here | ||
| // TODO: eventually we can clean this up when the "old" runtime is deprecated | ||
| // (old runtime support is the else block below) | ||
| if (isUsingAdaptiveRuntime(runtime)) { | ||
| const buildFolder = this.getProjectFolder(resourcekey, mode); | ||
|
|
||
| // clean up from any previous deploys | ||
| await this.cleanup(resourcekey); | ||
|
|
||
| // copy bot and runtime into projFolder | ||
| await copy(srcTemplate, buildFolder); | ||
| } else { | ||
| const botFolder = this.getBotFolder(resourcekey, mode); | ||
| const runtimeFolder = this.getRuntimeFolder(resourcekey); | ||
|
|
||
| // clean up from any previous deploys | ||
| await this.cleanup(resourcekey); | ||
|
|
||
| // create the temporary folder to contain this project | ||
| mkdirSync(runtimeFolder, { recursive: true }); | ||
|
|
||
| // create the ComposerDialogs/ folder | ||
| mkdirSync(botFolder, { recursive: true }); | ||
|
|
||
| let manifestPath; | ||
| for (const file of botFiles) { | ||
| const pattern = /manifests\/[0-9A-z-]*.json/; | ||
| if (file.relativePath.match(pattern)) { | ||
| manifestPath = path.dirname(file.path); | ||
| } | ||
| // save bot files | ||
| const filePath = path.resolve(botFolder, file.relativePath); | ||
| if (!(await pathExists(path.dirname(filePath)))) { | ||
| mkdirSync(path.dirname(filePath), { recursive: true }); | ||
| } | ||
| writeFileSync(filePath, file.content); | ||
| } | ||
|
|
||
| // save manifest | ||
| runtime.setSkillManifest(runtimeFolder, project.fileStorage, manifestPath, project.fileStorage, mode); | ||
|
|
||
| // copy bot and runtime into projFolder | ||
| await copy(srcTemplate, runtimeFolder); | ||
| } | ||
| } catch (error) { | ||
| throw createCustomizeError( | ||
| AzurePublishErrors.INITIALIZE_ERROR, | ||
| `Error during init publish folder, ${error.message}` | ||
| ); | ||
| } | ||
| }; | ||
|
|
||
| /** | ||
| * Remove any previous version of a project's working files | ||
| * @param resourcekey | ||
| */ | ||
| private async cleanup(resourcekey: string) { | ||
| try { | ||
| const projFolder = this.getRuntimeFolder(resourcekey); | ||
| await emptyDir(projFolder); | ||
| await rmdir(projFolder); | ||
| } catch (error) { | ||
| this.logger('$O', error); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Take the project from a given folder, build it, and push it to Azure. | ||
|
|
@@ -294,7 +184,7 @@ export default async (composer: IExtensionRegistration): Promise<void> => { | |
| } | ||
| }, | ||
| accessToken: accessToken, | ||
| projPath: this.getProjectFolder(resourcekey, mode), | ||
| projPath: project.getRuntimePath(), | ||
| runtime: runtime, | ||
| }); | ||
|
|
||
|
|
@@ -311,8 +201,6 @@ export default async (composer: IExtensionRegistration): Promise<void> => { | |
| await this.updateHistory(botId, profileName, publishResultFromStatus(status).result); | ||
| // clean up the background process | ||
| BackgroundProcessManager.removeProcess(jobId); | ||
| // clean up post-deploy | ||
| await this.cleanup(resourcekey); | ||
| }; | ||
|
|
||
| /*******************************************************************************************************************************/ | ||
|
|
@@ -341,20 +229,6 @@ export default async (composer: IExtensionRegistration): Promise<void> => { | |
| // get the appropriate runtime template which contains methods to build and configure the runtime | ||
| const runtime = composer.getRuntimeByProject(project); | ||
| // set runtime code path as runtime template folder path | ||
| let runtimeCodePath = runtime.path; | ||
|
|
||
| // If the project is using an "ejected" runtime, use that version of the code instead of the built-in template | ||
| if ( | ||
| project.settings && | ||
| project.settings.runtime && | ||
| project.settings.runtime.customRuntime === true && | ||
| project.settings.runtime.path | ||
| ) | ||
| runtimeCodePath = project.getRuntimePath(); // get computed absolute path | ||
|
|
||
| // Prepare the temporary project | ||
| // this writes all the settings to the root settings/appsettings.json file | ||
| await this.init(project, runtimeCodePath, resourcekey, runtime); | ||
|
|
||
| // Merge all the settings | ||
| // this combines the bot-wide settings, the environment specific settings, and 2 new fields needed for deployed bots | ||
|
|
@@ -390,7 +264,6 @@ export default async (composer: IExtensionRegistration): Promise<void> => { | |
| publishResultFromStatus(BackgroundProcessManager.getStatus(jobId)).result | ||
| ); | ||
| BackgroundProcessManager.removeProcess(jobId); | ||
| this.cleanup(resourcekey); | ||
| } | ||
| }; | ||
|
|
||
|
|
@@ -567,8 +440,6 @@ export default async (composer: IExtensionRegistration): Promise<void> => { | |
| const status = publishResultFromStatus(BackgroundProcessManager.getStatus(jobId)); | ||
| await this.updateHistory(botId, profileName, status.result); | ||
| BackgroundProcessManager.removeProcess(jobId); | ||
| this.cleanup(resourcekey as string); | ||
|
|
||
| return status; | ||
| } | ||
| }; | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
| /* eslint-disable security/detect-non-literal-fs-filename */ | ||
|
|
||
| import Path from 'path'; | ||
|
|
||
| import { IFileStorage } from './interface'; | ||
|
|
||
| export async function copyDir( | ||
| srcDir: string, | ||
| srcStorage: IFileStorage, | ||
| dstDir: string, | ||
| dstStorage: IFileStorage, | ||
| pathsToExclude?: Set<string> | ||
| ) { | ||
| if (!(await srcStorage.exists(srcDir)) || !(await srcStorage.stat(srcDir)).isDir) { | ||
| throw new Error(`No such dir ${srcDir}}`); | ||
| } | ||
|
|
||
| if (!(await dstStorage.exists(dstDir))) { | ||
| await dstStorage.mkDir(dstDir, { recursive: true }); | ||
| } | ||
|
|
||
| const paths = await srcStorage.readDir(srcDir); | ||
|
|
||
| for (const path of paths) { | ||
| const srcPath = Path.join(srcDir, path); | ||
| if (pathsToExclude?.has(srcPath)) { | ||
| continue; | ||
| } | ||
| const dstPath = Path.join(dstDir, path); | ||
|
|
||
| if ((await srcStorage.stat(srcPath)).isFile) { | ||
| // copy files | ||
| const content = await srcStorage.readFile(srcPath); | ||
| await dstStorage.writeFile(dstPath, content); | ||
| } else { | ||
| // recursively copy dirs | ||
| await copyDir(srcPath, srcStorage, dstPath, dstStorage, pathsToExclude); | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@luhan2017 FYI for the skill manifest scenarios you are working on