@mittwald/cli 1.11.1 → 1.12.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/app/copy.d.ts +1 -0
- package/dist/commands/app/copy.js +6 -1
- package/dist/commands/app/create/node.d.ts +1 -1
- package/dist/commands/app/create/php-worker.d.ts +1 -1
- package/dist/commands/app/create/php.d.ts +1 -1
- package/dist/commands/app/create/python.d.ts +1 -1
- package/dist/commands/app/create/static.d.ts +1 -1
- package/dist/commands/app/install/contao.d.ts +1 -1
- package/dist/commands/app/install/joomla.d.ts +1 -1
- package/dist/commands/app/install/matomo.d.ts +1 -1
- package/dist/commands/app/install/nextcloud.d.ts +1 -1
- package/dist/commands/app/install/shopware5.d.ts +1 -1
- package/dist/commands/app/install/shopware6.d.ts +1 -1
- package/dist/commands/app/install/typo3.d.ts +1 -1
- package/dist/commands/app/install/wordpress.d.ts +1 -1
- package/dist/commands/app/open.d.ts +3 -0
- package/dist/commands/app/open.js +35 -9
- package/dist/commands/container/port-forward.js +2 -2
- package/dist/commands/container/run.d.ts +9 -0
- package/dist/commands/container/run.js +42 -4
- package/dist/commands/container/update.js +2 -2
- package/dist/commands/database/mysql/create.js +1 -1
- package/dist/lib/apiutil/api_retry.js +2 -1
- package/dist/lib/basecommands/RenderBaseCommand.js +1 -0
- package/dist/lib/resources/app/Installer.d.ts +1 -1
- package/dist/lib/resources/app/Installer.js +7 -1
- package/dist/lib/resources/app/flags.d.ts +1 -0
- package/dist/lib/resources/app/flags.js +6 -0
- package/dist/lib/resources/app/install.d.ts +1 -0
- package/dist/lib/resources/app/install.js +1 -0
- package/dist/lib/resources/database/mysql/flags.js +1 -1
- package/dist/lib/units/PortMapping.d.ts +2 -0
- package/dist/lib/units/PortMapping.js +21 -6
- package/dist/lib/units/PortMapping.test.js +10 -0
- package/package.json +1 -1
|
@@ -10,6 +10,7 @@ export declare class Copy extends ExecRenderBaseCommand<typeof Copy, Result> {
|
|
|
10
10
|
};
|
|
11
11
|
static flags: {
|
|
12
12
|
description: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
"install-path": 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
|
protected exec(): Promise<Result>;
|
|
@@ -14,16 +14,21 @@ export class Copy extends ExecRenderBaseCommand {
|
|
|
14
14
|
summary: "set a description for the new app installation",
|
|
15
15
|
required: true,
|
|
16
16
|
}),
|
|
17
|
+
"install-path": Flags.string({
|
|
18
|
+
summary: "set the installation path for the new app installation; if omitted, this will default to an autogenerated directory name",
|
|
19
|
+
required: false,
|
|
20
|
+
}),
|
|
17
21
|
};
|
|
18
22
|
async exec() {
|
|
19
23
|
const appInstallationId = await this.withAppInstallationId(Copy);
|
|
20
|
-
const { description } = this.flags;
|
|
24
|
+
const { description, "install-path": installPath } = this.flags;
|
|
21
25
|
const p = makeProcessRenderer(this.flags, "Copying app installation");
|
|
22
26
|
const result = await p.runStep("requesting app copy", async () => {
|
|
23
27
|
const r = await this.apiClient.app.requestAppinstallationCopy({
|
|
24
28
|
appInstallationId,
|
|
25
29
|
data: {
|
|
26
30
|
description,
|
|
31
|
+
installationPath: installPath,
|
|
27
32
|
},
|
|
28
33
|
});
|
|
29
34
|
assertStatus(r, 201);
|
|
@@ -4,7 +4,7 @@ import { AppInstallationResult, AppInstaller } from "../../../lib/resources/app/
|
|
|
4
4
|
export declare const nodeInstaller: AppInstaller<"site-title" | "entrypoint">;
|
|
5
5
|
export default class InstallNode extends ExecRenderBaseCommand<typeof InstallNode, AppInstallationResult> {
|
|
6
6
|
static description: string;
|
|
7
|
-
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("entrypoint" | ("wait" | "wait-timeout" | "site-title"))[]>>;
|
|
7
|
+
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("entrypoint" | ("wait" | "wait-timeout" | "site-title" | "install-path"))[]>>;
|
|
8
8
|
protected exec(): Promise<AppInstallationResult>;
|
|
9
9
|
protected render(result: AppInstallationResult): React.ReactNode;
|
|
10
10
|
}
|
|
@@ -4,7 +4,7 @@ import { AppInstallationResult, AppInstaller } from "../../../lib/resources/app/
|
|
|
4
4
|
export declare const phpWorkerInstaller: AppInstaller<"site-title" | "entrypoint">;
|
|
5
5
|
export default class InstallPhpWorker extends ExecRenderBaseCommand<typeof InstallPhpWorker, AppInstallationResult> {
|
|
6
6
|
static description: string;
|
|
7
|
-
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("entrypoint" | ("wait" | "wait-timeout" | "site-title"))[]>>;
|
|
7
|
+
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("entrypoint" | ("wait" | "wait-timeout" | "site-title" | "install-path"))[]>>;
|
|
8
8
|
protected exec(): Promise<AppInstallationResult>;
|
|
9
9
|
protected render(result: AppInstallationResult): React.ReactNode;
|
|
10
10
|
}
|
|
@@ -4,7 +4,7 @@ import { AppInstallationResult, AppInstaller } from "../../../lib/resources/app/
|
|
|
4
4
|
export declare const phpInstaller: AppInstaller<"site-title" | "document-root">;
|
|
5
5
|
export default class InstallPhp extends ExecRenderBaseCommand<typeof InstallPhp, AppInstallationResult> {
|
|
6
6
|
static description: string;
|
|
7
|
-
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("document-root" | ("wait" | "wait-timeout" | "site-title"))[]>>;
|
|
7
|
+
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("document-root" | ("wait" | "wait-timeout" | "site-title" | "install-path"))[]>>;
|
|
8
8
|
protected exec(): Promise<AppInstallationResult>;
|
|
9
9
|
protected render(result: AppInstallationResult): React.ReactNode;
|
|
10
10
|
}
|
|
@@ -4,7 +4,7 @@ import { AppInstallationResult, AppInstaller } from "../../../lib/resources/app/
|
|
|
4
4
|
export declare const pythonInstaller: AppInstaller<"site-title" | "entrypoint">;
|
|
5
5
|
export default class InstallPython extends ExecRenderBaseCommand<typeof InstallPython, AppInstallationResult> {
|
|
6
6
|
static description: string;
|
|
7
|
-
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("entrypoint" | ("wait" | "wait-timeout" | "site-title"))[]>>;
|
|
7
|
+
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("entrypoint" | ("wait" | "wait-timeout" | "site-title" | "install-path"))[]>>;
|
|
8
8
|
protected exec(): Promise<AppInstallationResult>;
|
|
9
9
|
protected render(result: AppInstallationResult): React.ReactNode;
|
|
10
10
|
}
|
|
@@ -4,7 +4,7 @@ import { AppInstallationResult, AppInstaller } from "../../../lib/resources/app/
|
|
|
4
4
|
export declare const staticInstaller: AppInstaller<"site-title" | "document-root">;
|
|
5
5
|
export default class InstallStatic extends ExecRenderBaseCommand<typeof InstallStatic, AppInstallationResult> {
|
|
6
6
|
static description: string;
|
|
7
|
-
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("document-root" | ("wait" | "wait-timeout" | "site-title"))[]>>;
|
|
7
|
+
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("document-root" | ("wait" | "wait-timeout" | "site-title" | "install-path"))[]>>;
|
|
8
8
|
protected exec(): Promise<AppInstallationResult>;
|
|
9
9
|
protected render(result: AppInstallationResult): React.ReactNode;
|
|
10
10
|
}
|
|
@@ -3,7 +3,7 @@ import React from "react";
|
|
|
3
3
|
import { AppInstallationResult } from "../../../lib/resources/app/Installer.js";
|
|
4
4
|
export default class InstallContao extends ExecRenderBaseCommand<typeof InstallContao, AppInstallationResult> {
|
|
5
5
|
static description: string;
|
|
6
|
-
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("version" | "host" | "admin-user" | "admin-email" | "admin-pass" | "admin-firstname" | "admin-lastname" | ("wait" | "wait-timeout" | "site-title"))[]>>;
|
|
6
|
+
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("version" | "host" | "admin-user" | "admin-email" | "admin-pass" | "admin-firstname" | "admin-lastname" | ("wait" | "wait-timeout" | "site-title" | "install-path"))[]>>;
|
|
7
7
|
protected exec(): Promise<AppInstallationResult>;
|
|
8
8
|
protected render(result: AppInstallationResult): React.ReactNode;
|
|
9
9
|
}
|
|
@@ -3,7 +3,7 @@ import React from "react";
|
|
|
3
3
|
import { AppInstallationResult } from "../../../lib/resources/app/Installer.js";
|
|
4
4
|
export default class InstallJoomla extends ExecRenderBaseCommand<typeof InstallJoomla, AppInstallationResult> {
|
|
5
5
|
static description: string;
|
|
6
|
-
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("version" | "host" | "admin-user" | "admin-email" | "admin-pass" | "admin-firstname" | "admin-lastname" | ("wait" | "wait-timeout" | "site-title"))[]>>;
|
|
6
|
+
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("version" | "host" | "admin-user" | "admin-email" | "admin-pass" | "admin-firstname" | "admin-lastname" | ("wait" | "wait-timeout" | "site-title" | "install-path"))[]>>;
|
|
7
7
|
protected exec(): Promise<AppInstallationResult>;
|
|
8
8
|
protected render(result: AppInstallationResult): React.ReactNode;
|
|
9
9
|
}
|
|
@@ -3,7 +3,7 @@ import React from "react";
|
|
|
3
3
|
import { AppInstallationResult } from "../../../lib/resources/app/Installer.js";
|
|
4
4
|
export default class InstallMatomo extends ExecRenderBaseCommand<typeof InstallMatomo, AppInstallationResult> {
|
|
5
5
|
static description: string;
|
|
6
|
-
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("version" | "host" | "admin-user" | "admin-email" | "admin-pass" | ("wait" | "wait-timeout" | "site-title"))[]>>;
|
|
6
|
+
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("version" | "host" | "admin-user" | "admin-email" | "admin-pass" | ("wait" | "wait-timeout" | "site-title" | "install-path"))[]>>;
|
|
7
7
|
protected exec(): Promise<AppInstallationResult>;
|
|
8
8
|
protected render(result: AppInstallationResult): React.ReactNode;
|
|
9
9
|
}
|
|
@@ -3,7 +3,7 @@ import React from "react";
|
|
|
3
3
|
import { AppInstallationResult } from "../../../lib/resources/app/Installer.js";
|
|
4
4
|
export default class InstallNextcloud extends ExecRenderBaseCommand<typeof InstallNextcloud, AppInstallationResult> {
|
|
5
5
|
static description: string;
|
|
6
|
-
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("version" | "host" | "admin-user" | "admin-email" | "admin-pass" | ("wait" | "wait-timeout" | "site-title"))[]>>;
|
|
6
|
+
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("version" | "host" | "admin-user" | "admin-email" | "admin-pass" | ("wait" | "wait-timeout" | "site-title" | "install-path"))[]>>;
|
|
7
7
|
protected exec(): Promise<AppInstallationResult>;
|
|
8
8
|
protected render(result: AppInstallationResult): React.ReactNode;
|
|
9
9
|
}
|
|
@@ -3,7 +3,7 @@ import React from "react";
|
|
|
3
3
|
import { AppInstallationResult } from "../../../lib/resources/app/Installer.js";
|
|
4
4
|
export default class InstallShopware5 extends ExecRenderBaseCommand<typeof InstallShopware5, AppInstallationResult> {
|
|
5
5
|
static description: string;
|
|
6
|
-
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("version" | "host" | "admin-user" | "admin-email" | "admin-pass" | "admin-firstname" | "admin-lastname" | "shop-email" | "shop-lang" | "shop-currency" | ("wait" | "wait-timeout" | "site-title"))[]>>;
|
|
6
|
+
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("version" | "host" | "admin-user" | "admin-email" | "admin-pass" | "admin-firstname" | "admin-lastname" | "shop-email" | "shop-lang" | "shop-currency" | ("wait" | "wait-timeout" | "site-title" | "install-path"))[]>>;
|
|
7
7
|
protected exec(): Promise<AppInstallationResult>;
|
|
8
8
|
protected render(result: AppInstallationResult): React.ReactNode;
|
|
9
9
|
}
|
|
@@ -4,7 +4,7 @@ import { AppInstallationResult, AppInstaller } from "../../../lib/resources/app/
|
|
|
4
4
|
export declare const shopware6Installer: AppInstaller<"version" | "host" | "admin-user" | "admin-email" | "admin-pass" | "admin-firstname" | "admin-lastname" | "site-title" | "shop-email" | "shop-lang" | "shop-currency">;
|
|
5
5
|
export default class InstallShopware6 extends ExecRenderBaseCommand<typeof InstallShopware6, AppInstallationResult> {
|
|
6
6
|
static description: string;
|
|
7
|
-
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("version" | "host" | "admin-user" | "admin-email" | "admin-pass" | "admin-firstname" | "admin-lastname" | "shop-email" | "shop-lang" | "shop-currency" | ("wait" | "wait-timeout" | "site-title"))[]>>;
|
|
7
|
+
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("version" | "host" | "admin-user" | "admin-email" | "admin-pass" | "admin-firstname" | "admin-lastname" | "shop-email" | "shop-lang" | "shop-currency" | ("wait" | "wait-timeout" | "site-title" | "install-path"))[]>>;
|
|
8
8
|
protected exec(): Promise<AppInstallationResult>;
|
|
9
9
|
protected render(result: AppInstallationResult): React.ReactNode;
|
|
10
10
|
}
|
|
@@ -4,7 +4,7 @@ import { AppInstallationResult, AppInstaller } from "../../../lib/resources/app/
|
|
|
4
4
|
export declare const typo3Installer: AppInstaller<"version" | "host" | "admin-user" | "admin-email" | "admin-pass" | "site-title" | "install-mode">;
|
|
5
5
|
export default class InstallTYPO3 extends ExecRenderBaseCommand<typeof InstallTYPO3, AppInstallationResult> {
|
|
6
6
|
static description: string;
|
|
7
|
-
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("version" | "host" | "admin-user" | "admin-email" | "admin-pass" | "install-mode" | ("wait" | "wait-timeout" | "site-title"))[]>>;
|
|
7
|
+
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("version" | "host" | "admin-user" | "admin-email" | "admin-pass" | "install-mode" | ("wait" | "wait-timeout" | "site-title" | "install-path"))[]>>;
|
|
8
8
|
protected exec(): Promise<AppInstallationResult>;
|
|
9
9
|
protected render(result: AppInstallationResult): React.ReactNode;
|
|
10
10
|
}
|
|
@@ -4,7 +4,7 @@ import { AppInstallationResult, AppInstaller } from "../../../lib/resources/app/
|
|
|
4
4
|
export declare const wordpressInstaller: AppInstaller<"version" | "host" | "admin-user" | "admin-email" | "admin-pass" | "site-title">;
|
|
5
5
|
export default class InstallWordPress extends ExecRenderBaseCommand<typeof InstallWordPress, AppInstallationResult> {
|
|
6
6
|
static description: string;
|
|
7
|
-
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("version" | "host" | "admin-user" | "admin-email" | "admin-pass" | ("wait" | "wait-timeout" | "site-title"))[]>>;
|
|
7
|
+
static flags: import("@oclif/core/interfaces").FlagInput<import("../../../lib/resources/app/flags.js").RelevantFlags<readonly ("version" | "host" | "admin-user" | "admin-email" | "admin-pass" | ("wait" | "wait-timeout" | "site-title" | "install-path"))[]>>;
|
|
8
8
|
protected exec(): Promise<AppInstallationResult>;
|
|
9
9
|
protected render(result: AppInstallationResult): React.ReactNode;
|
|
10
10
|
}
|
|
@@ -5,5 +5,8 @@ export declare class Open extends ExtendedBaseCommand<typeof Open> {
|
|
|
5
5
|
static args: {
|
|
6
6
|
"installation-id": import("@oclif/core/interfaces").Arg<string>;
|
|
7
7
|
};
|
|
8
|
+
static flags: {
|
|
9
|
+
backend: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
8
11
|
run(): Promise<void>;
|
|
9
12
|
}
|
|
@@ -3,27 +3,53 @@ import open from "open";
|
|
|
3
3
|
import { appInstallationArgs, withAppInstallationId, } from "../../lib/resources/app/flags.js";
|
|
4
4
|
import { ExtendedBaseCommand } from "../../lib/basecommands/ExtendedBaseCommand.js";
|
|
5
5
|
import buildAppURLsFromIngressList from "../../lib/resources/app/buildAppURLsFromIngressList.js";
|
|
6
|
+
import { Flags } from "@oclif/core";
|
|
6
7
|
export class Open extends ExtendedBaseCommand {
|
|
7
8
|
static summary = "Open an app installation in the browser.";
|
|
8
9
|
static description = "This command opens an app installation in the browser. For this to work, there needs to be at least one virtual host linked to the app installation.";
|
|
9
10
|
static args = { ...appInstallationArgs };
|
|
11
|
+
static flags = {
|
|
12
|
+
backend: Flags.boolean({
|
|
13
|
+
summary: "open the backend of the app installation",
|
|
14
|
+
description: "If this flag is set, the backend of the app installation will be opened instead of the frontend. This flag is only available for some types of apps (like PHP and Node.js).",
|
|
15
|
+
default: false,
|
|
16
|
+
required: false,
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
10
19
|
async run() {
|
|
11
20
|
const appInstallationId = await withAppInstallationId(this.apiClient, Open, this.flags, this.args, this.config);
|
|
12
21
|
const installation = await this.apiClient.app.getAppinstallation({
|
|
13
22
|
appInstallationId,
|
|
14
23
|
});
|
|
15
24
|
assertStatus(installation, 200);
|
|
16
|
-
const domains = await
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
const [appVersion, domains] = await Promise.all([
|
|
26
|
+
(async () => {
|
|
27
|
+
const appVersion = await this.apiClient.app.getAppversion({
|
|
28
|
+
appId: installation.data.appId,
|
|
29
|
+
appVersionId: installation.data.appVersion.desired,
|
|
30
|
+
});
|
|
31
|
+
assertStatus(appVersion, 200);
|
|
32
|
+
return appVersion.data;
|
|
33
|
+
})(),
|
|
34
|
+
(async () => {
|
|
35
|
+
const domains = await this.apiClient.domain.ingressListIngresses({
|
|
36
|
+
queryParameters: {
|
|
37
|
+
projectId: installation.data.projectId,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
assertStatus(domains, 200);
|
|
41
|
+
return domains.data;
|
|
42
|
+
})(),
|
|
43
|
+
]);
|
|
44
|
+
const urls = buildAppURLsFromIngressList(domains, installation.data.id);
|
|
23
45
|
if (urls.length === 0) {
|
|
24
46
|
throw new Error("This app installation is not linked to any virtual hosts.");
|
|
25
47
|
}
|
|
26
|
-
|
|
27
|
-
|
|
48
|
+
let url = urls[0];
|
|
49
|
+
if (this.flags.backend && appVersion.backendPathTemplate) {
|
|
50
|
+
url = appVersion.backendPathTemplate.replace("{domain}", url.replace(/\/$/, ""));
|
|
51
|
+
}
|
|
52
|
+
console.log("opening " + url);
|
|
53
|
+
await open(url);
|
|
28
54
|
}
|
|
29
55
|
}
|
|
@@ -28,8 +28,8 @@ export class PortForward extends ExecRenderBaseCommand {
|
|
|
28
28
|
required: true,
|
|
29
29
|
}),
|
|
30
30
|
port: PortMapping.arg({
|
|
31
|
-
summary: "Port mapping in the format 'local-port:container-port'",
|
|
32
|
-
description: "Specifies the port mapping between your local machine and the container. Format: 'local-port:container-port'. If not specified, available ports will be detected automatically.",
|
|
31
|
+
summary: "Port mapping in the format 'local-port:container-port' or 'port'",
|
|
32
|
+
description: "Specifies the port mapping between your local machine and the container. Format: 'local-port:container-port' or just 'port' (in which case the same port is used locally and in the container). If not specified, available ports will be detected automatically.",
|
|
33
33
|
required: false,
|
|
34
34
|
}),
|
|
35
35
|
};
|
|
@@ -19,6 +19,8 @@ export declare class Run extends ExecRenderBaseCommand<typeof Run, Result> {
|
|
|
19
19
|
"publish-all": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
20
20
|
volume: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
21
21
|
"create-volumes": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
22
|
+
cpus: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
23
|
+
memory: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
22
24
|
"project-id": import("@oclif/core/interfaces").OptionFlag<string>;
|
|
23
25
|
quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
24
26
|
};
|
|
@@ -46,6 +48,13 @@ export declare class Run extends ExecRenderBaseCommand<typeof Run, Result> {
|
|
|
46
48
|
* or undefined if no specific command is set.
|
|
47
49
|
*/
|
|
48
50
|
private buildContainerCommand;
|
|
51
|
+
/**
|
|
52
|
+
* Builds the deploy.resources structure from command line flags
|
|
53
|
+
*
|
|
54
|
+
* @returns The deploy configuration with resource limits, or undefined if no
|
|
55
|
+
* limits are specified
|
|
56
|
+
*/
|
|
57
|
+
private buildDeployResources;
|
|
49
58
|
/**
|
|
50
59
|
* Builds a container service request from command line arguments and image
|
|
51
60
|
* metadata
|
|
@@ -48,10 +48,10 @@ export class Run extends ExecRenderBaseCommand {
|
|
|
48
48
|
required: false,
|
|
49
49
|
}),
|
|
50
50
|
publish: Flags.string({
|
|
51
|
-
summary: "publish a container's port(s)
|
|
52
|
-
description: "
|
|
53
|
-
"Format: <
|
|
54
|
-
"For example, --publish 8080:80 maps port 80 in the container to port 8080
|
|
51
|
+
summary: "publish a container's port(s)",
|
|
52
|
+
description: "Expose a container's port within the cluster. " +
|
|
53
|
+
"Format: <cluster-port>:<container-port> or just <port> (in which case the same port is used for both cluster and container). " +
|
|
54
|
+
"For example, --publish 8080:80 maps port 80 in the container to port 8080 within the cluster, while --publish 8080 exposes port 8080 as port 8080. " +
|
|
55
55
|
"Use multiple --publish flags to publish multiple ports.\n\n" +
|
|
56
56
|
"NOTE: Please note that the usual shorthand -p is not supported for this flag, as it would conflict with the --project flag.",
|
|
57
57
|
required: false,
|
|
@@ -83,6 +83,19 @@ export class Run extends ExecRenderBaseCommand {
|
|
|
83
83
|
required: false,
|
|
84
84
|
default: false,
|
|
85
85
|
}),
|
|
86
|
+
cpus: Flags.string({
|
|
87
|
+
summary: "set CPU limit for the container",
|
|
88
|
+
description: "Specify the number of CPUs available to the container (e.g., '0.5', '1', '2'). " +
|
|
89
|
+
"This is equivalent to the docker run --cpus flag or the deploy.resources.limits.cpus field in docker-compose.",
|
|
90
|
+
required: false,
|
|
91
|
+
}),
|
|
92
|
+
memory: Flags.string({
|
|
93
|
+
summary: "set memory limit for the container",
|
|
94
|
+
description: "Specify the maximum amount of memory the container can use (e.g., '512m', '1g', '2g'). " +
|
|
95
|
+
"This is equivalent to the docker run --memory flag or the deploy.resources.limits.memory field in docker-compose.",
|
|
96
|
+
required: false,
|
|
97
|
+
char: "m",
|
|
98
|
+
}),
|
|
86
99
|
};
|
|
87
100
|
static args = {
|
|
88
101
|
image: Args.string({
|
|
@@ -180,6 +193,29 @@ export class Run extends ExecRenderBaseCommand {
|
|
|
180
193
|
const command = [this.args.command, ...this.argv.slice(firstArg)];
|
|
181
194
|
return command;
|
|
182
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Builds the deploy.resources structure from command line flags
|
|
198
|
+
*
|
|
199
|
+
* @returns The deploy configuration with resource limits, or undefined if no
|
|
200
|
+
* limits are specified
|
|
201
|
+
*/
|
|
202
|
+
buildDeployResources() {
|
|
203
|
+
if (!this.flags.cpus && !this.flags.memory) {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
const limits = {};
|
|
207
|
+
if (this.flags.cpus) {
|
|
208
|
+
limits.cpus = this.flags.cpus;
|
|
209
|
+
}
|
|
210
|
+
if (this.flags.memory) {
|
|
211
|
+
limits.memory = this.flags.memory;
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
resources: {
|
|
215
|
+
limits,
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
183
219
|
/**
|
|
184
220
|
* Builds a container service request from command line arguments and image
|
|
185
221
|
* metadata
|
|
@@ -198,6 +234,7 @@ export class Run extends ExecRenderBaseCommand {
|
|
|
198
234
|
const environment = await parseEnvironmentVariables(this.flags.env, this.flags["env-file"]);
|
|
199
235
|
const ports = getPortMappings(imageMeta, this.flags["publish-all"], this.flags.publish);
|
|
200
236
|
const volumes = this.flags.volume;
|
|
237
|
+
const deploy = this.buildDeployResources();
|
|
201
238
|
return {
|
|
202
239
|
image,
|
|
203
240
|
command,
|
|
@@ -206,6 +243,7 @@ export class Run extends ExecRenderBaseCommand {
|
|
|
206
243
|
environment,
|
|
207
244
|
ports,
|
|
208
245
|
volumes,
|
|
246
|
+
deploy,
|
|
209
247
|
};
|
|
210
248
|
}
|
|
211
249
|
async getImageAndMeta(projectId) {
|
|
@@ -50,8 +50,8 @@ export class Update extends ExecRenderBaseCommand {
|
|
|
50
50
|
}),
|
|
51
51
|
publish: Flags.string({
|
|
52
52
|
summary: "update the container's port mappings",
|
|
53
|
-
description: "
|
|
54
|
-
"Format: <
|
|
53
|
+
description: "Expose a container's port within the cluster. " +
|
|
54
|
+
"Format: <cluster-port>:<container-port> or just <port> (in which case the same port is used for both cluster and container). " +
|
|
55
55
|
"Use multiple -p flags to publish multiple ports.",
|
|
56
56
|
required: false,
|
|
57
57
|
multiple: true,
|
|
@@ -30,7 +30,7 @@ export class Create extends ExecRenderBaseCommand {
|
|
|
30
30
|
default: "utf8mb4",
|
|
31
31
|
}),
|
|
32
32
|
"user-password": Flags.string({
|
|
33
|
-
summary: "the password to use for the default user
|
|
33
|
+
summary: "the password to use for the default user",
|
|
34
34
|
env: "MYSQL_PWD",
|
|
35
35
|
}),
|
|
36
36
|
"user-external": Flags.boolean({
|
|
@@ -32,9 +32,10 @@ export function configureAxiosRetry(axios) {
|
|
|
32
32
|
return true;
|
|
33
33
|
}
|
|
34
34
|
const isSafeRequest = error.config?.method?.toLowerCase() === "get";
|
|
35
|
+
const isConditionalRequest = !!error.config?.headers?.["if-event-reached"];
|
|
35
36
|
const isPreconditionFailed = error.response?.status === 412;
|
|
36
37
|
const isAccessDenied = error.response?.status === 403;
|
|
37
|
-
if (isPreconditionFailed) {
|
|
38
|
+
if (isPreconditionFailed && isConditionalRequest) {
|
|
38
39
|
return true;
|
|
39
40
|
}
|
|
40
41
|
return isSafeRequest && isAccessDenied && shouldRetryAccessDenied;
|
|
@@ -52,6 +52,7 @@ export class RenderBaseCommand extends ExtendedBaseCommand {
|
|
|
52
52
|
useIncreaseInkStdoutColumns();
|
|
53
53
|
return this.render();
|
|
54
54
|
} }) }) }) }) }));
|
|
55
|
+
await handle.waitUntilExit();
|
|
55
56
|
}
|
|
56
57
|
useAppInstallationId(command) {
|
|
57
58
|
return usePromise(() => this.withAppInstallationId(command), []);
|
|
@@ -5,7 +5,7 @@ import { MittwaldAPIV2, MittwaldAPIV2Client } from "@mittwald/api-client";
|
|
|
5
5
|
import { Config } from "@oclif/core";
|
|
6
6
|
type AppVersion = MittwaldAPIV2.Components.Schemas.AppAppVersion;
|
|
7
7
|
type AppInstallation = MittwaldAPIV2.Components.Schemas.AppAppInstallation;
|
|
8
|
-
type ImplicitDefaultFlag = "wait" | "wait-timeout" | "site-title";
|
|
8
|
+
type ImplicitDefaultFlag = "wait" | "wait-timeout" | "site-title" | "install-path";
|
|
9
9
|
export interface AppInstallationResult {
|
|
10
10
|
appInstallation: AppInstallation;
|
|
11
11
|
appVersion: AppVersion;
|
|
@@ -25,7 +25,13 @@ export class AppInstaller {
|
|
|
25
25
|
this.description = AppInstaller.makeDescription(appName);
|
|
26
26
|
}
|
|
27
27
|
get flags() {
|
|
28
|
-
const flags = provideSupportedFlags([
|
|
28
|
+
const flags = provideSupportedFlags([
|
|
29
|
+
...this.appSupportedFlags,
|
|
30
|
+
"wait",
|
|
31
|
+
"wait-timeout",
|
|
32
|
+
"site-title",
|
|
33
|
+
"install-path",
|
|
34
|
+
], this.appName);
|
|
29
35
|
if (this.mutateFlags) {
|
|
30
36
|
this.mutateFlags(flags);
|
|
31
37
|
}
|
|
@@ -22,6 +22,7 @@ type AvailableFlags = typeof waitFlags & {
|
|
|
22
22
|
"shop-lang": OptionFlag<string | undefined>;
|
|
23
23
|
"shop-currency": OptionFlag<string | undefined>;
|
|
24
24
|
"install-mode": OptionFlag<string>;
|
|
25
|
+
"install-path": OptionFlag<string>;
|
|
25
26
|
"document-root": OptionFlag<string>;
|
|
26
27
|
"opensearch-host": OptionFlag<string>;
|
|
27
28
|
"opensearch-port": OptionFlag<string>;
|
|
@@ -113,6 +113,12 @@ function buildFlagsWithDescription(appName) {
|
|
|
113
113
|
options: ["composer", "symlink"],
|
|
114
114
|
default: "composer",
|
|
115
115
|
}),
|
|
116
|
+
"install-path": Flags.string({
|
|
117
|
+
required: false,
|
|
118
|
+
summary: `the installation path of your ${appName} application`,
|
|
119
|
+
description: "This is the path where your application will be installed. If omitted, this will default to an automatically-generated path.",
|
|
120
|
+
default: undefined,
|
|
121
|
+
}),
|
|
116
122
|
"document-root": Flags.string({
|
|
117
123
|
required: true,
|
|
118
124
|
summary: `the document root from which your ${appName} will be served (relative to the installation path)`,
|
|
@@ -5,6 +5,7 @@ type AppAppVersion = MittwaldAPIV2.Components.Schemas.AppAppVersion;
|
|
|
5
5
|
export declare function triggerAppInstallation(apiClient: MittwaldAPIV2Client, process: ProcessRenderer, projectId: string, flags: {
|
|
6
6
|
"site-title": string;
|
|
7
7
|
"document-root"?: string;
|
|
8
|
+
"install-path"?: string;
|
|
8
9
|
} & {
|
|
9
10
|
[k: string]: unknown;
|
|
10
11
|
}, appVersion: AppAppVersion): Promise<AppAppInstallation>;
|
|
@@ -7,6 +7,7 @@ export async function triggerAppInstallation(apiClient, process, projectId, flag
|
|
|
7
7
|
appVersionId: appVersion.id,
|
|
8
8
|
description: flags["site-title"],
|
|
9
9
|
updatePolicy: "none",
|
|
10
|
+
installationPath: flags["install-path"],
|
|
10
11
|
userInputs: Object.keys(flags).map((k) => ({
|
|
11
12
|
name: k.replace("-", "_"),
|
|
12
13
|
value: flags[k],
|
|
@@ -3,7 +3,7 @@ import { assertStatus } from "@mittwald/api-client-commons";
|
|
|
3
3
|
export const mysqlConnectionFlags = {
|
|
4
4
|
"mysql-password": Flags.string({
|
|
5
5
|
char: "p",
|
|
6
|
-
summary: "the password to use for the MySQL user
|
|
6
|
+
summary: "the password to use for the MySQL user",
|
|
7
7
|
description: `\
|
|
8
8
|
The password to use for the MySQL user. If not provided, the environment variable MYSQL_PWD will be used. If that is not set either, the command will interactively ask for the password.
|
|
9
9
|
|
|
@@ -4,6 +4,8 @@ export default class PortMapping {
|
|
|
4
4
|
readonly remotePort: number;
|
|
5
5
|
constructor(localPort: number, remotePort: number);
|
|
6
6
|
private static validatePort;
|
|
7
|
+
private static isValidPortString;
|
|
8
|
+
private static parseAndValidatePort;
|
|
7
9
|
static arg: import("@oclif/core/interfaces").ArgDefinition<PortMapping, Record<string, unknown>>;
|
|
8
10
|
/** @param str Port and protocol; example: `8080/tcp` */
|
|
9
11
|
static fromPortAndProtocol(str: string): PortMapping;
|
|
@@ -10,6 +10,19 @@ export default class PortMapping {
|
|
|
10
10
|
static validatePort(port) {
|
|
11
11
|
return !isNaN(port) && port > 0 && port <= 65535;
|
|
12
12
|
}
|
|
13
|
+
static isValidPortString(str) {
|
|
14
|
+
return /^\d+$/.test(str);
|
|
15
|
+
}
|
|
16
|
+
static parseAndValidatePort(str) {
|
|
17
|
+
if (!PortMapping.isValidPortString(str)) {
|
|
18
|
+
throw new Error("Invalid port number. Ports must be between 1 and 65535.");
|
|
19
|
+
}
|
|
20
|
+
const portNum = parseInt(str);
|
|
21
|
+
if (!PortMapping.validatePort(portNum)) {
|
|
22
|
+
throw new Error("Invalid port number. Ports must be between 1 and 65535.");
|
|
23
|
+
}
|
|
24
|
+
return portNum;
|
|
25
|
+
}
|
|
13
26
|
static arg = Args.custom({
|
|
14
27
|
parse: async (input) => PortMapping.fromString(input),
|
|
15
28
|
});
|
|
@@ -26,13 +39,15 @@ export default class PortMapping {
|
|
|
26
39
|
return new PortMapping(portNum, portNum);
|
|
27
40
|
}
|
|
28
41
|
static fromString(str) {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
throw new Error("Invalid port number. Ports must be between 1 and 65535.");
|
|
42
|
+
const parts = str.split(":");
|
|
43
|
+
// If only one part, use it for both local and remote port
|
|
44
|
+
if (parts.length === 1) {
|
|
45
|
+
const portNum = PortMapping.parseAndValidatePort(parts[0]);
|
|
46
|
+
return new PortMapping(portNum, portNum);
|
|
35
47
|
}
|
|
48
|
+
const [localPort, remotePort] = parts;
|
|
49
|
+
const localPortNum = PortMapping.parseAndValidatePort(localPort);
|
|
50
|
+
const remotePortNum = PortMapping.parseAndValidatePort(remotePort);
|
|
36
51
|
return new PortMapping(localPortNum, remotePortNum);
|
|
37
52
|
}
|
|
38
53
|
}
|
|
@@ -7,6 +7,12 @@ describe("PortMapping", () => {
|
|
|
7
7
|
expect(result.localPort).toBe(8080);
|
|
8
8
|
expect(result.remotePort).toBe(9090);
|
|
9
9
|
});
|
|
10
|
+
// Test: Successfully parse single integer as identical local and remote port
|
|
11
|
+
it("should correctly parse single integer to identical ports", () => {
|
|
12
|
+
const result = PortMapping.fromString("8080");
|
|
13
|
+
expect(result.localPort).toBe(8080);
|
|
14
|
+
expect(result.remotePort).toBe(8080);
|
|
15
|
+
});
|
|
10
16
|
// Test: Throws an error for invalid local port
|
|
11
17
|
it("should throw an error for invalid local port", () => {
|
|
12
18
|
expect(() => PortMapping.fromString("100000:8080")).toThrow("Invalid port number. Ports must be between 1 and 65535.");
|
|
@@ -19,6 +25,10 @@ describe("PortMapping", () => {
|
|
|
19
25
|
it("should throw an error for invalid string format", () => {
|
|
20
26
|
expect(() => PortMapping.fromString("8080-9090")).toThrow("Invalid port number. Ports must be between 1 and 65535.");
|
|
21
27
|
});
|
|
28
|
+
// Test: Throws an error for invalid single port
|
|
29
|
+
it("should throw an error for invalid single port", () => {
|
|
30
|
+
expect(() => PortMapping.fromString("100000")).toThrow("Invalid port number. Ports must be between 1 and 65535.");
|
|
31
|
+
});
|
|
22
32
|
// Test: Successfully assign local and remote ports via constructor
|
|
23
33
|
it("should correctly initialize PortMapping with valid ports", () => {
|
|
24
34
|
const portMapping = new PortMapping(3000, 4000);
|