From 14da014a6f5542e2c006b6e3c33db3e395209af5 Mon Sep 17 00:00:00 2001 From: Hide_D Date: Sat, 7 Feb 2026 07:52:00 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=97=AC=EB=9F=AC=20=EC=95=A1=EC=85=98?= =?UTF-8?q?=EC=9D=98=20=EB=A1=9C=EA=B7=B8=20=EB=A9=94=EC=8B=9C=EC=A7=80=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=ED=85=9C=ED=94=8C=EB=A6=BF=20?= =?UTF-8?q?=EC=B9=B4=EC=9A=B4=ED=8A=B8=20=EB=B9=84=EA=B5=90=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../actions/instant/nation/che_불가침수락.ts | 10 +- .../instant/nation/che_불가침파기수락.ts | 6 +- .../src/actions/turn/general/che_군량매매.ts | 8 +- .../src/actions/turn/general/che_등용수락.ts | 3 +- .../src/actions/turn/general/che_선동.ts | 10 +- .../src/actions/turn/general/che_파괴.ts | 12 +- .../src/actions/turn/general/che_화계.ts | 10 +- .../actions/turn/nation/che_무작위수도이전.ts | 6 +- .../src/actions/turn/nation/che_물자원조.ts | 7 +- .../src/actions/turn/nation/che_선전포고.ts | 39 ++++--- tools/compare-command-logs.mjs | 107 +++++++++++++++--- 11 files changed, 166 insertions(+), 52 deletions(-) diff --git a/packages/logic/src/actions/instant/nation/che_불가침수락.ts b/packages/logic/src/actions/instant/nation/che_불가침수락.ts index e1fb122..493d8aa 100644 --- a/packages/logic/src/actions/instant/nation/che_불가침수락.ts +++ b/packages/logic/src/actions/instant/nation/che_불가침수락.ts @@ -15,6 +15,7 @@ import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition import type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; import { createDiplomacyPatchEffect, createLogEffect } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js'; +import { JosaUtil } from '@sammo-ts/common'; export interface NonAggressionAcceptArgs { destNationId: number; @@ -175,6 +176,8 @@ export class ActionDefinition< const currentMonth = resolveMonthIndex(context.currentYear, context.currentMonth); const targetMonth = args.year * 12 + args.month; const term = Math.max(0, targetMonth - currentMonth); + const destNationName = String(args.destNationId); + const josaWa = JosaUtil.pick(destNationName, '와'); return { effects: [ @@ -186,11 +189,14 @@ export class ActionDefinition< state: DIPLOMACY_NON_AGGRESSION, term, }), - createLogEffect(`${ACTION_NAME}을 실행했습니다. (국가 ${args.destNationId})`, { + createLogEffect( + `${destNationName}${josaWa} ${args.year}년 ${args.month}월까지 불가침에 성공했습니다.`, + { scope: LogScope.GENERAL, category: LogCategory.ACTION, format: LogFormat.MONTH, - }), + } + ), ], }; } diff --git a/packages/logic/src/actions/instant/nation/che_불가침파기수락.ts b/packages/logic/src/actions/instant/nation/che_불가침파기수락.ts index ad3f146..b1c82cc 100644 --- a/packages/logic/src/actions/instant/nation/che_불가침파기수락.ts +++ b/packages/logic/src/actions/instant/nation/che_불가침파기수락.ts @@ -12,6 +12,7 @@ import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition import type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; import { createDiplomacyPatchEffect, createLogEffect } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js'; +import { JosaUtil } from '@sammo-ts/common'; export interface NonAggressionCancelAcceptArgs { destNationId: number; @@ -81,6 +82,9 @@ export class ActionDefinition< }; } + const destNationName = String(args.destNationId); + const josaWa = JosaUtil.pick(destNationName, '와'); + return { effects: [ createDiplomacyPatchEffect(nationId, args.destNationId, { @@ -91,7 +95,7 @@ export class ActionDefinition< state: DIPLOMACY_NEUTRAL, term: 0, }), - createLogEffect(`${ACTION_NAME}을 실행했습니다. (국가 ${args.destNationId})`, { + createLogEffect(`${destNationName}${josaWa}의 불가침을 파기했습니다.`, { scope: LogScope.GENERAL, category: LogCategory.ACTION, format: LogFormat.MONTH, diff --git a/packages/logic/src/actions/turn/general/che_군량매매.ts b/packages/logic/src/actions/turn/general/che_군량매매.ts index 8e5972f..c50fc38 100644 --- a/packages/logic/src/actions/turn/general/che_군량매매.ts +++ b/packages/logic/src/actions/turn/general/che_군량매매.ts @@ -88,9 +88,9 @@ export class ActionDefinition< general.gold = Math.max(0, general.gold - sellAmount); general.rice += buyAmount; context.addLog( - `군량 ${Math.round(buyAmount).toLocaleString()}을 사서 자금 ${Math.round( + `군량 ${Math.round(buyAmount).toLocaleString()}을 사서 자금 ${Math.round( sellAmount - ).toLocaleString()}을 썼습니다.`, + ).toLocaleString()}을 썼습니다.`, { format: LogFormat.PLAIN } ); } else { @@ -101,9 +101,9 @@ export class ActionDefinition< general.rice = Math.max(0, general.rice - sellAmount); general.gold += buyAmount; context.addLog( - `군량 ${Math.round(sellAmount).toLocaleString()}을 팔아 자금 ${Math.round( + `군량 ${Math.round(sellAmount).toLocaleString()}을 팔아 자금 ${Math.round( buyAmount - ).toLocaleString()}을 얻었습니다.`, + ).toLocaleString()}을 얻었습니다.`, { format: LogFormat.PLAIN } ); } diff --git a/packages/logic/src/actions/turn/general/che_등용수락.ts b/packages/logic/src/actions/turn/general/che_등용수락.ts index 6d0b72e..6b0038a 100644 --- a/packages/logic/src/actions/turn/general/che_등용수락.ts +++ b/packages/logic/src/actions/turn/general/che_등용수락.ts @@ -18,7 +18,7 @@ import type { GeneralActionEffect, } from '@sammo-ts/logic/actions/engine.js'; import { createGeneralPatchEffect, createNationPatchEffect } from '@sammo-ts/logic/actions/engine.js'; -import { LogCategory, LogFormat } from '@sammo-ts/logic/logging/types.js'; +import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js'; import { JosaUtil } from '@sammo-ts/common'; import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; @@ -76,6 +76,7 @@ export class ActionResolver< // Global Log context.addLog(`${generalName}${josaYi} ${destNationName}${josaRo} 망명하였습니다.`, { + scope: LogScope.SYSTEM, category: LogCategory.ACTION, format: LogFormat.PLAIN, }); diff --git a/packages/logic/src/actions/turn/general/che_선동.ts b/packages/logic/src/actions/turn/general/che_선동.ts index 3faec62..8933af4 100644 --- a/packages/logic/src/actions/turn/general/che_선동.ts +++ b/packages/logic/src/actions/turn/general/che_선동.ts @@ -72,6 +72,7 @@ export class ActionResolver< const actualSecuDmg = destCity.security - newSecu; const actualTrustDmg = currentTrust - newTrust; + const injuryCount = 0; // Log const commandName = ACTION_NAME; @@ -80,10 +81,15 @@ export class ActionResolver< category: LogCategory.ACTION, format: LogFormat.MONTH, }); - ctx.addLog(`치안이 ${actualSecuDmg}, 민심이 ${actualTrustDmg.toFixed(1)} 만큼 감소했습니다.`, { + ctx.addLog( + `도시의 치안이 ${actualSecuDmg}, 민심이 ${actualTrustDmg.toFixed( + 1 + )}만큼 감소하고, 장수 ${injuryCount}명이 부상 당했습니다.`, + { category: LogCategory.ACTION, format: LogFormat.PLAIN, - }); + } + ); // City Update effects.push( diff --git a/packages/logic/src/actions/turn/general/che_파괴.ts b/packages/logic/src/actions/turn/general/che_파괴.ts index 009ae69..e6310ad 100644 --- a/packages/logic/src/actions/turn/general/che_파괴.ts +++ b/packages/logic/src/actions/turn/general/che_파괴.ts @@ -72,6 +72,7 @@ export class ActionResolver< const actualDefDmg = destCity.defence - newDef; const actualWallDmg = destCity.wall - newWall; + const injuryCount = 0; // Log const commandName = ACTION_NAME; @@ -80,10 +81,13 @@ export class ActionResolver< category: LogCategory.ACTION, format: LogFormat.MONTH, }); - ctx.addLog(`수비가 ${actualDefDmg}, 성벽이 ${actualWallDmg} 만큼 감소했습니다.`, { - category: LogCategory.ACTION, - format: LogFormat.PLAIN, - }); + ctx.addLog( + `도시의 수비가 ${actualDefDmg}, 성벽이 ${actualWallDmg}만큼 감소하고, 장수 ${injuryCount}명이 부상 당했습니다.`, + { + category: LogCategory.ACTION, + format: LogFormat.PLAIN, + } + ); // City Update effects.push( diff --git a/packages/logic/src/actions/turn/general/che_화계.ts b/packages/logic/src/actions/turn/general/che_화계.ts index 128e26e..79aa2ff 100644 --- a/packages/logic/src/actions/turn/general/che_화계.ts +++ b/packages/logic/src/actions/turn/general/che_화계.ts @@ -325,7 +325,8 @@ export class ActionResolver< ) ); - context.addLog(`${context.destCity.name}이 불타고 있습니다.`, { + const destCityName = context.destCity.name; + context.addLog(`${destCityName}${JosaUtil.pick(destCityName, '이')} 불타고 있습니다.`, { scope: LogScope.SYSTEM, category: LogCategory.SUMMARY, format: LogFormat.MONTH, @@ -343,6 +344,13 @@ export class ActionResolver< } ); + const itemCode = general.role.items.item; + if (typeof itemCode === 'string' && itemCode.length > 0) { + context.addLog(`${itemCode}${JosaUtil.pick(itemCode, '을')} 사용!`, { + format: LogFormat.PLAIN, + }); + } + for (const injured of result.injuredGenerals) { // 타겟 장수는 Draft가 아니므로 Effect 반환 effects.push(createGeneralPatchEffect(injured.patch, injured.id)); diff --git a/packages/logic/src/actions/turn/nation/che_무작위수도이전.ts b/packages/logic/src/actions/turn/nation/che_무작위수도이전.ts index bf14514..ead2811 100644 --- a/packages/logic/src/actions/turn/nation/che_무작위수도이전.ts +++ b/packages/logic/src/actions/turn/nation/che_무작위수도이전.ts @@ -116,7 +116,7 @@ export class ActionDefinition< ), // Global Action Log createLogEffect( - `${generalName}${josaYi} ${destCityName}${josaRo} 수도 이전${generalName}${josaYi} ${destCityName}${josaRo} 수도 이전하였습니다.`, { scope: LogScope.SYSTEM, category: LogCategory.ACTION, @@ -125,7 +125,7 @@ export class ActionDefinition< ), // Global History Log createLogEffect( - `【${ACTION_NAME}】${nationName}${josaYiNation} ${destCityName}${josaRo} 수도 이전【${ACTION_NAME}】${nationName}${josaYiNation} ${destCityName}${josaRo} 수도 이전하였습니다.`, { scope: LogScope.SYSTEM, category: LogCategory.HISTORY, @@ -134,7 +134,7 @@ export class ActionDefinition< ), // Actor Nation History Log createLogEffect( - `${generalName}${josaYi} ${destCityName}${josaRo} ${ACTION_NAME}${generalName}${josaYi} ${destCityName}${josaRo} ${ACTION_NAME}`, { scope: LogScope.NATION, nationId: nation.id, diff --git a/packages/logic/src/actions/turn/nation/che_물자원조.ts b/packages/logic/src/actions/turn/nation/che_물자원조.ts index 3f6ccc4..993b211 100644 --- a/packages/logic/src/actions/turn/nation/che_물자원조.ts +++ b/packages/logic/src/actions/turn/nation/che_물자원조.ts @@ -157,7 +157,7 @@ export class ActionDefinition< ), // Dest Nation History Log createLogEffect( - `${nationName}로부터 금${goldText} 쌀${riceText}${josaUlRice} 지원 받음`, + `${nationName}${JosaUtil.pick(nationName, '부터')} 금${goldText} 쌀${riceText}${josaUlRice} 지원 받음`, { scope: LogScope.NATION, nationId: destNation.id, @@ -171,6 +171,11 @@ export class ActionDefinition< category: LogCategory.ACTION, format: LogFormat.MONTH, }), + createLogEffect(`${destNation.name}${josaRo} 물자를 지원합니다.`, { + scope: LogScope.GENERAL, + category: LogCategory.ACTION, + format: LogFormat.PLAIN, + }), ]; general.experience += 5; diff --git a/packages/logic/src/actions/turn/nation/che_선전포고.ts b/packages/logic/src/actions/turn/nation/che_선전포고.ts index 7121a52..d1c994d 100644 --- a/packages/logic/src/actions/turn/nation/che_선전포고.ts +++ b/packages/logic/src/actions/turn/nation/che_선전포고.ts @@ -118,11 +118,8 @@ export class ActionDefinition< const nationName = context.nation?.name ?? '아국'; const destNationName = context.destNation.name; const generalName = context.general.name; - const josaGa = JosaUtil.pick(nationName, '가'); - const josaGaGeneral = JosaUtil.pick(generalName, '이'); - - const broadcastMessage = `${nationName}${josaGa} ${destNationName}에게 ${ACTION_NAME}하였습니다!`; - const historyMessage = `${nationName}${josaGa} ${destNationName}에게 ${ACTION_NAME}`; + const josaYiGeneral = JosaUtil.pick(generalName, '이'); + const josaYiNation = JosaUtil.pick(nationName, '이'); const effects: Array> = [ createDiplomacyPatchEffect(nationId, args.destNationId, { @@ -133,35 +130,47 @@ export class ActionDefinition< state: DIPLOMACY_DECLARE, term: DECLARE_TERM, }), - // Global Action Log - createLogEffect(broadcastMessage, { - scope: LogScope.SYSTEM, + // General Action Log + createLogEffect(`${destNationName}에 선전 포고 했습니다.`, { + scope: LogScope.GENERAL, category: LogCategory.ACTION, - format: LogFormat.PLAIN, + format: LogFormat.MONTH, }), - // Global History Log - createLogEffect(historyMessage, { - scope: LogScope.SYSTEM, + // General History Log + createLogEffect(`${destNationName}에 선전 포고`, { + scope: LogScope.GENERAL, category: LogCategory.HISTORY, format: LogFormat.YEAR_MONTH, }), // Actor Nation History Log - createLogEffect(historyMessage, { + createLogEffect(`${generalName}${josaYiGeneral} ${destNationName}에 선전 포고`, { scope: LogScope.NATION, nationId: nationId, category: LogCategory.HISTORY, format: LogFormat.YEAR_MONTH, }), // Target Nation History Log - createLogEffect(historyMessage, { + createLogEffect(`${nationName}${generalName}${josaYiGeneral} 아국에 선전 포고`, { scope: LogScope.NATION, nationId: args.destNationId, category: LogCategory.HISTORY, format: LogFormat.YEAR_MONTH, }), + // Global Action Log + createLogEffect(`${generalName}${josaYiGeneral} ${destNationName}선전 포고 하였습니다.`, { + scope: LogScope.SYSTEM, + category: LogCategory.ACTION, + format: LogFormat.PLAIN, + }), + // Global History Log + createLogEffect(`【선포】${nationName}${josaYiNation} ${destNationName}에 선전 포고 하였습니다.`, { + scope: LogScope.SYSTEM, + category: LogCategory.HISTORY, + format: LogFormat.YEAR_MONTH, + }), // National Message (국메) createLogEffect( - `【국메】${generalName}${josaGaGeneral} ${destNationName}에게 ${ACTION_NAME}하였습니다!`, + `【국메】${generalName}${josaYiGeneral} ${destNationName}에게 ${ACTION_NAME}하였습니다!`, { scope: LogScope.NATION, nationId: nationId, diff --git a/tools/compare-command-logs.mjs b/tools/compare-command-logs.mjs index 91e5f94..86a705f 100644 --- a/tools/compare-command-logs.mjs +++ b/tools/compare-command-logs.mjs @@ -19,6 +19,7 @@ Options: --include Only include command keys matching regex. --include-guards Include guard/invalid-state logs (default: excluded). --include-target Include target/broadcast logs (default: excluded). + --count-sensitive Compare duplicated template counts (default: off). --strict Compare raw templates without normalization. --keep-date Keep <1>... date markers in normalized output. --ignore-file JSON ignore list file (default: tools/compare-command-logs.ignore.json). @@ -37,6 +38,7 @@ const keepDate = args.includes('--keep-date'); const asJson = args.includes('--json'); const includeGuards = args.includes('--include-guards'); const includeTarget = args.includes('--include-target'); +const countSensitive = args.includes('--count-sensitive'); const checklist = args.includes('--checklist'); const ignoreFileIndex = args.indexOf('--ignore-file'); const ignoreFile = ignoreFileIndex >= 0 ? args[ignoreFileIndex + 1] : DEFAULT_IGNORE_FILE; @@ -568,7 +570,7 @@ const readOptionsInfo = (node) => { return { category, scope, format, hasGeneralId }; }; -const renderTsExpr = (expr, constants) => { +const renderTsExpr = (expr, resolveConst) => { if (!expr) { return '${}'; } @@ -576,8 +578,8 @@ const renderTsExpr = (expr, constants) => { return expr.text; } if (ts.isIdentifier(expr)) { - const resolved = constants?.get(expr.text); - if (resolved !== undefined) { + const resolved = resolveConst?.(expr.text); + if (resolved != null) { return resolved; } return '${}'; @@ -585,17 +587,17 @@ const renderTsExpr = (expr, constants) => { if (ts.isTemplateExpression(expr)) { let out = expr.head.text; for (const span of expr.templateSpans) { - const inlined = renderTsExpr(span.expression, constants); + const inlined = renderTsExpr(span.expression, resolveConst); out += inlined === '${}' ? '${}' : inlined; out += span.literal.text; } return out; } if (ts.isBinaryExpression(expr) && expr.operatorToken.kind === ts.SyntaxKind.PlusToken) { - return `${renderTsExpr(expr.left, constants)}${renderTsExpr(expr.right, constants)}`; + return `${renderTsExpr(expr.left, resolveConst)}${renderTsExpr(expr.right, resolveConst)}`; } if (ts.isParenthesizedExpression(expr)) { - return renderTsExpr(expr.expression, constants); + return renderTsExpr(expr.expression, resolveConst); } return '${}'; }; @@ -626,6 +628,7 @@ const extractTsLogs = (filePath, text) => { }; collectConstants(sourceFile); + const resolveConst = (name) => constants.get(name) ?? null; const visit = (node) => { if (ts.isCallExpression(node)) { @@ -644,7 +647,7 @@ const extractTsLogs = (filePath, text) => { const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); results.push({ kind: calleeName, - template: renderTsExpr(firstArg, constants), + template: renderTsExpr(firstArg, resolveConst), raw: firstArg ? firstArg.getText(sourceFile) : '', line: line + 1, category: info.category, @@ -869,6 +872,26 @@ const formatEntries = (entries) => { return lines; }; +const buildTemplateCountMap = (entries) => { + const map = new Map(); + for (const entry of entries) { + const template = normalizeTemplate(entry.template); + map.set(template, (map.get(template) ?? 0) + 1); + } + return map; +}; + +const diffTemplateCounts = (lhsCounts, rhsCounts) => { + const items = []; + for (const [template, lhsCount] of lhsCounts.entries()) { + const rhsCount = rhsCounts.get(template) ?? 0; + if (lhsCount > rhsCount) { + items.push({ template, count: lhsCount - rhsCount }); + } + } + return items; +}; + const loadPhpLogs = async () => { const files = (await collectFiles(PHP_ROOT)).filter((file) => file.endsWith('.php')); const logsByKey = new Map(); @@ -996,23 +1019,57 @@ const buildReport = (phpLogs, tsLogs, ignoreRules) => { continue; } - const phpSet = new Set(phpEntries.map((entry) => normalizeTemplate(entry.template))); - const tsSet = new Set(tsEntries.map((entry) => normalizeTemplate(entry.template))); + const rawMissingDetails = []; + const rawExtraDetails = []; + if (countSensitive) { + const phpCounts = buildTemplateCountMap(phpEntries); + const tsCounts = buildTemplateCountMap(tsEntries); + rawMissingDetails.push(...diffTemplateCounts(phpCounts, tsCounts)); + rawExtraDetails.push(...diffTemplateCounts(tsCounts, phpCounts)); + } else { + const phpSet = new Set(phpEntries.map((entry) => normalizeTemplate(entry.template))); + const tsSet = new Set(tsEntries.map((entry) => normalizeTemplate(entry.template))); + for (const template of phpSet) { + if (!tsSet.has(template)) { + rawMissingDetails.push({ template, count: 1 }); + } + } + for (const template of tsSet) { + if (!phpSet.has(template)) { + rawExtraDetails.push({ template, count: 1 }); + } + } + } - const rawMissing = [...phpSet].filter((item) => !tsSet.has(item)); - const rawExtra = [...tsSet].filter((item) => !phpSet.has(item)); - const missing = rawMissing.filter((item) => !shouldIgnoreTemplate(key, item, ignoreRules)); - const extra = rawExtra.filter((item) => !shouldIgnoreTemplate(key, item, ignoreRules)); - const ignoredMissing = rawMissing.filter((item) => !missing.includes(item)); - const ignoredExtra = rawExtra.filter((item) => !extra.includes(item)); + const missingDetails = rawMissingDetails.filter( + (item) => !shouldIgnoreTemplate(key, item.template, ignoreRules) + ); + const extraDetails = rawExtraDetails.filter((item) => !shouldIgnoreTemplate(key, item.template, ignoreRules)); + const ignoredMissingDetails = rawMissingDetails.filter( + (item) => !missingDetails.some((kept) => kept.template === item.template) + ); + const ignoredExtraDetails = rawExtraDetails.filter( + (item) => !extraDetails.some((kept) => kept.template === item.template) + ); + + const missing = missingDetails.map((item) => item.template); + const extra = extraDetails.map((item) => item.template); + const ignoredMissing = ignoredMissingDetails.map((item) => item.template); + const ignoredExtra = ignoredExtraDetails.map((item) => item.template); if (missing.length === 0 && extra.length === 0) { matches.push(key); } else { - mismatches.push({ key, phpEntries, tsEntries, missing, extra }); + mismatches.push({ key, phpEntries, tsEntries, missing, extra, missingDetails, extraDetails }); } if (ignoredMissing.length > 0 || ignoredExtra.length > 0) { - ignored.push({ key, missing: ignoredMissing, extra: ignoredExtra }); + ignored.push({ + key, + missing: ignoredMissing, + extra: ignoredExtra, + missingDetails: ignoredMissingDetails, + extraDetails: ignoredExtraDetails, + }); } } @@ -1074,7 +1131,7 @@ const main = async () => { } console.log( - `Compare command logs (mode: ${mode}, strict: ${strict ? 'on' : 'off'}, keepDate: ${keepDate ? 'on' : 'off'}, excludeGuards: ${excludeGuards ? 'on' : 'off'}, excludeTarget: ${excludeTarget ? 'on' : 'off'})` + `Compare command logs (mode: ${mode}, strict: ${strict ? 'on' : 'off'}, keepDate: ${keepDate ? 'on' : 'off'}, excludeGuards: ${excludeGuards ? 'on' : 'off'}, excludeTarget: ${excludeTarget ? 'on' : 'off'}, countSensitive: ${countSensitive ? 'on' : 'off'})` ); console.log(`PHP commands: ${report.totals.phpCommands}`); console.log(`TS commands: ${report.totals.tsCommands}`); @@ -1102,6 +1159,20 @@ const main = async () => { console.log('\nMismatch Details:'); for (const mismatch of report.mismatches) { console.log(`\n== ${mismatch.key} ==`); + if (mismatch.missingDetails.length > 0) { + console.log( + `PHP only: ${mismatch.missingDetails + .map((item) => (item.count > 1 ? `${item.template} x${item.count}` : item.template)) + .join(' | ')}` + ); + } + if (mismatch.extraDetails.length > 0) { + console.log( + `TS only: ${mismatch.extraDetails + .map((item) => (item.count > 1 ? `${item.template} x${item.count}` : item.template)) + .join(' | ')}` + ); + } const phpLines = formatEntries(mismatch.phpEntries); const tsLines = formatEntries(mismatch.tsEntries);