implement project discoverer for nodejs

This commit is contained in:
DavHau 2022-02-25 11:03:50 +07:00
parent afcf7158ae
commit fb8928ae0a
5 changed files with 274 additions and 5 deletions

View File

@ -0,0 +1,35 @@
{
dlib,
lib,
}:
let
l = lib // builtins;
subsystems = dlib.dirNames ./.;
allDiscoverers =
l.collect
(v: v ? discover)
discoverers;
discoverProjects = source:
let
tree = dlib.prepareSourceTree { inherit source; };
in
l.flatten
(l.map
(discoverer: discoverer.discover { inherit tree; })
allDiscoverers);
discoverers = l.genAttrs subsystems (subsystem:
(import (./. + "/${subsystem}") { inherit dlib lib subsystem; })
);
in
{
inherit
discoverProjects
discoverers
;
}

View File

@ -0,0 +1,141 @@
{
dlib,
lib,
subsystem,
}:
let
l = lib // builtins;
discover =
{
tree,
}:
discoverInternal {
inherit tree;
};
getTranslatorNames = path:
let
nodes = l.readDir path;
in
l.optionals (nodes ? "package-lock.json") [ "package-lock" ]
++ l.optionals (nodes ? "yarn.lock") [ "yarn-lock" ]
++ [ "package-json" ];
# returns the parsed package.json of a given directory
getPackageJson = dirPath:
l.fromJSON (l.readFile "${dirPath}/package.json");
# returns all relative paths to workspaces defined by a glob
getWorkspacePaths = glob: tree:
if l.hasSuffix "*" glob then
let
prefix = l.removeSuffix "*" glob;
dirNames = dlib.listDirs "${tree.fullPath}/${prefix}";
in
l.map (dname: "${prefix}/${dname}") dirNames
else
[ glob ];
# collect project info for workspaces defined by current package.json
getWorkspaces = tree: parentInfo:
let
packageJson = tree.files."package.json".jsonContent;
in
l.flatten
(l.forEach packageJson.workspaces
(glob:
let
workspacePaths = getWorkspacePaths glob tree;
in
l.forEach workspacePaths
(wPath: makeWorkspaceProjectInfo tree wPath parentInfo)));
makeWorkspaceProjectInfo = tree: wsRelPath: parentInfo:
{
inherit subsystem;
name = (getPackageJson "${tree.fullPath}/${wsRelPath}").name or null;
relPath = dlib.sanitizeRelativePath "${tree.relPath}/${wsRelPath}";
translators =
l.unique
(parentInfo.translators
++ (getTranslatorNames "${tree.fullPath}/${wsRelPath}"));
subsystemInfo = {
workspaceParent = tree.relPath;
};
};
discoverInternal =
{
tree,
# Internal parameter preventing workspace projects from being discovered
# twice.
alreadyDiscovered ? {},
}:
# skip if not a nodajs project
if alreadyDiscovered ? "${tree.relPath}"
|| ! tree ? files."package.json"
then
# this will be cleaned by `flatten` for sub-directories
[]
else
let
# project info of current directory
currentProjectInfo =
{
inherit subsystem;
inherit (tree) relPath;
name = tree.files."package.json".jsonContent.name or null;
translators = getTranslatorNames tree.fullPath;
subsystemInfo =
l.optionalAttrs (workspaces != []) {
workspaces = l.map (w: w.relPath) workspaces;
};
};
workspaces = getWorkspaces tree currentProjectInfo;
# list of all projects infos found by the current iteration
foundProjects =
# current directories project info
[ currentProjectInfo ]
# workspaces defined by the current directory
++
workspaces;
# index of already found projects
# This is needed, because sub-projects also contain a `package.json`,
# and would otherwise be discovered again as an independent project.
alreadyDiscovered' =
alreadyDiscovered
//
(l.genAttrs
(l.map (p: p.relPath) foundProjects)
(relPath: null));
in
# the current directory
foundProjects
# sub-directories
# Thanks to `alreadyDiscovered`, workspace projects won't be discovered
# a second time.
++
l.flatten
((l.mapAttrsToList
(dname: dir: discoverInternal {
alreadyDiscovered = alreadyDiscovered';
tree = dir;
})
(tree.directories or {})));
in
{
inherit discover;
}

