Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for swa-cli.config.json file #294

Merged
merged 3 commits into from
Sep 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions cypress/fixtures/static/swa-cli.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "../../../schema/swa-cli.config.schema.json",
"configurations": {
"app": {
"context": "./cypress/fixtures/static",
"apiLocation": "./cypress/fixtures/api",
"port": 1234,
"devserverTimeout": 10000,
"verbose": "silly"
},
"app2": {
"context": "./cypress/fixtures/static",
"apiLocation": "./cypress/fixtures/api",
"port": 4321,
"devserverTimeout": 10000,
"verbose": "silly"
}
}
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
"description": "Azure Static Web Apps CLI",
"scripts": {
"start": "node ./dist/cli/bin.js start ./cypress/fixtures/static --api=./cypress/fixtures/api --port 1234 --devserver-timeout 10000 --verbose silly",
"start:config": "node ./dist/cli/bin.js --config ./cypress/fixtures/static/swa-cli.config.json start app",
"prestart": "npm run build",
"pretest": "npm run build",
"test": "jest --detectOpenHandles --silent --verbose",
"e2e": "start-server-and-test start http://0.0.0.0:1234 cy:run",
"e2e:config": "start-server-and-test start:config http://0.0.0.0:1234 cy:run",
"cy:run": "cypress run",
"cy:open": "cypress open",
"build": "tsc",
Expand Down
93 changes: 78 additions & 15 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,21 +165,84 @@ swa start http://localhost:3000 --swa-config-location ./my-app-source

If you need to override the default values, provide the following options:

| Options | Description | Default | Example |
| -------------------------------- | ------------------------------------------------------- | --------- | ---------------------------------------------------- |
| `--app-location` | set location for the static app source code | `./` | `--app-location="./my-project"` |
| `--app, --app-artifact-location` | set app artifact (dist) folder or dev server | `./` | `--app="./my-dist"` or `--app=http://localhost:4200` |
| `--api, --api-artifact-location` | set the API folder or dev server | | `--api="./api"` or `--api=http://localhost:8083` |
| `--swa-config-location` | set the directory of the staticwebapp.config.json file. | | `--swa-config-location=./my-project-folder` |
| `--api-port` | set the API server port | `7071` | `--api-port=8082` |
| `--host` | set the emulator host address | `0.0.0.0` | `--host=192.168.68.80` |
| `--port` | set the emulator port value | `4280` | `--port=8080` |
| `--ssl` | serving the app and API over HTTPS (default: false) | `false` | `--ssl` or `--ssl=true` |
| `--ssl-cert` | SSL certificate to use for serving HTTPS | | `--ssl-cert="/home/user/ssl/example.crt"` |
| `--ssl-key` | SSL key to use for serving HTTPS | | `--ssl-key="/home/user/ssl/example.key"` |
| `--run` | Run a command at startup | | `--run="cd app & npm start"` |
| `--devserver-timeout` | The time to wait(in ms) for the dev server to start | 30000 | `--devserver-timeout=60000` |
| `--func-args` | Additional arguments to pass to `func start` | | `--func-args="--javascript"` |
| Options | Description | Default | Example |
| -------------------------------- | ------------------------------------------------------- | ----------------------- | ---------------------------------------------------- |
| `--app-location` | set location for the static app source code | `./` | `--app-location="./my-project"` |
| `--app, --app-artifact-location` | set app artifact (dist) folder or dev server | `./` | `--app="./my-dist"` or `--app=http://localhost:4200` |
| `--api, --api-artifact-location` | set the API folder or dev server | | `--api="./api"` or `--api=http://localhost:8083` |
| `--swa-config-location` | set the directory of the staticwebapp.config.json file. | | `--swa-config-location=./my-project-folder` |
| `--api-port` | set the API server port | `7071` | `--api-port=8082` |
| `--host` | set the emulator host address | `0.0.0.0` | `--host=192.168.68.80` |
| `--port` | set the emulator port value | `4280` | `--port=8080` |
| `--ssl` | serving the app and API over HTTPS (default: false) | `false` | `--ssl` or `--ssl=true` |
| `--ssl-cert` | SSL certificate to use for serving HTTPS | | `--ssl-cert="/home/user/ssl/example.crt"` |
| `--ssl-key` | SSL key to use for serving HTTPS | | `--ssl-key="/home/user/ssl/example.key"` |
| `--run` | Run a command at startup | | `--run="cd app & npm start"` |
| `--devserver-timeout` | The time to wait(in ms) for the dev server to start | 30000 | `--devserver-timeout=60000` |
| `--func-args` | Additional arguments to pass to `func start` | | `--func-args="--javascript"` |
| `--config` | Path to swa-cli.config.json file to use. | `./swa-cli.config.json` | `--config ./config/swa-cli.config.json` |
| `--print-config` | Print all resolved options. Useful for debugging. | | `--print-config` or `--print-config=true` |

## swa-cli.config.json file

The CLI can also load options from a `swa-cli.config.json` file.

```json
{
"configurations": {
"app": {
"context": "http://localhost:3000",
"apiLocation": "api",
"run": "npm run start",
"swaConfigLocation": "./my-app-source"
}
}
}
```

If only a single configuration is present in the `swa-cli.config.json` file, running `swa start` will use it by default. If options are loaded from a config file, no options passed in via command line will be respected. For example `swa start app --ssl=true`. The `--ssl=true` option will not be picked up by the CLI.

### Example

We can simplify these commands by putting the options into a config file.

```bash
# static configuration
swa start ./my-dist --swa-config-location ./my-app-source

# devserver configuration
swa start http://localhost:3000 --swa-config-location ./my-app-source
```

```json
{
"configurations": {
"static": {
"context": "./my-dist",
"swaConfigLocation": "./my-app-source"
},
"devserver": {
"context": "http://localhost:3000",
"swaConfigLocation": "./my-app-source"
}
}
}
```

These configurations can be run with `swa start static` and `swa start devserver`.

### Validation

You can validate your `swa-cli.config.json` with a JSON Schema like so:

```json
{
"$schema": "https://raw.githubusercontent.com/Azure/static-web-apps-cli/main/schema/swa-cli.config.schema.json",
"configurations": {
...
}
}
```

## Local authentication & authorization emulation

Expand Down
100 changes: 100 additions & 0 deletions schema/swa-cli.config.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"configurations": {
"additionalProperties": {
"allOf": [
{
"properties": {
"apiLocation": {
"description": "API folder or Azure Functions emulator address",
"type": "string"
},
"apiPort": {
"description": "API backend port",
"type": "number"
},
"apiPrefix": {
"enum": ["api"],
"type": "string"
},
"appArtifactLocation": {
"description": "Location of the build output directory relative to the appLocation",
"type": "string"
},
"appLocation": {
"description": "Location for the static app source code",
"type": "string"
},
"build": {
"type": "boolean"
},
"customUrlScheme": {
"type": "string"
},
"devserverTimeout": {
"description": "Time to wait(in ms) for the dev server to start",
"type": "number"
},
"host": {
"description": "CLI host address",
"type": "string"
},
"overridableErrorCode": {
"items": {
"type": "number"
},
"type": "array"
},
"port": {
"description": "set the cli port",
"type": "number"
},
"run": {
"description": "Run a command at startup",
"type": "string"
},
"ssl": {
"description": "Serve the app and API over HTTPS",
"type": "boolean"
},
"sslCert": {
"description": "SSL certificate (.crt) to use for serving HTTPS",
"type": "string"
},
"sslKey": {
"description": "SSL key (.key) to use for serving HTTPS",
"type": "string"
},
"swaConfigFilename": {
"enum": ["staticwebapp.config.json"],
"type": "string"
},
"swaConfigFilenameLegacy": {
"enum": ["routes.json"],
"type": "string"
},
"swaConfigLocation": {
"type": "string"
},
"verbose": {
"type": "string"
}
},
"type": "object"
},
{
"properties": {
"context": {
"type": "string"
}
},
"type": "object"
}
]
},
"type": "object"
}
},
"type": "object"
}
34 changes: 25 additions & 9 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import program, { Option } from "commander";
import path from "path";
import { DEFAULT_CONFIG } from "../config";
import { parsePort } from "../core";
import { logger, parsePort } from "../core";
import { parseDevserverTimeout } from "../core";
import { start } from "./commands/start";
import updateNotifier from "update-notifier";
import { getFileOptions, swaCliConfigFilename } from "../core/utils/cli-config";
const pkg = require("../../package.json");

