@mittwald/cli 1.5.1 → 1.7.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.
Files changed (130) hide show
  1. package/README.md +1 -1
  2. package/dist/commands/app/dependency/list.d.ts +1 -0
  3. package/dist/commands/app/dependency/versions.d.ts +1 -0
  4. package/dist/commands/app/get.js +1 -2
  5. package/dist/commands/app/list-upgrade-candidates.d.ts +1 -0
  6. package/dist/commands/app/list.d.ts +1 -0
  7. package/dist/commands/app/upgrade.d.ts +14 -0
  8. package/dist/commands/app/upgrade.js +35 -22
  9. package/dist/commands/backup/delete.d.ts +1 -0
  10. package/dist/commands/backup/get.js +1 -2
  11. package/dist/commands/backup/list.d.ts +1 -0
  12. package/dist/commands/backup/schedule/list.d.ts +1 -0
  13. package/dist/commands/container/delete.d.ts +1 -0
  14. package/dist/commands/container/exec.d.ts +25 -0
  15. package/dist/commands/container/exec.js +93 -0
  16. package/dist/commands/container/list.d.ts +1 -0
  17. package/dist/commands/container/logs.d.ts +1 -0
  18. package/dist/commands/container/port-forward.d.ts +20 -0
  19. package/dist/commands/container/port-forward.js +68 -0
  20. package/dist/commands/container/run.d.ts +12 -0
  21. package/dist/commands/container/run.js +27 -8
  22. package/dist/commands/container/ssh.d.ts +17 -0
  23. package/dist/commands/container/ssh.js +61 -0
  24. package/dist/commands/conversation/categories.d.ts +1 -0
  25. package/dist/commands/conversation/create.d.ts +1 -0
  26. package/dist/commands/conversation/list.d.ts +1 -0
  27. package/dist/commands/conversation/reply.d.ts +1 -0
  28. package/dist/commands/cronjob/delete.d.ts +1 -0
  29. package/dist/commands/cronjob/execution/get.d.ts +1 -1
  30. package/dist/commands/cronjob/execution/list.d.ts +1 -0
  31. package/dist/commands/cronjob/execution/logs.d.ts +1 -0
  32. package/dist/commands/cronjob/get.js +1 -2
  33. package/dist/commands/cronjob/list.d.ts +1 -0
  34. package/dist/commands/database/list.d.ts +1 -0
  35. package/dist/commands/database/mysql/charsets.d.ts +1 -0
  36. package/dist/commands/database/mysql/delete.d.ts +1 -0
  37. package/dist/commands/database/mysql/get.d.ts +1 -1
  38. package/dist/commands/database/mysql/list.d.ts +1 -0
  39. package/dist/commands/database/mysql/user/delete.d.ts +1 -0
  40. package/dist/commands/database/mysql/user/get.d.ts +1 -1
  41. package/dist/commands/database/mysql/user/list.d.ts +1 -0
  42. package/dist/commands/database/mysql/versions.d.ts +1 -0
  43. package/dist/commands/database/redis/get.d.ts +1 -1
  44. package/dist/commands/database/redis/list.d.ts +1 -0
  45. package/dist/commands/database/redis/versions.d.ts +1 -0
  46. package/dist/commands/domain/dnszone/get.js +1 -2
  47. package/dist/commands/domain/dnszone/list.d.ts +1 -0
  48. package/dist/commands/domain/dnszone/update.js +3 -2
  49. package/dist/commands/domain/get.js +1 -2
  50. package/dist/commands/domain/list.d.ts +1 -0
  51. package/dist/commands/domain/virtualhost/delete.d.ts +1 -0
  52. package/dist/commands/domain/virtualhost/get.js +1 -2
  53. package/dist/commands/domain/virtualhost/list.d.ts +1 -0
  54. package/dist/commands/extension/list-installed.d.ts +1 -0
  55. package/dist/commands/extension/list.d.ts +1 -0
  56. package/dist/commands/mail/address/delete.d.ts +1 -0
  57. package/dist/commands/mail/address/get.d.ts +1 -1
  58. package/dist/commands/mail/address/list.d.ts +1 -0
  59. package/dist/commands/mail/deliverybox/delete.d.ts +1 -0
  60. package/dist/commands/mail/deliverybox/get.d.ts +1 -1
  61. package/dist/commands/mail/deliverybox/list.d.ts +1 -0
  62. package/dist/commands/org/delete.d.ts +1 -0
  63. package/dist/commands/org/get.js +1 -2
  64. package/dist/commands/org/invite/list-own.d.ts +1 -0
  65. package/dist/commands/org/invite/list.d.ts +1 -0
  66. package/dist/commands/org/list.d.ts +1 -0
  67. package/dist/commands/org/membership/list-own.d.ts +1 -0
  68. package/dist/commands/org/membership/list.d.ts +1 -0
  69. package/dist/commands/project/delete.d.ts +1 -0
  70. package/dist/commands/project/filesystem/usage.js +1 -2
  71. package/dist/commands/project/get.js +1 -2
  72. package/dist/commands/project/invite/get.d.ts +1 -1
  73. package/dist/commands/project/invite/list-own.d.ts +1 -0
  74. package/dist/commands/project/invite/list.d.ts +1 -0
  75. package/dist/commands/project/list.d.ts +1 -0
  76. package/dist/commands/project/membership/get-own.d.ts +1 -0
  77. package/dist/commands/project/membership/get.d.ts +1 -1
  78. package/dist/commands/project/membership/list-own.d.ts +1 -0
  79. package/dist/commands/project/membership/list.d.ts +1 -0
  80. package/dist/commands/registry/delete.d.ts +1 -0
  81. package/dist/commands/registry/list.d.ts +1 -0
  82. package/dist/commands/server/get.d.ts +1 -1
  83. package/dist/commands/server/list.d.ts +1 -0
  84. package/dist/commands/sftp-user/delete.d.ts +1 -0
  85. package/dist/commands/sftp-user/list.d.ts +1 -0
  86. package/dist/commands/ssh-user/delete.d.ts +1 -0
  87. package/dist/commands/ssh-user/list.d.ts +1 -0
  88. package/dist/commands/stack/delete.d.ts +1 -0
  89. package/dist/commands/stack/deploy.js +1 -8
  90. package/dist/commands/stack/list.d.ts +1 -0
  91. package/dist/commands/stack/ps.d.ts +1 -0
  92. package/dist/commands/stack/ps.js +1 -1
  93. package/dist/commands/user/api-token/get.d.ts +1 -1
  94. package/dist/commands/user/api-token/list.d.ts +1 -0
  95. package/dist/commands/user/api-token/revoke.d.ts +1 -0
  96. package/dist/commands/user/get.d.ts +1 -1
  97. package/dist/commands/user/session/get.d.ts +1 -1
  98. package/dist/commands/user/session/list.d.ts +1 -0
  99. package/dist/commands/user/ssh-key/delete.d.ts +1 -0
  100. package/dist/commands/user/ssh-key/get.d.ts +1 -1
  101. package/dist/commands/user/ssh-key/list.d.ts +1 -0
  102. package/dist/lib/basecommands/BaseCommand.d.ts +7 -5
  103. package/dist/lib/basecommands/BaseCommand.js +26 -8
  104. package/dist/lib/basecommands/CommandFlags.d.ts +3 -3
  105. package/dist/lib/basecommands/CoreBaseCommand.d.ts +12 -0
  106. package/dist/lib/basecommands/CoreBaseCommand.js +16 -0
  107. package/dist/lib/basecommands/DeleteBaseCommand.d.ts +1 -0
  108. package/dist/lib/basecommands/DeleteBaseCommand.js +1 -0
  109. package/dist/lib/basecommands/ExecRenderBaseCommand.d.ts +3 -0
  110. package/dist/lib/basecommands/ExecRenderBaseCommand.js +3 -0
  111. package/dist/lib/basecommands/ExtendedBaseCommand.d.ts +3 -0
  112. package/dist/lib/basecommands/ExtendedBaseCommand.js +3 -0
  113. package/dist/lib/basecommands/GetBaseCommand.d.ts +1 -1
  114. package/dist/lib/basecommands/GetBaseCommand.js +1 -0
  115. package/dist/lib/basecommands/ListBaseCommand.d.ts +1 -0
  116. package/dist/lib/basecommands/ListBaseCommand.js +1 -0
  117. package/dist/lib/basecommands/UnauthenticatedBaseCommand.d.ts +10 -0
  118. package/dist/lib/basecommands/UnauthenticatedBaseCommand.js +16 -0
  119. package/dist/lib/error/NoTokenFoundError.d.ts +11 -0
  120. package/dist/lib/error/NoTokenFoundError.js +13 -0
  121. package/dist/lib/resources/ssh/container.d.ts +3 -0
  122. package/dist/lib/resources/ssh/container.js +29 -0
  123. package/dist/lib/resources/stack/enrich.d.ts +2 -2
  124. package/dist/lib/resources/stack/enrich.js +1 -31
  125. package/dist/lib/units/PortMapping.d.ts +11 -0
  126. package/dist/lib/units/PortMapping.js +38 -0
  127. package/dist/lib/units/PortMapping.test.d.ts +1 -0
  128. package/dist/lib/units/PortMapping.test.js +28 -0
  129. package/dist/rendering/react/components/ErrorBox.js +4 -0
  130. package/package.json +67 -19
