Skip to content

Instantly share code, notes, and snippets.

@pivstone
Last active October 7, 2024 15:35
Show Gist options
  • Save pivstone/c633d86c7651a48677ed5dbc3675be4c to your computer and use it in GitHub Desktop.
Save pivstone/c633d86c7651a48677ed5dbc3675be4c to your computer and use it in GitHub Desktop.
yarn to pnpm
import json
import subprocess
import re
import os
import os.path
def match_name(text, name):
pattern = rf'({re.escape(name)})@(.*)(?!.*@)'
match = re.search(pattern, text)
if match:
version = match.group(2).endswith('"') and match.group(2)[:-1] or match.group(2)
return version
# find the actual version of a package
def yarn_why(name):
output = subprocess.run(["yarn", "why", name, '--json'], check=True, capture_output=True, encoding= 'utf-8')
for line in output.stdout.split("\n"):
try:
data = json.loads(line)
if data['type'] == 'info' and data['data'].startswith('\r=> Found '):
return match_name(data['data'], name)
except:
pass
def extract_package_names(file_path):
package_names = set()
with open(file_path, 'r', encoding='utf-8') as file:
for line in file:
# Match import statements
match = re.match(r'^\s*import\s+(?:\*\s+as\s+\w+|(\w+)|\{[^}]*\})\s+from\s+[\'"]([^\'"]+)[\'"]', line)
if match and not match.group(2).startswith('.'):
package_name = match.group(2)
package_names.add(package_name)
return package_names
def scan_src_folder():
all_package_names = set()
for root, dirs, files in os.walk('src'):
for file in files:
if file.endswith('.ts') or file.endswith('.tsx'):
file_path = os.path.join(root, file)
package_names = extract_package_names(file_path)
all_package_names.update(package_names)
return all_package_names
def text_replace(content):
content = content.replace('@main', '@pnpm')
content = content.replace('yarn.lock', 'pnpm-lock.yaml')
content = content.replace('yarn deploy', 'pnpm run deploy')
content = content.replace('yarn global', 'pnpm -g')
content = content.replace('yarn test', 'pnpm run test')
content = content.replace('yarn run convert-cypress-to-allure', 'pnpm convert-cypress-to-allure')
content = content.replace('choco-scripts', 'pnpm choco-scripts')
content = content.replace('yarn', 'pnpm')
content = content.replace('Yarn', 'Pnpm')
return content
def update_file(file_path):
with open(file_path, 'rt') as file:
content = file.read()
content = text_replace(content)
# Write the file out again
with open(file_path, 'wt') as file:
file.write(content)
def update_github_workflow():
print('====== Update Github Workflow=======')
for root, dirs, files in os.walk('.github'):
for file in files:
if file.endswith('.yml') or file.endswith('.yaml'):
file_path = os.path.join(root, file)
update_file(file_path)
def update_buildspec():
print('======Update Buildspec Yml=======')
if not os.path.exists('buildspec.yml'):
print('Buildspec Not Found Skip')
else:
update_file('buildspec.yml')
def update_git_hook():
print('======Update .lintstagedrc=======')
if not os.path.exists('.lintstagedrc'):
print('.lintstagedrc Not Found Skip')
else:
update_file('.lintstagedrc')
def update_readme():
print('======Update readme=======')
if not os.path.exists('README.md'):
print('README.md Not Found Skip')
else:
update_file('README.md')
def add_common_missing_pkg(package):
pkgs = ['url-loader', '@svgr/webpack', 'graphql', 'react-router',]
for pkg in pkgs:
version = yarn_why(pkg)
if version and 'dependencies' in package:
print('Add pkg:' + pkg + ' to version:'+ version)
package['dependencies'][pkg] = version
return package
def add_common_missing_dev_pkg(package):
dev_pkgs = ['@graphql-codegen/cli', 'webpack-cli', '@babel/runtime', 'process', 'buffer', 'nyc', 'mochawesome', '@typescript-eslint/eslint-plugin', '@types/react-modal', '@types/lodash.debounce', '@types/history', '@types/testing-library__jest-dom', '@types/testing-library__react-hooks', 'webpack', 'crypto-browserify', 'stream-browserify', 'vm-browserify', '@types/lodash', '@types/react-router', ]
for pkg in dev_pkgs:
version = yarn_why(pkg)
if version and 'devDependencies' in package:
print('Add pkg:' + pkg + ' to version:'+ version)
package['devDependencies'][pkg] = version
return package
if __name__ == "__main__":
with open('package.json', 'rt') as f:
package = json.load(f)
all_package_names = set()
# update version
print('========Update Version=========')
for type in ['dependencies', 'devDependencies', 'peerDependencies', 'resolutions', ]:
if type in package:
for name in package[type]:
all_package_names.add(name)
version = yarn_why(name)
print('Update pkg:' + name + ' to version:'+ version)
package[type][name] = version
add_common_missing_dev_pkg(package)
add_common_missing_pkg(package)
print('=======Fix @typescript-eslint=====')
eslint_version = yarn_why('@typescript-eslint/eslint-plugin')
if eslint_version and 'resolutions' in package:
package['resolutions']['@typescript-eslint/eslint-plugin'] = eslint_version
package['resolutions']['@typescript-eslint/parser'] = eslint_version
print('==========Add @types/node=======')
if 'devDependencies' in package:
package['devDependencies']['@types/node'] = '16.18.55'
print('========Scan Code=======')
import_packages = scan_src_folder()
print('========Detect Phantom Dependencies=======')
for pkg_name in import_packages:
if pkg_name not in all_package_names:
version = yarn_why(pkg_name)
if version:
package['dependencies'][pkg_name] = version
print('Fix Phantom pkg:' + pkg_name)
else:
print('CANNOT fix Phantom pkg:' + pkg_name)
print('========Update Script=========')
for cmd in package['scripts']:
package['scripts'][cmd] = text_replace(package['scripts'][cmd]).replace(' webpack ', ' webpack-cli ')
# update misc
package['scripts']['preinstall'] = 'npx only-allow pnpm'
package['engines'] = {
"node": ">=16",
"pnpm": "8.8.0"
}
package["packageManager"] = "[email protected]"
with open('package.json', 'wt') as f:
json.dump(package, f, indent=2)
update_buildspec()
update_github_workflow()
update_git_hook()
update_readme()
print('========Creating pnpm lockfile=========')
subprocess.run(["pnpm", "import"], check=True)
print('========Pnpm install=========')
subprocess.run(["pnpm", "i"], check=True)
print('========Pnpm prettier=========')
subprocess.run(["pnpm", "--if-present", "prettier"], check=True)
print('========Remove Yarn Lockfile=========')
os.remove('yarn.lock')
print('========Pnpm build=========')
subprocess.run(["pnpm", "--if-present", "build"], check=True)
print('========Pnpm lint=========')
subprocess.run(["pnpm", "--if-present", "lint", ], check=True)
print('========Done=========')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment