diff --git a/lib/modules/manager/devbox/extract.spec.ts b/lib/modules/manager/devbox/extract.spec.ts index e97a2f470c2535..ba295b3a5ca43c 100644 --- a/lib/modules/manager/devbox/extract.spec.ts +++ b/lib/modules/manager/devbox/extract.spec.ts @@ -21,10 +21,10 @@ describe('modules/manager/devbox/extract', () => { it('returns a package dependency when the devbox JSON file has a single package', () => { const result = extractPackageFile( codeBlock` - { - "packages": ["nodejs@20.1.8"] - } - `, + { + "packages": ["nodejs@20.1.8"] + } + `, 'devbox.lock', ); expect(result).toEqual({ @@ -33,7 +33,6 @@ describe('modules/manager/devbox/extract', () => { depName: 'nodejs', currentValue: '20.1.8', datasource: 'devbox', - packageName: 'nodejs', }, ], }); @@ -42,12 +41,12 @@ describe('modules/manager/devbox/extract', () => { it('returns a package dependency when the devbox JSON file has a single package with a version object', () => { const result = extractPackageFile( codeBlock` - { - "packages": { - "nodejs": "20.1.8" + { + "packages": { + "nodejs": "20.1.8" + } } - } - `, + `, 'devbox.lock', ); expect(result).toEqual({ @@ -56,7 +55,6 @@ describe('modules/manager/devbox/extract', () => { depName: 'nodejs', currentValue: '20.1.8', datasource: 'devbox', - packageName: 'nodejs', }, ], }); @@ -65,12 +63,12 @@ describe('modules/manager/devbox/extract', () => { it('returns invalid-version when the devbox JSON file has a single package with an invalid version', () => { const result = extractPackageFile( codeBlock` - { - "packages": { - "nodejs": "^20.1.8" + { + "packages": { + "nodejs": "^20.1.8" + } } - } - `, + `, 'devbox.lock', ); expect(result).toEqual({ @@ -79,7 +77,6 @@ describe('modules/manager/devbox/extract', () => { currentValue: '^20.1.8', datasource: 'devbox', depName: 'nodejs', - packageName: 'nodejs', skipReason: 'invalid-version', }, ], @@ -89,10 +86,10 @@ describe('modules/manager/devbox/extract', () => { it('returns a package dependency when the devbox JSON file has multiple packages', () => { const result = extractPackageFile( codeBlock` - { - "packages": ["nodejs@20.1.8", "yarn@1.22.10"] - } - `, + { + "packages": ["nodejs@20.1.8", "yarn@1.22.10"] + } + `, 'devbox.lock', ); expect(result).toEqual({ @@ -101,13 +98,11 @@ describe('modules/manager/devbox/extract', () => { depName: 'nodejs', currentValue: '20.1.8', datasource: 'devbox', - packageName: 'nodejs', }, { depName: 'yarn', currentValue: '1.22.10', datasource: 'devbox', - packageName: 'yarn', }, ], }); @@ -116,13 +111,13 @@ describe('modules/manager/devbox/extract', () => { it('returns a package dependency when the devbox JSON file has multiple packages with in a packages object', () => { const result = extractPackageFile( codeBlock` - { - "packages": { - "nodejs": "20.1.8", - "yarn": "1.22.10" + { + "packages": { + "nodejs": "20.1.8", + "yarn": "1.22.10" + } } - } - `, + `, 'devbox.lock', ); expect(result).toEqual({ @@ -131,13 +126,11 @@ describe('modules/manager/devbox/extract', () => { depName: 'nodejs', currentValue: '20.1.8', datasource: 'devbox', - packageName: 'nodejs', }, { depName: 'yarn', currentValue: '1.22.10', datasource: 'devbox', - packageName: 'yarn', }, ], }); @@ -146,17 +139,17 @@ describe('modules/manager/devbox/extract', () => { it('returns a package dependency when the devbox JSON file has multiple packages with package objects', () => { const result = extractPackageFile( codeBlock` - { - "packages": { - "nodejs": { - "version": "20.1.8" - }, - "yarn": { - "version": "1.22.10" + { + "packages": { + "nodejs": { + "version": "20.1.8" + }, + "yarn": { + "version": "1.22.10" + } } } - } - `, + `, 'devbox.lock', ); expect(result).toEqual({ @@ -165,13 +158,11 @@ describe('modules/manager/devbox/extract', () => { depName: 'nodejs', currentValue: '20.1.8', datasource: 'devbox', - packageName: 'nodejs', }, { depName: 'yarn', currentValue: '1.22.10', datasource: 'devbox', - packageName: 'yarn', }, ], }); @@ -180,14 +171,14 @@ describe('modules/manager/devbox/extract', () => { it('returns invalid dependencies', () => { const result = extractPackageFile( codeBlock` - { - "packages": { - "nodejs": "20.1.8", - "yarn": "1.22.10", - "invalid": "invalid" + { + "packages": { + "nodejs": "20.1.8", + "yarn": "1.22.10", + "invalid": "invalid" + } } - } - `, + `, 'devbox.lock', ); expect(result).toEqual({ @@ -196,19 +187,16 @@ describe('modules/manager/devbox/extract', () => { depName: 'nodejs', currentValue: '20.1.8', datasource: 'devbox', - packageName: 'nodejs', }, { depName: 'yarn', currentValue: '1.22.10', datasource: 'devbox', - packageName: 'yarn', }, { currentValue: 'invalid', datasource: 'devbox', depName: 'invalid', - packageName: 'invalid', skipReason: 'invalid-version', }, ], @@ -218,16 +206,16 @@ describe('modules/manager/devbox/extract', () => { it('returns invalid dependencies with package objects', () => { const result = extractPackageFile( codeBlock` - { - "packages": { - "nodejs": "20.1.8", - "yarn": "1.22.10", - "invalid": { - "version": "invalid" + { + "packages": { + "nodejs": "20.1.8", + "yarn": "1.22.10", + "invalid": { + "version": "invalid" + } } } - } - `, + `, 'devbox.lock', ); expect(result).toEqual({ @@ -236,19 +224,16 @@ describe('modules/manager/devbox/extract', () => { depName: 'nodejs', currentValue: '20.1.8', datasource: 'devbox', - packageName: 'nodejs', }, { depName: 'yarn', currentValue: '1.22.10', datasource: 'devbox', - packageName: 'yarn', }, { currentValue: 'invalid', datasource: 'devbox', depName: 'invalid', - packageName: 'invalid', skipReason: 'invalid-version', }, ], @@ -258,10 +243,10 @@ describe('modules/manager/devbox/extract', () => { it('returns invalid dependencies from the packages array', () => { const result = extractPackageFile( codeBlock` - { - "packages": ["nodejs@20.1.8", "yarn@1.22.10", "invalid@invalid", "invalid2"] - } - `, + { + "packages": ["nodejs@20.1.8", "yarn@1.22.10", "invalid@invalid", "invalid2"] + } + `, 'devbox.lock', ); expect(result).toEqual({ @@ -270,26 +255,21 @@ describe('modules/manager/devbox/extract', () => { depName: 'nodejs', currentValue: '20.1.8', datasource: 'devbox', - packageName: 'nodejs', }, { depName: 'yarn', currentValue: '1.22.10', datasource: 'devbox', - packageName: 'yarn', }, { currentValue: 'invalid', datasource: 'devbox', depName: 'invalid', - packageName: 'invalid', skipReason: 'invalid-version', }, { - currentValue: undefined, datasource: 'devbox', depName: 'invalid2', - packageName: 'invalid2', skipReason: 'not-a-version', }, ], @@ -299,10 +279,10 @@ describe('modules/manager/devbox/extract', () => { it('returns null if there are no dependencies', () => { const result = extractPackageFile( codeBlock` - { - "packages": [] - } - `, + { + "packages": [] + } + `, 'devbox.lock', ); expect(result).toBeNull(); diff --git a/lib/modules/manager/devbox/extract.ts b/lib/modules/manager/devbox/extract.ts index ab557ce7ea4b9e..1703779ea89ed4 100644 --- a/lib/modules/manager/devbox/extract.ts +++ b/lib/modules/manager/devbox/extract.ts @@ -1,6 +1,6 @@ -import { logger } from '../../../logger'; +import { logger, withMeta } from '../../../logger'; import type { PackageFileContent } from '../types'; -import { DevboxFileSchema } from './schema'; +import { DevboxSchema } from './schema'; export function extractPackageFile( content: string, @@ -8,20 +8,10 @@ export function extractPackageFile( ): PackageFileContent | null { logger.trace('devbox.extractPackageFile()'); - const parsedFile = DevboxFileSchema.safeParse(content); - if (parsedFile.error) { - logger.debug( - { packageFile, error: parsedFile.error }, - 'Error parsing devbox.json', - ); + const deps = withMeta({ packageFile }, () => DevboxSchema.parse(content)); + if (!deps.length) { return null; } - const deps = parsedFile.data.packages; - - if (deps.length) { - return { deps }; - } - - return null; + return { deps }; } diff --git a/lib/modules/manager/devbox/schema.spec.ts b/lib/modules/manager/devbox/schema.spec.ts deleted file mode 100644 index 02bd8b97ac537f..00000000000000 --- a/lib/modules/manager/devbox/schema.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { DevboxSchema } from './schema'; - -describe('modules/manager/devbox/schema', () => { - it('parses devbox.json with empty packages', () => { - expect(DevboxSchema.parse({ packages: {} })).toEqual({ - packages: [], - }); - }); - - it.each([ - [ - 'parses devbox.json with packages record', - { packages: { foo: '1.2.3' } }, - { packages: { foo: '1.2.3', bar: '1.2.3' } }, - ], - [ - 'parses devbox.json with packages record with version key', - { - packages: { - foo: { - version: '1.2.3', - }, - }, - }, - { - packages: { - foo: { - version: '1.2.3', - }, - bar: { - version: '1.2.3', - }, - }, - }, - ], - [ - 'parses devbox.json with packages array', - { packages: ['foo@1.2.3'] }, - { packages: ['foo@1.2.3', 'bar@1.2.3'] }, - ], - ])('%s', (_, singleTest, multipleTest) => { - expect(DevboxSchema.parse(singleTest)).toEqual({ - packages: [ - { - currentValue: '1.2.3', - datasource: 'devbox', - depName: 'foo', - packageName: 'foo', - }, - ], - }); - expect(DevboxSchema.parse(multipleTest)).toEqual({ - packages: [ - { - currentValue: '1.2.3', - datasource: 'devbox', - depName: 'foo', - packageName: 'foo', - }, - { - currentValue: '1.2.3', - datasource: 'devbox', - depName: 'bar', - packageName: 'bar', - }, - ], - }); - }); -}); diff --git a/lib/modules/manager/devbox/schema.ts b/lib/modules/manager/devbox/schema.ts index d96a4eb9417bb3..77f88ee8f18dc5 100644 --- a/lib/modules/manager/devbox/schema.ts +++ b/lib/modules/manager/devbox/schema.ts @@ -1,68 +1,63 @@ import { z } from 'zod'; import { logger } from '../../../logger'; -import { Jsonc } from '../../../util/schema-utils'; +import { + Jsonc, + LooseArray, + LooseRecord, + withDebugMessage, +} from '../../../util/schema-utils'; import { DevboxDatasource } from '../../datasource/devbox'; -import * as devboxVersioning from '../../versioning/devbox'; +import { api as devboxVersioning } from '../../versioning/devbox'; import type { PackageDependency } from '../types'; -export const DevboxSchema = z.object({ - packages: z - .union([ - z.array(z.string()).transform((packages) => - packages.reduce( - (result, pkg) => { - const [name, version] = pkg.split('@'); - result[name] = version; - return result; - }, - {} as Record, - ), - ), - z.record( - z.union([ - z.string(), - z.object({ version: z.string() }).transform(({ version }) => version), - ]), - ), - ]) - .transform((packages): PackageDependency[] => - Object.entries(packages) - .map(([pkgName, pkgVer]) => getDep(pkgName, pkgVer)) - .filter((pkgDep): pkgDep is PackageDependency => !!pkgDep), - ), -}); +const DevboxEntry = z + .array(z.string()) + .min(1) + .transform(([depName, currentValue]) => { + const dep: PackageDependency = { + datasource: DevboxDatasource.id, + depName, + }; -export const DevboxFileSchema = Jsonc.pipe(DevboxSchema); + if (!currentValue) { + logger.trace( + { depName }, + 'Devbox: skipping invalid devbox dependency in devbox JSON file.', + ); + dep.skipReason = 'not-a-version'; + return dep; + } -function getDep( - packageName: string, - version: string | undefined, -): PackageDependency { - const dep = { - depName: packageName, - currentValue: version, - datasource: DevboxDatasource.id, - packageName, - }; - if (!dep.currentValue) { - logger.trace( - { packageName }, - 'Skipping devbox dependency with no version in devbox JSON file.', - ); - return { - ...dep, - skipReason: 'not-a-version', - }; - } - if (!devboxVersioning.api.isValid(dep.currentValue)) { - logger.trace( - { packageName }, - 'Skipping invalid devbox dependency in devbox JSON file.', - ); - return { - ...dep, - skipReason: 'invalid-version', - }; - } - return dep; -} + dep.currentValue = currentValue; + + if (!devboxVersioning.isValid(currentValue)) { + logger.debug( + { depName }, + 'Devbox: skipping invalid devbox dependency in devbox JSON file.', + ); + dep.skipReason = 'invalid-version'; + return dep; + } + + return dep; + }); + +export const DevboxSchema = Jsonc.pipe( + z.object({ + packages: z + .union([ + LooseArray(z.string().transform((pkg) => pkg.split('@'))), + LooseRecord( + z.union([ + z.string(), + z + .object({ version: z.string() }) + .transform(({ version }) => version), + ]), + ).transform((obj) => Object.entries(obj)), + ]) + .pipe(LooseArray(DevboxEntry)), + }), +) + .transform(({ packages }) => packages) + .catch(withDebugMessage([], 'Devbox: error parsing file'));