@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
@@ -0,0 +1,167 @@
1
+ import { describe, expect, it, jest, beforeEach } from "@jest/globals";
2
+ const mockReadFile = jest.fn();
3
+ jest.unstable_mockModule("fs/promises", () => ({
4
+ readFile: mockReadFile,
5
+ }));
6
+ const { enrichStackDefinition } = await import("./enrich.js");
7
+ describe("enrichStackDefinition", () => {
8
+ beforeEach(() => {
9
+ jest.clearAllMocks();
10
+ });
11
+ it("should handle service without env_file", async () => {
12
+ const input = {
13
+ services: {
14
+ nginx: {
15
+ image: "nginx:latest",
16
+ ports: ["80:80"],
17
+ },
18
+ },
19
+ };
20
+ const result = await enrichStackDefinition(input);
21
+ expect(result).toEqual(input);
22
+ expect(mockReadFile).not.toHaveBeenCalled();
23
+ });
24
+ it("should handle single env_file as string", async () => {
25
+ mockReadFile.mockResolvedValueOnce("FOO=bar\nBAZ=qux");
26
+ const input = {
27
+ services: {
28
+ nginx: {
29
+ image: "nginx:latest",
30
+ env_file: ".env",
31
+ },
32
+ },
33
+ };
34
+ const result = await enrichStackDefinition(input);
35
+ expect(mockReadFile).toHaveBeenCalledWith(".env", "utf-8");
36
+ expect(result).toEqual({
37
+ services: {
38
+ nginx: {
39
+ image: "nginx:latest",
40
+ envs: {
41
+ FOO: "bar",
42
+ BAZ: "qux",
43
+ },
44
+ },
45
+ },
46
+ });
47
+ });
48
+ it("should handle env_file as array", async () => {
49
+ mockReadFile
50
+ .mockResolvedValueOnce("FOO=bar\nBAZ=qux")
51
+ .mockResolvedValueOnce("FOO=overridden\nNEW=value");
52
+ const input = {
53
+ services: {
54
+ nginx: {
55
+ image: "nginx:latest",
56
+ env_file: [".env", ".env.local"],
57
+ },
58
+ },
59
+ };
60
+ const result = await enrichStackDefinition(input);
61
+ expect(mockReadFile).toHaveBeenCalledWith(".env", "utf-8");
62
+ expect(mockReadFile).toHaveBeenCalledWith(".env.local", "utf-8");
63
+ expect(result).toEqual({
64
+ services: {
65
+ nginx: {
66
+ image: "nginx:latest",
67
+ envs: {
68
+ FOO: "overridden", // Later file should override
69
+ BAZ: "qux",
70
+ NEW: "value",
71
+ },
72
+ },
73
+ },
74
+ });
75
+ });
76
+ it("should merge env_file variables with existing envs", async () => {
77
+ mockReadFile.mockResolvedValueOnce("FOO=from_file\nFILE_VAR=file_value");
78
+ const input = {
79
+ services: {
80
+ nginx: {
81
+ image: "nginx:latest",
82
+ env_file: ".env",
83
+ envs: {
84
+ FOO: "existing", // Should override env_file
85
+ EXISTING_VAR: "existing_value",
86
+ },
87
+ },
88
+ },
89
+ };
90
+ const result = await enrichStackDefinition(input);
91
+ expect(result).toEqual({
92
+ services: {
93
+ nginx: {
94
+ image: "nginx:latest",
95
+ envs: {
96
+ FOO: "existing", // Existing envs take precedence
97
+ FILE_VAR: "file_value",
98
+ EXISTING_VAR: "existing_value",
99
+ },
100
+ },
101
+ },
102
+ });
103
+ });
104
+ it("should handle multiple services with different env_file configurations", async () => {
105
+ mockReadFile
106
+ .mockResolvedValueOnce("NGINX_VAR=nginx_value")
107
+ .mockResolvedValueOnce("APP_VAR=app_value1")
108
+ .mockResolvedValueOnce("APP_VAR=app_value2\nOTHER=other");
109
+ const input = {
110
+ services: {
111
+ nginx: {
112
+ image: "nginx:latest",
113
+ env_file: ".env.nginx",
114
+ },
115
+ app: {
116
+ image: "app:latest",
117
+ env_file: [".env.app", ".env.app.local"],
118
+ },
119
+ db: {
120
+ image: "postgres:latest",
121
+ // No env_file
122
+ },
123
+ },
124
+ };
125
+ const result = await enrichStackDefinition(input);
126
+ expect(result).toEqual({
127
+ services: {
128
+ nginx: {
129
+ image: "nginx:latest",
130
+ envs: {
131
+ NGINX_VAR: "nginx_value",
132
+ },
133
+ },
134
+ app: {
135
+ image: "app:latest",
136
+ envs: {
137
+ APP_VAR: "app_value2", // Later file overrides
138
+ OTHER: "other",
139
+ },
140
+ },
141
+ db: {
142
+ image: "postgres:latest",
143
+ },
144
+ },
145
+ });
146
+ });
147
+ it("should handle empty env files", async () => {
148
+ mockReadFile.mockResolvedValueOnce("");
149
+ const input = {
150
+ services: {
151
+ nginx: {
152
+ image: "nginx:latest",
153
+ env_file: ".env",
154
+ },
155
+ },
156
+ };
157
+ const result = await enrichStackDefinition(input);
158
+ expect(result).toEqual({
159
+ services: {
160
+ nginx: {
161
+ image: "nginx:latest",
162
+ envs: {},
163
+ },
164
+ },
165
+ });
166
+ });
167
+ });
@@ -3,7 +3,7 @@ type ContainerServiceDeclareRequest = MittwaldAPIV2.Components.Schemas.Container
3
3
  export type ContainerServiceInput = ContainerServiceDeclareRequest & {
4
4
  command?: string[] | string;
5
5
  entrypoint?: string[] | string;
6
- env_file?: string;
6
+ env_file?: string | string[];
7
7
  environment?: {
8
8
  [k: string]: string;
9
9
  };
@@ -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>;
@@ -1,4 +1,4 @@
1
- import React, { ReactElement, ReactNode } from "react";
1
+ import { ReactElement } from "react";
2
2
  import { ProcessRenderer, RunnableHandler } from "./process.js";
3
3
  export declare class FancyProcessRenderer implements ProcessRenderer {
4
4
  private readonly title;
@@ -7,18 +7,18 @@ export declare class FancyProcessRenderer implements ProcessRenderer {
7
7
  private cleanupFns;
8
8
  constructor(title: string);
9
9
  start(): void;
10
- addStep(title: ReactNode): RunnableHandler;
11
- runStep<TRes>(title: ReactNode, fn: (() => Promise<TRes>) | Promise<TRes>): Promise<TRes>;
12
- addInfo(title: ReactNode): void;
13
- addInput(question: React.ReactElement, mask?: boolean): Promise<string>;
14
- addSelect<TVal>(question: React.ReactNode, options: {
10
+ addStep(title: string): RunnableHandler;
11
+ runStep<TRes>(title: string, fn: (() => Promise<TRes>) | Promise<TRes>): Promise<TRes>;
12
+ addInfo(title: string): void;
13
+ addInput(question: string, mask?: boolean): Promise<string>;
14
+ addSelect<TVal>(question: string, options: {
15
15
  value: TVal;
16
- label: React.ReactNode;
16
+ label: string;
17
17
  }[]): Promise<TVal>;
18
- addConfirmation(question: ReactElement): Promise<boolean>;
18
+ addConfirmation(question: string): Promise<boolean>;
19
19
  complete(summary: ReactElement): Promise<void>;
20
20
  error(err: unknown): Promise<void>;
21
21
  private renderStart;
22
- addCleanup(title: ReactNode, fn: () => Promise<unknown>): void;
22
+ addCleanup(title: string, fn: () => Promise<unknown>): void;
23
23
  private cleanup;
24
24
  }
@@ -1,6 +1,7 @@
1
1
  import { FancyProcessRenderer } from "./process_fancy.js";
2
2
  import { Flags } from "@oclif/core";
3
3
  import { SilentProcessRenderer } from "./process_quiet.js";
4
+ import { SimpleProcessRenderer } from "./process_simple.js";
4
5
  export const processFlags = {
5
6
  quiet: Flags.boolean({
6
7
  char: "q",
@@ -25,5 +26,8 @@ export const makeProcessRenderer = (flags, title) => {
25
26
  if (flags.quiet) {
26
27
  return new SilentProcessRenderer();
27
28
  }
29
+ if (!process.stdout.isTTY) {
30
+ return new SimpleProcessRenderer(title, process.stdout);
31
+ }
28
32
  return new FancyProcessRenderer(title);
29
33
  };
@@ -1,16 +1,15 @@
1
- import { ReactNode } from "react";
2
1
  import { ProcessRenderer, RunnableHandler } from "./process.js";
3
2
  export declare class SilentProcessRenderer implements ProcessRenderer {
4
3
  private cleanupFns;
5
4
  start(): void;
6
- addStep(title: ReactNode): RunnableHandler;
7
- runStep<TRes>(unusedTitle: ReactNode, fn: (() => Promise<TRes>) | Promise<TRes>): Promise<TRes>;
5
+ addStep(title: string): RunnableHandler;
6
+ runStep<TRes>(unusedTitle: string, fn: (() => Promise<TRes>) | Promise<TRes>): Promise<TRes>;
8
7
  addInfo(): void;
9
8
  complete(): Promise<void>;
10
9
  error(err: unknown): Promise<void>;
11
10
  addConfirmation(): Promise<boolean>;
12
11
  addInput(): Promise<string>;
13
12
  addSelect<TVal>(): Promise<TVal>;
14
- addCleanup(_: ReactNode, fn: () => Promise<unknown>): void;
13
+ addCleanup(_: string, fn: () => Promise<unknown>): void;
15
14
  private cleanup;
16
15
  }
@@ -0,0 +1,26 @@
1
+ import { ReactElement } from "react";
2
+ import { ProcessRenderer, RunnableHandler } from "./process.js";
3
+ import { Writable } from "stream";
4
+ export declare class SimpleProcessRenderer implements ProcessRenderer {
5
+ private readonly title;
6
+ private readonly output;
7
+ private started;
8
+ private cleanupFns;
9
+ private stepCounter;
10
+ constructor(title: string, output: Writable);
11
+ start(): void;
12
+ addStep(title: string): RunnableHandler;
13
+ runStep<TRes>(title: string, fn: (() => Promise<TRes>) | Promise<TRes>): Promise<TRes>;
14
+ addInfo(title: string): void;
15
+ addConfirmation(question: string): Promise<boolean>;
16
+ addInput(question: string, mask?: boolean): Promise<string>;
17
+ addSelect<TVal>(question: string, options: {
18
+ value: TVal;
19
+ label: string;
20
+ }[]): Promise<TVal>;
21
+ addCleanup(title: string, fn: () => Promise<unknown>): void;
22
+ complete(summary: ReactElement): Promise<void>;
23
+ error(err: unknown): Promise<void>;
24
+ private cleanup;
25
+ private renderElementToText;
26
+ }
@@ -0,0 +1,143 @@
1
+ import { RunnableHandler, } from "./process.js";
2
+ export class SimpleProcessRenderer {
3
+ title;
4
+ output;
5
+ started = false;
6
+ cleanupFns = [];
7
+ stepCounter = 0;
8
+ constructor(title, output) {
9
+ this.title = title;
10
+ this.output = output;
11
+ }
12
+ start() {
13
+ if (this.started) {
14
+ return;
15
+ }
16
+ this.started = true;
17
+ this.output.write(`Starting: ${this.title}\n\n`);
18
+ }
19
+ addStep(title) {
20
+ this.start();
21
+ this.stepCounter++;
22
+ const titleText = title;
23
+ this.output.write(`Step ${this.stepCounter}: ${titleText}... `);
24
+ const state = {
25
+ type: "step",
26
+ title,
27
+ phase: "running",
28
+ };
29
+ return new RunnableHandler(state, () => {
30
+ if (state.phase === "completed") {
31
+ this.output.write("completed\n");
32
+ }
33
+ else if (state.phase === "failed") {
34
+ this.output.write("FAILED\n");
35
+ if (state.error) {
36
+ this.output.write(` Error: ${state.error}\n`);
37
+ }
38
+ }
39
+ else if (state.phase === "aborted") {
40
+ this.output.write("aborted\n");
41
+ }
42
+ if (state.progress) {
43
+ this.output.write(` Progress: ${state.progress}\n`);
44
+ }
45
+ if (state.output) {
46
+ const lines = state.output
47
+ .split("\n")
48
+ .filter((line) => line.trim());
49
+ this.output.write("got output:\n");
50
+ for (const line of lines) {
51
+ this.output.write(` ${line}\n`);
52
+ }
53
+ }
54
+ });
55
+ }
56
+ async runStep(title, fn) {
57
+ const step = this.addStep(title);
58
+ try {
59
+ const promise = typeof fn === "function" ? fn() : fn;
60
+ const result = await promise;
61
+ step.complete();
62
+ return result;
63
+ }
64
+ catch (err) {
65
+ step.error(err);
66
+ throw err;
67
+ }
68
+ }
69
+ addInfo(title) {
70
+ this.start();
71
+ const titleText = title;
72
+ this.output.write(`Info: ${titleText}\n`);
73
+ }
74
+ async addConfirmation(question) {
75
+ this.start();
76
+ const questionText = question;
77
+ this.output.write(`Confirm: ${questionText}; automatically confirmed\n`);
78
+ return true;
79
+ }
80
+ async addInput(question, mask) {
81
+ this.start();
82
+ const questionText = question;
83
+ const maskText = mask ? " (masked)" : "";
84
+ this.output.write(`Input: ${questionText}${maskText}; no input available\n`);
85
+ // For non-interactive use, throw an error
86
+ throw new Error("Interactive input not available in simple process renderer");
87
+ }
88
+ async addSelect(question, options) {
89
+ this.start();
90
+ const questionText = question;
91
+ this.output.write(`Selection: ${questionText}\n`);
92
+ this.output.write("Available options:\n");
93
+ options.forEach((option, index) => {
94
+ const labelText = option.label;
95
+ this.output.write(` ${index + 1}. ${labelText}\n`);
96
+ });
97
+ throw new Error("Interactive selection not available in simple process renderer");
98
+ }
99
+ addCleanup(title, fn) {
100
+ this.cleanupFns.push(fn);
101
+ }
102
+ async complete(summary) {
103
+ await this.cleanup();
104
+ const summaryText = this.renderElementToText(summary);
105
+ this.output.write("Completed: Process completed successfully\n\n");
106
+ this.output.write(`Summary: ${summaryText}\n`);
107
+ }
108
+ async error(err) {
109
+ await this.cleanup();
110
+ this.output.write(`ERROR: Process failed: ${err?.toString()}\n`);
111
+ }
112
+ async cleanup() {
113
+ if (this.cleanupFns.length === 0) {
114
+ return;
115
+ }
116
+ this.output.write("Cleanup: Running cleanup tasks... ");
117
+ for (let i = 0; i < this.cleanupFns.length; i++) {
118
+ try {
119
+ await this.cleanupFns[i]();
120
+ }
121
+ catch (err) {
122
+ this.output.write(`task ${i + 1} failed: ${err}`);
123
+ }
124
+ }
125
+ this.output.write("completed\n");
126
+ }
127
+ renderElementToText(node) {
128
+ if (node === null || node === undefined) {
129
+ return "";
130
+ }
131
+ // Check if it's a React element with props.children
132
+ if (typeof node === "object" &&
133
+ "props" in node &&
134
+ node.props &&
135
+ typeof node.props === "object") {
136
+ const props = node.props;
137
+ if (props.children && typeof props.children === "string") {
138
+ return props.children;
139
+ }
140
+ }
141
+ return "[Complex content]";
142
+ }
143
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,149 @@
1
+ import { SimpleProcessRenderer } from "./process_simple.js";
2
+ import { describe, expect, it, jest, beforeEach, } from "@jest/globals";
3
+ import React from "react";
4
+ import { Writable } from "stream";
5
+ describe("SimpleProcessRenderer", () => {
6
+ let output;
7
+ let testStream;
8
+ beforeEach(() => {
9
+ output = "";
10
+ testStream = new Writable({
11
+ write(chunk, encoding, callback) {
12
+ output += chunk.toString();
13
+ if (callback)
14
+ callback();
15
+ return true;
16
+ },
17
+ });
18
+ });
19
+ it("should start with a title", () => {
20
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
21
+ renderer.start();
22
+ expect(output).toContain("Starting: Test Process\n");
23
+ });
24
+ it("should display info messages", () => {
25
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
26
+ renderer.addInfo("This is an info message");
27
+ expect(output).toContain("Info: This is an info message\n");
28
+ });
29
+ it("should handle string step titles", () => {
30
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
31
+ const handler = renderer.addStep("Processing data");
32
+ expect(output).toContain("Step 1: Processing data... ");
33
+ handler.complete();
34
+ expect(output).toContain("completed\n");
35
+ });
36
+ it("should handle step failures", () => {
37
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
38
+ const handler = renderer.addStep("Failing step");
39
+ handler.error("Something went wrong");
40
+ expect(output).toContain("FAILED\n");
41
+ expect(output).toContain(" Error: Something went wrong\n");
42
+ });
43
+ it("should handle step progress", () => {
44
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
45
+ const handler = renderer.addStep("Long running task");
46
+ handler.progress("50% complete");
47
+ expect(output).toContain(" Progress: 50% complete\n");
48
+ });
49
+ it("should handle step output", () => {
50
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
51
+ const handler = renderer.addStep("Task with output");
52
+ handler.appendOutput("Line 1\nLine 2\n");
53
+ expect(output).toContain("got output:\n");
54
+ expect(output).toContain(" Line 1\n");
55
+ expect(output).toContain(" Line 2\n");
56
+ });
57
+ it("should run steps successfully", async () => {
58
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
59
+ const result = await renderer.runStep("Test step", async () => {
60
+ return "success";
61
+ });
62
+ expect(result).toBe("success");
63
+ expect(output).toContain("Step 1: Test step... ");
64
+ expect(output).toContain("completed\n");
65
+ });
66
+ it("should handle runStep failures", async () => {
67
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
68
+ await expect(renderer.runStep("Failing step", async () => {
69
+ throw new Error("Test error");
70
+ })).rejects.toThrow("Test error");
71
+ expect(output).toContain("FAILED\n");
72
+ });
73
+ it("should auto-confirm confirmations", async () => {
74
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
75
+ const result = await renderer.addConfirmation("Do you want to continue?");
76
+ expect(result).toBe(true);
77
+ expect(output).toContain("Confirm: Do you want to continue?; automatically confirmed\n");
78
+ });
79
+ it("should throw error for input requests", async () => {
80
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
81
+ await expect(renderer.addInput("Enter your name")).rejects.toThrow("Interactive input not available in simple process renderer");
82
+ });
83
+ it("should throw error for select requests", async () => {
84
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
85
+ await expect(renderer.addSelect("Choose option", [
86
+ { value: "a", label: "Option A" },
87
+ { value: "b", label: "Option B" },
88
+ ])).rejects.toThrow("Interactive selection not available in simple process renderer");
89
+ });
90
+ it("should handle string titles", () => {
91
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
92
+ renderer.addStep("React step");
93
+ expect(output).toContain("Step 1: React step... ");
94
+ });
95
+ it("should complete with summary", async () => {
96
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
97
+ const summary = React.createElement("div", null, "Process completed!");
98
+ await renderer.complete(summary);
99
+ expect(output).toContain("Completed: Process completed successfully\n\n");
100
+ expect(output).toContain("Summary: Process completed!\n");
101
+ });
102
+ it("should handle errors", async () => {
103
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
104
+ await renderer.error("Something went wrong");
105
+ expect(output).toContain("ERROR: Process failed: Something went wrong\n");
106
+ });
107
+ it("should handle cleanup tasks", async () => {
108
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
109
+ const cleanupFn = jest.fn(async () => { });
110
+ renderer.addCleanup("Cleanup task", cleanupFn);
111
+ await renderer.complete(React.createElement("div", null, "Done"));
112
+ expect(cleanupFn).toHaveBeenCalled();
113
+ expect(output).toContain("Cleanup: Running cleanup tasks... ");
114
+ });
115
+ it("should render string titles", () => {
116
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
117
+ renderer.addStep("Hello from Text component");
118
+ expect(output).toContain("Step 1: Hello from Text component... ");
119
+ });
120
+ it("should render nested string content", () => {
121
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
122
+ renderer.addStep("Nested content");
123
+ expect(output).toContain("Step 1: Nested content... ");
124
+ });
125
+ it("should handle string content", () => {
126
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
127
+ renderer.addStep("First Second Third");
128
+ expect(output).toContain("Step 1: First Second Third... ");
129
+ });
130
+ it("should handle complex string content", () => {
131
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
132
+ renderer.addStep("String content 42 and React element");
133
+ expect(output).toContain("Step 1: String content 42 and React element... ");
134
+ });
135
+ it("should handle empty string content gracefully", () => {
136
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
137
+ renderer.addStep("");
138
+ expect(output).toContain("Step 1: ... ");
139
+ });
140
+ it("should handle different string content", () => {
141
+ const renderer = new SimpleProcessRenderer("Test Process", testStream);
142
+ renderer.addStep("true");
143
+ renderer.addStep("false");
144
+ renderer.addStep("123");
145
+ expect(output).toContain("Step 1: true... ");
146
+ expect(output).toContain("Step 2: false... ");
147
+ expect(output).toContain("Step 3: 123... ");
148
+ });
149
+ });
@@ -0,0 +1,15 @@
1
+ import { FC } from "react";
2
+ import type { MittwaldAPIV2 } from "@mittwald/api-client";
3
+ type AppInstallation = MittwaldAPIV2.Components.Schemas.AppAppInstallation;
4
+ interface AppBackendAccessHintsProps {
5
+ appInstallation: AppInstallation;
6
+ backendPathTemplate: string;
7
+ appName: string;
8
+ appHost?: string;
9
+ }
10
+ /**
11
+ * AppBackendAccessHints displays backend/admin access information when a
12
+ * backendPathTemplate is available in the app version.
13
+ */
14
+ export declare const AppBackendAccessHints: FC<AppBackendAccessHintsProps>;
15
+ export {};