package/README.md CHANGED
@@ -107,7 +107,7 @@ USAGE
107
107
  * [`mw app`](docs/app.md) - Manage apps, and app installations in your projects
108
108
  * [`mw autocomplete`](docs/autocomplete.md) - Display autocomplete installation instructions.
109
109
  * [`mw backup`](docs/backup.md) - Manage backups of your projects
110
- * [`mw container`](docs/container.md) - Delete a container
110
+ * [`mw container`](docs/container.md) - Manage containers
111
111
  * [`mw context`](docs/context.md) - Save certain environment parameters for later use
112
112
  * [`mw conversation`](docs/conversation.md) - Manage your support cases
113
113
  * [`mw cronjob`](docs/cronjob.md) - Manage cronjobs of your projects
@@ -14,6 +14,7 @@ export declare class List extends ListBaseCommand<typeof List, ResponseItem, Res
14
14
  "no-truncate": import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
15
  "no-relative-dates": import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
16
  "csv-separator": import("@oclif/core/interfaces").OptionFlag<"," | ";">;
17
+ token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
18
  };
18
19
  protected sorter: SorterFunction<ResponseItem>;
19
20
  getData(): Promise<Response>;
@@ -16,6 +16,7 @@ export declare class Versions extends ListBaseCommand<typeof Versions, ResponseI
16
16
  "no-truncate": import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
