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:
@@ -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
|
||||
|
||||
|
||||
@@ -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
17
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,95 +1,181 @@
|
||||
/**
|
||||
* 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 }>> {
|
||||
const entries: Array<{ path: string; isFile: boolean; isDirectory: boolean }> = [];
|
||||
// Configuration methods (return this for chaining)
|
||||
public encoding(encoding: BufferEncoding): this {
|
||||
this.options.encoding = encoding;
|
||||
return this;
|
||||
}
|
||||
|
||||
if (options?.recursive) {
|
||||
// Recursive listing
|
||||
const walk = async (dir: string) => {
|
||||
const items = await fsPromises.readdir(dir);
|
||||
for (const item of items) {
|
||||
const fullPath = path.join(dir, item);
|
||||
const stats = await fsPromises.stat(fullPath);
|
||||
public recursive(): this {
|
||||
this.options.recursive = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
if (stats.isFile()) {
|
||||
entries.push({ path: fullPath, isFile: true, isDirectory: false });
|
||||
} else if (stats.isDirectory()) {
|
||||
entries.push({ path: fullPath, isFile: false, isDirectory: true });
|
||||
await walk(fullPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
await walk(dirPath);
|
||||
} else {
|
||||
// Non-recursive listing
|
||||
const items = await fsPromises.readdir(dirPath);
|
||||
for (const item of items) {
|
||||
const fullPath = path.join(dirPath, item);
|
||||
const stats = await fsPromises.stat(fullPath);
|
||||
entries.push({
|
||||
path: fullPath,
|
||||
isFile: stats.isFile(),
|
||||
isDirectory: stats.isDirectory(),
|
||||
});
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
return entries;
|
||||
},
|
||||
async create(options?: { recursive?: boolean }): Promise<void> {
|
||||
if (options?.recursive) {
|
||||
await ensureDir(dirPath);
|
||||
} else {
|
||||
await fsPromises.mkdir(dirPath);
|
||||
}
|
||||
},
|
||||
async exists(): Promise<boolean> {
|
||||
return await pathExists(dirPath);
|
||||
},
|
||||
async delete(): Promise<void> {
|
||||
await remove(dirPath);
|
||||
},
|
||||
};
|
||||
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 (this.options.recursive) {
|
||||
// Recursive listing
|
||||
const walk = async (dir: string) => {
|
||||
const items = await fsPromises.readdir(dir);
|
||||
for (const item of items) {
|
||||
const fullPath = path.join(dir, item);
|
||||
const stats = await fsPromises.stat(fullPath);
|
||||
|
||||
if (stats.isFile()) {
|
||||
entries.push({ path: fullPath, isFile: true, isDirectory: false });
|
||||
} else if (stats.isDirectory()) {
|
||||
entries.push({ path: fullPath, isFile: false, isDirectory: true });
|
||||
await walk(fullPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
await walk(this.dirPath);
|
||||
} else {
|
||||
// Non-recursive listing
|
||||
const items = await fsPromises.readdir(this.dirPath);
|
||||
for (const item of items) {
|
||||
const fullPath = path.join(this.dirPath, item);
|
||||
const stats = await fsPromises.stat(fullPath);
|
||||
entries.push({
|
||||
path: fullPath,
|
||||
isFile: stats.isFile(),
|
||||
isDirectory: stats.isDirectory(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
public async create(): Promise<void> {
|
||||
if (this.options.recursive) {
|
||||
await fsPromises.mkdir(this.dirPath, { recursive: true });
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.'
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user