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:
Родитель
822be3486e
Коммит
1a3f3d299c
|
@ -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",
|
||||
|
|
93
readme.md
93
readme.md
|
@ -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.
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче