@mittwald/cli 1.9.0 → 1.10.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/download.js +2 -2
- package/dist/commands/app/exec.d.ts +17 -0
- package/dist/commands/app/exec.js +71 -0
- package/dist/commands/container/exec.d.ts +1 -7
- package/dist/commands/container/exec.js +11 -22
- package/dist/hooks/prerun/update-brew-check.d.ts +3 -0
- package/dist/hooks/prerun/update-brew-check.js +19 -0
- package/dist/lib/context/TerraformContextProvider.js +1 -1
- package/dist/lib/resources/ssh/environment.d.ts +7 -0
- package/dist/lib/resources/ssh/environment.js +21 -0
- package/dist/rendering/formatter/ListFormatter.d.ts +1 -1
- package/package.json +4 -1
|
@@ -33,11 +33,11 @@ export class Download extends ExecRenderBaseCommand {
|
|
|
33
33
|
static examples = [
|
|
34
34
|
{
|
|
35
35
|
description: "Download entire app to current working directory",
|
|
36
|
-
command: "$ <%= config.bin %> <%= command.id %> .",
|
|
36
|
+
command: "$ <%= config.bin %> <%= command.id %> --target .",
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
description: "Download only shared dir from a deployer-managed app",
|
|
40
|
-
command: "<%= config.bin %> <%= command.id %> --remote-sub-directory=shared .",
|
|
40
|
+
command: "<%= config.bin %> <%= command.id %> --remote-sub-directory=shared --target .",
|
|
41
41
|
},
|
|
42
42
|
];
|
|
43
43
|
async exec() {
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
command: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
workdir: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
env: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
"installation-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,71 @@
|
|
|
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 { sshConnectionFlags } from "../../lib/resources/ssh/flags.js";
|
|
5
|
+
import { sshUsageDocumentation } from "../../lib/resources/ssh/doc.js";
|
|
6
|
+
import { buildSSHClientFlags } from "../../lib/resources/ssh/connection.js";
|
|
7
|
+
import { appInstallationFlags } from "../../lib/resources/app/flags.js";
|
|
8
|
+
import { getSSHConnectionForAppInstallation } from "../../lib/resources/ssh/appinstall.js";
|
|
9
|
+
import shellEscape from "shell-escape";
|
|
10
|
+
import { prepareEnvironmentVariables } from "../../lib/resources/ssh/environment.js";
|
|
11
|
+
export default class Exec extends ExtendedBaseCommand {
|
|
12
|
+
static summary = "Execute a command in an app installation via SSH non-interactively.";
|
|
13
|
+
static description = sshUsageDocumentation;
|
|
14
|
+
static args = {
|
|
15
|
+
command: Args.string({
|
|
16
|
+
description: "Command to execute in the app installation",
|
|
17
|
+
required: true,
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
static flags = {
|
|
21
|
+
...sshConnectionFlags,
|
|
22
|
+
...appInstallationFlags,
|
|
23
|
+
workdir: Flags.string({
|
|
24
|
+
char: "w",
|
|
25
|
+
summary: "working directory where the command will be executed",
|
|
26
|
+
default: undefined,
|
|
27
|
+
}),
|
|
28
|
+
env: Flags.string({
|
|
29
|
+
char: "e",
|
|
30
|
+
summary: "environment variables to set for the command (format: KEY=VALUE)",
|
|
31
|
+
multiple: true,
|
|
32
|
+
multipleNonGreedy: true,
|
|
33
|
+
}),
|
|
34
|
+
quiet: Flags.boolean({
|
|
35
|
+
char: "q",
|
|
36
|
+
summary: "disable informational output, only show command results",
|
|
37
|
+
default: false,
|
|
38
|
+
}),
|
|
39
|
+
};
|
|
40
|
+
async run() {
|
|
41
|
+
const { args, flags } = await this.parse(Exec);
|
|
42
|
+
const appInstallationId = await this.withAppInstallationId(Exec);
|
|
43
|
+
const { host, user, directory } = await getSSHConnectionForAppInstallation(this.apiClient, appInstallationId, flags["ssh-user"]);
|
|
44
|
+
if (!flags.quiet) {
|
|
45
|
+
this.log("executing command on %s as %s", host, user);
|
|
46
|
+
}
|
|
47
|
+
const command = args.command;
|
|
48
|
+
const workdir = flags.workdir ?? directory;
|
|
49
|
+
// Build the command to execute
|
|
50
|
+
let execCommand = "";
|
|
51
|
+
// Add environment variables if provided
|
|
52
|
+
if (flags.env && flags.env.length > 0) {
|
|
53
|
+
execCommand += prepareEnvironmentVariables(flags.env);
|
|
54
|
+
}
|
|
55
|
+
// Change to working directory if specified, otherwise use app directory
|
|
56
|
+
execCommand += `cd ${shellEscape([workdir])} && `;
|
|
57
|
+
// Add the actual command
|
|
58
|
+
execCommand += command;
|
|
59
|
+
const sshArgs = buildSSHClientFlags(user, host, flags, {
|
|
60
|
+
interactive: false,
|
|
61
|
+
});
|
|
62
|
+
const wrappedExecCommand = shellEscape(["/bin/bash", "-c", execCommand]);
|
|
63
|
+
this.debug("running ssh %o, with command %o", sshArgs, wrappedExecCommand);
|
|
64
|
+
const result = child_process.spawnSync("/usr/bin/ssh", [...sshArgs, wrappedExecCommand], {
|
|
65
|
+
stdio: "inherit",
|
|
66
|
+
});
|
|
67
|
+
if (result.status !== 0) {
|
|
68
|
+
this.error(`Command failed with exit code ${result.status}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -10,16 +10,10 @@ export default class Exec extends ExtendedBaseCommand<typeof Exec> {
|
|
|
10
10
|
workdir: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
11
|
env: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
12
|
shell: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
14
|
"project-id": import("@oclif/core/interfaces").OptionFlag<string>;
|
|
14
15
|
"ssh-user": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
16
|
"ssh-identity-file": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
17
|
};
|
|
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
18
|
run(): Promise<void>;
|
|
25
19
|
}
|
|
@@ -8,6 +8,7 @@ import { buildSSHClientFlags } from "../../lib/resources/ssh/connection.js";
|
|
|
8
8
|
import { withContainerAndStackId } from "../../lib/resources/container/flags.js";
|
|
9
9
|
import { projectFlags } from "../../lib/resources/project/flags.js";
|
|
10
10
|
import shellEscape from "shell-escape";
|
|
11
|
+
import { prepareEnvironmentVariables } from "../../lib/resources/ssh/environment.js";
|
|
11
12
|
export default class Exec extends ExtendedBaseCommand {
|
|
12
13
|
static summary = "Execute a command in a container via SSH non-interactively.";
|
|
13
14
|
static description = sshUsageDocumentation;
|
|
@@ -33,44 +34,32 @@ export default class Exec extends ExtendedBaseCommand {
|
|
|
33
34
|
char: "e",
|
|
34
35
|
summary: "environment variables to set for the command (format: KEY=VALUE)",
|
|
35
36
|
multiple: true,
|
|
37
|
+
multipleNonGreedy: true,
|
|
36
38
|
}),
|
|
37
39
|
shell: Flags.string({
|
|
38
40
|
summary: "shell to use for the SSH connection",
|
|
39
41
|
default: "/bin/sh",
|
|
40
42
|
}),
|
|
43
|
+
quiet: Flags.boolean({
|
|
44
|
+
char: "q",
|
|
45
|
+
summary: "disable informational output, only show command results",
|
|
46
|
+
default: false,
|
|
47
|
+
}),
|
|
41
48
|
};
|
|
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
49
|
async run() {
|
|
63
50
|
const { args, flags } = await this.parse(Exec);
|
|
64
51
|
const [containerId, stackId] = await withContainerAndStackId(this.apiClient, Exec, flags, this.args, this.config);
|
|
65
52
|
const { host, user } = await getSSHConnectionForContainer(this.apiClient, containerId, stackId, flags["ssh-user"]);
|
|
66
|
-
|
|
53
|
+
if (!flags.quiet) {
|
|
54
|
+
this.log("executing command on %s as %s", host, user);
|
|
55
|
+
}
|
|
67
56
|
const command = args.command;
|
|
68
57
|
const workdir = flags.workdir;
|
|
69
58
|
// Build the command to execute
|
|
70
59
|
let execCommand = "";
|
|
71
60
|
// Add environment variables if provided
|
|
72
61
|
if (flags.env && flags.env.length > 0) {
|
|
73
|
-
execCommand +=
|
|
62
|
+
execCommand += prepareEnvironmentVariables(flags.env);
|
|
74
63
|
}
|
|
75
64
|
// Change to working directory if specified
|
|
76
65
|
if (workdir !== undefined) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
const hook = async function (opts) {
|
|
3
|
+
const isInstalledWithBrew = () => {
|
|
4
|
+
try {
|
|
5
|
+
const cellar = execSync("brew --cellar", { encoding: "utf8" });
|
|
6
|
+
return opts.config.root.startsWith(cellar.trim());
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
if (opts.Command.id === "update") {
|
|
13
|
+
if (isInstalledWithBrew()) {
|
|
14
|
+
opts.context.warn("installed with brew.\nUse `brew upgrade mw` to update to the newest version");
|
|
15
|
+
opts.context.exit(1);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
export default hook;
|
|
@@ -3,7 +3,7 @@ import { cwd } from "process";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { pathExists } from "../util/fs/pathExists.js";
|
|
5
5
|
function overrideIDFromState(state, type) {
|
|
6
|
-
const instances = state.resources?.find((r) => r.type === type)?.instances;
|
|
6
|
+
const instances = state.resources?.find((r) => r.type === type && r.mode === "managed")?.instances;
|
|
7
7
|
if (instances === undefined) {
|
|
8
8
|
return undefined;
|
|
9
9
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prepare environment variables for SSH command execution
|
|
3
|
+
*
|
|
4
|
+
* @param envVars Array of environment variables in KEY=VALUE format
|
|
5
|
+
* @returns Formatted string with export commands
|
|
6
|
+
*/
|
|
7
|
+
export declare function prepareEnvironmentVariables(envVars: string[]): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import shellEscape from "shell-escape";
|
|
2
|
+
/**
|
|
3
|
+
* Prepare environment variables for SSH command execution
|
|
4
|
+
*
|
|
5
|
+
* @param envVars Array of environment variables in KEY=VALUE format
|
|
6
|
+
* @returns Formatted string with export commands
|
|
7
|
+
*/
|
|
8
|
+
export function prepareEnvironmentVariables(envVars) {
|
|
9
|
+
return (envVars
|
|
10
|
+
.map((env) => {
|
|
11
|
+
const eqIdx = env.indexOf("=");
|
|
12
|
+
if (eqIdx === -1) {
|
|
13
|
+
// If no '=', treat the whole string as key with empty value
|
|
14
|
+
return `export ${shellEscape([env])}=`;
|
|
15
|
+
}
|
|
16
|
+
const key = env.slice(0, eqIdx);
|
|
17
|
+
const value = env.slice(eqIdx + 1);
|
|
18
|
+
return `export ${shellEscape([key])}=${shellEscape([value])}`;
|
|
19
|
+
})
|
|
20
|
+
.join("; ") + "; ");
|
|
21
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BooleanFlag, OptionFlag } from "@oclif/core/interfaces";
|
|
2
2
|
import { ListColumns } from "./Table.js";
|
|
3
|
-
export { ListColumn, ListColumns } from "./Table.js";
|
|
3
|
+
export type { ListColumn, ListColumns } from "./Table.js";
|
|
4
4
|
type ListFormatterFlags = {
|
|
5
5
|
output: OptionFlag<OutputFormat>;
|
|
6
6
|
extended: BooleanFlag<boolean>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mittwald/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Hand-crafted CLI for the mittwald API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -281,6 +281,9 @@
|
|
|
281
281
|
"bucket": "mittwald-cli",
|
|
282
282
|
"host": "https://mittwald-cli.s3.eu-central-1.amazonaws.com"
|
|
283
283
|
}
|
|
284
|
+
},
|
|
285
|
+
"hooks": {
|
|
286
|
+
"prerun": "./dist/hooks/prerun/update-brew-check"
|
|
284
287
|
}
|
|
285
288
|
},
|
|
286
289
|
"packageManager": "yarn@3.6.1"
|