Last active
July 10, 2023 11:56
-
-
Save SherryH/b4ce7d4f239538d5f1d662180fcb2f78 to your computer and use it in GitHub Desktop.
A script for finding all parents and ancestors of the given package. Used for finding the repos consuming outdated packages to assess risks of migration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const yaml = require('js-yaml'); | |
const fsPromise = require('fs/promises'); | |
const fs = require('fs'); | |
const { AsyncParser } = require('json2csv'); | |
console.log('running script...'); | |
// stream is good for reading a large file. maybe next time | |
async function openFile(fileName) { | |
let data; | |
try { | |
data = await fsPromise.readFile(fileName, { encoding: 'utf8' }); | |
return yaml.load(data); | |
} catch (err) { | |
console.log(err); | |
} | |
} | |
function isPackageNode(keyNode) { | |
const packageNodes = ['/@smartly', 'packages/']; | |
return packageNodes.some((key) => keyNode.startsWith(key)); | |
} | |
function getLastPackageNodeParent(parents) { | |
const packageNode = parents[parents.length - 1]; | |
return transformPackageFormat(packageNode); | |
} | |
function containsRootNode(nodes) { | |
return nodes.some((node) => node?.startsWith('packages/')); | |
} | |
function isInterestedNode(keyNode) { | |
const interestedKeys = ['dependencies', '/@smartly', 'packages', 'importers']; | |
return interestedKeys.some((key) => keyNode.startsWith(key)); | |
} | |
function find1Parent(obj, packageName, packageVersion, parent) { | |
if (obj && typeof obj === 'object') { | |
if ( | |
typeof obj[packageName] === 'string' && | |
obj[packageName].startsWith(packageVersion) | |
) { | |
return parent; | |
} | |
for (key in obj) { | |
// a key can be a node (its value is an object) | |
// or a key can be a dependency (its value is a string) | |
// keep the node if it starts with "dependencies", "/@smartly" or "packages" or "importers" | |
if (isInterestedNode(key)) { | |
return find1Parent(obj[key], packageName, packageVersion, [ | |
...parent, | |
key, | |
]); | |
} | |
} | |
// no match found, aded 'undefined' to the parent array | |
if (!parent) { | |
debugger; | |
} | |
} // end obj === 'object' | |
if (!parent) { | |
debugger; | |
} | |
} | |
function findParents( | |
obj, | |
packageName, | |
packageVersion, | |
parents = [], | |
matches = [], | |
originalObj | |
) { | |
if (obj && typeof obj === 'object') { | |
if ( | |
typeof obj[packageName] === 'string' && | |
obj[packageName].startsWith(packageVersion) | |
) { | |
matches.push({ | |
parents, | |
oldSmartlyUI: `${packageName}/${obj[packageName]}`, | |
}); | |
} | |
// the level of the obj does not contain the package | |
// loop through all keys to do recursive search | |
for (const key in obj) { | |
if (Object.prototype.hasOwnProperty.call(obj, key)) { | |
if (isInterestedNode(key)) { | |
const result = findParents( | |
obj[key], | |
packageName, | |
packageVersion, | |
[...parents, key], | |
matches, | |
originalObj | |
); | |
} | |
} // end Object.prototype | |
// return matches; | |
} | |
} | |
} | |
// the matches object now contains the parents of the package | |
// we can transform the parents format to trace its ancestors all the way up | |
// transform the first match to {packageName: packageVersion} | |
// then apply findParents() | |
// until we can't break the parent down into packageName: packageVersion | |
function transformPackageFormat(packagePath) { | |
// input /@smartly/button/3.113.1_w2bf6tsmpojz6yvlz3yaqqr2a4 | |
// output @smartly/button: 3.113.1_w2bf6tsmpojz6yvlz3yaqqr2a4 | |
// remove the initial '/' from the path /@smartly | |
packagePath = packagePath.replace(/^\//, ''); | |
// regex match for last / | |
const reg = /(\/)(?!.*\1)/; | |
// [ '@smartly/button', '/', '3.113.1_w2bf6tsmpojz6yvlz3yaqqr2a4' ] | |
const result = packagePath.split(reg); | |
return { packageName: result[0], packageVersion: result[2] }; | |
} | |
function hasValidPackageVersion(packageVersion) { | |
if (!packageVersion.match(/\d/)) { | |
console.log('packageVersion no number found'); | |
return false; | |
} | |
return true; | |
} | |
function findRootParentsByLoopingThroughPackages( | |
packageObj, | |
packageName, | |
packageVersion, | |
parents = [] | |
) { | |
if (!hasValidPackageVersion(packageVersion)) { | |
return; | |
} | |
const parent = find1ParentByLoopingThroughPackages( | |
packageObj, | |
packageName, | |
packageVersion | |
); | |
if (parent) { | |
parents.push(parent); | |
const { packageName, packageVersion } = transformPackageFormat(parent); | |
debugger; | |
findRootParentsByLoopingThroughPackages( | |
packageObj, | |
packageName, | |
packageVersion, | |
parents | |
); | |
} else { | |
console.log('no more parent can be found'); | |
// console.log(parents); | |
return; | |
} | |
} | |
// inputs: data, packageName, packageVersion, parents = [] | |
// output: parents | |
function find1ParentByLoopingThroughPackages( | |
packageObj, | |
packageName, | |
packageVersion, | |
parents = [] | |
) { | |
// dataPackages is an object.packages - where the internal packages are | |
// we use similar ways to find the first matching parent | |
const foundSometing = []; | |
for (const key in packageObj) { | |
const parent = find1Parent( | |
packageObj[key], | |
packageName, | |
packageVersion, | |
key | |
); | |
if (parent) { | |
foundSometing.push(parent); | |
return parent; | |
} | |
} | |
if (!foundSometing.length) { | |
console.log('already reach root package'); | |
return; | |
} | |
} | |
function getPackageObject(data, obj = {}) { | |
let individualPackage = {}; | |
let packageObj = { ...data.packages, ...data.importers }; | |
for (const key in packageObj) { | |
// obj[key] is an object | |
for (const dependency in packageObj[key]) { | |
if (dependency === 'dependencies') { | |
individualPackage = { | |
...individualPackage, | |
[key]: packageObj[key][dependency], | |
}; | |
} | |
} | |
} | |
return individualPackage; | |
} | |
function writeToCSV(matches) { | |
const filename = 'oldSmartlyUI.csv'; | |
const writableStream = fs.createWriteStream(filename); | |
// check fields align with findParents {matches.parents} | |
const fields = ['parents', 'oldSmartlyUI', 'last2Parents']; | |
const options = { fields }; | |
const asyncParser = new AsyncParser(options); | |
asyncParser.parse(matches).pipe(writableStream); | |
} | |
async function getAllAncestors({ fileName, packageName, packageVersion }) { | |
// const fileName = './pnpm-lock-short.yaml'; | |
// const packageName = '@smartly/block'; | |
// const packageVersion = '5.114'; | |
const data = await openFile(fileName); | |
// search for the '@smartly/tokens': 3 | |
// test search for the '@smartly/block': 5.114 in ./pnpm-lock-short.yaml | |
const packageObject = getPackageObject(data); | |
// console.log(JSON.stringify(packageObject, null, 2)); | |
debugger; | |
const matches = []; | |
findParents(packageObject, packageName, packageVersion, [], matches, data); | |
// matches.parents = [@smarltly/button/3.11_sd, @smartly/block...] | |
const results = matches.map((match, matchIndex) => { | |
const { parents } = match; | |
const { packageName, packageVersion } = getLastPackageNodeParent(parents); | |
// if the packageVersion does not contain a number, break | |
// move to the next match | |
if (!hasValidPackageVersion(packageVersion)) { | |
return; | |
} | |
// mutate match.parents with all ancestors | |
findRootParentsByLoopingThroughPackages( | |
packageObject, | |
packageName, | |
packageVersion, | |
match.parents | |
); | |
// parentPackage = parent && parent.find(isPackageNode); | |
// if a parent is found | |
// parent && match.parents.push(parent); | |
}); | |
matches.forEach((match) => { | |
match.last2Parents = match.parents.slice(-2); | |
}); | |
console.log(results.length); | |
// matches: [{parents, oldSmartlyUI, last2Parents},{parents, oldSmartlyUI, last2Parents}] | |
return matches; | |
} | |
async function main() { | |
const fileName = './pnpm-lock.yaml'; | |
// const packageName = '@smartly/tokens'; | |
// const packageVersion = '3'; | |
const packageList = [ | |
{ packageName: '@smartly/tokens', packageVersion: '3' }, | |
{ packageName: '@smartly/tokens', packageVersion: '4' }, | |
{ packageName: '@smartly/button', packageVersion: '3' }, | |
{ packageName: '@smartly/button', packageVersion: '4' }, | |
, | |
{ packageName: '@smartly/container', packageVersion: '3' }, | |
, | |
{ packageName: '@smartly/container', packageVersion: '4' }, | |
{ packageName: '@smartly/input', packageVersion: '3' }, | |
, | |
{ packageName: '@smartly/input', packageVersion: '4' }, | |
]; | |
try { | |
const oldSmartlyUIRepos = await packageList.map(async (package) => { | |
const { packageName, packageVersion } = package; | |
const ancestors = await getAllAncestors({ | |
fileName, | |
packageName, | |
packageVersion, | |
}); | |
// writeToCSV(ancestors); | |
return ancestors; | |
}); | |
const resolvedRepos = await Promise.all(oldSmartlyUIRepos); | |
console.log('===old smartly ui repos==='); | |
console.log(resolvedRepos); | |
writeToCSV(resolvedRepos.flat()); | |
} catch { | |
console.log('error!!'); | |
} | |
// write to a csv file | |
} | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment