Просмотр исходного кода

Script to track ES Modules migration progress (#20448)

* Script to track ES Modules migration progress

GitOrigin-RevId: 8582f529313c40c26d27d7c2f1542b1828c5a7e4
Miguel Serrano 2 лет назад
Родитель
Сommit
8ca24b104b
1 измененных файлов с 260 добавлено и 0 удалено
  1. 260 0
      services/web/scripts/esm-check-migration.js

+ 260 - 0
services/web/scripts/esm-check-migration.js

@@ -0,0 +1,260 @@
+const fs = require('fs')
+const path = require('path')
+const minimist = require('minimist')
+
+const APP_CODE_PATH = ['app', 'modules']
+
+const {
+  _: args,
+  files,
+  help,
+  json,
+} = minimist(process.argv.slice(2), {
+  boolean: ['files', 'help', 'json'],
+  alias: {
+    files: 'f',
+    help: 'h',
+    json: 'j',
+  },
+  default: {
+    _: ['app', 'modules'],
+    files: false,
+    help: false,
+    json: false,
+  },
+})
+const paths = args.length > 0 ? args : APP_CODE_PATH
+
+function usage() {
+  console.error(`Usage: node check-esm-migration.js [OPTS...] dir1 dir2
+       node check-esm-migration.js file
+
+Usage with directories
+----------------------
+
+When the arguments are a list of directories it prints the status of ES Modules migration within those directories.
+When no directory is provided, it checks app/ and modules/, which represent the entire codebase.
+
+With the --files (-f) option, it prints the list of JS files that:
+ - Are not migrated to ESM
+ - Are not required by any file that is not migrated yet to ESM (in the entire codebase)
+
+These files should be the most immediate candidates to be migrated.
+
+WARNING: please note that this script only looks up literals in require() statements, so paths
+built dynamically (such as those in infrastructure/Modules.js) are not being taken into account.
+
+Usage with a JS file
+--------------------
+
+When the argument is a JS file, the script outputs the files that depend on this file that have not been converted
+yet to ES Modules.
+
+The files in the list must to be converted to ES Modules before converting the JS file.
+
+Example:
+   node scrips/check-esm-migration.js --files modules/admin-panel
+   node scrips/check-esm-migration.js app/src/router.js
+
+Options:
+    --files      Prints the files that are not imported by app code via CommonJS
+    --json       Prints the result in JSON format, including the list of files from --files
+    --help       Prints this help
+`)
+}
+
+function resolveImportPaths(dir, file) {
+  const absolutePath = path.resolve(dir, file)
+  if (fs.existsSync(absolutePath)) {
+    return absolutePath
+  } else if (fs.existsSync(absolutePath + '.js')) {
+    return absolutePath + '.js'
+  } else if (fs.existsSync(absolutePath + '.mjs')) {
+    return absolutePath + '.mjs'
+  } else {
+    return null
+  }
+}
+
+function collectJsFiles(dir, files = []) {
+  const items = fs.readdirSync(dir)
+  items.forEach(item => {
+    const fullPath = path.join(dir, item)
+    const stat = fs.statSync(fullPath)
+
+    if (stat.isDirectory()) {
+      const basename = path.basename(fullPath)
+
+      // skipping 'test' and 'frontend' directories from search
+      if (basename !== 'test' && basename !== 'frontend') {
+        collectJsFiles(fullPath, files)
+      }
+    } else if (
+      stat.isFile() &&
+      (fullPath.endsWith('.js') || fullPath.endsWith('.mjs'))
+    ) {
+      files.push(fullPath)
+    }
+  })
+  return files
+}
+
+function extractImports(filePath) {
+  const fileContent = fs.readFileSync(filePath, 'utf-8')
+
+  // not 100% compliant (string escaping, etc.) but does the work here
+  const contentWithoutComments = fileContent.replace(
+    /\/\/.*|\/\*[\s\S]*?\*\//g,
+    ''
+  )
+
+  const requireRegex = /require\s*\(\s*['"](.+?)['"]\s*\)/g
+
+  const dependencies = []
+  while (true) {
+    const match = requireRegex.exec(contentWithoutComments)
+    if (!match) {
+      break
+    }
+    dependencies.push(match[1])
+  }
+
+  // build absolute path for the imported file
+  return dependencies
+    .map(depPath => resolveImportPaths(path.dirname(filePath), depPath))
+    .filter(path => path !== null)
+}
+
+// Main function to process a list of directories and create the Map of dependencies
+function findJSAndImports(directories) {
+  const fileDependenciesMap = new Map()
+
+  directories.forEach(dir => {
+    if (fs.existsSync(dir)) {
+      const jsFiles = collectJsFiles(dir)
+
+      jsFiles.forEach(filePath => {
+        const imports = extractImports(filePath)
+        fileDependenciesMap.set(filePath, imports)
+      })
+    } else {
+      console.error(`Directory not found: ${dir}`)
+      process.exit(1)
+    }
+  })
+
+  return fileDependenciesMap
+}
+
+function printDirectoriesReport(allFilesAndImports) {
+  // collect all files that are imported via CommonJS in the entire backend codebase
+  const filesImportedViaCjs = new Set()
+  allFilesAndImports.forEach((imports, file) => {
+    if (!file.endsWith('.mjs')) {
+      imports.forEach(imprt => filesImportedViaCjs.add(imprt))
+    }
+  })
+
+  // collect js files from the selected paths
+  const selectedFiles = Array.from(
+    findJSAndImports(paths.map(dir => path.resolve(dir))).keys()
+  )
+  const nonMigratedFiles = selectedFiles.filter(file => !file.endsWith('.mjs'))
+  const migratedFileCount = selectedFiles.filter(file =>
+    file.endsWith('.mjs')
+  ).length
+
+  // collect files in the selected paths that are not imported via CommonJs in the entire backend codebase
+  const filesNotImportedViaCjs = nonMigratedFiles.filter(
+    file => !filesImportedViaCjs.has(file)
+  )
+
+  if (json) {
+    console.log(
+      JSON.stringify(
+        {
+          fileCount: selectedFiles.length,
+          migratedFileCount,
+          filesNotImportedViaCjs,
+        },
+        null,
+        2
+      )
+    )
+  } else {
+    console.log(`Found ${selectedFiles.length} files in ${paths}:
+    - ${migratedFileCount} have been migrated to ES Modules (progress=${((migratedFileCount / selectedFiles.length) * 100).toFixed(2)}%)
+    - ${filesNotImportedViaCjs.length} are ready to migrate (these are not imported via CommonJS in the entire codebase)
+`)
+    if (files) {
+      console.log(`Files that are ready to migrate:`)
+      filesNotImportedViaCjs.forEach(file =>
+        console.log(`    - ${file.replace(process.cwd() + '/', '')}`)
+      )
+    }
+  }
+}
+
+function printFileReport(allFilesAndImports) {
+  const filePath = path.resolve(paths[0])
+  if (filePath.endsWith('.mjs')) {
+    console.log(`${filePath} is already migrated to ESM`)
+    return
+  }
+  const filePathWithoutExtension = filePath.replace('.js', '')
+
+  const importingFiles = []
+  allFilesAndImports.forEach((imports, file) => {
+    if (file.endsWith('.mjs')) {
+      return
+    }
+    if (
+      imports.some(
+        imprt => imprt === filePath || imprt === filePathWithoutExtension
+      )
+    ) {
+      importingFiles.push(file)
+    }
+  })
+
+  if (json) {
+    console.log(
+      JSON.stringify(
+        {
+          importingFiles,
+        },
+        null,
+        2
+      )
+    )
+  } else {
+    console.log(`${filePath} is required by ${importingFiles.length} CJS file`)
+    importingFiles.forEach(file =>
+      console.log(`    - ${file.replace(process.cwd() + '/', '')}`)
+    )
+  }
+}
+
+function main() {
+  if (help) {
+    usage()
+    process.exit(0)
+  }
+
+  // collect all the js files in the entire backend codebase (app/ + modules/) with its imports
+  const allFilesAndImports = findJSAndImports(
+    APP_CODE_PATH.map(dir => path.resolve(dir))
+  )
+  const entryPoint = fs.existsSync('app.js') ? 'app.js' : 'app.mjs'
+  allFilesAndImports.set(path.resolve(entryPoint), extractImports(entryPoint))
+
+  const isFileReport = fs.statSync(paths[0]).isFile()
+
+  if (isFileReport) {
+    printFileReport(allFilesAndImports)
+  } else {
+    printDirectoriesReport(allFilesAndImports)
+  }
+}
+
+main()