@mittwald/cli 1.10.0 → 1.11.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.
Files changed (102) 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/rendering/process/components/ProcessStateIcon.js +1 -1
  59. package/dist/rendering/process/process.d.ts +16 -16
  60. package/dist/rendering/process/process_exec.d.ts +1 -2
  61. package/dist/rendering/process/process_fancy.d.ts +9 -9
  62. package/dist/rendering/process/process_flags.js +4 -0
  63. package/dist/rendering/process/process_quiet.d.ts +3 -4
  64. package/dist/rendering/process/process_simple.d.ts +26 -0
  65. package/dist/rendering/process/process_simple.js +143 -0
  66. package/dist/rendering/process/process_simple.test.d.ts +1 -0
  67. package/dist/rendering/process/process_simple.test.js +149 -0
  68. package/dist/rendering/react/components/AppInstallation/AppBackendAccessHints.d.ts +15 -0
  69. package/dist/rendering/react/components/AppInstallation/AppBackendAccessHints.js +13 -0
  70. package/dist/rendering/react/components/AppInstallation/AppDomainConnectionHints.d.ts +12 -0
  71. package/dist/rendering/react/components/AppInstallation/AppDomainConnectionHints.js +21 -0
  72. package/dist/rendering/react/components/AppInstallation/AppManagementCommands.d.ts +12 -0
  73. package/dist/rendering/react/components/AppInstallation/AppManagementCommands.js +17 -0
  74. package/dist/rendering/react/components/AppInstallation/AppUsageHints.d.ts +17 -0
  75. package/dist/rendering/react/components/AppInstallation/AppUsageHints.js +23 -0
  76. package/dist/rendering/react/components/Container/CommandHint.d.ts +14 -0
  77. package/dist/rendering/react/components/Container/CommandHint.js +13 -0
  78. package/dist/rendering/react/components/Container/ContainerManagementCommands.d.ts +22 -0
  79. package/dist/rendering/react/components/Container/ContainerManagementCommands.js +23 -0
  80. package/dist/rendering/react/components/Container/ContainerUsageHints.d.ts +12 -0
  81. package/dist/rendering/react/components/Container/ContainerUsageHints.js +35 -0
  82. package/dist/rendering/react/components/Container/DomainConnectionHints.d.ts +12 -0
  83. package/dist/rendering/react/components/Container/DomainConnectionHints.js +21 -0
  84. package/dist/rendering/react/components/Container/InternalConnectionHints.d.ts +12 -0
  85. package/dist/rendering/react/components/Container/InternalConnectionHints.js +12 -0
  86. package/dist/rendering/react/components/Container/NoPortsUsageHints.d.ts +12 -0
  87. package/dist/rendering/react/components/Container/NoPortsUsageHints.js +12 -0
  88. package/dist/rendering/react/components/Container/PortConnectionHints.d.ts +13 -0
  89. package/dist/rendering/react/components/Container/PortConnectionHints.js +11 -0
  90. package/dist/rendering/react/components/Container/PortForwardingHints.d.ts +12 -0
  91. package/dist/rendering/react/components/Container/PortForwardingHints.js +18 -0
  92. package/dist/rendering/react/components/Container/types.d.ts +4 -0
  93. package/dist/rendering/react/components/Container/types.js +1 -0
  94. package/dist/rendering/react/components/Error/ErrorBox.d.ts +1 -1
  95. package/dist/rendering/react/components/Error/ErrorBox.js +3 -4
  96. package/dist/rendering/react/components/Error/GenericError.js +1 -1
  97. package/dist/rendering/react/components/ErrorBoundary.d.ts +1 -1
  98. package/dist/rendering/react/components/Success.d.ts +0 -1
  99. package/dist/rendering/react/components/Success.js +4 -2
  100. package/dist/rendering/react/styles/useDefaultBoxStyles.d.ts +11 -0
  101. package/dist/rendering/react/styles/useDefaultBoxStyles.js +23 -0
  102. package/package.json +6 -5
