@mittwald/cli 1.10.0 → 1.11.1

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 (106) hide show
  1. package/bin/run.js +4 -1
  2. package/dist/commands/app/create/node.d.ts +1 -3
  3. package/dist/commands/app/create/php-worker.d.ts +1 -3
  4. package/dist/commands/app/create/php.d.ts +1 -3
  5. package/dist/commands/app/create/python.d.ts +1 -3
  6. package/dist/commands/app/create/static.d.ts +1 -3
  7. package/dist/commands/app/dependency/update.js +2 -4
  8. package/dist/commands/app/install/contao.d.ts +1 -3
  9. package/dist/commands/app/install/joomla.d.ts +1 -3
  10. package/dist/commands/app/install/matomo.d.ts +1 -3
  11. package/dist/commands/app/install/nextcloud.d.ts +1 -3
  12. package/dist/commands/app/install/shopware5.d.ts +1 -3
  13. package/dist/commands/app/install/shopware6.d.ts +1 -3
  14. package/dist/commands/app/install/typo3.d.ts +1 -3
  15. package/dist/commands/app/install/wordpress.d.ts +1 -3
  16. package/dist/commands/app/ssh.d.ts +1 -0
  17. package/dist/commands/app/ssh.js +15 -1
  18. package/dist/commands/app/update.js +4 -8
  19. package/dist/commands/app/upgrade.js +9 -9
  20. package/dist/commands/backup/create.js +1 -2
  21. package/dist/commands/backup/download.js +4 -5
  22. package/dist/commands/container/run.d.ts +4 -2
  23. package/dist/commands/container/run.js +7 -6
  24. package/dist/commands/database/mysql/create.js +1 -2
  25. package/dist/commands/database/mysql/dump.js +1 -2
  26. package/dist/commands/database/mysql/import.js +1 -2
  27. package/dist/commands/ddev/init.js +2 -6
  28. package/dist/commands/extension/install.js +1 -3
  29. package/dist/commands/login/reset.js +1 -2
  30. package/dist/commands/mail/address/create.js +1 -4
  31. package/dist/commands/mail/deliverybox/create.js +1 -4
  32. package/dist/commands/project/create.js +4 -6
  33. package/dist/commands/registry/create.js +1 -2
  34. package/dist/commands/registry/update.js +1 -2
  35. package/dist/commands/user/api-token/create.js +1 -1
  36. package/dist/commands/user/ssh-key/create.js +3 -8
  37. package/dist/commands/user/ssh-key/import.js +2 -3
  38. package/dist/lib/basecommands/DeleteBaseCommand.js +4 -4
  39. package/dist/lib/ddev/init_assert.js +1 -6
  40. package/dist/lib/ddev/init_database.js +1 -7
  41. package/dist/lib/ddev/init_projecttype.js +2 -11
  42. package/dist/lib/intellij/config.d.ts +5 -0
  43. package/dist/lib/intellij/config.js +295 -0
  44. package/dist/lib/intellij/config.test.d.ts +1 -0
  45. package/dist/lib/intellij/config.test.js +262 -0
  46. package/dist/lib/intellij/config_xml_types.d.ts +72 -0
  47. package/dist/lib/intellij/config_xml_types.js +2 -0
  48. package/dist/lib/resources/app/Installer.d.ts +6 -2
  49. package/dist/lib/resources/app/Installer.js +13 -6
  50. package/dist/lib/resources/app/flags.js +9 -12
  51. package/dist/lib/resources/app/install.d.ts +2 -1
  52. package/dist/lib/resources/app/install.js +3 -1
  53. package/dist/lib/resources/app/versions.js +1 -4
  54. package/dist/lib/resources/app/wait.js +1 -3
  55. package/dist/lib/resources/mail/commons.js +1 -4
  56. package/dist/lib/resources/ssh/appinstall.d.ts +3 -1
  57. package/dist/lib/resources/ssh/appinstall.js +1 -0
  58. package/dist/lib/resources/stack/enrich.js +9 -2
  59. package/dist/lib/resources/stack/enrich.test.d.ts +1 -0
  60. package/dist/lib/resources/stack/enrich.test.js +167 -0
  61. package/dist/lib/resources/stack/types.d.ts +1 -1
  62. package/dist/rendering/process/components/ProcessStateIcon.js +1 -1
  63. package/dist/rendering/process/process.d.ts +16 -16
  64. package/dist/rendering/process/process_exec.d.ts +1 -2
  65. package/dist/rendering/process/process_fancy.d.ts +9 -9
  66. package/dist/rendering/process/process_flags.js +4 -0
  67. package/dist/rendering/process/process_quiet.d.ts +3 -4
  68. package/dist/rendering/process/process_simple.d.ts +26 -0
  69. package/dist/rendering/process/process_simple.js +143 -0
  70. package/dist/rendering/process/process_simple.test.d.ts +1 -0
  71. package/dist/rendering/process/process_simple.test.js +149 -0
  72. package/dist/rendering/react/components/AppInstallation/AppBackendAccessHints.d.ts +15 -0
  73. package/dist/rendering/react/components/AppInstallation/AppBackendAccessHints.js +13 -0
  74. package/dist/rendering/react/components/AppInstallation/AppDomainConnectionHints.d.ts +12 -0
  75. package/dist/rendering/react/components/AppInstallation/AppDomainConnectionHints.js +21 -0
  76. package/dist/rendering/react/components/AppInstallation/AppManagementCommands.d.ts +12 -0
  77. package/dist/rendering/react/components/AppInstallation/AppManagementCommands.js +17 -0
  78. package/dist/rendering/react/components/AppInstallation/AppUsageHints.d.ts +17 -0
  79. package/dist/rendering/react/components/AppInstallation/AppUsageHints.js +23 -0
  80. package/dist/rendering/react/components/Container/CommandHint.d.ts +14 -0
  81. package/dist/rendering/react/components/Container/CommandHint.js +13 -0
  82. package/dist/rendering/react/components/Container/ContainerManagementCommands.d.ts +22 -0
  83. package/dist/rendering/react/components/Container/ContainerManagementCommands.js +23 -0
  84. package/dist/rendering/react/components/Container/ContainerUsageHints.d.ts +12 -0
  85. package/dist/rendering/react/components/Container/ContainerUsageHints.js +35 -0
  86. package/dist/rendering/react/components/Container/DomainConnectionHints.d.ts +12 -0
  87. package/dist/rendering/react/components/Container/DomainConnectionHints.js +21 -0
  88. package/dist/rendering/react/components/Container/InternalConnectionHints.d.ts +12 -0
  89. package/dist/rendering/react/components/Container/InternalConnectionHints.js +12 -0
  90. package/dist/rendering/react/components/Container/NoPortsUsageHints.d.ts +12 -0
  91. package/dist/rendering/react/components/Container/NoPortsUsageHints.js +12 -0
  92. package/dist/rendering/react/components/Container/PortConnectionHints.d.ts +13 -0
  93. package/dist/rendering/react/components/Container/PortConnectionHints.js +11 -0
  94. package/dist/rendering/react/components/Container/PortForwardingHints.d.ts +12 -0
  95. package/dist/rendering/react/components/Container/PortForwardingHints.js +18 -0
  96. package/dist/rendering/react/components/Container/types.d.ts +4 -0
  97. package/dist/rendering/react/components/Container/types.js +1 -0
  98. package/dist/rendering/react/components/Error/ErrorBox.d.ts +1 -1
  99. package/dist/rendering/react/components/Error/ErrorBox.js +3 -4
  100. package/dist/rendering/react/components/Error/GenericError.js +1 -1
  101. package/dist/rendering/react/components/ErrorBoundary.d.ts +1 -1
  102. package/dist/rendering/react/components/Success.d.ts +0 -1
  103. package/dist/rendering/react/components/Success.js +4 -2
  104. package/dist/rendering/react/styles/useDefaultBoxStyles.d.ts +11 -0
  105. package/dist/rendering/react/styles/useDefaultBoxStyles.js +23 -0
  106. package/package.json +7 -6
