@mittwald/cli 1.2.1 → 1.2.5
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/set.js +3 -1
- package/dist/commands/login/status.js +6 -5
- package/dist/commands/org/invite/list-own.d.ts +1 -1
- package/dist/commands/org/membership/list-own.d.ts +1 -1
- package/dist/commands/org/membership/list.d.ts +7 -0
- package/dist/lib/context/Context.d.ts +5 -1
- package/dist/lib/context/Context.js +18 -3
- package/dist/lib/context/DDEVContextProvider.js +18 -12
- package/dist/lib/error/InvalidContextError.d.ts +2 -0
- package/dist/lib/error/InvalidContextError.js +2 -0
- package/dist/lib/resources/database/mysql/temp_user.d.ts +10 -0
- package/dist/lib/resources/database/mysql/temp_user.js +34 -3
- package/dist/lib/resources/database/mysql/temp_user.test.d.ts +1 -0
- package/dist/lib/resources/database/mysql/temp_user.test.js +40 -0
- package/dist/lib/units/Duration.js +1 -1
- package/dist/rendering/react/components/OptionalValue.d.ts +2 -2
- package/package.json +6 -7
|
@@ -21,7 +21,9 @@ export class Set extends BaseCommand {
|
|
|
21
21
|
};
|
|
22
22
|
async run() {
|
|
23
23
|
const { flags } = await this.parse(Set);
|
|
24
|
-
const ctx = new Context(this.apiClient, this.config
|
|
24
|
+
const ctx = new Context(this.apiClient, this.config, {
|
|
25
|
+
onInitError() { },
|
|
26
|
+
});
|
|
25
27
|
if (flags["project-id"]) {
|
|
26
28
|
const projectId = await ctx.setProjectId(flags["project-id"]);
|
|
27
29
|
this.log(`Set project ID to ${projectId}`);
|
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { formatDistanceToNow } from "date-fns";
|
|
3
3
|
import { RenderBaseCommand } from "../../lib/basecommands/RenderBaseCommand.js";
|
|
4
4
|
import { SingleResult, SingleResultTable, } from "../../rendering/react/components/SingleResult.js";
|
|
5
5
|
import useOwnAccount from "../../lib/resources/login/useOwnAccount.js";
|
|
6
|
+
import { Text } from "ink";
|
|
6
7
|
export default class Status extends RenderBaseCommand {
|
|
7
8
|
static description = "Checks your current authentication status";
|
|
8
9
|
render() {
|
|
9
10
|
const account = useOwnAccount(this.apiClient);
|
|
10
11
|
const rows = {};
|
|
11
12
|
rows["User identification"] = (_jsx(SingleResultTable, { rows: {
|
|
12
|
-
Id: account.userId,
|
|
13
|
-
Email: account.email,
|
|
13
|
+
Id: _jsx(Text, { children: account.userId }),
|
|
14
|
+
Email: _jsx(Text, { children: account.email }),
|
|
14
15
|
} }));
|
|
15
16
|
if (account.person) {
|
|
16
|
-
rows["Name"] =
|
|
17
|
+
rows["Name"] = (_jsxs(Text, { children: [account.person.firstName, " ", account.person.lastName] }));
|
|
17
18
|
}
|
|
18
19
|
if (account.passwordUpdatedAt) {
|
|
19
|
-
rows["Last password change"] =
|
|
20
|
+
rows["Last password change"] = (_jsxs(Text, { children: [formatDistanceToNow(new Date(account.passwordUpdatedAt)), " ago"] }));
|
|
20
21
|
}
|
|
21
22
|
return _jsx(SingleResult, { title: "Login status", rows: rows });
|
|
22
23
|
}
|
|
@@ -40,7 +40,7 @@ export declare class List extends ListBaseCommand<typeof List, ResponseItem, Res
|
|
|
40
40
|
owner?: import("node_modules/@mittwald/api-client/dist/types/generated/v2/types.js").MittwaldAPIV2.Components.Schemas.CustomerContact | undefined;
|
|
41
41
|
projectCount: number;
|
|
42
42
|
vatId?: string | undefined;
|
|
43
|
-
vatIdValidationState?:
|
|
43
|
+
vatIdValidationState?: "valid" | "invalid" | "pending" | "unspecified" | undefined;
|
|
44
44
|
};
|
|
45
45
|
avatarRefId?: string;
|
|
46
46
|
customerId: string;
|
|
@@ -40,7 +40,7 @@ export declare class ListOwn extends ListBaseCommand<typeof ListOwn, ResponseIte
|
|
|
40
40
|
owner?: import("node_modules/@mittwald/api-client/dist/types/generated/v2/types.js").MittwaldAPIV2.Components.Schemas.CustomerContact | undefined;
|
|
41
41
|
projectCount: number;
|
|
42
42
|
vatId?: string | undefined;
|
|
43
|
-
vatIdValidationState?:
|
|
43
|
+
vatIdValidationState?: "valid" | "invalid" | "pending" | "unspecified" | undefined;
|
|
44
44
|
};
|
|
45
45
|
customerId: string;
|
|
46
46
|
email: string;
|
|
@@ -27,10 +27,14 @@ export declare class List extends ListBaseCommand<typeof List, ResponseItem, Res
|
|
|
27
27
|
protected mapData(data: SuccessfulResponse<Response, 200>["data"]): Promise<(MittwaldAPIV2.Components.Schemas.MembershipCustomerMembership | {
|
|
28
28
|
user: {
|
|
29
29
|
avatarRef?: string | undefined;
|
|
30
|
+
customerMemberships?: {
|
|
31
|
+
[k: string]: import("node_modules/@mittwald/api-client/dist/types/generated/v2/types.js").MittwaldAPIV2.Components.Schemas.UserCustomerMembership;
|
|
32
|
+
} | undefined;
|
|
30
33
|
email?: string | undefined;
|
|
31
34
|
employeeInformation?: {
|
|
32
35
|
department: string;
|
|
33
36
|
} | undefined;
|
|
37
|
+
isEmployee?: boolean | undefined;
|
|
34
38
|
mfa?: {
|
|
35
39
|
active: boolean;
|
|
36
40
|
setup: boolean;
|
|
@@ -38,6 +42,9 @@ export declare class List extends ListBaseCommand<typeof List, ResponseItem, Res
|
|
|
38
42
|
passwordUpdatedAt?: string | undefined;
|
|
39
43
|
person: import("node_modules/@mittwald/api-client/dist/types/generated/v2/types.js").MittwaldAPIV2.Components.Schemas.CommonsPerson;
|
|
40
44
|
phoneNumber?: string | undefined;
|
|
45
|
+
projectMemberships?: {
|
|
46
|
+
[k: string]: import("node_modules/@mittwald/api-client/dist/types/generated/v2/types.js").MittwaldAPIV2.Components.Schemas.UserProjectMembership;
|
|
47
|
+
} | undefined;
|
|
41
48
|
registeredAt?: string | undefined;
|
|
42
49
|
userId: string;
|
|
43
50
|
};
|
|
@@ -16,11 +16,15 @@ export type ContextValue = {
|
|
|
16
16
|
export declare const contextIDNormalizers: {
|
|
17
17
|
[k in ContextKey]?: (apiClient: MittwaldAPIV2Client, id: string) => Promise<string>;
|
|
18
18
|
};
|
|
19
|
+
export interface ContextOptions {
|
|
20
|
+
onInitError: (err: unknown) => void;
|
|
21
|
+
}
|
|
19
22
|
export default class Context {
|
|
20
23
|
private readonly contextData;
|
|
21
24
|
private readonly apiClient;
|
|
25
|
+
private readonly opts;
|
|
22
26
|
readonly providers: ContextProvider[];
|
|
23
|
-
constructor(apiClient: MittwaldAPIV2Client, config: Config);
|
|
27
|
+
constructor(apiClient: MittwaldAPIV2Client, config: Config, opts?: Partial<ContextOptions>);
|
|
24
28
|
private initializeContextData;
|
|
25
29
|
reset(): Promise<void>;
|
|
26
30
|
private persist;
|
|
@@ -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 InvalidContextError from "../error/InvalidContextError.js";
|
|
4
5
|
function isWritable(p) {
|
|
5
6
|
return "update" in p;
|
|
6
7
|
}
|
|
@@ -8,21 +9,35 @@ export const contextIDNormalizers = {};
|
|
|
8
9
|
export default class Context {
|
|
9
10
|
contextData;
|
|
10
11
|
apiClient;
|
|
12
|
+
opts;
|
|
11
13
|
providers;
|
|
12
|
-
constructor(apiClient, config) {
|
|
14
|
+
constructor(apiClient, config, opts = {}) {
|
|
13
15
|
this.apiClient = apiClient;
|
|
14
16
|
this.providers = [
|
|
15
17
|
new UserContextProvider(config),
|
|
16
18
|
new TerraformContextProvider(),
|
|
17
19
|
new DDEVContextProvider(apiClient),
|
|
18
20
|
];
|
|
21
|
+
this.opts = {
|
|
22
|
+
onInitError(err) {
|
|
23
|
+
throw err;
|
|
24
|
+
},
|
|
25
|
+
...opts,
|
|
26
|
+
};
|
|
19
27
|
this.contextData = this.initializeContextData();
|
|
20
28
|
}
|
|
21
29
|
async initializeContextData() {
|
|
22
30
|
const contextData = {};
|
|
23
31
|
for (const provider of this.providers) {
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
try {
|
|
33
|
+
const overrides = await provider.getOverrides();
|
|
34
|
+
Object.assign(contextData, overrides);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
if (err instanceof InvalidContextError) {
|
|
38
|
+
this.opts.onInitError(err);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
26
41
|
}
|
|
27
42
|
return contextData;
|
|
28
43
|
}
|
|
@@ -4,6 +4,7 @@ import fs from "fs/promises";
|
|
|
4
4
|
import yaml from "js-yaml";
|
|
5
5
|
import { assertStatus } from "@mittwald/api-client";
|
|
6
6
|
import { pathExists } from "../util/fs/pathExists.js";
|
|
7
|
+
import InvalidContextError from "../error/InvalidContextError.js";
|
|
7
8
|
/**
|
|
8
9
|
* DDEVContextProvider is a ContextProvider that reads context overrides from
|
|
9
10
|
* local DDEV configuration files; it looks for a .ddev directory in the current
|
|
@@ -44,20 +45,25 @@ export default class DDEVContextProvider {
|
|
|
44
45
|
return overrides;
|
|
45
46
|
}
|
|
46
47
|
async fillOverridesFromAppInstallationId(source, appInstallationId) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (response.data.projectId) {
|
|
55
|
-
out["project-id"] = {
|
|
56
|
-
value: response.data.projectId,
|
|
57
|
-
source,
|
|
48
|
+
try {
|
|
49
|
+
const response = await this.apiClient.app.getAppinstallation({
|
|
50
|
+
appInstallationId,
|
|
51
|
+
});
|
|
52
|
+
assertStatus(response, 200);
|
|
53
|
+
const out = {
|
|
54
|
+
"installation-id": { value: response.data.id, source },
|
|
58
55
|
};
|
|
56
|
+
if (response.data.projectId) {
|
|
57
|
+
out["project-id"] = {
|
|
58
|
+
value: response.data.projectId,
|
|
59
|
+
source,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
throw new InvalidContextError(`error while getting app installation ${appInstallationId}: ${err}`, { cause: err });
|
|
59
66
|
}
|
|
60
|
-
return out;
|
|
61
67
|
}
|
|
62
68
|
/**
|
|
63
69
|
* Find the .ddev directory in the current working directory or any of its
|
|
@@ -2,6 +2,16 @@ import type { MittwaldAPIV2 } from "@mittwald/api-client";
|
|
|
2
2
|
import { MittwaldAPIV2Client } from "@mittwald/api-client";
|
|
3
3
|
import { ProcessRenderer } from "../../../../rendering/process/process.js";
|
|
4
4
|
type DatabaseMySqlUser = MittwaldAPIV2.Components.Schemas.DatabaseMySqlUser;
|
|
5
|
+
/**
|
|
6
|
+
* Generates a random password that fulfills the requirements for [mysql
|
|
7
|
+
* passwords][mysql].
|
|
8
|
+
*
|
|
9
|
+
* [mysql]: https://developer.mittwald.de/docs/v2/api/security/passwords#mysql
|
|
10
|
+
*
|
|
11
|
+
* @param length Desired length in characters
|
|
12
|
+
* @returns A randomly generated password
|
|
13
|
+
*/
|
|
14
|
+
export declare function generateRandomPassword(length?: number): string;
|
|
5
15
|
/**
|
|
6
16
|
* Runs a callback function with a temporary user for a database operation.
|
|
7
17
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { assertStatus } from "@mittwald/api-client";
|
|
2
|
-
import { randomBytes } from "crypto";
|
|
3
2
|
import assertSuccess from "../../../apiutil/assert_success.js";
|
|
3
|
+
import { randomInt } from "crypto";
|
|
4
4
|
async function createTemporaryUser(apiClient, databaseId, password) {
|
|
5
5
|
const createResponse = await apiClient.database.createMysqlUser({
|
|
6
6
|
mysqlDatabaseId: databaseId,
|
|
@@ -22,8 +22,39 @@ async function retrieveTemporaryUser(apiClient, mysqlUserId) {
|
|
|
22
22
|
assertStatus(userResponse, 200);
|
|
23
23
|
return userResponse.data;
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Generates a random password that fulfills the requirements for [mysql
|
|
27
|
+
* passwords][mysql].
|
|
28
|
+
*
|
|
29
|
+
* [mysql]: https://developer.mittwald.de/docs/v2/api/security/passwords#mysql
|
|
30
|
+
*
|
|
31
|
+
* @param length Desired length in characters
|
|
32
|
+
* @returns A randomly generated password
|
|
33
|
+
*/
|
|
34
|
+
export function generateRandomPassword(length = 32) {
|
|
35
|
+
// Character pools
|
|
36
|
+
const lowercase = "abcdefghijklmnopqrstuvwxyz";
|
|
37
|
+
const uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
38
|
+
const digits = "0123456789";
|
|
39
|
+
const specialChars = "#!~%^*_+-=?{}()<>|.,;";
|
|
40
|
+
const allChars = lowercase + uppercase + digits + specialChars;
|
|
41
|
+
// Ensure the password includes at least one of each required character type
|
|
42
|
+
const passwordArray = [
|
|
43
|
+
lowercase[randomInt(0, lowercase.length)],
|
|
44
|
+
uppercase[randomInt(0, uppercase.length)],
|
|
45
|
+
digits[randomInt(0, digits.length)],
|
|
46
|
+
specialChars[randomInt(0, specialChars.length)],
|
|
47
|
+
];
|
|
48
|
+
// Fill the remaining characters randomly
|
|
49
|
+
for (let i = passwordArray.length; i < length; i++) {
|
|
50
|
+
passwordArray.push(allChars[randomInt(0, allChars.length)]);
|
|
51
|
+
}
|
|
52
|
+
// Shuffle the array to avoid predictable patterns
|
|
53
|
+
for (let i = passwordArray.length - 1; i > 0; i--) {
|
|
54
|
+
const j = randomInt(0, i + 1);
|
|
55
|
+
[passwordArray[i], passwordArray[j]] = [passwordArray[j], passwordArray[i]];
|
|
56
|
+
}
|
|
57
|
+
return passwordArray.join("");
|
|
27
58
|
}
|
|
28
59
|
/**
|
|
29
60
|
* Runs a callback function with a temporary user for a database operation.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, it, expect } from "@jest/globals";
|
|
2
|
+
import { generateRandomPassword } from "./temp_user.js";
|
|
3
|
+
describe("generateRandomPassword", () => {
|
|
4
|
+
it("should generate a password of the specified length", () => {
|
|
5
|
+
const length = 12;
|
|
6
|
+
const password = generateRandomPassword(length);
|
|
7
|
+
expect(password.length).toBe(length);
|
|
8
|
+
});
|
|
9
|
+
it("should include at least one lowercase character", () => {
|
|
10
|
+
const password = generateRandomPassword(12);
|
|
11
|
+
expect(/[a-z]/.test(password)).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
it("should include at least one uppercase character", () => {
|
|
14
|
+
const password = generateRandomPassword(12);
|
|
15
|
+
expect(/[A-Z]/.test(password)).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
it("should include at least one digit", () => {
|
|
18
|
+
const password = generateRandomPassword(12);
|
|
19
|
+
expect(/[0-9]/.test(password)).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
it("should include at least one special character", () => {
|
|
22
|
+
const specialChars = "#!~%^*_+-=?{}()<>|.,;";
|
|
23
|
+
const password = generateRandomPassword(12);
|
|
24
|
+
expect(new RegExp(`[${specialChars.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&")}]`).test(password)).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
it("should generate passwords that are unique", () => {
|
|
27
|
+
const password1 = generateRandomPassword(12);
|
|
28
|
+
const password2 = generateRandomPassword(12);
|
|
29
|
+
expect(password1).not.toBe(password2);
|
|
30
|
+
});
|
|
31
|
+
it("should work for passwords with length greater than 8", () => {
|
|
32
|
+
const password = generateRandomPassword(20);
|
|
33
|
+
expect(password.length).toBe(20);
|
|
34
|
+
expect(/[a-z]/.test(password)).toBe(true);
|
|
35
|
+
expect(/[A-Z]/.test(password)).toBe(true);
|
|
36
|
+
expect(/[0-9]/.test(password)).toBe(true);
|
|
37
|
+
const specialChars = "#!~%^*_+-=?{}()<>|.,;";
|
|
38
|
+
expect(new RegExp(`[${specialChars.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&")}]`).test(password)).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
|
-
interface OptionalValueProps<T> {
|
|
2
|
+
interface OptionalValueProps<T extends ReactNode> {
|
|
3
3
|
value: T | undefined | null;
|
|
4
4
|
render?: (value: T) => ReactNode;
|
|
5
5
|
}
|
|
@@ -9,5 +9,5 @@ interface OptionalValueProps<T> {
|
|
|
9
9
|
* @class
|
|
10
10
|
* @param props
|
|
11
11
|
*/
|
|
12
|
-
declare function OptionalValue<T>(props: OptionalValueProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
declare function OptionalValue<T extends ReactNode>(props: OptionalValueProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
13
13
|
export default OptionalValue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mittwald/cli",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.5",
|
|
4
4
|
"description": "Hand-crafted CLI for the mittwald API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"package": "yarn package:tarballs && yarn package:windows && yarn package:macos",
|
|
29
29
|
"package:macos": "oclif pack macos",
|
|
30
30
|
"package:tarballs": "oclif pack tarballs --targets=linux-x64,linux-arm64,darwin-x64,darwin-arm64",
|
|
31
|
-
"package:windows": "oclif pack win",
|
|
31
|
+
"package:windows": "oclif pack win --targets=win32-x64,win32-x86",
|
|
32
32
|
"post:generate": "yarn run -T compile && yarn run -T compile:cjs",
|
|
33
33
|
"test": "yarn test:format && yarn test:licenses && yarn test:unit",
|
|
34
34
|
"test:format": "yarn lint && yarn format --check",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"slice-ansi": "^7.1.0",
|
|
69
69
|
"string-width": "^7.2.0",
|
|
70
70
|
"tempfile": "^5.0.0",
|
|
71
|
-
"uuid": "^
|
|
71
|
+
"uuid": "^11.0.3"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@jest/globals": "^29.7.0",
|
|
@@ -76,24 +76,23 @@
|
|
|
76
76
|
"@types/chalk": "^2.2.0",
|
|
77
77
|
"@types/js-yaml": "^4.0.9",
|
|
78
78
|
"@types/marked-terminal": "^3.1.3",
|
|
79
|
-
"@types/node": "^
|
|
79
|
+
"@types/node": "^22.7.5",
|
|
80
80
|
"@types/parse-duration": "^0.3.0",
|
|
81
81
|
"@types/pretty-bytes": "^5.2.0",
|
|
82
82
|
"@types/react": "^18",
|
|
83
83
|
"@types/semver": "^7.5.0",
|
|
84
84
|
"@types/shell-escape": "^0.2.3",
|
|
85
|
-
"@types/uuid": "^10.0.0",
|
|
86
85
|
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
|
87
86
|
"@typescript-eslint/parser": "^8.3.0",
|
|
88
87
|
"@yarnpkg/pnpify": "^4.0.0-rc.48",
|
|
89
88
|
"eslint": "^9.9.1",
|
|
90
|
-
"eslint-config-prettier": "^
|
|
89
|
+
"eslint-config-prettier": "^10.0.1",
|
|
91
90
|
"eslint-plugin-json": "^4.0.0",
|
|
92
91
|
"eslint-plugin-prettier": "^5.1.3",
|
|
93
92
|
"globals": "^15.9.0",
|
|
94
93
|
"jest": "^29.7.0",
|
|
95
94
|
"license-checker-rseidelsohn": "^4.2.6",
|
|
96
|
-
"nock": "^
|
|
95
|
+
"nock": "^14.0.0",
|
|
97
96
|
"oclif": "^4.14.31",
|
|
98
97
|
"prettier": "^3.2.5",
|
|
99
98
|
"prettier-plugin-jsdoc": "^1.3.0",
|