UNPKG

@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
// 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 };