From 53381e4d46e79716906035a58611582736c500fd Mon Sep 17 00:00:00 2001 From: Wassim Chegham Date: Fri, 15 Apr 2022 20:46:02 +0200 Subject: [PATCH] refactor: clean up cli context --- readme.md | 81 +++++++++++++++---------------- src/cli/commands/deploy.ts | 40 +++++++++++---- src/cli/commands/init.ts | 3 +- src/cli/commands/start.ts | 23 ++++++--- src/core/utils/user-config.ts | 16 ++++-- src/core/utils/workflow-config.ts | 32 +++++++++--- src/msha/server.ts | 4 +- 7 files changed, 129 insertions(+), 70 deletions(-) diff --git a/readme.md b/readme.md index 66f17205..56f15da2 100644 --- a/readme.md +++ b/readme.md @@ -160,9 +160,7 @@ A **Deployment Token** is required in order to make a deployment! Read the steps ### Deployment token -Before deploying your application to Azure Static Web Apps, you need to create a new application instance. This is done by following the instructions in the [Azure Static Web Apps documentation](https://docs.microsoft.com/en-us/azure/static-web-apps/get-started-portal). - -Once your application instance is ready, you can get a deployment token from: +The CLI supports Deployment token. This is usually useful when deploying from a CI/CD environment. You can get a deployment token either from: - The [Azure portal](https://portal.azure.com/): **Home → Static Web App → Your Instance → Overview → Manage deployment token** @@ -172,7 +170,13 @@ Once your application instance is ready, you can get a deployment token from: az staticwebapp secrets list --name --query "properties.apiKey" ``` -You can then use that value with the `--deployment-token `, or you can create an environment variable called `SWA_CLI_DEPLOYMENT_TOKEN` and set it to the deployment token. Read the next section for more details. +- If you are using the [Azure Static Web Apps CLI (this project)](aka.ms/swa/cli-local-development), you can get the deployment token of your project using the following command: + +```bash +swa deploy --print-token +``` + +You can then use that value with the `--deployment-token ` (e.g. from a CI/CD environment), or you can create an environment variable called `SWA_CLI_DEPLOYMENT_TOKEN` and set it to the deployment token. Read the next section for more details. **IMPORTANT:** Don't store the deployment token in a public repository. It should be kept secret! @@ -185,10 +189,11 @@ You can deploy a front-end application (without an API) to Azure Static Web Apps **Option 1:** From build folder you would like to deploy, run the deploy command: ```bash -swa deploy --deployment-token +cd build/ +swa deploy ``` -> Note: the current folder must contain the static content of your app to be deployed! +> Note: the "build" folder must contain the static content of your app to be deployed! **Option 2:** You can also deploy a specific folder: @@ -197,7 +202,7 @@ swa deploy --deployment-token 2. Deploy your app: ```bash -swa deploy ./my-dist --deployment-token +swa deploy ./my-dist ``` ### Deploy a front-end app with an API @@ -219,7 +224,7 @@ To deploy both the front-end app and an API to Azure Static Web Apps, use the fo 3. Deploy your app: ```bash -swa deploy ./my-dist --api-location ./api --deployment-token +swa deploy ./my-dist --api-location ./api ``` ### Deploy a Blazor app @@ -235,41 +240,26 @@ dotnet publish -c Release 2. From the root of your project, run the deploy command: ```bash -swa deploy ./Client/bin/Release/net6.0/publish/wwwroot --api-location ./Api --deployment-token +swa deploy ./Client/bin/Release/net6.0/publish/wwwroot --api-location ./Api ``` -3.a (Optional) create a `swa-cli.config.json` file at the root of your project with the following content: - -```json -{ - "configurations": { - "deploy": { - "context": "./", - "apiLocation": "./Api", - "outputLocation": "Client/bin/Release/net6.0/publish/wwwroot" - } - } -} -``` - -3.b Deploy your app using the configuration file: - -```bash -swa deploy --deployment-token -``` - -### Deploy using the swa-cli.config.json +### Deploy using the `swa-cli.config.json` If you are using a [`swa-cli.config.json`](#swa-cli.config.json) configuration file in your project and have a single configuration entry, for example: ```json { "configurations": { - "app": { + "my-app": { "appLocation": "./", - "context": "./", - "outputLocation": "./front-end", - "apiLocation": "./api" + "apiLocation": "api", + "outputLocation": "frontend", + "start": { + "context": "frontend" + }, + "deploy": { + "context": "frontend" + } } } } @@ -288,7 +278,7 @@ swa deploy If you have multiple configuration entries, you can provide the entry ID to specify which one to use: ```bash -swa deploy otherapp +swa deploy my-otherapp ``` ## Use a runtime configuration file (staticwebapp.config.json) @@ -332,6 +322,10 @@ If you need to override the default values for the `swa` command, you can provid | `--print-config` | Print all resolved options | `false` | `--print-config` or `--print-config=true` | | `--swa-config-location` | The directory where the `staticwebapp.config.json` file is located | `./` | `--swa-config-location=./app` | +### Subcommand `swa login` options + +TODO + ### Subcommand `swa start` options If you need to override the default values for the `swa start` subcommand, you can provide the following options: @@ -355,13 +349,16 @@ If you need to override the default values for the `swa start` subcommand, you c If you need to override the default values for the `swa deploy` subcommand, you can provide the following options: -| Option | Description | Default | Example | -| -------------------- | -------------------------------------------------------------- | --------- | ----------------------------------------- | -| `--api-location` | The folder containing the source code of the API application | `./api` | `--api-location="./api"` | -| `--deployment-token` | The secret toekn used to authenticate with the Static Web Apps | | `--deployment-token="123"` | -| `--dry-run` | Simulate a deploy process without actually running it | `false` | `--dry-run` | -| `--print-token` | print the deployment token | `false` | `--print-token` | -| `--env` | the type of deployment environment where to deploy the project | `preview` | `--env="production"` or `--env="preview"` | +| Option | Description | Default | Example | +| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------------------------------------- | +| `--api-location` | The folder containing the source code of the API application | `./api` | `--api-location="./api"` | +| `--deployment-token` | The secret toekn used to authenticate with the Static Web Apps | | `--deployment-token="123"` | +| `--dry-run` | Simulate a deploy process without actually running it | `false` | `--dry-run` | +| `--print-token` | print the deployment token | `false` | `--print-token` | +| `--env` | the type of deployment environment where to deploy the project | `preview` | `--env="production"` or `--env="preview"` | +| `--print-token` | Print the deployment token. Usefull when using `--deployment-token` on CI/CD
Note: this command does not run the deployment process. | `false` | `--print-token` | + +The deploy command does also support the same options as the `swa login` command. diff --git a/src/cli/commands/deploy.ts b/src/cli/commands/deploy.ts index 742a0005..acb34d7d 100644 --- a/src/cli/commands/deploy.ts +++ b/src/cli/commands/deploy.ts @@ -16,7 +16,7 @@ import { } from "../../core"; import { chooseOrCreateProjectDetails, getStaticSiteDeployment } from "../../core/account"; import { cleanUp, getDeployClientPath } from "../../core/deploy-client"; -import { getSwaEnvList, swaCLIEnv } from "../../core/env"; +import { swaCLIEnv } from "../../core/env"; import { addSharedLoginOptionsToCommand, login } from "./login"; const packageInfo = require(path.join(__dirname, "..", "..", "..", "package.json")); @@ -60,10 +60,12 @@ Examples: addSharedLoginOptionsToCommand(deployCommand); } -export async function deploy(deployContext: string, options: SWACLIConfig) { +export async function deploy(_deployContext: string | undefined, options: SWACLIConfig) { const { SWA_CLI_DEPLOYMENT_TOKEN, SWA_CLI_DEBUG } = swaCLIEnv(); const isVerboseEnabled = SWA_CLI_DEBUG === "silly"; + // TODO: get rid of _deployContext + if (options.dryRun) { logger.warn("***********************************************************************"); logger.warn("* WARNING: Running in dry run mode. This project will not be deployed *"); @@ -71,14 +73,34 @@ export async function deploy(deployContext: string, options: SWACLIConfig) { logger.warn(""); } - const frontendFolder = path.resolve(process.cwd(), deployContext); + // make sure outputLocation is set + if (options.outputLocation) { + options.outputLocation = path.resolve(options.outputLocation); + } + + // make sure appLocation is set + if (!options.appLocation) { + options.appLocation = path.resolve(process.cwd()); + } + + // make sure outputLocation is set + if (!options.outputLocation) { + options.outputLocation = path.resolve(process.cwd()); + } + + // if folder exists, deploy from a specific build folder (outputLocation), relative to appLocation + if (!fs.existsSync(options.outputLocation)) { + logger.error(`The folder "${options.outputLocation}" is not found. Exit.`, true); + return; + } + logger.log(`Deploying front-end files from folder:`); - logger.log(` ${chalk.green(frontendFolder)}`); + logger.log(` ${chalk.green(options.outputLocation)}`); logger.log(``); // if --api-location is provided, use it as the api folder if (options.apiLocation) { - const userApiFolder = path.resolve(process.cwd(), options.apiLocation!); + const userApiFolder = path.resolve(options.appLocation as string, options.apiLocation!); if (!fs.existsSync(userApiFolder)) { logger.error(`The provided API folder ${userApiFolder} does not exist. Abort.`, true); return; @@ -94,6 +116,7 @@ export async function deploy(deployContext: string, options: SWACLIConfig) { if (fs.existsSync(defaultApiFolder)) { logger.warn( `An API folder was found at ".${ + // TODO: should handle ./Api and ./api path.sep + path.basename(defaultApiFolder) }" but the --api-location option was not provided. The API will not be deployed.\n` ); @@ -113,9 +136,6 @@ export async function deploy(deployContext: string, options: SWACLIConfig) { logger.silly(`No deployment token found. Trying interactive login...`); try { - logger.silly(options); - logger.silly(getSwaEnvList()); - const { credentialChain, subscriptionId } = await login({ ...options, }); @@ -220,11 +240,11 @@ export async function deploy(deployContext: string, options: SWACLIConfig) { const deployClientEnv: StaticSiteClientEnv = { DEPLOYMENT_ACTION: options.dryRun ? "close" : "upload", DEPLOYMENT_PROVIDER: `swa-cli-${packageInfo.version}`, - REPOSITORY_BASE: deployContext, + REPOSITORY_BASE: options.appLocation, SKIP_APP_BUILD: "true", SKIP_API_BUILD: "true", DEPLOYMENT_TOKEN: deploymentToken, - APP_LOCATION: deployContext, + APP_LOCATION: options.appLocation, OUTPUT_LOCATION: options.outputLocation, API_LOCATION: options.apiLocation, VERBOSE: isVerboseEnabled ? "true" : "false", diff --git a/src/cli/commands/init.ts b/src/cli/commands/init.ts index f9ca6cdd..3acfe94c 100644 --- a/src/cli/commands/init.ts +++ b/src/cli/commands/init.ts @@ -59,6 +59,7 @@ export async function init(name: string | undefined, options: SWACLIConfig, show }; projectConfig = await promptConfigSettings(disablePrompts, projectConfig); + logger.silly(projectConfig); // printFrameworkConfig(projectConfig); @@ -107,7 +108,7 @@ function convertToCliConfig(config: FrameworkConfig): SWACLIConfig { apiBuildCommand: config.apiBuildCommand, run: config.devServerCommand, start: { - context: config.devServerUrl || config.appLocation, + context: config.devServerUrl || config.outputLocation, }, deploy: { context: config.outputLocation, diff --git a/src/cli/commands/start.ts b/src/cli/commands/start.ts index 8689be10..dd60439c 100644 --- a/src/cli/commands/start.ts +++ b/src/cli/commands/start.ts @@ -105,16 +105,19 @@ export async function start(startContext: string | undefined, options: SWACLICon logger.silly(`Resolved port number: ${resolvedPortNumber}`); options.port = resolvedPortNumber; - // start context should never be undefined but we'll check anyway! + logger.silly(`Resolving outputLocation...`); + + // start context should never be undefined (will default to ./) but we'll check anyway! // if the user didn't provide a context, use the current directory if (!startContext) { - startContext = DEFAULT_CONFIG.outputLocation; + startContext = options.outputLocation; } else { if (isHttpUrl(startContext)) { useAppDevServer = startContext; options.outputLocation = useAppDevServer; } else { - let outputLocationAbsolute = path.resolve(options.appLocation as string, startContext); + let outputLocationAbsolute = path.resolve(options.appLocation as string, (options.outputLocation as string) || startContext); + // if folder exists, start the emulator from a specific build folder (outputLocation), relative to appLocation if (fs.existsSync(outputLocationAbsolute)) { options.outputLocation = outputLocationAbsolute; @@ -129,6 +132,9 @@ export async function start(startContext: string | undefined, options: SWACLICon } } + logger.silly(`Resolved outputLocation:`); + logger.silly(` ${options.outputLocation}`); + if (options.apiLocation) { // resolves to the absolute path of the apiLocation let apiLocationAbsolute = path.resolve(options.appLocation as string, options.apiLocation); @@ -163,6 +169,9 @@ export async function start(startContext: string | undefined, options: SWACLICon userWorkflowConfig = readWorkflowFile({ userWorkflowConfig, }); + + logger.silly(`User workflow config:`); + logger.silly(userWorkflowConfig!); } catch (err) { logger.warn(``); logger.warn(`Error reading workflow configuration:`); @@ -225,7 +234,7 @@ export async function start(startContext: string | undefined, options: SWACLICon days: 365, commonName: options.host, organization: `Azure Static Web Apps CLI ${packageInfo.version}`, - organizationUnit: "Engineering", + organizationUnit: "Azure Engineering", emailAddress: `secure@microsoft.com`, }); options.sslCert = pemFilepath; @@ -315,8 +324,10 @@ export async function start(startContext: string | undefined, options: SWACLICon await result .then( - (code: CloseEvent[]) => { - logger.silly(`SWA emulator exited with code ${code.values().next().value}`); + (errorEvent: CloseEvent[]) => { + const killedCommand = errorEvent.filter((event) => event.killed).pop(); + const exitCode = killedCommand?.exitCode; + logger.silly(`SWA emulator exited with code ${exitCode}`); process.exit(); }, (errorEvent: CloseEvent[]) => { diff --git a/src/core/utils/user-config.ts b/src/core/utils/user-config.ts index d547167c..f583da00 100644 --- a/src/core/utils/user-config.ts +++ b/src/core/utils/user-config.ts @@ -93,8 +93,11 @@ export function validateUserWorkflowConfig(userWorkflowConfig: Partial { + logger.silly(getSwaEnvList()); + let socketConnection: net.Socket | undefined; const localIpAdress = await internalIp.v4();