View File

@ -13,18 +13,98 @@ let
calcInvalidationHash
containsMatchingFile
dirNames
discoverers
listDirs
listFiles
prepareSourceTree
readTextFile
translators
sanitizeRelativePath
;
};
# other libs
translators = import ./translators.nix { inherit dlib lib; };
discoverers = import ../discoverers { inherit dlib lib; };
# INTERNAL
# prepare source tree for executing discovery phase
# produces this structure:
# {
# files = {
# "package.json" = {
# relPath = "package.json"
# fullPath = "${source}/package.json"
# content = ;
# jsonContent = ;
# tomlContent = ;
# }
# };
# directories = {
# "packages" = {
# relPath = "packages";
# fullPath = "${source}/packages";
# files = {
#
# };
# directories = {
#
# };
# };
# };
# }
prepareSourceTreeInternal = sourceRoot: relPath: name: depth:
let
relPath' = relPath;
fullPath' = "${sourceRoot}/${relPath}";
current = l.readDir fullPath';
fileNames =
l.filterAttrs (n: v: v == "regular") current;
directoryNames =
l.filterAttrs (n: v: v == "directory") current;
makeNewPath = prefix: name:
if prefix == "" then
name
else
"${prefix}/name";
directories =
l.mapAttrs
(dname: _:
prepareSourceTreeInternal
sourceRoot
(makeNewPath relPath dname)
dname
(depth - 1))
directoryNames;
files =
l.mapAttrs
(fname: _: l.trace fname rec {
name = fname;
fullPath = "${fullPath'}/${fname}";
relPath = makeNewPath relPath fname;
content = readTextFile fullPath;
jsonContent = l.fromJSON content;
tomlContent = l.fromTOML content;
})
fileNames;
in
{
inherit name files relPath;
fullPath = fullPath';
}
# stop recursion if depth is reached
// (l.optionalAttrs (depth > 0) {
inherit directories;
});
# EXPORTED
@ -52,11 +132,24 @@ let
patterns;
# directory names of a given directory
dirNames = dir: lib.attrNames (lib.filterAttrs (name: type: type == "directory") (builtins.readDir dir));
dirNames = dir: l.attrNames (l.filterAttrs (name: type: type == "directory") (builtins.readDir dir));
listDirs = path: lib.attrNames (lib.filterAttrs (n: v: v == "directory") (builtins.readDir path));
listDirs = path: l.attrNames (l.filterAttrs (n: v: v == "directory") (builtins.readDir path));
listFiles = path: l.attrNames (l.filterAttrs (n: v: v == "regular") (builtins.readDir path));
prepareSourceTree =
{
source,
depth ? 3,
}:
prepareSourceTreeInternal source "" "" depth;
readTextFile = file: l.replaceStrings [ "\r\n" ] [ "\n" ] (l.readFile file);
sanitizeRelativePath = path:
l.removePrefix "/" (l.toString (l.toPath "/${path}"));
in
dlib

View File

@ -5,9 +5,10 @@
let
b = builtins;
l = lib // builtins;
in
{
rec {
translate =
{
translatorName,

View File

@ -52,14 +52,13 @@ rec {
dirNames
listDirs
listFiles
readTextFile
;
dreamLock = dreamLockUtils;
inherit (dreamLockUtils) readDreamLock;
readTextFile = file: lib.replaceStrings [ "\r\n" ] [ "\n" ] (b.readFile file);
traceJ = toTrace: eval: b.trace (b.toJSON toTrace) eval;
isFile = path: (builtins.readDir (b.dirOf path))."${b.baseNameOf path}" == "regular";