fix(smartfile): Stream and filesystem integrations: remove fs-extra, switch to fs/promises.rename, add Web WritableStream compatibility, and use smartFs recursive directory methods; update plugins exports and README.

This commit is contained in:
2025-11-22 13:41:24 +00:00
parent 4f5eb4a4d4
commit bfc0c0576b
10 changed files with 205 additions and 98 deletions

View File

@@ -1,5 +1,14 @@
# Changelog
## 2025-11-22 - 13.0.1 - fix(smartfile)
Stream and filesystem integrations: remove fs-extra, switch to fs/promises.rename, add Web WritableStream compatibility, and use smartFs recursive directory methods; update plugins exports and README.
- Remove fs-extra dependency and its types from package.json and stop exporting fsExtra from plugins.
- Replace fs-extra rename with fs/promises.rename in SmartFile.rename to use native promises API.
- Add Web WritableStream handling in StreamFile.writeToDisk (convert to Node.js Writable via Writable.fromWeb) and ensure Web ReadableStream conversion in createReadStream.
- Use smartFs.directory(...).recursive().create() when creating directories and smartFs.directory(path).recursive().list() when listing to ensure recursive behavior.
- Update README to add Issue Reporting and Security section pointing to community.foss.global for bug/vulnerability reports.
## 2025-11-22 - 13.0.0 - BREAKING CHANGE(SmartFileFactory)
Refactor to in-memory file API and introduce SmartFileFactory; delegate filesystem operations to @push.rocks/smartfs; bump to 12.0.0

View File

@@ -48,9 +48,7 @@
"@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartrequest": "^4.2.1",
"@push.rocks/smartstream": "^3.2.5",
"@types/fs-extra": "^11.0.4",
"@types/js-yaml": "^4.0.9",
"fs-extra": "^11.3.1",
"glob": "^11.0.3",
"js-yaml": "^4.1.0"
},

17
pnpm-lock.yaml generated
View File

@@ -17,6 +17,9 @@ importers:
'@push.rocks/smartfile-interfaces':
specifier: ^1.0.7
version: 1.0.7
'@push.rocks/smartfs':
specifier: ^1.0.0
version: 1.1.0
'@push.rocks/smarthash':
specifier: ^3.2.3
version: 3.2.3
@@ -38,15 +41,9 @@ importers:
'@push.rocks/smartstream':
specifier: ^3.2.5
version: 3.2.5
'@types/fs-extra':
specifier: ^11.0.4
version: 11.0.4
'@types/js-yaml':
specifier: ^4.0.9
version: 4.0.9
fs-extra:
specifier: ^11.3.1
version: 11.3.1
glob:
specifier: ^11.0.3
version: 11.0.3
@@ -896,6 +893,9 @@ packages:
'@push.rocks/smartfile@11.2.5':
resolution: {integrity: sha512-Szmv0dFvDZBLsAOC2kJ0r0J0vZM0zqMAXT1G8XH11maU8pNYtYC1vceTpxoZGy4qbJcko7oGpgNUAlY+8LN3HA==}
'@push.rocks/smartfs@1.1.0':
resolution: {integrity: sha512-fg8JIjFUPPX5laRoBpTaGwhMfZ3Y8mFT4fUaW54Y4J/BfOBa/y0+rIFgvgvqcOZgkQlyZU+FIfL8Z6zezqxyTg==}
'@push.rocks/smartguard@3.1.0':
resolution: {integrity: sha512-J23q84f1O+TwFGmd4lrO9XLHUh2DaLXo9PN/9VmTWYzTkQDv5JehmifXVI0esophXcCIfbdIu6hbt7/aHlDF4A==}
@@ -3524,6 +3524,7 @@ packages:
resolution: {integrity: sha512-5RJYU5zWFXTQ5iRXAo75vlhK5ybZOyqEyg/szw2VtHc6ZOPcC7ruX4nnXk1OqqlY56Z7XT+WCFhV+/XPj4QwtQ==}
engines: {node: '>=20.18.0'}
hasBin: true
bundledDependencies: []
peek-readable@5.3.1:
resolution: {integrity: sha512-GVlENSDW6KHaXcd9zkZltB7tCLosKB/4Hg0fqBJkAoBgYG2Tn1xtMgXtSUuMU9AK/gCm/tTdT8mgAeF4YNeeqw==}
@@ -5939,6 +5940,10 @@ snapshots:
glob: 11.0.3
js-yaml: 4.1.0
'@push.rocks/smartfs@1.1.0':
dependencies:
'@push.rocks/smartpath': 6.0.0
'@push.rocks/smartguard@3.1.0':
dependencies:
'@push.rocks/smartpromise': 4.2.3

