@mittwald/cli 1.9.1 → 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.js +1 -1
- package/dist/lib/resources/ssh/environment.d.ts +7 -0
- package/dist/lib/resources/ssh/environment.js +21 -0
- package/package.json +1 -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) {
|
|
@@ -3,7 +3,7 @@ const hook = async function (opts) {
|
|
|
3
3
|
const isInstalledWithBrew = () => {
|
|
4
4
|
try {
|
|
5
5
|
const cellar = execSync("brew --cellar", { encoding: "utf8" });
|
|
6
|
-
return opts.config.root.startsWith(cellar);
|
|
6
|
+
return opts.config.root.startsWith(cellar.trim());
|
|
7
7
|
}
|
|
8
8
|
catch {
|
|
9
9
|
return false;
|
|
@@ -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
|
+
}
|