import { Fixtures } from '~test/fixtures.ts'; import type { Upgrade } from '../types.ts'; import type { DenoManagerData } from './types.ts'; import { updateDependency } from './update.ts'; describe('modules/manager/deno/update', () => { describe('updateDependency', () => { describe('deno.json/jsonc', () => { it('updates dependency in imports', () => { const fileContent = JSON.stringify( { imports: { fs: 'https://deno.land/std@0.223.0/fs/mod.ts', }, }, null, 2, ); const upgrade: Upgrade = { depName: 'https://deno.land/std', depType: 'imports', currentValue: '0.223.0', newValue: '0.224.0', datasource: 'deno', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); const parsed = JSON.parse(result!); expect(parsed.imports.fs).toBe( 'https://deno.land/std@0.224.0/fs/mod.ts', ); }); it('throws when multiple imports require more than one replacement', () => { const fileContent = JSON.stringify( { imports: { fs: 'https://deno.land/std@0.223.0/fs/mod.ts', path: 'https://deno.land/std@0.223.0/path/mod.ts', }, }, null, 2, ); const upgrade: Upgrade = { depName: 'https://deno.land/std', depType: 'imports', currentValue: '0.223.0', newValue: '0.224.0', datasource: 'deno', }; expect(() => updateDependency({ fileContent, packageFile: 'deno.json', upgrade }), ).toThrow(Error); }); it('updates dependency in scopes', () => { const fileContent = JSON.stringify( { scopes: { 'https://deno.land/x/': { dep2: 'jsr:@scope/dep1@latest', }, }, }, null, 2, ); const upgrade: Upgrade = { depName: '@scope/dep1', depType: 'scopes', currentValue: 'latest', newValue: '2.0.0', datasource: 'jsr', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); expect(result).not.toBeNull(); const parsed = JSON.parse(result!); expect(parsed.scopes['https://deno.land/x/'].dep2).toBe( 'jsr:@scope/dep1@2.0.0', ); }); it('returns null when scopes element not found', () => { const fileContent = JSON.stringify( { scopes: { 'https://deno.land/x/': { dep2: 'jsr:@scope/dep2@latest', }, }, }, null, 2, ); const upgrade: Upgrade = { depName: '@scope/dep1', depType: 'scopes', currentValue: 'latest', newValue: '2.0.0', datasource: 'jsr', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); expect(result).toBeNull(); }); it('updates dependency in tasks', () => { const fileContent = JSON.stringify( { tasks: { build: 'deno run -A npm:dep1@4.0.0', dev: { command: 'deno run --allow-net npm:dep2@14.0.1', }, }, }, null, 2, ); const upgrade: Upgrade = { depName: 'dep1', depType: 'tasks', currentValue: '4.0.0', newValue: '4.1.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); const parsed = JSON.parse(result!); expect(parsed.tasks.build).toBe('deno run -A npm:dep1@4.1.0'); }); it('updates dependency in tasks.command', () => { const fileContent = JSON.stringify( { tasks: { build: 'deno run -A npm:dep1@4.0.0', dev: { command: 'deno run --allow-net npm:dep2@14.0.1', }, }, }, null, 2, ); const upgrade: Upgrade = { depName: 'dep2', depType: 'tasks.command', currentValue: '14.0.1', newValue: '16.0.0', datasource: 'npm', }; const resul = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); const parsed = JSON.parse(resul!); expect(parsed.tasks.dev.command).toBe( 'deno run --allow-net npm:dep2@16.0.0', ); }); it('returns null when tasks element not found', () => { const fileContent = JSON.stringify( { tasks: { build: 'deno run -A npm:dep2@4.0.0', dev: { command: 'deno run --allow-net npm:dep2@14.0.1', }, }, }, null, 2, ); const upgrade: Upgrade = { depName: 'dep1', depType: 'tasks', currentValue: '4.0.0', newValue: '4.1.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); expect(result).toBeNull(); }); it('returns null when tasks.command element not found', () => { const fileContent = JSON.stringify( { tasks: { build: 'deno run -A npm:dep1@4.0.0', dev: { command: 'deno run --allow-net npm:dep2@14.0.1', }, }, }, null, 2, ); const upgrade: Upgrade = { depName: 'dep1', depType: 'tasks.command', currentValue: '14.0.1', newValue: '16.0.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); expect(result).toBeNull(); }); it('updates dependency in compilerOptions.types', () => { const fileContent = JSON.stringify( { compilerOptions: { types: ['npm:@types/dep2@18.0.0'], jsxImportSource: 'https://deno.land/x/dep2@18.0.0', jsxImportSourceTypes: 'npm:@types/dep2@18.0.0', }, }, null, 2, ); const upgrade: Upgrade = { depName: '@types/dep2', depType: 'compilerOptions.types', currentValue: '18.0.0', newValue: '19.0.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); const parsed = JSON.parse(result!); expect(parsed.compilerOptions.types[0]).toBe('npm:@types/dep2@19.0.0'); }); it('returns null when compilerOptions.types is empty array', () => { const fileContent = JSON.stringify( { compilerOptions: { types: [], }, }, null, 2, ); const upgrade: Upgrade = { depName: '@types/dep2', depType: 'compilerOptions.types', currentValue: '18.0.0', newValue: '19.0.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); expect(result).toBeNull(); }); it('returns null when compilerOptions.types element not found', () => { const fileContent = JSON.stringify( { compilerOptions: { types: ['npm:@types/other@18.0.0'], }, }, null, 2, ); const upgrade: Upgrade = { depName: '@types/dep2', depType: 'compilerOptions.types', currentValue: '18.0.0', newValue: '19.0.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); expect(result).toBeNull(); }); it('updates dependency in compilerOptions.jsxImportSource', () => { const fileContent = JSON.stringify( { compilerOptions: { types: ['npm:@types/dep2@18.0.0'], jsxImportSource: 'https://deno.land/x/dep2@18.0.0', jsxImportSourceTypes: 'npm:@types/dep2@18.0.0', }, }, null, 2, ); const upgrade: Upgrade = { depName: 'https://deno.land/x/dep2', depType: 'compilerOptions.jsxImportSource', currentValue: '18.0.0', newValue: '19.0.0', datasource: 'deno', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); const parsed = JSON.parse(result!); expect(parsed.compilerOptions.jsxImportSource).toBe( 'https://deno.land/x/dep2@19.0.0', ); }); it('returns null when compilerOptions.jsxImportSource does not exist', () => { const fileContent = JSON.stringify( { compilerOptions: { types: ['npm:@types/dep2@18.0.0'], }, }, null, 2, ); const upgrade: Upgrade = { depName: 'https://deno.land/x/dep2', depType: 'compilerOptions.jsxImportSource', currentValue: '18.0.0', newValue: '19.0.0', datasource: 'deno', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); expect(result).toBeNull(); }); it('returns null when compilerOptions.jsxImportSourceTypes does not exist', () => { const fileContent = JSON.stringify( { compilerOptions: { types: ['npm:@types/dep2@18.0.0'], }, }, null, 2, ); const upgrade: Upgrade = { depName: '@types/dep2', depType: 'compilerOptions.jsxImportSourceTypes', currentValue: '18.0.0', newValue: '19.0.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); expect(result).toBeNull(); }); it('updates dependency in compilerOptions.jsxImportSourceTypes', () => { const fileContent = JSON.stringify( { compilerOptions: { types: ['npm:@types/dep2@18.0.0'], jsxImportSource: 'https://deno.land/x/dep2@18.0.0', jsxImportSourceTypes: 'npm:@types/dep2@18.0.0', }, }, null, 2, ); const upgrade: Upgrade = { depName: '@types/dep2', depType: 'compilerOptions.jsxImportSourceTypes', currentValue: '18.0.0', newValue: '19.0.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); const parsed = JSON.parse(result!); expect(parsed.compilerOptions.jsxImportSourceTypes).toBe( 'npm:@types/dep2@19.0.0', ); }); it('updates dependency in lint plugins', () => { const fileContent = JSON.stringify( { lint: { plugins: ['npm:dep1@5.0.0'], }, }, null, 2, ); const upgrade: Upgrade = { depName: 'dep1', depType: 'lint.plugins', currentValue: '5.0.0', newValue: '6.0.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); const parsed = JSON.parse(result!); expect(parsed.lint.plugins).toContain('npm:dep1@6.0.0'); }); it('returns null when lint.plugins element not found', () => { const fileContent = JSON.stringify( { lint: { plugins: ['npm:dep2@5.0.0'], }, }, null, 2, ); const upgrade: Upgrade = { depName: 'dep1', depType: 'lint.plugins', currentValue: '5.0.0', newValue: '6.0.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); expect(result).toBeNull(); }); it('returns null when lint.plugins is empty array', () => { const fileContent = JSON.stringify( { lint: { plugins: [], }, }, null, 2, ); const upgrade: Upgrade = { depName: 'dep1', depType: 'lint.plugins', currentValue: '5.0.0', newValue: '6.0.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); expect(result).toBeNull(); }); it('handles dependency without version', () => { const fileContent = JSON.stringify( { imports: { dep1: 'npm:dep1', }, }, null, 2, ); const upgrade: Upgrade = { depName: 'dep1', depType: 'imports', currentValue: undefined, newValue: '1.0.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); const parsed = JSON.parse(result!); expect(parsed.imports.dep1).toBe('npm:dep1@1.0.0'); }); it('returns null if packageFile is not defined', () => { const fileContent = JSON.stringify({}, null, 2); const upgrade: Upgrade = {}; const result = updateDependency({ fileContent, packageFile: '', upgrade, }); expect(result).toBeNull(); }); it('returns null for not supported datasource', () => { const fileContent = JSON.stringify( { imports: { dep1: 'unknown:dep1@1.0.0', }, }, null, 2, ); const upgrade: Upgrade = { depName: 'dep1', depType: 'imports', currentValue: '1.0.0', newValue: '2.0.0', datasource: 'unknown', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); expect(result).toBeNull(); }); it('currentValue is not defined when deno datasource', () => { const fileContent = JSON.stringify( { imports: { fs: 'https://deno.land/std/fs', }, }, null, 2, ); const upgrade: Upgrade = { depName: 'https://deno.land/std/fs', depType: 'imports', newValue: '2.0.0', datasource: 'deno', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); const parsed = JSON.parse(result!); expect(parsed.imports.fs).toBe('https://deno.land/std/fs@2.0.0'); }); it('returns null for missing required values', () => { const fileContent = '{}'; const upgrade: Upgrade = { depName: undefined, depType: undefined, currentValue: '1.0.0', newValue: '2.0.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); expect(result).toBeNull(); }); it('handles complex JSON with nested structures', () => { const fileContent = JSON.stringify( { name: 'my-deno-app', version: '1.0.0', imports: { dep1: 'npm:dep1@1.0.0', dep2: 'npm:dep2@1.0.0', }, tasks: { build: 'deno run -A npm:dep1@2.0.0 build', dev: { command: 'deno run -A --watch npm:dep1@3.0.0 dev', }, }, compilerOptions: { jsx: 'dep1', jsxImportSource: 'npm:dep1@1.0.0', }, }, null, 2, ); const upgrade: Upgrade = { depName: 'dep1', depType: 'imports', currentValue: '1.0.0', newValue: '1.1.0', datasource: 'npm', }; const result1 = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); const parsed = JSON.parse(result1!); expect(parsed.imports.dep1).toBe('npm:dep1@1.1.0'); }); it('handles the case where the desired version is already supported', () => { const fileContent = JSON.stringify({ imports: { dep1: 'npm:dep1@1.0.0', }, }); const upgrade: Upgrade = { depName: 'dep1', depType: 'imports', currentValue: '1.0.0', newValue: '1.0.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); expect(result).toEqual(fileContent); }); it('returns null if empty file', () => { const fileContent = '{}'; const upgrade: Upgrade = { depName: undefined, depType: undefined, currentValue: '1.0.0', newValue: '2.0.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); expect(result).toBeNull(); }); it('handles error during update gracefully', () => { const fileContent = 'invalid json'; const upgrade: Upgrade = { depName: 'dep1', depType: 'imports', currentValue: '1.0.0', newValue: '2.0.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); expect(result).toBeNull(); }); it('depName is not defined', () => { const fileContent = JSON.stringify({ imports: { dep1: 'invalid:dep1@1.0.0', }, }); const upgrade: Upgrade = { depName: undefined, depType: 'imports', currentValue: '1.0.0', newValue: '2.0.0', datasource: 'invalid', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); expect(result).toBeNull(); }); it('unsupported packageFile', () => { const fileContent = JSON.stringify({}); const upgrade: Upgrade = { depName: 'dep1', depType: 'imports', currentValue: '1.0.0', newValue: '2.0.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'unknown.yaml', upgrade, }); expect(result).toBeNull(); }); it('replaces only exact matches', () => { const fileContent = JSON.stringify({ imports: { dep1: 'npm:dep1@1.0.0', dep11: 'npm:dep11@1.0.0', }, }); const upgrade: Upgrade = { depName: 'dep1', depType: 'imports', currentValue: '1.0.0', newValue: '2.0.0', datasource: 'npm', packageFile: 'deno.json', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); const parsed = JSON.parse(result!); expect(parsed.imports.dep1).toContain('npm:dep1@2.0.0'); }); it('returns null when depType is not handled', () => { const fileContent = JSON.stringify({ imports: { dep1: 'npm:dep1@1.0.0', }, }); const upgrade: Upgrade = { depName: 'dep1', depType: 'unknown.type', currentValue: '1.0.0', newValue: '2.0.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); expect(result).toBeNull(); }); it('returns null when compilerOptions.types does not exist', () => { const fileContent = JSON.stringify({ compilerOptions: { jsx: 'react', }, }); const upgrade: Upgrade = { depName: '@types/dep2', depType: 'compilerOptions.types', currentValue: '18.0.0', newValue: '19.0.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); expect(result).toBeNull(); }); it('returns null when lint.plugins does not exist', () => { const fileContent = JSON.stringify({ lint: { rules: {}, }, }); const upgrade: Upgrade = { depName: 'dep1', depType: 'lint.plugins', currentValue: '5.0.0', newValue: '6.0.0', datasource: 'npm', }; const result = updateDependency({ fileContent, packageFile: 'deno.json', upgrade, }); expect(result).toBeNull(); }); }); describe('.json', () => { it('updates dependency in imports', () => { const fileContent = JSON.stringify({ imports: { dep1: 'jsr:@scope/dep1@1.0.0', }, }); const upgrade: Upgrade = { depName: '@scope/dep1', depType: 'imports', currentValue: '1.0.0', newValue: '2.0.0', datasource: 'jsr', managerData: { importMapReferrer: 'deno.json', }, }; const result = updateDependency({ fileContent, packageFile: 'import_map.json', upgrade, }); const parsed = JSON.parse(result!); expect(parsed.imports.dep1).toContain('jsr:@scope/dep1@2.0.0'); }); it('handles error during update gracefully', () => { const fileContent = 'invalid json'; const upgrade: Upgrade = { depName: 'dep1', depType: 'imports', currentValue: '1.0.0', newValue: '2.0.0', datasource: 'npm', managerData: { importMapReferrer: 'deno.json', }, }; const result = updateDependency({ fileContent, packageFile: 'import_map.json', upgrade, }); expect(result).toBeNull(); }); it('returns null for not supported datasource', () => { const fileContent = JSON.stringify( { imports: { dep1: 'unknown:dep1@1.0.0', }, }, null, 2, ); const upgrade: Upgrade = { depName: 'dep1', depType: 'imports', currentValue: '1.0.0', newValue: '2.0.0', datasource: 'unknown', managerData: { importMapReferrer: 'deno.json', }, }; const result = updateDependency({ fileContent, packageFile: 'import_map.json', upgrade, }); expect(result).toBeNull(); }); it('depName is not defined', () => { const fileContent = JSON.stringify({ imports: { dep1: 'invalid:dep1@1.0.0', }, }); const upgrade: Upgrade = { depName: undefined, depType: 'imports', currentValue: '1.0.0', newValue: '2.0.0', datasource: 'invalid', managerData: { importMapReferrer: 'deno.json', }, }; const result = updateDependency({ fileContent, packageFile: 'import_map.json', upgrade, }); expect(result).toBeNull(); }); }); describe('package.json', () => { // ported from lib/modules/manager/npm/update/dependency/index.spec.ts it('replaces a dependency value', () => { function readFixture(x: string): string { return Fixtures.get(x, '../npm'); } const input01Content = readFixture('inputs/01.json'); const upgrade: Upgrade = { depType: 'dependencies', depName: 'cheerio', newValue: '0.22.1', }; const outputContent = readFixture('outputs/011.json'); const testContent = updateDependency({ fileContent: input01Content, packageFile: 'package.json', upgrade, }); expect(testContent).toEqual(outputContent); }); }); }); });