nx-fs
v2.0.1
Published
Smart filesystem layer with intelligent file discovery, cross-platform paths, and auto-format detection. Drop-in replacement for Node's fs with superpowers.
Maintainers
Keywords
Readme
nx-fs
Smart filesystem layer with intelligent file discovery, cross-platform paths, and auto-format detection. Drop-in replacement for Node's fs with superpowers.
nx-fs feels like fs, but with superpowers: ✅ All standard fs methods work as expected ✅ Plus smart discovery when files aren't found ✅ Plus auto-format detection (JSON, YAML, Markdown, etc.) ✅ Plus cross-platform path handling ✅ Plus extension guessing
📚 Table of Contents
- 🌟 Why nx-disklocation?
- ✨ Features
- 📦 Installation
- 🚀 Quick Start
- 📖 Core Concepts
- 🎯 API Reference
- 📚 Usage Examples
- 🏗️ Real-World Use Cases
- 🎓 Advanced Topics
- 🤝 Contributing
- 🗺️ Roadmap
🌟 Why nx-fs?
The Problem: Node's fs module is powerful but lacks modern conveniences. You need manual path handling, format detection, file discovery, and cross-platform compatibility.
The Solution: nx-fs is a drop-in replacement for fs with superpowers. It feels exactly like fs, but adds intelligent file discovery, auto-format detection, cross-platform paths, and extension guessing - all while maintaining 100% compatibility with existing fs code.
// Standard fs usage (works exactly the same):
import nxfs from 'nx-fs';
const content = nxfs.readFileSync('file.txt', 'utf-8');
// 🆕 Plus smart features:
const config = await nxfs.smartRead('config'); // Auto-detects JSON/YAML
await nxfs.smartWrite('config', { foo: 'bar' }); // Auto-serializes
const result = await nxfs.find('test'); // Intelligent discovery
const path = nxfs.guessFile('readme'); // Extension guessing
// All existing fs methods work unchanged:
nxfs.writeFileSync('file.txt', 'content');
nxfs.existsSync('file.txt');
nxfs.readdirSync('./');✨ Features
🎯 Smart Search Strategies
- Upward Traversal: Automatically searches parent directories to find project roots
- Sibling Discovery: Falls back to sibling directories (perfect for monorepos!)
- Recursive Descent: Searches all subdirectories within found roots
- Multi-Extension Support: Try multiple file extensions in priority order
- 🆕 Two-Tier Smart Fetch System: Valid found + comprehensive troubleshooting
- 🆕 Tier 1 (Valid Found): Only returns
found: truefor valid strategy results - 🆕 Tier 2 (Troubleshooting): Discovers EVERYTHING for debugging and suggestions
- 🆕 Strategy Presets: Conservative, standard, aggressive, troubleshooting modes
- 🆕 Content Registry Integration: Specialized preset for metadata contract handling
- 🆕 Smart Path Normalization: Handles all path variations seamlessly
- 🆕 Cross-Platform Path Support: Works identically on Windows, Mac, and Linux
🔄 Drop-in fs Replacement
- 100% fs Compatible: All standard
fsmethods work exactly as expected - Zero Breaking Changes: Existing code continues to work unchanged
- Same API:
readFileSync,writeFileSync,existsSync, etc. all work identically
🧠 Smart Features
- 🆕 Auto-Format Detection: Automatically parses JSON, YAML, Markdown, etc.
- 🆕 Auto-Serialization: Smart writing with format detection
- 🆕 Extension Guessing:
guessFile('config')findsconfig.jsonorconfig.yaml - 🆕 Intelligent Discovery:
find('test')searches intelligently across directories
🔧 Intelligent Troubleshooting
- Path Analysis: Analyze any path to understand what's there
- Method Recommendations: Get told exactly which SDK methods to use
- Configuration Suggestions: Receive optimal configuration for your project structure
- Priority Ranking: HIGH/MEDIUM/LOW priority recommendations
📊 Rich Feedback
- Search Strategy Reporting: Know if a file was found in parent or sibling directories
- Location Descriptions: Human-readable explanations of where things were found
- Console Logging: Beautiful, emoji-enhanced output for debugging
- Detailed Results: Comprehensive result objects with all the information you need
🛠️ Developer Experience
- Full TypeScript Support: Complete type definitions included
- Zero Dependencies: Uses only Node.js built-ins
- Flexible Configuration: Case-sensitive/insensitive, custom extensions, depth limits
- 🆕 Strategy-Based Search: Define complex search patterns with automatic fallbacks
- 🆕 Preset Strategies: Ready-to-use strategies for common use cases
- 🆕 Content Management Integration: Metadata-aware results for AI/content systems
- 🆕 Migration Assistance: Automatic suggestions for file organization
- Extensive Documentation: README, guides, and 20+ examples
📦 Installation
npm install nx-fsyarn add nx-fspnpm add nx-fs🚀 Quick Start
Drop-in fs Replacement
import nxfs from 'nx-fs';
// Works exactly like fs - zero changes needed!
const content = nxfs.readFileSync('file.txt', 'utf-8');
nxfs.writeFileSync('file.txt', 'new content');
if (nxfs.existsSync('file.txt')) {
console.log('File exists!');
}🆕 Smart Features
import nxfs from 'nx-fs';
// Smart read - auto-detects format:
const config = await nxfs.smartRead('config');
// Tries: config.json, config.yaml, config.yml, config.toml
// Auto-parses based on extension!
// Smart write - auto-serializes:
await nxfs.smartWrite('config', { foo: 'bar' });
// Automatically serializes to JSON
// Guess file extension:
const path = nxfs.guessFile('readme');
// Returns: 'readme.md' if it exists
// Intelligent discovery:
const result = await nxfs.find('test');
// Uses standard search strategy with fallbacks🆕 Path Normalization (All Variations Work)
// All these work identically:
await nxfs.smartRead('config');
await nxfs.smartRead('./config');
await nxfs.smartRead('.\\config'); // Windows
await nxfs.smartRead('path/to/config');
// Console output:
// 📍 Input: "./config"
// Normalized: "config"
// ℹ️ Cleaned from: "./config"⚙️ Configuration
nx-fs is ERC-compliant and fully configurable via environment variables.
Default Configuration
By default, nx-fs uses the standard strategy:
| Setting | Default Value | Description |
|---------|---------------|-------------|
| NX_FS_STRATEGY | standard | Search strategy (conservative/standard/aggressive) |
| NX_FS_MAX_PARENT_DEPTH | 5 | Parent directory search depth |
| NX_FS_MAX_CHILD_DEPTH | 2 | Child directory search depth |
| NX_FS_MAX_SIBLING_DEPTH | 1 | Sibling directory search depth |
| NX_FS_EXTENSIONS | .json,.yaml,.yml,.md,.txt,.toml | File extensions to try |
Configuration via .env
Create a .env file to customize behavior:
# Use aggressive search strategy
NX_FS_STRATEGY=aggressive
# Search deeper in parent directories
NX_FS_MAX_PARENT_DEPTH=10
# Search deeper in child directories
NX_FS_MAX_CHILD_DEPTH=3
# Add custom extensions
NX_FS_EXTENSIONS=.json,.yaml,.toml,.ini,.confStrategy Presets
Conservative (shallow search):
- Parent depth: 3 levels
- Child depth: 1 level
- No sibling search
- Best for: Strict directory hierarchies
Standard (balanced - DEFAULT):
- Parent depth: 5 levels
- Child depth: 2 levels
- Sibling depth: 1 level
- Best for: Most projects, including monorepos
Aggressive (deep search):
- Parent depth: 10+ levels
- Child depth: 3+ levels
- Sibling depth: 2+ levels
- Best for: Complex nested structures, discovery mode
Programmatic Configuration
Override environment variables in code:
import { NxFs } from 'nx-fs';
const nxfs = new NxFs({
strategy: {
preset: 'aggressive',
maxParentDepth: 10,
maxChildDepth: 3,
maxSiblingDepth: 2,
},
extensions: ['.json', '.yaml', '.toml'],
features: {
warnOnFallback: false, // Disable warnings
},
});Runtime Configuration Updates
Update configuration at runtime:
import nxfs from 'nx-fs';
// Update strategy
nxfs.updateConfig({
strategy: {
preset: 'conservative',
maxParentDepth: 3,
},
});ERC Compliance
nx-fs follows the Environment Requirements Contract (ERC) 1.1.0 standard:
✅ All environment variables are documented
✅ .env.example generated automatically
✅ erc-manifest.json included
✅ No required variables (all optional with defaults)
✅ Type-safe configuration with validation
Verify ERC compliance:
npm run erc-verifySee .env.example for complete configuration reference.
const aggressiveResult = finder.fetchWithPreset('config', 'aggressive'); // Deep search everywhere: exact + 10 levels up + 3 levels down + deep siblings
const troubleshootResult = finder.fetchWithPreset('config', 'troubleshooting'); // Discovers EVERYTHING for debugging (may be slow)
const aiResult = finder.fetchWithPreset('prompt', 'instructions'); // Automatically tries: .metadata/instruction/ → .metadata/prompt/ → .metadata/skills/
### **🆕 Content Registry Integration (Advanced)**
```typescript
// Perfect for content management systems with /content/ subfolder handling
const result = finder.fetchWithPreset('test', 'contentRegistryWithFallback', '.metadata', ['instruction']);
// Two-tier system: finds via valid strategies + discovers all files for troubleshooting
// Content-registry can build metadata contracts for fallback paths + migration suggestionsimport { FileFinder } from 'nx-disklocation';
// Create a finder instance
const finder = new FileFinder({
extensions: ['.yaml', '.json', '.toml'],
caseInsensitive: false,
maxDepth: 10
});
// 🆕 Smart Fetch with path normalization (all these work identically)
const result = finder.smartFetch('./config', { // ./ prefix
strategies: StrategyPresets.standard(),
warnOnFallback: true
});
// Or use preset (recommended):
const result2 = finder.fetchWithPreset('config', 'standard');
// Or with Windows-style paths:
const result3 = finder.smartFetch('.\\config', { // Windows backslashes
strategies: StrategyPresets.standard()
});
// All produce normalized output:
// 📍 Input: "./config"
// Normalized: "config"
// ℹ️ Cleaned from: "./config"
// Traditional single-strategy approach still works
const traditionalResult = finder.findFile('config', 'src');
if (traditionalResult.found) {
console.log(`✅ Found at: ${traditionalResult.filePath}`);
console.log(`📂 Root folder: ${traditionalResult.rootPath}`);
console.log(`🔍 Search method: ${traditionalResult.searchStrategy}`);
}📖 Core Concepts
How It Works
nx-disklocation uses a three-step process to find files:
Find the Root Folder
- Searches upward through parent directories (higher priority)
- Falls back to sibling directories if not found (lower priority)
- Provides feedback on where and how the root was found
Search Within Root
- Recursively searches all subdirectories
- Respects configured extension priorities
- Returns first match or all matches
Return Rich Results
- Full paths and metadata
- Search strategy used
- Location descriptions
Search Priority Example
monorepo/
├── packages/
│ ├── app-a/ ← You are here
│ │ └── src/
│ │ └── component.tsx
│ ├── app-b/
│ │ └── src/ ← Sibling (lower priority)
│ └── shared/
│ └── src/ ← Sibling (lower priority)
└── src/ ← Parent (higher priority) ✅ CHOSEN
Priority 1: Searches upward (monorepo/packages/app-a/src → monorepo/src)
Priority 2: Searches siblings (app-b/src, shared/src)🆕 Smart Fetch: Multi-Strategy Search System
The Problem: Sometimes you need complex fallback logic. Looking for .metadata/content/test.md but it should fall back to .metadata/skills/test.md if not found.
The Solution: Define multiple search strategies with priorities:
// Define strategies for content → skills fallback
const strategies = [
{
name: 'primary-content',
description: 'Look in .metadata/content/',
searchPaths: ['.metadata/content'],
extensions: ['.md', '.json'],
priority: 1 // Try first
},
{
name: 'fallback-skills',
description: 'Fallback to .metadata/skills/',
rootFolders: ['.metadata'], // Find .metadata folder first
searchSiblings: true, // Then search its siblings
extensions: ['.md', '.json'],
priority: 2 // Try if primary fails
}
];
// Execute with automatic fallback
const result = finder.smartFetch('test', {
strategies,
warnOnFallback: true, // Log warnings when using fallback
stopOnFirst: true // Stop at first successful match
});Strategy Priority Flow:
- Priority 1: Direct path search in
.metadata/content/ - Priority 2: Find
.metadatafolder, then search sibling folders - Automatic Logging: Warns when fallback strategies are used
🆕 Smart Path Normalization
The Problem: Users type paths in many different ways. Should test, ./test, and .\\test (Windows) all work identically?
The Solution: Automatic path normalization handles all variations seamlessly:
// All these inputs work identically:
finder.smartFetch('test', options);
finder.smartFetch('./test', options);
finder.smartFetch('.\\test', options); // Windows
finder.smartFetch('instruction/test', options);
finder.smartFetch('./instruction/test', options);
finder.smartFetch('.\\instruction\\test', options); // Windows
finder.smartFetch('/absolute/unix/path', options); // Unix absolute
finder.smartFetch('C:\\absolute\\windows\\path', options); // Windows absolute
// Console output:
// 📍 Input: "./instruction/test"
// Normalized: "instruction/test"
// ℹ️ Cleaned from: "./instruction/test"What Gets Normalized:
- ✅ Prefixes:
./,.\\,/(relative only) - ✅ Separators:
\→/(cross-platform) - ✅ Duplicates:
//→/ - ✅ Trailing:
path/→path - ✅ Platform: Windows drive letters, UNC paths, Unix absolute paths
PathNormalizer Utilities:
// Check if two paths are equivalent
PathNormalizer.areEquivalent('test', './test'); // true
// Resolve path relative to base
PathNormalizer.resolve('/base', './path'); // '/base/path'
// Detect path characteristics
const info = PathNormalizer.normalize('./test/');
console.log(info.normalized); // 'test'
console.log(info.isAbsolute); // false
console.log(info.segments); // ['test']🎯 API Reference
Constructor
new FileFinder(config?: FileFinderConfig)Configuration Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| extensions | string[] | ['.md', '.txt', '.yaml', '.yml', '.json'] | File extensions to try |
| maxDepth | number | 10 | Maximum levels to search upward |
| caseInsensitive | boolean | false | Enable case-insensitive matching |
Key Methods Overview
| Method | Purpose | Use Case |
|--------|---------|----------|
| findFile() | Simple file search in root folder | Basic file discovery |
| smartFetch() | 🆕 Multi-strategy search with fallbacks | Chain of responsibility patterns |
| fetchWithPreset() | 🆕 Pre-built strategy presets | Conservative, standard, aggressive, troubleshooting, contentRegistryWithFallback |
| findRootFolderEnhanced() | Enhanced folder search | Complex directory structures |
| locationTroubleshooting() | Analyze paths and get recommendations | Debugging and optimization |
PathNormalizer Utilities
| Method | Purpose | Example |
|--------|---------|---------|
| PathNormalizer.normalize() | Clean and standardize path input | normalize('./test/') → { normalized: 'test', ... } |
| PathNormalizer.areEquivalent() | Check if two paths are equivalent | areEquivalent('test', './test') → true |
| PathNormalizer.resolve() | Resolve path relative to base | resolve('/base', './path') → '/base/path' |
How to Choose the Right Method
// 🟢 SIMPLE: Just find a file in a known root folder
const result = finder.findFile('config', 'src');
// 🟡 CONSERVATIVE: Only exact + shallow search
const result = finder.fetchWithPreset('config', 'conservative');
// Perfect for controlled environments
// 🟠 STANDARD: Balanced search (recommended for most cases)
const result = finder.fetchWithPreset('config', 'standard');
// Good balance of thoroughness and performance
// 🟠 AGGRESSIVE: Deep search everywhere
const result = finder.fetchWithPreset('config', 'aggressive');
// Find anything, anywhere (may be slow)
// 🔴 TROUBLESHOOTING: Discover EVERYTHING
const result = finder.fetchWithPreset('config', 'troubleshooting');
// For debugging - finds all possible matches
// 🟠 CONTENT REGISTRY: Special handling for AI content
const result = finder.fetchWithPreset('test', 'contentRegistryWithFallback');
// Handles /content/ subfolder issues + metadata construction context
// 🔴 CUSTOM: Full control with two-tier system
const result = finder.smartFetch('config', {
strategies: [/* custom strategies */]
});
// Tier 1: Valid found, Tier 2: Comprehensive troubleshootingCore Methods
findFile(fileName, rootFolderName, startPath?, extensions?)
Find a file by first locating a root folder, then searching within it.
Parameters:
fileName(string): File name with or without extensionrootFolderName(string): Root folder to find firststartPath(string, optional): Starting directory (defaults toprocess.cwd())extensions(string[], optional): Extensions to try (overrides config)
Returns: FileSearchResult
interface FileSearchResult {
found: boolean;
filePath?: string;
rootPath?: string;
matchedExtension?: string;
searchStrategy?: 'parent' | 'sibling' | 'not-found';
locationDescription?: string;
error?: string;
}Example:
const result = finder.findFile('tsconfig', 'config', process.cwd(), ['.json']);
if (result.found) {
console.log(result.filePath); // '/path/to/config/tsconfig.json'
console.log(result.searchStrategy); // 'parent' or 'sibling'
console.log(result.locationDescription); // 'In parent directory (2 levels up)'
}findRootFolder(folderName, startPath?)
Find a folder by searching upward through parent directories, then siblings.
Parameters:
folderName(string): Name of the folder to findstartPath(string, optional): Starting directory
Returns: string | null - Absolute path to the folder, or null if not found
Example:
const srcPath = finder.findRootFolder('src');
if (srcPath) {
console.log(`Found src at: ${srcPath}`);
}findRootFolderEnhanced(folderName, startPath?)
Enhanced version with detailed search strategy information.
Returns: RootFolderResult
interface RootFolderResult {
found: boolean;
path?: string;
strategy?: 'parent' | 'sibling';
description?: string;
depth?: number;
}findAllFiles(fileName, rootFolderName, startPath?)
Find all files matching a name (with any extension) within a root folder.
Parameters:
fileName(string): Base file name (without extension)rootFolderName(string): Root folder to search withinstartPath(string, optional): Starting directory
Returns: string[] - Array of all matching file paths
Example:
const allConfigs = finder.findAllFiles('config', 'src');
// Returns: ['/path/to/src/config.yaml', '/path/to/src/sub/config.json', ...]locationTroubleshooting(relativePath, startPath?)
🆕 Analyze a path and get intelligent SDK recommendations!
Parameters:
relativePath(string): Path to analyzestartPath(string, optional): Starting directory
Returns: TroubleshootingResult
interface TroubleshootingResult {
analyzedPath: string;
absolutePath: string;
exists: boolean;
type?: 'file' | 'directory' | 'unknown';
findings: StrategyFinding[];
recommendations: MethodRecommendation[];
suggestions: string[];
issues: string[];
summary: string;
}Example:
const analysis = finder.locationTroubleshooting('./src/config');
console.log(analysis.summary);
// "✅ Location is a DIRECTORY with contents. Recommended methods: findFile(), listFiles()."
// Get the best method to use
const topRec = analysis.recommendations.find(r => r.priority === 'high');
console.log(topRec.method); // 'findFile()'
console.log(topRec.example); // 'const result = finder.findFile("config", "src");'smartFetch(fileName, options, startPath?)
🆕 Execute multiple search strategies with automatic fallback!
Parameters:
fileName(string): File to find (with or without extension)options(SmartFetchOptions): Configuration with strategies and optionsstartPath(string, optional): Starting directory (defaults toprocess.cwd())
Returns: SmartFetchResult
interface SmartFetchOptions {
strategies: SearchStrategy[]; // Strategies to try (sorted by priority)
warnOnFallback?: boolean; // Log warnings when using fallback strategies
stopOnFirst?: boolean; // Stop at first successful match
returnAllMatches?: boolean; // Return all matches from all strategies
}
interface SmartFetchResult {
// TIER 1: Valid found result
found: boolean; // TRUE only if valid strategy succeeded
filePath?: string; // Only if found via valid strategy
strategy?: string; // Which valid strategy found it
// TIER 2: Comprehensive troubleshooting
troubleshooting: {
allDiscovered: DiscoveredFile[]; // ALL files found (valid or not)
validMatches: DiscoveredFile[]; // Only valid matches
invalidMatches: DiscoveredFile[]; // Found but not via valid strategy
suggestedKeys: string[]; // Recommended keys to use
searchedPaths: string[]; // All paths attempted
};
attemptedStrategies: string[]; // All strategies that were attempted
warnings: string[]; // Warnings generated during search
}
interface DiscoveredFile {
path: string; // Full path to discovered file
suggestedKey: string; // Suggested key for accessing this file
location: 'exact' | 'parent' | 'child' | 'sibling'; // Location relative to search origin
depth: number; // How far from original location
strategy: string; // Which strategy found it
isValid: boolean; // Is this from a valid strategy?
}Example:
const result = finder.smartFetch('config', {
strategies: [
{ name: 'local', searchPaths: ['.'], priority: 1 },
{ name: 'parent', searchPaths: ['../'], priority: 2 }
],
warnOnFallback: true
});
if (result.found) {
console.log(`Found: ${result.filePath}`);
console.log(`Strategy: ${result.strategy}`);
if (result.fallbackUsed) {
console.log('⚠️ Used fallback strategy');
}
}fetchWithPreset(fileName, preset, contentType?, categories?)
🆕 Quick method using predefined strategy presets!
Parameters:
fileName(string): File to findpreset('conservative' | 'standard' | 'aggressive' | 'troubleshooting' | 'contentRegistryWithFallback'): Preset typerootPath(string, optional): Root path for registry presetscategories(string[], optional): Categories for contentRegistryWithFallback
Returns: SmartFetchResult with two-tier information
Example:
// Conservative: Only exact + shallow search
const result = finder.fetchWithPreset('config', 'conservative');
// Perfect for controlled environments
// Standard: Balanced search (recommended)
const result = finder.fetchWithPreset('config', 'standard');
// Good for most use cases
// Aggressive: Deep search everywhere
const result = finder.fetchWithPreset('config', 'aggressive');
// Find anything, anywhere
// Troubleshooting: Discover EVERYTHING
const result = finder.fetchWithPreset('config', 'troubleshooting');
// For debugging - exhaustive search
// Content Registry: Special handling for AI content
const result = finder.fetchWithPreset('test', 'contentRegistryWithFallback', '.metadata', ['instruction']);
// Handles /content/ subfolder issues + provides metadata construction contextPathNormalizer Class
🆕 Utilities for handling all path input variations
PathNormalizer.normalize(input: string): NormalizedPath
Normalize path input to consistent format. Handles ./test, .\\test, instruction/test, etc.
Parameters:
input(string): Path input in any format
Returns: NormalizedPath object
interface NormalizedPath {
original: string; // Original input
normalized: string; // Cleaned version
isAbsolute: boolean; // Is it an absolute path?
hasExplicitCurrentDir: boolean; // Had ./ prefix?
hasRootPrefix: boolean; // Had / prefix (but not absolute)?
segments: string[]; // Path segments
}Example:
const info = PathNormalizer.normalize('./instruction//test/');
console.log(info.original); // './instruction//test/'
console.log(info.normalized); // 'instruction/test'
console.log(info.segments); // ['instruction', 'test']
console.log(info.isAbsolute); // falsePathNormalizer.areEquivalent(path1: string, path2: string): boolean
Check if two paths are equivalent after normalization.
Example:
PathNormalizer.areEquivalent('test', './test'); // true
PathNormalizer.areEquivalent('test', '.\\test'); // true (Windows)
PathNormalizer.areEquivalent('a/b', 'a//b'); // true
PathNormalizer.areEquivalent('test', '/test'); // false (different types)PathNormalizer.resolve(basePath: string, input: string): string
Resolve path relative to base path using normalized input.
Example:
PathNormalizer.resolve('/project', './src/file.ts'); // '/project/src/file.ts'
PathNormalizer.resolve('/project', 'src/file.ts'); // '/project/src/file.ts'Strategy Presets
Predefined strategy collections with validity constraints:
StrategyPresets.conservative()
Perfect for: Strict environments, controlled file locations, performance-critical code
const strategies = StrategyPresets.conservative();
// Strategies: exact + parent(3) + child(1)
// All considerValid: trueStrategyPresets.standard() (Recommended)
Perfect for: Balanced environments, reasonable search scope, most use cases
const strategies = StrategyPresets.standard();
// Strategies: exact + parent(5) + child(2) + sibling(1)
// All considerValid: trueStrategyPresets.aggressive()
Perfect for: Monorepos, complex project structures, finding anything
const strategies = StrategyPresets.aggressive();
// Strategies: exact + parent(10) + child(3) + sibling(2)
// All considerValid: trueStrategyPresets.troubleshooting()
Perfect for: Debugging, finding all possible matches, migration planning
const strategies = StrategyPresets.troubleshooting();
// Strategies: exact + parent(∞) + child(∞) + sibling(∞)
// considerValid: false (for exhaustive discovery)StrategyPresets.contentRegistryWithFallback(rootPath?, categories?)
🆕 Specialized preset for content management systems (like content-registry)!
Solves the critical issue where content systems expect files in standard paths (.metadata/content/{category}/) but files might exist in legacy locations (.metadata/{category}/ without /content/).
Parameters:
rootPath(string, optional): Root metadata path (defaults to'.metadata')categories(string[], optional): Categories to search (defaults to['instruction', 'prompt', 'skills'])
Two-tier approach:
- Tier 1: Valid strategies find files in expected locations
- Tier 2: Troubleshooting discovers all files + provides metadata construction context
Example:
const strategies = StrategyPresets.contentRegistryWithFallback('.metadata', ['instruction']);
/*
Creates strategies:
1. primary-instruction: .metadata/content/instruction/ (valid: true)
2. no-content-instruction: .metadata/instruction/ (valid: true) - THE FIX!
3. sibling-folders: siblings of .metadata (valid: true)
4. root-fallback: .metadata/ directly (valid: false)
*/SmartFetch result provides context for content-registry:
const result = finder.smartFetch('test', { strategies });
// TIER 1: Valid found
if (result.found) {
console.log(`✅ Found via valid strategy: ${result.filePath}`);
} else {
console.log('❌ Not found via valid strategies');
}
// TIER 2: Comprehensive troubleshooting
console.log(`📊 Discovered ${result.troubleshooting.allDiscovered.length} files total`);
console.log(`✅ Valid matches: ${result.troubleshooting.validMatches.length}`);
console.log(`⚠️ Invalid matches: ${result.troubleshooting.invalidMatches.length}`);
console.log(`💡 Suggested keys: ${result.troubleshooting.suggestedKeys.join(', ')}`);Utility Methods
| Method | Description |
|--------|-------------|
| checkPath(path) | Check if a path exists and get info |
| fileExists(path) | Check if a file exists |
| directoryExistsPublic(path) | Check if a directory exists |
| listFiles(path) | List all files in a directory |
| listDirectories(path) | List all subdirectories |
| getFileInfo(path) | Get file statistics (fs.Stats) |
📚 Usage Examples
Example 1: Find Configuration Files
import { FileFinder } from 'nx-disklocation';
const finder = new FileFinder({
extensions: ['.yaml', '.yml', '.json', '.toml']
});
const configResult = finder.findFile('config', 'config');
if (configResult.found) {
console.log(`Config file: ${configResult.filePath}`);
console.log(`Extension: ${configResult.matchedExtension}`);
} else {
console.error(`Config not found: ${configResult.error}`);
}Example 2: Monorepo Package Discovery
import { FileFinder } from 'nx-disklocation';
const finder = new FileFinder();
// Find shared utilities across packages
const utilsResult = finder.findFile('index', 'shared');
if (utilsResult.searchStrategy === 'sibling') {
console.log('✅ Found in sibling package (shared configuration)');
console.log(`Location: ${utilsResult.locationDescription}`);
}Example 3: CLI Tool - Find Project Root
#!/usr/bin/env node
import { FileFinder } from 'nx-disklocation';
const finder = new FileFinder();
// Find package.json to locate project root
const pkgResult = finder.findFile('package.json', 'my-app');
if (!pkgResult.found) {
console.error('❌ Not in a valid project directory');
process.exit(1);
}
console.log(`✅ Project root: ${pkgResult.rootPath}`);
// Continue with your CLI tool logic...Example 4: Build Tool - TypeScript Config Discovery
import { FileFinder } from 'nx-disklocation';
const finder = new FileFinder({
extensions: ['.json']
});
// Find all TypeScript configs in project
const allTsConfigs = finder.findAllFiles('tsconfig', 'src');
allTsConfigs.forEach(configPath => {
console.log(`Building with config: ${configPath}`);
// Run TypeScript compiler for each config
});Example 5: Smart Path Analysis
import { FileFinder } from 'nx-disklocation';
const finder = new FileFinder();
// Not sure which method to use? Ask the troubleshooter!
const analysis = finder.locationTroubleshooting('./src/components');
if (!analysis.exists) {
console.log('Path does not exist. Suggestions:');
analysis.suggestions.forEach(s => console.log(` - ${s}`));
}
// Get high-priority recommendations
const highPriority = analysis.recommendations.filter(r => r.priority === 'high');
highPriority.forEach(rec => {
console.log(`\n📌 ${rec.method}`);
console.log(` Reason: ${rec.reason}`);
console.log(` Example:\n${rec.example}`);
});Example 6: Custom Extension Priority
import { FileFinder } from 'nx-disklocation';
// For a TypeScript project, prioritize .ts files
const finder = new FileFinder({
extensions: ['.ts', '.tsx', '.js', '.jsx']
});
const indexFile = finder.findFile('index', 'src');
// Tries index.ts first, then .tsx, then .js, then .jsxExample 7: Case-Insensitive Search (Cross-Platform)
import { FileFinder } from 'nx-disklocation';
// Works across Windows, macOS, Linux
const finder = new FileFinder({
caseInsensitive: true
});
const readme = finder.findFile('readme', 'docs');
// Finds README.md, readme.md, ReadMe.MD, etc.Example 8: 🆕 Smart Fetch with Automatic Fallback
import { FileFinder, StrategyPresets } from 'nx-disklocation';
const finder = new FileFinder();
// Content registry pattern - your exact use case!
// Looks for content, falls back to skills, then project root
const result = finder.fetchWithPreset('test', 'contentRegistry');
if (result.found) {
console.log(`✅ Found: ${result.filePath}`);
console.log(`Strategy: ${result.strategy}`);
if (result.fallbackUsed) {
console.log('⚠️ Used fallback - primary location not found');
}
}Example 9: 🆕 Custom Multi-Strategy Search
import { FileFinder } from 'nx-disklocation';
const finder = new FileFinder();
// Define custom strategies for complex scenarios
const strategies = [
{
name: 'workspace-config',
description: 'Look in workspace root config',
searchPaths: ['config'],
extensions: ['.yaml', '.json'],
priority: 1,
},
{
name: 'package-config',
description: 'Look in current package config',
searchPaths: ['packages/current/config', 'src/config'],
extensions: ['.yaml', '.json'],
priority: 2,
},
{
name: 'shared-config',
description: 'Look in shared packages',
rootFolders: ['packages'],
searchSiblings: true,
extensions: ['.yaml', '.json'],
priority: 3,
},
];
const result = finder.smartFetch('app-config', {
strategies,
warnOnFallback: true, // Log when using fallbacks
stopOnFirst: true, // Stop at first match
});
console.log(`Strategies attempted: ${result.attemptedStrategies.join(' → ')}`);Example 10: 🆕 AI Content Management System
import { FileFinder } from 'nx-disklocation';
class AIContentManager {
private finder: FileFinder;
constructor() {
this.finder = new FileFinder({
extensions: ['.md', '.txt', '.json']
});
}
// Find content with automatic fallback chain
findContent(contentName: string): string | null {
const strategies = [
{
name: 'primary-content',
description: 'Primary content repository',
searchPaths: ['.metadata/content'],
priority: 1,
},
{
name: 'skills-fallback',
description: 'Fallback to skills repository',
rootFolders: ['.metadata'],
searchSiblings: true,
priority: 2,
},
{
name: 'project-fallback',
description: 'Project root fallback',
searchPaths: ['.'],
priority: 3,
},
];
const result = this.finder.smartFetch(contentName, {
strategies,
warnOnFallback: true,
});
return result.found ? result.filePath! : null;
}
}
// Usage in AI pipeline
const manager = new AIContentManager();
const contentPath = manager.findContent('user-preferences');
// Automatically tries: content/ → skills/ → rootExample 11: 🆕 Path Normalization (All Input Variations Work)
import { FileFinder, PathNormalizer } from 'nx-disklocation';
const finder = new FileFinder();
// All these path inputs work identically due to smart normalization:
const pathVariations = [
'package.json', // Simple
'./package.json', // With ./ prefix
'.\\package.json', // Windows backslash
'package.json/', // With trailing slash
'./package.json/', // Both prefix and trailing
'.\\package.json\\', // Windows with trailing backslash
];
console.log('Path Normalization Examples:');
pathVariations.forEach(path => {
const normalized = PathNormalizer.normalize(path);
console.log(`"${path}" → "${normalized.normalized}"`);
});
// Output:
// "package.json" → "package.json"
// "./package.json" → "package.json"
// ".\package.json" → "package.json"
// "package.json/" → "package.json"
// "./package.json/" → "package.json"
// ".\package.json\" → "package.json"
// All work identically in smartFetch:
pathVariations.forEach(path => {
const result = finder.smartFetch(path, {
strategies: StrategyPresets.conservative()
});
// Each call produces the same result!
});Example 12: 🆕 Content Registry Integration (The Complete Solution)
import { FileFinder } from 'nx-disklocation';
class ContentRegistry {
private finder: FileFinder;
constructor() {
this.finder = new FileFinder({
extensions: ['.md', '.txt', '.json', '.yaml']
});
}
// 🆕 THE SOLUTION: Use contentRegistryWithFallback preset
async fetch(key: string, category?: string): Promise<ContentResult> {
// Use the advanced preset that handles /content/ subfolder issues
const result = this.finder.fetchWithPreset(
key,
'contentRegistryWithFallback',
'.metadata', // root path
category ? [category] : ['instruction', 'prompt', 'skills']
);
if (!result.found) {
throw new Error(`Content not found: ${key}`);
}
// 🆕 CRITICAL: Build metadata contract for fallback paths
const metadata = result.isPrimaryPath
? await this.loadMetadataFromPrimaryPath(result.filePath!)
: await this.constructMetadataForFallbackPath(result);
// Log warnings when using non-standard paths
if (result.fallbackUsed) {
console.warn(`⚠️ Content loaded from non-standard location`);
console.warn(` Expected: .metadata/content/${category || 'instruction'}/${key}`);
console.warn(` Actual: ${result.filePath}`);
console.warn(` Strategy: ${result.strategy}`);
// Suggest migration for legacy paths
if (result.strategyMetadata?.isLegacyPath) {
console.warn(`💡 MIGRATION SUGGESTED:`);
console.warn(` Move: ${result.filePath}`);
console.warn(` To: .metadata/content/${result.resolvedCategory}/${key}.md`);
console.warn(` Reason: Primary path supports full metadata contract`);
}
}
return {
content: await fs.readFile(result.filePath!, 'utf-8'),
metadata,
filePath: result.filePath!,
strategy: result.strategy!,
category: result.resolvedCategory,
isPrimaryPath: result.isPrimaryPath,
fallbackUsed: result.fallbackUsed,
};
}
// 🆕 Build metadata for fallback paths (solves auto-extraction!)
private async constructMetadataForFallbackPath(result: SmartFetchResult): Promise<ContentMetadata> {
const content = await fs.readFile(result.filePath!, 'utf-8');
// Extract metadata from file content (YAML front-matter, etc.)
const extracted = this.extractMetadataFromContent(content);
return {
key: path.basename(result.filePath!, path.extname(result.filePath!)),
category: result.resolvedCategory || 'instruction',
version: extracted.version || '1.0.0',
format: extracted.format || 'flex-md',
source: 'local-fallback',
resolvedAt: new Date().toISOString(),
strategyUsed: result.strategy,
...extracted,
};
}
private extractMetadataFromContent(content: string): Partial<ContentMetadata> {
// Simplified - would parse YAML front-matter, etc.
return {};
}
}
// Usage - now works even with legacy file locations!
const registry = new ContentRegistry();
const result = await registry.fetch('test', 'instruction');
// ✅ Finds file even if in .metadata/instruction/ instead of .metadata/content/instruction/
// ✅ Constructs full metadata contract for auto-extraction
// ✅ Provides migration suggestions
// ✅ Logs warnings for transparency🏗️ Real-World Use Cases
Monorepo Configuration Management
import { FileFinder } from 'nx-disklocation';
class MonorepoConfigManager {
private finder: FileFinder;
constructor() {
this.finder = new FileFinder({
extensions: ['.json', '.yaml']
});
}
getSharedConfig(configName: string): any {
// Finds config in shared package even from deep nested locations
const result = this.finder.findFile(configName, 'shared');
if (result.found) {
return require(result.filePath);
}
throw new Error(`Config ${configName} not found`);
}
getAllPackageConfigs(): string[] {
return this.finder.findAllFiles('package.json', 'packages');
}
}Build Tool Plugin
import { FileFinder } from 'nx-disklocation';
export class BuildPlugin {
private finder: FileFinder;
constructor() {
this.finder = new FileFinder({
extensions: ['.ts', '.tsx', '.js', '.jsx']
});
}
findEntryPoints(): string[] {
// Find all index files as entry points
return this.finder.findAllFiles('index', 'src');
}
locateConfig(): string {
const result = this.finder.findFile('build.config', 'config');
if (!result.found) {
throw new Error('Build config not found');
}
return result.filePath!;
}
}Documentation Generator
import { FileFinder } from 'nx-disklocation';
class DocGenerator {
private finder: FileFinder;
constructor() {
this.finder = new FileFinder({
extensions: ['.md']
});
}
generateIndex(): void {
const docsRoot = this.finder.findRootFolder('docs');
if (!docsRoot) {
console.error('Docs folder not found');
return;
}
const allDocs = this.finder.findAllFiles('', 'docs')
.filter(f => f.endsWith('.md'));
// Generate index from all markdown files
this.createIndex(allDocs);
}
}🆕 AI Content Management System
import { FileFinder, StrategyPresets } from 'nx-disklocation';
class AIContentRegistry {
private finder: FileFinder;
constructor() {
this.finder = new FileFinder({
extensions: ['.md', '.txt', '.json', '.yaml']
});
}
/**
* Find content with intelligent fallback chain
* Chain: content/ → skills/ → project root
*/
findContent(contentId: string): ContentResult {
const result = this.finder.fetchWithPreset(contentId, 'contentRegistry');
return {
found: result.found,
path: result.filePath,
strategy: result.strategy,
fallbackUsed: result.fallbackUsed,
warnings: result.warnings,
};
}
/**
* Find AI instructions with multiple fallback strategies
* Chain: instruction/ → prompt/ → skills/
*/
findInstructions(instructionName: string): string | null {
const result = this.finder.fetchWithPreset(instructionName, 'instructions');
return result.found ? result.filePath! : null;
}
/**
* Batch find all content in registry
*/
findAllContent(): string[] {
return this.finder.findAllFiles('', '.metadata');
}
}
interface ContentResult {
found: boolean;
path?: string;
strategy?: string;
fallbackUsed: boolean;
warnings: string[];
}🆕 Content Registry Integration
import { FileFinder, StrategyPresets } from 'nx-disklocation';
class ContentRegistryIntegration {
private finder: FileFinder;
constructor(localRoot: string = '.metadata') {
this.finder = new FileFinder({
extensions: ['.md', '.txt', '.json', '.yaml']
});
}
/**
* Fetch content with automatic fallback and metadata construction
* Solves the "/content/" subfolder problem completely
*/
async fetchContent(key: string, category?: string): Promise<ContentFetchResult> {
// Use the specialized preset for content registry
const result = this.finder.fetchWithPreset(
key,
'contentRegistryWithFallback',
'.metadata',
category ? [category] : ['instruction', 'prompt', 'skills']
);
if (!result.found) {
throw new Error(`Content not found: ${key}`);
}
// Build complete metadata contract
const metadata = result.needsMetadataConstruction
? await this.constructMetadata(result)
: await this.loadStandardMetadata(result.filePath!);
// Log helpful information
if (result.fallbackUsed) {
console.warn(`⚠️ Content loaded from fallback location: ${result.strategy}`);
if (result.strategyMetadata?.isLegacyPath) {
console.warn(`💡 Consider migrating to standard path for better performance`);
}
}
const content = await fs.readFile(result.filePath!, 'utf-8');
return {
content,
metadata,
filePath: result.filePath!,
resolvedCategory: result.resolvedCategory,
strategyUsed: result.strategy,
isPrimaryPath: result.isPrimaryPath,
needsMetadataConstruction: result.needsMetadataConstruction,
};
}
private async constructMetadata(result: SmartFetchResult): Promise<ContentMetadata> {
// Extract metadata from file content and result context
const content = await fs.readFile(result.filePath!, 'utf-8');
const extracted = this.extractFrontMatter(content);
return {
key: path.basename(result.filePath!, path.extname(result.filePath!)),
category: result.resolvedCategory || 'instruction',
version: extracted.version || '1.0.0',
format: extracted.format || 'flex-md',
source: result.isPrimaryPath ? 'local-primary' : 'local-fallback',
resolvedAt: new Date().toISOString(),
strategyUsed: result.strategy,
...extracted,
};
}
private extractFrontMatter(content: string): Partial<ContentMetadata> {
// Parse YAML front-matter, detect output formats, etc.
return {};
}
}
// Now content-registry works seamlessly regardless of file location!
const integration = new ContentRegistryIntegration();
const content = await integration.fetchContent('test');
// ✅ Always works - even with legacy file locations
// ✅ Always provides complete metadata contract
// ✅ Always enables auto-extraction🎓 Advanced Topics
Understanding Search Strategies
nx-disklocation supports multiple search paradigms:
Traditional Two-Tier Strategy
Parent Search (Higher Priority)
- Searches upward from current location
- Checks each parent directory level
- Stops at first match or max depth
Sibling Search (Lower Priority)
- Searches sibling directories if parent search fails
- Perfect for monorepo structures
- Finds shared packages automatically
🆕 Smart Fetch Multi-Strategy System
Define complex search patterns with automatic fallback chains:
Strategy Definition
- Each strategy has a priority (lower number = higher priority)
- Strategies can search direct paths or find folders first
- Automatic logging when fallbacks are used
Execution Flow
- Strategies executed in priority order
- Stops at first successful match (unless
returnAllMatches) - Rich feedback about which strategy succeeded
Smart Fetch Example:
🔍 Trying strategy: primary-content (Look in .metadata/content/)
❌ Strategy "primary-content" failed
🔍 Trying strategy: sibling-folders (Look in sibling folders of .metadata)
🔍 Searching sibling directories (lower priority)...
✅ Found file using strategy "sibling-folders": /path/to/.metadata/skills/test.md
⚠️ File found using fallback strategy "sibling-folders" (not primary strategy)
Primary: primary-content
Used: sibling-folders
File: /path/to/.metadata/skills/test.mdOptimizing for Your Project Structure
Use locationTroubleshooting() to get optimal configuration:
const finder = new FileFinder();
// Analyze your project structure
const analysis = finder.locationTroubleshooting('./src');
// Extract recommended configuration
const configRec = analysis.recommendations.find(r => r.config);
if (configRec) {
// Create an optimized finder
const optimizedFinder = new FileFinder(configRec.config);
}Error Handling Best Practices
const result = finder.findFile('config', 'src');
if (!result.found) {
// Check if root folder was found
if (!result.rootPath) {
console.error('Root folder "src" not found');
console.error(`Error: ${result.error}`);
} else {
console.error('File not found in root folder');
// Root exists but file doesn't
}
// Troubleshoot for recommendations
const analysis = finder.locationTroubleshooting('./src');
console.log('Suggestions:', analysis.suggestions);
}🆕 Smart Fetch Strategy Patterns
Content Registry Pattern (Your Use Case)
// Perfect for AI systems with content → skills fallback
const contentStrategies = StrategyPresets.contentRegistry('content');
/*
Creates:
1. Primary: .metadata/content/ (priority 1)
2. Fallback: .metadata siblings (priority 2)
3. Last resort: project root (priority 3)
*/Chain of Responsibility Pattern
const strategies = [
{ name: 'local', searchPaths: ['.'], priority: 1 },
{ name: 'user', searchPaths: ['~/.config'], priority: 2 },
{ name: 'system', searchPaths: ['/etc'], priority: 3 },
{ name: 'default', searchPaths: ['./defaults'], priority: 4 },
];Monorepo Package Discovery
const strategies = [
{
name: 'local-package',
searchPaths: ['src', 'lib'],
priority: 1,
},
{
name: 'shared-packages',
rootFolders: ['packages'],
searchSiblings: true,
priority: 2,
},
];Configuration Hierarchy
const configStrategies = StrategyPresets.config();
/*
Creates:
1. Config folder: config/ (priority 1)
2. Project root: ./ (priority 2)
*/Best Practices for Smart Fetch
- Priority Ordering: Lower numbers = higher priority
- Descriptive Names: Use clear strategy names for debugging
- Fallback Warnings: Enable
warnOnFallbackin development - Stop on First: Use
stopOnFirst: truefor performance - Strategy Reuse: Define strategies once, reuse across calls
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Development Setup
# Clone the repository
git clone https://github.com/yourusername/nx-disklocation.git
# Install dependencies
cd nx-disklocation
npm install
# Build
npm run build
# Run examples
npm testRunning Tests
npm run testCode Style
This project uses TypeScript strict mode and follows standard TypeScript conventions.
📄 License
MIT © [Your Name]
🙏 Acknowledgments
- Inspired by the challenges of working with complex monorepo structures
- Built with ❤️ for the Node.js and TypeScript community
📞 Support
🗺️ Roadmap
- [x] ~~Two-tier Smart Fetch system~~ ✅ COMPLETED
- [x] ~~Valid found + comprehensive troubleshooting~~ ✅ COMPLETED
- [x] ~~Strategy validity constraints~~ ✅ COMPLETED
- [x] ~~Conservative/Standard/Aggressive/Troubleshooting presets~~ ✅ COMPLETED
- [x] ~~Content Registry Integration~~ ✅ COMPLETED
- [x] ~~Metadata construction for fallback paths~~ ✅ COMPLETED
- [x] ~~Migration guidance system~~ ✅ COMPLETED
- [x] ~~Smart path normalization~~ ✅ COMPLETED
- [x] ~~Cross-platform path support~~ ✅ COMPLETED
- [ ] Glob pattern matching support
- [ ] Watch mode for file changes
- [ ] Parallel directory search for large trees
- [ ] Plugin system for custom search strategies
- [ ] Integration examples with popular build tools
- [ ] Performance benchmarks
- [ ] Caching mechanism for repeated searches
Made with ❤️ for developers who need intelligent file discovery