17
  "no-relative-dates": import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
18
  "csv-separator": import("@oclif/core/interfaces").OptionFlag<"," | ";">;
19
+ token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
20
  };
20
21
  sorter: SorterFunction<ResponseItem>;
21
22
  getData(): Promise<Response>;
@@ -1,13 +1,12 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { RenderBaseCommand } from "../../lib/basecommands/RenderBaseCommand.js";
3
3
  import { RenderJson } from "../../rendering/react/json/RenderJson.js";
4
- import { GetBaseCommand } from "../../lib/basecommands/GetBaseCommand.js";
5
4
  import { AppInstallationDetails } from "../../rendering/react/components/AppInstallation/AppInstallationDetails.js";
6
5
  import { useApp, useAppInstallation } from "../../lib/resources/app/hooks.js";
7
6
  import { appInstallationArgs } from "../../lib/resources/app/flags.js";
8
7
  export default class Get extends RenderBaseCommand {
9
8
  static description = "Get details about an app installation";
10
- static flags = { ...GetBaseCommand.baseFlags };
9
+ static flags = { ...RenderBaseCommand.buildFlags() };
11
10
  static args = { ...appInstallationArgs };
12
11
  render() {
13
12
  const appInstallationId = this.useAppInstallationId(Get);
@@ -16,6 +16,7 @@ export default class List extends ListBaseCommand<typeof List, ResponseItem, Res
16
16
  "no-truncate": import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
17
  "no-relative-dates": import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
18
  "csv-separator": import("@oclif/core/interfaces").OptionFlag<"," | ";">;
19
+ token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
20
  };
20
21
  protected getData(): Promise<Response>;
21
22
  protected mapData(data: ResponseItem[]): Promise<ResponseItem[]>;
@@ -22,6 +22,7 @@ export default class List extends ListBaseCommand<typeof List, ResponseItem, Res
22
22
  "no-truncate": import("@oclif/core/interfaces").BooleanFlag<boolean>;
23
23
  "no-relative-dates": import("@oclif/core/interfaces").BooleanFlag<boolean>;
24
24
  "csv-separator": import("@oclif/core/interfaces").OptionFlag<"," | ";">;
25
+ token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
25
26
  };
26
27
  protected getData(): Promise<Response>;
27
28
  protected mapData(data: SuccessfulResponse<Response, 200>["data"]): Promise<ExtendedResponseItem[]>;
@@ -14,5 +14,19 @@ export declare class UpgradeApp extends ExecRenderBaseCommand<typeof UpgradeApp,
14
14
  force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
15
  };
16
16
  protected exec(): Promise<void>;
17
+ /**
18
+ * Determines the target application version based on the provided input and
19
+ * available upgrade candidates.
20
+ *
21
+ * @param currentApp The current application instance.
22
+ * @param currentAppVersion The current version of the application.
23
+ * @param targetAppVersionCandidates List of potential target application
24
+ * versions.
25
+ * @param process The process renderer to handle user interactions and display
26
+ * information.
27
+ * @returns The determined target application version, or undefined if not
28
+ * resolved.
29
+ */
30
+ private determineTargetAppVersion;
17
31
  protected render(): ReactNode;
18
32
  }
@@ -11,7 +11,7 @@ import { Success } from "../../rendering/react/components/Success.js";
11
11
  import { waitUntilAppStateHasNormalized } from "../../lib/resources/app/wait.js";
12
12
  import { assertStatus } from "@mittwald/api-client-commons";
13
13
  import { waitFlags } from "../../lib/wait.js";
