Compare commits

...

9 Commits

10 changed files with 1950 additions and 2734 deletions

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
registry=https://registry.npmjs.org/

View File

@@ -1,5 +1,32 @@
# Changelog
## 2025-05-21 - 11.2.2 - fix(tests/settings)
Improve test assertions and update local settings permissions
- Refactor StreamFile tests to assert content string type using toBeTypeofString
- Update file existence tests to use resolves.toBeTrue and resolves.toBeFalse for cleaner promise handling
- Add .claude/settings.local.json to allow specific Bash permissions for pnpm test commands
## 2025-05-21 - 11.2.1 - fix(fs)
Fix inconsistent glob matching in listFileTree and update test imports and dependency versions for enhanced stability.
- Enhanced listFileTree to support **/ patterns by using dual patterns (root and nested) with deduplication.
- Updated test imports to use '@git.zone/tstest/tapbundle' for consistency across test files.
- Bumped dependency versions (@push.rocks/lik, smartpromise, smartrequest, glob) in package.json.
- Added npm configuration (.npmrc) and local settings for improved test verbosity.
## 2025-01-29 - 11.2.0 - feat(fs)
Enhanced copy method with optional replaceTargetDir option for directory replacement
- Added optional 'replaceTargetDir' option to 'copy' and 'copySync' methods in 'fs.ts'.
- The 'replaceTargetDir' option allows replacing the target directory if both source and target are directories.
## 2025-01-29 - 11.1.9 - fix(fs)
Fix directory handling in copy and copySync functions
- Ensured existing directories at destination are removed before copying over them in async copy.
- Added a similar check and handling for synchronous copySync when destination is a directory.
## 2025-01-29 - 11.1.8 - fix(fs)
Fixed copy and copySync functions to ensure they always overwrite files.

View File

@@ -1,13 +1,13 @@
{
"name": "@push.rocks/smartfile",
"private": false,
"version": "11.1.8",
"version": "11.2.3",
"description": "Provides comprehensive tools for efficient file management in Node.js using TypeScript, including handling streams, virtual directories, and various file operations.",
"main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts",
"type": "module",
"scripts": {
"test": "(tstest test/)",
"test": "(tstest test/ --verbose)",
"build": "(tsbuild --web --allowimplicitany)",
"buildDocs": "tsdoc"
},
@@ -42,29 +42,28 @@
},
"homepage": "https://code.foss.global/push.rocks/smartfile",
"dependencies": {
"@push.rocks/lik": "^6.1.0",
"@push.rocks/lik": "^6.2.2",
"@push.rocks/smartdelay": "^3.0.5",
"@push.rocks/smartfile-interfaces": "^1.0.7",
"@push.rocks/smarthash": "^3.0.4",
"@push.rocks/smartjson": "^5.0.20",
"@push.rocks/smartmime": "^2.0.4",
"@push.rocks/smartpath": "^5.0.18",
"@push.rocks/smartpromise": "^4.2.2",
"@push.rocks/smartrequest": "^2.0.23",
"@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartrequest": "^2.1.0",
"@push.rocks/smartstream": "^3.2.5",
"@types/fs-extra": "^11.0.4",
"@types/glob": "^8.1.0",
"@types/js-yaml": "^4.0.9",
"fs-extra": "^11.3.0",
"glob": "^11.0.1",
"glob": "^11.0.2",
"js-yaml": "^4.1.0"
},
"devDependencies": {
"@git.zone/tsbuild": "^2.2.1",
"@git.zone/tsbuild": "^2.5.2",
"@git.zone/tsrun": "^1.3.3",
"@git.zone/tstest": "^1.0.96",
"@push.rocks/tapbundle": "^5.5.6",
"@types/node": "^22.12.0"
"@git.zone/tstest": "^1.9.0",
"@types/node": "^22.15.21"
},
"files": [
"ts/**/*",
@@ -80,5 +79,6 @@
],
"browserslist": [
"last 1 chrome versions"
]
],
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
}

