Skip to content

Commit 211176d

Browse files
committed
resolve conflict for minio
1 parent b9dae0e commit 211176d

File tree

6 files changed

+152
-37
lines changed

6 files changed

+152
-37
lines changed

docs/docs/docs/getting-started/installation.md

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,31 @@ You will need to configure the API to work correctly.
4444
## Running the Setup Script
4545
To configure the .env file, run one of the following commands in your project’s root directory:
4646

47-
```
47+
```bash
4848
npm run setup
4949
```
5050
or
51-
```
51+
```bash
5252
pnpm tsx setup.ts
5353
```
5454

55-
The script will ask whether you’re in CI mode (CI=true) or non-CI (CI=false) mode. Choose:
55+
The script will ask whether you're in CI mode (CI=true) or non-CI (CI=false) mode. Choose:
56+
57+
1. CI=false for local/development environments:
58+
- Uses configuration from `.env.devcontainer`
59+
- Includes complete interactive setup with all configuration options
60+
- Sets up CloudBeaver for database management
61+
- Configures all Minio and PostgreSQL extended options
62+
- Best for developers working on the application locally
63+
64+
2. CI=true for testing or continuous integration pipelines:
65+
- Uses configuration from `.env.ci`
66+
- Streamlined setup with minimal configuration
67+
- Excludes CloudBeaver-related settings
68+
- Contains only essential database and storage options
69+
- Best for automated testing environments or CI/CD pipelines
5670

57-
1. CI=false for local/development environments.
58-
2. CI=true for testing or continuous integration pipelines.
59-
3. It will also ask whether you want to use recommended defaults. Answer “yes” to quickly accept safe defaults or “no” to provide custom inputs. Once the prompts finish, your .env file will be generated or updated.
71+
3. It will also ask whether you want to use recommended defaults. Answer "yes" to quickly accept safe defaults or "no" to provide custom inputs. Once the prompts finish, your .env file will be generated or updated.
6072

6173
## Prerequisities
6274