@@ -0,0 +1,262 @@
1
+ import { describe, expect, test, beforeEach, afterEach } from "@jest/globals";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import * as os from "os";
5
+ import { XMLParser } from "fast-xml-parser";
6
+ import { generateIntellijConfigs } from "./config.js";
7
+ describe("IntelliJ Config Generator", () => {
8
+ let tempDir;
9
+ let testData;
10
+ const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "@_" });
11
+ beforeEach(() => {
12
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "intellij-test-"));
13
+ testData = {
14
+ host: "ssh.test.example.com",
15
+ user: "testuser@app123",
16
+ directory: "/var/www/html/app123",
17
+ appShortId: "app123",
18
+ };
19
+ });
20
+ afterEach(() => {
21
+ fs.rmSync(tempDir, { recursive: true, force: true });
22
+ });
23
+ describe("generateIntellijConfigs", () => {
24
+ test("should create .idea directory if it doesn't exist", () => {
25
+ generateIntellijConfigs(testData, tempDir);
26
+ const ideaDir = path.join(tempDir, ".idea");
27
+ expect(fs.existsSync(ideaDir)).toBe(true);
28
+ expect(fs.statSync(ideaDir).isDirectory()).toBe(true);
29
+ });
30
+ test("should generate all three configuration files", () => {
31
+ generateIntellijConfigs(testData, tempDir);
32
+ const ideaDir = path.join(tempDir, ".idea");
33
+ expect(fs.existsSync(path.join(ideaDir, "sshConfigs.xml"))).toBe(true);
34
+ expect(fs.existsSync(path.join(ideaDir, "webServers.xml"))).toBe(true);
35
+ expect(fs.existsSync(path.join(ideaDir, "deployment.xml"))).toBe(true);
36
+ });
37
+ test("should generate valid XML files", () => {
38
+ generateIntellijConfigs(testData, tempDir);
39
+ const ideaDir = path.join(tempDir, ".idea");
40
+ // Test that each file can be parsed without errors
41
+ const sshContent = fs.readFileSync(path.join(ideaDir, "sshConfigs.xml"), "utf8");
42
+ expect(() => parser.parse(sshContent)).not.toThrow();
43
+ const webContent = fs.readFileSync(path.join(ideaDir, "webServers.xml"), "utf8");
44
+ expect(() => parser.parse(webContent)).not.toThrow();
45
+ const deployContent = fs.readFileSync(path.join(ideaDir, "deployment.xml"), "utf8");
46
+ expect(() => parser.parse(deployContent)).not.toThrow();
47
+ });
48
+ });
49
+ describe("SSH Configs XML", () => {
50
+ test("should create SSH config with correct attributes", () => {
51
+ generateIntellijConfigs(testData, tempDir);
52
+ const configPath = path.join(tempDir, ".idea", "sshConfigs.xml");
53
+ const content = fs.readFileSync(configPath, "utf8");
54
+ const xmlDoc = parser.parse(content);
55
+ expect(xmlDoc.project["@_version"]).toBe("4");
56
+ expect(xmlDoc.project.component["@_name"]).toBe("SshConfigs");
57
+ const sshConfig = xmlDoc.project.component.configs.sshConfig;
58
+ expect(sshConfig["@_authType"]).toBe("OPEN_SSH");
59
+ expect(sshConfig["@_host"]).toBe("ssh.test.example.com");
60
+ expect(sshConfig["@_port"]).toBe("22");
61
+ expect(sshConfig["@_username"]).toBe("testuser@app123");
62
+ expect(sshConfig["@_useOpenSSHConfig"]).toBe("true");
63
+ expect(sshConfig["@_nameFormat"]).toBe("DESCRIPTIVE");
64
+ expect(sshConfig["@_id"]).toBeDefined();
65
+ });
66
+ test("should not add duplicate SSH configs for same host", () => {
67
+ generateIntellijConfigs(testData, tempDir);
68
+ generateIntellijConfigs(testData, tempDir); // Run twice
69
+ const configPath = path.join(tempDir, ".idea", "sshConfigs.xml");
70
+ const content = fs.readFileSync(configPath, "utf8");
71
+ const xmlDoc = parser.parse(content);
72
+ const sshConfig = xmlDoc.project.component.configs.sshConfig;
73
+ expect(Array.isArray(sshConfig)).toBe(false); // Should still be single config
74
+ });
75
+ test("should add multiple SSH configs for different hosts", () => {
76
+ generateIntellijConfigs(testData, tempDir);
77
+ const differentHostData = { ...testData, host: "ssh.other.example.com", appShortId: "app456" };
78
+ generateIntellijConfigs(differentHostData, tempDir);
79
+ const configPath = path.join(tempDir, ".idea", "sshConfigs.xml");
80
+ const content = fs.readFileSync(configPath, "utf8");
81
+ const xmlDoc = parser.parse(content);
82
+ const sshConfigs = xmlDoc.project.component.configs.sshConfig;
83
+ expect(Array.isArray(sshConfigs)).toBe(true);
84
+ expect(sshConfigs).toHaveLength(2);
85
+ expect(sshConfigs[0]["@_host"]).toBe("ssh.test.example.com");
86
+ expect(sshConfigs[1]["@_host"]).toBe("ssh.other.example.com");
87
+ });
88
+ test("should handle existing SSH config file correctly", () => {
89
+ const ideaDir = path.join(tempDir, ".idea");
90
+ fs.mkdirSync(ideaDir, { recursive: true });
91
+ const existingConfig = `<?xml version="1.0" encoding="UTF-8"?>
92
+ <project version="4">
93
+ <component name="SshConfigs">
94
+ <configs>
95
+ <sshConfig authType="OPEN_SSH" host="existing.example.com" id="existing-id" port="22" nameFormat="DESCRIPTIVE" username="existing" useOpenSSHConfig="true" />
96
+ </configs>
97
+ </component>
98
+ </project>`;
99
+ fs.writeFileSync(path.join(ideaDir, "sshConfigs.xml"), existingConfig);
100
+ generateIntellijConfigs(testData, tempDir);
101
+ const configPath = path.join(ideaDir, "sshConfigs.xml");
102
+ const content = fs.readFileSync(configPath, "utf8");
103
+ const xmlDoc = parser.parse(content);
104
+ const sshConfigs = xmlDoc.project.component.configs.sshConfig;
105
+ expect(Array.isArray(sshConfigs)).toBe(true);
106
+ expect(sshConfigs).toHaveLength(2);
107
+ });
108
+ test("should reuse existing SSH config ID in web server configuration", () => {
109
+ const ideaDir = path.join(tempDir, ".idea");
110
+ fs.mkdirSync(ideaDir, { recursive: true });
111
+ // Create existing SSH config with the same host as testData
112
+ const existingConfig = `<?xml version="1.0" encoding="UTF-8"?>
113
+ <project version="4">
114
+ <component name="SshConfigs">
115
+ <configs>
116
+ <sshConfig authType="OPEN_SSH" host="ssh.test.example.com" id="existing-ssh-id" port="22" nameFormat="DESCRIPTIVE" username="testuser@app123" useOpenSSHConfig="true" />
117
+ </configs>
118
+ </component>
119
+ </project>`;
120
+ fs.writeFileSync(path.join(ideaDir, "sshConfigs.xml"), existingConfig);
121
+ generateIntellijConfigs(testData, tempDir);
122
+ // Verify SSH config wasn't duplicated
123
+ const sshConfigPath = path.join(ideaDir, "sshConfigs.xml");
124
+ const sshContent = fs.readFileSync(sshConfigPath, "utf8");
125
+ const sshXmlDoc = parser.parse(sshContent);
126
+ const sshConfig = sshXmlDoc.project.component.configs.sshConfig;
127
+ expect(Array.isArray(sshConfig)).toBe(false); // Should still be single config
128
+ expect(sshConfig["@_id"]).toBe("existing-ssh-id");
129
+ // Verify web server config uses the existing SSH config ID
130
+ const webConfigPath = path.join(ideaDir, "webServers.xml");
131
+ const webContent = fs.readFileSync(webConfigPath, "utf8");
132
+ const webXmlDoc = parser.parse(webContent);
133
+ const webServer = webXmlDoc.project.component.option.webServer;
134
+ expect(webServer.fileTransfer["@_sshConfigId"]).toBe("existing-ssh-id");
135
+ });
136
+ });
137
+ describe("Web Servers XML", () => {
138
+ test("should create web server config with correct structure", () => {
139
+ generateIntellijConfigs(testData, tempDir);
140
+ const configPath = path.join(tempDir, ".idea", "webServers.xml");
141
+ const content = fs.readFileSync(configPath, "utf8");
142
+ const xmlDoc = parser.parse(content);
143
+ expect(xmlDoc.project["@_version"]).toBe("4");
144
+ expect(xmlDoc.project.component["@_name"]).toBe("WebServers");
145
+ expect(xmlDoc.project.component.option["@_name"]).toBe("servers");
146
+ const webServer = xmlDoc.project.component.option.webServer;
147
+ expect(webServer["@_name"]).toBe("app123");
148
+ expect(webServer["@_id"]).toBeDefined();
149
+ const fileTransfer = webServer.fileTransfer;
150
+ expect(fileTransfer["@_accessType"]).toBe("SFTP");
151
+ expect(fileTransfer["@_host"]).toBe("ssh.test.example.com");
152
+ expect(fileTransfer["@_port"]).toBe("22");
153
+ expect(fileTransfer["@_authAgent"]).toBe("true");
154
+ expect(fileTransfer["@_sshConfig"]).toBe("testuser@app123@ssh.test.example.com:22 agent");
155
+ const advancedOptions = fileTransfer.advancedOptions.advancedOptions;
156
+ expect(advancedOptions["@_dataProtectionLevel"]).toBe("Private");
157
+ expect(advancedOptions["@_keepAliveTimeout"]).toBe("0");
158
+ expect(advancedOptions["@_passiveMode"]).toBe("true");
159
+ expect(advancedOptions["@_shareSSLContext"]).toBe("true");
160
+ });
161
+ test("should not add duplicate web servers for same app", () => {
162
+ generateIntellijConfigs(testData, tempDir);
163
+ generateIntellijConfigs(testData, tempDir); // Run twice
164
+ const configPath = path.join(tempDir, ".idea", "webServers.xml");
165
+ const content = fs.readFileSync(configPath, "utf8");
166
+ const xmlDoc = parser.parse(content);
167
+ const webServer = xmlDoc.project.component.option.webServer;
168
+ expect(Array.isArray(webServer)).toBe(false); // Should still be single server
169
+ });
170
+ });
171
+ describe("Deployment XML", () => {
172
+ test("should create deployment config with correct mapping", () => {
173
+ generateIntellijConfigs(testData, tempDir);
174
+ const configPath = path.join(tempDir, ".idea", "deployment.xml");
175
+ const content = fs.readFileSync(configPath, "utf8");
176
+ const xmlDoc = parser.parse(content);
177
+ expect(xmlDoc.project["@_version"]).toBe("4");
178
+ expect(xmlDoc.project.component["@_name"]).toBe("PublishConfigData");
179
+ expect(xmlDoc.project.component["@_serverName"]).toBe("app123");
180
+ expect(xmlDoc.project.component["@_remoteFilesAllowedToDisappearOnAutoupload"]).toBe("false");
181
+ const paths = xmlDoc.project.component.serverData.paths;
182
+ expect(paths["@_name"]).toBe("app123");
183
+ const mapping = paths.serverdata.mappings.mapping;
184
+ expect(mapping["@_deploy"]).toBe("/var/www/html/app123");
185
+ expect(mapping["@_local"]).toBe("$PROJECT_DIR$");
186
+ expect(mapping["@_web"]).toBe("/");
187
+ });
188
+ test("should not add duplicate deployment configs for same app", () => {
189
+ generateIntellijConfigs(testData, tempDir);
190
+ generateIntellijConfigs(testData, tempDir); // Run twice
191
+ const configPath = path.join(tempDir, ".idea", "deployment.xml");
192
+ const content = fs.readFileSync(configPath, "utf8");
193
+ const xmlDoc = parser.parse(content);
194
+ const paths = xmlDoc.project.component.serverData.paths;
195
+ expect(Array.isArray(paths)).toBe(false); // Should still be single path
196
+ });
197
+ });
198
+ describe("SSH username handling", () => {
199
+ test("should use complete user string as SSH username", () => {
200
+ const userData = { ...testData, user: "m.helmich@mittwald.de@a-ce3rzc" };
201
+ generateIntellijConfigs(userData, tempDir);
202
+ const configPath = path.join(tempDir, ".idea", "sshConfigs.xml");
203
+ const content = fs.readFileSync(configPath, "utf8");
204
+ const xmlDoc = parser.parse(content);
205
+ const sshConfig = xmlDoc.project.component.configs.sshConfig;
206
+ expect(sshConfig["@_username"]).toBe("m.helmich@mittwald.de@a-ce3rzc");
207
+ // Also check web server config uses the same username
208
+ const webConfigPath = path.join(tempDir, ".idea", "webServers.xml");
209
+ const webContent = fs.readFileSync(webConfigPath, "utf8");
210
+ const webXmlDoc = parser.parse(webContent);
211
+ const webServer = webXmlDoc.project.component.option.webServer;
212
+ expect(webServer.fileTransfer["@_sshConfig"]).toBe("m.helmich@mittwald.de@a-ce3rzc@ssh.test.example.com:22 agent");
213
+ });
214
+ test("should handle simple usernames as-is", () => {
215
+ const userData = { ...testData, user: "plainuser" };
216
+ generateIntellijConfigs(userData, tempDir);
217
+ const configPath = path.join(tempDir, ".idea", "sshConfigs.xml");
218
+ const content = fs.readFileSync(configPath, "utf8");
219
+ const xmlDoc = parser.parse(content);
220
+ const sshConfig = xmlDoc.project.component.configs.sshConfig;
221
+ expect(sshConfig["@_username"]).toBe("plainuser");
222
+ });
223
+ });
224
+ describe("Error handling", () => {
225
+ test("should handle invalid directory gracefully", () => {
226
+ const invalidDir = "/root/nonexistent/readonly";
227
+ // This should throw an error for invalid directory
228
+ expect(() => {
229
+ generateIntellijConfigs(testData, invalidDir);
230
+ }).toThrow("Cannot create .idea directory");
231
+ });
232
+ test("should throw error for malformed existing XML files", () => {
233
+ const ideaDir = path.join(tempDir, ".idea");
234
+ fs.mkdirSync(ideaDir, { recursive: true });
235
+ // Create malformed XML
236
+ const malformedXml = "<?xml version='1.0'?><project><unclosed>";
237
+ fs.writeFileSync(path.join(ideaDir, "sshConfigs.xml"), malformedXml);
238
+ // Should throw an error for malformed XML
239
+ expect(() => {
240
+ generateIntellijConfigs(testData, tempDir);
241
+ }).toThrow();
242
+ });
243
+ });
244
+ describe("XML structure validation", () => {
245
+ test("should generate properly formatted XML with correct declarations", () => {
246
+ generateIntellijConfigs(testData, tempDir);
247
+ const ideaDir = path.join(tempDir, ".idea");
248
+ // Check SSH config
249
+ const sshContent = fs.readFileSync(path.join(ideaDir, "sshConfigs.xml"), "utf8");
250
+ expect(sshContent).toMatch(/^<\?xml version="1\.0" encoding="UTF-8"\?>/);
251
+ expect(sshContent).toContain('<project version="4">');
252
+ // Check web servers
253
+ const webContent = fs.readFileSync(path.join(ideaDir, "webServers.xml"), "utf8");
254
+ expect(webContent).toMatch(/^<\?xml version="1\.0" encoding="UTF-8"\?>/);
255
+ expect(webContent).toContain('<project version="4">');
256
+ // Check deployment
257
+ const deployContent = fs.readFileSync(path.join(ideaDir, "deployment.xml"), "utf8");
258
+ expect(deployContent).toMatch(/^<\?xml version="1\.0" encoding="UTF-8"\?>/);
259
+ expect(deployContent).toContain('<project version="4">');
260
+ });
261
+ });
262
+ });
@@ -0,0 +1,72 @@
1
+ export interface XmlDocument {
2
+ "?xml": {
3
+ "@_version": string;
4
+ "@_encoding": string;
5
+ };
6
+ project: XmlProject;
7
+ }
8
+ export interface XmlProject {
9
+ "@_version": string;
10
+ component: XmlComponent;
11
+ }
12
+ export interface XmlComponent {
13
+ "@_name": string;
14
+ configs?: XmlConfigs;
15
+ option?: XmlOption;
16
+ serverData?: XmlServerData;
17
+ "@_serverName"?: string;
18
+ "@_remoteFilesAllowedToDisappearOnAutoupload"?: string;
19
+ }
20
+ export interface XmlConfigs {
21
+ sshConfig?: XmlSshConfig | XmlSshConfig[];
22
+ }
23
+ export interface XmlSshConfig {
24
+ "@_authType": string;
25
+ "@_host": string;
26
+ "@_id": string;
27
+ "@_port": string;
28
+ "@_nameFormat": string;
29
+ "@_username": string;
30
+ "@_useOpenSSHConfig": string;
31
+ }
32
+ export interface XmlOption {
33
+ "@_name": string;
34
+ webServer?: XmlWebServer | XmlWebServer[];
35
+ }
36
+ export interface XmlWebServer {
37
+ "@_id": string;
38
+ "@_name": string;
39
+ fileTransfer: XmlFileTransfer;
40
+ }
41
+ export interface XmlFileTransfer {
42
+ "@_accessType": string;
43
+ "@_host": string;
44
+ "@_port": string;
45
+ "@_sshConfigId": string;
46
+ "@_sshConfig": string;
47
+ "@_authAgent": string;
48
+ advancedOptions: XmlAdvancedOptions;
49
+ }
50
+ export interface XmlAdvancedOptions {
51
+ advancedOptions: {
52
+ "@_dataProtectionLevel": string;
53
+ "@_keepAliveTimeout": string;
54
+ "@_passiveMode": string;
55
+ "@_shareSSLContext": string;
56
+ };
57
+ }
58
+ export interface XmlServerData {
59
+ paths?: XmlPath | XmlPath[];
60
+ }
61
+ export interface XmlPath {
62
+ "@_name": string;
63
+ serverdata: {
64
+ mappings: {
65
+ mapping: {
66
+ "@_deploy": string;
67
+ "@_local": string;
68
+ "@_web": string;
69
+ };
70
+ };
71
+ };
72
+ }
@@ -0,0 +1,2 @@
1
+ // XML Document Structure Types for IntelliJ IDEA configuration files
2
+ export {};
@@ -1,11 +1,15 @@
1
1
  import { OutputFlags } from "@oclif/core/interfaces";