export const defaultStartContext = `.${path.sep}`;

export async function run(argv?: string[]) {
// Once a day, check for updates
updateNotifier({ pkg }).notify();
Expand All @@ -18,8 +21,10 @@ export async function run(argv?: string[]) {

// SWA config
.option("--verbose [prefix]", "enable verbose output. Values are: silly,info,log,silent", DEFAULT_CONFIG.verbose)
.addHelpText("after", "\nDocumentation:\n https://aka.ms/swa/cli-local-development\n")

.addHelpText("after", "\nDocumentation:\n https://aka.ms/swa/cli-local-development\n");
.option("--config <path>", "Path to swa-cli.config.json file to use.", path.relative(process.cwd(), swaCliConfigFilename))
.option("--print-config", "Print all resolved options.", false);

program
.command("start [context]")
Expand Down Expand Up @@ -60,20 +65,31 @@ export async function run(argv?: string[]) {
.option("--func-args <funcArgs>", "pass additional arguments to the func start command")

.action(async (context: string = `.${path.sep}`, options: SWACLIConfig) => {
options = {
...options,
verbose: cli.opts().verbose,
};
const verbose = cli.opts().verbose;

// make sure the start command gets the right verbosity level
process.env.SWA_CLI_DEBUG = options.verbose;
if (options.verbose?.includes("silly")) {
process.env.SWA_CLI_DEBUG = verbose;
if (verbose?.includes("silly")) {
// when silly level is set,
// propagate debugging level to other tools using the DEBUG environment variable
process.env.DEBUG = "*";
}

await start(context, options);
const fileOptions = await getFileOptions(context, cli.opts().config);

options = {
...options,
...fileOptions,
verbose,
};

if (cli.opts().printConfig) {
logger.log("", "swa");
logger.log("Options: ", "swa");
logger.log({ ...DEFAULT_CONFIG, ...options }, "swa");
}

await start(fileOptions.context ?? context, options);
})

.addHelpText(
Expand Down
87 changes: 87 additions & 0 deletions src/core/utils/cli-config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import mockFs from "mock-fs";
import { defaultStartContext } from "../../cli";

import { getFileOptions } from "./cli-config";

const mockConfig1 = {
$schema: "../../../schema/swa-cli.config.schema.json",
configurations: {
app: {
context: "./cypress/fixtures/static",
apiLocation: "./cypress/fixtures/api",
port: 1111,
devServerTimeout: 10000,
verbose: "silly",
},
app2: {
context: "./cypress/fixtures/static",
apiLocation: "./cypress/fixtures/api",
port: 2222,
devServerTimeout: 10000,
verbose: "silly",
},
},
};

const mockConfig2 = {
$schema: "../../../schema/swa-cli.config.schema.json",
configurations: {
app: {
context: "./cypress/fixtures/static",
apiLocation: "./cypress/fixtures/api",
port: 3333,
devServerTimeout: 10000,
verbose: "silly",
},
},
};

describe("getFileOptions()", () => {
afterEach(() => {
mockFs.restore();
});

const mockConfig = (config: any = mockConfig1) => {
mockFs({
"swa-cli.config.json": JSON.stringify(config),
});
};

mockFs({
"swa-cli.config.json": ``,
});

it("Should return empty object if not found", async () => {
mockConfig();
expect(await getFileOptions("app", "")).toStrictEqual({});
});

it("Should return empty object if config name is not found", async () => {
mockConfig();
expect(await getFileOptions("configName", "swa-cli.config.json")).toStrictEqual({});
});

it("Should return proper config options", async () => {
mockConfig();
expect(await getFileOptions("app", "swa-cli.config.json")).toStrictEqual(mockConfig1.configurations.app);
});

it("Should only return a default config if there is only one config", async () => {
mockConfig();
expect(await getFileOptions(defaultStartContext, "swa-cli.config.json")).toStrictEqual({});
});

it("Should return a default config", async () => {
mockConfig(mockConfig2);
expect(await getFileOptions(defaultStartContext, "swa-cli.config.json")).toStrictEqual(mockConfig2.configurations.app);
});

it("Should return empty object if config file is not found", async () => {
expect(await getFileOptions(defaultStartContext, "swa-cli.config.json")).toStrictEqual({});
});

it("Should return proper config without path specified", async () => {
mockConfig(mockConfig1);
expect(await getFileOptions("app", "swa-cli.config.json")).toStrictEqual(mockConfig1.configurations.app);
});
});
Loading