зеркало из
1
0
Форкнуть 0

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

* Add support for swa-cli.config.json file

* Document swa-cli.config.json in readme
This commit is contained in:
Alex Weininger 2021-09-27 00:26:46 -07:00 коммит произвёл GitHub
Родитель 822be3486e
Коммит 1a3f3d299c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 382 добавлений и 27 удалений

Просмотреть файл

@ -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"
}
}
}

Просмотреть файл

@ -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",

Просмотреть файл

@ -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

Просмотреть файл

@ -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"
}

Просмотреть файл

@ -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();
@ -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]")
@ -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(

Просмотреть файл

@ -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);
});
});

Просмотреть файл

@ -0,0 +1,53 @@
import * as path from "path";
import fs, { promises as fsPromises } from "fs";
import { logger } from "./logger";
import { defaultStartContext } from "../../cli";
const { readFile } = fsPromises;
export const swaCliConfigFilename = "swa-cli.config.json";
export async function getFileOptions(context: string, configFilePath: string): Promise<SWACLIConfig & { context?: string }> {
if (!fs.existsSync(configFilePath)) {
return {};
}
const cliConfig = await tryParseSwaCliConfig(configFilePath);
if (!cliConfig.configurations) {
logger.warn(`${swaCliConfigFilename} is missing the "configurations" property. No options will be loaded.`);
return {};
}
const hasOnlyOneConfig = Object.entries(cliConfig.configurations).length === 1;
if (hasOnlyOneConfig && context === defaultStartContext) {
const [configName, config] = Object.entries(cliConfig.configurations)[0];
printConfigMsg(configName, configFilePath);
return { context: `.${path.sep}`, ...config };
}
const config = cliConfig.configurations?.[context];
if (config) {
printConfigMsg(context, configFilePath);
return { context: `.${path.sep}`, ...config };
}
return {};
}
async function tryParseSwaCliConfig(file: string) {
try {
return JSON.parse((await readFile(file)).toString("utf-8")) as SWACLIConfigFile;
} catch (e) {
logger.error(`Error parsing swa-cli.config.json file at ${file}`);
if (e instanceof Error) {
logger.error(e);
}
return {};
}
}
function printConfigMsg(name: string, file: string) {
logger.log(`Using configuration "${name}" from file:`, "swa");
logger.log(`\t${path.resolve(process.cwd(), file)}`, "swa");
logger.log("", "swa");
logger.log(`Options passed in via CLI will be overridden by options in file.`, "swa");
}

Просмотреть файл

@ -42,7 +42,14 @@ export const logger = {
log(data: string | object, prefix: string | null = null) {
this.silly(data, prefix, "log", chalk.reset);
},
/**
* Print information data.
* @param data Either a string or an object to be printed.
* @param prefix (optional) A prefix to prepend to the printed message.
*/
warn(data: string | object, prefix: string | null = null) {
this.silly(data, prefix, "log", chalk.yellow);
},
/**
* Print error data and optionally exit the CLI instance.
* @param data Either a string or an object to be printed.

12
src/swa.d.ts поставляемый
Просмотреть файл

@ -57,7 +57,7 @@ declare type GithubActionWorkflow = {
files?: string[];
};
declare type SWACLIConfig = GithubActionWorkflow & {
declare type SWACLIOptions = {
port?: number;
host?: string;
apiPort?: number;
@ -68,7 +68,7 @@ declare type SWACLIConfig = GithubActionWorkflow & {
swaConfigFilename?: "staticwebapp.config.json";
swaConfigFilenameLegacy?: "routes.json";
app?: string;
api?: string;
apiLocation?: string;
build?: boolean;
verbose?: string;
run?: string;
@ -79,6 +79,8 @@ declare type SWACLIConfig = GithubActionWorkflow & {
funcArgs?: string;
};
declare type SWACLIConfig = SWACLIOptions & GithubActionWorkflow;
declare type ResponseOptions = {
[key: string]: any;
};
@ -135,3 +137,9 @@ declare type SWAConfigFile = {
};
declare type DebugFilterLevel = "silly" | "silent" | "log" | "info" | "error";
declare type SWACLIConfigFile = {
configurations?: {
[name: string]: SWACLIOptions & { context?: string };
};
};