2
2
  import { AvailableFlagName, RelevantFlagInput } from "./flags.js";
3
3
  import React from "react";
4
- import { MittwaldAPIV2Client } from "@mittwald/api-client";
4
+ import { MittwaldAPIV2, MittwaldAPIV2Client } from "@mittwald/api-client";
5
5
  import { Config } from "@oclif/core";
6
+ type AppVersion = MittwaldAPIV2.Components.Schemas.AppAppVersion;
7
+ type AppInstallation = MittwaldAPIV2.Components.Schemas.AppAppInstallation;
6
8
  type ImplicitDefaultFlag = "wait" | "wait-timeout" | "site-title";
7
9
  export interface AppInstallationResult {
8
- appInstallationId: string;
10
+ appInstallation: AppInstallation;
11
+ appVersion: AppVersion;
12
+ host?: string;
9
13
  }
10
14
  export declare class AppInstaller<TFlagName extends AvailableFlagName> {
11
15
  readonly appId: string;
@@ -6,6 +6,7 @@ import { normalizeToAppVersionUuid } from "./versions.js";
6
6
  import { triggerAppInstallation } from "./install.js";
7
7
  import { waitUntilAppStateHasNormalized } from "./wait.js";
8
8
  import { Success } from "../../../rendering/react/components/Success.js";
9
+ import AppUsageHints from "../../../rendering/react/components/AppInstallation/AppUsageHints.js";
9
10
  export class AppInstaller {
10
11
  appId;
11
12
  appName;
@@ -35,22 +36,28 @@ export class AppInstaller {
35
36
  const projectId = await withProjectId(apiClient, "flag", flags, args, config);
36
37
  await autofillFlags(apiClient, process, this.appSupportedFlags, flags, projectId, this.appName, this.defaultFlagValues);
37
38
  const appVersion = await normalizeToAppVersionUuid(apiClient, "version" in flags ? flags.version : "latest", process, this.appId);
38
- const appInstallationId = await triggerAppInstallation(apiClient, process, projectId, flags, appVersion);
39
+ const appInstallation = await triggerAppInstallation(apiClient, process, projectId, flags, appVersion);
39
40
  let successText;
40
41
  if (flags.wait) {
41
- await waitUntilAppStateHasNormalized(apiClient, process, appInstallationId, "waiting for app installation to be ready", flags["wait-timeout"]);
42
+ await waitUntilAppStateHasNormalized(apiClient, process, appInstallation.id, "waiting for app installation to be ready", flags["wait-timeout"]);
42
43
  successText = `Your ${this.appName} installation is now complete. Have fun! 🎉`;
43
44
  }
44
45
  else {
45
46
  successText = `Your ${this.appName} installation has started. Have fun when it's ready! 🎉`;
46
47
  }
47
- process.complete(_jsx(Success, { children: successText }));
48
- return { appInstallationId };
48
+ await process.complete(_jsx(Success, { children: successText }));
49
+ return {
50
+ appInstallation,
51
+ appVersion,
52
+ host: "host" in flags && typeof flags.host === "string"
53
+ ? flags.host
54
+ : undefined,
55
+ };
49
56
  }
50
57
  render(result, flags) {
51
58
  if (flags.quiet) {
52
- return result.appInstallationId;
59
+ return result.appInstallation.id;
53
60
  }
54
- return undefined;
61
+ return (_jsx(AppUsageHints, { appInstallation: result.appInstallation, appVersion: result.appVersion, appName: this.appName, appHost: result.host }));
55
62
  }
56
63
  }
@@ -1,8 +1,5 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
1
  import { getDefaultIngressForProject } from "../project/ingress.js";
3
- import { Value } from "../../../rendering/react/components/Value.js";
4
2
  import { getProjectShortIdFromUuid } from "../project/shortId.js";
5
- import { Text } from "ink";
6
3
  import { assertStatus } from "@mittwald/api-client-commons";
7
4
  import { projectFlags } from "../project/flags.js";
8
5
  import { processFlags, } from "../../../rendering/process/process_flags.js";
@@ -169,7 +166,7 @@ export async function autofillFlags(apiClient, process, necessaryFlags, flags, p
169
166
  if (necessaryFlags.includes("host") && !flags.host) {
170
167
  flags.host =
171
168
  "https://" + (await getDefaultIngressForProject(apiClient, projectId));
172
- process.addInfo(_jsxs(Text, { children: ["Using default Host ", _jsx(Value, { children: flags["host"] })] }));
169
+ process.addInfo(`Using default Host ${flags["host"]}`);
173
170
  }
174
171
  // Title
175
172
  if (necessaryFlags.includes("site-title") && !flags["site-title"]) {
@@ -186,12 +183,12 @@ export async function autofillFlags(apiClient, process, necessaryFlags, flags, p
186
183
  else {
187
184
  flags["admin-user"] = await getProjectShortIdFromUuid(apiClient, projectId);
188
185
  }
189
- process.addInfo(_jsxs(Text, { children: ["Using generated Admin User: ", _jsx(Value, { children: flags["admin-user"] })] }));
186
+ process.addInfo(`Using generated Admin User: ${flags["admin-user"]}`);
190
187
  }
191
188
  // Admin Pass
192
189
  if (necessaryFlags.includes("admin-pass") && !flags["admin-pass"]) {
193
190
  flags["admin-pass"] = generatePasswordWithSpecialChars();
194
- process.addInfo(_jsxs(Text, { children: ["Using generated random Admin Pass: ", _jsx(Value, { children: flags["admin-pass"] })] }));
191
+ process.addInfo(`Using generated random Admin Pass: ${flags["admin-pass"]}`);
195
192
  }
196
193
  // Admin Firstname
197
194
  if (necessaryFlags.includes("admin-firstname") && !flags["admin-firstname"]) {
@@ -201,7 +198,7 @@ export async function autofillFlags(apiClient, process, necessaryFlags, flags, p
201
198
  else {
202
199
  flags["admin-firstname"] = "Max";
203
200
  }
204
- process.addInfo(_jsxs(Text, { children: ["Using mStudio firstname as Admin firstname (", _jsx(Value, { children: flags["admin-firstname"] }), ")"] }));
201
+ process.addInfo(`Using mStudio firstname as Admin firstname (${flags["admin-firstname"]})`);
205
202
  }
206
203
  // Admin Lastname
207
204
  if (necessaryFlags.includes("admin-lastname") && !flags["admin-lastname"]) {
@@ -211,26 +208,26 @@ export async function autofillFlags(apiClient, process, necessaryFlags, flags, p
211
208
  else {
212
209
  flags["admin-lastname"] = "Mustermann";
213
210
  }
214
- process.addInfo(_jsxs(Text, { children: ["Using mStudio lastname as Admin lastname (", _jsx(Value, { children: flags["admin-lastname"] }), ")"] }));
211
+ process.addInfo(`Using mStudio lastname as Admin lastname (${flags["admin-lastname"]})`);
215
212
  }
216
213
  // Admin E-Mail
217
214
  if (necessaryFlags.includes("admin-email") && !flags["admin-email"]) {
218
215
  flags["admin-email"] = ownUser.data.email;
219
- process.addInfo(_jsxs(Text, { children: ["Using mStudio email as Admin email (", _jsx(Value, { children: flags["admin-email"] }), ")"] }));
216
+ process.addInfo(`Using mStudio email as Admin email (${flags["admin-email"]})`);
220
217
  }
221
218
  // Shop E-Mail
222
219
  if (necessaryFlags.includes("shop-email") && !flags["shop-email"]) {
223
220
  flags["shop-email"] = ownUser.data.email;
224
- process.addInfo(_jsxs(Text, { children: ["Using mStudio email as Shop email (", _jsx(Value, { children: flags["shop-email"] }), ")"] }));
221
+ process.addInfo(`Using mStudio email as Shop email (${flags["shop-email"]})`);
225
222
  }
226
223
  // Shop Language Code
227
224
  if (necessaryFlags.includes("shop-lang") && !flags["shop-lang"]) {
228
225
  flags["shop-lang"] = defaults["shop-lang"] ?? "de-DE";
229
- process.addInfo(_jsxs(Text, { children: ["Using default shop language '", flags["shop-lang"], "'."] }));
226
+ process.addInfo(`Using default shop language '${flags["shop-lang"]}'.`);
230
227
  }
231
228
  // Shop Currency
232
229
  if (necessaryFlags.includes("shop-currency") && !flags["shop-currency"]) {
233
230
  flags["shop-currency"] = "EUR";
234
- process.addInfo(_jsx(Text, { children: "Using default shop currency '\u20AC'." }));
231
+ process.addInfo("Using default shop currency ''.");
235
232
  }
236
233
  }
@@ -1,10 +1,11 @@
1
1
  import { MittwaldAPIV2, MittwaldAPIV2Client } from "@mittwald/api-client";
2
2
  import { ProcessRenderer } from "../../../rendering/process/process.js";
3
+ type AppAppInstallation = MittwaldAPIV2.Components.Schemas.AppAppInstallation;
3
4
  type AppAppVersion = MittwaldAPIV2.Components.Schemas.AppAppVersion;
4
5
  export declare function triggerAppInstallation(apiClient: MittwaldAPIV2Client, process: ProcessRenderer, projectId: string, flags: {
5
6
  "site-title": string;
6
7
  "document-root"?: string;
7
8
  } & {
8
9
  [k: string]: unknown;
9
- }, appVersion: AppAppVersion): Promise<string>;
10
+ }, appVersion: AppAppVersion): Promise<AppAppInstallation>;
10
11
  export {};
@@ -38,5 +38,7 @@ export async function triggerAppInstallation(apiClient, process, projectId, flag
38
38
  assertStatus(result, 204);
39
39
  });
40
40
  }
