Skip to content

Instantly share code, notes, and snippets.

@SherryH
Last active July 10, 2023 11:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SherryH/b4ce7d4f239538d5f1d662180fcb2f78 to your computer and use it in GitHub Desktop.
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
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