14
- import semver from "semver/preload.js";
14
+ import semver from "semver";
15
15
  export class UpgradeApp extends ExecRenderBaseCommand {
16
16
  static description = "Upgrade app installation to target version";
17
17
  static args = {
@@ -19,11 +19,11 @@ export class UpgradeApp extends ExecRenderBaseCommand {
19
19
  };
20
20
  static flags = {
21
21
  "target-version": Flags.string({
22
- description: "target version to upgrade app to; if omitted, target version will be prompted interactively",
22
+ description: "target version to upgrade app to; if omitted, target version will be prompted interactively. May also be a semantic versioning range, e.g. ^1.0.0. If set to 'latest', the latest available version will be used.",
23
23
  }),
24
24
  force: Flags.boolean({
25
25
  char: "f",
26
- description: "Do not ask for confirmation.",
26
+ summary: "do not ask for confirmation.",
27
27
  }),
28
28
  ...projectFlags,
29
29
  ...processFlags,
@@ -43,25 +43,7 @@ export class UpgradeApp extends ExecRenderBaseCommand {
43
43
  process.complete(_jsxs(Text, { children: ["Your ", currentApp.name, " ", currentAppVersion.externalVersion, " is already Up-To-Date. \u2705"] }));
44
44
  return;
45
45
  }
46
- let targetAppVersion;
47
- if (this.flags["target-version"] == "latest") {
48
- targetAppVersion =
49
- (await getLatestAvailableTargetAppVersionForAppVersionUpgradeCandidates(this.apiClient, currentApp.id, currentAppVersion.id));
50
- }
51
- else if (this.flags["target-version"]) {
52
- const targetVersionMatchFromCandidates = targetAppVersionCandidates.find((targetAppVersionCandidate) => targetAppVersionCandidate.externalVersion ===
53
- this.flags["target-version"]);
54
- if (targetVersionMatchFromCandidates) {
55
- targetAppVersion = targetVersionMatchFromCandidates;
56
- }
57
- else {
58
- process.addInfo(_jsx(Text, { children: "The given target upgrade version does not seem to be a valid upgrade candidate." }));
59
- targetAppVersion = (await forceTargetVersionSelection(process, this.apiClient, targetAppVersionCandidates, currentApp, currentAppVersion));
60
- }
61
- }
62
- else {
63
- targetAppVersion = (await forceTargetVersionSelection(process, this.apiClient, targetAppVersionCandidates, currentApp, currentAppVersion));
64
- }
46
+ const targetAppVersion = await this.determineTargetAppVersion(currentApp, currentAppVersion, targetAppVersionCandidates, process);
65
47
  if (!targetAppVersion) {
66
48
  process.error("Target app version could not be determined properly.");
67
49
  ux.exit(1);
@@ -121,6 +103,37 @@ export class UpgradeApp extends ExecRenderBaseCommand {
121
103
  }
122
104
  await process.complete(_jsx(Success, { children: successText }));
123
105
  }
106
+ /**
107
+ * Determines the target application version based on the provided input and
108
+ * available upgrade candidates.
109
+ *
110
+ * @param currentApp The current application instance.
111
+ * @param currentAppVersion The current version of the application.
112
+ * @param targetAppVersionCandidates List of potential target application
113
+ * versions.
114
+ * @param process The process renderer to handle user interactions and display
115
+ * information.
116
+ * @returns The determined target application version, or undefined if not
117
+ * resolved.
118
+ */
119
+ async determineTargetAppVersion(currentApp, currentAppVersion, targetAppVersionCandidates, process) {
120
+ const targetAppVersionString = this.flags["target-version"];
121
+ if (targetAppVersionString == "latest") {
122
+ return await getLatestAvailableTargetAppVersionForAppVersionUpgradeCandidates(this.apiClient, currentApp.id, currentAppVersion.id);
123
+ }
124
+ if (targetAppVersionString) {
125
+ const exactVersionMatch = targetAppVersionCandidates.find((v) => v.externalVersion === targetAppVersionString);
126
+ if (exactVersionMatch) {
127
+ return exactVersionMatch;
128
+ }
129
+ const semverMatch = targetAppVersionCandidates.findLast((v) => semver.satisfies(v.externalVersion, targetAppVersionString));
130
+ if (semverMatch) {
131
+ return semverMatch;
132
+ }
133
+ process.addInfo(_jsx(Text, { children: "The given target upgrade version does not seem to be a valid upgrade candidate." }));
134
+ }
135
+ return await forceTargetVersionSelection(process, this.apiClient, targetAppVersionCandidates, currentApp, currentAppVersion);
136
+ }
124
137
  render() {
125
138
  return true;
126
139
  }
@@ -5,6 +5,7 @@ export declare class Delete extends DeleteBaseCommand<typeof Delete> {
5
5
  static flags: {
6
6
  force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
7
  quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
9
  };
9
10
  static args: {
10
11
  "backup-id": import("@oclif/core/interfaces").Arg<string>;
@@ -3,7 +3,6 @@ import { RenderBaseCommand } from "../../lib/basecommands/RenderBaseCommand.js";
3
3
  import { useProjectBackup } from "../../lib/resources/projectbackup/hooks.js";
4
4
  import { ProjectBackupDetails } from "../../rendering/react/components/ProjectBackup/ProjectBackupDetails.js";
5
5
  import { RenderJson } from "../../rendering/react/json/RenderJson.js";
6
- import { GetBaseCommand } from "../../lib/basecommands/GetBaseCommand.js";
7
6
  import { Box } from "ink";
8
7
  import { backupArgs, withBackupId } from "../../lib/resources/backup/flags.js";
9
8
  import { usePromise } from "@mittwald/react-use-promise";
@@ -11,7 +10,7 @@ export default class Get extends RenderBaseCommand {
11
10
  static description = "Show details of a backup.";
12
11
  static args = { ...backupArgs };
13
12
  static flags = {
14
- ...GetBaseCommand.baseFlags,
13
+ ...RenderBaseCommand.buildFlags(),
15
14
  };
16
15
  static aliases = ["project:backup:get"];
17
16
  static deprecateAliases = true;
@@ -16,6 +16,7 @@ export declare class List extends ListBaseCommand<typeof List, ListItem, ListRes
16
16
  "no-truncate": import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
17
  "no-relative-dates": import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
18
  "csv-separator": import("@oclif/core/interfaces").OptionFlag<"," | ";">;
19
+ token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
20
  };
20
21
  static aliases: string[];
21
22
  static deprecateAliases: boolean;
@@ -16,6 +16,7 @@ export declare class List extends ListBaseCommand<typeof List, ResponseItem, Res
16
16
  "no-truncate": import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
17
  "no-relative-dates": import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
18
  "csv-separator": import("@oclif/core/interfaces").OptionFlag<"," | ";">;
19
+ token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
20
  };
20
21
  static aliases: string[];
21
22
  static deprecateAliases: boolean;
@@ -7,6 +7,7 @@ export default class Delete extends DeleteBaseCommand<typeof Delete> {
7
7
  "project-id": import("@oclif/core/interfaces").OptionFlag<string>;
8
8
  force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
9
  quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
11
  };
11
12
  static args: {
12
13
  "container-id": import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
@@ -0,0 +1,25 @@
1
+ import { ExtendedBaseCommand } from "../../lib/basecommands/ExtendedBaseCommand.js";
2
+ export default class Exec extends ExtendedBaseCommand<typeof Exec> {
3
+ static summary: string;
4
+ static description: string;
5
+ static args: {
6
+ "container-id": import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ command: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
8
+ };
9
+ static flags: {
10
+ workdir: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ env: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ shell: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ "project-id": import("@oclif/core/interfaces").OptionFlag<string>;
14
+ "ssh-user": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
15
+ "ssh-identity-file": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
16
+ };
17
+ /**
18
+ * Prepare environment variables for the SSH command
19
+ *
20
+ * @param envVars Array of environment variables in KEY=VALUE format
21
+ * @returns Formatted string with export commands
22
+ */
23
+ private prepareEnvironmentVariables;
24
+ run(): Promise<void>;
25
+ }
@@ -0,0 +1,93 @@
1
+ import * as child_process from "child_process";
2
+ import { Args, Flags } from "@oclif/core";
3
+ import { ExtendedBaseCommand } from "../../lib/basecommands/ExtendedBaseCommand.js";
4
+ import { getSSHConnectionForContainer } from "../../lib/resources/ssh/container.js";
5
+ import { sshConnectionFlags } from "../../lib/resources/ssh/flags.js";
6
+ import { sshUsageDocumentation } from "../../lib/resources/ssh/doc.js";
7
+ import { buildSSHClientFlags } from "../../lib/resources/ssh/connection.js";
8
+ import { withContainerAndStackId } from "../../lib/resources/container/flags.js";
9
+ import { projectFlags } from "../../lib/resources/project/flags.js";
10
+ import shellEscape from "shell-escape";
11
+ export default class Exec extends ExtendedBaseCommand {
12
+ static summary = "Execute a command in a container via SSH non-interactively.";
13
+ static description = sshUsageDocumentation;
14
+ static args = {
15
+ "container-id": Args.string({
16
+ description: "ID or short ID of the container to connect to",
17
+ required: true,
18
+ }),
19
+ command: Args.string({
20
+ description: "Command to execute in the container",
21
+ required: true,
22
+ }),
23
+ };
24
+ static flags = {
25
+ ...sshConnectionFlags,
26
+ ...projectFlags,
27
+ workdir: Flags.string({
28
+ char: "w",
29
+ summary: "working directory where the command will be executed",
30
+ default: undefined,
31
+ }),
32
+ env: Flags.string({
33
+ char: "e",
34
+ summary: "environment variables to set for the command (format: KEY=VALUE)",
35
+ multiple: true,
36
+ }),
37
+ shell: Flags.string({
38
+ summary: "shell to use for the SSH connection",
39
+ default: "/bin/sh",
40
+ }),
41
+ };
42
+ /**
43
+ * Prepare environment variables for the SSH command
44
+ *
45
+ * @param envVars Array of environment variables in KEY=VALUE format
46
+ * @returns Formatted string with export commands
47
+ */
48
+ prepareEnvironmentVariables(envVars) {
49
+ return (envVars
50
+ .map((env) => {
51
+ const eqIdx = env.indexOf("=");
52
+ if (eqIdx === -1) {
53
+ // If no '=', treat the whole string as key with empty value
54
+ return `export ${shellEscape([env])}=`;
55
+ }
56
+ const key = env.slice(0, eqIdx);
57
+ const value = env.slice(eqIdx + 1);
58
+ return `export ${shellEscape([key])}=${shellEscape([value])}`;
59
+ })
60
+ .join("; ") + "; ");
61
+ }
62
+ async run() {
63
+ const { args, flags } = await this.parse(Exec);
64
+ const [containerId, stackId] = await withContainerAndStackId(this.apiClient, Exec, flags, this.args, this.config);
65
+ const { host, user } = await getSSHConnectionForContainer(this.apiClient, containerId, stackId, flags["ssh-user"]);
66
+ this.log("executing command on %s as %s", host, user);
67
+ const command = args.command;
68
+ const workdir = flags.workdir;
69
+ // Build the command to execute
70
+ let execCommand = "";
71
+ // Add environment variables if provided
72
+ if (flags.env && flags.env.length > 0) {
73
+ execCommand += this.prepareEnvironmentVariables(flags.env);
74
+ }
75
+ // Change to working directory if specified
76
+ if (workdir !== undefined) {
77
+ execCommand += `cd ${shellEscape([workdir])} && `;
78
+ }
79
+ // Add the actual command
80
+ execCommand += command;
81
+ const sshArgs = buildSSHClientFlags(user, host, flags, {
82
+ interactive: false,
83
+ });
84
+ const wrappedExecCommand = shellEscape([flags.shell, "-c", execCommand]);
85
+ this.debug("running ssh %o, with command %o", sshArgs, wrappedExecCommand);
86
+ const result = child_process.spawnSync("/usr/bin/ssh", [...sshArgs, wrappedExecCommand], {
87
+ stdio: "inherit",
88
+ });
89
+ if (result.status !== 0) {
90
+ this.error(`Command failed with exit code ${result.status}`);
91
+ }
92
+ }
93
+ }
@@ -16,6 +16,7 @@ export declare class List extends ListBaseCommand<typeof List, ResponseItem, Res
16
16
  "no-truncate": import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
17
  "no-relative-dates": import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
18
  "csv-separator": import("@oclif/core/interfaces").OptionFlag<"," | ";">;
19
+ token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
20
  };
20
21
  getData(): Promise<Response>;
21
22
  protected getColumns(data: ResponseItem[]): ListColumns<ResponseItem>;
@@ -6,6 +6,7 @@ export declare class Logs extends BaseCommand {
6
6
  static flags: {
7
7
  "no-pager": import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
8
  "project-id": import("@oclif/core/interfaces").OptionFlag<string>;
9
+ token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
10
  };
10
11
  static args: {
11
12
  "container-id": import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
@@ -0,0 +1,20 @@
1
+ import { ExecRenderBaseCommand } from "../../lib/basecommands/ExecRenderBaseCommand.js";
2
+ import { ReactNode } from "react";
3
+ import PortMapping from "../../lib/units/PortMapping.js";
4
+ export declare class PortForward extends ExecRenderBaseCommand<typeof PortForward, Record<string, never>> {
5
+ static summary: string;
6
+ static description: string;
7
+ static flags: {
8
+ "project-id": import("@oclif/core/interfaces").OptionFlag<string>;
9
+ "ssh-user": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ "ssh-identity-file": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ };
13
+ static args: {
14
+ "container-id": import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
15
+ port: import("@oclif/core/interfaces").Arg<PortMapping | undefined, Record<string, unknown>>;
16
+ };
17
+ protected exec(): Promise<Record<string, never>>;
18
+ private getPortMappings;
19
+ protected render(): ReactNode;
20
+ }
@@ -0,0 +1,68 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ExecRenderBaseCommand } from "../../lib/basecommands/ExecRenderBaseCommand.js";
3
+ import { makeProcessRenderer, processFlags, } from "../../rendering/process/process_flags.js";
4
+ import * as cp from "child_process";
5
+ import { Box, Text } from "ink";
6
+ import { Value } from "../../rendering/react/components/Value.js";
7
+ import { Args } from "@oclif/core";
8
+ import { sshConnectionFlags } from "../../lib/resources/ssh/flags.js";
9
+ import { sshUsageDocumentation } from "../../lib/resources/ssh/doc.js";
10
+ import { buildSSHClientFlags } from "../../lib/resources/ssh/connection.js";
11
+ import { withContainerAndStackId } from "../../lib/resources/container/flags.js";
12
+ import { getSSHConnectionForContainer } from "../../lib/resources/ssh/container.js";
13
+ import { projectFlags } from "../../lib/resources/project/flags.js";
14
+ import PortMapping from "../../lib/units/PortMapping.js";
15
+ import { assertStatus } from "@mittwald/api-client";
16
+ export class PortForward extends ExecRenderBaseCommand {
17
+ static summary = "Forward a container port to a local port";
18
+ static description = "This command forwards a TCP port from a container to a local port on your machine. This allows you to connect to services running in the container as if they were running on your local machine.\n\n" +
19
+ sshUsageDocumentation;
20
+ static flags = {
21
+ ...processFlags,
22
+ ...sshConnectionFlags,
23
+ ...projectFlags,
24
+ };
25
+ static args = {
26
+ "container-id": Args.string({
27
+ description: "ID or short ID of the container to connect to",
28
+ required: true,
29
+ }),
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.",
33
+ required: false,
34
+ }),
35
+ };
36
+ async exec() {
37
+ const [serviceId, stackId] = await withContainerAndStackId(this.apiClient, PortForward, this.flags, this.args, this.config);
38
+ const p = makeProcessRenderer(this.flags, "Port-forwarding a container");
39
+ const { host, user } = await getSSHConnectionForContainer(this.apiClient, serviceId, stackId, this.flags["ssh-user"]);
40
+ const portMappings = await this.getPortMappings(stackId, serviceId);
41
+ await p.complete(_jsxs(Box, { flexDirection: "column", children: [portMappings.map((p, idx) => (_jsxs(Text, { children: ["Forwarding container port ", _jsx(Value, { children: p.remotePort }), " to local port ", _jsx(Value, { children: p.localPort }), "."] }, idx))), _jsx(Text, { children: "Use CTRL+C to cancel." })] }));
42
+ const sshArgs = buildSSHClientFlags(user, host, this.flags, {
43
+ interactive: false,
44
+ additionalFlags: portMappings
45
+ .map((p) => ["-L", `${p.localPort}:localhost:${p.remotePort}`])
46
+ .flat(),
47
+ });
48
+ cp.spawnSync("ssh", [...sshArgs, "cat", "/dev/zero"], {
49
+ stdio: ["ignore", process.stdout, process.stderr],
50
+ });
51
+ return {};
52
+ }
53
+ async getPortMappings(stackId, serviceId) {
54
+ if (this.args.port) {
55
+ return [this.args.port];
56
+ }
57
+ const containerResponse = await this.apiClient.container.getService({
58
+ stackId,
59
+ serviceId,
60
+ });
61
+ assertStatus(containerResponse, 200);
62
+ const ports = containerResponse.data.deployedState.ports ?? [];
63
+ return ports.map((p) => PortMapping.fromPortAndProtocol(p));
64
+ }
65
+ render() {
66
+ return undefined;
67
+ }
68
+ }
@@ -5,6 +5,8 @@ type Result = {
5
5
  };
6
6
  export declare class Run extends ExecRenderBaseCommand<typeof Run, Result> {
7
7
  static summary: string;
8
+ static strict: boolean;
9
+ static usage: string;
8
10
  static flags: {
9
11
  env: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
12
  "env-file": import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -24,6 +26,16 @@ export declare class Run extends ExecRenderBaseCommand<typeof Run, Result> {
24
26
  };
25
27
  protected exec(): Promise<Result>;
26
28
  private addServiceToStack;
29
+ /**
30
+ * Builds and returns the container command based on the provided image
31
+ * metadata and arguments.
32
+ *
33
+ * @param imageMeta The configuration object containing the metadata of the
34
+ * container image, including the default command.
35
+ * @returns An array of strings representing the container command to execute,
36
+ * or undefined if no specific command is set.
37
+ */
38
+ private buildContainerCommand;
27
39
  /**
28
40
  * Builds a container service request from command line arguments and image
29
41
  * metadata
@@ -3,13 +3,16 @@ import { Args, Flags } from "@oclif/core";
3
3
  import { ExecRenderBaseCommand } from "../../lib/basecommands/ExecRenderBaseCommand.js";
4
4
  import { makeProcessRenderer, processFlags, } from "../../rendering/process/process_flags.js";
5
5
  import { projectFlags } from "../../lib/resources/project/flags.js";
6
+ import dockerNames from "docker-names";
7
+ import { assertStatus } from "@mittwald/api-client";
8
+ import { getImageMeta, getPortMappings, parseEnvironmentVariables, } from "../../lib/resources/container/containerconfig.js";
6
9
  import { Success } from "../../rendering/react/components/Success.js";
7
10
  import { Value } from "../../rendering/react/components/Value.js";
8
- import * as dockerNames from "docker-names";
9
- import { assertStatus } from "@mittwald/api-client";
10
- import { parseEnvironmentVariables, getPortMappings, getImageMeta, } from "../../lib/resources/container/containerconfig.js";
11
11
  export class Run extends ExecRenderBaseCommand {
12
12
  static summary = "Creates and starts a new container.";
13
+ static strict = false;
14
+ // Usage needs to be overwritten because the autogenerated one is incorrect due to the variadic arguments.
15
+ static usage = "container run [--token <value>] [-q] [-p <value>] [-e <value>...] [--env-file <value>...] [--description <value>] [--entrypoint <value>] [--name <value>] [-p <value>...] [-P] [-v <value>...] IMAGE [COMMAND] [ARGS...]";
13
16
  static flags = {
14
17
  ...processFlags,
15
18
  ...projectFlags,
@@ -45,11 +48,11 @@ export class Run extends ExecRenderBaseCommand {
45
48
  summary: "publish a container's port(s) to the host",
46
49
  description: "Map a container's port to a port on the host system. " +
47
50
  "Format: <host-port>:<container-port> or just <container-port> (in which case the host port will be automatically assigned). " +
48
- "For example, -p 8080:80 maps port 80 in the container to port 8080 on the host. " +
49
- "Use multiple -p flags to publish multiple ports.",
51
+ "For example, --publish 8080:80 maps port 80 in the container to port 8080 on the host. " +
52
+ "Use multiple --publish flags to publish multiple ports.\n\n" +
53
+ "NOTE: Please note that the usual shorthand -p is not supported for this flag, as it would conflict with the --project flag.",
50
54
  required: false,
51
55
  multiple: true,
52
- char: "p",
53
56
  }),
54
57
  "publish-all": Flags.boolean({
55
58
  summary: "publish all ports that are defined in the image",
@@ -85,7 +88,6 @@ export class Run extends ExecRenderBaseCommand {
85
88
  summary: "arguments to pass to the command",
86
89
  description: "These are the runtime arguments passed to the command specified by the command parameter or the container's default command, not to the container itself. For example, if the command is 'echo', the args might be 'hello world'.",
87
90
  required: false,
88
- variadic: true,
89
91
  }),
90
92
  };
91
93
  async exec() {
@@ -116,6 +118,23 @@ export class Run extends ExecRenderBaseCommand {
116
118
  assertStatus(resp, 200);
117
119
  return resp.data;
118
120
  }
121
+ /**
122
+ * Builds and returns the container command based on the provided image
123
+ * metadata and arguments.
124
+ *
125
+ * @param imageMeta The configuration object containing the metadata of the
126
+ * container image, including the default command.
127
+ * @returns An array of strings representing the container command to execute,
128
+ * or undefined if no specific command is set.
129
+ */
130
+ buildContainerCommand(imageMeta) {
131
+ if (!this.args.command) {
132
+ return imageMeta.command;
133
+ }
134
+ const firstArg = (this.argv.lastIndexOf(this.args.command) ?? Infinity) + 1;
135
+ const command = [this.args.command, ...this.argv.slice(firstArg)];
136
+ return command;
137
+ }
119
138
  /**
120
139
  * Builds a container service request from command line arguments and image
121
140
  * metadata
@@ -126,7 +145,7 @@ export class Run extends ExecRenderBaseCommand {
126
145
  * @returns A properly formatted container service request
127
146
  */
128
147
  async buildServiceRequest(image, imageMeta, serviceName) {
129
- const command = this.args.command ? [this.args.command] : imageMeta.command;
148
+ const command = this.buildContainerCommand(imageMeta);
130
149
  const entrypoint = this.flags.entrypoint
131
150
  ? [this.flags.entrypoint]
132
151
  : imageMeta.entrypoint;
@@ -0,0 +1,17 @@
1
+ import { ExtendedBaseCommand } from "../../lib/basecommands/ExtendedBaseCommand.js";
2
+ export default class Ssh extends ExtendedBaseCommand<typeof Ssh> {
3
+ static summary: string;
4
+ static description: string;
5
+ static args: {
6
+ "container-id": import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ info: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ test: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ shell: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ "project-id": import("@oclif/core/interfaces").OptionFlag<string>;
13
+ "ssh-user": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
14
+ "ssh-identity-file": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
15
+ };
16
+ run(): Promise<void>;
17
+ }
@@ -0,0 +1,61 @@
1
+ import * as child_process from "child_process";
2
+ import { Args, Flags } from "@oclif/core";
3
+ import { ExtendedBaseCommand } from "../../lib/basecommands/ExtendedBaseCommand.js";
4
+ import { getSSHConnectionForContainer } from "../../lib/resources/ssh/container.js";
5
+ import { sshConnectionFlags, } from "../../lib/resources/ssh/flags.js";
6
+ import { sshWrapperDocumentation } from "../../lib/resources/ssh/doc.js";
7
+ import { buildSSHClientFlags } from "../../lib/resources/ssh/connection.js";
8
+ import { withContainerAndStackId } from "../../lib/resources/container/flags.js";
9
+ import { projectFlags } from "../../lib/resources/project/flags.js";
10
+ export default class Ssh extends ExtendedBaseCommand {
11
+ static summary = "Connect to a container via SSH";
12
+ static description = "Establishes an interactive SSH connection to a container.\n\n" +
13
+ sshWrapperDocumentation;
14
+ static args = {
15
+ "container-id": Args.string({
16
+ description: "ID or short ID of the container to connect to",
17
+ required: true,
18
+ }),
19
+ };
20
+ static flags = {
21
+ ...sshConnectionFlags,
22
+ ...projectFlags,
23
+ info: Flags.boolean({
24
+ summary: "only print connection information, without actually connecting",
25
+ }),
26
+ test: Flags.boolean({
27
+ summary: "test connection and exit",
28
+ }),
29
+ shell: Flags.string({
30
+ summary: "shell to use for the SSH connection",
31
+ default: "/bin/sh",
32
+ }),
33
+ };
34
+ async run() {
35
+ const { flags } = await this.parse(Ssh);
36
+ const [containerId, stackId] = await withContainerAndStackId(this.apiClient, Ssh, flags, this.args, this.config);
37
+ const { host, user, directory } = await getSSHConnectionForContainer(this.apiClient, containerId, stackId, flags["ssh-user"]);
38
+ if (flags.info) {
39
+ this.log("hostname: %o", host);
40
+ this.log("username: %o", user);
41
+ this.log("directory: %o", directory);
42
+ return;
43
+ }
44
+ this.log("connecting to %o as %o", host, user);
45
+ const [cmd, args] = buildSSHCmdAndFlags(user, host, flags);
46
+ this.debug("running ssh %o, with command %o", args, cmd);
47
+ child_process.spawnSync("/usr/bin/ssh", [...args, cmd], {
48
+ stdio: "inherit",
49
+ });
50
+ }
51
+ }
52
+ function buildSSHCmdAndFlags(user, host, flags) {
53
+ const args = buildSSHClientFlags(user, host, flags, {
54
+ interactive: true,
55
+ additionalFlags: flags.test ? ["-q"] : [],
56
+ });
57
+ if (flags.test) {
58
+ return ["/bin/true", args];
59
+ }
60
+ return [flags.shell, args];
61
+ }