41
- return appInstallationId;
41
+ const result = await apiClient.app.getAppinstallation({ appInstallationId });
42
+ assertStatus(result, 200);
43
+ return result.data;
42
44
  }
@@ -1,8 +1,5 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
1
  import { assertStatus } from "@mittwald/api-client-commons";
3
2
  import { gt } from "semver";
4
- import { Value } from "../../../rendering/react/components/Value.js";
5
- import { Text } from "ink";
6
3
  import { getAppInstallationFromUuid, getAppNameFromUuid } from "./uuid.js";
7
4
  import { compare } from "semver";
8
5
  export async function normalizeToAppVersionUuid(apiClient, version, process, appUuid) {
@@ -16,7 +13,7 @@ export async function normalizeToAppVersionUuid(apiClient, version, process, app
16
13
  if (!appVersion) {
17
14
  throw new Error(`${await getAppNameFromUuid(apiClient, appUuid)} version ${version} does not seem to exist for the mStudio.`);
18
15
  }
19
- process.addInfo(_jsxs(Text, { children: ["installing version: ", _jsx(Value, { children: appVersion.externalVersion })] }));
16
+ process.addInfo(`installing version: ${appVersion.externalVersion}`);
20
17
  return appVersion;
21
18
  }
22
19
  // Get latest available Internal App Version for App UUID
@@ -1,8 +1,6 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
1
  import { waitUntil } from "../../wait.js";
3
- import { Text } from "ink";
4
2
  export async function waitUntilAppStateHasNormalized(apiClient, process, appInstallationId, label, timeout) {
5
- const stepWaiting = process.addStep(_jsx(Text, { children: label }));
3
+ const stepWaiting = process.addStep(label);
6
4
  await waitUntil(async () => {
7
5
  const installationResponse = await apiClient.app.getAppinstallation({
8
6
  appInstallationId,
@@ -1,9 +1,6 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
1
  import { generatePassword } from "../../util/password/generatePassword.js";
3
- import { Value } from "../../../rendering/react/components/Value.js";
4
- import { Text } from "ink";
5
2
  export async function generateRandomPassword(process) {
6
3
  const generated = await process.runStep("generating random password", async () => generatePassword(32));
7
- process.addInfo(_jsxs(Text, { children: [" ", "generated password: ", _jsx(Value, { children: generated }), " "] }));
4
+ process.addInfo(` generated password: ${generated} `);
8
5
  return generated;
9
6
  }
@@ -1,3 +1,5 @@
1
1
  import { MittwaldAPIV2Client } from "@mittwald/api-client";
2
2
  import { SSHConnectionData } from "./types.js";
3
- export declare function getSSHConnectionForAppInstallation(client: MittwaldAPIV2Client, appInstallationId: string, sshUser: string | undefined): Promise<SSHConnectionData>;
3
+ export declare function getSSHConnectionForAppInstallation(client: MittwaldAPIV2Client, appInstallationId: string, sshUser: string | undefined): Promise<SSHConnectionData & {
4
+ appShortId: string;
5
+ }>;
@@ -24,5 +24,6 @@ export async function getSSHConnectionForAppInstallation(client, appInstallation
24
24
  host,
25
25
  user,
26
26
  directory,
27
+ appShortId: appInstallationResponse.data.shortId,
27
28
  };
28
29
  }
@@ -10,7 +10,7 @@ export const ProcessStateIcon = ({ step }) => {
10
10
  return _jsx(Text, { children: "\u2753" });
11
11
  }
12
12
  else if (step.phase === "completed") {
13
- return _jsx(Text, { children: "\u2705" });
13
+ return _jsx(Text, { children: "\u2705 " });
14
14
  }
15
15
  else if (step.phase === "aborted") {
16
16
  return _jsx(Text, { children: "\u23E9\uFE0F " });
@@ -1,11 +1,11 @@
1
- import { ReactElement, ReactNode } from "react";
1
+ import { ReactElement } from "react";
2
2
  export type ProcessStepInfo = {
3
3
  type: "info";
4
- title: ReactNode;
4
+ title: string;
5
5
  };
6
6
  export type ProcessStepRunnable = {
7
7
  type: "step";
8
- title: ReactNode;
8
+ title: string;
9
9
  phase: "running" | "completed" | "failed" | "aborted";
10
10
  error?: unknown;
11
11
  progress?: string;
@@ -13,27 +13,27 @@ export type ProcessStepRunnable = {
13
13
  };
14
14
  export type ProcessStepConfirm = {
15
15
  type: "confirm";
16
- title: ReactNode;
16
+ title: string;
17
17
  confirmed: boolean | undefined;
18
18
  };
19
19
  export type ProcessStepInput = {
20
20
  type: "input";
21
- title: ReactNode;
21
+ title: string;
22
22
  mask?: boolean;
23
23
  value?: string;
24
24
  };
25
25
  export type ProcessStepSelect<TVal> = {
26
26
  type: "select";
27
- title: ReactNode;
27
+ title: string;
28
28
  options: {
29
29
  value: TVal;
30
- label: ReactNode;
30
+ label: string;
31
31
  }[];
32
32
  selected?: TVal;
33
33
  };
34
34
  export type ProcessStep = ProcessStepInfo | ProcessStepRunnable | ProcessStepConfirm | ProcessStepInput | ProcessStepSelect<unknown>;
35
35
  export type CleanupFunction = {
36
- title: ReactNode;
36
+ title: string;
37
37
  fn: () => Promise<unknown>;
38
38
  };
39
39
  export declare class RunnableHandler {
@@ -53,16 +53,16 @@ export declare class RunnableHandler {
53
53
  }
54
54
  export interface ProcessRenderer {
55
55
  start(): void;
56
- addStep(title: ReactNode): RunnableHandler;
57
- runStep<TRes>(title: ReactNode, fn: (() => Promise<TRes>) | Promise<TRes>): Promise<TRes>;
58
- addInfo(title: ReactNode): void;
59
- addConfirmation(question: ReactNode): Promise<boolean>;
60
- addInput(question: ReactNode, mask?: boolean): Promise<string>;
61
- addSelect<TVal>(question: ReactNode, options: {
56
+ addStep(title: string): RunnableHandler;
57
+ runStep<TRes>(title: string, fn: (() => Promise<TRes>) | Promise<TRes>): Promise<TRes>;
58
+ addInfo(title: string): void;
59
+ addConfirmation(question: string): Promise<boolean>;
60
+ addInput(question: string, mask?: boolean): Promise<string>;
61
+ addSelect<TVal>(question: string, options: {
62
62
  value: TVal;
63
- label: ReactNode;
63
+ label: string;
64
64
  }[]): Promise<TVal>;
65
- addCleanup(title: ReactNode, fn: () => Promise<unknown>): void;
65
+ addCleanup(title: string, fn: () => Promise<unknown>): void;
66
66
  complete(summary: ReactElement): Promise<void>;
67
67
  error(err: unknown): Promise<void>;
68
68
  }
@@ -1,3 +1,2 @@
1
1
  import { ProcessRenderer } from "./process.js";
2
- import { ReactNode } from "react";
3
- export declare function spawnInProcess(r: ProcessRenderer, title: ReactNode, cmd: string, args: string[]): Promise<void>;
2
+ export declare function spawnInProcess(r: ProcessRenderer, title: string, cmd: string, args: string[]): Promise<void>;