scripts/setup/setup.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ async function promptConfirm(
3737
name: string,
3838
message: string,
3939
defaultValue?: boolean,
40-
): Promise<string> {
40+
): Promise<boolean> {
4141
const { [name]: result } = await inquirer.prompt([
4242
{ type: "confirm", name, message, default: defaultValue },
4343
]);
@@ -445,10 +445,23 @@ export async function minioSetup(answers: SetupAnswers): Promise<SetupAnswers> {
445445
validatePort,
446446
);
447447

448-
if (answers.MINIO_API_MAPPED_PORT === answers.MINIO_CONSOLE_MAPPED_PORT) {
449-
throw new Error(
450-
"Port conflict detected: MinIO API and Console ports must be different",
451-
);
448+
let portConflict = true;
449+
while (portConflict && answers.CI === "false") {
450+
if (
451+
answers.MINIO_API_MAPPED_PORT === answers.MINIO_CONSOLE_MAPPED_PORT
452+
) {
453+
console.warn(
454+
"⚠️ Port conflict detected: MinIO API and Console ports must be different.",
455+
);
456+
answers.MINIO_CONSOLE_MAPPED_PORT = await promptInput(
457+
"MINIO_CONSOLE_MAPPED_PORT",
458+
"Please enter a different Minio console mapped port:",
459+
String(Number(answers.MINIO_API_MAPPED_PORT) + 1), // Suggest next available port
460+
validatePort,
461+
);
462+
} else {
463+
portConflict = false;
464+
}
452465
}
453466
}
454467

scripts/setup/updateEnvVariable.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ import fs from "node:fs";
55
* @param config - An object containing key-value pairs where the keys are the environment variable names and
66
* the values are the new values for those variables.
77
*/
8+
9+
function quoteIfNeeded(value: string | number): string {
10+
const stringValue = String(value);
11+
if (
12+
typeof value === "string" &&
13+
(stringValue.includes(" ") ||
14+
stringValue.includes('"') ||
15+
stringValue.includes("'"))
16+
) {
17+
return `"${stringValue.replace(/"/g, '\\"')}"`;
18+
}
19+
return String(value);
20+
}
21+
822
export function updateEnvVariable(config: {
923
[key: string]: string | number;
1024
}): void {
@@ -23,16 +37,19 @@ export function updateEnvVariable(config: {
2337
let updatedContent: string = existingContent;
2438

2539
for (const key in config) {
26-
const value = config[key];
40+
const value = config[key] as string | number;
2741
const regex = new RegExp(
2842
`^${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}=.*`,
2943
"gm",
3044
);
3145

3246
if (regex.test(updatedContent)) {
33-
updatedContent = updatedContent.replace(regex, `${key}=${value}`);
47+
updatedContent = updatedContent.replace(
48+
regex,
49+
`${key}=${quoteIfNeeded(value)}`,
50+
);
3451
} else {
35-
updatedContent += `\n${key}=${value}`;
52+
updatedContent += `\n${key}=${quoteIfNeeded(value)}`;
3653
}
3754

3855
process.env[key] = String(value);

test/setup/cloudbeaverSetup.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ describe("CloudBeaver Validation", () => {
120120
expect(validateCloudBeaverURL("ftp://127.0.0.1")).toBe(
121121
"URL must use HTTP or HTTPS protocol",
122122
);
123+
expect(validateCloudBeaverURL("http://127.0.0.1:99999")).toBe(
124+
"Invalid URL format",
125+
);
123126
expect(validateCloudBeaverURL("http://127.0.0.1:8978")).toBe(true);
124127
expect(validateCloudBeaverURL("https://localhost:8978")).toBe(true);
125128
});

test/setup/minioSetup.test.ts

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -79,34 +79,38 @@ describe("Setup -> minioSetup", () => {
7979
expect(process.env[key]).toBe(value);
8080
}
8181
});
82-
it("should detect port conflicts between API and Console ports", async () => {
83-
const inputAnswers = { CI: "false" };
84-
85-
const mockResponses = [
86-
{ MINIO_BROWSER: "on" },
87-
{ MINIO_API_MAPPED_HOST_IP: "127.0.0.1" },
88-
{ MINIO_API_MAPPED_PORT: "9000" },
89-
{ MINIO_CONSOLE_MAPPED_HOST_IP: "127.0.0.1" },
90-
{ MINIO_CONSOLE_MAPPED_PORT: "9000" },
91-
];
82+
it("should handle port conflict between API and Console ports by prompting for a new port", async () => {
83+
process.env.CI = "false";
9284

9385
const promptMock = vi.spyOn(inquirer, "prompt");
94-
for (const response of mockResponses) {
95-
promptMock.mockResolvedValueOnce(response);
96-
}
97-
98-
const processExitSpy = vi
99-
.spyOn(process, "exit")
100-
.mockImplementation((code) => {
101-
throw new Error(`process.exit called with ${code}`);
102-
});
10386

104-
await expect(minioSetup(inputAnswers)).rejects.toThrow(
105-
/process\.exit called with 1/,
87+
promptMock
88+
.mockResolvedValueOnce({ MINIO_BROWSER: "on" })
89+
.mockResolvedValueOnce({ MINIO_API_MAPPED_HOST_IP: "127.0.0.1" })
90+
.mockResolvedValueOnce({ MINIO_API_MAPPED_PORT: "9000" })
91+
.mockResolvedValueOnce({ MINIO_CONSOLE_MAPPED_HOST_IP: "127.0.0.1" })
92+
.mockResolvedValueOnce({ MINIO_CONSOLE_MAPPED_PORT: "9000" }) // Conflict: same as API port
93+
// Response for the re-prompt after conflict detection
94+
.mockResolvedValueOnce({ MINIO_CONSOLE_MAPPED_PORT: "9001" })
95+
.mockResolvedValueOnce({ MINIO_ROOT_USER: "talawa" })
96+
.mockResolvedValueOnce({ MINIO_ROOT_PASSWORD: "password" });
97+
98+
const consoleWarnSpy = vi.spyOn(console, "warn");
99+
100+
const answers: Record<string, string> = { CI: "false" };
101+
await minioSetup(answers);
102+
103+
// Verify port conflict was resolved
104+
expect(answers.MINIO_API_MAPPED_PORT).toBe("9000");
105+
expect(answers.MINIO_CONSOLE_MAPPED_PORT).toBe("9001");
106+
107+
// Verify warning message was shown
108+
expect(consoleWarnSpy).toHaveBeenCalledWith(
109+
"⚠️ Port conflict detected: MinIO API and Console ports must be different.",
106110
);
107111

108-
expect(processExitSpy).toHaveBeenCalledWith(1);
109-
processExitSpy.mockRestore();
112+
// Verify inquirer was called the correct number of times (including the extra prompt)
113+
expect(promptMock).toHaveBeenCalledTimes(8);
110114
});
111115
it("should handle prompt errors correctly", async () => {
112116
const processExitSpy = vi

test/setup/updateEnvVariable.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,70 @@ describe("updateEnvVariable", () => {
7777
);
7878
expect(process.env.NEW_VAR).toBe("new_value");
7979
});
80+
81+
it("should properly quote values with spaces and special characters", () => {
82+
vi.spyOn(fs, "readFileSync").mockReturnValue("EXISTING_VAR=old_value");
83+
const writeSpy = vi.spyOn(fs, "writeFileSync").mockImplementation(() => {});
84+
85+
updateEnvVariable({
86+
SPACE_VAR: "value with spaces",
87+
QUOTE_VAR: 'value with "quotes"',
88+
});
89+
90+
expect(writeSpy).toHaveBeenCalledWith(
91+
envFileName,
92+
expect.stringContaining('SPACE_VAR="value with spaces"'),
93+
"utf8",
94+
);
95+
expect(writeSpy).toHaveBeenCalledWith(
96+
envFileName,
97+
expect.stringContaining('QUOTE_VAR="value with \\"quotes\\""'),
98+
"utf8",
99+
);
100+
});
101+
102+
describe("quoteIfNeeded", () => {
103+
let quoteIfNeeded: (value: string | number) => string;
104+
105+
beforeEach(() => {
106+
quoteIfNeeded = (value: string | number): string => {
107+
const stringValue = String(value);
108+
if (
109+
typeof value === "string" &&
110+
(stringValue.includes(" ") ||
111+
stringValue.includes('"') ||
112+
stringValue.includes("'"))
113+
) {
114+
return `"${stringValue.replace(/"/g, '\\"')}"`;
115+
}
116+
return String(value);
117+
};
118+
});
119+
120+
it("should not quote simple values", () => {
121+
expect(quoteIfNeeded("simple")).toBe("simple");
122+
expect(quoteIfNeeded("no_special_chars")).toBe("no_special_chars");
123+
});
124+
125+
it("should quote values with spaces", () => {
126+
expect(quoteIfNeeded("hello world")).toBe('"hello world"');
127+
expect(quoteIfNeeded("spaces in text")).toBe('"spaces in text"');
128+
});
129+
130+
it("should escape and quote values with double quotes", () => {
131+
expect(quoteIfNeeded('text with "quotes"')).toBe(
132+
'"text with \\"quotes\\""',
133+
);
134+
expect(quoteIfNeeded('just"quote')).toBe('"just\\"quote"');
135+
});
136+
137+
it("should quote values with single quotes", () => {
138+
expect(quoteIfNeeded("it's a value")).toBe('"it\'s a value"');
139+
});
140+
141+
it("should convert numeric values to strings without quotes", () => {
142+
expect(quoteIfNeeded(123)).toBe("123");
143+
expect(quoteIfNeeded(-45.67)).toBe("-45.67");
144+
});
145+
});
80146
});

0 commit comments

Comments
 (0)