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