diff --git a/.bumpy/fix-port-integer.md b/.bumpy/fix-port-integer.md new file mode 100644 index 000000000..3f05b3529 --- /dev/null +++ b/.bumpy/fix-port-integer.md @@ -0,0 +1,5 @@ +--- +varlock: patch +--- + +Reject non-integer values for @type=port diff --git a/packages/varlock/src/env-graph/lib/data-types.ts b/packages/varlock/src/env-graph/lib/data-types.ts index ecf7fb680..8b21a53bb 100644 --- a/packages/varlock/src/env-graph/lib/data-types.ts +++ b/packages/varlock/src/env-graph/lib/data-types.ts @@ -545,9 +545,18 @@ const PortDataType = createEnvGraphDataType( if (rawVal.includes('.')) throw new CoercionError('Port number must be an integer'); if (rawVal.includes('e')) throw new CoercionError('Port number should be an integer, not in exponential notation'); } - return coerceToNumber(rawVal); + const numVal = coerceToNumber(rawVal); + // Unquoted schema values like 80.5 are already numbers after parse auto-coerce; + // the string '.' check above does not cover that path. + if (!Number.isInteger(numVal)) { + throw new CoercionError('Port number must be an integer'); + } + return numVal; }, validate(val) { + if (!Number.isInteger(val)) { + return new ValidationError('Port number must be an integer'); + } if (settings?.min !== undefined && val < settings?.min) { return new ValidationError(`Min value is ${settings?.min}`); } diff --git a/packages/varlock/src/env-graph/test/data-types.test.ts b/packages/varlock/src/env-graph/test/data-types.test.ts index 2aac08f93..f7644f03d 100644 --- a/packages/varlock/src/env-graph/test/data-types.test.ts +++ b/packages/varlock/src/env-graph/test/data-types.test.ts @@ -44,6 +44,33 @@ describe('number data type - Infinity coercion', () => { }); }); +describe('port data type', () => { + it('accepts integer ports', async () => { + const g = await loadAndResolve(outdent` + # @type=port + P=8080 + `); + expect(g.configSchema.P.isValid).toBe(true); + expect(g.configSchema.P.resolvedValue).toBe(8080); + }); + + it('rejects non-integer numeric ports from schema auto-coerce', async () => { + const g = await loadAndResolve(outdent` + # @type=port + P=80.5 + `); + expect(g.configSchema.P.isValid).toBe(false); + }); + + it('rejects non-integer string ports', async () => { + const g = await loadAndResolve(outdent` + # @type=port + P="80.5" + `); + expect(g.configSchema.P.isValid).toBe(false); + }); +}); + describe('url data type', () => { describe('prependHttps', () => { it('prepends https:// when missing', async () => {