123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- #!/usr/bin/env node
- /**
- * This script can be used to initiate the transition for a plugin from ESM source to
- * TS source. It will rename the files, update the imports, and add a `tsconfig.json`.
- */
- import {
- appendFile,
- opendir,
- readFile,
- open,
- writeFile,
- rm,
- } from 'node:fs/promises'
- import { createRequire } from 'node:module'
- import { argv } from 'node:process'
- import { basename, extname, join } from 'node:path'
- import { existsSync } from 'node:fs'
- const packageRoot = new URL(`../../packages/${argv[2]}/`, import.meta.url)
- let dir
- try {
- dir = await opendir(new URL('./src/', packageRoot), { recursive: true })
- } catch (cause) {
- throw new Error(`Unable to find package "${argv[2]}"`, { cause })
- }
- const packageJSON = JSON.parse(
- await readFile(new URL('./package.json', packageRoot), 'utf-8'),
- )
- if (packageJSON.type !== 'module') {
- throw new Error('Cannot convert non-ESM package to TS')
- }
- const uppyDeps = new Set(
- Object.keys(packageJSON.dependencies || {})
- .concat(Object.keys(packageJSON.peerDependencies || {}))
- .concat(Object.keys(packageJSON.devDependencies || {}))
- .filter((pkg) => pkg.startsWith('@uppy/')),
- )
- // We want TS to check the source files so it doesn't use outdated (or missing) types:
- const paths = Object.fromEntries(
- (function* generatePaths() {
- const require = createRequire(packageRoot)
- for (const pkg of uppyDeps) {
- const nickname = pkg.slice('@uppy/'.length)
- // eslint-disable-next-line import/no-dynamic-require
- const pkgJson = require(`../${nickname}/package.json`)
- if (pkgJson.main) {
- yield [
- pkg,
- [`../${nickname}/${pkgJson.main.replace(/^(\.\/)?lib\//, 'src/')}`],
- ]
- }
- yield [`${pkg}/lib/*`, [`../${nickname}/src/*`]]
- }
- })(),
- )
- const references = Array.from(uppyDeps, (pkg) => ({
- path: `../${pkg.slice('@uppy/'.length)}/tsconfig.build.json`,
- }))
- const depsNotYetConvertedToTS = references.filter(
- (ref) => !existsSync(new URL(ref.path, packageRoot)),
- )
- if (depsNotYetConvertedToTS.length) {
- // We need to first convert the dependencies, otherwise we won't be working with the correct types.
- throw new Error('Some dependencies have not yet been converted to TS', {
- cause: depsNotYetConvertedToTS.map((ref) =>
- ref.path.replace(/^\.\./, '@uppy'),
- ),
- })
- }
- let tsConfig
- try {
- tsConfig = await open(new URL('./tsconfig.json', packageRoot), 'wx')
- } catch (cause) {
- throw new Error('It seems this package has already been transitioned to TS', {
- cause,
- })
- }
- for await (const dirent of dir) {
- if (!dirent.isDirectory()) {
- const { name } = dirent
- const ext = extname(name)
- if (ext !== '.js' && ext !== '.jsx') continue // eslint-disable-line no-continue
- const filePath =
- basename(dirent.path) === name ?
- dirent.path // Some versions of Node.js give the full path as dirent.path.
- : join(dirent.path, name) // Others supply only the path to the parent.
- await writeFile(
- `${filePath.slice(0, -ext.length)}${ext.replace('js', 'ts')}`,
- (await readFile(filePath, 'utf-8'))
- .replace(
- // The following regex aims to capture all imports and reexports of local .js(x) files to replace it to .ts(x)
- // It's far from perfect and will have false positives and false negatives.
- /((?:^|\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
- '$1ts$2',
- )
- .replace(
- // The following regex aims to capture all local package.json imports.
- /\nimport \w+ from ['"]..\/([^'"]+\/)*package.json['"]\n/g,
- (originalImport) =>
- `\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n` +
- `// @ts-ignore We don't want TS to generate types for the package.json${originalImport}`,
- ),
- )
- await rm(filePath)
- }
- }
- await tsConfig.writeFile(
- `${JSON.stringify(
- {
- extends: '../../../tsconfig.shared',
- compilerOptions: {
- emitDeclarationOnly: false,
- noEmit: true,
- paths,
- },
- include: ['./package.json', './src/**/*.*'],
- references,
- },
- undefined,
- 2,
- )}\n`,
- )
- await tsConfig.close()
- await writeFile(
- new URL('./tsconfig.build.json', packageRoot),
- `${JSON.stringify(
- {
- extends: '../../../tsconfig.shared',
- compilerOptions: {
- outDir: './lib',
- paths,
- resolveJsonModule: false,
- rootDir: './src',
- },
- include: ['./src/**/*.*'],
- exclude: ['./src/**/*.test.ts'],
- references,
- },
- undefined,
- 2,
- )}\n`,
- )
- await appendFile(new URL('./.npmignore', packageRoot), `\ntsconfig.*\n`)
- console.log('Done')
|