Skip to content

Commit e224afb

Browse files
committed
coderabbit comments
1 parent 22d67c9 commit e224afb

20 files changed

Lines changed: 88 additions & 29 deletions

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
"winston": "3.19.0",
6262
"winston-daily-rotate-file": "5.0.0",
6363
"ws": "8.18.2",
64-
"zod": "^4.3.6"
64+
"zod": "4.3.6"
6565
},
6666
"devDependencies": {
6767
"@actions/core": "3.0.0",

spec/Options/loaders/envLoader.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ describe('envLoader', () => {
106106

107107
it('coerces object env vars from JSON', () => {
108108
const schema = z.object({
109-
push: option(z.record(z.unknown()).optional(), {
109+
push: option(z.record(z.string(), z.unknown()).optional(), {
110110
env: 'PARSE_SERVER_PUSH',
111111
help: 'Push config',
112112
}),

spec/Options/loaders/fileLoader.spec.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,32 @@ describe('fileLoader', () => {
2929
}
3030
});
3131

32+
it('throws for empty apps array', () => {
33+
const fs = require('fs');
34+
const emptyAppsPath = path.join(configDir, 'CLIConfigEmptyApps.json');
35+
fs.writeFileSync(emptyAppsPath, JSON.stringify({ apps: [] }));
36+
try {
37+
expect(() => loadFromFile(emptyAppsPath)).toThrow(
38+
'The "apps" array must contain at least one configuration'
39+
);
40+
} finally {
41+
fs.unlinkSync(emptyAppsPath);
42+
}
43+
});
44+
45+
it('throws for apps that is not an array', () => {
46+
const fs = require('fs');
47+
const nonArrayAppsPath = path.join(configDir, 'CLIConfigNonArrayApps.json');
48+
fs.writeFileSync(nonArrayAppsPath, JSON.stringify({ apps: {} }));
49+
try {
50+
expect(() => loadFromFile(nonArrayAppsPath)).toThrow(
51+
'The "apps" property must be an array'
52+
);
53+
} finally {
54+
fs.unlinkSync(nonArrayAppsPath);
55+
}
56+
});
57+
3258
it('resolves relative paths', () => {
3359
const result = loadFromFile('./spec/configs/CLIConfig.json');
3460
expect(result.arg1).toBe('my_app');

src/Config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,13 @@ export class Config {
6363
})
6464
);
6565

66-
const cachedConfig = AppCache.get(this.appId);
66+
const cachedConfig = AppCache.get(this.applicationId);
6767
if (cachedConfig) {
6868
const updatedConfig = { ...cachedConfig };
6969
asyncKeys.forEach(key => {
7070
updatedConfig[key] = this[key];
7171
});
72-
AppCache.put(this.appId, updatedConfig);
72+
AppCache.put(this.applicationId, updatedConfig);
7373
}
7474
}
7575

src/Options/loaders/cliLoader.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export function extractCliOptions(
5353
* Checks if a Zod schema field is optional or has a default value.
5454
*/
5555
function isOptionalOrDefaulted(schema: z.ZodTypeAny): boolean {
56-
if (schema instanceof z.ZodOptional || schema instanceof z.ZodNullable) {
56+
if (schema instanceof z.ZodOptional) {
5757
return true;
5858
}
5959
if (schema instanceof z.ZodDefault) {

src/Options/loaders/envLoader.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,12 @@ function setNestedValue(obj: Record<string, unknown>, path: string[], value: unk
4242
let current: Record<string, unknown> = obj;
4343
for (let i = 0; i < path.length - 1; i++) {
4444
const key = path[i];
45-
if (current[key] === undefined || typeof current[key] !== 'object') {
45+
if (current[key] === undefined) {
4646
current[key] = {};
47+
} else if (current[key] === null || typeof current[key] !== 'object' || Array.isArray(current[key])) {
48+
throw new Error(
49+
`Environment variable path collision at "${path.slice(0, i + 1).join('.')}": expected an object but found ${Array.isArray(current[key]) ? 'an array' : typeof current[key]}`
50+
);
4751
}
4852
current = current[key] as Record<string, unknown>;
4953
}

src/Options/loaders/fileLoader.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ export function loadFromFile(filePath: string): Record<string, unknown> {
1818
let options: Record<string, unknown>;
1919

2020
if (jsonConfig.apps) {
21+
if (!Array.isArray(jsonConfig.apps)) {
22+
throw new Error('The "apps" property must be an array');
23+
}
24+
if (jsonConfig.apps.length === 0) {
25+
throw new Error('The "apps" array must contain at least one configuration');
26+
}
2127
if (jsonConfig.apps.length > 1) {
2228
throw new Error('Multiple apps are not supported');
2329
}

src/Options/loaders/mergeConfig.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ function deepMerge(
3232
source: Record<string, unknown>
3333
): void {
3434
for (const key of Object.keys(source)) {
35+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
36+
continue;
37+
}
38+
if (!Object.prototype.hasOwnProperty.call(source, key)) {
39+
continue;
40+
}
41+
3542
const sourceVal = source[key];
3643
const targetVal = target[key];
3744

@@ -44,6 +51,8 @@ function deepMerge(
4451
targetVal as Record<string, unknown>,
4552
sourceVal as Record<string, unknown>
4653
);
54+
} else if (isPlainObject(sourceVal)) {
55+
target[key] = structuredClone(sourceVal);
4756
} else {
4857
target[key] = sourceVal;
4958
}

src/Options/schemaUtils.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,14 +146,14 @@ export function coerceValue(value: string, fieldSchema: z.ZodTypeAny): unknown {
146146

147147
if (innerType instanceof z.ZodArray) {
148148
if (typeof value === 'string') {
149-
// Try JSON array first, fall back to CSV
150149
try {
151150
const parsed = JSON.parse(value);
152151
if (Array.isArray(parsed)) return parsed;
153152
} catch {
154-
// Not JSON, treat as CSV
153+
// Not valid JSON
155154
}
156-
return value.split(',');
155+
// Return original value so Zod validation fails upstream
156+
return value;
157157
}
158158
return value;
159159
}
@@ -171,7 +171,8 @@ export function coerceValue(value: string, fieldSchema: z.ZodTypeAny): unknown {
171171

172172
if (innerType instanceof z.ZodUnion) {
173173
// For union types, try each branch
174-
const options = (innerType as z.ZodUnion<[z.ZodTypeAny, ...z.ZodTypeAny[]]>)._def.options;
174+
const unionDef = innerType as z.ZodUnion<[z.ZodTypeAny, ...z.ZodTypeAny[]]>;
175+
const options = (unionDef as any)._zod?.def?.options ?? (unionDef as any)._def.options;
175176
for (const opt of options) {
176177
const inner = unwrapType(opt);
177178
// Skip function types for string coercion

src/Options/schemas/AccountLockoutOptions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export const AccountLockoutOptionsSchema = z
55
.object({
66
duration: option(
77
z.number()
8-
.min(1, 'Account lockout duration should be greater than 0')
8+
.gt(0, 'Account lockout duration should be greater than 0')
99
.max(99999, 'Account lockout duration should be less than 100000')
1010
.optional(),
1111
{

0 commit comments

Comments
 (0)