4513
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1,27 @@
# SmartFile Implementation Hints
## listFileTree Function Enhancement (ts/fs.ts:367-415)
### Issue Fixed
The `listFileTree` function previously had inconsistent behavior with `**/*.extension` patterns across different systems and glob implementations. Some implementations would miss root-level files when using patterns like `**/*.ts`.
### Solution Implemented
Modified the function to explicitly handle `**/` patterns by:
1. Detecting when a pattern starts with `**/`
2. Extracting the file pattern after `**/` (e.g., `*.ts` from `**/*.ts`)
3. Running both the original pattern and the extracted root pattern
4. Using a Set to deduplicate results and ensure consistent ordering
### Key Benefits
- Guarantees consistent behavior across all systems
- Ensures both root-level and nested files are found with `**/*` patterns
- Maintains backward compatibility
- No performance degradation due to efficient deduplication
### Test Coverage
Added comprehensive tests to verify:
- Both root and nested files are found with `**/*.ts`
- No duplicate entries in results
- Edge cases with various file extensions work correctly
This fix ensures tools like `tsbuild check **/*.ts` work reliably across all systems.

View File

@@ -1,5 +1,5 @@
import * as path from 'path';
import { expect, tap } from '@push.rocks/tapbundle';
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as smartfile from '../ts/index.js'; // adjust the import path as needed
// Test assets path
@@ -57,7 +57,7 @@ tap.test('StreamFile should return content as a buffer', async () => {
tap.test('StreamFile should return content as a string', async () => {
const streamFile = await smartfile.StreamFile.fromPath(path.join(testAssetsPath, 'mytest.json'));
const contentString = await streamFile.getContentAsString();
expect(typeof contentString).toBeTypeofString();
expect(contentString).toBeTypeofString();
// Verify the content matches what's expected
// This assumes the file contains a JSON object with a key 'key1' with value 'this works'
expect(JSON.parse(contentString).key1).toEqual('this works');

View File

@@ -1,7 +1,7 @@
import * as smartfile from '../ts/index.js';
import * as path from 'path';
import { expect, tap } from '@push.rocks/tapbundle';
import { expect, tap } from '@git.zone/tstest/tapbundle';
// ---------------------------
// smartfile.fs
@@ -15,13 +15,8 @@ tap.test('.fs.fileExistsSync -> should return an accurate boolean', async () =>
});
tap.test('.fs.fileExists -> should resolve or reject a promise', async () => {
expect(smartfile.fs.fileExists('./test/testassets/mytest.json')).toBeInstanceOf(Promise);
await smartfile.fs.fileExists('./test/testassets/mytest.json');
await smartfile.fs.fileExists('./test/testassets/notthere.json').catch((err) => {
return expect(err.message).toEqual(
"ENOENT: no such file or directory, access './test/testassets/notthere.json'"
);
});
await expect(smartfile.fs.fileExists('./test/testassets/mytest.json')).resolves.toBeTrue();
await expect(smartfile.fs.fileExists('./test/testassets/notthere.json')).resolves.toBeFalse();
});
tap.test('.fs.listFoldersSync() -> should get the file type from a string', async () => {
@@ -59,6 +54,43 @@ tap.test('.fs.listFileTree() -> should get a file tree', async () => {
expect(folderArrayArg).not.toContain('mytest.json');
});
tap.test('.fs.listFileTree() -> should find both root and nested .ts files with **/*.ts pattern', async () => {
const tsFiles = await smartfile.fs.listFileTree(
process.cwd(),
'**/*.ts'
);
// Should find both root-level and nested TypeScript files
expect(tsFiles).toContain('ts/index.ts');
expect(tsFiles).toContain('ts/classes.smartfile.ts');
expect(tsFiles).toContain('test/test.ts');
// Should find files in multiple levels of nesting
expect(tsFiles.filter(f => f.endsWith('.ts')).length).toBeGreaterThan(5);
// Verify it finds files at all levels (root 'ts/' and nested 'test/')
const hasRootLevelTs = tsFiles.some(f => f.startsWith('ts/') && f.endsWith('.ts'));
const hasNestedTs = tsFiles.some(f => f.startsWith('test/') && f.endsWith('.ts'));
expect(hasRootLevelTs).toBeTrue();
expect(hasNestedTs).toBeTrue();
});
tap.test('.fs.listFileTree() -> should handle edge cases with **/ patterns consistently', async () => {
// Test that our fix ensures no duplicate files in results
const jsonFiles = await smartfile.fs.listFileTree(
path.resolve('./test/testassets/'),
'**/*.json'
);
const uniqueFiles = [...new Set(jsonFiles)];
expect(jsonFiles.length).toEqual(uniqueFiles.length);
// Test that it finds root level files with **/ patterns
const txtFiles = await smartfile.fs.listFileTree(
path.resolve('./test/testassets/'),
'**/*.txt'
);
// Should include both direct files and nested files
expect(txtFiles).toContain('mytest.txt');
expect(txtFiles).toContain('testfolder/testfile1.txt');
});
tap.test('.fs.fileTreeToObject -> should read a file tree into an Object', async () => {
const fileArrayArg = await smartfile.fs.fileTreeToObject(
path.resolve('./test/testassets/'),

View File

@@ -1,4 +1,4 @@
import { tap, expect } from '@push.rocks/tapbundle';
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as smartfile from '../ts/index.js';

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartfile',
version: '11.1.8',
version: '11.2.2',
description: 'Provides comprehensive tools for efficient file management in Node.js using TypeScript, including handling streams, virtual directories, and various file operations.'
}

View File

@@ -74,15 +74,21 @@ export const isFile = (pathArg): boolean => {
/**
* copies a file or directory from A to B on the local disk
*/
export const copy = async (fromArg: string, toArg: string): Promise<void> => {
return await plugins.fsExtra.copy(fromArg, toArg, {overwrite: true});
export const copy = async (fromArg: string, toArg: string, optionsArg?: plugins.fsExtra.CopyOptions & { replaceTargetDir?: boolean }): Promise<void> => {
if (optionsArg?.replaceTargetDir && isDirectory(fromArg) && isDirectory(toArg)) {
await remove(toArg);
}
return await plugins.fsExtra.copy(fromArg, toArg, optionsArg as plugins.fsExtra.CopyOptions);
};
/**
* copies a file or directory SYNCHRONOUSLY from A to B on the local disk
*/
export const copySync = (fromArg: string, toArg: string): void => {
return plugins.fsExtra.copySync(fromArg, toArg, {overwrite: true});
export const copySync = (fromArg: string, toArg: string, optionsArg?: plugins.fsExtra.CopyOptionsSync & { replaceTargetDir?: boolean }): void => {
if (optionsArg?.replaceTargetDir && isDirectory(fromArg) && isDirectory(toArg)) {
removeSync(toArg);
}
return plugins.fsExtra.copySync(fromArg, toArg, optionsArg as plugins.fsExtra.CopyOptionsSync);
};
/**
@@ -377,7 +383,28 @@ export const listFileTree = async (
dot: true,
};
let fileList = await plugins.glob.glob(miniMatchFilter, options);
// Fix inconsistent **/* glob behavior across systems
// Some glob implementations don't include root-level files when using **/*
// To ensure consistent behavior, we expand **/* patterns to include both root and nested files
let patterns: string[];
if (miniMatchFilter.startsWith('**/')) {
// Extract the part after **/ (e.g., "*.ts" from "**/*.ts")
const rootPattern = miniMatchFilter.substring(3);
// Use both the root pattern and the original pattern to ensure we catch everything
patterns = [rootPattern, miniMatchFilter];
} else {
patterns = [miniMatchFilter];
}
// Collect results from all patterns
const allFiles = new Set<string>();
for (const pattern of patterns) {
const files = await plugins.glob.glob(pattern, options);
files.forEach(file => allFiles.add(file));
}
let fileList = Array.from(allFiles).sort();
if (absolutePathsBool) {
fileList = fileList.map((filePath) => {
return plugins.path.resolve(plugins.path.join(dirPath, filePath));