@feature-sliced/filesystem
Version:
A set of utilities for locating and working with FSD roots in the file system.
182 lines (178 loc) • 4.92 kB
JavaScript
// src/fsd-aware-traverse.ts
import { basename, join, parse } from "node:path";
// src/definitions.ts
var layerSequence = [
"shared",
"entities",
"features",
"widgets",
"pages",
"app"
];
var unslicedLayers = ["shared", "app"];
var conventionalSegmentNames = ["ui", "api", "lib", "model", "config"];
var crossReferenceToken = "@x";
// src/fsd-aware-traverse.ts
function getLayers(fsdRoot) {
return Object.fromEntries(
fsdRoot.children.filter(
(child) => child.type === "folder" && layerSequence.includes(basename(child.path))
).map((child) => [basename(child.path), child])
);
}
function getSlices(slicedLayer, additionalSegmentNames = []) {
const slices = {};
function traverse(folder, pathPrefix = "") {
if (isSlice(folder, additionalSegmentNames)) {
slices[join(pathPrefix, basename(folder.path))] = folder;
} else {
folder.children.forEach((child) => {
if (child.type === "folder") {
traverse(child, join(pathPrefix, basename(folder.path)));
}
});
}
}
for (const child of slicedLayer.children) {
if (child.type === "folder") {
traverse(child);
}
}
return slices;
}
function getSegments(sliceOrUnslicedLayer) {
return Object.fromEntries(
sliceOrUnslicedLayer.children.filter((child) => !isIndex(child)).map((child) => [parse(child.path).name, child])
);
}
function getAllSlices(fsdRoot, additionalSegmentNames = []) {
return Object.values(getLayers(fsdRoot)).filter(isSliced).reduce((slices, layer) => {
return {
...slices,
...Object.fromEntries(
Object.entries(getSlices(layer, additionalSegmentNames)).map(
([name, slice]) => [
name,
{ ...slice, layerName: basename(layer.path) }
]
)
)
};
}, {});
}
function getAllSegments(fsdRoot) {
return Object.entries(getLayers(fsdRoot)).flatMap(([layerName, layer]) => {
if (isSliced(layer)) {
return Object.entries(getSlices(layer)).flatMap(
([sliceName, slice]) => Object.entries(getSegments(slice)).map(([segmentName, segment]) => ({
segment,
segmentName,
sliceName,
layerName
}))
);
} else {
return Object.entries(getSegments(layer)).map(
([segmentName, segment]) => ({
segment,
segmentName,
sliceName: null,
layerName
})
);
}
});
}
function isSliced(layerOrName) {
return !unslicedLayers.includes(
basename(typeof layerOrName === "string" ? layerOrName : layerOrName.path)
);
}
function getIndexes(fileOrFolder) {
if (fileOrFolder.type === "file") {
return [fileOrFolder];
}
return fileOrFolder.children.filter(isIndex);
}
function isIndex(fileOrFolder) {
if (fileOrFolder.type === "file") {
const separator = ".";
const parsedFileName = parse(fileOrFolder.path).name;
return parsedFileName.split(separator).at(0) === "index";
}
return false;
}
function isCrossImportPublicApi(file, {
inSlice,
forSlice,
layerPath
}) {
const { dir, name } = parse(file.path);
if (isIndex(file)) {
return dir === join(layerPath, inSlice, "@x", forSlice);
}
return name === forSlice && dir === join(layerPath, inSlice, "@x");
}
function isSlice(folder, additionalSegmentNames = []) {
return folder.children.some(
(child) => conventionalSegmentNames.concat(additionalSegmentNames).includes(parse(child.path).name)
);
}
// src/resolve-import.ts
import { sep } from "node:path";
import ts from "typescript";
function resolveImport(importedPath, importerPath, tsCompilerOptions, fileExists, directoryExists) {
return ts.resolveModuleName(
importedPath,
importerPath,
normalizeCompilerOptions(tsCompilerOptions),
{
...ts.sys,
fileExists,
directoryExists
}
).resolvedModule?.resolvedFileName?.replaceAll("/", sep) ?? null;
}
var imperfectKeys = {
module: ts.ModuleKind,
moduleResolution: {
...ts.ModuleResolutionKind,
node: ts.ModuleResolutionKind.Node10
},
moduleDetection: ts.ModuleDetectionKind,
newLine: ts.NewLineKind,
target: ts.ScriptTarget
};
function normalizeCompilerOptions(compilerOptions) {
return Object.fromEntries(
Object.entries(compilerOptions).map(([key, value]) => {
if (Object.keys(imperfectKeys).includes(key) && typeof value === "string") {
for (const [enumKey, enumValue] of Object.entries(
imperfectKeys[key]
)) {
if (enumKey.toLowerCase() === value.toLowerCase()) {
return [key, enumValue];
}
}
}
return [key, value];
})
);
}
export {
conventionalSegmentNames,
crossReferenceToken,
getAllSegments,
getAllSlices,
getIndexes,
getLayers,
getSegments,
getSlices,
isCrossImportPublicApi,
isIndex,
isSlice,
isSliced,
layerSequence,
resolveImport,
unslicedLayers
};