@mittwald/cli 1.0.0-alpha.41 → 1.0.0-alpha.42
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/README.md +108 -6
- package/dist/commands/app/list.js +2 -3
- package/dist/commands/app/upload.d.ts +18 -0
- package/dist/commands/app/upload.js +74 -0
- package/dist/commands/database/mysql/dump.d.ts +1 -2
- package/dist/commands/database/mysql/dump.js +18 -56
- package/dist/commands/database/mysql/import.d.ts +19 -0
- package/dist/commands/database/mysql/import.js +69 -0
- package/dist/lib/app/custom_installation.d.ts +7 -0
- package/dist/lib/app/custom_installation.js +16 -0
- package/dist/lib/app/flags.js +4 -0
- package/dist/lib/context_flags.d.ts +4 -0
- package/dist/lib/context_flags.js +14 -1
- package/dist/lib/database/mysql/connect.d.ts +22 -14
- package/dist/lib/database/mysql/connect.js +15 -0
- package/dist/lib/database/mysql/flags.d.ts +4 -0
- package/dist/lib/database/mysql/flags.js +10 -0
- package/dist/lib/database/mysql/temp_user.d.ts +13 -0
- package/dist/lib/database/mysql/temp_user.js +52 -0
- package/dist/lib/error/UnexpectedShortIDPassedError.d.ts +5 -0
- package/dist/lib/error/UnexpectedShortIDPassedError.js +10 -0
- package/dist/lib/language.d.ts +10 -0
- package/dist/lib/language.js +12 -0
- package/dist/lib/project/flags.js +9 -2
- package/dist/lib/ssh/exec.d.ts +5 -1
- package/dist/lib/ssh/exec.js +2 -2
- package/dist/rendering/react/RenderBaseCommand.js +12 -5
- package/dist/rendering/react/components/AppInstallation/AppInstallationDetails.js +2 -4
- package/dist/rendering/react/components/Error/APIError.d.ts +13 -0
- package/dist/rendering/react/components/Error/APIError.js +29 -0
- package/dist/rendering/react/components/Error/ErrorBox.d.ts +4 -0
- package/dist/rendering/react/components/Error/ErrorBox.js +14 -0
- package/dist/rendering/react/components/Error/ErrorStack.d.ts +4 -0
- package/dist/rendering/react/components/Error/ErrorStack.js +7 -0
- package/dist/rendering/react/components/Error/ErrorText.d.ts +3 -0
- package/dist/rendering/react/components/Error/ErrorText.js +6 -0
- package/dist/rendering/react/components/Error/GenericError.d.ts +12 -0
- package/dist/rendering/react/components/Error/GenericError.js +13 -0
- package/dist/rendering/react/components/Error/InvalidArgsError.d.ts +5 -0
- package/dist/rendering/react/components/Error/InvalidArgsError.js +8 -0
- package/dist/rendering/react/components/Error/InvalidFlagsError.d.ts +5 -0
- package/dist/rendering/react/components/Error/InvalidFlagsError.js +8 -0
- package/dist/rendering/react/components/Error/UnexpectedShortIDPassedErrorBox.d.ts +4 -0
- package/dist/rendering/react/components/Error/UnexpectedShortIDPassedErrorBox.js +7 -0
- package/dist/rendering/react/components/ErrorBoundary.d.ts +20 -0
- package/dist/rendering/react/components/ErrorBoundary.js +32 -0
- package/dist/rendering/react/components/ErrorBox.d.ts +0 -1
- package/dist/rendering/react/components/ErrorBox.js +12 -48
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -138,6 +138,7 @@ USAGE
|
|
|
138
138
|
* [`mw app list`](#mw-app-list)
|
|
139
139
|
* [`mw app ssh [INSTALLATION-ID]`](#mw-app-ssh-installation-id)
|
|
140
140
|
* [`mw app uninstall [INSTALLATION-ID]`](#mw-app-uninstall-installation-id)
|
|
141
|
+
* [`mw app upload [INSTALLATION-ID]`](#mw-app-upload-installation-id)
|
|
141
142
|
* [`mw app versions [APP]`](#mw-app-versions-app)
|
|
142
143
|
* [`mw autocomplete [SHELL]`](#mw-autocomplete-shell)
|
|
143
144
|
* [`mw backup create`](#mw-backup-create)
|
|
@@ -170,6 +171,7 @@ USAGE
|
|
|
170
171
|
* [`mw database mysql delete DATABASE-ID`](#mw-database-mysql-delete-database-id)
|
|
171
172
|
* [`mw database mysql dump DATABASE-ID`](#mw-database-mysql-dump-database-id)
|
|
172
173
|
* [`mw database mysql get DATABASE-ID`](#mw-database-mysql-get-database-id)
|
|
174
|
+
* [`mw database mysql import DATABASE-ID`](#mw-database-mysql-import-database-id)
|
|
173
175
|
* [`mw database mysql list`](#mw-database-mysql-list)
|
|
174
176
|
* [`mw database mysql phpmyadmin DATABASE-ID`](#mw-database-mysql-phpmyadmin-database-id)
|
|
175
177
|
* [`mw database mysql port-forward DATABASE-ID`](#mw-database-mysql-port-forward-database-id)
|
|
@@ -1781,6 +1783,47 @@ FLAG DESCRIPTIONS
|
|
|
1781
1783
|
scripts), you can use this flag to easily get the IDs of created resources for further processing.
|
|
1782
1784
|
```
|
|
1783
1785
|
|
|
1786
|
+
## `mw app upload [INSTALLATION-ID]`
|
|
1787
|
+
|
|
1788
|
+
Upload the filesystem of an app to a project
|
|
1789
|
+
|
|
1790
|
+
```
|
|
1791
|
+
USAGE
|
|
1792
|
+
$ mw app upload [INSTALLATION-ID] --source <value> [-q] [--ssh-user <value>] [--dry-run] [--delete]
|
|
1793
|
+
|
|
1794
|
+
ARGUMENTS
|
|
1795
|
+
INSTALLATION-ID ID or short ID of an app installation; this argument is optional if a default app installation is set
|
|
1796
|
+
in the context
|
|
1797
|
+
|
|
1798
|
+
FLAGS
|
|
1799
|
+
-q, --quiet suppress process output and only display a machine-readable summary.
|
|
1800
|
+
--delete delete remote files that are not present locally
|
|
1801
|
+
--dry-run do not actually upload the app installation
|
|
1802
|
+
--source=<value> (required) source directory from which to upload the app installation
|
|
1803
|
+
--ssh-user=<value> override the SSH user to connect with; if omitted, your own user will be used
|
|
1804
|
+
|
|
1805
|
+
DESCRIPTION
|
|
1806
|
+
Upload the filesystem of an app to a project
|
|
1807
|
+
|
|
1808
|
+
Upload the filesystem of an app from your local machine to a project.
|
|
1809
|
+
|
|
1810
|
+
CAUTION: This is a potentially destructive operation. It will overwrite files on the server with the files from your
|
|
1811
|
+
local machine. This is NOT a turnkey deployment solution. It is intended for development purposes only.
|
|
1812
|
+
|
|
1813
|
+
FLAG DESCRIPTIONS
|
|
1814
|
+
-q, --quiet suppress process output and only display a machine-readable summary.
|
|
1815
|
+
|
|
1816
|
+
This flag controls if you want to see the process output or only a summary. When using mw non-interactively (e.g. in
|
|
1817
|
+
scripts), you can use this flag to easily get the IDs of created resources for further processing.
|
|
1818
|
+
|
|
1819
|
+
--ssh-user=<value> override the SSH user to connect with; if omitted, your own user will be used
|
|
1820
|
+
|
|
1821
|
+
This flag can be used to override the SSH user that is used for a connection; be default, your own personal user
|
|
1822
|
+
will be used for this.
|
|
1823
|
+
|
|
1824
|
+
You can also set this value by setting the MITTWALD_SSH_USER environment variable.
|
|
1825
|
+
```
|
|
1826
|
+
|
|
1784
1827
|
## `mw app versions [APP]`
|
|
1785
1828
|
|
|
1786
1829
|
List supported Apps and Versions
|
|
@@ -1825,7 +1868,7 @@ EXAMPLES
|
|
|
1825
1868
|
$ mw autocomplete --refresh-cache
|
|
1826
1869
|
```
|
|
1827
1870
|
|
|
1828
|
-
_See code: [@oclif/plugin-autocomplete](https://github.com/oclif/plugin-autocomplete/blob/v3.0.
|
|
1871
|
+
_See code: [@oclif/plugin-autocomplete](https://github.com/oclif/plugin-autocomplete/blob/v3.0.15/src/commands/autocomplete/index.ts)_
|
|
1829
1872
|
|
|
1830
1873
|
## `mw backup create`
|
|
1831
1874
|
|
|
@@ -2540,7 +2583,7 @@ Create a dump of a MySQL database
|
|
|
2540
2583
|
|
|
2541
2584
|
```
|
|
2542
2585
|
USAGE
|
|
2543
|
-
$ mw database mysql dump DATABASE-ID -o <value> [-q] [-p <value>] [--
|
|
2586
|
+
$ mw database mysql dump DATABASE-ID -o <value> [-q] [-p <value>] [--temporary-user] [--ssh-user <value>] [--gzip]
|
|
2544
2587
|
|
|
2545
2588
|
ARGUMENTS
|
|
2546
2589
|
DATABASE-ID The ID or name of the database
|
|
@@ -2587,8 +2630,8 @@ FLAG DESCRIPTIONS
|
|
|
2587
2630
|
|
|
2588
2631
|
--[no-]temporary-user create a temporary user for the dump
|
|
2589
2632
|
|
|
2590
|
-
Create a temporary user for
|
|
2591
|
-
you want to
|
|
2633
|
+
Create a temporary user for this operation. This user will be deleted after the operation has completed. This is
|
|
2634
|
+
useful if you want to work with a database that is not accessible from the outside.
|
|
2592
2635
|
|
|
2593
2636
|
If this flag is disabled, you will need to specify the password of the default user; either via the --mysql-password
|
|
2594
2637
|
flag or via the MYSQL_PWD environment variable.
|
|
@@ -2613,6 +2656,65 @@ DESCRIPTION
|
|
|
2613
2656
|
Get a MySQLDatabase.
|
|
2614
2657
|
```
|
|
2615
2658
|
|
|
2659
|
+
## `mw database mysql import DATABASE-ID`
|
|
2660
|
+
|
|
2661
|
+
Imports a dump of a MySQL database
|
|
2662
|
+
|
|
2663
|
+
```
|
|
2664
|
+
USAGE
|
|
2665
|
+
$ mw database mysql import DATABASE-ID -i <value> [-q] [-p <value>] [--temporary-user] [--ssh-user <value>] [--gzip]
|
|
2666
|
+
|
|
2667
|
+
ARGUMENTS
|
|
2668
|
+
DATABASE-ID The ID or name of the database
|
|
2669
|
+
|
|
2670
|
+
FLAGS
|
|
2671
|
+
-i, --input=<value> (required) the input file from which to read the dump ("-" for stdin)
|
|
2672
|
+
-p, --mysql-password=<value> the password to use for the MySQL user (env: MYSQL_PWD)
|
|
2673
|
+
-q, --quiet suppress process output and only display a machine-readable summary.
|
|
2674
|
+
--gzip uncompress the dump with gzip
|
|
2675
|
+
--ssh-user=<value> override the SSH user to connect with; if omitted, your own user will be used
|
|
2676
|
+
--[no-]temporary-user create a temporary user for the dump
|
|
2677
|
+
|
|
2678
|
+
FLAG DESCRIPTIONS
|
|
2679
|
+
-i, --input=<value> the input file from which to read the dump ("-" for stdin)
|
|
2680
|
+
|
|
2681
|
+
The input file from which to read the dump to. You can specify "-" or "/dev/stdin" to read the dump directly from
|
|
2682
|
+
STDIN.
|
|
2683
|
+
|
|
2684
|
+
-p, --mysql-password=<value> the password to use for the MySQL user (env: MYSQL_PWD)
|
|
2685
|
+
|
|
2686
|
+
The password to use for the MySQL user. If not provided, the environment variable MYSQL_PWD will be used. If that is
|
|
2687
|
+
not set either, the command will interactively ask for the password.
|
|
2688
|
+
|
|
2689
|
+
NOTE: This is a security risk, as the password will be visible in the process list of your system, and will be
|
|
2690
|
+
visible in your Shell history. It is recommended to use the environment variable instead.
|
|
2691
|
+
|
|
2692
|
+
-q, --quiet suppress process output and only display a machine-readable summary.
|
|
2693
|
+
|
|
2694
|
+
This flag controls if you want to see the process output or only a summary. When using mw non-interactively (e.g. in
|
|
2695
|
+
scripts), you can use this flag to easily get the IDs of created resources for further processing.
|
|
2696
|
+
|
|
2697
|
+
--gzip uncompress the dump with gzip
|
|
2698
|
+
|
|
2699
|
+
Uncompress the dump with gzip while importing. This is useful for large databases, as it can significantly reduce
|
|
2700
|
+
the size of the dump.
|
|
2701
|
+
|
|
2702
|
+
--ssh-user=<value> override the SSH user to connect with; if omitted, your own user will be used
|
|
2703
|
+
|
|
2704
|
+
This flag can be used to override the SSH user that is used for a connection; be default, your own personal user
|
|
2705
|
+
will be used for this.
|
|
2706
|
+
|
|
2707
|
+
You can also set this value by setting the MITTWALD_SSH_USER environment variable.
|
|
2708
|
+
|
|
2709
|
+
--[no-]temporary-user create a temporary user for the dump
|
|
2710
|
+
|
|
2711
|
+
Create a temporary user for this operation. This user will be deleted after the operation has completed. This is
|
|
2712
|
+
useful if you want to work with a database that is not accessible from the outside.
|
|
2713
|
+
|
|
2714
|
+
If this flag is disabled, you will need to specify the password of the default user; either via the --mysql-password
|
|
2715
|
+
flag or via the MYSQL_PWD environment variable.
|
|
2716
|
+
```
|
|
2717
|
+
|
|
2616
2718
|
## `mw database mysql list`
|
|
2617
2719
|
|
|
2618
2720
|
List MySQLDatabases belonging to a Project.
|
|
@@ -3391,7 +3493,7 @@ DESCRIPTION
|
|
|
3391
3493
|
Display help for mw.
|
|
3392
3494
|
```
|
|
3393
3495
|
|
|
3394
|
-
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.0.
|
|
3496
|
+
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.0.21/src/commands/help.ts)_
|
|
3395
3497
|
|
|
3396
3498
|
## `mw login reset`
|
|
3397
3499
|
|
|
@@ -4785,7 +4887,7 @@ EXAMPLES
|
|
|
4785
4887
|
$ mw update --available
|
|
4786
4888
|
```
|
|
4787
4889
|
|
|
4788
|
-
_See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v4.2.
|
|
4890
|
+
_See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v4.2.6/src/commands/update.ts)_
|
|
4789
4891
|
|
|
4790
4892
|
## `mw user api-token create`
|
|
4791
4893
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { assertStatus } from "@mittwald/api-client-commons";
|
|
2
2
|
import { projectFlags, withProjectId } from "../../lib/project/flags.js";
|
|
3
3
|
import { ListBaseCommand } from "../../ListBaseCommand.js";
|
|
4
|
-
import { phpInstaller } from "./create/php.js";
|
|
5
|
-
import { nodeInstaller } from "./create/node.js";
|
|
6
4
|
import { getAppFromUuid, getAppVersionFromUuid } from "../../lib/app/uuid.js";
|
|
5
|
+
import { isCustomAppInstallation } from "../../lib/app/custom_installation.js";
|
|
7
6
|
export default class List extends ListBaseCommand {
|
|
8
7
|
static description = "List installed apps in a project.";
|
|
9
8
|
static flags = {
|
|
@@ -44,7 +43,7 @@ export default class List extends ListBaseCommand {
|
|
|
44
43
|
appVersion: {
|
|
45
44
|
header: "Version",
|
|
46
45
|
get: (i) => {
|
|
47
|
-
if (
|
|
46
|
+
if (isCustomAppInstallation(i.appId)) {
|
|
48
47
|
return "n/a";
|
|
49
48
|
}
|
|
50
49
|
if (i.appVersionCurrent?.id === i.appVersionDesired.id) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ExecRenderBaseCommand } from "../../rendering/react/ExecRenderBaseCommand.js";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
export declare class Upload extends ExecRenderBaseCommand<typeof Upload, void> {
|
|
4
|
+
static summary: string;
|
|
5
|
+
static description: string;
|
|
6
|
+
static args: {
|
|
7
|
+
"installation-id": import("@oclif/core/lib/interfaces/parser.js").Arg<string>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
"dry-run": import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
11
|
+
delete: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
12
|
+
source: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
13
|
+
"ssh-user": import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
14
|
+
quiet: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
15
|
+
};
|
|
16
|
+
protected exec(): Promise<void>;
|
|
17
|
+
protected render(): ReactNode;
|
|
18
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ExecRenderBaseCommand } from "../../rendering/react/ExecRenderBaseCommand.js";
|
|
3
|
+
import { appInstallationArgs } from "../../lib/app/flags.js";
|
|
4
|
+
import { makeProcessRenderer, processFlags, } from "../../rendering/process/process_flags.js";
|
|
5
|
+
import { Flags } from "@oclif/core";
|
|
6
|
+
import { Success } from "../../rendering/react/components/Success.js";
|
|
7
|
+
import { hasBinary } from "../../lib/hasbin.js";
|
|
8
|
+
import { getSSHConnectionForAppInstallation } from "../../lib/ssh/appinstall.js";
|
|
9
|
+
import { spawnInProcess } from "../../rendering/process/process_exec.js";
|
|
10
|
+
import { sshConnectionFlags } from "../../lib/ssh/flags.js";
|
|
11
|
+
export class Upload extends ExecRenderBaseCommand {
|
|
12
|
+
static summary = "Upload the filesystem of an app to a project";
|
|
13
|
+
static description = "Upload the filesystem of an app from your local machine to a project.\n\n" +
|
|
14
|
+
"" +
|
|
15
|
+
"CAUTION: This is a potentially destructive operation. It will overwrite files on the server with the files from your local machine. " +
|
|
16
|
+
"This is NOT a turnkey deployment solution. It is intended for development purposes only.";
|
|
17
|
+
static args = {
|
|
18
|
+
...appInstallationArgs,
|
|
19
|
+
};
|
|
20
|
+
static flags = {
|
|
21
|
+
...processFlags,
|
|
22
|
+
...sshConnectionFlags,
|
|
23
|
+
"dry-run": Flags.boolean({
|
|
24
|
+
description: "do not actually upload the app installation",
|
|
25
|
+
default: false,
|
|
26
|
+
}),
|
|
27
|
+
delete: Flags.boolean({
|
|
28
|
+
description: "delete remote files that are not present locally",
|
|
29
|
+
default: false,
|
|
30
|
+
}),
|
|
31
|
+
source: Flags.directory({
|
|
32
|
+
description: "source directory from which to upload the app installation",
|
|
33
|
+
required: true,
|
|
34
|
+
exists: true,
|
|
35
|
+
}),
|
|
36
|
+
};
|
|
37
|
+
async exec() {
|
|
38
|
+
const appInstallationId = await this.withAppInstallationId(Upload);
|
|
39
|
+
const { "dry-run": dryRun, source, delete: deleteRemote, "ssh-user": sshUser, } = this.flags;
|
|
40
|
+
const p = makeProcessRenderer(this.flags, "Uploading app installation");
|
|
41
|
+
const { host, user, directory } = await p.runStep("getting connection data", async () => {
|
|
42
|
+
return getSSHConnectionForAppInstallation(this.apiClient, appInstallationId, sshUser);
|
|
43
|
+
});
|
|
44
|
+
await p.runStep("check if rsync is installed", async () => {
|
|
45
|
+
if (!(await hasBinary("rsync"))) {
|
|
46
|
+
throw new Error("this command requires rsync to be installed");
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
const rsyncOpts = [
|
|
50
|
+
"--archive",
|
|
51
|
+
"--recursive",
|
|
52
|
+
"--verbose",
|
|
53
|
+
"--progress",
|
|
54
|
+
"--exclude=typo3temp",
|
|
55
|
+
];
|
|
56
|
+
if (dryRun) {
|
|
57
|
+
rsyncOpts.push("--dry-run");
|
|
58
|
+
}
|
|
59
|
+
if (deleteRemote) {
|
|
60
|
+
rsyncOpts.push("--delete");
|
|
61
|
+
}
|
|
62
|
+
await spawnInProcess(p, "uploading app installation" + (dryRun ? " (dry-run)" : ""), "rsync", [...rsyncOpts, source, `${user}@${host}:${directory}/`]);
|
|
63
|
+
await p.complete(_jsx(UploadSuccess, { dryRun: dryRun }));
|
|
64
|
+
}
|
|
65
|
+
render() {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function UploadSuccess({ dryRun }) {
|
|
70
|
+
if (dryRun) {
|
|
71
|
+
return (_jsx(Success, { children: "App would (probably) have successfully been uploaded. \uD83D\uDE42" }));
|
|
72
|
+
}
|
|
73
|
+
return _jsx(Success, { children: "App successfully uploaded; have fun! \uD83D\uDE80" });
|
|
74
|
+
}
|
|
@@ -3,10 +3,10 @@ import { ReactNode } from "react";
|
|
|
3
3
|
export declare class Dump extends ExecRenderBaseCommand<typeof Dump, Record<string, never>> {
|
|
4
4
|
static summary: string;
|
|
5
5
|
static flags: {
|
|
6
|
-
"temporary-user": import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
7
6
|
output: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
8
7
|
gzip: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
9
8
|
"ssh-user": import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
9
|
+
"temporary-user": import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
10
10
|
"mysql-password": import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
11
11
|
quiet: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
12
12
|
};
|
|
@@ -16,5 +16,4 @@ export declare class Dump extends ExecRenderBaseCommand<typeof Dump, Record<stri
|
|
|
16
16
|
protected exec(): Promise<Record<string, never>>;
|
|
17
17
|
protected render(): ReactNode;
|
|
18
18
|
private getOutputStream;
|
|
19
|
-
private createTemporaryUser;
|
|
20
19
|
}
|
|
@@ -6,27 +6,17 @@ import { Text } from "ink";
|
|
|
6
6
|
import { Value } from "../../../rendering/react/components/Value.js";
|
|
7
7
|
import * as fs from "fs";
|
|
8
8
|
import { Success } from "../../../rendering/react/components/Success.js";
|
|
9
|
-
import { mysqlArgs,
|
|
10
|
-
import {
|
|
11
|
-
import { assertStatus } from "@mittwald/api-client";
|
|
12
|
-
import { randomBytes } from "crypto";
|
|
9
|
+
import { mysqlArgs, mysqlConnectionFlagsWithTempUser, withMySQLId, } from "../../../lib/database/mysql/flags.js";
|
|
10
|
+
import { runWithConnectionDetails } from "../../../lib/database/mysql/connect.js";
|
|
13
11
|
import { executeViaSSH } from "../../../lib/ssh/exec.js";
|
|
14
|
-
import assertSuccess from "../../../lib/assert_success.js";
|
|
15
12
|
import shellEscape from "shell-escape";
|
|
16
13
|
import { sshConnectionFlags } from "../../../lib/ssh/flags.js";
|
|
17
14
|
export class Dump extends ExecRenderBaseCommand {
|
|
18
15
|
static summary = "Create a dump of a MySQL database";
|
|
19
16
|
static flags = {
|
|
20
17
|
...processFlags,
|
|
21
|
-
...
|
|
18
|
+
...mysqlConnectionFlagsWithTempUser,
|
|
22
19
|
...sshConnectionFlags,
|
|
23
|
-
"temporary-user": Flags.boolean({
|
|
24
|
-
summary: "create a temporary user for the dump",
|
|
25
|
-
description: "Create a temporary user for the dump. This user will be deleted after the dump has been created. This is useful if you want to dump a database that is not accessible from the outside.\n\nIf this flag is disabled, you will need to specify the password of the default user; either via the --mysql-password flag or via the MYSQL_PWD environment variable.",
|
|
26
|
-
default: true,
|
|
27
|
-
required: false,
|
|
28
|
-
allowNo: true,
|
|
29
|
-
}),
|
|
30
20
|
output: Flags.string({
|
|
31
21
|
char: "o",
|
|
32
22
|
summary: 'the output file to write the dump to ("-" for stdout)',
|
|
@@ -45,29 +35,20 @@ export class Dump extends ExecRenderBaseCommand {
|
|
|
45
35
|
async exec() {
|
|
46
36
|
const databaseId = await withMySQLId(this.apiClient, this.flags, this.args);
|
|
47
37
|
const p = makeProcessRenderer(this.flags, "Dumping a MySQL database");
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
connectionDetails.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
let cmd = { command: "mysqldump", args: mysqldumpArgs };
|
|
63
|
-
if (this.flags.gzip) {
|
|
64
|
-
const escapedArgs = shellEscape(mysqldumpArgs);
|
|
65
|
-
cmd = {
|
|
66
|
-
shell: `set -e -o pipefail > /dev/null ; mysqldump ${escapedArgs} | gzip`,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
await p.runStep(_jsxs(Text, { children: ["starting mysqldump via SSH on project ", _jsx(Value, { children: project.shortId })] }), () => executeViaSSH(this.apiClient, this.flags["ssh-user"], { projectId: connectionDetails.project.id }, cmd, this.getOutputStream()));
|
|
70
|
-
await p.complete(_jsx(DumpSuccess, { database: connectionDetails.database, output: this.flags.output }));
|
|
38
|
+
const databaseName = await runWithConnectionDetails(this.apiClient, databaseId, p, this.flags, async (connectionDetails) => {
|
|
39
|
+
const { project } = connectionDetails;
|
|
40
|
+
const mysqldumpArgs = buildMySqlDumpArgs(connectionDetails);
|
|
41
|
+
let cmd = { command: "mysqldump", args: mysqldumpArgs };
|
|
42
|
+
if (this.flags.gzip) {
|
|
43
|
+
const escapedArgs = shellEscape(mysqldumpArgs);
|
|
44
|
+
cmd = {
|
|
45
|
+
shell: `set -e -o pipefail > /dev/null ; mysqldump ${escapedArgs} | gzip`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
await p.runStep(_jsxs(Text, { children: ["starting mysqldump via SSH on project", " ", _jsx(Value, { children: project.shortId })] }), () => executeViaSSH(this.apiClient, this.flags["ssh-user"], { projectId: connectionDetails.project.id }, cmd, { input: null, output: this.getOutputStream() }));
|
|
49
|
+
return connectionDetails.database;
|
|
50
|
+
});
|
|
51
|
+
await p.complete(_jsx(DumpSuccess, { database: databaseName, output: this.flags.output }));
|
|
71
52
|
return {};
|
|
72
53
|
}
|
|
73
54
|
render() {
|
|
@@ -79,29 +60,10 @@ export class Dump extends ExecRenderBaseCommand {
|
|
|
79
60
|
}
|
|
80
61
|
return fs.createWriteStream(this.flags.output);
|
|
81
62
|
}
|
|
82
|
-
async createTemporaryUser(databaseId) {
|
|
83
|
-
const password = randomBytes(32).toString("base64");
|
|
84
|
-
const createResponse = await this.apiClient.database.createMysqlUser({
|
|
85
|
-
mysqlDatabaseId: databaseId,
|
|
86
|
-
data: {
|
|
87
|
-
accessLevel: "full", // needed for "PROCESS" privilege
|
|
88
|
-
externalAccess: false,
|
|
89
|
-
password,
|
|
90
|
-
databaseId,
|
|
91
|
-
description: "Temporary user for exporting database",
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
assertStatus(createResponse, 201);
|
|
95
|
-
const userResponse = await this.apiClient.database.getMysqlUser({
|
|
96
|
-
mysqlUserId: createResponse.data.id,
|
|
97
|
-
});
|
|
98
|
-
assertStatus(userResponse, 200);
|
|
99
|
-
return [userResponse.data, password];
|
|
100
|
-
}
|
|
101
63
|
}
|
|
102
64
|
function DumpSuccess({ database, output, }) {
|
|
103
65
|
return (_jsxs(Success, { children: ["Dump of MySQL database ", _jsx(Value, { children: database }), " written to", " ", _jsx(Value, { children: output })] }));
|
|
104
66
|
}
|
|
105
67
|
function buildMySqlDumpArgs(d) {
|
|
106
|
-
return ["-h", d.hostname, "-u", d.user,
|
|
68
|
+
return ["-h", d.hostname, "-u", d.user, `-p${d.password}`, d.database];
|
|
107
69
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ExecRenderBaseCommand } from "../../../rendering/react/ExecRenderBaseCommand.js";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
export declare class Import extends ExecRenderBaseCommand<typeof Import, Record<string, never>> {
|
|
4
|
+
static summary: string;
|
|
5
|
+
static flags: {
|
|
6
|
+
input: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
gzip: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
8
|
+
"ssh-user": import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
9
|
+
"temporary-user": import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
10
|
+
"mysql-password": import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
11
|
+
quiet: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
static args: {
|
|
14
|
+
"database-id": import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
|
|
15
|
+
};
|
|
16
|
+
protected exec(): Promise<Record<string, never>>;
|
|
17
|
+
protected render(): ReactNode;
|
|
18
|
+
private getInputStream;
|
|
19
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ExecRenderBaseCommand } from "../../../rendering/react/ExecRenderBaseCommand.js";
|
|
3
|
+
import { Flags } from "@oclif/core";
|
|
4
|
+
import { makeProcessRenderer, processFlags, } from "../../../rendering/process/process_flags.js";
|
|
5
|
+
import { Text } from "ink";
|
|
6
|
+
import { Value } from "../../../rendering/react/components/Value.js";
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import { Success } from "../../../rendering/react/components/Success.js";
|
|
9
|
+
import { mysqlArgs, mysqlConnectionFlagsWithTempUser, withMySQLId, } from "../../../lib/database/mysql/flags.js";
|
|
10
|
+
import { executeViaSSH } from "../../../lib/ssh/exec.js";
|
|
11
|
+
import { sshConnectionFlags } from "../../../lib/ssh/flags.js";
|
|
12
|
+
import shellEscape from "shell-escape";
|
|
13
|
+
import { runWithConnectionDetails } from "../../../lib/database/mysql/connect.js";
|
|
14
|
+
export class Import extends ExecRenderBaseCommand {
|
|
15
|
+
static summary = "Imports a dump of a MySQL database";
|
|
16
|
+
static flags = {
|
|
17
|
+
...processFlags,
|
|
18
|
+
...mysqlConnectionFlagsWithTempUser,
|
|
19
|
+
...sshConnectionFlags,
|
|
20
|
+
input: Flags.string({
|
|
21
|
+
char: "i",
|
|
22
|
+
summary: 'the input file from which to read the dump ("-" for stdin)',
|
|
23
|
+
description: 'The input file from which to read the dump to. You can specify "-" or "/dev/stdin" to read the dump directly from STDIN.',
|
|
24
|
+
required: true,
|
|
25
|
+
}),
|
|
26
|
+
gzip: Flags.boolean({
|
|
27
|
+
summary: "uncompress the dump with gzip",
|
|
28
|
+
aliases: ["gz"],
|
|
29
|
+
description: "Uncompress the dump with gzip while importing. This is useful for large databases, as it can significantly reduce the size of the dump.",
|
|
30
|
+
default: false,
|
|
31
|
+
required: false,
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
static args = { ...mysqlArgs };
|
|
35
|
+
async exec() {
|
|
36
|
+
const databaseId = await withMySQLId(this.apiClient, this.flags, this.args);
|
|
37
|
+
const p = makeProcessRenderer(this.flags, "Importing a MySQL database");
|
|
38
|
+
const databaseName = await runWithConnectionDetails(this.apiClient, databaseId, p, this.flags, async (connectionDetails) => {
|
|
39
|
+
const { project } = connectionDetails;
|
|
40
|
+
const mysqlArgs = buildMySqlArgs(connectionDetails);
|
|
41
|
+
let cmd = { command: "mysql", args: mysqlArgs };
|
|
42
|
+
if (this.flags.gzip) {
|
|
43
|
+
const escapedArgs = shellEscape(mysqlArgs);
|
|
44
|
+
cmd = {
|
|
45
|
+
shell: `set -e -o pipefail > /dev/null ; gunzip | mysql ${escapedArgs}`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
await p.runStep(_jsxs(Text, { children: ["starting mysql via SSH on project ", _jsx(Value, { children: project.shortId })] }), () => executeViaSSH(this.apiClient, this.flags["ssh-user"], { projectId: connectionDetails.project.id }, cmd, { input: this.getInputStream(), output: null }));
|
|
49
|
+
return connectionDetails.database;
|
|
50
|
+
});
|
|
51
|
+
await p.complete(_jsx(ImportSuccess, { database: databaseName, input: this.flags.input }));
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
render() {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
getInputStream() {
|
|
58
|
+
if (this.flags.input === "-") {
|
|
59
|
+
return process.stdin;
|
|
60
|
+
}
|
|
61
|
+
return fs.createReadStream(this.flags.input);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function ImportSuccess({ database, input, }) {
|
|
65
|
+
return (_jsxs(Success, { children: ["Dump of MySQL database ", _jsx(Value, { children: database }), " successfully imported", " ", "from ", _jsx(Value, { children: input })] }));
|
|
66
|
+
}
|
|
67
|
+
function buildMySqlArgs(d) {
|
|
68
|
+
return ["-h", d.hostname, "-u", d.user, `-p${d.password}`, d.database];
|
|
69
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { phpInstaller } from "../../commands/app/create/php.js";
|
|
2
|
+
import { nodeInstaller } from "../../commands/app/create/node.js";
|
|
3
|
+
import { pythonInstaller } from "../../commands/app/create/python.js";
|
|
4
|
+
/**
|
|
5
|
+
* Tests if an app installation is for a custom app (for example, a custom PHP
|
|
6
|
+
* or Node.js app). These are treated differently in the UI.
|
|
7
|
+
*
|
|
8
|
+
* @param appId
|
|
9
|
+
*/
|
|
10
|
+
export function isCustomAppInstallation(appId) {
|
|
11
|
+
return [
|
|
12
|
+
phpInstaller.appId,
|
|
13
|
+
nodeInstaller.appId,
|
|
14
|
+
pythonInstaller.appId,
|
|
15
|
+
].includes(appId);
|
|
16
|
+
}
|
package/dist/lib/app/flags.js
CHANGED
|
@@ -13,6 +13,10 @@ import { makeFlagSet } from "../context_flags.js";
|
|
|
13
13
|
export const { flags: appInstallationFlags, args: appInstallationArgs, withId: withAppInstallationId, } = makeFlagSet("installation", "i", {
|
|
14
14
|
displayName: "app installation",
|
|
15
15
|
normalize: normalizeAppInstallationId,
|
|
16
|
+
expectedShortIDFormat: {
|
|
17
|
+
pattern: /^a-.*/,
|
|
18
|
+
display: "a-XXXXXX",
|
|
19
|
+
},
|
|
16
20
|
});
|
|
17
21
|
function buildFlagsWithDescription(appName) {
|
|
18
22
|
return {
|
|
@@ -33,6 +33,10 @@ export type FlagSet<TName extends ContextNames> = {
|
|
|
33
33
|
export type FlagSetOptions = {
|
|
34
34
|
normalize: NormalizeFn;
|
|
35
35
|
displayName: string;
|
|
36
|
+
expectedShortIDFormat: {
|
|
37
|
+
pattern: RegExp;
|
|
38
|
+
display: string;
|
|
39
|
+
};
|
|
36
40
|
};
|
|
37
41
|
export type NormalizeFn = (apiClient: MittwaldAPIV2Client, id: string) => string | Promise<string>;
|
|
38
42
|
export declare function makeMissingContextInputError<TName extends ContextNames>(commandType: {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Args, Flags } from "@oclif/core";
|
|
2
2
|
import { Context } from "./context.js";
|
|
3
|
+
import UnexpectedShortIDPassedError from "./error/UnexpectedShortIDPassedError.js";
|
|
4
|
+
import { isUuid } from "../normalize_id.js";
|
|
5
|
+
import { articleForWord } from "./language.js";
|
|
3
6
|
export class MissingFlagError extends Error {
|
|
4
7
|
constructor(name, flagName) {
|
|
5
8
|
super(`No ${name} ID given. Please specify one with --${flagName} or set a default ${name} with 'mittwald context set --${flagName} <${flagName}>'`);
|
|
@@ -30,7 +33,7 @@ export function makeMissingContextInputError(commandType, name, flagName, contex
|
|
|
30
33
|
}
|
|
31
34
|
export function makeFlagSet(name, char, opts = {}) {
|
|
32
35
|
const { displayName = name, normalize = (_, id) => id } = opts;
|
|
33
|
-
const article = displayName
|
|
36
|
+
const article = articleForWord(displayName);
|
|
34
37
|
const flagName = `${name}-id`;
|
|
35
38
|
const flags = {
|
|
36
39
|
[flagName]: Flags.string({
|
|
@@ -55,9 +58,19 @@ export function makeFlagSet(name, char, opts = {}) {
|
|
|
55
58
|
}
|
|
56
59
|
return undefined;
|
|
57
60
|
};
|
|
61
|
+
let idInputSanityCheck = () => { };
|
|
62
|
+
if (opts.expectedShortIDFormat != null) {
|
|
63
|
+
const format = opts.expectedShortIDFormat;
|
|
64
|
+
idInputSanityCheck = (id) => {
|
|
65
|
+
if (!isUuid(id) && !format.pattern.test(id)) {
|
|
66
|
+
throw new UnexpectedShortIDPassedError(displayName, format.display);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
58
70
|
const withId = async (apiClient, commandType, flags, args, cfg) => {
|
|
59
71
|
const idInput = idFromArgsOrFlag(flags, args);
|
|
60
72
|
if (idInput) {
|
|
73
|
+
idInputSanityCheck(idInput);
|
|
61
74
|
return normalize(apiClient, idInput);
|
|
62
75
|
}
|
|
63
76
|
const idFromContext = await new Context(apiClient, cfg).getContextValue(flagName);
|
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
import { ProcessRenderer } from "../../../rendering/process/process.js";
|
|
2
|
-
import { MittwaldAPIV2
|
|
3
|
-
|
|
2
|
+
import type { MittwaldAPIV2 } from "@mittwald/api-client";
|
|
3
|
+
import { MittwaldAPIV2Client } from "@mittwald/api-client";
|
|
4
|
+
type Project = MittwaldAPIV2.Components.Schemas.ProjectProject;
|
|
5
|
+
export interface MySQLConnectionFlags {
|
|
4
6
|
"mysql-password": string | undefined;
|
|
5
7
|
"temporary-user"?: boolean;
|
|
6
8
|
"ssh-user"?: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
+
}
|
|
10
|
+
export interface MySQLConnectionDetails {
|
|
9
11
|
hostname: string;
|
|
10
12
|
database: string;
|
|
11
13
|
user: string;
|
|
12
14
|
sshHost: string;
|
|
13
15
|
sshUser: string;
|
|
14
|
-
project:
|
|
15
|
-
}
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
project: Project;
|
|
17
|
+
}
|
|
18
|
+
export type MySQLConnectionDetailsWithPassword = MySQLConnectionDetails & {
|
|
19
|
+
password: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Runs a callback function with connection details for a MySQL database.
|
|
23
|
+
*
|
|
24
|
+
* Depending on the flags, this function will either use the credentials
|
|
25
|
+
* provided in the flags (or prompt for a password), or create a temporary user
|
|
26
|
+
* for the operation, which will be cleaned up afterwards.
|
|
27
|
+
*/
|
|
28
|
+
export declare function runWithConnectionDetails<TRes>(apiClient: MittwaldAPIV2Client, databaseId: string, p: ProcessRenderer, flags: MySQLConnectionFlags, cb: (connectionDetails: MySQLConnectionDetailsWithPassword) => Promise<TRes>): Promise<TRes>;
|
|
29
|
+
export declare function getConnectionDetailsWithPassword(apiClient: MittwaldAPIV2Client, databaseId: string, p: ProcessRenderer, flags: MySQLConnectionFlags): Promise<MySQLConnectionDetailsWithPassword>;
|
|
30
|
+
export declare function getConnectionDetails(apiClient: MittwaldAPIV2Client, databaseId: string, sshUser: string | undefined, p: ProcessRenderer): Promise<MySQLConnectionDetails>;
|
|
31
|
+
export {};
|