@mittwald/cli 1.1.0 → 1.2.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.
@@ -11,10 +11,15 @@ export declare class Download extends ExecRenderBaseCommand<typeof Download, voi
11
11
  exclude: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
12
12
  "dry-run": import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
13
  delete: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ "remote-sub-directory": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
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
  quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
18
  };
19
+ static examples: {
20
+ description: string;
21
+ command: string;
22
+ }[];
18
23
  protected exec(): Promise<void>;
19
24
  protected render(): ReactNode;
20
25
  }
@@ -8,7 +8,7 @@ import { getSSHConnectionForAppInstallation } from "../../lib/resources/ssh/appi
8
8
  import { spawnInProcess } from "../../rendering/process/process_exec.js";
9
9
  import { sshConnectionFlags } from "../../lib/resources/ssh/flags.js";
10
10
  import { sshUsageDocumentation } from "../../lib/resources/ssh/doc.js";
11
- import { appInstallationSyncFlags, appInstallationSyncFlagsToRsyncFlags, filterFileDocumentation, filterFileToRsyncFlagsIfPresent, } from "../../lib/resources/app/sync.js";
11
+ import { appInstallationSyncFlags, appInstallationSyncFlagsToRsyncFlags, buildRsyncConnectionString, filterFileDocumentation, filterFileToRsyncFlagsIfPresent, } from "../../lib/resources/app/sync.js";
12
12
  import { hasBinaryInPath } from "../../lib/util/fs/hasBinaryInPath.js";
13
13
  export class Download extends ExecRenderBaseCommand {
14
14
  static summary = "Download the filesystem of an app within a project to your local machine";
@@ -30,11 +30,21 @@ export class Download extends ExecRenderBaseCommand {
30
30
  exists: false,
31
31
  }),
32
32
  };
