index.mjs 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. #!/usr/bin/env node
  2. /**
  3. * This script can be used to initiate the transition for a plugin from ESM source to
  4. * TS source. It will rename the files, update the imports, and add a `tsconfig.json`.
  5. */
  6. import { opendir, readFile, open, writeFile, rm } from 'node:fs/promises'
  7. import { argv } from 'node:process'
  8. import { extname } from 'node:path'
  9. import { existsSync } from 'node:fs'
  10. const packageRoot = new URL(`../../packages/${argv[2]}/`, import.meta.url)
  11. let dir
  12. try {
  13. dir = await opendir(new URL('./src/', packageRoot), { recursive: true })
  14. } catch (cause) {
  15. throw new Error(`Unable to find package "${argv[2]}"`, { cause })
  16. }
  17. const packageJSON = JSON.parse(
  18. await readFile(new URL('./package.json', packageRoot), 'utf-8'),
  19. )
  20. if (packageJSON.type !== 'module') {
  21. throw new Error('Cannot convert non-ESM package to TS')
  22. }
  23. const references = Object.keys(packageJSON.dependencies || {})
  24. .concat(Object.keys(packageJSON.peerDependencies || {}))
  25. .concat(Object.keys(packageJSON.devDependencies || {}))
  26. .filter((pkg) => pkg.startsWith('@uppy/'))
  27. .map((pkg) => ({
  28. path: `../${pkg.slice('@uppy/'.length)}/tsconfig.build.json`,
  29. }))
  30. const depsNotYetConvertedToTS = references.filter(
  31. (ref) => !existsSync(new URL(ref.path, packageRoot)),
  32. )
  33. if (depsNotYetConvertedToTS.length) {
  34. // We need to first convert the dependencies, otherwise we won't be working with the correct types.
  35. throw new Error('Some dependencies have not yet been converted to TS', {
  36. cause: depsNotYetConvertedToTS.map((ref) =>
  37. ref.path.replace(/^\.\./, '@uppy'),
  38. ),
  39. })
  40. }
  41. let tsConfig
  42. try {
  43. tsConfig = await open(new URL('./tsconfig.json', packageRoot), 'wx')
  44. } catch (cause) {
  45. throw new Error('It seems this package has already been transitioned to TS', {
  46. cause,
  47. })
  48. }
  49. for await (const dirent of dir) {
  50. if (!dirent.isDirectory()) {
  51. const { path: filepath } = dirent
  52. const ext = extname(filepath)
  53. if (ext !== '.js' && ext !== '.jsx') continue // eslint-disable-line no-continue
  54. await writeFile(
  55. filepath.slice(0, -ext.length) + ext.replace('js', 'ts'),
  56. (await readFile(filepath, 'utf-8'))
  57. .replace(
  58. // The following regex aims to capture all imports and reexports of local .js(x) files to replace it to .ts(x)
  59. // It's far from perfect and will have false positives and false negatives.
  60. /((?:^|\n)(?:import(?:\s+\w+\s+from)?|export\s*\*\s*from|(?:import|export)\s*(?:\{[^}]*\}|\*\s*as\s+\w+\s)\s*from)\s*["']\.\.?\/[^'"]+\.)js(x?["'])/g, // eslint-disable-line max-len
  61. '$1ts$2',
  62. )
  63. .replace(
  64. // The following regex aims to capture all local package.json imports.
  65. /\nimport \w+ from ['"]..\/([^'"]+\/)*package.json['"]\n/g,
  66. (originalImport) =>
  67. `// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n` +
  68. `// @ts-ignore We don't want TS to generate types for the package.json${originalImport}`,
  69. ),
  70. )
  71. await rm(filepath)
  72. }
  73. }
  74. await tsConfig.writeFile(
  75. `${JSON.stringify(
  76. {
  77. extends: '../../../tsconfig.shared',
  78. compilerOptions: {
  79. emitDeclarationOnly: false,
  80. noEmit: true,
  81. },
  82. include: ['./package.json', './src/**/*.*'],
  83. references,
  84. },
  85. undefined,
  86. 2,
  87. )}\n`,
  88. )
  89. await tsConfig.close()
  90. await writeFile(
  91. new URL('./tsconfig.build.json', packageRoot),
  92. `${JSON.stringify(
  93. {
  94. extends: '../../../tsconfig.shared',
  95. compilerOptions: {
  96. outDir: './lib',
  97. rootDir: './src',
  98. resolveJsonModule: false,
  99. noImplicitAny: false,
  100. skipLibCheck: true,
  101. },
  102. include: ['./src/**/*.*'],
  103. exclude: ['./src/**/*.test.ts'],
  104. references,
  105. },
  106. undefined,
  107. 2,
  108. )}\n`,
  109. )
  110. console.log('Done')