Visualizing yarn.lock files using JavaScript and Pharo
Yarn is a package manager for Node.js JavaScript.
This page describes how to parse and visualize yarn.lock
files. A yarn file can be dowloaded from a url or loaded from disk.
Note: In case the JavaScript snippet is not working view, please check JavaScript snippet.
Let's get ourselves a yarn.lock
:
We get the file from a GitHub project. We do this through a Pharo snippet:
yarnLockUrl := 'https://raw.githubusercontent.com/d3/d3/master/yarn.lock'. response := ZnEasy get: yarnLockUrl. response isSuccess ifFalse: [ Error signal: 'Could not download file' ]. lockFileContent := response contents
Now that we have the contents of the file, we need to parse it. So, we use a JavaScript snippet to do just that and transform it to JSON.
Before we run the JavaScript code, we first need to load the module into the remote server:
JSLinkApplication start. JSLinkApplication uniqueInstance installModule: '@yarnpkg/lockfile'
Now, we can run the code to parse the contents:
var lockFile = require('@yarnpkg/lockfile'); var parsedLockFileJson = lockFile.parse(lockFileContent); var lockFileJsonString = JSON.stringify(parsedLockFileJson); lockFileJsonString;
Ok, the JSON is here. We can just focus on extracting and visualizing the dependencies.
Extract the projects defined inside the yarn lock file.
lockFileJson := STONJSON fromString: lockFileJsonString. lockFileData := lockFileJson at: 'object'.
Create a list of nodes and connect them with edges.
projectIds := OrderedCollection new. edges := OrderedCollection new. lockFileData associations collect: [ :assoc | | projectName versionName projectId | projectName := assoc key copyFrom: 1 to: (assoc key indexOf: $@ startingAt: 2) - 1. versionName := assoc value at: 'version'. projectId := projectName,'@', versionName. (projectIds includes: projectId) ifFalse: [ projectIds add: projectId ]. (assoc value includesKey: 'dependencies') ifTrue: [ (assoc value at: 'dependencies') keysAndValuesDo: [ :dependencyName :dependencyValue | | targetNodeId edge | targetNodeId := lockFileData at: dependencyName, '@', dependencyValue ifPresent: [ :aTargetNode | dependencyName, '@', (aTargetNode at: #version) ] ifAbsent: [ dependencyName, '@', dependencyValue ]. edge := projectId -> targetNodeId. (edges includes: edge) ifFalse: [ edges add: edge ] ] ] ]. mainProjectName := 'd3'. rootProjectIds := projectIds copy. edges do: [ :anEdge | rootProjectIds remove: anEdge value ifAbsent: [] ]. rootProjectIds do: [ :aRootProjectId | edges add: mainProjectName -> aRootProjectId ]. projectIds add: mainProjectName. {projectIds. edges}
View dependencies using a tree.
view := GtMondrian new. view nodes stencil: [ :projectId | BrLabel new aptitude: BrGlamorousLabelAptitude; text: projectId ]; with: projectIds. view edges shape: [ :each | BlParabollaArcElement new zIndex: 0; curvatureFraction: 0.2; border: (BlBorder paint: (Color gray alpha: 0.2) width: 2); toHead: (BlArrowheadSimpleArrow new border: (BlBorder builder paint: (Color gray alpha: 0.2); width: 2; build)) ]; fromRightCenter; toLeftCenter; connect: edges from: #key to: #value. view layout custom: (GtGradHorizontalDominanceCenteredTreeLayout new levelDistance: 100; nodeDistance: 10). view