33
+ static examples = [
34
+ {
35
+ description: "Download entire app to current working directory",
36
+ command: "$ <%= config.bin %> <%= command.id %> .",
37
+ },
38
+ {
39
+ description: "Download only shared dir from a deployer-managed app",
40
+ command: "<%= config.bin %> <%= command.id %> --remote-sub-directory=shared .",
41
+ },
42
+ ];
33
43
  async exec() {
34
44
  const appInstallationId = await this.withAppInstallationId(Download);
35
45
  const { "dry-run": dryRun, target, "ssh-user": sshUser } = this.flags;
36
46
  const p = makeProcessRenderer(this.flags, "Downloading app installation");
37
- const { host, user, directory } = await p.runStep("getting connection data", async () => {
47
+ const connectionData = await p.runStep("getting connection data", async () => {
38
48
  return getSSHConnectionForAppInstallation(this.apiClient, appInstallationId, sshUser);
39
49
  });
40
50
  await p.runStep("check if rsync is installed", async () => {
@@ -42,11 +52,12 @@ export class Download extends ExecRenderBaseCommand {
42
52
  throw new Error("this command requires rsync to be installed");
43
53
  }
44
54
  });
55
+ const rsyncHost = buildRsyncConnectionString(connectionData, this.flags);
45
56
  const rsyncOpts = [
46
57
  ...appInstallationSyncFlagsToRsyncFlags(this.flags),
47
58
  ...(await filterFileToRsyncFlagsIfPresent(target)),
48
59
  ];
49
- await spawnInProcess(p, "downloading app installation" + (dryRun ? " (dry-run)" : ""), "rsync", [...rsyncOpts, `${user}@${host}:${directory}/`, target]);
60
+ await spawnInProcess(p, "downloading app installation" + (dryRun ? " (dry-run)" : ""), "rsync", [...rsyncOpts, rsyncHost, target]);
50
61
  if (dryRun) {
51
62
  await p.complete(_jsx(Success, { children: "App would (probably) have successfully been downloaded. \uD83D\uDE42" }));
52
63
  }
@@ -11,6 +11,7 @@ export declare class Upload extends ExecRenderBaseCommand<typeof Upload, void> {
11
11
  exclude: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
12
12
  "dry-run": import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
13
  delete: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ "remote-sub-directory": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
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
  quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -8,7 +8,7 @@ import { getSSHConnectionForAppInstallation } from "../../lib/resources/ssh/appi
8
8
  import { spawnInProcess } from "../../rendering/process/process_exec.js";
9
9
  import { sshConnectionFlags } from "../../lib/resources/ssh/flags.js";
10
10
  import { sshUsageDocumentation } from "../../lib/resources/ssh/doc.js";
11
- import { appInstallationSyncFlags, appInstallationSyncFlagsToRsyncFlags, filterFileDocumentation, filterFileToRsyncFlagsIfPresent, } from "../../lib/resources/app/sync.js";
11
+ import { appInstallationSyncFlags, appInstallationSyncFlagsToRsyncFlags, buildRsyncConnectionString, filterFileDocumentation, filterFileToRsyncFlagsIfPresent, } from "../../lib/resources/app/sync.js";
12
12
  import { hasBinaryInPath } from "../../lib/util/fs/hasBinaryInPath.js";
13
13
  export class Upload extends ExecRenderBaseCommand {
14
14
  static summary = "Upload the filesystem of an app to a project";
@@ -36,7 +36,7 @@ export class Upload extends ExecRenderBaseCommand {
36
36
  const appInstallationId = await this.withAppInstallationId(Upload);
37
37
  const { "dry-run": dryRun, source, "ssh-user": sshUser } = this.flags;
38
38
  const p = makeProcessRenderer(this.flags, "Uploading app installation");
39
- const { host, user, directory } = await p.runStep("getting connection data", async () => {
39
+ const connectionData = await p.runStep("getting connection data", async () => {
40
40
  return getSSHConnectionForAppInstallation(this.apiClient, appInstallationId, sshUser);
41
41
  });
42
42
  await p.runStep("check if rsync is installed", async () => {
@@ -44,11 +44,12 @@ export class Upload extends ExecRenderBaseCommand {
44
44
  throw new Error("this command requires rsync to be installed");
45
45
  }
46
46
  });
47
+ const rsyncHost = buildRsyncConnectionString(connectionData, this.flags);
47
48
  const rsyncOpts = [
48
49
  ...appInstallationSyncFlagsToRsyncFlags(this.flags),
49
50
  ...(await filterFileToRsyncFlagsIfPresent(source)),
50
51
  ];
51
- await spawnInProcess(p, "uploading app installation" + (dryRun ? " (dry-run)" : ""), "rsync", [...rsyncOpts, source, `${user}@${host}:${directory}/`]);
52
+ await spawnInProcess(p, "uploading app installation" + (dryRun ? " (dry-run)" : ""), "rsync", [...rsyncOpts, source, rsyncHost]);
52
53
  await p.complete(_jsx(UploadSuccess, { dryRun: dryRun }));
53
54
  }
54
55
  render() {
@@ -1,15 +1,29 @@
1
1
  import { SSHConnectionFlags } from "../ssh/flags.js";
2
+ import { SSHConnectionData } from "../ssh/types.js";
2
3
  export declare const defaultRsyncFilterFile = ".mw-rsync-filter";
3
4
  export interface AppInstallationSyncFlags {
4
5
  exclude: string[];
5
6
  "dry-run": boolean;
6
7
  delete: boolean;
8
+ "sub-directory"?: string;
7
9
  }
8
10
  export declare const appInstallationSyncFlags: (direction: "upload" | "download") => {
9
11
  exclude: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
10
12
  "dry-run": import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
13
  delete: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ "remote-sub-directory": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
15
  };
13
16
  export declare const filterFileDocumentation: string;
14
17
  export declare function filterFileToRsyncFlagsIfPresent(targetDir: string, filterFile?: string): Promise<string[]>;
18
+ /**
19
+ * Build the rsync connection string for the given SSH connection data and flag
20
+ * inputs.
21
+ *
22
+ * @param host Remote SSH hostname
23
+ * @param directory Remove base directory of the app installation
24
+ * @param user Remote SSH user
25
+ * @param subDirectory Optional sub-directory within the app installation to
26
+ * sync
27
+ */
28
+ export declare function buildRsyncConnectionString({ host, directory, user }: SSHConnectionData, { "sub-directory": subDirectory }: AppInstallationSyncFlags): string;
15
29
  export declare function appInstallationSyncFlagsToRsyncFlags(f: AppInstallationSyncFlags & SSHConnectionFlags): string[];
@@ -18,6 +18,13 @@ export const appInstallationSyncFlags = (direction) => ({
18
18
  : "delete remote files that are not present locally",
19
19
  default: false,
20
20
  }),
21
+ "remote-sub-directory": Flags.string({
22
+ summary: `specify a sub-directory within the app installation to ${direction}`,
23
+ description: `This is particularly useful when you only want to ${direction} a specific sub-directory of the app installation, ` +
24
+ "for example when you are using a deployment tool that manages the app installation directory itself, " +
25
+ `and you only want to ${direction} exempt files, like environment specific configuration files or user data. ` +
26
+ `For example, if you want to ${direction} ${direction === "upload" ? "to" : "from"} "/html/my-app-XXXXX/config", set "--remote-sub-directory=config".`,
27
+ }),
21
28
  });
22
29
  export const filterFileDocumentation = `This command will also look for a file named ${defaultRsyncFilterFile} in the current ` +
23
30
  "directory and use it as a filter file for rsync. Have a look at " +
@@ -30,6 +37,22 @@ export async function filterFileToRsyncFlagsIfPresent(targetDir, filterFile = de
30
37
  }
31
38
  return [];
32
39
  }
40
+ /**
41
+ * Build the rsync connection string for the given SSH connection data and flag
42
+ * inputs.
43
+ *
44
+ * @param host Remote SSH hostname
45
+ * @param directory Remove base directory of the app installation
46
+ * @param user Remote SSH user
47
+ * @param subDirectory Optional sub-directory within the app installation to
48
+ * sync
49
+ */
50
+ export function buildRsyncConnectionString({ host, directory, user }, { "sub-directory": subDirectory }) {
51
+ if (subDirectory) {
52
+ directory = path.join(directory, subDirectory).replace(/\/$/, "");
53
+ }
54
+ return `${user}@${host}:${directory}/`;
55
+ }
33
56
  export function appInstallationSyncFlagsToRsyncFlags(f) {
34
57
  const { "dry-run": dryRun, "ssh-identity-file": sshIdentityFile, exclude, } = f;
35
58
  const rsyncOpts = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mittwald/cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Hand-crafted CLI for the mittwald API",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -51,7 +51,7 @@
51
51
  "@oclif/plugin-warn-if-update-available": "^3.0.2",
52
52
  "axios-retry": "^4.0.0",
53
53
  "chalk": "^5.3.0",
54
- "date-fns": "^3.2.0",
54
+ "date-fns": "^4.0.0",
55
55
  "ink": "^5.0.1",
56
56
  "ink-link": "^4.0.0",
57
57
  "ink-text-input": "^6.0.0",