@@ -1,11 +1,9 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Flags } from "@oclif/core";
3
3
  import { assertStatus } from "@mittwald/api-client-commons";
4
4
  import { serverFlags } from "../../lib/resources/server/flags.js";
5
5
  import { ExecRenderBaseCommand } from "../../lib/basecommands/ExecRenderBaseCommand.js";
6
- import { Text } from "ink";
7
6
  import { Success } from "../../rendering/react/components/Success.js";
8
- import { Value } from "../../rendering/react/components/Value.js";
9
7
  import { makeProcessRenderer, processFlags, } from "../../rendering/process/process_flags.js";
10
8
  import { waitFlags, waitUntil } from "../../lib/wait.js";
11
9
  import Context from "../../lib/context/Context.js";
@@ -29,16 +27,16 @@ export default class Create extends ExecRenderBaseCommand {
29
27
  const { description } = flags;
30
28
  const process = makeProcessRenderer(flags, "Creating project");
31
29
  const serverId = await this.withServerId(Create);
32
- const stepCreating = process.addStep(_jsx(Text, { children: "creating a new project" }));
30
+ const stepCreating = process.addStep("creating a new project");
33
31
  const result = await this.apiClient.project.createProject({
34
32
  serverId,
35
33
  data: { description },
36
34
  });
37
35
  assertStatus(result, 201);
38
36
  stepCreating.complete();
39
- process.addInfo(_jsxs(Text, { children: ["project ID: ", _jsx(Value, { children: result.data.id })] }));
37
+ process.addInfo(`project ID: ${result.data.id}`);
40
38
  if (flags.wait) {
41
- const stepWaiting = process.addStep(_jsx(Text, { children: "waiting for project to be ready" }));
39
+ const stepWaiting = process.addStep("waiting for project to be ready");
42
40
  await waitUntil(async () => {
43
41
  const projectResponse = await this.apiClient.project.getProject({
44
42
  projectId: result.data.id,
@@ -6,7 +6,6 @@ import { Success } from "../../rendering/react/components/Success.js";
6
6
  import { Value } from "../../rendering/react/components/Value.js";
7
7
  import { projectFlags } from "../../lib/resources/project/flags.js";
8
8
  import { Flags } from "@oclif/core";
9
- import { Text } from "ink";
10
9
  export class Create extends ExecRenderBaseCommand {
11
10
  static summary = "Create a new container registry";
12
11
  static flags = {
@@ -72,6 +71,6 @@ export class Create extends ExecRenderBaseCommand {
72
71
  if (this.flags.password) {
73
72
  return this.flags.password;
74
73
  }
75
- return await process.addInput(_jsx(Text, { children: "enter registry password" }), true);
74
+ return await process.addInput("enter registry password", true);
76
75
  }
77
76
  }
@@ -4,7 +4,6 @@ import { Args, Flags } from "@oclif/core";
4
4
  import { makeProcessRenderer, processFlags, } from "../../rendering/process/process_flags.js";
5
5
  import { Success } from "../../rendering/react/components/Success.js";
6
6
  import assertSuccess from "../../lib/apiutil/assert_success.js";
7
- import { Text } from "ink";
8
7
  export default class Update extends ExecRenderBaseCommand {
9
8
  static description = "Update an existing container registry";
10
9
  static args = {
@@ -68,6 +67,6 @@ export default class Update extends ExecRenderBaseCommand {
68
67
  if (this.flags.password) {
69
68
  return this.flags.password;
70
69
  }
71
- return await process.addInput(_jsx(Text, { children: "enter registry password" }), true);
70
+ return await process.addInput("enter registry password", true);
72
71
  }
73
72
  }
@@ -39,7 +39,7 @@ export default class Create extends ExecRenderBaseCommand {
39
39
  });
40
40
  step.complete();
41
41
  assertStatus(response, 201);
42
- process.complete(_jsx(Success, { width: 100, children: _jsxs(Text, { children: ["API token successfully created. Have fun. \uD83E\uDD73", _jsx(Newline, { count: 2 }), "This is your API token; make sure to store it somewhere safe:", _jsx(Newline, { count: 1 }), _jsx(Value, { children: response.data.token })] }) }));
42
+ process.complete(_jsx(Success, { children: _jsxs(Text, { children: ["API token successfully created. Have fun. \uD83E\uDD73", _jsx(Newline, { count: 2 }), "This is your API token; make sure to store it somewhere safe:", _jsx(Newline, { count: 1 }), _jsx(Value, { children: response.data.token })] }) }));
43
43
  return { token: response.data.token };
44
44
  }
45
45
  catch (e) {
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Flags } from "@oclif/core";
3
3
  import { assertStatus } from "@mittwald/api-client-commons";
4
4
  import * as path from "path";
@@ -7,8 +7,6 @@ import * as fs from "fs/promises";
7
7
  import { ExecRenderBaseCommand } from "../../../lib/basecommands/ExecRenderBaseCommand.js";
8
8
  import { makeProcessRenderer, processFlags, } from "../../../rendering/process/process_flags.js";
9
9
  import { Success } from "../../../rendering/react/components/Success.js";
10
- import { Filename } from "../../../rendering/react/components/Filename.js";
11
- import { Text } from "ink";
12
10
  import { spawnInProcess } from "../../../rendering/process/process_exec.js";
13
11
  import { expireFlags } from "../../../lib/flags/expireFlags.js";
14
12
  export default class Create extends ExecRenderBaseCommand {
@@ -39,7 +37,7 @@ export default class Create extends ExecRenderBaseCommand {
39
37
  }
40
38
  await spawnInProcess(r, "generating SSH key using ssh-keygen", cmd, args);
41
39
  const publicKey = await fs.readFile(outputFile + ".pub", "utf-8");
42
- r.addInfo(_jsx(InfoSSHKeySaved, { filename: outputFile }));
40
+ r.addInfo(`ssh key saved to ${outputFile}.`);
43
41
  await r.runStep("importing SSH key", async () => {
44
42
  const response = await this.apiClient.user.createSshKey({
45
43
  data: {
@@ -58,12 +56,9 @@ export default class Create extends ExecRenderBaseCommand {
58
56
  if (this.flags["no-passphrase"]) {
59
57
  return "";
60
58
  }
61
- return await r.addInput(_jsx(Text, { children: "enter passphrase for SSH key" }), true);
59
+ return await r.addInput("enter passphrase for SSH key", true);
62
60
  }
63
61
  }
64
62
  function SSHKeySuccess() {
65
63
  return (_jsx(Success, { children: "Your SSH key was successfully created and imported to your user profile." }));
66
64
  }
67
- function InfoSSHKeySaved({ filename }) {
68
- return (_jsxs(Text, { children: ["ssh key saved to ", _jsx(Filename, { filename: filename }), "."] }));
69
- }
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Flags } from "@oclif/core";
3
3
  import { assertStatus } from "@mittwald/api-client-commons";
4
4
  import * as path from "path";
@@ -7,7 +7,6 @@ import * as fs from "fs/promises";
7
7
  import { ExecRenderBaseCommand } from "../../../lib/basecommands/ExecRenderBaseCommand.js";
8
8
  import { makeProcessRenderer, processFlags, } from "../../../rendering/process/process_flags.js";
9
9
  import { Success } from "../../../rendering/react/components/Success.js";
10
- import { Filename } from "../../../rendering/react/components/Filename.js";
11
10
  import { expireFlags } from "../../../lib/flags/expireFlags.js";
12
11
  export default class Import extends ExecRenderBaseCommand {
13
12
  static description = "Import an existing (local) SSH key";
@@ -32,7 +31,7 @@ export default class Import extends ExecRenderBaseCommand {
32
31
  });
33
32
  const keyAlreadyExists = (keys.sshKeys ?? []).some(({ key }) => publicKeyParts.includes(key));
34
33
  if (keyAlreadyExists) {
35
- r.addInfo(_jsxs(_Fragment, { children: ["the SSH key ", _jsx(Filename, { filename: inputFile }), " is already imported."] }));
34
+ r.addInfo(`the SSH key ${inputFile} is already imported.`);
36
35
  await r.complete(_jsx(SSHKeySuccess, {}));
37
36
  return;
38
37
  }
@@ -1,4 +1,4 @@
1
- import { jsxs as _jsxs, Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Flags, ux } from "@oclif/core";
3
3
  import { ExecRenderBaseCommand } from "./ExecRenderBaseCommand.js";
4
4
  import { makeProcessRenderer, processFlags, } from "../../rendering/process/process_flags.js";
@@ -18,15 +18,15 @@ export class DeleteBaseCommand extends ExecRenderBaseCommand {
18
18
  const resourceName = this.ctor.resourceName;
19
19
  const process = makeProcessRenderer(this.flags, `Deleting ${resourceName}`);
20
20
  if (!this.flags.force) {
21
- const confirmed = await process.addConfirmation(_jsxs(Text, { children: ["confirm deletion of ", resourceName] }));
21
+ const confirmed = await process.addConfirmation(`confirm deletion of ${resourceName}`);
22
22
  if (!confirmed) {
23
- process.addInfo(_jsxs(Text, { children: ["deletion of ", resourceName, " was cancelled"] }));
23
+ process.addInfo(`deletion of ${resourceName} was cancelled`);
24
24
  process.complete(_jsx(_Fragment, {}));
25
25
  ux.exit(1);
26
26
  return;
27
27
  }
28
28
  }
29
- const deletingStep = process.addStep(_jsxs(Text, { children: ["deleting ", resourceName] }));
29
+ const deletingStep = process.addStep(`deleting ${resourceName}`);
30
30
  try {
31
31
  await this.deleteResource();
32
32
  deletingStep.complete();
@@ -1,7 +1,5 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
1
  import { promisify } from "util";
3
2
  import { exec } from "child_process";
4
- import { Value } from "../../rendering/react/components/Value.js";
5
3
  import { hasBinaryInPath } from "../util/fs/hasBinaryInPath.js";
6
4
  const execAsync = promisify(exec);
7
5
  export async function assertDDEVIsInstalled(r) {
@@ -14,9 +12,6 @@ export async function assertDDEVIsInstalled(r) {
14
12
  export async function determineDDEVVersion(r) {
15
13
  const { stdout } = await execAsync("ddev --version");
16
14
  const version = stdout.trim().replace(/^ddev version +/, "");
17
- r.addInfo(_jsx(InfoDDEVVersion, { version: version }));
15
+ r.addInfo(`detected DDEV version: ${version}`);
18
16
  return version;
19
17
  }
20
- function InfoDDEVVersion({ version }) {
21
- return (_jsxs(_Fragment, { children: ["detected DDEV version: ", _jsx(Value, { children: version })] }));
22
- }
@@ -1,7 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
1
  import { assertStatus, } from "@mittwald/api-client";
3
- import { Value } from "../../rendering/react/components/Value.js";
4
- import { Text } from "ink";
5
2
  /**
6
3
  * Determines the database ID to use for the DDEV project.
7
4
  *
@@ -29,7 +26,7 @@ export async function determineDDEVDatabaseId(r, apiClient, flags, appInstallati
29
26
  mysqlDatabaseId: databaseId,
30
27
  });
31
28
  assertStatus(mysqlDatabaseResponse, 200);
32
- r.addInfo(_jsx(InfoDatabase, { name: mysqlDatabaseResponse.data.name }));
29
+ r.addInfo(`using database: ${mysqlDatabaseResponse.data.name}`);
33
30
  return mysqlDatabaseResponse.data.name;
34
31
  }
35
32
  return await promptDatabaseFromUser(r, apiClient, appInstallation);
@@ -54,6 +51,3 @@ async function promptDatabaseFromUser(r, apiClient, appInstallation) {
54
51
  },
55
52
  ]);
56
53
  }
57
- function InfoDatabase({ name }) {
58
- return (_jsxs(Text, { children: ["using database: ", _jsx(Value, { children: name })] }));
59
- }
@@ -1,9 +1,6 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
1
  import { typo3Installer } from "../../commands/app/install/typo3.js";
3
2
  import { wordpressInstaller } from "../../commands/app/install/wordpress.js";
4
3
  import { shopware6Installer } from "../../commands/app/install/shopware6.js";
5
- import { Value } from "../../rendering/react/components/Value.js";
6
- import { Text } from "ink";
7
4
  /**
8
5
  * A list of all known DDEV project types. Shamelessly stolen from
9
6
  * https://ddev.readthedocs.io/en/latest/users/configuration/config/#type
@@ -38,12 +35,12 @@ export const knownDDEVProjectTypes = [
38
35
  */
39
36
  export async function determineProjectType(r, client, inst, typeOverride) {
40
37
  if (typeOverride !== "auto") {
41
- r.addInfo(_jsx(ProjectTypeInfoOverride, { type: typeOverride }));
38
+ r.addInfo(`using DDEV project type: ${typeOverride} (explicitly specified)`);
42
39
  return typeOverride;
43
40
  }
44
41
  const determinedProjectType = await determineProjectTypeFromAppInstallation(client, inst);
45
42
  if (determinedProjectType !== null) {
46
- r.addInfo(_jsx(ProjectTypeInfoAuto, { type: determinedProjectType }));
43
+ r.addInfo(`using DDEV project type: ${determinedProjectType} (derived from app installation)`);
47
44
  return determinedProjectType;
48
45
  }
49
46
  return await promptProjectTypeFromUser(r);
@@ -76,9 +73,3 @@ export async function determineProjectTypeFromAppInstallation(client, inst) {
76
73
  return null;
77
74
  }
78
75
  }
79
- function ProjectTypeInfoOverride({ type }) {
80
- return (_jsxs(Text, { children: ["using DDEV project type: ", _jsx(Value, { children: type }), " (explicitly specified)"] }));
81
- }
82
- function ProjectTypeInfoAuto({ type }) {
83
- return (_jsxs(Text, { children: ["using DDEV project type: ", _jsx(Value, { children: type }), " (derived from app installation)"] }));
84
- }
@@ -0,0 +1,5 @@
1
+ import { SSHConnectionData } from "../resources/ssh/types.js";
2
+ export interface IntellijConfigData extends SSHConnectionData {
3
+ appShortId: string;
4
+ }
5
+ export declare function generateIntellijConfigs(data: IntellijConfigData, projectDir?: string): void;
@@ -0,0 +1,295 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { randomUUID } from "crypto";
4
+ import { XMLBuilder, XMLParser } from "fast-xml-parser";
5
+ // Common XML configuration
6
+ const XML_PARSER_CONFIG = {
7
+ ignoreAttributes: false,
8
+ attributeNamePrefix: "@_",
9
+ parseAttributeValue: false,
10
+ };
11
+ const XML_BUILDER_CONFIG = {
12
+ ignoreAttributes: false,
13
+ attributeNamePrefix: "@_",
14
+ format: true,
15
+ suppressBooleanAttributes: false,
16
+ };
17
+ // Common XML document structure
18
+ const createXmlDocumentBase = () => ({
19
+ "?xml": { "@_version": "1.0", "@_encoding": "UTF-8" },
20
+ project: { "@_version": "4" },
21
+ });
22
+ export function generateIntellijConfigs(data, projectDir = ".") {
23
+ const ideaDir = path.join(projectDir, ".idea");
24
+ try {
25
+ if (!fs.existsSync(ideaDir)) {
26
+ fs.mkdirSync(ideaDir, { recursive: true });
27
+ }
28
+ }
29
+ catch (error) {
30
+ throw new Error(`Cannot create .idea directory: ${error instanceof Error ? error.message : error}`);
31
+ }
32
+ const webServerId = randomUUID();
33
+ // First, ensure SSH config exists and get its ID (existing or newly created)
34
+ const sshConfigId = ensureSshConfigExists(data, ideaDir);
35
+ // Then create web server and deployment configs using the correct SSH config ID
36
+ generateWebServersXml(data, webServerId, sshConfigId, ideaDir);
37
+ generateDeploymentXml(data, ideaDir);
38
+ }
39
+ // Common XML manipulation utility
40
+ function manipulateXmlFile(ideaDir, config, newItem) {
41
+ const configPath = path.join(ideaDir, config.filename);
42
+ const parser = new XMLParser(XML_PARSER_CONFIG);
43
+ const builder = new XMLBuilder(XML_BUILDER_CONFIG);
44
+ let xmlDoc = loadExistingXmlDocument(configPath, parser);
45
+ if (xmlDoc) {
46
+ const wasAdded = tryAddToExistingDocument(xmlDoc, newItem, config);
47
+ if (!wasAdded) {
48
+ return; // Item already exists
49
+ }
50
+ }
51
+ else {
52
+ xmlDoc = config.createNewDocumentFn(newItem);
53
+ }
54
+ const xmlContent = builder.build(xmlDoc);
55
+ fs.writeFileSync(configPath, xmlContent);
56
+ }
57
+ function loadExistingXmlDocument(configPath, parser) {
58
+ if (!fs.existsSync(configPath)) {
59
+ return null;
60
+ }
61
+ const content = fs.readFileSync(configPath, "utf8");
62
+ const xmlDoc = parser.parse(content);
63
+ if (!xmlDoc.project?.component) {
64
+ throw new Error("Invalid XML structure");
65
+ }
66
+ return xmlDoc;
67
+ }
68
+ function tryAddToExistingDocument(xmlDoc, newItem, config) {
69
+ // This function delegates to the specific addItemFn
70
+ // Return false if item already exists, true if added
71
+ return config.addItemFn(xmlDoc, newItem);
72
+ }
73
+ // Helper function to handle array/single element patterns
74
+ function ensureArray(item) {
75
+ return Array.isArray(item) ? item : [item];
76
+ }
77
+ // Specific type-safe helper functions for each XML structure
78
+ function addSshConfig(configs, newConfig) {
79
+ if (Array.isArray(configs.sshConfig)) {
80
+ configs.sshConfig.push(newConfig);
81
+ }
82
+ else if (configs.sshConfig) {
83
+ configs.sshConfig = [configs.sshConfig, newConfig];
84
+ }
85
+ else {
86
+ configs.sshConfig = newConfig;
87
+ }
88
+ }
89
+ function addWebServer(option, newServer) {
90
+ if (Array.isArray(option.webServer)) {
91
+ option.webServer.push(newServer);
92
+ }
93
+ else if (option.webServer) {
94
+ option.webServer = [option.webServer, newServer];
95
+ }
96
+ else {
97
+ option.webServer = newServer;
98
+ }
99
+ }
100
+ function addDeploymentPath(serverData, newPath) {
101
+ if (Array.isArray(serverData.paths)) {
102
+ serverData.paths.push(newPath);
103
+ }
104
+ else if (serverData.paths) {
105
+ serverData.paths = [serverData.paths, newPath];
106
+ }
107
+ else {
108
+ serverData.paths = newPath;
109
+ }
110
+ }
111
+ // Function to ensure SSH config exists and return its ID
112
+ function ensureSshConfigExists(data, ideaDir) {
113
+ const configPath = path.join(ideaDir, "sshConfigs.xml");
114
+ const parser = new XMLParser(XML_PARSER_CONFIG);
115
+ const existingXmlDoc = loadExistingXmlDocument(configPath, parser);
116
+ if (existingXmlDoc) {
117
+ // Check if SSH config for this host already exists
118
+ const configs = existingXmlDoc.project.component.configs;
119
+ if (configs?.sshConfig) {
120
+ const existingConfigs = ensureArray(configs.sshConfig);
121
+ const existingConfig = existingConfigs.find((config) => config["@_host"] === data.host);
122
+ if (existingConfig) {
123
+ // Return existing SSH config ID
124
+ return existingConfig["@_id"];
125
+ }
126
+ }
127
+ }
128
+ // SSH config doesn't exist, create it with a new ID
129
+ const newSshConfigId = randomUUID();
130
+ generateSshConfigsXml(data, newSshConfigId, ideaDir);
131
+ return newSshConfigId;
132
+ }
133
+ // SSH Config specific functions
134
+ function createSshConfigItem(host, username, configId) {
135
+ return {
136
+ "@_authType": "OPEN_SSH",
137
+ "@_host": host,
138
+ "@_id": configId,
139
+ "@_port": "22",
140
+ "@_nameFormat": "DESCRIPTIVE",
141
+ "@_username": username,
142
+ "@_useOpenSSHConfig": "true",
143
+ };
144
+ }
145
+ function generateSshConfigsXml(data, configId, ideaDir) {
146
+ const sshConfigItem = createSshConfigItem(data.host, data.user, configId);
147
+ const config = {
148
+ filename: "sshConfigs.xml",
149
+ componentName: "SshConfigs",
150
+ checkDuplicateFn: (existing, newItem) => existing.some((config) => config["@_host"] === newItem["@_host"]),
151
+ addItemFn: (xmlDoc, newItem) => {
152
+ const configs = xmlDoc.project.component.configs;
153
+ if (configs?.sshConfig) {
154
+ const existingConfigs = ensureArray(configs.sshConfig);
155
+ if (existingConfigs.some((config) => config["@_host"] === newItem["@_host"])) {
156
+ return false; // Already exists
157
+ }
158
+ addSshConfig(configs, newItem);
159
+ }
160
+ else {
161
+ if (!xmlDoc.project.component.configs) {
162
+ xmlDoc.project.component.configs = {};
163
+ }
164
+ xmlDoc.project.component.configs.sshConfig = newItem;
165
+ }
166
+ return true;
167
+ },
168
+ createNewDocumentFn: (newItem) => ({
169
+ ...createXmlDocumentBase(),
170
+ project: {
171
+ "@_version": "4",
172
+ component: {
173
+ "@_name": "SshConfigs",
174
+ configs: { sshConfig: newItem },
175
+ },
176
+ },
177
+ }),
178
+ };
179
+ manipulateXmlFile(ideaDir, config, sshConfigItem);
180
+ }
181
+ // Web Server specific functions
182
+ function createWebServerItem(serverId, appShortId, host, username, sshConfigId) {
183
+ return {
184
+ "@_id": serverId,
185
+ "@_name": appShortId,
186
+ fileTransfer: {
187
+ "@_accessType": "SFTP",
188
+ "@_host": host,
189
+ "@_port": "22",
190
+ "@_sshConfigId": sshConfigId,
191
+ "@_sshConfig": `${username}@${host}:22 agent`,
192
+ "@_authAgent": "true",
193
+ advancedOptions: {
194
+ advancedOptions: {
195
+ "@_dataProtectionLevel": "Private",
196
+ "@_keepAliveTimeout": "0",
197
+ "@_passiveMode": "true",
198
+ "@_shareSSLContext": "true",
199
+ },
200
+ },
201
+ },
202
+ };
203
+ }
204
+ function generateWebServersXml(data, serverId, sshConfigId, ideaDir) {
205
+ const webServerItem = createWebServerItem(serverId, data.appShortId, data.host, data.user, sshConfigId);
206
+ const config = {
207
+ filename: "webServers.xml",
208
+ componentName: "WebServers",
209
+ checkDuplicateFn: (existing, newItem) => existing.some((server) => server["@_name"] === newItem["@_name"]),
210
+ addItemFn: (xmlDoc, newItem) => {
211
+ const servers = xmlDoc.project.component.option;
212
+ if (servers?.webServer) {
213
+ const existingServers = ensureArray(servers.webServer);
214
+ if (existingServers.some((server) => server["@_name"] === newItem["@_name"])) {
215
+ return false; // Already exists
216
+ }
217
+ addWebServer(servers, newItem);
218
+ }
219
+ else {
220
+ if (!xmlDoc.project.component.option) {
221
+ xmlDoc.project.component.option = { "@_name": "servers" };
222
+ }
223
+ xmlDoc.project.component.option.webServer = newItem;
224
+ }
225
+ return true;
226
+ },
227
+ createNewDocumentFn: (newItem) => ({
228
+ ...createXmlDocumentBase(),
229
+ project: {
230
+ "@_version": "4",
231
+ component: {
232
+ "@_name": "WebServers",
233
+ option: {
234
+ "@_name": "servers",
235
+ webServer: newItem,
236
+ },
237
+ },
238
+ },
239
+ }),
240
+ };
241
+ manipulateXmlFile(ideaDir, config, webServerItem);
242
+ }
243
+ // Deployment specific functions
244
+ function createDeploymentPathItem(appShortId, directory) {
245
+ return {
246
+ "@_name": appShortId,
247
+ serverdata: {
248
+ mappings: {
249
+ mapping: {
250
+ "@_deploy": directory,
251
+ "@_local": "$PROJECT_DIR$",
252
+ "@_web": "/",
253
+ },
254
+ },
255
+ },
256
+ };
257
+ }
258
+ function generateDeploymentXml(data, ideaDir) {
259
+ const deploymentPathItem = createDeploymentPathItem(data.appShortId, data.directory);
260
+ const config = {
261
+ filename: "deployment.xml",
262
+ componentName: "PublishConfigData",
263
+ checkDuplicateFn: (existing, newItem) => existing.some((path) => path["@_name"] === newItem["@_name"]),
264
+ addItemFn: (xmlDoc, newItem) => {
265
+ const serverData = xmlDoc.project.component.serverData;
266
+ if (serverData?.paths) {
267
+ const existingPaths = ensureArray(serverData.paths);
268
+ if (existingPaths.some((path) => path["@_name"] === newItem["@_name"])) {
269
+ return false; // Already exists
270
+ }
271
+ addDeploymentPath(serverData, newItem);
272
+ }
273
+ else {
274
+ if (!xmlDoc.project.component.serverData) {
275
+ xmlDoc.project.component.serverData = {};
276
+ }
277
+ xmlDoc.project.component.serverData.paths = newItem;
278
+ }
279
+ return true;
280
+ },
281
+ createNewDocumentFn: (newItem) => ({
282
+ ...createXmlDocumentBase(),
283
+ project: {
284
+ "@_version": "4",
285
+ component: {
286
+ "@_name": "PublishConfigData",
287
+ "@_serverName": data.appShortId,
288
+ "@_remoteFilesAllowedToDisappearOnAutoupload": "false",
289
+ serverData: { paths: newItem },
290
+ },
291
+ },
292
+ }),
293
+ };
294
+ manipulateXmlFile(ideaDir, config, deploymentPathItem);
295
+ }
@@ -0,0 +1 @@
1
+ export {};