From 84171e1019cb17f0c741751b512f5e6577f3bd86 Mon Sep 17 00:00:00 2001 From: GhostTypes <106415648+GhostTypes@users.noreply.github.com> Date: Sat, 28 Mar 2026 14:04:01 -0400 Subject: [PATCH 1/3] fix: add unobfuscated version awareness to MappingService (#5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MappingService.getMappings() and lookupMapping() now detect unobfuscated Minecraft versions (26.1+) and throw clear, actionable errors instead of failing with cryptic "Intermediary mappings not available" messages. The RemapService already handled this correctly, but tools like find_mapping and the mappings resource endpoint called MappingService directly, bypassing the check. Bumps version to 1.1.0 for npm publish — v1.0.0 on npm predates the unobfuscated version support added in PR #4. --- __tests__/core/mapping-service.test.ts | 78 +++++++++++++++++++++++++- __tests__/core/version-manager.test.ts | 19 +++++++ __tests__/tools/core-tools.test.ts | 15 +++++ package-lock.json | 4 +- package.json | 2 +- src/services/mapping-service.ts | 32 +++++++++++ 6 files changed, 146 insertions(+), 4 deletions(-) diff --git a/__tests__/core/mapping-service.test.ts b/__tests__/core/mapping-service.test.ts index 2d573db..73b38e2 100644 --- a/__tests__/core/mapping-service.test.ts +++ b/__tests__/core/mapping-service.test.ts @@ -1,7 +1,7 @@ import { existsSync, readFileSync } from 'node:fs'; import { describe, expect, it } from 'vitest'; import { getMappingService } from '../../src/services/mapping-service.js'; -import { TEST_MAPPING, TEST_VERSION } from '../test-constants.js'; +import { TEST_MAPPING, TEST_VERSION, UNOBFUSCATED_TEST_VERSION } from '../test-constants.js'; /** * Mapping Service Tests @@ -434,3 +434,79 @@ describe('Mojmap Tiny v2 Structure Verification', () => { expect(firstLine).toContain('named'); }, 180000); }); + +/** + * Unobfuscated version handling (26.1+) + * + * Unobfuscated Minecraft versions ship JARs without obfuscation. + * No intermediary, yarn, or mojmap mapping files exist for these versions. + * MappingService.getMappings() and lookupMapping() must fail with clear, + * actionable error messages instead of cryptic download failures. + * + * Reproduces: https://github.com/MCDxAI/minecraft-dev-mcp/issues/5 + */ +describe('Unobfuscated version handling', () => { + it('should throw actionable error for getMappings(intermediary) on unobfuscated version', async () => { + const mappingService = getMappingService(); + await expect( + mappingService.getMappings(UNOBFUSCATED_TEST_VERSION, 'intermediary'), + ).rejects.toThrow(/unobfuscated/i); + await expect( + mappingService.getMappings(UNOBFUSCATED_TEST_VERSION, 'intermediary'), + ).rejects.toThrow(/mojmap/i); + }, 30000); + + it('should throw actionable error for getMappings(yarn) on unobfuscated version', async () => { + const mappingService = getMappingService(); + await expect( + mappingService.getMappings(UNOBFUSCATED_TEST_VERSION, 'yarn'), + ).rejects.toThrow(/unobfuscated/i); + await expect( + mappingService.getMappings(UNOBFUSCATED_TEST_VERSION, 'yarn'), + ).rejects.toThrow(/mojmap/i); + }, 30000); + + it('should throw actionable error for getMappings(mojmap) on unobfuscated version', async () => { + const mappingService = getMappingService(); + await expect( + mappingService.getMappings(UNOBFUSCATED_TEST_VERSION, 'mojmap'), + ).rejects.toThrow(/unobfuscated/i); + await expect( + mappingService.getMappings(UNOBFUSCATED_TEST_VERSION, 'mojmap'), + ).rejects.toThrow(/already in Mojang/i); + }, 30000); + + it('should throw actionable error for lookupMapping on unobfuscated version', async () => { + const mappingService = getMappingService(); + await expect( + mappingService.lookupMapping( + UNOBFUSCATED_TEST_VERSION, + 'Entity', + 'mojmap', + 'yarn', + ), + ).rejects.toThrow(/unobfuscated/i); + await expect( + mappingService.lookupMapping( + UNOBFUSCATED_TEST_VERSION, + 'Entity', + 'mojmap', + 'yarn', + ), + ).rejects.toThrow(/no mapping translation is needed/i); + }, 30000); + + it('should allow same-type lookupMapping on unobfuscated version (identity)', async () => { + const mappingService = getMappingService(); + // Same source and target mapping should still return identity (no mapping file needed) + const result = await mappingService.lookupMapping( + UNOBFUSCATED_TEST_VERSION, + 'net/minecraft/world/entity/Entity', + 'mojmap', + 'mojmap', + ); + expect(result.found).toBe(true); + expect(result.source).toBe('net/minecraft/world/entity/Entity'); + expect(result.target).toBe('net/minecraft/world/entity/Entity'); + }, 10000); +}); diff --git a/__tests__/core/version-manager.test.ts b/__tests__/core/version-manager.test.ts index fcd942d..a244a1e 100644 --- a/__tests__/core/version-manager.test.ts +++ b/__tests__/core/version-manager.test.ts @@ -70,5 +70,24 @@ describe('Version Management', () => { const result = await versionManager.isVersionUnobfuscated('26.1-snapshot-9'); expect(result).toBe(true); }, 30000); + + // Regression tests for https://github.com/MCDxAI/minecraft-dev-mcp/issues/5 + it('should return true for 26.1-snapshot-10 (issue #5)', async () => { + const versionManager = getVersionManager(); + const result = await versionManager.isVersionUnobfuscated('26.1-snapshot-10'); + expect(result).toBe(true); + }, 30000); + + it('should return true for 26.1-snapshot-11 (issue #5)', async () => { + const versionManager = getVersionManager(); + const result = await versionManager.isVersionUnobfuscated('26.1-snapshot-11'); + expect(result).toBe(true); + }, 30000); + + it('should return true for 26.1-rc-3 (issue #5)', async () => { + const versionManager = getVersionManager(); + const result = await versionManager.isVersionUnobfuscated('26.1-rc-3'); + expect(result).toBe(true); + }, 30000); }); }); diff --git a/__tests__/tools/core-tools.test.ts b/__tests__/tools/core-tools.test.ts index 6cec7c9..fcce214 100644 --- a/__tests__/tools/core-tools.test.ts +++ b/__tests__/tools/core-tools.test.ts @@ -560,6 +560,21 @@ describe('Decompile and Remap Tools', () => { expect(text).toMatch(/use ['"]mojmap['"] mapping/i); }, 120000); + it('should return actionable error for find_mapping on unobfuscated version', async () => { + const result = await handleFindMapping({ + symbol: 'Entity', + version: UNOBFUSCATED_TEST_VERSION, + sourceMapping: 'mojmap', + targetMapping: 'yarn', + }); + + expect(result).toBeDefined(); + expect(result.isError).toBe(true); + const text = result.content[0].text; + expect(text).toMatch(/unobfuscated/i); + expect(text).toMatch(/no mapping translation is needed/i); + }, 60000); + it('should handle remap_mod_jar with Fabric mod', async () => { // Skip if fixture doesn't exist if (!existsSync(METEOR_JAR_PATH)) { diff --git a/package-lock.json b/package-lock.json index 767abdf..e13e314 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@minecraft-dev/mcp-server", + "name": "@mcdxai/minecraft-dev-mcp", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@minecraft-dev/mcp-server", + "name": "@mcdxai/minecraft-dev-mcp", "version": "1.0.0", "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 3da2f35..b0c6eb2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mcdxai/minecraft-dev-mcp", - "version": "1.0.0", + "version": "1.1.0", "description": "MCP server for Minecraft mod development - decompile, remap, and explore Minecraft source code", "type": "module", "main": "./dist/index.js", diff --git a/src/services/mapping-service.ts b/src/services/mapping-service.ts index 5f1e91e..dc7b9ac 100644 --- a/src/services/mapping-service.ts +++ b/src/services/mapping-service.ts @@ -11,6 +11,7 @@ import { MappingNotFoundError } from '../utils/errors.js'; import { ensureDir } from '../utils/file-utils.js'; import { logger } from '../utils/logger.js'; import { getMojmapTinyPath } from '../utils/paths.js'; +import { getVersionManager } from './version-manager.js'; /** * Manages mapping downloads and caching @@ -19,6 +20,7 @@ export class MappingService { private mojangDownloader = getMojangDownloader(); private fabricMaven = getFabricMaven(); private cache = getCacheManager(); + private versionManager = getVersionManager(); // Lock to prevent concurrent downloads of the same mappings private downloadLocks = new Map>(); @@ -28,6 +30,25 @@ export class MappingService { * Uses locking to prevent concurrent downloads of the same mapping */ async getMappings(version: string, mappingType: MappingType): Promise { + // Unobfuscated versions (26.1+) ship without obfuscation — no mapping files exist. + const isUnobfuscated = await this.versionManager.isVersionUnobfuscated(version); + if (isUnobfuscated) { + if (mappingType === 'mojmap') { + throw new MappingNotFoundError( + version, + mappingType, + `Mojmap mapping files are not available for unobfuscated version ${version}. ` + + `The JAR is already in Mojang's human-readable names — decompile it directly with mapping 'mojmap'.`, + ); + } + throw new MappingNotFoundError( + version, + mappingType, + `${mappingType} mappings are not available for unobfuscated version ${version}. ` + + `Use 'mojmap' mapping instead — the JAR ships without obfuscation.`, + ); + } + const lockKey = `${version}-${mappingType}`; // For Mojmap, check for converted Tiny file first (not raw ProGuard) @@ -267,6 +288,17 @@ export class MappingService { return this.createLookupResult(true, symbol, symbol); } + // Unobfuscated versions (26.1+) have no mapping files — lookups are not possible. + const isUnobfuscated = await this.versionManager.isVersionUnobfuscated(version); + if (isUnobfuscated) { + throw new MappingNotFoundError( + version, + `${sourceMapping}->${targetMapping}`, + `Mapping lookup is not available for unobfuscated version ${version}. ` + + `The source code is already in Mojang's human-readable names — no mapping translation is needed.`, + ); + } + // Determine routing strategy const singleFile = this.getSingleFileLookup(sourceMapping, targetMapping); From 771015c9a4e37b9715d11d660971e5018b612ef6 Mon Sep 17 00:00:00 2001 From: GhostTypes <106415648+GhostTypes@users.noreply.github.com> Date: Sat, 28 Mar 2026 14:20:49 -0400 Subject: [PATCH 2/3] refactor: address review feedback from Gemini and Codex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move unobfuscated check after cache hits in getMappings() to avoid network-gating cached mapping paths (Codex P1) - Extract throwIfUnobfuscated() helper called only before downloads - Remove redundant lookupMapping() guard — getMappings() handles it (Codex P1) - Combine double await-expect assertions into single regex (Gemini) - Use it.each for parameterized version-manager regression tests (Gemini) - Collapse string concatenation into single template literals (Gemini) --- __tests__/core/mapping-service.test.ts | 24 ++--------- __tests__/core/version-manager.test.ts | 20 +++------ __tests__/tools/core-tools.test.ts | 1 - src/services/mapping-service.ts | 59 +++++++++++++------------- 4 files changed, 39 insertions(+), 65 deletions(-) diff --git a/__tests__/core/mapping-service.test.ts b/__tests__/core/mapping-service.test.ts index 73b38e2..d9c96ee 100644 --- a/__tests__/core/mapping-service.test.ts +++ b/__tests__/core/mapping-service.test.ts @@ -450,34 +450,26 @@ describe('Unobfuscated version handling', () => { const mappingService = getMappingService(); await expect( mappingService.getMappings(UNOBFUSCATED_TEST_VERSION, 'intermediary'), - ).rejects.toThrow(/unobfuscated/i); - await expect( - mappingService.getMappings(UNOBFUSCATED_TEST_VERSION, 'intermediary'), - ).rejects.toThrow(/mojmap/i); + ).rejects.toThrow(/unobfuscated.*mojmap/is); }, 30000); it('should throw actionable error for getMappings(yarn) on unobfuscated version', async () => { const mappingService = getMappingService(); await expect( mappingService.getMappings(UNOBFUSCATED_TEST_VERSION, 'yarn'), - ).rejects.toThrow(/unobfuscated/i); - await expect( - mappingService.getMappings(UNOBFUSCATED_TEST_VERSION, 'yarn'), - ).rejects.toThrow(/mojmap/i); + ).rejects.toThrow(/unobfuscated.*mojmap/is); }, 30000); it('should throw actionable error for getMappings(mojmap) on unobfuscated version', async () => { const mappingService = getMappingService(); await expect( mappingService.getMappings(UNOBFUSCATED_TEST_VERSION, 'mojmap'), - ).rejects.toThrow(/unobfuscated/i); - await expect( - mappingService.getMappings(UNOBFUSCATED_TEST_VERSION, 'mojmap'), - ).rejects.toThrow(/already in Mojang/i); + ).rejects.toThrow(/unobfuscated.*already in Mojang/is); }, 30000); it('should throw actionable error for lookupMapping on unobfuscated version', async () => { const mappingService = getMappingService(); + // lookupMapping calls getMappings internally, which throws for unobfuscated versions await expect( mappingService.lookupMapping( UNOBFUSCATED_TEST_VERSION, @@ -486,14 +478,6 @@ describe('Unobfuscated version handling', () => { 'yarn', ), ).rejects.toThrow(/unobfuscated/i); - await expect( - mappingService.lookupMapping( - UNOBFUSCATED_TEST_VERSION, - 'Entity', - 'mojmap', - 'yarn', - ), - ).rejects.toThrow(/no mapping translation is needed/i); }, 30000); it('should allow same-type lookupMapping on unobfuscated version (identity)', async () => { diff --git a/__tests__/core/version-manager.test.ts b/__tests__/core/version-manager.test.ts index a244a1e..17acc3d 100644 --- a/__tests__/core/version-manager.test.ts +++ b/__tests__/core/version-manager.test.ts @@ -72,21 +72,13 @@ describe('Version Management', () => { }, 30000); // Regression tests for https://github.com/MCDxAI/minecraft-dev-mcp/issues/5 - it('should return true for 26.1-snapshot-10 (issue #5)', async () => { + it.each([ + '26.1-snapshot-10', + '26.1-snapshot-11', + '26.1-rc-3', + ])('should return true for %s (issue #5)', async (version) => { const versionManager = getVersionManager(); - const result = await versionManager.isVersionUnobfuscated('26.1-snapshot-10'); - expect(result).toBe(true); - }, 30000); - - it('should return true for 26.1-snapshot-11 (issue #5)', async () => { - const versionManager = getVersionManager(); - const result = await versionManager.isVersionUnobfuscated('26.1-snapshot-11'); - expect(result).toBe(true); - }, 30000); - - it('should return true for 26.1-rc-3 (issue #5)', async () => { - const versionManager = getVersionManager(); - const result = await versionManager.isVersionUnobfuscated('26.1-rc-3'); + const result = await versionManager.isVersionUnobfuscated(version); expect(result).toBe(true); }, 30000); }); diff --git a/__tests__/tools/core-tools.test.ts b/__tests__/tools/core-tools.test.ts index fcce214..3c077ec 100644 --- a/__tests__/tools/core-tools.test.ts +++ b/__tests__/tools/core-tools.test.ts @@ -572,7 +572,6 @@ describe('Decompile and Remap Tools', () => { expect(result.isError).toBe(true); const text = result.content[0].text; expect(text).toMatch(/unobfuscated/i); - expect(text).toMatch(/no mapping translation is needed/i); }, 60000); it('should handle remap_mod_jar with Fabric mod', async () => { diff --git a/src/services/mapping-service.ts b/src/services/mapping-service.ts index dc7b9ac..40320d2 100644 --- a/src/services/mapping-service.ts +++ b/src/services/mapping-service.ts @@ -30,25 +30,6 @@ export class MappingService { * Uses locking to prevent concurrent downloads of the same mapping */ async getMappings(version: string, mappingType: MappingType): Promise { - // Unobfuscated versions (26.1+) ship without obfuscation — no mapping files exist. - const isUnobfuscated = await this.versionManager.isVersionUnobfuscated(version); - if (isUnobfuscated) { - if (mappingType === 'mojmap') { - throw new MappingNotFoundError( - version, - mappingType, - `Mojmap mapping files are not available for unobfuscated version ${version}. ` + - `The JAR is already in Mojang's human-readable names — decompile it directly with mapping 'mojmap'.`, - ); - } - throw new MappingNotFoundError( - version, - mappingType, - `${mappingType} mappings are not available for unobfuscated version ${version}. ` + - `Use 'mojmap' mapping instead — the JAR ships without obfuscation.`, - ); - } - const lockKey = `${version}-${mappingType}`; // For Mojmap, check for converted Tiny file first (not raw ProGuard) @@ -66,6 +47,9 @@ export class MappingService { return existingDownload; } + // Unobfuscated versions (26.1+) have no mapping files — check before attempting download. + await this.throwIfUnobfuscated(version, mappingType); + // Download and convert Mojmap with lock const downloadPromise = this.downloadAndConvertMojmap(version); this.downloadLocks.set(lockKey, downloadPromise); @@ -90,6 +74,9 @@ export class MappingService { return existingDownload; } + // Unobfuscated versions (26.1+) have no mapping files — check before attempting download. + await this.throwIfUnobfuscated(version, mappingType); + // Download based on type with lock logger.info(`Downloading ${mappingType} mappings for ${version}`); let downloadPromise: Promise; @@ -248,6 +235,29 @@ export class MappingService { // Intermediary should exist for all Fabric-supported versions } + /** + * Throw a clear error if the version is unobfuscated and no mapping files exist. + * Called just before attempting a download, AFTER cache checks, so that cached + * mappings still work without hitting the network. + */ + private async throwIfUnobfuscated(version: string, mappingType: MappingType): Promise { + const isUnobfuscated = await this.versionManager.isVersionUnobfuscated(version); + if (!isUnobfuscated) return; + + if (mappingType === 'mojmap') { + throw new MappingNotFoundError( + version, + mappingType, + `Mojmap mapping files are not available for unobfuscated version ${version}. The JAR is already in Mojang's human-readable names — decompile it directly with mapping 'mojmap'.`, + ); + } + throw new MappingNotFoundError( + version, + mappingType, + `${mappingType} mappings are not available for unobfuscated version ${version}. Use 'mojmap' mapping instead — the JAR ships without obfuscation.`, + ); + } + /** * Lookup result type */ @@ -288,17 +298,6 @@ export class MappingService { return this.createLookupResult(true, symbol, symbol); } - // Unobfuscated versions (26.1+) have no mapping files — lookups are not possible. - const isUnobfuscated = await this.versionManager.isVersionUnobfuscated(version); - if (isUnobfuscated) { - throw new MappingNotFoundError( - version, - `${sourceMapping}->${targetMapping}`, - `Mapping lookup is not available for unobfuscated version ${version}. ` + - `The source code is already in Mojang's human-readable names — no mapping translation is needed.`, - ); - } - // Determine routing strategy const singleFile = this.getSingleFileLookup(sourceMapping, targetMapping); From 59d4cec7e0a07d0892b4b86b4fc98a75e39cac9a Mon Sep 17 00:00:00 2001 From: GhostTypes <106415648+GhostTypes@users.noreply.github.com> Date: Sat, 28 Mar 2026 14:35:43 -0400 Subject: [PATCH 3/3] refactor: unify getMappings() flow and fix dedupe lock race condition - Extract getCachedMapping() and startDownload() to eliminate duplicate cache/lock/download logic between mojmap and other mapping types - Recheck dedupe lock after async throwIfUnobfuscated() to prevent concurrent callers from starting parallel downloads - Fix template literal concatenation lint warning --- src/services/mapping-service.ts | 107 +++++++++++++++----------------- 1 file changed, 50 insertions(+), 57 deletions(-) diff --git a/src/services/mapping-service.ts b/src/services/mapping-service.ts index 40320d2..45d917e 100644 --- a/src/services/mapping-service.ts +++ b/src/services/mapping-service.ts @@ -26,68 +26,75 @@ export class MappingService { private downloadLocks = new Map>(); /** - * Get or download mappings for a version - * Uses locking to prevent concurrent downloads of the same mapping + * Get or download mappings for a version. + * Flow: cache → dedupe lock → unobfuscated guard → recheck lock → download. */ async getMappings(version: string, mappingType: MappingType): Promise { const lockKey = `${version}-${mappingType}`; - // For Mojmap, check for converted Tiny file first (not raw ProGuard) - if (mappingType === 'mojmap') { - const convertedPath = getMojmapTinyPath(version); - if (existsSync(convertedPath)) { - logger.info(`Using cached Mojmap (Tiny format) mappings for ${version}: ${convertedPath}`); - return convertedPath; - } - - // Check if download is already in progress - const existingDownload = this.downloadLocks.get(lockKey); - if (existingDownload) { - logger.info(`Waiting for existing Mojmap download of ${version} to complete`); - return existingDownload; - } - - // Unobfuscated versions (26.1+) have no mapping files — check before attempting download. - await this.throwIfUnobfuscated(version, mappingType); - - // Download and convert Mojmap with lock - const downloadPromise = this.downloadAndConvertMojmap(version); - this.downloadLocks.set(lockKey, downloadPromise); - try { - return await downloadPromise; - } finally { - this.downloadLocks.delete(lockKey); - } - } - - // Check cache first for other mapping types - const cachedPath = this.cache.getMappingPath(version, mappingType); + // 1. Return immediately from cache without any network access + const cachedPath = this.getCachedMapping(version, mappingType); if (cachedPath) { logger.info(`Using cached ${mappingType} mappings for ${version}: ${cachedPath}`); return cachedPath; } - // Check if download is already in progress + // 2. Deduplicate concurrent downloads for the same version+type const existingDownload = this.downloadLocks.get(lockKey); if (existingDownload) { logger.info(`Waiting for existing ${mappingType} download of ${version} to complete`); return existingDownload; } - // Unobfuscated versions (26.1+) have no mapping files — check before attempting download. + // 3. Unobfuscated versions (26.1+) have no mapping files — check before attempting download await this.throwIfUnobfuscated(version, mappingType); - // Download based on type with lock + // 4. Recheck lock — another caller may have started a download during the async check above + const postCheckDownload = this.downloadLocks.get(lockKey); + if (postCheckDownload) { + return postCheckDownload; + } + + // 5. Download with lock logger.info(`Downloading ${mappingType} mappings for ${version}`); - let downloadPromise: Promise; + const downloadPromise = this.startDownload(version, mappingType); + this.downloadLocks.set(lockKey, downloadPromise); + try { + return await downloadPromise; + } finally { + this.downloadLocks.delete(lockKey); + } + } + + /** + * Check for a locally cached mapping file without hitting the network. + */ + private getCachedMapping(version: string, mappingType: MappingType): string | null { + if (mappingType === 'mojmap') { + const convertedPath = getMojmapTinyPath(version); + return existsSync(convertedPath) ? convertedPath : null; + } + return this.cache.getMappingPath(version, mappingType) ?? null; + } + /** + * Start the actual download for a mapping type. + * Mojmap handles its own caching internally; yarn/intermediary are cached here. + */ + private async startDownload(version: string, mappingType: MappingType): Promise { switch (mappingType) { - case 'yarn': - downloadPromise = this.downloadAndExtractYarn(version); - break; - case 'intermediary': - downloadPromise = this.downloadAndExtractIntermediary(version); - break; + case 'mojmap': + return this.downloadAndConvertMojmap(version); + case 'yarn': { + const path = await this.downloadAndExtractYarn(version); + this.cache.cacheMapping(version, mappingType, path); + return path; + } + case 'intermediary': { + const path = await this.downloadAndExtractIntermediary(version); + this.cache.cacheMapping(version, mappingType, path); + return path; + } default: throw new MappingNotFoundError( version, @@ -95,19 +102,6 @@ export class MappingService { `Unsupported mapping type: ${mappingType}`, ); } - - this.downloadLocks.set(lockKey, downloadPromise); - let mappingPath: string; - try { - mappingPath = await downloadPromise; - } finally { - this.downloadLocks.delete(lockKey); - } - - // Cache the mapping - this.cache.cacheMapping(version, mappingType, mappingPath); - - return mappingPath; } /** @@ -200,8 +194,7 @@ export class MappingService { !parsed.header.namespaces.includes('named') ) { throw new Error( - `Invalid mapping-io output: expected namespaces 'intermediary' and 'named', ` + - `got ${parsed.header.namespaces.join(', ')}` + `Invalid mapping-io output: expected namespaces 'intermediary' and 'named', got ${parsed.header.namespaces.join(', ')}`, ); }