@mittwald/cli 1.13.4 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/context/get.js +6 -1
- package/dist/lib/apiutil/api_baseurl.d.ts +2 -0
- package/dist/lib/apiutil/api_baseurl.js +17 -0
- package/dist/lib/basecommands/BaseCommand.js +17 -0
- package/dist/lib/context/Context.js +2 -0
- package/dist/lib/context/DotfileContextProvider.d.ts +26 -0
- package/dist/lib/context/DotfileContextProvider.js +63 -0
- package/package.json +1 -1
|
@@ -18,6 +18,8 @@ const ContextSourceValue = ({ source }) => {
|
|
|
18
18
|
return (_jsx(ContextSourceKnownValue, { name: "terraform state file", source: source, relative: true }));
|
|
19
19
|
case "ddev":
|
|
20
20
|
return (_jsx(ContextSourceKnownValue, { name: "DDEV configuration", source: source, relative: true }));
|
|
21
|
+
case "dotfile":
|
|
22
|
+
return (_jsx(ContextSourceKnownValue, { name: ".mw-context.json", source: source, relative: true }));
|
|
21
23
|
default:
|
|
22
24
|
return _jsx(ContextSourceUnknown, {});
|
|
23
25
|
}
|
|
@@ -37,6 +39,7 @@ const GetContext = ({ ctx }) => {
|
|
|
37
39
|
const values = {};
|
|
38
40
|
let hasTerraformSource = false;
|
|
39
41
|
let hasDDEVSource = false;
|
|
42
|
+
let hasDotfileSource = false;
|
|
40
43
|
for (const key of [
|
|
41
44
|
"project-id",
|
|
42
45
|
"server-id",
|
|
@@ -51,6 +54,7 @@ const GetContext = ({ ctx }) => {
|
|
|
51
54
|
hasTerraformSource =
|
|
52
55
|
hasTerraformSource || value.source.type === "terraform";
|
|
53
56
|
hasDDEVSource = hasDDEVSource || value.source.type === "ddev";
|
|
57
|
+
hasDotfileSource = hasDotfileSource || value.source.type === "dotfile";
|
|
54
58
|
}
|
|
55
59
|
else {
|
|
56
60
|
rows[`--${key}`] = _jsx(Value, { notSet: true });
|
|
@@ -59,10 +63,11 @@ const GetContext = ({ ctx }) => {
|
|
|
59
63
|
if (renderAsJson) {
|
|
60
64
|
return _jsx(RenderJson, { name: "context", data: values });
|
|
61
65
|
}
|
|
62
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(SingleResult, { title: "Current CLI context", rows: rows }) }), hasTerraformSource && _jsx(TerraformHint, {}), hasDDEVSource && _jsx(DDEVHint, {}), _jsx(ContextSetHint, {})] }));
|
|
66
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(SingleResult, { title: "Current CLI context", rows: rows }) }), hasTerraformSource && _jsx(TerraformHint, {}), hasDDEVSource && _jsx(DDEVHint, {}), hasDotfileSource && _jsx(DotfileHint, {}), _jsx(ContextSetHint, {})] }));
|
|
63
67
|
};
|
|
64
68
|
const TerraformHint = () => (_jsx(Note, { marginBottom: 1, children: "You are in a directory that contains a terraform state file; some of the context values were read from there." }));
|
|
65
69
|
const DDEVHint = () => (_jsx(Note, { marginBottom: 1, children: "You are in a directory that contains a DDEV project; some of the context values were read from there." }));
|
|
70
|
+
const DotfileHint = () => (_jsx(Note, { marginBottom: 1, children: "You are in a directory that contains a .mw-context.json file; some of the context values were read from there." }));
|
|
66
71
|
const ContextSetHint = () => (_jsxs(Note, { marginBottom: 1, children: ["Use the ", _jsx(Value, { children: "mw context set" }), " command to set one of the values listed above."] }));
|
|
67
72
|
export class Get extends RenderBaseCommand {
|
|
68
73
|
static summary = "Print an overview of currently set context parameters";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function configureAxiosBaseURL(axios, baseURL) {
|
|
2
|
+
const trimmedBaseURL = baseURL.trim();
|
|
3
|
+
if (!trimmedBaseURL) {
|
|
4
|
+
throw new Error("MITTWALD_API_BASE_URL is empty or contains only whitespace. Please provide a valid absolute URL.");
|
|
5
|
+
}
|
|
6
|
+
let parsedUrl;
|
|
7
|
+
try {
|
|
8
|
+
parsedUrl = new URL(trimmedBaseURL);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
throw new Error(`MITTWALD_API_BASE_URL="${trimmedBaseURL}" is not a valid absolute URL. Please provide a value like "https://api.example.com".`);
|
|
12
|
+
}
|
|
13
|
+
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
|
|
14
|
+
throw new Error(`MITTWALD_API_BASE_URL="${trimmedBaseURL}" must use http or https scheme. Received protocol "${parsedUrl.protocol}".`);
|
|
15
|
+
}
|
|
16
|
+
axios.defaults.baseURL = parsedUrl.toString();
|
|
17
|
+
}
|
|
@@ -5,6 +5,7 @@ import { CoreBaseCommand } from "./CoreBaseCommand.js";
|
|
|
5
5
|
import { configureAxiosLogging } from "../apiutil/api_logging.js";
|
|
6
6
|
import { configureAxiosRetry } from "../apiutil/api_retry.js";
|
|
7
7
|
import { configureConsistencyHandling } from "../apiutil/api_consistency.js";
|
|
8
|
+
import { configureAxiosBaseURL } from "../apiutil/api_baseurl.js";
|
|
8
9
|
import NoTokenFoundError from "../error/NoTokenFoundError.js";
|
|
9
10
|
/** Base command class for authenticated commands that includes the --token flag. */
|
|
10
11
|
export class BaseCommand extends CoreBaseCommand {
|
|
@@ -27,6 +28,22 @@ export class BaseCommand extends CoreBaseCommand {
|
|
|
27
28
|
this.apiClient = MittwaldAPIV2Client.newWithToken(token);
|
|
28
29
|
this.apiClient.axios.defaults.headers["User-Agent"] =
|
|
29
30
|
`mittwald-cli/${this.config.version}`;
|
|
31
|
+
// Allow overriding API base URL for local testing/mocking.
|
|
32
|
+
// NOTE: This is intentionally dangerous; be careful not to leak tokens
|
|
33
|
+
// to unintended hosts via a leftover environment variable.
|
|
34
|
+
const rawBaseUrlEnv = process.env.MITTWALD_API_BASE_URL;
|
|
35
|
+
const overriddenBaseUrl = typeof rawBaseUrlEnv === "string" ? rawBaseUrlEnv.trim() : "";
|
|
36
|
+
if (overriddenBaseUrl) {
|
|
37
|
+
try {
|
|
38
|
+
const parsed = new URL(overriddenBaseUrl);
|
|
39
|
+
// Warn the user that requests (including tokens) will be sent to a non-default host.
|
|
40
|
+
this.warn(`Using overridden API base URL from MITTWALD_API_BASE_URL (host: ${parsed.host}).`);
|
|
41
|
+
configureAxiosBaseURL(this.apiClient.axios, overriddenBaseUrl);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
this.warn("Ignoring MITTWALD_API_BASE_URL because it is not a valid URL.");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
30
47
|
configureAxiosLogging(this.apiClient.axios);
|
|
31
48
|
configureAxiosRetry(this.apiClient.axios);
|
|
32
49
|
configureConsistencyHandling(this.apiClient.axios);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import UserContextProvider from "./UserContextProvider.js";
|
|
2
2
|
import TerraformContextProvider from "./TerraformContextProvider.js";
|
|
3
3
|
import DDEVContextProvider from "./DDEVContextProvider.js";
|
|
4
|
+
import DotfileContextProvider from "./DotfileContextProvider.js";
|
|
4
5
|
import InvalidContextError from "../error/InvalidContextError.js";
|
|
5
6
|
function isWritable(p) {
|
|
6
7
|
return "update" in p;
|
|
@@ -17,6 +18,7 @@ export default class Context {
|
|
|
17
18
|
new UserContextProvider(config),
|
|
18
19
|
new TerraformContextProvider(),
|
|
19
20
|
new DDEVContextProvider(apiClient),
|
|
21
|
+
new DotfileContextProvider(),
|
|
20
22
|
];
|
|
21
23
|
this.opts = {
|
|
22
24
|
onInitError(err) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ContextMap } from "./Context.js";
|
|
2
|
+
import ContextProvider from "./ContextProvider.js";
|
|
3
|
+
/**
|
|
4
|
+
* DotfileContextProvider is a ContextProvider that reads context overrides from
|
|
5
|
+
* a local .mw-context.json file; it looks for the file in the current working
|
|
6
|
+
* directory or any of its parent directories.
|
|
7
|
+
*
|
|
8
|
+
* The file format is a simple JSON object mapping context keys to string
|
|
9
|
+
* values, e.g.:
|
|
10
|
+
*
|
|
11
|
+
* ```json
|
|
12
|
+
* {
|
|
13
|
+
* "project-id": "p-xxxxx",
|
|
14
|
+
* "server-id": "s-xxxxx"
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export default class DotfileContextProvider implements ContextProvider {
|
|
19
|
+
readonly name = "dotfile";
|
|
20
|
+
getOverrides(): Promise<ContextMap>;
|
|
21
|
+
/**
|
|
22
|
+
* Find the .mw-context.json file in the current working directory or any of
|
|
23
|
+
* its parent directories.
|
|
24
|
+
*/
|
|
25
|
+
private findDotfile;
|
|
26
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import { cwd } from "process";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { pathExists } from "../util/fs/pathExists.js";
|
|
5
|
+
import InvalidContextError from "../error/InvalidContextError.js";
|
|
6
|
+
const dotfileName = ".mw-context.json";
|
|
7
|
+
/**
|
|
8
|
+
* DotfileContextProvider is a ContextProvider that reads context overrides from
|
|
9
|
+
* a local .mw-context.json file; it looks for the file in the current working
|
|
10
|
+
* directory or any of its parent directories.
|
|
11
|
+
*
|
|
12
|
+
* The file format is a simple JSON object mapping context keys to string
|
|
13
|
+
* values, e.g.:
|
|
14
|
+
*
|
|
15
|
+
* ```json
|
|
16
|
+
* {
|
|
17
|
+
* "project-id": "p-xxxxx",
|
|
18
|
+
* "server-id": "s-xxxxx"
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export default class DotfileContextProvider {
|
|
23
|
+
name = "dotfile";
|
|
24
|
+
async getOverrides() {
|
|
25
|
+
const file = await this.findDotfile();
|
|
26
|
+
if (!file) {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
const contents = await fs.readFile(file, "utf-8");
|
|
30
|
+
let parsed;
|
|
31
|
+
try {
|
|
32
|
+
parsed = JSON.parse(contents);
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
throw new InvalidContextError(`error while parsing ${file}: ${err}`, {
|
|
36
|
+
cause: err,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const source = { type: "dotfile", identifier: file };
|
|
40
|
+
const overrides = {};
|
|
41
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
42
|
+
if (typeof value === "string") {
|
|
43
|
+
overrides[key] = { value, source };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return overrides;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Find the .mw-context.json file in the current working directory or any of
|
|
50
|
+
* its parent directories.
|
|
51
|
+
*/
|
|
52
|
+
async findDotfile() {
|
|
53
|
+
let currentDir = cwd();
|
|
54
|
+
while (currentDir !== path.dirname(currentDir)) {
|
|
55
|
+
const dotfile = path.join(currentDir, dotfileName);
|
|
56
|
+
if (await pathExists(dotfile)) {
|
|
57
|
+
return dotfile;
|
|
58
|
+
}
|
|
59
|
+
currentDir = path.dirname(currentDir);
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|