From ddb46646fae88fb2804d5f4a059d181e4ebabb56 Mon Sep 17 00:00:00 2001 From: Girish Ramakrishnan Date: Sat, 14 Feb 2026 15:12:07 +0100 Subject: [PATCH] remove esm migration files --- .../cjs-to-esm/check-import-mismatches.mjs | 96 ----- scripts/cjs-to-esm/cjs-to-esm.mjs | 355 ------------------ scripts/cjs-to-esm/fix-consumer-imports.mjs | 66 ---- scripts/cjs-to-esm/fix-exports-self-ref.mjs | 177 --------- scripts/cjs-to-esm/fix-exports.mjs | 103 ----- scripts/cjs-to-esm/fix-imports-for-named.mjs | 66 ---- scripts/cjs-to-esm/fix-remaining-exports.mjs | 129 ------- scripts/cjs-to-esm/revert-and-fix.mjs | 60 --- 8 files changed, 1052 deletions(-) delete mode 100644 scripts/cjs-to-esm/check-import-mismatches.mjs delete mode 100644 scripts/cjs-to-esm/cjs-to-esm.mjs delete mode 100644 scripts/cjs-to-esm/fix-consumer-imports.mjs delete mode 100644 scripts/cjs-to-esm/fix-exports-self-ref.mjs delete mode 100644 scripts/cjs-to-esm/fix-exports.mjs delete mode 100644 scripts/cjs-to-esm/fix-imports-for-named.mjs delete mode 100644 scripts/cjs-to-esm/fix-remaining-exports.mjs delete mode 100644 scripts/cjs-to-esm/revert-and-fix.mjs diff --git a/scripts/cjs-to-esm/check-import-mismatches.mjs b/scripts/cjs-to-esm/check-import-mismatches.mjs deleted file mode 100644 index 49c7e6d8f..000000000 --- a/scripts/cjs-to-esm/check-import-mismatches.mjs +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env node - -// Check for import/export mismatches: -// 1. Files using `import X from './file.js'` where file.js has no default export -// 2. Files using `import * as X from './file.js'` where file.js ONLY has default export - -import fs from 'node:fs'; -import path from 'node:path'; - -const ROOT = process.cwd(); - -function walk(dir) { - const result = []; - for (const e of fs.readdirSync(dir, { withFileTypes: true })) { - const p = path.join(dir, e.name); - if (e.isDirectory()) result.push(...walk(p)); - else if (e.name.endsWith('.js')) result.push(p); - } - return result; -} - -const srcFiles = walk(path.join(ROOT, 'src')); -srcFiles.push(path.join(ROOT, 'syslog.js')); - -// Build export map -const exportMap = {}; // file -> 'default' | 'named' | 'both' | 'none' -for (const file of srcFiles) { - const content = fs.readFileSync(file, 'utf-8'); - const hasDefault = /^export default /m.test(content); - const hasNamed = /^export \{/m.test(content) || /^export (const|let|var|function|class) /m.test(content); - - if (hasDefault && hasNamed) exportMap[file] = 'both'; - else if (hasDefault) exportMap[file] = 'default'; - else if (hasNamed) exportMap[file] = 'named'; - else exportMap[file] = 'none'; -} - -// Check imports -let mismatches = 0; -const fixes = []; // { file, line, from, to } - -for (const file of srcFiles) { - const content = fs.readFileSync(file, 'utf-8'); - const lines = content.split('\n'); - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - - // Default import of local file - let m = line.match(/^import\s+(\w+)\s+from\s+'(\.[^']+\.js)';$/); - if (m) { - const [, name, importPath] = m; - const resolved = path.resolve(path.dirname(file), importPath); - const exportType = exportMap[resolved]; - if (exportType === 'named') { - console.log(`MISMATCH: ${path.relative(ROOT, file)}:${i+1} - import ${name} from '${importPath}' but ${path.relative(ROOT, resolved)} has only named exports`); - fixes.push({ file, lineNum: i, old: line, new: line.replace(`import ${name}`, `import * as ${name}`) }); - mismatches++; - } - } - - // Namespace import of local file - m = line.match(/^import\s+\*\s+as\s+(\w+)\s+from\s+'(\.[^']+\.js)';$/); - if (m) { - const [, name, importPath] = m; - const resolved = path.resolve(path.dirname(file), importPath); - const exportType = exportMap[resolved]; - if (exportType === 'default') { - console.log(`MISMATCH: ${path.relative(ROOT, file)}:${i+1} - import * as ${name} from '${importPath}' but ${path.relative(ROOT, resolved)} has only default export`); - fixes.push({ file, lineNum: i, old: line, new: line.replace(`import * as ${name}`, `import ${name}`) }); - mismatches++; - } - } - } -} - -console.log(`\nFound ${mismatches} mismatches.`); - -if (process.argv.includes('--fix') && fixes.length > 0) { - // Group by file - const byFile = {}; - for (const fix of fixes) { - if (!byFile[fix.file]) byFile[fix.file] = []; - byFile[fix.file].push(fix); - } - - for (const [file, fileFixes] of Object.entries(byFile)) { - let content = fs.readFileSync(file, 'utf-8'); - for (const fix of fileFixes) { - content = content.replace(fix.old, fix.new); - } - fs.writeFileSync(file, content); - console.log(`Fixed: ${path.relative(ROOT, file)} (${fileFixes.length} changes)`); - } - console.log('All mismatches fixed.'); -} diff --git a/scripts/cjs-to-esm/cjs-to-esm.mjs b/scripts/cjs-to-esm/cjs-to-esm.mjs deleted file mode 100644 index f0b0b5434..000000000 --- a/scripts/cjs-to-esm/cjs-to-esm.mjs +++ /dev/null @@ -1,355 +0,0 @@ -#!/usr/bin/env node - -// One-shot CJS → ESM conversion script for the box codebase. -// Usage: -// node scripts/cjs-to-esm.mjs # convert all src/ + syslog.js -// node scripts/cjs-to-esm.mjs --dry-run # preview without writing -// node scripts/cjs-to-esm.mjs src/paths.js # convert specific file(s) - -import fs from 'node:fs'; -import path from 'node:path'; - -const ROOT = process.cwd(); -const DRY_RUN = process.argv.includes('--dry-run'); -const args = process.argv.slice(2).filter(a => !a.startsWith('--')); - -// ========== File Collection ========== - -function walk(dir) { - const result = []; - for (const e of fs.readdirSync(dir, { withFileTypes: true })) { - const p = path.join(dir, e.name); - if (e.isDirectory()) result.push(...walk(p)); - else if (e.name.endsWith('.js')) result.push(p); - } - return result; -} - -function getFiles() { - if (args.length) return args.map(f => path.resolve(f)); - const files = walk(path.join(ROOT, 'src')); - files.push(path.join(ROOT, 'syslog.js')); - return files; -} - -// ========== NPM Package ESM Detection ========== - -const esmCache = {}; -function isEsmPkg(modPath) { - const name = modPath.startsWith('@') ? modPath.split('/').slice(0, 2).join('/') : modPath.split('/')[0]; - if (name in esmCache) return esmCache[name]; - try { - const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'node_modules', name, 'package.json'), 'utf-8')); - esmCache[name] = pkg.type === 'module'; - } catch { - esmCache[name] = false; - } - return esmCache[name]; -} - -// ========== Import Path Fixup ========== - -function fixPath(mod, fromFile) { - if (!mod.startsWith('.')) return mod; - if (mod.endsWith('.js') || mod.endsWith('.mjs')) return mod; - const dir = path.dirname(fromFile); - const resolved = path.resolve(dir, mod); - if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) return mod + '/index.js'; - if (fs.existsSync(resolved + '.js')) return mod + '.js'; - return mod; -} - -// ========== Convert a Single Require Declaration ========== -// Input: trimmed declaration text WITHOUT leading const/let/var and trailing ,; -// Returns { imports: string[], consts: string[] } or null - -function convertDecl(decl, fromFile) { - decl = decl.trim().replace(/[,;]\s*$/, '').trim(); - if (!decl || !decl.includes('require(')) return null; - - let m; - - // 1) name = require('mod')('args...') e.g. debug = require('debug')('box:dns') - m = decl.match(/^(\w+)\s*=\s*require\(['"]([^'"]+)['"]\)\((.+)\)$/); - if (m) { - const [, name, mod, callArgs] = m; - const alias = name + 'Module'; - return { - imports: [`import ${alias} from '${fixPath(mod, fromFile)}';`], - consts: [`const ${name} = ${alias}(${callArgs});`] - }; - } - - // 2) name = require('mod').prop e.g. HttpError = require('@cloudron/connect-lastmile').HttpError - m = decl.match(/^(\w+)\s*=\s*require\(['"]([^'"]+)['"]\)\.(\w+)$/); - if (m) { - const [, name, mod, prop] = m; - const fp = fixPath(mod, fromFile); - const imp = name === prop - ? `import { ${prop} } from '${fp}';` - : `import { ${prop} as ${name} } from '${fp}';`; - return { imports: [imp], consts: [] }; - } - - // 3) { a, b } = require('mod') e.g. { CronTime } = require('cron') - m = decl.match(/^(\{[^}]+\})\s*=\s*require\(['"]([^'"]+)['"]\)$/); - if (m) { - const [, destr, mod] = m; - return { imports: [`import ${destr} from '${fixPath(mod, fromFile)}';`], consts: [] }; - } - - // 4) name = require('mod') e.g. apps = require('./apps.js') - m = decl.match(/^(\w+)\s*=\s*require\(['"]([^'"]+)['"]\)$/); - if (m) { - const [, name, mod] = m; - const fp = fixPath(mod, fromFile); - // Use namespace import for ESM-only npm packages (they likely have no default export) - const useNamespace = !mod.startsWith('.') && !mod.startsWith('node:') && isEsmPkg(mod); - const imp = useNamespace - ? `import * as ${name} from '${fp}';` - : `import ${name} from '${fp}';`; - return { imports: [imp], consts: [] }; - } - - return null; // unknown pattern -} - -// ========== Process a const-require Block ========== - -function handleBlock(blockLines, imports, consts, warnings, fromFile) { - const block = blockLines.join('\n'); - // Strip leading const/let/var and trailing ; - let inner = block.replace(/^\s*(const|let|var)\s+/, ''); - inner = inner.replace(/;\s*$/, ''); - - // Split individual declarations by comma-at-end-of-line - const decls = inner.split(/,\n/).map(d => d.trim()); - - for (const decl of decls) { - if (!decl) continue; - - if (!decl.includes('require(')) { - // Non-require declaration in a mixed block — keep as const - consts.push(`const ${decl};`); - continue; - } - - const result = convertDecl(decl, fromFile); - if (result) { - imports.push(...result.imports); - if (result.consts.length) consts.push(...result.consts); - } else { - warnings.push(`Unknown require pattern: ${decl}`); - consts.push(`const ${decl};`); - } - } -} - -// ========== Convert Exports Block ========== - -function convertExports(block) { - // exports = module.exports = { ... }; - let m = block.match(/exports\s*=\s*module\.exports\s*=\s*(\{[\s\S]*\});/); - if (m) return `export default ${m[1]};`; - - // exports = module.exports = name; - m = block.match(/exports\s*=\s*module\.exports\s*=\s*(\w+)\s*;/); - if (m) return `export default ${m[1]};`; - - return block; // fallback -} - -// ========== Main File Processor ========== - -function processFile(filePath) { - const lines = fs.readFileSync(filePath, 'utf-8').split('\n'); - const imports = []; - const consts = []; - const output = []; - const warnings = []; - - let i = 0; - let inBlock = false; - let blockLines = []; - - while (i < lines.length) { - const line = lines[i]; - const t = line.trim(); - - // --- Skip 'use strict' --- - if (t === "'use strict';" || t === '"use strict";') { - i++; - if (i < lines.length && lines[i].trim() === '') i++; - continue; - } - - // --- Detect start of const/let/var block --- - if (!inBlock && /^\s*(const|let|var)\s+/.test(line)) { - blockLines = [line]; - if (t.endsWith(';')) { - // Single-line block - if (/require\(/.test(line)) { - handleBlock(blockLines, imports, consts, warnings, filePath); - } else { - output.push(line); - } - } else { - inBlock = true; - } - i++; - continue; - } - - // --- Continue multi-line const block --- - if (inBlock) { - blockLines.push(line); - if (t.endsWith(';')) { - const fullBlock = blockLines.join('\n'); - if (/require\(/.test(fullBlock)) { - handleBlock(blockLines, imports, consts, warnings, filePath); - } else { - output.push(...blockLines); - } - inBlock = false; - } - i++; - continue; - } - - // --- Exports block --- - if (/^\s*exports\s*=\s*module\.exports\s*=/.test(line)) { - let block = line; - if (!t.endsWith(';')) { - i++; - while (i < lines.length) { - block += '\n' + lines[i]; - if (lines[i].trim().endsWith(';')) { i++; break; } - i++; - } - } else { - i++; - } - - if (/require\(/.test(block)) { - warnings.push('Inline require() in exports block — needs manual fix'); - output.push(block); - } else { - output.push(convertExports(block)); - } - continue; - } - - // --- require.main === module --- - if (/require\.main\s*===\s*module/.test(t)) { - output.push(line.replace(/require\.main\s*===\s*module/, 'process.argv[1] === import.meta.filename')); - i++; - continue; - } - - // --- Warn about any remaining require() calls --- - if (/\brequire\s*\(/.test(t) && !/^\s*\/\//.test(line) && !/^\s*\*/.test(line)) { - warnings.push(`Line ${i + 1}: Unconverted require(): ${t}`); - } - - output.push(line); - i++; - } - - // --- Replace __dirname / __filename with import.meta equivalents (Node 21.2+) --- - for (let j = 0; j < output.length; j++) { - output[j] = output[j].replace(/\b__dirname\b/g, 'import.meta.dirname'); - output[j] = output[j].replace(/\b__filename\b/g, 'import.meta.filename'); - } - - // --- Reconstruct file --- - // Preserve shebang and leading block comments before imports - const leading = []; - let start = 0; - - // Shebang - if (output.length > 0 && output[0].trimStart().startsWith('#!')) { - leading.push(output[0]); - start = 1; - // skip blank line after shebang - while (start < output.length && output[start].trim() === '') start++; - } - - // Leading block comment (/* jslint */, /* global */, /* eslint */) - if (start < output.length && output[start].trim().startsWith('/*')) { - // Could be single-line or multi-line - if (output[start].includes('*/')) { - leading.push(output[start]); - start++; - } else { - while (start < output.length) { - leading.push(output[start]); - if (output[start].includes('*/')) { start++; break; } - start++; - } - } - // skip blank line after comment - if (start < output.length && output[start].trim() === '') start++; - } - - const rest = output.slice(start); - // Remove leading blank lines from rest - while (rest.length > 0 && rest[0].trim() === '') rest.shift(); - - // Build final content - const parts = []; - if (leading.length > 0) { - parts.push(...leading); - parts.push(''); - } - if (imports.length > 0) { - parts.push(...imports); - } - if (consts.length > 0) { - if (imports.length > 0) parts.push(''); - parts.push(...consts); - } - if (imports.length > 0 || consts.length > 0) { - parts.push(''); - } - parts.push(...rest); - - let result = parts.join('\n'); - // Clean up 3+ consecutive blank lines → 2 - result = result.replace(/\n{3,}/g, '\n\n'); - // Ensure trailing newline - if (!result.endsWith('\n')) result += '\n'; - - return { content: result, warnings }; -} - -// ========== Run ========== - -const files = getFiles(); -console.log(`Processing ${files.length} files${DRY_RUN ? ' (dry run)' : ''}...\n`); - -let totalWarnings = 0; -for (const file of files) { - const rel = path.relative(ROOT, file); - try { - const { content, warnings } = processFile(file); - - if (warnings.length > 0) { - console.log(`⚠ ${rel}:`); - for (const w of warnings) console.log(` ${w}`); - totalWarnings += warnings.length; - } - - if (!DRY_RUN) { - fs.writeFileSync(file, content); - console.log(`✓ ${rel}`); - } else { - console.log(` ${rel}${warnings.length ? '' : ' (ok)'}`); - } - } catch (err) { - console.error(`✗ ${rel}: ${err.message}`); - totalWarnings++; - } -} - -console.log(`\nDone. ${files.length} files processed, ${totalWarnings} warnings.`); -if (totalWarnings > 0) console.log('Review warnings above and fix manually.'); diff --git a/scripts/cjs-to-esm/fix-consumer-imports.mjs b/scripts/cjs-to-esm/fix-consumer-imports.mjs deleted file mode 100644 index 17a7fd841..000000000 --- a/scripts/cjs-to-esm/fix-consumer-imports.mjs +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env node - -// Fix consumers of files that still use export default { ... } -// Change `import * as X from './file.js'` back to `import X from './file.js'` -// for files whose target still uses export default. - -import fs from 'node:fs'; -import path from 'node:path'; - -const ROOT = process.cwd(); - -function walk(dir) { - const result = []; - for (const e of fs.readdirSync(dir, { withFileTypes: true })) { - const p = path.join(dir, e.name); - if (e.isDirectory()) result.push(...walk(p)); - else if (e.name.endsWith('.js')) result.push(p); - } - return result; -} - -const srcFiles = walk(path.join(ROOT, 'src')); -srcFiles.push(path.join(ROOT, 'syslog.js')); - -// Find files that use export default (not named exports) -const defaultExportFiles = new Set(); -for (const file of srcFiles) { - const content = fs.readFileSync(file, 'utf-8'); - if (/^export default /m.test(content)) { - defaultExportFiles.add(file); - } -} - -console.log(`Files with export default: ${defaultExportFiles.size}`); -for (const f of defaultExportFiles) { - console.log(` ${path.relative(ROOT, f)}`); -} - -// Fix consumers: change `import * as X from './file.js'` → `import X from './file.js'` -// for files that use export default -let changes = 0; - -for (const file of srcFiles) { - let content = fs.readFileSync(file, 'utf-8'); - let changed = false; - - const importRegex = /^(import\s+)\* as (\w+)(\s+from\s+')(\.[^']+\.js)(';)$/gm; - const newContent = content.replace(importRegex, (match, pre, name, mid, importPath, post) => { - const dir = path.dirname(file); - const resolved = path.resolve(dir, importPath); - - if (defaultExportFiles.has(resolved)) { - changed = true; - changes++; - return `${pre}${name}${mid}${importPath}${post}`; - } - return match; - }); - - if (changed) { - fs.writeFileSync(file, newContent); - console.log(`✓ Fixed imports: ${path.relative(ROOT, file)}`); - } -} - -console.log(`\nReverted ${changes} imports to default import style.`); diff --git a/scripts/cjs-to-esm/fix-exports-self-ref.mjs b/scripts/cjs-to-esm/fix-exports-self-ref.mjs deleted file mode 100644 index 0cbfbf74d..000000000 --- a/scripts/cjs-to-esm/fix-exports-self-ref.mjs +++ /dev/null @@ -1,177 +0,0 @@ -#!/usr/bin/env node - -// Fix `exports.CONSTANT` self-references in ESM files. -// For files with `export default { ... }`: -// - Extract constant properties (simple key: 'literal' pairs) as standalone const declarations -// - Replace the inline definition in the export block with shorthand property -// - Replace `exports.CONSTANT` with `CONSTANT` everywhere -// For files with `export { ... }` (constants already standalone): -// - Just replace `exports.CONSTANT` with `CONSTANT` - -import fs from 'node:fs'; -import path from 'node:path'; - -const ROOT = path.resolve(import.meta.dirname, '..'); -const args = process.argv.slice(2).filter(a => !a.startsWith('--')); - -function findFilesWithExportsRef(dir) { - const results = []; - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const full = path.join(dir, entry.name); - if (entry.isDirectory()) { - if (entry.name === 'node_modules' || entry.name === 'migrations' || entry.name === 'dashboard') continue; - results.push(...findFilesWithExportsRef(full)); - } else if (entry.name.endsWith('.js')) { - const content = fs.readFileSync(full, 'utf8'); - if (/\bexports\./.test(content)) { - results.push(full); - } - } - } - return results; -} - -function processFile(filePath) { - let content = fs.readFileSync(filePath, 'utf8'); - const relPath = path.relative(ROOT, filePath); - - // Find all exports.SOMETHING references - const exportsRefs = new Set(); - const re = /\bexports\.(\w+)/g; - let m; - while ((m = re.exec(content)) !== null) { - exportsRefs.add(m[1]); - } - - if (exportsRefs.size === 0) return; - - console.log(`\n=== ${relPath} ===`); - console.log(` Self-referenced constants: ${[...exportsRefs].join(', ')}`); - - // Check if these constants already exist as standalone const/let/var declarations - const alreadyDeclared = new Set(); - for (const name of exportsRefs) { - const declPattern = new RegExp(`^(?:const|let|var)\\s+${name}\\s*=`, 'm'); - if (declPattern.test(content)) { - alreadyDeclared.add(name); - } - } - - const needsExtraction = [...exportsRefs].filter(n => !alreadyDeclared.has(n)); - - if (alreadyDeclared.size > 0) { - console.log(` Already declared: ${[...alreadyDeclared].join(', ')}`); - } - - // For constants that need extraction, find them in the export default block - if (needsExtraction.length > 0) { - console.log(` Need extraction: ${needsExtraction.join(', ')}`); - - // Find export default { ... } block - // Parse it carefully - we need to handle the block properly - const exportStart = content.indexOf('export default {'); - if (exportStart === -1) { - console.log(` WARNING: No 'export default {' found but need extraction for: ${needsExtraction.join(', ')}`); - // Just do the replacement anyway - } else { - // Find the matching closing brace - let braceDepth = 0; - let exportEnd = -1; - let inString = false; - let stringChar = ''; - for (let i = exportStart + 'export default '.length; i < content.length; i++) { - const ch = content[i]; - if (inString) { - if (ch === '\\') { i++; continue; } - if (ch === stringChar) inString = false; - continue; - } - if (ch === '\'' || ch === '"' || ch === '`') { - inString = true; - stringChar = ch; - continue; - } - if (ch === '{') braceDepth++; - else if (ch === '}') { - braceDepth--; - if (braceDepth === 0) { - exportEnd = i + 1; - // Include trailing semicolon - if (content[exportEnd] === ';') exportEnd++; - break; - } - } - } - - if (exportEnd === -1) { - console.log(` WARNING: Could not find end of export default block`); - } else { - const exportBlock = content.slice(exportStart, exportEnd); - const lines = exportBlock.split('\n'); - - const extracted = []; // { name, value, originalLine } - const newLines = []; - - for (const line of lines) { - const trimmed = line.trim(); - let found = false; - - for (const name of needsExtraction) { - // Match: NAME: 'value', or NAME: 123, or NAME: true, - // Only match simple literal values (strings, numbers, booleans) - const pattern = new RegExp(`^${name}:\\s*('[^']*'|"[^"]*"|\\d+(?:\\.\\d+)?|true|false|null),?\\s*(?:\\/\\/.*)?$`); - const match = trimmed.match(pattern); - if (match) { - extracted.push({ name, value: match[1] }); - // Replace the line with just the shorthand property - const indent = line.match(/^(\s*)/)[1]; - newLines.push(`${indent}${name},`); - found = true; - break; - } - } - - if (!found) { - newLines.push(line); - } - } - - if (extracted.length > 0) { - const constDecls = extracted.map(e => `const ${e.name} = ${e.value};`).join('\n'); - const newExportBlock = newLines.join('\n'); - content = content.slice(0, exportStart) + constDecls + '\n\n' + newExportBlock + content.slice(exportEnd); - console.log(` Extracted ${extracted.length} constants: ${extracted.map(e => e.name).join(', ')}`); - } - - const notExtracted = needsExtraction.filter(n => !extracted.find(e => e.name === n)); - if (notExtracted.length > 0) { - console.log(` WARNING: Could not extract (complex values?): ${notExtracted.join(', ')}`); - } - } - } - } - - // Replace all exports.CONSTANT references with just CONSTANT - for (const name of exportsRefs) { - const pattern = new RegExp(`\\bexports\\.${name}\\b`, 'g'); - content = content.replace(pattern, name); - } - - fs.writeFileSync(filePath, content); - console.log(` Done!`); -} - -let files; -if (args.length) { - files = args.map(f => path.resolve(f)); -} else { - files = findFilesWithExportsRef(path.join(ROOT, 'src')); -} - -console.log(`Processing ${files.length} files...`); - -for (const file of files) { - processFile(file); -} - -console.log('\nAll done!'); diff --git a/scripts/cjs-to-esm/fix-exports.mjs b/scripts/cjs-to-esm/fix-exports.mjs deleted file mode 100644 index 93631c865..000000000 --- a/scripts/cjs-to-esm/fix-exports.mjs +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env node - -// Phase 2: Convert export default { shorthand } → export { shorthand } -// and update consumers from `import X from` → `import * as X from` for local files. - -import fs from 'node:fs'; -import path from 'node:path'; - -const ROOT = process.cwd(); -const DRY_RUN = process.argv.includes('--dry-run'); - -function walk(dir) { - const result = []; - for (const e of fs.readdirSync(dir, { withFileTypes: true })) { - const p = path.join(dir, e.name); - if (e.isDirectory()) result.push(...walk(p)); - else if (e.name.endsWith('.js')) result.push(p); - } - return result; -} - -const srcFiles = walk(path.join(ROOT, 'src')); -srcFiles.push(path.join(ROOT, 'syslog.js')); - -// Phase 1: Identify files with shorthand-only export default { ... } -// and convert them to export { ... } -const convertedFiles = new Set(); - -for (const file of srcFiles) { - let content = fs.readFileSync(file, 'utf-8'); - - // Match export default { ... }; - const match = content.match(/^export default (\{[\s\S]*?\});/m); - if (!match) continue; - - const objContent = match[1]; - - // Check if this is shorthand-only (no "key: value" patterns) - // Shorthand entries look like: " funcName," or " funcName" (last one) - // Key-value entries look like: " KEY: value," or " key: value" - // We strip the braces and check each line - const inner = objContent.slice(1, -1); // remove { and } - const lines = inner.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('//')); - - // Check if any line has a colon that's NOT in a comment - const hasKeyValue = lines.some(line => { - // Remove trailing comma - const cleaned = line.replace(/,\s*$/, '').trim(); - if (!cleaned) return false; - // A shorthand property is just an identifier (possibly with leading/trailing whitespace) - // A key-value has "identifier: something" or "'key': something" - return cleaned.includes(':'); - }); - - if (hasKeyValue) { - // Cannot convert to named exports - has computed values - continue; - } - - // Convert export default { ... } → export { ... } - const newContent = content.replace(/^export default (\{[\s\S]*?\});/m, 'export $1;'); - if (newContent !== content) { - if (!DRY_RUN) fs.writeFileSync(file, newContent); - convertedFiles.add(file); - console.log(`✓ Converted exports: ${path.relative(ROOT, file)}`); - } -} - -console.log(`\nConverted ${convertedFiles.size} files to named exports.`); - -// Phase 2: Update consumers -// For each converted file, find all files that import it with `import X from './path'` -// and change to `import * as X from './path'` -let importChanges = 0; - -for (const file of srcFiles) { - let content = fs.readFileSync(file, 'utf-8'); - let changed = false; - - // Match: import identifier from './relative/path.js'; - // or: import identifier from '../relative/path.js'; - const importRegex = /^(import\s+)(\w+)(\s+from\s+')(\.[^']+\.js)(';)$/gm; - const newContent = content.replace(importRegex, (match, pre, name, mid, importPath, post) => { - // Resolve the import path to absolute - const dir = path.dirname(file); - const resolved = path.resolve(dir, importPath); - - if (convertedFiles.has(resolved)) { - changed = true; - importChanges++; - return `${pre}* as ${name}${mid}${importPath}${post}`; - } - return match; - }); - - if (changed && !DRY_RUN) { - fs.writeFileSync(file, newContent); - console.log(`✓ Updated imports: ${path.relative(ROOT, file)}`); - } -} - -console.log(`\nUpdated ${importChanges} import statements.`); -console.log('Done.'); diff --git a/scripts/cjs-to-esm/fix-imports-for-named.mjs b/scripts/cjs-to-esm/fix-imports-for-named.mjs deleted file mode 100644 index fc3f5b732..000000000 --- a/scripts/cjs-to-esm/fix-imports-for-named.mjs +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env node - -// For a set of target files, find imports that use `import X from './module.js'` -// where the module uses `export { ... }` (named exports, no default). -// Convert those to `import * as X from './module.js'`. - -import fs from 'node:fs'; -import path from 'node:path'; - -const ROOT = path.resolve(import.meta.dirname, '..'); -const targetFiles = process.argv.slice(2).map(f => path.resolve(f)); - -if (targetFiles.length === 0) { - console.log('Usage: node scripts/fix-imports-for-named.mjs file1.js file2.js ...'); - process.exit(1); -} - -// Build a set of files that use named exports (export { ... }) without a default export -function hasOnlyNamedExports(filePath) { - if (!fs.existsSync(filePath)) return false; - const content = fs.readFileSync(filePath, 'utf8'); - const hasNamedExport = /^export \{/m.test(content); - const hasDefaultExport = /^export default /m.test(content); - return hasNamedExport && !hasDefaultExport; -} - -for (const filePath of targetFiles) { - let content = fs.readFileSync(filePath, 'utf8'); - const relPath = path.relative(ROOT, filePath); - const dir = path.dirname(filePath); - let changes = 0; - - // Find all default imports: import NAME from './something.js' - const importRe = /^import (\w+) from '([^']+)';$/gm; - let match; - const replacements = []; - - while ((match = importRe.exec(content)) !== null) { - const [fullMatch, name, specifier] = match; - - // Only handle local/relative imports - if (!specifier.startsWith('.')) continue; - - // Resolve the full path - const resolved = path.resolve(dir, specifier); - - if (hasOnlyNamedExports(resolved)) { - replacements.push({ - from: fullMatch, - to: `import * as ${name} from '${specifier}';` - }); - } - } - - for (const r of replacements) { - content = content.replace(r.from, r.to); - changes++; - } - - if (changes > 0) { - fs.writeFileSync(filePath, content); - console.log(`${relPath}: fixed ${changes} imports`); - } else { - console.log(`${relPath}: no changes needed`); - } -} diff --git a/scripts/cjs-to-esm/fix-remaining-exports.mjs b/scripts/cjs-to-esm/fix-remaining-exports.mjs deleted file mode 100644 index bcd76d597..000000000 --- a/scripts/cjs-to-esm/fix-remaining-exports.mjs +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env node - -// Phase 3: Convert remaining export default { ... } (with computed properties) -// to named exports by extracting computed values as const declarations. -// Also updates consumers from `import X from` → `import * as X from`. - -import fs from 'node:fs'; -import path from 'node:path'; - -const ROOT = process.cwd(); -const DRY_RUN = process.argv.includes('--dry-run'); - -function walk(dir) { - const result = []; - for (const e of fs.readdirSync(dir, { withFileTypes: true })) { - const p = path.join(dir, e.name); - if (e.isDirectory()) result.push(...walk(p)); - else if (e.name.endsWith('.js')) result.push(p); - } - return result; -} - -const srcFiles = walk(path.join(ROOT, 'src')); -srcFiles.push(path.join(ROOT, 'syslog.js')); - -const convertedFiles = new Set(); - -for (const file of srcFiles) { - let content = fs.readFileSync(file, 'utf-8'); - - // Match export default { ... }; - const match = content.match(/^(export default )(\{[\s\S]*?\});/m); - if (!match) continue; - - const fullMatch = match[0]; - const objContent = match[2]; - const inner = objContent.slice(1, -1); // strip { } - - // Parse entries - const lines = inner.split('\n'); - const extractedConsts = []; - const exportNames = []; - - for (const rawLine of lines) { - const line = rawLine.trim(); - if (!line || line.startsWith('//') || line.startsWith('/*') || line.startsWith('*')) continue; - - // Remove trailing comma - const cleaned = line.replace(/,\s*$/, '').trim(); - if (!cleaned) continue; - - // Check if it's a key-value pair (has : that's not in a string or ternary) - // Simple heuristic: if the line matches "identifier: expression" - const kvMatch = cleaned.match(/^(\w+)\s*:\s*(.+)$/); - if (kvMatch) { - const [, key, value] = kvMatch; - // Check if this is truly a key-value or just a shorthand with same name - // If key === value, it's shorthand (e.g. "x: x" is rare but possible) - if (key !== value) { - extractedConsts.push(`const ${key} = ${value};`); - exportNames.push(key); - continue; - } - } - - // Shorthand property - exportNames.push(cleaned); - } - - // Build new export block - const newExport = `export {\n ${exportNames.join(',\n ')},\n};`; - - // Build replacement: extracted consts + newline + export block - let replacement; - if (extractedConsts.length > 0) { - replacement = extractedConsts.join('\n') + '\n\n' + newExport; - } else { - replacement = newExport; - } - - const newContent = content.replace(fullMatch, replacement); - if (newContent !== content) { - if (!DRY_RUN) fs.writeFileSync(file, newContent); - convertedFiles.add(file); - console.log(`✓ Converted exports: ${path.relative(ROOT, file)}`); - } -} - -console.log(`\nConverted ${convertedFiles.size} files.`); - -// Phase 2: Update consumers -let importChanges = 0; - -for (const file of srcFiles) { - let content = fs.readFileSync(file, 'utf-8'); - let changed = false; - - const importRegex = /^(import\s+)(\w+)(\s+from\s+')(\.[^']+\.js)(';)$/gm; - const newContent = content.replace(importRegex, (match, pre, name, mid, importPath, post) => { - const dir = path.dirname(file); - const resolved = path.resolve(dir, importPath); - - if (convertedFiles.has(resolved)) { - changed = true; - importChanges++; - return `${pre}* as ${name}${mid}${importPath}${post}`; - } - return match; - }); - - if (changed && !DRY_RUN) { - fs.writeFileSync(file, newContent); - console.log(`✓ Updated imports: ${path.relative(ROOT, file)}`); - } -} - -console.log(`\nUpdated ${importChanges} import statements.`); - -// Verify no more export default { ... } remain -let remaining = 0; -for (const file of srcFiles) { - const content = fs.readFileSync(file, 'utf-8'); - if (/^export default \{/m.test(content)) { - console.log(`⚠ Still has export default: ${path.relative(ROOT, file)}`); - remaining++; - } -} -if (remaining > 0) console.log(`\n${remaining} files still need manual attention.`); -else console.log('\nAll export default { ... } converted to named exports.'); diff --git a/scripts/cjs-to-esm/revert-and-fix.mjs b/scripts/cjs-to-esm/revert-and-fix.mjs deleted file mode 100644 index 6ce5daeda..000000000 --- a/scripts/cjs-to-esm/revert-and-fix.mjs +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env node - -// Revert the broken extraction from fix-remaining-exports.mjs -// and instead use a proper approach: -// 1. Keep export default { ... } for files with computed properties -// 2. Change all `import X from './local.js'` for those files to -// `import * as _X from './local.js'; const X = _X.default;` -// Actually that's ugly. Better approach: -// Just lazify the top-level constant evaluation in services.js -// OR use a re-export pattern. -// -// Actually, the simplest approach: restore the 34 files from git, -// then re-run the first two conversion scripts on them, -// then for files with complex exports, add a thin named-export wrapper. - -import fs from 'node:fs'; -import path from 'node:path'; -import { execSync } from 'node:child_process'; - -const ROOT = process.cwd(); - -// Find files that still have export default but with broken syntax -const files = execSync('git diff --name-only', { cwd: ROOT, encoding: 'utf-8' }) - .trim().split('\n') - .filter(f => f.startsWith('src/') || f === 'syslog.js'); - -// Check which files have syntax errors -const brokenFiles = []; -for (const file of files) { - try { - execSync(`node --check ${file}`, { cwd: ROOT, stdio: 'pipe' }); - } catch { - brokenFiles.push(file); - } -} - -console.log(`Found ${brokenFiles.length} files with syntax errors:`); -brokenFiles.forEach(f => console.log(` ${f}`)); - -// Restore these files from git -for (const file of brokenFiles) { - execSync(`git checkout -- ${file}`, { cwd: ROOT }); - console.log(`Restored: ${file}`); -} - -// Now re-run the initial CJS→ESM conversion on just these files -console.log('\nRe-running CJS→ESM conversion on restored files...'); -const fileArgs = brokenFiles.join(' '); -execSync(`node scripts/cjs-to-esm.mjs ${fileArgs}`, { cwd: ROOT, stdio: 'inherit' }); - -// Now re-run the first export fix (shorthand-only) on these files -// But we need a targeted approach: for each file, check if its exports -// are shorthand-only, and if so convert to named exports. -// Files with computed properties will keep export default. - -console.log('\nRe-running export fixes...'); -execSync('node scripts/fix-exports.mjs', { cwd: ROOT, stdio: 'inherit' }); - -console.log('\nDone. Files with complex exports will keep export default.'); -console.log('Circular dependency issues will be fixed by lazifying top-level access.');