From 1b071d1c053ca3d0014f8dbbf00d5a145d3e8a20 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Tue, 1 Jul 2025 14:23:38 +0200 Subject: [PATCH 1/3] feat(logger): add codemode name --- recipes/correct-ts-specifiers/src/workflow.ts | 3 ++ .../src/re-work.ts | 0 utils/src/logger.test.snap.cjs | 8 ++- utils/src/logger.test.ts | 36 +++++++++++++- utils/src/logger.ts | 49 +++++++++++-------- 5 files changed, 72 insertions(+), 24 deletions(-) create mode 100644 recipes/import-assertions-to-attributes/src/re-work.ts diff --git a/recipes/correct-ts-specifiers/src/workflow.ts b/recipes/correct-ts-specifiers/src/workflow.ts index 436eff80..129476cf 100644 --- a/recipes/correct-ts-specifiers/src/workflow.ts +++ b/recipes/correct-ts-specifiers/src/workflow.ts @@ -2,12 +2,15 @@ import module from 'node:module'; import { type Api, api } from '@codemod.com/workflow'; import type { Helpers } from '@codemod.com/workflow/dist/jsFam.d.ts'; +import { setCodemodName } from '@nodejs/codemod-utils/logger'; import { mapImports } from './map-imports.ts'; import type { FSAbsolutePath } from './index.d.ts'; import * as aliasLoader from '@nodejs-loaders/alias/alias.loader.mjs'; +setCodemodName('correct-ts-specifiers'); + module.registerHooks(aliasLoader); export async function workflow({ contexts, files }: Api) { diff --git a/recipes/import-assertions-to-attributes/src/re-work.ts b/recipes/import-assertions-to-attributes/src/re-work.ts new file mode 100644 index 00000000..e69de29b diff --git a/utils/src/logger.test.snap.cjs b/utils/src/logger.test.snap.cjs index 2ccff3c1..8200e0d7 100644 --- a/utils/src/logger.test.snap.cjs +++ b/utils/src/logger.test.snap.cjs @@ -1,7 +1,11 @@ exports[`logger > should emit error entries to standard error, collated by source module 1`] = ` -" • sh*t happened\\n • maybe bad\\n • sh*t happened\\n • maybe other bad\\n[Codemod: correct-ts-specifiers]: migration incomplete!\\n" +" • sh*t happened\\n • maybe bad\\n • sh*t happened\\n • maybe other bad\\n[Codemod: test-codemod]: migration incomplete!\\n" `; exports[`logger > should emit non-error entries to standard out, collated by source module 1`] = ` -"[Codemod: correct-ts-specifiers]: /tmp/foo.js\\n • maybe don’t\\n • maybe not that either\\n • still maybe don’t\\n • more maybe not\\n[Codemod: correct-ts-specifiers]: migration complete!\\n" +"[Codemod: test-codemod]: /tmp/foo.js\\n • maybe don’t\\n • maybe not that either\\n • still maybe don’t\\n • more maybe not\\n[Codemod: test-codemod]: migration complete!\\n" +`; + +exports[`logger > should work without a codemod name 1`] = ` +"[Codemod: nodjs-codemod]: /tmp/foo.js\\n • maybe don’t\\n • maybe not that either\\n • still maybe don’t\\n • more maybe not\\n[Codemod: nodjs-codemod]: migration complete!\\n" `; diff --git a/utils/src/logger.test.ts b/utils/src/logger.test.ts index 6e08dcd9..d32f1a73 100644 --- a/utils/src/logger.test.ts +++ b/utils/src/logger.test.ts @@ -13,7 +13,9 @@ describe('logger', { concurrency: true }, () => { '--experimental-strip-types', '-e', dedent` - import { logger } from './logger.ts'; + import { logger, setCodemodName } from './logger.ts'; + + setCodemodName('test-codemod'); const source1 = '/tmp/foo.js'; logger(source1, 'log', 'maybe don’t'); @@ -41,7 +43,9 @@ describe('logger', { concurrency: true }, () => { '--experimental-strip-types', '-e', dedent` - import { logger } from './logger.ts'; + import { logger, setCodemodName } from './logger.ts'; + + setCodemodName('test-codemod'); const source1 = '/tmp/foo.js'; logger(source1, 'error', 'sh*t happened'); @@ -60,4 +64,32 @@ describe('logger', { concurrency: true }, () => { t.assert.snapshot(stderr); assert.equal(code, 1); }); + + it('should work without a codemod name', async (t) => { + const { code, stdout } = await spawnPromisified( + execPath, + [ + '--no-warnings', + '--experimental-strip-types', + '-e', + dedent` + import { logger } from './logger.ts'; + + const source1 = '/tmp/foo.js'; + logger(source1, 'log', 'maybe don’t'); + logger(source1, 'log', 'maybe not that either'); + + const source2 = '/tmp/foo.js'; + logger(source2, 'log', 'still maybe don’t'); + logger(source2, 'log', 'more maybe not'); + `, + ], + { + cwd: import.meta.dirname, + }, + ); + + t.assert.snapshot(stdout); + assert.equal(code, 0); + }); }); diff --git a/utils/src/logger.ts b/utils/src/logger.ts index 4ca042a3..242e5cd3 100644 --- a/utils/src/logger.ts +++ b/utils/src/logger.ts @@ -5,14 +5,23 @@ type LogType = 'error' | 'log' | 'warn'; type FileLog = { msg: LogMsg; type: LogType }; type Source = URL['pathname']; +let codemodName = 'nodjs-codemod'; + +/** + * Set the codemod name for logging output + */ +export const setCodemodName = (name: string) => { + codemodName = name; +}; + /** * Collect log entries and report them at the end, collated by source module. */ export const logger = (source: Source, type: LogType, msg: LogMsg) => { - const fileLog = new Set(logs.has(source) ? logs.get(source) : []); + const fileLog = new Set(logs.has(source) ? logs.get(source) : []); - fileLog.add({ msg, type }); - logs.set(source, fileLog); + fileLog.add({ msg, type }); + logs.set(source, fileLog); }; /** @@ -23,21 +32,21 @@ const logs = new Map>(); process.once('beforeExit', emitLogs); function emitLogs() { - let hasError = false; - - for (const [sourceFile, fileLog] of logs.entries()) { - console.log('[Codemod: correct-ts-specifiers]:', sourceFile); - for (const { msg, type } of fileLog) { - console[type](' •', msg); - if (type === 'error') hasError = true; - } - } - - if (hasError) { - console.error('[Codemod: correct-ts-specifiers]: migration incomplete!'); - process.exitCode = 1; - } else { - process.exitCode = 0; - console.log('[Codemod: correct-ts-specifiers]: migration complete!'); - } + let hasError = false; + + for (const [sourceFile, fileLog] of logs.entries()) { + console.log(`[Codemod: ${codemodName}]:`, sourceFile); + for (const { msg, type } of fileLog) { + console[type](' •', msg); + if (type === 'error') hasError = true; + } + } + + if (hasError) { + console.error(`[Codemod: ${codemodName}]: migration incomplete!`); + process.exitCode = 1; + } else { + process.exitCode = 0; + console.log(`[Codemod: ${codemodName}]: migration complete!`); + } } From d82feef62280e19a6fefae0c5695bcb7fc3d137e Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:15:19 +0200 Subject: [PATCH 2/3] multiple codemod same time --- utils/src/logger.test.ts | 34 ++++++++++++++++++++++ utils/src/logger.ts | 61 +++++++++++++++++++++++++++++----------- 2 files changed, 78 insertions(+), 17 deletions(-) diff --git a/utils/src/logger.test.ts b/utils/src/logger.test.ts index d32f1a73..ada9f6a6 100644 --- a/utils/src/logger.test.ts +++ b/utils/src/logger.test.ts @@ -92,4 +92,38 @@ describe('logger', { concurrency: true }, () => { t.assert.snapshot(stdout); assert.equal(code, 0); }); + + it('should handle multiple codemods with different names correctly', async () => { + const { code, stdout } = await spawnPromisified( + execPath, + [ + '--no-warnings', + '--experimental-strip-types', + '-e', + dedent` + import { logger, setCodemodName } from './logger.ts'; + + // Simulate first codemod + setCodemodName('codemod-a'); + logger('/tmp/file1.js', 'log', 'Message from codemod A'); + + // Simulate second codemod (this would previously overwrite the name) + logger('/tmp/file2.js', 'log', 'Message from codemod B', 'codemod-b'); + + // Another message from first codemod (should still show as codemod-a) + logger('/tmp/file3.js', 'log', 'Another message from codemod A'); + `, + ], + { + cwd: import.meta.dirname, + }, + ); + + // Should show both codemod names in output + assert(stdout.includes('[Codemod: codemod-a]')); + assert(stdout.includes('[Codemod: codemod-b]')); + assert(stdout.includes('Message from codemod A')); + assert(stdout.includes('Message from codemod B')); + assert.equal(code, 0); + }); }); diff --git a/utils/src/logger.ts b/utils/src/logger.ts index 242e5cd3..15766ff5 100644 --- a/utils/src/logger.ts +++ b/utils/src/logger.ts @@ -2,25 +2,26 @@ import process from 'node:process'; type LogMsg = string; type LogType = 'error' | 'log' | 'warn'; -type FileLog = { msg: LogMsg; type: LogType }; +type FileLog = { msg: LogMsg; type: LogType; codemodName: string }; type Source = URL['pathname']; -let codemodName = 'nodjs-codemod'; +let defaultCodemodName = 'nodjs-codemod'; /** - * Set the codemod name for logging output + * Set the default codemod name for logging output */ export const setCodemodName = (name: string) => { - codemodName = name; + defaultCodemodName = name; }; /** * Collect log entries and report them at the end, collated by source module. */ -export const logger = (source: Source, type: LogType, msg: LogMsg) => { +export const logger = (source: Source, type: LogType, msg: LogMsg, codemodName?: string) => { + const name = codemodName ?? defaultCodemodName; const fileLog = new Set(logs.has(source) ? logs.get(source) : []); - fileLog.add({ msg, type }); + fileLog.add({ msg, type, codemodName: name }); logs.set(source, fileLog); }; @@ -34,19 +35,45 @@ process.once('beforeExit', emitLogs); function emitLogs() { let hasError = false; - for (const [sourceFile, fileLog] of logs.entries()) { - console.log(`[Codemod: ${codemodName}]:`, sourceFile); - for (const { msg, type } of fileLog) { - console[type](' •', msg); - if (type === 'error') hasError = true; + // Group logs by codemod name first, then by source file + const logsByCodemod = new Map>>(); + + for (const [sourceFile, fileLogs] of logs.entries()) { + for (const fileLog of fileLogs) { + if (!logsByCodemod.has(fileLog.codemodName)) { + logsByCodemod.set(fileLog.codemodName, new Map()); + } + const codemodLogs = logsByCodemod.get(fileLog.codemodName)!; + if (!codemodLogs.has(sourceFile)) { + codemodLogs.set(sourceFile, new Set()); + } + codemodLogs.get(sourceFile)!.add(fileLog); } } - if (hasError) { - console.error(`[Codemod: ${codemodName}]: migration incomplete!`); - process.exitCode = 1; - } else { - process.exitCode = 0; - console.log(`[Codemod: ${codemodName}]: migration complete!`); + for (const [codemodName, codemodLogs] of logsByCodemod.entries()) { + for (const [sourceFile, fileLogs] of codemodLogs.entries()) { + console.log(`[Codemod: ${codemodName}]:`, sourceFile); + for (const { msg, type } of fileLogs) { + console[type](' •', msg); + if (type === 'error') hasError = true; + } + } + + if (hasError) { + console.error(`[Codemod: ${codemodName}]: migration incomplete!`); + } else { + console.log(`[Codemod: ${codemodName}]: migration complete!`); + } + hasError = false; // Reset for next codemod } + + // Set overall exit code based on any errors + process.exitCode = Array.from(logsByCodemod.values()) + .some(codemodLogs => + Array.from(codemodLogs.values()) + .some(fileLogs => + Array.from(fileLogs).some(log => log.type === 'error') + ) + ) ? 1 : 0; } From b4ff8359475c251c5eb4d38bca175f4a6fd83529 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Tue, 12 Aug 2025 15:11:16 +0200 Subject: [PATCH 3/3] Delete re-work.ts --- recipes/import-assertions-to-attributes/src/re-work.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 recipes/import-assertions-to-attributes/src/re-work.ts diff --git a/recipes/import-assertions-to-attributes/src/re-work.ts b/recipes/import-assertions-to-attributes/src/re-work.ts deleted file mode 100644 index e69de29b..00000000