View File

@@ -8,6 +8,10 @@
Think of it as your go-to solution for **content manipulation**, **file transformations**, and **in-memory file operations** - all while seamlessly integrating with [@push.rocks/smartfs](https://code.foss.global/push.rocks/smartfs) for actual filesystem operations.
## Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who want to sign a contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
## 💾 Installation
```bash

View File

@@ -1,50 +1,113 @@
/**
* Mock SmartFs implementation for testing until @push.rocks/smartfs is available
* This wraps fs-extra to provide the SmartFs interface
* Mock SmartFs implementation for testing
* Provides fluent API matching @push.rocks/smartfs using native Node.js fs
*/
import { ensureDir, pathExists, remove, copy } from 'fs-extra';
import { promises as fsPromises, createReadStream, createWriteStream } from 'fs';
import * as path from 'path';
import { Readable, Writable } from 'stream';
export class MockSmartFs {
public file(filePath: string) {
return {
async read(): Promise<string | Buffer> {
return await fsPromises.readFile(filePath);
},
async write(content: string | Buffer): Promise<void> {
await ensureDir(path.dirname(filePath));
await fsPromises.writeFile(filePath, content);
},
async exists(): Promise<boolean> {
return await pathExists(filePath);
},
async delete(): Promise<void> {
await remove(filePath);
},
async stat(): Promise<any> {
return await fsPromises.stat(filePath);
},
async readStream(): Promise<Readable> {
return Promise.resolve(createReadStream(filePath));
},
async writeStream(): Promise<Writable> {
await ensureDir(path.dirname(filePath));
return Promise.resolve(createWriteStream(filePath));
},
async copy(dest: string): Promise<void> {
await copy(filePath, dest);
},
};
/**
* Mock SmartFsFile - Fluent file operations builder
*/
class MockSmartFsFile {
private filePath: string;
private options: {
encoding?: BufferEncoding;
recursive?: boolean;
} = {};
constructor(filePath: string) {
this.filePath = filePath;
}
public directory(dirPath: string) {
return {
async list(options?: { recursive?: boolean }): Promise<Array<{ path: string; isFile: boolean; isDirectory: boolean }>> {
// Configuration methods (return this for chaining)
public encoding(encoding: BufferEncoding): this {
this.options.encoding = encoding;
return this;
}
public recursive(): this {
this.options.recursive = true;
return this;
}
// Action methods (return Promises)
public async read(): Promise<string | Buffer> {
if (this.options.encoding) {
return await fsPromises.readFile(this.filePath, this.options.encoding);
}
return await fsPromises.readFile(this.filePath);
}
public async write(content: string | Buffer): Promise<void> {
// Ensure directory exists
const dirPath = path.dirname(this.filePath);
await fsPromises.mkdir(dirPath, { recursive: true });
if (this.options.encoding && typeof content === 'string') {
await fsPromises.writeFile(this.filePath, content, this.options.encoding);
} else {
await fsPromises.writeFile(this.filePath, content);
}
}
public async exists(): Promise<boolean> {
try {
await fsPromises.access(this.filePath);
return true;
} catch {
return false;
}
}
public async delete(): Promise<void> {
await fsPromises.unlink(this.filePath);
}
public async stat(): Promise<any> {
return await fsPromises.stat(this.filePath);
}
public async readStream(): Promise<Readable> {
return createReadStream(this.filePath);
}
public async writeStream(): Promise<Writable> {
// Ensure directory exists
const dirPath = path.dirname(this.filePath);
await fsPromises.mkdir(dirPath, { recursive: true });
return createWriteStream(this.filePath);
}
public async copy(dest: string): Promise<void> {
await fsPromises.copyFile(this.filePath, dest);
}
}
/**
* Mock SmartFsDirectory - Fluent directory operations builder
*/
class MockSmartFsDirectory {
private dirPath: string;
private options: {
recursive?: boolean;
} = {};
constructor(dirPath: string) {
this.dirPath = dirPath;
}
// Configuration methods (return this for chaining)
public recursive(): this {
this.options.recursive = true;
return this;
}
// Action methods (return Promises)
public async list(): Promise<Array<{ path: string; isFile: boolean; isDirectory: boolean }>> {
const entries: Array<{ path: string; isFile: boolean; isDirectory: boolean }> = [];
if (options?.recursive) {
if (this.options.recursive) {
// Recursive listing
const walk = async (dir: string) => {
const items = await fsPromises.readdir(dir);
@@ -60,12 +123,12 @@ export class MockSmartFs {
}
}
};
await walk(dirPath);
await walk(this.dirPath);
} else {
// Non-recursive listing
const items = await fsPromises.readdir(dirPath);
const items = await fsPromises.readdir(this.dirPath);
for (const item of items) {
const fullPath = path.join(dirPath, item);
const fullPath = path.join(this.dirPath, item);
const stats = await fsPromises.stat(fullPath);
entries.push({
path: fullPath,
@@ -76,20 +139,43 @@ export class MockSmartFs {
}
return entries;
},
async create(options?: { recursive?: boolean }): Promise<void> {
if (options?.recursive) {
await ensureDir(dirPath);
}
public async create(): Promise<void> {
if (this.options.recursive) {
await fsPromises.mkdir(this.dirPath, { recursive: true });
} else {
await fsPromises.mkdir(dirPath);
}
},
async exists(): Promise<boolean> {
return await pathExists(dirPath);
},
async delete(): Promise<void> {
await remove(dirPath);
},
};
await fsPromises.mkdir(this.dirPath);
}
}
public async exists(): Promise<boolean> {
try {
const stats = await fsPromises.stat(this.dirPath);
return stats.isDirectory();
} catch {
return false;
}
}
public async delete(): Promise<void> {
if (this.options.recursive) {
await fsPromises.rm(this.dirPath, { recursive: true, force: true });
} else {
await fsPromises.rmdir(this.dirPath);
}
}
}
/**
* Mock SmartFs - Main class matching @push.rocks/smartfs API
*/
export class MockSmartFs {
public file(filePath: string): MockSmartFsFile {
return new MockSmartFsFile(filePath);
}
public directory(dirPath: string): MockSmartFsDirectory {
return new MockSmartFsDirectory(dirPath);
}
}

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartfile',
version: '13.0.0',
version: '13.0.1',
description: 'High-level file representation classes (SmartFile, StreamFile, VirtualDirectory) for efficient in-memory file management in Node.js using TypeScript. Works seamlessly with @push.rocks/smartfs for filesystem operations.'
}

View File

@@ -203,7 +203,7 @@ export class SmartFile extends plugins.smartjson.Smartjson {
);
// Rename the file on disk
await plugins.fsExtra.rename(oldAbsolutePath, newAbsolutePath);
await plugins.fsPromises.rename(oldAbsolutePath, newAbsolutePath);
}
// Return the new path

View File

@@ -1,5 +1,5 @@
import * as plugins from './plugins.js';
import { Readable } from 'stream';
import { Readable, Writable } from 'stream';
type TStreamSource = (streamFile: StreamFile) => Promise<Readable | ReadableStream>;
@@ -163,7 +163,13 @@ export class StreamFile {
this.checkMultiUse();
const readStream = await this.createReadStream();
const writeStream = await this.smartFs.file(filePathArg).writeStream();
let writeStream = await this.smartFs.file(filePathArg).writeStream();
// Check if it's a Web WritableStream and convert to Node.js Writable
if (writeStream && typeof (writeStream as any).getWriter === 'function') {
// This is a Web WritableStream, convert it to Node.js Writable
writeStream = Writable.fromWeb(writeStream as any);
}
return new Promise((resolve, reject) => {
readStream.pipe(writeStream);
@@ -181,7 +187,7 @@ export class StreamFile {
this.checkMultiUse();
const filePath = plugins.path.join(dirPathArg, this.relativeFilePath);
const dirPath = plugins.path.parse(filePath).dir;
await this.smartFs.directory(dirPath).create({ recursive: true });
await this.smartFs.directory(dirPath).recursive().create();
return this.writeToDisk(filePath);
}

View File

@@ -23,7 +23,7 @@ export class VirtualDirectory {
const newVirtualDir = new VirtualDirectory(smartFs, factory);
// Use smartFs to list directory and factory to create SmartFiles
const entries = await smartFs.directory(pathArg).list({ recursive: true });
const entries = await smartFs.directory(pathArg).recursive().list();
const smartfiles = await Promise.all(
entries
.filter((entry: any) => entry.isFile)

View File

@@ -32,8 +32,7 @@ export {
};
// third party scope
import fsExtra from 'fs-extra';
import * as glob from 'glob';
import yaml from 'js-yaml';
export { fsExtra, glob, yaml };
export { glob, yaml };