feat: 여러 액션의 로그 메시지 개선 및 템플릿 카운트 비교 기능 추가

This commit is contained in:
2026-02-07 07:52:00 +00:00
parent 15989e0436
commit 14da014a6f
11 changed files with 166 additions and 52 deletions
@@ -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 type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js';
import { createDiplomacyPatchEffect, createLogEffect } 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 { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js';
import { JosaUtil } from '@sammo-ts/common';
export interface NonAggressionAcceptArgs { export interface NonAggressionAcceptArgs {
destNationId: number; destNationId: number;
@@ -175,6 +176,8 @@ export class ActionDefinition<
const currentMonth = resolveMonthIndex(context.currentYear, context.currentMonth); const currentMonth = resolveMonthIndex(context.currentYear, context.currentMonth);
const targetMonth = args.year * 12 + args.month; const targetMonth = args.year * 12 + args.month;
const term = Math.max(0, targetMonth - currentMonth); const term = Math.max(0, targetMonth - currentMonth);
const destNationName = String(args.destNationId);
const josaWa = JosaUtil.pick(destNationName, '와');
return { return {
effects: [ effects: [
@@ -186,11 +189,14 @@ export class ActionDefinition<
state: DIPLOMACY_NON_AGGRESSION, state: DIPLOMACY_NON_AGGRESSION,
term, term,
}), }),
createLogEffect(`${ACTION_NAME}을 실행했습니다. (국가 ${args.destNationId})`, { createLogEffect(
`<D><b>${destNationName}</b></>${josaWa} <C>${args.year}</>년 <C>${args.month}</>월까지 불가침에 성공했습니다.`,
{
scope: LogScope.GENERAL, scope: LogScope.GENERAL,
category: LogCategory.ACTION, category: LogCategory.ACTION,
format: LogFormat.MONTH, format: LogFormat.MONTH,
}), }
),
], ],
}; };
} }
@@ -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 type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js';
import { createDiplomacyPatchEffect, createLogEffect } 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 { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js';
import { JosaUtil } from '@sammo-ts/common';
export interface NonAggressionCancelAcceptArgs { export interface NonAggressionCancelAcceptArgs {
destNationId: number; destNationId: number;
@@ -81,6 +82,9 @@ export class ActionDefinition<
}; };
} }
const destNationName = String(args.destNationId);
const josaWa = JosaUtil.pick(destNationName, '와');
return { return {
effects: [ effects: [
createDiplomacyPatchEffect(nationId, args.destNationId, { createDiplomacyPatchEffect(nationId, args.destNationId, {
@@ -91,7 +95,7 @@ export class ActionDefinition<
state: DIPLOMACY_NEUTRAL, state: DIPLOMACY_NEUTRAL,
term: 0, term: 0,
}), }),
createLogEffect(`${ACTION_NAME}을 실행했습니다. (국가 ${args.destNationId})`, { createLogEffect(`<D><b>${destNationName}</b></>${josaWa}의 불가침을 파기했습니다.`, {
scope: LogScope.GENERAL, scope: LogScope.GENERAL,
category: LogCategory.ACTION, category: LogCategory.ACTION,
format: LogFormat.MONTH, format: LogFormat.MONTH,
@@ -88,9 +88,9 @@ export class ActionDefinition<
general.gold = Math.max(0, general.gold - sellAmount); general.gold = Math.max(0, general.gold - sellAmount);
general.rice += buyAmount; general.rice += buyAmount;
context.addLog( context.addLog(
`군량 ${Math.round(buyAmount).toLocaleString()}을 사서 자금 ${Math.round( `군량 <C>${Math.round(buyAmount).toLocaleString()}</>을 사서 자금 <C>${Math.round(
sellAmount sellAmount
).toLocaleString()}을 썼습니다.`, ).toLocaleString()}</>을 썼습니다.`,
{ format: LogFormat.PLAIN } { format: LogFormat.PLAIN }
); );
} else { } else {
@@ -101,9 +101,9 @@ export class ActionDefinition<
general.rice = Math.max(0, general.rice - sellAmount); general.rice = Math.max(0, general.rice - sellAmount);
general.gold += buyAmount; general.gold += buyAmount;
context.addLog( context.addLog(
`군량 ${Math.round(sellAmount).toLocaleString()}을 팔아 자금 ${Math.round( `군량 <C>${Math.round(sellAmount).toLocaleString()}</>을 팔아 자금 <C>${Math.round(
buyAmount buyAmount
).toLocaleString()}을 얻었습니다.`, ).toLocaleString()}</>을 얻었습니다.`,
{ format: LogFormat.PLAIN } { format: LogFormat.PLAIN }
); );
} }
@@ -18,7 +18,7 @@ import type {
GeneralActionEffect, GeneralActionEffect,
} from '@sammo-ts/logic/actions/engine.js'; } from '@sammo-ts/logic/actions/engine.js';
import { createGeneralPatchEffect, createNationPatchEffect } 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 { JosaUtil } from '@sammo-ts/common';
import { z } from 'zod'; import { z } from 'zod';
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
@@ -76,6 +76,7 @@ export class ActionResolver<
// Global Log // Global Log
context.addLog(`<Y>${generalName}</>${josaYi} <D><b>${destNationName}</b></>${josaRo} <S>망명</>하였습니다.`, { context.addLog(`<Y>${generalName}</>${josaYi} <D><b>${destNationName}</b></>${josaRo} <S>망명</>하였습니다.`, {
scope: LogScope.SYSTEM,
category: LogCategory.ACTION, category: LogCategory.ACTION,
format: LogFormat.PLAIN, format: LogFormat.PLAIN,
}); });
@@ -72,6 +72,7 @@ export class ActionResolver<
const actualSecuDmg = destCity.security - newSecu; const actualSecuDmg = destCity.security - newSecu;
const actualTrustDmg = currentTrust - newTrust; const actualTrustDmg = currentTrust - newTrust;
const injuryCount = 0;
// Log // Log
const commandName = ACTION_NAME; const commandName = ACTION_NAME;
@@ -80,10 +81,15 @@ export class ActionResolver<
category: LogCategory.ACTION, category: LogCategory.ACTION,
format: LogFormat.MONTH, format: LogFormat.MONTH,
}); });
ctx.addLog(`치안이 ${actualSecuDmg}, 민심이 ${actualTrustDmg.toFixed(1)} 만큼 감소했습니다.`, { ctx.addLog(
`도시의 치안이 <C>${actualSecuDmg}</>, 민심이 <C>${actualTrustDmg.toFixed(
1
)}</>만큼 감소하고, 장수 <C>${injuryCount}</>명이 부상 당했습니다.`,
{
category: LogCategory.ACTION, category: LogCategory.ACTION,
format: LogFormat.PLAIN, format: LogFormat.PLAIN,
}); }
);
// City Update // City Update
effects.push( effects.push(
@@ -72,6 +72,7 @@ export class ActionResolver<
const actualDefDmg = destCity.defence - newDef; const actualDefDmg = destCity.defence - newDef;
const actualWallDmg = destCity.wall - newWall; const actualWallDmg = destCity.wall - newWall;
const injuryCount = 0;
// Log // Log
const commandName = ACTION_NAME; const commandName = ACTION_NAME;
@@ -80,10 +81,13 @@ export class ActionResolver<
category: LogCategory.ACTION, category: LogCategory.ACTION,
format: LogFormat.MONTH, format: LogFormat.MONTH,
}); });
ctx.addLog(`수비가 ${actualDefDmg}, 성벽이 ${actualWallDmg} 만큼 감소했습니다.`, { ctx.addLog(
category: LogCategory.ACTION, `도시의 수비가 <C>${actualDefDmg}</>, 성벽이 <C>${actualWallDmg}</>만큼 감소하고, 장수 <C>${injuryCount}</>명이 부상 당했습니다.`,
format: LogFormat.PLAIN, {
}); category: LogCategory.ACTION,
format: LogFormat.PLAIN,
}
);
// City Update // City Update
effects.push( effects.push(
@@ -325,7 +325,8 @@ export class ActionResolver<
) )
); );
context.addLog(`<G><b>${context.destCity.name}</b></>이 불타고 있습니다.`, { const destCityName = context.destCity.name;
context.addLog(`<G><b>${destCityName}</b></>${JosaUtil.pick(destCityName, '이')} 불타고 있습니다.`, {
scope: LogScope.SYSTEM, scope: LogScope.SYSTEM,
category: LogCategory.SUMMARY, category: LogCategory.SUMMARY,
format: LogFormat.MONTH, 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(`<C>${itemCode}</>${JosaUtil.pick(itemCode, '을')} 사용!`, {
format: LogFormat.PLAIN,
});
}
for (const injured of result.injuredGenerals) { for (const injured of result.injuredGenerals) {
// 타겟 장수는 Draft가 아니므로 Effect 반환 // 타겟 장수는 Draft가 아니므로 Effect 반환
effects.push(createGeneralPatchEffect(injured.patch, injured.id)); effects.push(createGeneralPatchEffect(injured.patch, injured.id));
@@ -116,7 +116,7 @@ export class ActionDefinition<
), ),
// Global Action Log // Global Action Log
createLogEffect( createLogEffect(
`<Y>${generalName}</>${josaYi} <G><b>${destCityName}</b></>${josaRo} <M>수도 이전</}하였습니다.`, `<Y>${generalName}</>${josaYi} <G><b>${destCityName}</b></>${josaRo} <M>수도 이전</>하였습니다.`,
{ {
scope: LogScope.SYSTEM, scope: LogScope.SYSTEM,
category: LogCategory.ACTION, category: LogCategory.ACTION,
@@ -125,7 +125,7 @@ export class ActionDefinition<
), ),
// Global History Log // Global History Log
createLogEffect( createLogEffect(
`<S><b>【${ACTION_NAME}】</b></><D><b>${nationName}</b></>${josaYiNation} <G><b>${destCityName}</b></>${josaRo} <M>수도 이전</}하였습니다.`, `<S><b>【${ACTION_NAME}】</b></><D><b>${nationName}</b></>${josaYiNation} <G><b>${destCityName}</b></>${josaRo} <M>수도 이전</>하였습니다.`,
{ {
scope: LogScope.SYSTEM, scope: LogScope.SYSTEM,
category: LogCategory.HISTORY, category: LogCategory.HISTORY,
@@ -134,7 +134,7 @@ export class ActionDefinition<
), ),
// Actor Nation History Log // Actor Nation History Log
createLogEffect( createLogEffect(
`<Y>${generalName}</>${josaYi} <G><b>${destCityName}</b></>${josaRo} <M>${ACTION_NAME}</} `, `<Y>${generalName}</>${josaYi} <G><b>${destCityName}</b></>${josaRo} <M>${ACTION_NAME}</>`,
{ {
scope: LogScope.NATION, scope: LogScope.NATION,
nationId: nation.id, nationId: nation.id,
@@ -157,7 +157,7 @@ export class ActionDefinition<
), ),
// Dest Nation History Log // Dest Nation History Log
createLogEffect( createLogEffect(
`<D><b>${nationName}</b></>로부터 금<C>${goldText}</> 쌀<C>${riceText}</>${josaUlRice} 지원 받음`, `<D><b>${nationName}</b></>${JosaUtil.pick(nationName, '부터')} 금<C>${goldText}</> 쌀<C>${riceText}</>${josaUlRice} 지원 받음`,
{ {
scope: LogScope.NATION, scope: LogScope.NATION,
nationId: destNation.id, nationId: destNation.id,
@@ -171,6 +171,11 @@ export class ActionDefinition<
category: LogCategory.ACTION, category: LogCategory.ACTION,
format: LogFormat.MONTH, format: LogFormat.MONTH,
}), }),
createLogEffect(`<D><b>${destNation.name}</b></>${josaRo} 물자를 지원합니다.`, {
scope: LogScope.GENERAL,
category: LogCategory.ACTION,
format: LogFormat.PLAIN,
}),
]; ];
general.experience += 5; general.experience += 5;
@@ -118,11 +118,8 @@ export class ActionDefinition<
const nationName = context.nation?.name ?? '아국'; const nationName = context.nation?.name ?? '아국';
const destNationName = context.destNation.name; const destNationName = context.destNation.name;
const generalName = context.general.name; const generalName = context.general.name;
const josaGa = JosaUtil.pick(nationName, ''); const josaYiGeneral = JosaUtil.pick(generalName, '');
const josaGaGeneral = JosaUtil.pick(generalName, '이'); const josaYiNation = JosaUtil.pick(nationName, '이');
const broadcastMessage = `<Y>${nationName}</>${josaGa} <Y>${destNationName}</>에게 <R>${ACTION_NAME}</>하였습니다!`;
const historyMessage = `<Y>${nationName}</>${josaGa} <Y>${destNationName}</>에게 <R>${ACTION_NAME}</>`;
const effects: Array<GeneralActionEffect<TriggerState>> = [ const effects: Array<GeneralActionEffect<TriggerState>> = [
createDiplomacyPatchEffect(nationId, args.destNationId, { createDiplomacyPatchEffect(nationId, args.destNationId, {
@@ -133,35 +130,47 @@ export class ActionDefinition<
state: DIPLOMACY_DECLARE, state: DIPLOMACY_DECLARE,
term: DECLARE_TERM, term: DECLARE_TERM,
}), }),
// Global Action Log // General Action Log
createLogEffect(broadcastMessage, { createLogEffect(`<D><b>${destNationName}</b></>에 선전 포고 했습니다.`, {
scope: LogScope.SYSTEM, scope: LogScope.GENERAL,
category: LogCategory.ACTION, category: LogCategory.ACTION,
format: LogFormat.PLAIN, format: LogFormat.MONTH,
}), }),
// Global History Log // General History Log
createLogEffect(historyMessage, { createLogEffect(`<D><b>${destNationName}</b></>에 선전 포고`, {
scope: LogScope.SYSTEM, scope: LogScope.GENERAL,
category: LogCategory.HISTORY, category: LogCategory.HISTORY,
format: LogFormat.YEAR_MONTH, format: LogFormat.YEAR_MONTH,
}), }),
// Actor Nation History Log // Actor Nation History Log
createLogEffect(historyMessage, { createLogEffect(`<Y>${generalName}</>${josaYiGeneral} <D><b>${destNationName}</b></>에 선전 포고`, {
scope: LogScope.NATION, scope: LogScope.NATION,
nationId: nationId, nationId: nationId,
category: LogCategory.HISTORY, category: LogCategory.HISTORY,
format: LogFormat.YEAR_MONTH, format: LogFormat.YEAR_MONTH,
}), }),
// Target Nation History Log // Target Nation History Log
createLogEffect(historyMessage, { createLogEffect(`<D><b>${nationName}</b></>의 <Y>${generalName}</>${josaYiGeneral} 아국에 선전 포고`, {
scope: LogScope.NATION, scope: LogScope.NATION,
nationId: args.destNationId, nationId: args.destNationId,
category: LogCategory.HISTORY, category: LogCategory.HISTORY,
format: LogFormat.YEAR_MONTH, format: LogFormat.YEAR_MONTH,
}), }),
// Global Action Log
createLogEffect(`<Y>${generalName}</>${josaYiGeneral} <D><b>${destNationName}</b></>에 <M>선전 포고</> 하였습니다.`, {
scope: LogScope.SYSTEM,
category: LogCategory.ACTION,
format: LogFormat.PLAIN,
}),
// Global History Log
createLogEffect(`<R><b>【선포】</b></><D><b>${nationName}</b></>${josaYiNation} <D><b>${destNationName}</b></>에 선전 포고 하였습니다.`, {
scope: LogScope.SYSTEM,
category: LogCategory.HISTORY,
format: LogFormat.YEAR_MONTH,
}),
// National Message (국메) // National Message (국메)
createLogEffect( createLogEffect(
`【국메】<Y>${generalName}</>${josaGaGeneral} <Y>${destNationName}</>에게 <R>${ACTION_NAME}</>하였습니다!`, `【국메】<Y>${generalName}</>${josaYiGeneral} <Y>${destNationName}</>에게 <R>${ACTION_NAME}</>하였습니다!`,
{ {
scope: LogScope.NATION, scope: LogScope.NATION,
nationId: nationId, nationId: nationId,
+89 -18
View File
@@ -19,6 +19,7 @@ Options:
--include <regex> Only include command keys matching regex. --include <regex> Only include command keys matching regex.
--include-guards Include guard/invalid-state logs (default: excluded). --include-guards Include guard/invalid-state logs (default: excluded).
--include-target Include target/broadcast 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. --strict Compare raw templates without normalization.
--keep-date Keep <1>...</> date markers in normalized output. --keep-date Keep <1>...</> date markers in normalized output.
--ignore-file <path> JSON ignore list file (default: tools/compare-command-logs.ignore.json). --ignore-file <path> 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 asJson = args.includes('--json');
const includeGuards = args.includes('--include-guards'); const includeGuards = args.includes('--include-guards');
const includeTarget = args.includes('--include-target'); const includeTarget = args.includes('--include-target');
const countSensitive = args.includes('--count-sensitive');
const checklist = args.includes('--checklist'); const checklist = args.includes('--checklist');
const ignoreFileIndex = args.indexOf('--ignore-file'); const ignoreFileIndex = args.indexOf('--ignore-file');
const ignoreFile = ignoreFileIndex >= 0 ? args[ignoreFileIndex + 1] : DEFAULT_IGNORE_FILE; const ignoreFile = ignoreFileIndex >= 0 ? args[ignoreFileIndex + 1] : DEFAULT_IGNORE_FILE;
@@ -568,7 +570,7 @@ const readOptionsInfo = (node) => {
return { category, scope, format, hasGeneralId }; return { category, scope, format, hasGeneralId };
}; };
const renderTsExpr = (expr, constants) => { const renderTsExpr = (expr, resolveConst) => {
if (!expr) { if (!expr) {
return '${}'; return '${}';
} }
@@ -576,8 +578,8 @@ const renderTsExpr = (expr, constants) => {
return expr.text; return expr.text;
} }
if (ts.isIdentifier(expr)) { if (ts.isIdentifier(expr)) {
const resolved = constants?.get(expr.text); const resolved = resolveConst?.(expr.text);
if (resolved !== undefined) { if (resolved != null) {
return resolved; return resolved;
} }
return '${}'; return '${}';
@@ -585,17 +587,17 @@ const renderTsExpr = (expr, constants) => {
if (ts.isTemplateExpression(expr)) { if (ts.isTemplateExpression(expr)) {
let out = expr.head.text; let out = expr.head.text;
for (const span of expr.templateSpans) { for (const span of expr.templateSpans) {
const inlined = renderTsExpr(span.expression, constants); const inlined = renderTsExpr(span.expression, resolveConst);
out += inlined === '${}' ? '${}' : inlined; out += inlined === '${}' ? '${}' : inlined;
out += span.literal.text; out += span.literal.text;
} }
return out; return out;
} }
if (ts.isBinaryExpression(expr) && expr.operatorToken.kind === ts.SyntaxKind.PlusToken) { 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)) { if (ts.isParenthesizedExpression(expr)) {
return renderTsExpr(expr.expression, constants); return renderTsExpr(expr.expression, resolveConst);
} }
return '${}'; return '${}';
}; };
@@ -626,6 +628,7 @@ const extractTsLogs = (filePath, text) => {
}; };
collectConstants(sourceFile); collectConstants(sourceFile);
const resolveConst = (name) => constants.get(name) ?? null;
const visit = (node) => { const visit = (node) => {
if (ts.isCallExpression(node)) { if (ts.isCallExpression(node)) {
@@ -644,7 +647,7 @@ const extractTsLogs = (filePath, text) => {
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
results.push({ results.push({
kind: calleeName, kind: calleeName,
template: renderTsExpr(firstArg, constants), template: renderTsExpr(firstArg, resolveConst),
raw: firstArg ? firstArg.getText(sourceFile) : '', raw: firstArg ? firstArg.getText(sourceFile) : '',
line: line + 1, line: line + 1,
category: info.category, category: info.category,
@@ -869,6 +872,26 @@ const formatEntries = (entries) => {
return lines; 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 loadPhpLogs = async () => {
const files = (await collectFiles(PHP_ROOT)).filter((file) => file.endsWith('.php')); const files = (await collectFiles(PHP_ROOT)).filter((file) => file.endsWith('.php'));
const logsByKey = new Map(); const logsByKey = new Map();
@@ -996,23 +1019,57 @@ const buildReport = (phpLogs, tsLogs, ignoreRules) => {
continue; continue;
} }
const phpSet = new Set(phpEntries.map((entry) => normalizeTemplate(entry.template))); const rawMissingDetails = [];
const tsSet = new Set(tsEntries.map((entry) => normalizeTemplate(entry.template))); 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 missingDetails = rawMissingDetails.filter(
const rawExtra = [...tsSet].filter((item) => !phpSet.has(item)); (item) => !shouldIgnoreTemplate(key, item.template, ignoreRules)
const missing = rawMissing.filter((item) => !shouldIgnoreTemplate(key, item, ignoreRules)); );
const extra = rawExtra.filter((item) => !shouldIgnoreTemplate(key, item, ignoreRules)); const extraDetails = rawExtraDetails.filter((item) => !shouldIgnoreTemplate(key, item.template, ignoreRules));
const ignoredMissing = rawMissing.filter((item) => !missing.includes(item)); const ignoredMissingDetails = rawMissingDetails.filter(
const ignoredExtra = rawExtra.filter((item) => !extra.includes(item)); (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) { if (missing.length === 0 && extra.length === 0) {
matches.push(key); matches.push(key);
} else { } else {
mismatches.push({ key, phpEntries, tsEntries, missing, extra }); mismatches.push({ key, phpEntries, tsEntries, missing, extra, missingDetails, extraDetails });
} }
if (ignoredMissing.length > 0 || ignoredExtra.length > 0) { 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( 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(`PHP commands: ${report.totals.phpCommands}`);
console.log(`TS commands: ${report.totals.tsCommands}`); console.log(`TS commands: ${report.totals.tsCommands}`);
@@ -1102,6 +1159,20 @@ const main = async () => {
console.log('\nMismatch Details:'); console.log('\nMismatch Details:');
for (const mismatch of report.mismatches) { for (const mismatch of report.mismatches) {
console.log(`\n== ${mismatch.key} ==`); 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 phpLines = formatEntries(mismatch.phpEntries);
const tsLines = formatEntries(mismatch.tsEntries); const tsLines = formatEntries(mismatch.tsEntries);