@mittwald/cli 1.2.5 → 1.3.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/database/list.d.ts +30 -0
- package/dist/commands/database/list.js +68 -0
- package/dist/commands/database/mysql/dump.d.ts +1 -0
- package/dist/commands/database/mysql/dump.js +10 -1
- package/dist/commands/database/mysql/import.d.ts +1 -0
- package/dist/commands/database/mysql/list.d.ts +1 -1
- package/dist/commands/database/mysql/port-forward.js +1 -1
- package/dist/commands/database/mysql/shell.d.ts +1 -0
- package/dist/lib/basecommands/ListBaseCommand.d.ts +1 -1
- package/dist/lib/basecommands/ListBaseCommand.js +8 -2
- package/dist/lib/resources/app/flags.js +1 -1
- package/dist/lib/resources/database/common.js +1 -1
- package/dist/lib/resources/database/mysql/connect.d.ts +3 -1
- package/dist/lib/resources/database/mysql/connect.js +4 -2
- package/dist/lib/resources/database/mysql/flags.d.ts +2 -0
- package/dist/lib/resources/database/mysql/flags.js +4 -0
- package/dist/lib/resources/database/mysql/temp_user.js +1 -1
- package/dist/lib/resources/login/useOwnAccount.d.ts +18 -5
- package/dist/lib/resources/login/useOwnAccount.js +1 -1
- package/dist/lib/resources/ssh/appinstall.js +1 -1
- package/dist/lib/resources/ssh/project.js +1 -1
- package/dist/lib/units/Duration.test.d.ts +1 -0
- package/dist/lib/units/Duration.test.js +59 -0
- package/package.json +2 -2
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { MittwaldAPIV2Client } from "@mittwald/api-client";
|
|
2
|
+
import { ListColumns } from "../../rendering/formatter/Table.js";
|
|
3
|
+
import { ListBaseCommand } from "../../lib/basecommands/ListBaseCommand.js";
|
|
4
|
+
type ResponseItem = {
|
|
5
|
+
id: string;
|
|
6
|
+
kind: "mysql" | "redis";
|
|
7
|
+
name: string;
|
|
8
|
+
version: string;
|
|
9
|
+
description: string;
|
|
10
|
+
hostname: string;
|
|
11
|
+
isReady: boolean;
|
|
12
|
+
createdAt: string;
|
|
13
|
+
};
|
|
14
|
+
type Response = Awaited<ReturnType<MittwaldAPIV2Client["database"]["listMysqlDatabases"]>>;
|
|
15
|
+
export declare class List extends ListBaseCommand<typeof List, ResponseItem, Response> {
|
|
16
|
+
static description: string;
|
|
17
|
+
static args: {};
|
|
18
|
+
static flags: {
|
|
19
|
+
"project-id": import("@oclif/core/interfaces").OptionFlag<string>;
|
|
20
|
+
output: import("@oclif/core/interfaces").OptionFlag<"json" | "txt" | "yaml" | "csv" | "tsv">;
|
|
21
|
+
extended: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
22
|
+
"no-header": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
23
|
+
"no-truncate": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
24
|
+
"no-relative-dates": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
25
|
+
"csv-separator": import("@oclif/core/interfaces").OptionFlag<"," | ";">;
|
|
26
|
+
};
|
|
27
|
+
getData(): Promise<ResponseItem[]>;
|
|
28
|
+
protected getColumns(ignoredData: ResponseItem[]): ListColumns<ResponseItem>;
|
|
29
|
+
}
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { assertStatus } from "@mittwald/api-client-commons";
|
|
2
|
+
import { ListBaseCommand } from "../../lib/basecommands/ListBaseCommand.js";
|
|
3
|
+
import { projectFlags } from "../../lib/resources/project/flags.js";
|
|
4
|
+
export class List extends ListBaseCommand {
|
|
5
|
+
static description = "List all kinds of databases belonging to a project.";
|
|
6
|
+
static args = {};
|
|
7
|
+
static flags = {
|
|
8
|
+
...ListBaseCommand.baseFlags,
|
|
9
|
+
...projectFlags,
|
|
10
|
+
};
|
|
11
|
+
async getData() {
|
|
12
|
+
const projectId = await this.withProjectId(List);
|
|
13
|
+
const databases = [];
|
|
14
|
+
const mysqlResponse = await this.apiClient.database.listMysqlDatabases({
|
|
15
|
+
projectId,
|
|
16
|
+
});
|
|
17
|
+
assertStatus(mysqlResponse, 200);
|
|
18
|
+
const redisResponse = await this.apiClient.database.listRedisDatabases({
|
|
19
|
+
projectId,
|
|
20
|
+
});
|
|
21
|
+
assertStatus(redisResponse, 200);
|
|
22
|
+
databases.push(...mysqlResponse.data.map((d) => ({ ...d, kind: "mysql" })), ...redisResponse.data.map((d) => ({
|
|
23
|
+
...d,
|
|
24
|
+
kind: "redis",
|
|
25
|
+
isReady: true,
|
|
26
|
+
})));
|
|
27
|
+
return databases;
|
|
28
|
+
}
|
|
29
|
+
getColumns(ignoredData) {
|
|
30
|
+
const { id, name, createdAt } = super.getColumns(ignoredData, {
|
|
31
|
+
shortIdKey: "name",
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
id,
|
|
35
|
+
name,
|
|
36
|
+
version: {
|
|
37
|
+
header: "Version",
|
|
38
|
+
get(row) {
|
|
39
|
+
if (row.kind === "mysql") {
|
|
40
|
+
return `MySQL ${row.version}`;
|
|
41
|
+
}
|
|
42
|
+
else if (row.kind === "redis") {
|
|
43
|
+
return `Redis ${row.version}`;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
return "Unknown";
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
description: {
|
|
51
|
+
header: "Description",
|
|
52
|
+
},
|
|
53
|
+
hostname: {
|
|
54
|
+
header: "Hostname",
|
|
55
|
+
},
|
|
56
|
+
status: {
|
|
57
|
+
header: "Status",
|
|
58
|
+
get: (row) => {
|
|
59
|
+
if (!row.isReady) {
|
|
60
|
+
return "pending";
|
|
61
|
+
}
|
|
62
|
+
return "ready";
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
createdAt,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -10,6 +10,7 @@ export declare class Dump extends ExecRenderBaseCommand<typeof Dump, Record<stri
|
|
|
10
10
|
"ssh-identity-file": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
11
|
"temporary-user": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
12
|
"mysql-password": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
"mysql-charset": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
14
|
quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
15
|
};
|
|
15
16
|
static args: {
|
|
@@ -68,5 +68,14 @@ function DumpSuccess({ database, output, }) {
|
|
|
68
68
|
return (_jsxs(Success, { children: ["Dump of MySQL database ", _jsx(Value, { children: database }), " written to", " ", _jsx(Value, { children: output })] }));
|
|
69
69
|
}
|
|
70
70
|
function buildMySqlDumpArgs(d) {
|
|
71
|
-
return [
|
|
71
|
+
return [
|
|
72
|
+
"-h",
|
|
73
|
+
d.hostname,
|
|
74
|
+
"-u",
|
|
75
|
+
d.user,
|
|
76
|
+
`-p${d.password}`,
|
|
77
|
+
"--default-character-set",
|
|
78
|
+
d.charset,
|
|
79
|
+
d.database,
|
|
80
|
+
];
|
|
72
81
|
}
|
|
@@ -10,6 +10,7 @@ export declare class Import extends ExecRenderBaseCommand<typeof Import, Record<
|
|
|
10
10
|
"ssh-identity-file": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
11
|
"temporary-user": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
12
|
"mysql-password": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
"mysql-charset": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
14
|
quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
15
|
};
|
|
15
16
|
static args: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Simplify } from "@mittwald/api-client-commons";
|
|
2
2
|
import { MittwaldAPIV2, MittwaldAPIV2Client } from "@mittwald/api-client";
|
|
3
|
-
import { ListColumns } from "../../../rendering/formatter/
|
|
3
|
+
import { ListColumns } from "../../../rendering/formatter/Table.js";
|
|
4
4
|
import { ListBaseCommand } from "../../../lib/basecommands/ListBaseCommand.js";
|
|
5
5
|
type ResponseItem = Simplify<MittwaldAPIV2.Paths.V2ProjectsProjectIdMysqlDatabases.Get.Responses.$200.Content.ApplicationJson[number]>;
|
|
6
6
|
type Response = Awaited<ReturnType<MittwaldAPIV2Client["database"]["listMysqlDatabases"]>>;
|
|
@@ -26,7 +26,7 @@ export class PortForward extends ExecRenderBaseCommand {
|
|
|
26
26
|
async exec() {
|
|
27
27
|
const databaseId = await withMySQLId(this.apiClient, this.flags, this.args);
|
|
28
28
|
const p = makeProcessRenderer(this.flags, "Port-forwarding a MySQL database");
|
|
29
|
-
const { sshUser, sshHost, hostname, database } = await getConnectionDetails(this.apiClient, databaseId, this.flags["ssh-user"], p);
|
|
29
|
+
const { sshUser, sshHost, hostname, database } = await getConnectionDetails(this.apiClient, databaseId, this.flags["ssh-user"], undefined, p);
|
|
30
30
|
const { port } = this.flags;
|
|
31
31
|
p.complete(_jsxs(Text, { children: ["Forwarding MySQL database ", _jsx(Value, { children: database }), " to local port", " ", _jsx(Value, { children: port }), ". Use CTRL+C to cancel."] }));
|
|
32
32
|
const sshArgs = buildSSHClientFlags(sshUser, sshHost, this.flags, {
|
|
@@ -5,6 +5,7 @@ export declare class Shell extends ExecRenderBaseCommand<typeof Shell, Record<st
|
|
|
5
5
|
static description: string;
|
|
6
6
|
static flags: {
|
|
7
7
|
"mysql-password": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
"mysql-charset": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
9
|
"ssh-user": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
10
|
"ssh-identity-file": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
11
|
quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
@@ -24,7 +24,7 @@ export declare abstract class ListBaseCommand<T extends typeof BaseCommand, TIte
|
|
|
24
24
|
protected sorter?: SorterFunction<TItem>;
|
|
25
25
|
init(): Promise<void>;
|
|
26
26
|
run(): Promise<void>;
|
|
27
|
-
protected abstract getData(): Promise<TAPIResponse>;
|
|
27
|
+
protected abstract getData(): Promise<TAPIResponse | TItem[]>;
|
|
28
28
|
protected mapData(data: SuccessfulResponse<TAPIResponse, 200>["data"]): TItem[] | Promise<TItem[]>;
|
|
29
29
|
protected getColumns(data: TItem[], opts?: ColumnOpts<TItem>): ListColumns<TItem>;
|
|
30
30
|
}
|
|
@@ -21,8 +21,14 @@ export class ListBaseCommand extends ExtendedBaseCommand {
|
|
|
21
21
|
}
|
|
22
22
|
async run() {
|
|
23
23
|
const response = await this.getData();
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
let data;
|
|
25
|
+
if (!Array.isArray(response)) {
|
|
26
|
+
assertStatus(response, 200);
|
|
27
|
+
data = await this.mapData(response.data);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
data = response;
|
|
31
|
+
}
|
|
26
32
|
this.formatter.log(data, this.getColumns(data), this.flags);
|
|
27
33
|
}
|
|
28
34
|
mapData(data) {
|
|
@@ -159,7 +159,7 @@ export function provideSupportedFlags(requestedFlagNames, appName) {
|
|
|
159
159
|
return flagsToReturn;
|
|
160
160
|
}
|
|
161
161
|
export async function autofillFlags(apiClient, process, necessaryFlags, flags, projectId, appName, defaults) {
|
|
162
|
-
const ownUser = await apiClient.user.
|
|
162
|
+
const ownUser = await apiClient.user.getUser({ userId: "self" });
|
|
163
163
|
assertStatus(ownUser, 200);
|
|
164
164
|
// Version
|
|
165
165
|
if (necessaryFlags.includes("version") && !flags.version) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { assertStatus } from "@mittwald/api-client-commons";
|
|
2
2
|
export async function getUser(apiClient, p) {
|
|
3
3
|
return await p.runStep("fetching user", async () => {
|
|
4
|
-
const r = await apiClient.user.
|
|
4
|
+
const r = await apiClient.user.getUser({ userId: "self" });
|
|
5
5
|
assertStatus(r, 200);
|
|
6
6
|
return r.data;
|
|
7
7
|
});
|
|
@@ -4,6 +4,7 @@ import { MittwaldAPIV2Client } from "@mittwald/api-client";
|
|
|
4
4
|
type Project = MittwaldAPIV2.Components.Schemas.ProjectProject;
|
|
5
5
|
export interface MySQLConnectionFlags {
|
|
6
6
|
"mysql-password": string | undefined;
|
|
7
|
+
"mysql-charset": string | undefined;
|
|
7
8
|
"temporary-user"?: boolean;
|
|
8
9
|
"ssh-user"?: string;
|
|
9
10
|
}
|
|
@@ -14,6 +15,7 @@ export interface MySQLConnectionDetails {
|
|
|
14
15
|
sshHost: string;
|
|
15
16
|
sshUser: string;
|
|
16
17
|
project: Project;
|
|
18
|
+
charset: string;
|
|
17
19
|
}
|
|
18
20
|
export type MySQLConnectionDetailsWithPassword = MySQLConnectionDetails & {
|
|
19
21
|
password: string;
|
|
@@ -27,5 +29,5 @@ export type MySQLConnectionDetailsWithPassword = MySQLConnectionDetails & {
|
|
|
27
29
|
*/
|
|
28
30
|
export declare function runWithConnectionDetails<TRes>(apiClient: MittwaldAPIV2Client, databaseId: string, p: ProcessRenderer, flags: MySQLConnectionFlags, cb: (connectionDetails: MySQLConnectionDetailsWithPassword) => Promise<TRes>): Promise<TRes>;
|
|
29
31
|
export declare function getConnectionDetailsWithPassword(apiClient: MittwaldAPIV2Client, databaseId: string, p: ProcessRenderer, flags: MySQLConnectionFlags): Promise<MySQLConnectionDetailsWithPassword>;
|
|
30
|
-
export declare function getConnectionDetails(apiClient: MittwaldAPIV2Client, databaseId: string, sshUser: string | undefined, p: ProcessRenderer): Promise<MySQLConnectionDetails>;
|
|
32
|
+
export declare function getConnectionDetails(apiClient: MittwaldAPIV2Client, databaseId: string, sshUser: string | undefined, characterSet: string | undefined, p: ProcessRenderer): Promise<MySQLConnectionDetails>;
|
|
31
33
|
export {};
|
|
@@ -19,12 +19,13 @@ export async function runWithConnectionDetails(apiClient, databaseId, p, flags,
|
|
|
19
19
|
export async function getConnectionDetailsWithPassword(apiClient, databaseId, p, flags) {
|
|
20
20
|
const password = flags["temporary-user"] ? "" : await getPassword(p, flags);
|
|
21
21
|
const sshUser = flags["ssh-user"];
|
|
22
|
+
const characterSet = flags["mysql-charset"];
|
|
22
23
|
return {
|
|
23
|
-
...(await getConnectionDetails(apiClient, databaseId, sshUser, p)),
|
|
24
|
+
...(await getConnectionDetails(apiClient, databaseId, sshUser, characterSet, p)),
|
|
24
25
|
password,
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
|
-
export async function getConnectionDetails(apiClient, databaseId, sshUser, p) {
|
|
28
|
+
export async function getConnectionDetails(apiClient, databaseId, sshUser, characterSet, p) {
|
|
28
29
|
const database = await getDatabase(apiClient, p, databaseId);
|
|
29
30
|
const databaseUser = await getDatabaseUser(apiClient, p, databaseId);
|
|
30
31
|
const project = await getProject(apiClient, p, database);
|
|
@@ -35,6 +36,7 @@ export async function getConnectionDetails(apiClient, databaseId, sshUser, p) {
|
|
|
35
36
|
user: databaseUser.name,
|
|
36
37
|
sshHost: sshConnectionData.host,
|
|
37
38
|
sshUser: sshConnectionData.user,
|
|
39
|
+
charset: characterSet ?? database.characterSettings.characterSet,
|
|
38
40
|
project,
|
|
39
41
|
};
|
|
40
42
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { MittwaldAPIV2Client } from "@mittwald/api-client";
|
|
2
2
|
export declare const mysqlConnectionFlags: {
|
|
3
3
|
"mysql-password": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
4
|
+
"mysql-charset": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
4
5
|
};
|
|
5
6
|
export declare const mysqlConnectionFlagsWithTempUser: {
|
|
6
7
|
"temporary-user": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
8
|
"mysql-password": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
"mysql-charset": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
10
|
};
|
|
9
11
|
export declare const mysqlArgs: {
|
|
10
12
|
"database-id": import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
@@ -12,6 +12,10 @@ NOTE: This is a security risk, as the password will be visible in the process li
|
|
|
12
12
|
required: false,
|
|
13
13
|
env: "MYSQL_PWD",
|
|
14
14
|
}),
|
|
15
|
+
"mysql-charset": Flags.string({
|
|
16
|
+
summary: "the character set to use for the MySQL connection",
|
|
17
|
+
description: "The character set that should be used for the MySQL connection. If omitted, the database's default character set will be used (for newer databases, this should be utf8mb4 in most cases, but really might be anything).",
|
|
18
|
+
}),
|
|
15
19
|
};
|
|
16
20
|
export const mysqlConnectionFlagsWithTempUser = {
|
|
17
21
|
...mysqlConnectionFlags,
|
|
@@ -36,7 +36,7 @@ export function generateRandomPassword(length = 32) {
|
|
|
36
36
|
const lowercase = "abcdefghijklmnopqrstuvwxyz";
|
|
37
37
|
const uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
38
38
|
const digits = "0123456789";
|
|
39
|
-
const specialChars = "
|
|
39
|
+
const specialChars = "_";
|
|
40
40
|
const allChars = lowercase + uppercase + digits + specialChars;
|
|
41
41
|
// Ensure the password includes at least one of each required character type
|
|
42
42
|
const passwordArray = [
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import { MittwaldAPIV2Client } from "@mittwald/api-client";
|
|
2
2
|
export default function useOwnAccount(client: MittwaldAPIV2Client): {
|
|
3
|
+
avatarRef?: string | undefined;
|
|
4
|
+
customerMemberships?: {
|
|
5
|
+
[k: string]: import("@mittwald/api-client").MittwaldAPIV2.Components.Schemas.UserCustomerMembership;
|
|
6
|
+
} | undefined;
|
|
3
7
|
email?: string | undefined;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
employeeInformation?: {
|
|
9
|
+
department: string;
|
|
10
|
+
} | undefined;
|
|
11
|
+
isEmployee?: boolean | undefined;
|
|
12
|
+
mfa?: {
|
|
13
|
+
active: boolean;
|
|
14
|
+
setup: boolean;
|
|
7
15
|
} | undefined;
|
|
8
16
|
passwordUpdatedAt?: string | undefined;
|
|
9
|
-
person
|
|
10
|
-
|
|
17
|
+
person: import("@mittwald/api-client").MittwaldAPIV2.Components.Schemas.CommonsPerson;
|
|
18
|
+
phoneNumber?: string | undefined;
|
|
19
|
+
projectMemberships?: {
|
|
20
|
+
[k: string]: import("@mittwald/api-client").MittwaldAPIV2.Components.Schemas.UserProjectMembership;
|
|
21
|
+
} | undefined;
|
|
22
|
+
registeredAt?: string | undefined;
|
|
23
|
+
userId: string;
|
|
11
24
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { assertStatus } from "@mittwald/api-client";
|
|
2
2
|
import { usePromise } from "@mittwald/react-use-promise";
|
|
3
3
|
export default function useOwnAccount(client) {
|
|
4
|
-
const result = usePromise(() => client.user.
|
|
4
|
+
const result = usePromise(() => client.user.getUser({ userId: "self" }), []);
|
|
5
5
|
assertStatus(result, 200);
|
|
6
6
|
return result.data;
|
|
7
7
|
}
|
|
@@ -13,7 +13,7 @@ export async function getSSHConnectionForAppInstallation(client, appInstallation
|
|
|
13
13
|
});
|
|
14
14
|
assertStatus(projectResponse, 200);
|
|
15
15
|
if (sshUser === undefined) {
|
|
16
|
-
const userResponse = await client.user.
|
|
16
|
+
const userResponse = await client.user.getUser({ userId: "self" });
|
|
17
17
|
assertStatus(userResponse, 200);
|
|
18
18
|
sshUser = userResponse.data.email;
|
|
19
19
|
}
|
|
@@ -3,7 +3,7 @@ export async function getSSHConnectionForProject(client, projectId, sshUser) {
|
|
|
3
3
|
const projectResponse = await client.project.getProject({ projectId });
|
|
4
4
|
assertStatus(projectResponse, 200);
|
|
5
5
|
if (sshUser === undefined) {
|
|
6
|
-
const userResponse = await client.user.
|
|
6
|
+
const userResponse = await client.user.getUser({ userId: "self" });
|
|
7
7
|
assertStatus(userResponse, 200);
|
|
8
8
|
sshUser = userResponse.data.email;
|
|
9
9
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import Duration from "./Duration.js";
|
|
3
|
+
describe("Duration class", () => {
|
|
4
|
+
describe("Static factory methods", () => {
|
|
5
|
+
test("fromZero should return a Duration of 0 milliseconds", () => {
|
|
6
|
+
const duration = Duration.fromZero();
|
|
7
|
+
expect(duration.milliseconds).toBe(0);
|
|
8
|
+
});
|
|
9
|
+
test("fromMilliseconds should return a Duration with the given milliseconds", () => {
|
|
10
|
+
const duration = Duration.fromMilliseconds(500);
|
|
11
|
+
expect(duration.milliseconds).toBe(500);
|
|
12
|
+
});
|
|
13
|
+
test("fromSeconds should return a Duration with the given seconds converted to milliseconds", () => {
|
|
14
|
+
const duration = Duration.fromSeconds(2);
|
|
15
|
+
expect(duration.milliseconds).toBe(2000);
|
|
16
|
+
});
|
|
17
|
+
test("fromString should parse valid duration strings", () => {
|
|
18
|
+
const duration = Duration.fromString("2s");
|
|
19
|
+
expect(duration.milliseconds).toBe(2000);
|
|
20
|
+
});
|
|
21
|
+
test("fromString should throw an error for invalid input", () => {
|
|
22
|
+
expect(() => Duration.fromString("invalid")).toThrow("could not parse duration: invalid");
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe("Instance methods", () => {
|
|
26
|
+
test("seconds should return duration in seconds", () => {
|
|
27
|
+
const duration = Duration.fromMilliseconds(3000);
|
|
28
|
+
expect(duration.seconds).toBe(3);
|
|
29
|
+
});
|
|
30
|
+
test("from should return a Date object offset by the duration", () => {
|
|
31
|
+
const baseDate = new Date("2023-01-01T00:00:00Z");
|
|
32
|
+
const duration = Duration.fromSeconds(10);
|
|
33
|
+
expect(duration.from(baseDate)).toEqual(new Date("2023-01-01T00:00:10Z"));
|
|
34
|
+
});
|
|
35
|
+
test("fromNow should return a future Date object", () => {
|
|
36
|
+
const duration = Duration.fromSeconds(5);
|
|
37
|
+
const now = new Date();
|
|
38
|
+
const future = duration.fromNow();
|
|
39
|
+
expect(future.getTime()).toBeGreaterThan(now.getTime());
|
|
40
|
+
});
|
|
41
|
+
test("add should correctly add two durations", () => {
|
|
42
|
+
const duration1 = Duration.fromSeconds(10);
|
|
43
|
+
const duration2 = Duration.fromSeconds(5);
|
|
44
|
+
const result = duration1.add(duration2);
|
|
45
|
+
expect(result.milliseconds).toBe(15000);
|
|
46
|
+
});
|
|
47
|
+
test("compare should return correct comparison results", () => {
|
|
48
|
+
const duration1 = Duration.fromSeconds(10);
|
|
49
|
+
const duration2 = Duration.fromSeconds(5);
|
|
50
|
+
expect(duration1.compare(duration2)).toBeGreaterThan(0);
|
|
51
|
+
expect(duration2.compare(duration1)).toBeLessThan(0);
|
|
52
|
+
expect(duration1.compare(duration1)).toBe(0);
|
|
53
|
+
});
|
|
54
|
+
test("toString should return formatted duration string", () => {
|
|
55
|
+
expect(Duration.fromMilliseconds(500).toString()).toBe("500ms");
|
|
56
|
+
expect(Duration.fromSeconds(3).toString()).toBe("3s");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mittwald/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Hand-crafted CLI for the mittwald API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"marked": "^12.0.0",
|
|
60
60
|
"marked-terminal": "^6.0.0",
|
|
61
61
|
"open": "^10.0.3",
|
|
62
|
-
"parse-duration": "^
|
|
62
|
+
"parse-duration": "^2.0.1",
|
|
63
63
|
"pretty-bytes": "^6.1.0",
|
|
64
64
|
"react": "^18.2.0",
|
|
65
65
|
"semver": "^7.5.4",
|