Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
0001b433bf | |||
cd147ca38e | |||
9b0d89b9ef | |||
b34ae1362d | |||
2fa68c23a9 | |||
4083a36d8c | |||
3988ccbcb3 | |||
6eadbc654f | |||
fda1543701 | |||
a9660eda9a | |||
dfd1db152b | |||
7a32835a74 | |||
e78682d9b4 | |||
8dceea67be | |||
40018532a7 | |||
f6fb28d32f | |||
2d1ac0bd50 |
@@ -6,8 +6,8 @@ on:
|
||||
- '**'
|
||||
|
||||
env:
|
||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
||||
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Install pnpm and npmci
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
pnpm install -g @ship.zone/npmci
|
||||
|
||||
- name: Run npm prepare
|
||||
run: npmci npm prepare
|
||||
|
@@ -6,8 +6,8 @@ on:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
||||
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
pnpm install -g @ship.zone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Audit production dependencies
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
pnpm install -g @ship.zone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Test stable
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
pnpm install -g @ship.zone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Release
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
pnpm install -g @ship.zone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Code quality
|
||||
|
7
.gitignore
vendored
7
.gitignore
vendored
@@ -3,7 +3,6 @@
|
||||
# artifacts
|
||||
coverage/
|
||||
public/
|
||||
pages/
|
||||
|
||||
# installs
|
||||
node_modules/
|
||||
@@ -17,4 +16,8 @@ node_modules/
|
||||
dist/
|
||||
dist_*/
|
||||
|
||||
# custom
|
||||
# AI
|
||||
.claude/
|
||||
.serena/
|
||||
|
||||
#------# custom
|
135
changelog.md
135
changelog.md
@@ -1,18 +1,90 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-08-18 - 11.2.6 - fix(fs)
|
||||
Improve fs and stream handling, enhance SmartFile/StreamFile, update tests and CI configs
|
||||
|
||||
- Fix listFileTree to correctly handle '**/' patterns by running both root and nested patterns and deduplicating results.
|
||||
- Enhance waitForFileToBeReady: support directory paths (wait for first file) and improved file-stability checks with timeouts and retries.
|
||||
- StreamFile improvements: support Web ReadableStream -> Node Readable conversion, better multi-use buffering, more robust fromUrl/fromBuffer/fromStream implementations, and accurate byte-length computation.
|
||||
- SmartFile updates: switch fromUrl to SmartRequest, robust rename (optional on-disk rename), safer read/write paths and consistent Buffer handling for hashing and content edits.
|
||||
- fs module tweaks: copy/copySync gained replaceTargetDir option, improved toObjectSync error messages, toReadStream now validates existence, and various synchronous/async API consistency fixes.
|
||||
- memory module: consistent async/sync write APIs and smartfileArrayToFs formatting/behavior fixes.
|
||||
- fsstream: improved stream processing and SmartReadStream robustness (error handling, read logic and watcher improvements).
|
||||
- Tests: reformatted and strengthened tests (more explicit assertions, added tests for '**/*.ts' and '**/*' edge cases, updated imports to tapbundle usage).
|
||||
- CI/workflows: updated IMAGE and NPMCI_COMPUTED_REPOURL values and switched npmci package install to @ship.zone/npmci in workflow files.
|
||||
- package.json: bumped various dependencies, updated test script (timeout, logfile), added typings/main fields, homepage fix, pnpm overrides and minor metadata fixes.
|
||||
- .gitignore: added entries for AI tool folders (.claude/, .serena/).
|
||||
- Docs/readme: expanded README with clearer examples, features and TypeScript usage; updated readme hints for listFileTree behavior.
|
||||
|
||||
## 2025-05-26 - 11.2.5 - fix(dev)
|
||||
|
||||
Update dev dependencies and add local permission settings
|
||||
|
||||
- Bump @git.zone/tsbuild from 2.5.2 to 2.6.4
|
||||
- Bump @git.zone/tstest from 1.9.0 to 2.2.5
|
||||
- Add .claude/settings.local.json to configure permissions for Bash(pnpm test:\*)
|
||||
|
||||
## 2025-05-24 - 11.2.4 - fix(config)
|
||||
|
||||
Add local permissions configuration for pnpm test commands in .claude/settings.local.json
|
||||
|
||||
- Introduced .claude/settings.local.json to allow Bash(pnpm test:\*) permissions
|
||||
- Ensured local testing commands have proper execution rights
|
||||
|
||||
## 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.
|
||||
|
||||
- Fixed bug in copy function where files were not being overwritten when they already existed at the destination.
|
||||
- Fixed bug in copySync function to ensure files are overwritten to match the async function's behavior.
|
||||
|
||||
## 2025-01-29 - 11.1.7 - fix(fs)
|
||||
|
||||
Refactor copy and copySync functions to simplify return type
|
||||
|
||||
- Changed the return type of fs.copy and fs.copySync from boolean to void.
|
||||
- Removed unnecessary promise handling in fs.copy.
|
||||
|
||||
## 2025-01-29 - 11.1.6 - fix(fs)
|
||||
|
||||
Fix issues with fs file copy functions.
|
||||
|
||||
- Updated dependencies in package.json.
|
||||
- Corrected comments for asynchronous and synchronous file copy functions in fs.ts.
|
||||
|
||||
## 2025-01-07 - 11.1.5 - fix(fs)
|
||||
|
||||
Improve waitForFileToBeReady function to handle directories and file stabilization
|
||||
|
||||
- Enhanced the waitForFileToBeReady to handle directory paths by checking for file existence within directories and waiting for stabilization.
|
||||
@@ -21,6 +93,7 @@ Improve waitForFileToBeReady function to handle directories and file stabilizati
|
||||
- Corrected logic for polling and stabilizing files within directories.
|
||||
|
||||
## 2025-01-07 - 11.1.4 - fix(fs)
|
||||
|
||||
Fix file existence check in waitForFileToBeReady method.
|
||||
|
||||
- Ensured that the directory and file exist before setting up the watcher in waitForFileToBeReady.
|
||||
@@ -28,11 +101,13 @@ Fix file existence check in waitForFileToBeReady method.
|
||||
- Handled ENOENT errors correctly to retry file existence checks until timeout is reached.
|
||||
|
||||
## 2025-01-07 - 11.1.3 - fix(fs)
|
||||
|
||||
Fix TypeScript type issue in fs module
|
||||
|
||||
- Corrected a TypeScript type in the fs module's checkFileStability function.
|
||||
|
||||
## 2025-01-07 - 11.1.2 - fix(fs)
|
||||
|
||||
Fix issues in file stability check and directory existence verification in fs module
|
||||
|
||||
- Removed unused variable 'isFileAvailable' in 'waitForFileToBeReady'.
|
||||
@@ -40,6 +115,7 @@ Fix issues in file stability check and directory existence verification in fs mo
|
||||
- Refactored directory existence logic into 'ensureDirectoryExists' function.
|
||||
|
||||
## 2025-01-07 - 11.1.1 - fix(fs)
|
||||
|
||||
Improve waitForFileToBeReady function for file stability detection
|
||||
|
||||
- Enhanced error handling and file stability checks in waitForFileToBeReady function
|
||||
@@ -47,17 +123,20 @@ Improve waitForFileToBeReady function for file stability detection
|
||||
- Improved directory access check before file availability check
|
||||
|
||||
## 2025-01-07 - 11.1.0 - feat(SmartFile)
|
||||
|
||||
Add rename functionality to SmartFile class
|
||||
|
||||
- Implemented a new method to rename a file within the SmartFile class.
|
||||
- The rename method updates the file path and optionally writes the renamed file to the disk.
|
||||
|
||||
## 2024-12-15 - 11.0.23 - fix(fs)
|
||||
|
||||
Handle errors in toObjectSync method
|
||||
|
||||
- Added error handling in toObjectSync function to capture and provide more informative error messages.
|
||||
|
||||
## 2024-06-23 - 11.0.22 - fix(core)
|
||||
|
||||
Update dependencies and changelog
|
||||
|
||||
- Updated @push.rocks/smartstream to ^3.0.44
|
||||
@@ -65,6 +144,7 @@ Update dependencies and changelog
|
||||
- Updated @types/node to ^20.14.8
|
||||
|
||||
## 2024-06-23 - 11.0.21 - fix(dependencies)
|
||||
|
||||
Update dependencies to latest versions
|
||||
|
||||
- Updated @push.rocks/smartpromise to ^4.0.4
|
||||
@@ -73,267 +153,318 @@ Update dependencies to latest versions
|
||||
- Updated @types/node to ^20.14.8
|
||||
|
||||
## 2024-06-07 - 11.0.20 - Changelog
|
||||
|
||||
11.0.20
|
||||
|
||||
## 2024-06-07 - 11.0.19 - fix(core): update
|
||||
|
||||
11.0.19
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2024-06-07 - 11.0.18 - fix(core): update
|
||||
|
||||
11.0.18
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2024-06-06 - 11.0.17 - fix(core): update
|
||||
|
||||
11.0.17
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2024-06-06 - 11.0.16 - fix(core): update
|
||||
|
||||
11.0.16
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2024-05-29 - 11.0.16 - update description
|
||||
|
||||
11.0.16
|
||||
|
||||
- update description
|
||||
|
||||
## 2024-05-17 - 11.0.15 - fix(core): update
|
||||
|
||||
11.0.15
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2024-04-14 - 11.0.14 - update tsconfig
|
||||
|
||||
11.0.14
|
||||
|
||||
- update tsconfig
|
||||
|
||||
## 2024-04-12 - 11.0.13 - fix(core): update
|
||||
|
||||
11.0.13
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2024-04-12 - 11.0.12 - fix(core): update
|
||||
|
||||
11.0.12
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2024-04-12 - 11.0.11 - fix(core): update
|
||||
|
||||
11.0.11
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2024-04-03 - 11.0.10 - fix(core): update
|
||||
|
||||
11.0.10
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2024-04-03 - 11.0.9 - fix(core): update
|
||||
|
||||
11.0.9
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2024-04-02 - 11.0.8 - fix(core): update
|
||||
|
||||
11.0.8
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2024-04-02 - 11.0.7 - fix(core): update
|
||||
|
||||
11.0.7
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2024-04-02 - 11.0.6 - fix(core): update
|
||||
|
||||
11.0.6
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2024-04-01 - 11.0.5 - update npmextra.json
|
||||
|
||||
11.0.5
|
||||
|
||||
- update npmextra.json: githost
|
||||
|
||||
## 2024-04-01 - 11.0.4 - fix(core): update
|
||||
|
||||
11.0.4
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-11-24 - 11.0.3 - fix(core): update
|
||||
|
||||
11.0.3
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-11-07 - 11.0.2 - fix(core): update
|
||||
|
||||
11.0.2
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-11-07 - 11.0.1 - fix(core): update
|
||||
|
||||
11.0.1
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-11-06 - 11.0.0 - fix(core): update
|
||||
|
||||
11.0.0
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-11-06 - 10.0.40 - BREAKING CHANGE(core): update
|
||||
|
||||
10.0.40
|
||||
|
||||
- BREAKING CHANGE(core): update
|
||||
|
||||
## 2023-11-04 - 10.0.39 - fix(core): update
|
||||
|
||||
10.0.39
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-11-04 - 10.0.38 - fix(core): update
|
||||
|
||||
10.0.38
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-11-04 - 10.0.37 - fix(core): update
|
||||
|
||||
10.0.37
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-11-03 - 10.0.36 - fix(core): update
|
||||
|
||||
10.0.36
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-11-03 - 10.0.35 - fix(core): update
|
||||
|
||||
10.0.35
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-11-03 - 10.0.34 - fix(core): update
|
||||
|
||||
10.0.34
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-11-03 - 10.0.33 - fix(core): update
|
||||
|
||||
10.0.33
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-10-12 - 10.0.32 - fix(core): update
|
||||
|
||||
10.0.32
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-09-22 - 10.0.31 - fix(core): update
|
||||
|
||||
10.0.31
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-08-31 - 10.0.30 - fix(core): update
|
||||
|
||||
10.0.30
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-08-23 - 10.0.29 - fix(core): update
|
||||
|
||||
10.0.29
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-07-12 - 10.0.28 - fix(core): update
|
||||
|
||||
10.0.28
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-07-10 - 10.0.27 - fix(core): update
|
||||
|
||||
10.0.27
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-07-10 - 10.0.26 - fix(core): update
|
||||
|
||||
10.0.26
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-07-08 - 10.0.25 - fix(core): update
|
||||
|
||||
10.0.25
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2023-06-25 - 10.0.24 to 10.0.14 - Series of Fixes
|
||||
|
||||
10.0.24 to 10.0.14
|
||||
|
||||
- Series of fixes in the core module
|
||||
|
||||
## 2023-01-09 - 10.0.13 to 10.0.6 - Series of Fixes
|
||||
|
||||
10.0.13 to 10.0.6
|
||||
|
||||
- Series of fixes in the core module
|
||||
|
||||
## 2022-09-05 - 10.0.5 to 10.0.3 - Series of Fixes
|
||||
|
||||
10.0.5 to 10.0.3
|
||||
|
||||
- Series of fixes in the core module
|
||||
|
||||
## 2022-06-07 - 10.0.2 to 10.0.1 - Series of Fixes
|
||||
|
||||
10.0.2 to 10.0.1
|
||||
|
||||
- Series of fixes in the core module
|
||||
|
||||
## 2022-06-07 - 9.0.7 - BREAKING CHANGE(core): switch to esm
|
||||
|
||||
9.0.7
|
||||
|
||||
- BREAKING CHANGE(core): switch to esm
|
||||
|
||||
## 2022-03-11 - 9.0.6 to 9.0.2 - Series of Fixes
|
||||
|
||||
9.0.6 to 9.0.2
|
||||
|
||||
- Series of fixes in the core module
|
||||
|
||||
## 2021-12-01 - 9.0.1 - fix(core): update
|
||||
|
||||
9.0.1
|
||||
|
||||
- fix(core): update
|
||||
|
||||
## 2021-12-01 - 9.0.0 - fix(absolute pathing)
|
||||
|
||||
9.0.0
|
||||
|
||||
- add functions for easily getting absolute paths
|
||||
|
||||
## 2021-11-30 - 8.0.11 - BREAKING CHANGE(relative pathing)
|
||||
|
||||
8.0.11
|
||||
|
||||
- improved relative pathing
|
||||
|
||||
## 2020-08-10 - 8.0.10 to 7.0.12 - Series of Fixes and Updates
|
||||
|
||||
8.0.10 to 7.0.12
|
||||
|
||||
- Series of fixes in the core module
|
||||
- BREAKING CHANGE(Smartfile class): switch to a Buffer-only approach
|
||||
|
||||
## 2019-02-17 - 7.0.0 - fix(core): update dependencies
|
||||
|
||||
7.0.0
|
||||
|
||||
- fix(core): update dependencies
|
||||
|
||||
## 2019-01-27 - 6.0.12 - BREAKING CHANGE(smartfile.fs.fileExists)
|
||||
|
||||
6.0.12
|
||||
|
||||
- now returns a Promise<boolean>
|
||||
|
||||
## 2018-08-19 - 6.0.11 to 6.0.6 - Series of Fixes
|
||||
|
||||
6.0.11 to 6.0.6
|
||||
|
||||
- Series of fixes in core and dependencies
|
||||
|
||||
## 2018-07-03 - 6.0.5 to 5.0.0 - Series of Fixes
|
||||
|
||||
6.0.5 to 5.0.0
|
||||
|
||||
- Series of fixes in core and dependencies
|
||||
|
||||
## 2018-02-16 - 4.2.28 - BREAKING CHANGE(scope)
|
||||
|
||||
4.2.28
|
||||
|
||||
- switch to pushrocks scope
|
||||
|
||||
|
||||
|
@@ -36,13 +36,10 @@
|
||||
}
|
||||
},
|
||||
"tsdoc": {
|
||||
"classes": [
|
||||
"SmartFile",
|
||||
"StreamFile"
|
||||
],
|
||||
"classes": ["SmartFile", "StreamFile"],
|
||||
"descriptions": [
|
||||
"the purpose of the StreamFile class is to provide a hybrid interface between streaming files and simple handling when writing and reading those files multiple times."
|
||||
],
|
||||
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
36
package.json
36
package.json
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@push.rocks/smartfile",
|
||||
"private": false,
|
||||
"version": "11.1.7",
|
||||
"version": "11.2.6",
|
||||
"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 --logfile --timeout 120)",
|
||||
"build": "(tsbuild --web --allowimplicitany)",
|
||||
"buildDocs": "tsdoc"
|
||||
},
|
||||
@@ -38,33 +38,31 @@
|
||||
"author": "Lossless GmbH <hello@lossless.com> (https://lossless.com)",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://gitlab.com/push.rocks/smartfile/issues"
|
||||
"url": "https://code.foss.global/push.rocks/smartfile/issues"
|
||||
},
|
||||
"homepage": "https://code.foss.global/push.rocks/smartfile",
|
||||
"homepage": "https://code.foss.global/push.rocks/smartfile#readme",
|
||||
"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/smarthash": "^3.2.3",
|
||||
"@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/smartpath": "^6.0.0",
|
||||
"@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/glob": "^8.1.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"fs-extra": "^11.3.0",
|
||||
"glob": "^11.0.1",
|
||||
"fs-extra": "^11.3.1",
|
||||
"glob": "^11.0.3",
|
||||
"js-yaml": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^2.2.1",
|
||||
"@git.zone/tsbuild": "^2.6.4",
|
||||
"@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": "^2.3.4",
|
||||
"@types/node": "^22.15.21"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
@@ -80,5 +78,9 @@
|
||||
],
|
||||
"browserslist": [
|
||||
"last 1 chrome versions"
|
||||
]
|
||||
],
|
||||
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39",
|
||||
"pnpm": {
|
||||
"overrides": {}
|
||||
}
|
||||
}
|
||||
|
6695
pnpm-lock.yaml
generated
6695
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1 +1,33 @@
|
||||
|
||||
# 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.
|
||||
|
429
readme.md
429
readme.md
@@ -1,226 +1,315 @@
|
||||
# @push.rocks/smartfile
|
||||
# @push.rocks/smartfile 📁
|
||||
|
||||
> Provides a robust suite of tools for managing files in Node.js using TypeScript.
|
||||
> **A powerful, TypeScript-based file management library for Node.js**
|
||||
|
||||
## Install
|
||||
## 🚀 What is smartfile?
|
||||
|
||||
To integrate `@push.rocks/smartfile` into your project, run:
|
||||
`@push.rocks/smartfile` is your go-to solution for file operations in Node.js. It offers a clean, promise-based API for handling files, directories, streams, and even virtual filesystems - all while maintaining maximum performance and reliability.
|
||||
|
||||
Think of it as `fs` on steroids, with TypeScript superpowers! 💪
|
||||
|
||||
## 💾 Installation
|
||||
|
||||
```bash
|
||||
npm install @push.rocks/smartfile
|
||||
```
|
||||
|
||||
## Usage
|
||||
## ✨ Features
|
||||
|
||||
`@push.rocks/smartfile` offers extensive file management utilities, enabling seamless file processing with TypeScript in a Node.js environment. Below are detailed examples showcasing various features of the module.
|
||||
- 🔥 **Streaming Support** - Handle massive files with ease using `StreamFile`
|
||||
- 📦 **Virtual Directories** - Work with in-memory file structures
|
||||
- 🌐 **URL Support** - Directly work with files from URLs
|
||||
- 🎯 **TypeScript First** - Full type safety and IntelliSense support
|
||||
- ⚡ **Promise-based API** - Modern async/await patterns throughout
|
||||
- 🛠️ **Comprehensive Toolset** - From basic CRUD to advanced operations
|
||||
|
||||
### Quick Start
|
||||
|
||||
First, ensure you're working in an environment that supports ECMAScript modules (ESM) and TypeScript. Here’s how you’d generally import and use `@push.rocks/smartfile`:
|
||||
## 📚 Quick Start
|
||||
|
||||
```typescript
|
||||
import { SmartFile, StreamFile, VirtualDirectory, fs, memory, interpreter } from '@push.rocks/smartfile';
|
||||
import * as smartfile from '@push.rocks/smartfile';
|
||||
|
||||
// Read a file
|
||||
const content = await smartfile.fs.toStringSync('./my-file.txt');
|
||||
|
||||
// Write a file
|
||||
await smartfile.memory.toFs('Hello World!', './output.txt');
|
||||
|
||||
// Work with JSON
|
||||
const data = await smartfile.fs.toObjectSync('./data.json');
|
||||
```
|
||||
|
||||
### Working with `SmartFile`
|
||||
## 🎨 Core Components
|
||||
|
||||
#### Reading Files
|
||||
### SmartFile Class
|
||||
|
||||
To read from a file and convert it to a `SmartFile` instance:
|
||||
The `SmartFile` class represents a single file with powerful manipulation capabilities:
|
||||
|
||||
```typescript
|
||||
const myJsonSmartFile: SmartFile = await SmartFile.fromFilePath('./somePath/data.json');
|
||||
const jsonData = JSON.parse(myJsonSmartFile.contents.toString());
|
||||
console.log(jsonData); // Assuming the file contains JSON content
|
||||
import { SmartFile } from '@push.rocks/smartfile';
|
||||
|
||||
// Create from file path
|
||||
const fileFromPath = await SmartFile.fromFilePath('./data.json');
|
||||
|
||||
// Create from URL
|
||||
const fileFromUrl = await SmartFile.fromUrl('https://example.com/config.json');
|
||||
|
||||
// Create from text
|
||||
const fileFromText = await SmartFile.fromString(
|
||||
'./my-file.txt',
|
||||
'This is my content',
|
||||
'utf8'
|
||||
);
|
||||
|
||||
// Create from Buffer
|
||||
const fileFromBuffer = await SmartFile.fromBuffer(
|
||||
'./binary.dat',
|
||||
Buffer.from([0x00, 0x01, 0x02])
|
||||
);
|
||||
|
||||
// Edit content
|
||||
await fileFromPath.editContentAsString(async (content) => {
|
||||
return content.replace(/old/g, 'new');
|
||||
});
|
||||
|
||||
// Write to disk
|
||||
await fileFromPath.write();
|
||||
|
||||
// Get content
|
||||
const contentString = fileFromPath.parseContentAsString();
|
||||
const contentBuffer = fileFromPath.parseContentAsBuffer();
|
||||
```
|
||||
|
||||
#### Writing Files
|
||||
### StreamFile Class 🌊
|
||||
|
||||
To write data to a file through a `SmartFile`:
|
||||
Perfect for handling large files without memory overhead:
|
||||
|
||||
```typescript
|
||||
const filePath: string = './output/outputData.json';
|
||||
const content: string = JSON.stringify({ key: 'value' });
|
||||
await memory.toFs(content, filePath);
|
||||
import { StreamFile } from '@push.rocks/smartfile';
|
||||
|
||||
// Create from path
|
||||
const streamFile = await StreamFile.fromPath('./bigfile.zip');
|
||||
|
||||
// Create from URL
|
||||
const urlStream = await StreamFile.fromUrl('https://example.com/large.mp4');
|
||||
|
||||
// Create from buffer
|
||||
const bufferStream = StreamFile.fromBuffer(
|
||||
Buffer.from('streaming content'),
|
||||
'stream.txt'
|
||||
);
|
||||
|
||||
// Write to disk
|
||||
await streamFile.writeToDisk('./output/bigfile.zip');
|
||||
|
||||
// Get as buffer (careful with large files!)
|
||||
const buffer = await streamFile.getContentAsBuffer();
|
||||
|
||||
// Get as stream
|
||||
const readStream = await streamFile.createReadStream();
|
||||
```
|
||||
|
||||
### Streaming Large Files with `StreamFile`
|
||||
### VirtualDirectory Class 📂
|
||||
|
||||
When dealing with large files, you can use `StreamFile` to handle such files efficiently, minimizing memory usage:
|
||||
Manage collections of files as virtual filesystems:
|
||||
|
||||
```typescript
|
||||
const largeFile: StreamFile = await StreamFile.fromPath('./largeInput/largeFile.mp4');
|
||||
await largeFile.writeToDisk('./largeOutput/largeFileCopy.mp4');
|
||||
import { VirtualDirectory } from '@push.rocks/smartfile';
|
||||
|
||||
// Create from filesystem
|
||||
const vDir = await VirtualDirectory.fromFsDirPath('./src');
|
||||
|
||||
// Create from file array
|
||||
const vDirFromFiles = await VirtualDirectory.fromFileArray([
|
||||
await SmartFile.fromFilePath('./file1.txt'),
|
||||
await SmartFile.fromFilePath('./file2.txt')
|
||||
]);
|
||||
|
||||
// Add files
|
||||
vDir.addSmartfiles([
|
||||
await SmartFile.fromString('./virtual/new.txt', 'content')
|
||||
]);
|
||||
|
||||
// List files
|
||||
const files = vDir.listFiles();
|
||||
const directories = vDir.listDirectories();
|
||||
|
||||
// Get file
|
||||
const file = vDir.getFileByPath('./some/path.txt');
|
||||
|
||||
// Save to disk
|
||||
await vDir.saveToDisk('./output');
|
||||
|
||||
// Load from disk
|
||||
await vDir.loadFromDisk('./source');
|
||||
```
|
||||
|
||||
### Working with Virtual Directories
|
||||
## 🛠️ File Operations
|
||||
|
||||
Handling multiple files as if they were part of a file system:
|
||||
### Basic Operations
|
||||
|
||||
```typescript
|
||||
const virtualDir = await VirtualDirectory.fromFsDirPath('./data/inputDir');
|
||||
await virtualDir.saveToDisk('./data/outputDir');
|
||||
// Check existence
|
||||
const exists = await smartfile.fs.fileExists('./file.txt');
|
||||
const existsSync = smartfile.fs.fileExistsSync('./file.txt');
|
||||
|
||||
// Read operations
|
||||
const content = await smartfile.fs.toStringSync('./file.txt');
|
||||
const buffer = await smartfile.fs.toBuffer('./file.txt');
|
||||
const object = await smartfile.fs.toObjectSync('./data.json');
|
||||
|
||||
// Write operations
|
||||
await smartfile.memory.toFs('content', './output.txt');
|
||||
smartfile.memory.toFsSync('content', './output-sync.txt');
|
||||
|
||||
// Copy operations
|
||||
await smartfile.fs.copy('./source.txt', './dest.txt');
|
||||
await smartfile.fs.copy('./src-dir', './dest-dir');
|
||||
|
||||
// Delete operations
|
||||
await smartfile.fs.remove('./file.txt');
|
||||
await smartfile.fs.removeSync('./file-sync.txt');
|
||||
await smartfile.fs.removeMany(['./file1.txt', './file2.txt']);
|
||||
|
||||
// Ensure operations (create if not exists)
|
||||
await smartfile.fs.ensureDir('./my/deep/directory');
|
||||
await smartfile.fs.ensureFile('./my/file.txt');
|
||||
await smartfile.fs.ensureEmptyDir('./empty-dir');
|
||||
```
|
||||
|
||||
### File System Operations
|
||||
|
||||
`@push.rocks/smartfile` provides a suite of utilities for common file system operations such as copying, deleting, and listing files or directories.
|
||||
|
||||
#### Copying a File
|
||||
### Directory Operations
|
||||
|
||||
```typescript
|
||||
await fs.copy('./sourceFile.txt', './destinationFile.txt');
|
||||
// List contents
|
||||
const files = await smartfile.fs.listFiles('./directory');
|
||||
const folders = await smartfile.fs.listFolders('./directory');
|
||||
const items = await smartfile.fs.listAllItems('./directory');
|
||||
|
||||
// Get file tree
|
||||
const tree = await smartfile.fs.listFileTree('./src', '**/*.ts');
|
||||
|
||||
// Directory checks
|
||||
const isDir = await smartfile.fs.isDirectory('./path');
|
||||
const isFile = await smartfile.fs.isFile('./path');
|
||||
```
|
||||
|
||||
#### Deleting a Directory
|
||||
### Advanced Features
|
||||
|
||||
```typescript
|
||||
await fs.remove('./directoryToDelete');
|
||||
// Wait for file to be ready
|
||||
await smartfile.fs.waitForFileToBeReady('./file.txt');
|
||||
|
||||
// Stream operations
|
||||
const readStream = smartfile.fsStream.createReadStream('./input.txt');
|
||||
const writeStream = smartfile.fsStream.createWriteStream('./output.txt');
|
||||
|
||||
// File type detection
|
||||
const fileType = smartfile.interpreter.filetype('./document.pdf');
|
||||
// Returns: 'pdf'
|
||||
|
||||
// Smart read stream (with custom processing)
|
||||
const smartStream = new smartfile.fsStream.SmartReadStream('./data.txt');
|
||||
smartStream.on('data', (chunk) => {
|
||||
// Process chunk
|
||||
console.log(chunk.toString());
|
||||
});
|
||||
```
|
||||
|
||||
#### Listing Files in a Directory
|
||||
## 🔄 Working with Multiple Files
|
||||
|
||||
```typescript
|
||||
const fileList: string[] = await fs.listFiles('./someDirectory');
|
||||
console.log(fileList);
|
||||
```
|
||||
// Process multiple SmartFiles
|
||||
const files = await smartfile.fs.fileTreeToObject(
|
||||
'./src',
|
||||
'**/*.{ts,js}'
|
||||
);
|
||||
|
||||
### Advanced File Management
|
||||
|
||||
For specialized file operations, such as editing the contents of a file or streaming files from URLs, `@push.rocks/smartfile` includes advanced management features.
|
||||
|
||||
#### Editing a File’s Contents
|
||||
|
||||
```typescript
|
||||
const smartFileToEdit: SmartFile = await SmartFile.fromFilePath('./editableFile.txt');
|
||||
await smartFileToEdit.editContentAsString(async (content) => content.replace(/originalText/g, 'newText'));
|
||||
await smartFileToEdit.write();
|
||||
```
|
||||
|
||||
#### Streaming a File from a URL
|
||||
|
||||
```typescript
|
||||
const streamedFile: StreamFile = await StreamFile.fromUrl('https://example.com/file.pdf');
|
||||
await streamedFile.writeToDisk('./downloadedFiles/file.pdf');
|
||||
```
|
||||
|
||||
### Working with File Buffers and Streams
|
||||
|
||||
`@push.rocks/smartfile` allows you to easily work with files using Buffers and Streams, facilitating operations like file transformations, uploads, and downloads.
|
||||
|
||||
#### Creating a SmartFile from a Buffer
|
||||
|
||||
```typescript
|
||||
const buffer: Buffer = Buffer.from('Sample data');
|
||||
const bufferSmartFile: SmartFile = await SmartFile.fromBuffer('./bufferFile.txt', buffer);
|
||||
await bufferSmartFile.write();
|
||||
```
|
||||
|
||||
### Using `VirtualDirectory` for Complex File Management
|
||||
|
||||
`VirtualDirectory` simplifies the management of multiple files that are otherwise scattered across different directories or created at runtime.
|
||||
|
||||
#### Creating a `VirtualDirectory`
|
||||
|
||||
To create a `VirtualDirectory` from an existing file directory:
|
||||
|
||||
```typescript
|
||||
const virtualDirectory = await VirtualDirectory.fromFsDirPath('./someDirectory');
|
||||
```
|
||||
|
||||
#### Adding More Files
|
||||
|
||||
You can add more `SmartFile` instances to your `VirtualDirectory`:
|
||||
|
||||
```typescript
|
||||
const additionalFiles = [
|
||||
await SmartFile.fromFilePath('./anotherDirectory/file1.txt'),
|
||||
await SmartFile.fromFilePath('./anotherDirectory/file2.txt')
|
||||
// Write array to disk
|
||||
const smartfiles = [
|
||||
await SmartFile.fromString('file1.txt', 'content1'),
|
||||
await SmartFile.fromString('file2.txt', 'content2')
|
||||
];
|
||||
virtualDirectory.addSmartfiles(additionalFiles);
|
||||
await smartfile.memory.smartfileArrayToFs(smartfiles, './output');
|
||||
```
|
||||
|
||||
#### Saving `VirtualDirectory` to Disk
|
||||
## 🎯 Real-World Examples
|
||||
|
||||
Save your entire `VirtualDirectory` to disk:
|
||||
### Website Bundler
|
||||
```typescript
|
||||
// Bundle website assets
|
||||
const website = await VirtualDirectory.fromFsDirPath('./website');
|
||||
const bundle = await website.smartfileArray;
|
||||
|
||||
// Process all CSS files
|
||||
for (const file of bundle.filter(f => f.path.endsWith('.css'))) {
|
||||
await file.editContentAsString(async (css) => {
|
||||
// Minify CSS here
|
||||
return css.replace(/\s+/g, ' ');
|
||||
});
|
||||
}
|
||||
|
||||
// Save processed bundle
|
||||
await website.saveToDisk('./dist');
|
||||
```
|
||||
|
||||
### File Watcher & Processor
|
||||
```typescript
|
||||
// Watch for new files and process them
|
||||
import { SmartFile, StreamFile } from '@push.rocks/smartfile';
|
||||
|
||||
async function processLargeFile(filePath: string) {
|
||||
const streamFile = await StreamFile.fromPath(filePath);
|
||||
|
||||
// Stream to processed location
|
||||
await streamFile.writeToDisk(`./processed/${path.basename(filePath)}`);
|
||||
|
||||
// Clean up original
|
||||
await smartfile.fs.remove(filePath);
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration Manager
|
||||
```typescript
|
||||
// Load and merge config files
|
||||
const defaultConfig = await smartfile.fs.toObjectSync('./config.default.json');
|
||||
const userConfig = await smartfile.fs.toObjectSync('./config.user.json');
|
||||
|
||||
const merged = { ...defaultConfig, ...userConfig };
|
||||
|
||||
await smartfile.memory.toFs(
|
||||
JSON.stringify(merged, null, 2),
|
||||
'./config.final.json'
|
||||
);
|
||||
```
|
||||
|
||||
## 🌟 API Reference
|
||||
|
||||
### Core Modules
|
||||
|
||||
- `fs` - File system operations
|
||||
- `fsStream` - Streaming operations
|
||||
- `memory` - Memory/buffer operations
|
||||
- `interpreter` - File type detection
|
||||
|
||||
### Main Classes
|
||||
|
||||
- `SmartFile` - Single file representation
|
||||
- `StreamFile` - Streaming file operations
|
||||
- `VirtualDirectory` - Virtual filesystem management
|
||||
|
||||
## 🏗️ TypeScript Support
|
||||
|
||||
Full TypeScript support with comprehensive type definitions:
|
||||
|
||||
```typescript
|
||||
await virtualDirectory.saveToDisk('./outputDirectory');
|
||||
import type { SmartFile, StreamFile, VirtualDirectory } from '@push.rocks/smartfile';
|
||||
|
||||
// All methods are fully typed
|
||||
const processFile = async (file: SmartFile): Promise<void> => {
|
||||
const content = file.parseContentAsString();
|
||||
// TypeScript knows content is string
|
||||
};
|
||||
```
|
||||
|
||||
### Utilizing StreamFile for Efficient File Handling
|
||||
|
||||
Using `StreamFile` can be especially beneficial for large files or when performing streaming operations.
|
||||
|
||||
#### Streaming from a URL
|
||||
|
||||
`StreamFile` provides capabilities to stream files directly from URLs, making it easier to work with remote content.
|
||||
|
||||
```typescript
|
||||
const urlStreamFile: StreamFile = await StreamFile.fromUrl('https://example.com/largefile.zip');
|
||||
await urlStreamFile.writeToDisk('./downloadedFiles/largefile.zip');
|
||||
```
|
||||
|
||||
### Combining Buffer and Stream Approaches
|
||||
|
||||
Create `StreamFile` from a buffer for efficient, multi-use streams.
|
||||
|
||||
```typescript
|
||||
const buffer = Buffer.from('Streaming buffer content');
|
||||
const bufferStreamFile = StreamFile.fromBuffer(buffer, 'bufferBasedStream.txt');
|
||||
await bufferStreamFile.writeToDisk('./streams/bufferBasedStream.txt');
|
||||
```
|
||||
|
||||
### Read and Write Operations with StreamFile
|
||||
|
||||
Perform read and write operations efficiently using `StreamFile`.
|
||||
|
||||
```typescript
|
||||
const fileStream = await StreamFile.fromPath('./inputData/largeFile.data');
|
||||
await fileStream.writeToDisk('./outputData/largeFileCopy.data');
|
||||
```
|
||||
|
||||
Check for completeness of your read and write operations, ensuring the integrity of file content.
|
||||
|
||||
```typescript
|
||||
const readBuffer = await fileStream.getContentAsBuffer();
|
||||
console.log(readBuffer.toString());
|
||||
```
|
||||
|
||||
### Ensuring File Readiness for Streaming
|
||||
|
||||
Ensure a file is ready for streaming or create a custom readable stream incorporating data transformation.
|
||||
|
||||
```typescript
|
||||
const smartReadStream = new SmartReadStream('./incomingData/sample.data');
|
||||
smartReadStream.on('data', (chunk) => {
|
||||
console.log('New Data Chunk:', chunk.toString());
|
||||
});
|
||||
```
|
||||
|
||||
### File Transformation with SmartReadStream
|
||||
|
||||
Perform transformations on the stream of data while reading:
|
||||
|
||||
```typescript
|
||||
smartReadStream.on('data', (chunk) => {
|
||||
// Perform some transformation
|
||||
const transformedChunk = chunk.toString().toUpperCase();
|
||||
console.log('Transformed Data Chunk:', transformedChunk);
|
||||
});
|
||||
```
|
||||
|
||||
### Streaming with SmartReadStream
|
||||
|
||||
Stream data from a `SmartReadStream` to a file efficiently managing large datasets.
|
||||
|
||||
```typescript
|
||||
const transformedWriteStream = fs.createWriteStream('./processedData/transformed.data');
|
||||
smartReadStream.pipe(transformedWriteStream);
|
||||
```
|
||||
|
||||
`@push.rocks/smartfile` significantly simplifies the handling of complex file operations in Node.js projects, making these tasks straightforward while maintaining efficiency and ease of use. Explore and leverage these features to enhance your project's file management capabilities.
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
|
||||
@@ -238,4 +327,4 @@ Registered at District court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
||||
|
||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
@@ -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
|
||||
@@ -9,55 +9,85 @@ const testAssetsPath = './test/testassets/';
|
||||
// StreamFile tests
|
||||
// ---------------------------
|
||||
|
||||
tap.test('StreamFile.fromPath should create a StreamFile from a file path', async () => {
|
||||
const streamFile = await smartfile.StreamFile.fromPath(path.join(testAssetsPath, 'mytest.json'));
|
||||
expect(streamFile).toBeInstanceOf(smartfile.StreamFile);
|
||||
const contentBuffer = await streamFile.getContentAsBuffer();
|
||||
expect(contentBuffer).toBeInstanceOf(Buffer);
|
||||
});
|
||||
tap.test(
|
||||
'StreamFile.fromPath should create a StreamFile from a file path',
|
||||
async () => {
|
||||
const streamFile = await smartfile.StreamFile.fromPath(
|
||||
path.join(testAssetsPath, 'mytest.json'),
|
||||
);
|
||||
expect(streamFile).toBeInstanceOf(smartfile.StreamFile);
|
||||
const contentBuffer = await streamFile.getContentAsBuffer();
|
||||
expect(contentBuffer).toBeInstanceOf(Buffer);
|
||||
},
|
||||
);
|
||||
|
||||
tap.test('StreamFile.fromUrl should create a StreamFile from a URL', async () => {
|
||||
const streamFile = await smartfile.StreamFile.fromUrl('http://example.com/somefile.json');
|
||||
expect(streamFile).toBeInstanceOf(smartfile.StreamFile);
|
||||
});
|
||||
tap.test(
|
||||
'StreamFile.fromUrl should create a StreamFile from a URL',
|
||||
async () => {
|
||||
const streamFile = await smartfile.StreamFile.fromUrl(
|
||||
'http://example.com/somefile.json',
|
||||
);
|
||||
expect(streamFile).toBeInstanceOf(smartfile.StreamFile);
|
||||
},
|
||||
);
|
||||
|
||||
tap.test('StreamFile.fromBuffer should create a StreamFile from a Buffer', async () => {
|
||||
const buffer = Buffer.from('Some content');
|
||||
const streamFile = smartfile.StreamFile.fromBuffer(buffer, 'bufferfile.txt');
|
||||
expect(streamFile).toBeInstanceOf(smartfile.StreamFile);
|
||||
});
|
||||
tap.test(
|
||||
'StreamFile.fromBuffer should create a StreamFile from a Buffer',
|
||||
async () => {
|
||||
const buffer = Buffer.from('Some content');
|
||||
const streamFile = smartfile.StreamFile.fromBuffer(
|
||||
buffer,
|
||||
'bufferfile.txt',
|
||||
);
|
||||
expect(streamFile).toBeInstanceOf(smartfile.StreamFile);
|
||||
},
|
||||
);
|
||||
|
||||
tap.test('StreamFile should write the stream to disk', async () => {
|
||||
const streamFile = await smartfile.StreamFile.fromPath(path.join(testAssetsPath, 'mytest.json'));
|
||||
await streamFile.writeToDisk(path.join(testAssetsPath, 'temp', 'mytest.json'));
|
||||
const streamFile = await smartfile.StreamFile.fromPath(
|
||||
path.join(testAssetsPath, 'mytest.json'),
|
||||
);
|
||||
await streamFile.writeToDisk(
|
||||
path.join(testAssetsPath, 'temp', 'mytest.json'),
|
||||
);
|
||||
// Verify the file was written
|
||||
expect(
|
||||
// We'll use the fileExists method from your smartfile library
|
||||
// Replace with the actual method you use to check file existence
|
||||
await smartfile.fs.fileExists(path.join(testAssetsPath, 'temp', 'mytest.json'))
|
||||
await smartfile.fs.fileExists(
|
||||
path.join(testAssetsPath, 'temp', 'mytest.json'),
|
||||
),
|
||||
).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('StreamFile should write to a directory', async () => {
|
||||
const streamFile = await smartfile.StreamFile.fromPath(path.join(testAssetsPath, 'mytest.json'));
|
||||
const streamFile = await smartfile.StreamFile.fromPath(
|
||||
path.join(testAssetsPath, 'mytest.json'),
|
||||
);
|
||||
await streamFile.writeToDir(path.join(testAssetsPath, 'temp'));
|
||||
// Verify the file was written
|
||||
expect(
|
||||
await smartfile.fs.fileExists(path.join(testAssetsPath, 'temp', 'mytest.json'))
|
||||
await smartfile.fs.fileExists(
|
||||
path.join(testAssetsPath, 'temp', 'mytest.json'),
|
||||
),
|
||||
).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('StreamFile should return content as a buffer', async () => {
|
||||
const streamFile = await smartfile.StreamFile.fromPath(path.join(testAssetsPath, 'mytest.json'));
|
||||
const streamFile = await smartfile.StreamFile.fromPath(
|
||||
path.join(testAssetsPath, 'mytest.json'),
|
||||
);
|
||||
const contentBuffer = await streamFile.getContentAsBuffer();
|
||||
expect(contentBuffer).toBeInstanceOf(Buffer);
|
||||
// Further checks on the content can be added here if necessary
|
||||
});
|
||||
|
||||
tap.test('StreamFile should return content as a string', async () => {
|
||||
const streamFile = await smartfile.StreamFile.fromPath(path.join(testAssetsPath, 'mytest.json'));
|
||||
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');
|
||||
|
315
test/test.ts
315
test/test.ts
@@ -1,85 +1,168 @@
|
||||
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
|
||||
// ---------------------------
|
||||
|
||||
tap.test('.fs.fileExistsSync -> should return an accurate boolean', async () => {
|
||||
// tslint:disable-next-line: no-unused-expression
|
||||
expect(smartfile.fs.fileExistsSync('./test/testassets/mytest.json')).toBeTrue();
|
||||
// tslint:disable-next-line: no-unused-expression
|
||||
expect(smartfile.fs.fileExistsSync('./test/testassets/notthere.json')).toBeFalse();
|
||||
});
|
||||
tap.test(
|
||||
'.fs.fileExistsSync -> should return an accurate boolean',
|
||||
async () => {
|
||||
// tslint:disable-next-line: no-unused-expression
|
||||
expect(
|
||||
smartfile.fs.fileExistsSync('./test/testassets/mytest.json'),
|
||||
).toBeTrue();
|
||||
// tslint:disable-next-line: no-unused-expression
|
||||
expect(
|
||||
smartfile.fs.fileExistsSync('./test/testassets/notthere.json'),
|
||||
).toBeFalse();
|
||||
},
|
||||
);
|
||||
|
||||
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 () => {
|
||||
expect(smartfile.fs.listFoldersSync('./test/testassets/')).toContain(
|
||||
'testfolder',
|
||||
);
|
||||
});
|
||||
});
|
||||
expect(smartfile.fs.listFoldersSync('./test/testassets/')).not.toContain(
|
||||
'notExistentFolder',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
tap.test('.fs.listFoldersSync() -> should get the file type from a string', async () => {
|
||||
expect(smartfile.fs.listFoldersSync('./test/testassets/')).toContain('testfolder');
|
||||
expect(smartfile.fs.listFoldersSync('./test/testassets/')).not.toContain('notExistentFolder');
|
||||
});
|
||||
tap.test(
|
||||
'.fs.listFolders() -> should get the file type from a string',
|
||||
async () => {
|
||||
const folderArrayArg = await smartfile.fs.listFolders('./test/testassets/');
|
||||
expect(folderArrayArg).toContain('testfolder');
|
||||
expect(folderArrayArg).not.toContain('notExistentFolder');
|
||||
},
|
||||
);
|
||||
|
||||
tap.test('.fs.listFolders() -> should get the file type from a string', async () => {
|
||||
const folderArrayArg = await smartfile.fs.listFolders('./test/testassets/');
|
||||
expect(folderArrayArg).toContain('testfolder');
|
||||
expect(folderArrayArg).not.toContain('notExistentFolder');
|
||||
});
|
||||
tap.test(
|
||||
'.fs.listFilesSync() -> should get the file type from a string',
|
||||
async () => {
|
||||
expect(smartfile.fs.listFilesSync('./test/testassets/')).toContain(
|
||||
'mytest.json',
|
||||
);
|
||||
expect(smartfile.fs.listFilesSync('./test/testassets/')).not.toContain(
|
||||
'notExistentFile',
|
||||
);
|
||||
expect(
|
||||
smartfile.fs.listFilesSync('./test/testassets/', /mytest\.json/),
|
||||
).toContain('mytest.json');
|
||||
expect(
|
||||
smartfile.fs.listFilesSync('./test/testassets/', /mytests.json/),
|
||||
).not.toContain('mytest.json');
|
||||
},
|
||||
);
|
||||
|
||||
tap.test('.fs.listFilesSync() -> should get the file type from a string', async () => {
|
||||
expect(smartfile.fs.listFilesSync('./test/testassets/')).toContain('mytest.json');
|
||||
expect(smartfile.fs.listFilesSync('./test/testassets/')).not.toContain('notExistentFile');
|
||||
expect(smartfile.fs.listFilesSync('./test/testassets/', /mytest\.json/)).toContain('mytest.json');
|
||||
expect(smartfile.fs.listFilesSync('./test/testassets/', /mytests.json/)).not.toContain(
|
||||
'mytest.json'
|
||||
);
|
||||
});
|
||||
|
||||
tap.test('.fs.listFiles() -> should get the file type from a string', async () => {
|
||||
const folderArrayArg = await smartfile.fs.listFiles('./test/testassets/');
|
||||
expect(folderArrayArg).toContain('mytest.json');
|
||||
expect(folderArrayArg).not.toContain('notExistentFile');
|
||||
});
|
||||
tap.test(
|
||||
'.fs.listFiles() -> should get the file type from a string',
|
||||
async () => {
|
||||
const folderArrayArg = await smartfile.fs.listFiles('./test/testassets/');
|
||||
expect(folderArrayArg).toContain('mytest.json');
|
||||
expect(folderArrayArg).not.toContain('notExistentFile');
|
||||
},
|
||||
);
|
||||
|
||||
tap.test('.fs.listFileTree() -> should get a file tree', async () => {
|
||||
const folderArrayArg = await smartfile.fs.listFileTree(
|
||||
path.resolve('./test/testassets/'),
|
||||
'**/*.txt'
|
||||
'**/*.txt',
|
||||
);
|
||||
expect(folderArrayArg).toContain('testfolder/testfile1.txt');
|
||||
expect(folderArrayArg).not.toContain('mytest.json');
|
||||
});
|
||||
|
||||
tap.test('.fs.fileTreeToObject -> should read a file tree into an Object', async () => {
|
||||
const fileArrayArg = await smartfile.fs.fileTreeToObject(
|
||||
path.resolve('./test/testassets/'),
|
||||
'**/*.txt'
|
||||
);
|
||||
expect(fileArrayArg[0]).toBeInstanceOf(smartfile.SmartFile);
|
||||
expect(fileArrayArg[0].contents.toString()).toEqual(fileArrayArg[0].contentBuffer.toString());
|
||||
});
|
||||
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/'),
|
||||
'**/*.txt',
|
||||
);
|
||||
expect(fileArrayArg[0]).toBeInstanceOf(smartfile.SmartFile);
|
||||
expect(fileArrayArg[0].contents.toString()).toEqual(
|
||||
fileArrayArg[0].contentBuffer.toString(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
tap.test('.fs.copy() -> should copy a directory', async () => {
|
||||
await smartfile.fs.copy('./test/testassets/testfolder/', './test/testassets/temp/');
|
||||
await smartfile.fs.copy(
|
||||
'./test/testassets/testfolder/',
|
||||
'./test/testassets/temp/',
|
||||
);
|
||||
});
|
||||
|
||||
tap.test('.fs.copy() -> should copy a file', async () => {
|
||||
await smartfile.fs.copy('./test/testassets/mytest.yaml', './test/testassets/temp/mytest.yaml');
|
||||
await smartfile.fs.copy(
|
||||
'./test/testassets/mytest.yaml',
|
||||
'./test/testassets/temp/mytest.yaml',
|
||||
);
|
||||
});
|
||||
|
||||
tap.test('.fs.copy() -> should copy a file and rename it', async () => {
|
||||
await smartfile.fs.copy(
|
||||
'./test/testassets/mytest.yaml',
|
||||
'./test/testassets/temp/mytestRenamed.yaml'
|
||||
'./test/testassets/temp/mytestRenamed.yaml',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -89,70 +172,105 @@ tap.test('.fs.remove -> should remove single files', async () => {
|
||||
await smartfile.fs.remove('./test/testassets/temp/mytestRenamed.yaml');
|
||||
});
|
||||
|
||||
tap.test('.fs.removeSync -> should remove single files synchronouly', async () => {
|
||||
smartfile.fs.removeSync('./test/testassets/temp/testfile1.txt');
|
||||
expect(smartfile.fs.fileExistsSync('./test/testassets/temp/testfile1.txt')).toBeFalse();
|
||||
});
|
||||
tap.test(
|
||||
'.fs.removeSync -> should remove single files synchronouly',
|
||||
async () => {
|
||||
smartfile.fs.removeSync('./test/testassets/temp/testfile1.txt');
|
||||
expect(
|
||||
smartfile.fs.fileExistsSync('./test/testassets/temp/testfile1.txt'),
|
||||
).toBeFalse();
|
||||
},
|
||||
);
|
||||
|
||||
tap.test('.fs.removeMany -> should remove and array of files', async () => {
|
||||
smartfile.fs
|
||||
.removeMany(['./test/testassets/temp/testfile1.txt', './test/testassets/temp/testfile2.txt'])
|
||||
.removeMany([
|
||||
'./test/testassets/temp/testfile1.txt',
|
||||
'./test/testassets/temp/testfile2.txt',
|
||||
])
|
||||
.then(() => {
|
||||
expect(smartfile.fs.fileExistsSync('./test/testassets/temp/testfile1.txt')).toBeFalse();
|
||||
expect(smartfile.fs.fileExistsSync('./test/testassets/temp/testfile2.txt')).toBeFalse();
|
||||
expect(
|
||||
smartfile.fs.fileExistsSync('./test/testassets/temp/testfile1.txt'),
|
||||
).toBeFalse();
|
||||
expect(
|
||||
smartfile.fs.fileExistsSync('./test/testassets/temp/testfile2.txt'),
|
||||
).toBeFalse();
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('.fs.removeManySync -> should remove and array of single files synchronouly', async () => {
|
||||
smartfile.fs.removeManySync([
|
||||
'./test/testassets/temp/testfile1.txt',
|
||||
'./test/testassets/temp/testfile2.txt',
|
||||
]);
|
||||
expect(smartfile.fs.fileExistsSync('./test/testassets/temp/testfile1.txt')).toBeFalse();
|
||||
expect(smartfile.fs.fileExistsSync('./test/testassets/temp/testfile2.txt')).toBeFalse();
|
||||
});
|
||||
tap.test(
|
||||
'.fs.removeManySync -> should remove and array of single files synchronouly',
|
||||
async () => {
|
||||
smartfile.fs.removeManySync([
|
||||
'./test/testassets/temp/testfile1.txt',
|
||||
'./test/testassets/temp/testfile2.txt',
|
||||
]);
|
||||
expect(
|
||||
smartfile.fs.fileExistsSync('./test/testassets/temp/testfile1.txt'),
|
||||
).toBeFalse();
|
||||
expect(
|
||||
smartfile.fs.fileExistsSync('./test/testassets/temp/testfile2.txt'),
|
||||
).toBeFalse();
|
||||
},
|
||||
);
|
||||
|
||||
tap.test('.fs.toObjectSync() -> should read an .yaml file to an object', async () => {
|
||||
const testData = smartfile.fs.toObjectSync('./test/testassets/mytest.yaml');
|
||||
expect(testData.key1).toEqual('this works');
|
||||
expect(testData.key2).toEqual('this works too');
|
||||
});
|
||||
tap.test(
|
||||
'.fs.toObjectSync() -> should read an .yaml file to an object',
|
||||
async () => {
|
||||
const testData = smartfile.fs.toObjectSync('./test/testassets/mytest.yaml');
|
||||
expect(testData.key1).toEqual('this works');
|
||||
expect(testData.key2).toEqual('this works too');
|
||||
},
|
||||
);
|
||||
tap.test(
|
||||
'.fs.toObjectSync() -> should state unknown file type for unknown file types',
|
||||
async () => {
|
||||
const testData = smartfile.fs.toObjectSync('./test/testassets/mytest.txt');
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
tap.test('.fs.toObjectSync() -> should read an .json file to an object', async () => {
|
||||
const testData = smartfile.fs.toObjectSync('./test/testassets/mytest.json');
|
||||
expect(testData.key1).toEqual('this works');
|
||||
expect(testData.key2).toEqual('this works too');
|
||||
});
|
||||
tap.test(
|
||||
'.fs.toObjectSync() -> should read an .json file to an object',
|
||||
async () => {
|
||||
const testData = smartfile.fs.toObjectSync('./test/testassets/mytest.json');
|
||||
expect(testData.key1).toEqual('this works');
|
||||
expect(testData.key2).toEqual('this works too');
|
||||
},
|
||||
);
|
||||
|
||||
tap.test('.fs.toStringSync() -> should read a file to a string', async () => {
|
||||
expect(smartfile.fs.toStringSync('./test/testassets/mytest.txt')).toEqual('Some TestString &&%$');
|
||||
expect(smartfile.fs.toStringSync('./test/testassets/mytest.txt')).toEqual(
|
||||
'Some TestString &&%$',
|
||||
);
|
||||
});
|
||||
|
||||
// ---------------------------
|
||||
// smartfile.interpreter
|
||||
// ---------------------------
|
||||
|
||||
tap.test('.interpreter.filetype() -> should get the file type from a string', async () => {
|
||||
expect(smartfile.interpreter.filetype('./somefolder/data.json')).toEqual('json');
|
||||
});
|
||||
tap.test(
|
||||
'.interpreter.filetype() -> should get the file type from a string',
|
||||
async () => {
|
||||
expect(smartfile.interpreter.filetype('./somefolder/data.json')).toEqual(
|
||||
'json',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// ---------------------------
|
||||
// smartfile.memory
|
||||
// ---------------------------
|
||||
|
||||
tap.test('.memory.toFs() -> should write a file to disk and return a promise', async () => {
|
||||
const localString = 'myString';
|
||||
await smartfile.memory.toFs(
|
||||
localString,
|
||||
path.join(process.cwd(), './test/testassets/temp/testMemToFs.txt')
|
||||
);
|
||||
});
|
||||
tap.test(
|
||||
'.memory.toFs() -> should write a file to disk and return a promise',
|
||||
async () => {
|
||||
const localString = 'myString';
|
||||
await smartfile.memory.toFs(
|
||||
localString,
|
||||
path.join(process.cwd(), './test/testassets/temp/testMemToFs.txt'),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
tap.test(
|
||||
'.memory.toFsSync() -> should write a file to disk and return true if successfull',
|
||||
@@ -160,9 +278,9 @@ tap.test(
|
||||
const localString = 'myString';
|
||||
smartfile.memory.toFsSync(
|
||||
localString,
|
||||
path.join(process.cwd(), './test/testassets/temp/testMemToFsSync.txt')
|
||||
path.join(process.cwd(), './test/testassets/temp/testMemToFsSync.txt'),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// ---------------------------
|
||||
@@ -172,7 +290,7 @@ tap.test(
|
||||
tap.test('.Smartfile -> should produce vinyl compatible files', async () => {
|
||||
const smartfileArray = await smartfile.fs.fileTreeToObject(
|
||||
process.cwd(),
|
||||
'./test/testassets/testfolder/**/*'
|
||||
'./test/testassets/testfolder/**/*',
|
||||
);
|
||||
const localSmartfile = smartfileArray[0];
|
||||
expect(localSmartfile).toBeInstanceOf(smartfile.SmartFile);
|
||||
@@ -186,7 +304,10 @@ tap.test('.Smartfile -> should produce vinyl compatible files', async () => {
|
||||
});
|
||||
|
||||
tap.test('should output a smartfile array to disk', async () => {
|
||||
const smartfileArray = await smartfile.fs.fileTreeToObject('./test/testassets/testfolder/', '*');
|
||||
const smartfileArray = await smartfile.fs.fileTreeToObject(
|
||||
'./test/testassets/testfolder/',
|
||||
'*',
|
||||
);
|
||||
for (const smartfileInstance of smartfileArray) {
|
||||
console.log(smartfileInstance.relative);
|
||||
console.log(smartfileInstance.path);
|
||||
@@ -195,14 +316,18 @@ tap.test('should output a smartfile array to disk', async () => {
|
||||
}
|
||||
await smartfile.memory.smartfileArrayToFs(
|
||||
smartfileArray,
|
||||
path.resolve('./test/testassets/temp/testoutput/')
|
||||
path.resolve('./test/testassets/temp/testoutput/'),
|
||||
);
|
||||
});
|
||||
|
||||
tap.test('should create, store and retrieve valid smartfiles', async () => {
|
||||
const fileString = 'hi there';
|
||||
const filePath = './test/testassets/utf8.txt';
|
||||
const smartfileInstance = await smartfile.SmartFile.fromString(filePath, fileString, 'utf8');
|
||||
const smartfileInstance = await smartfile.SmartFile.fromString(
|
||||
filePath,
|
||||
fileString,
|
||||
'utf8',
|
||||
);
|
||||
smartfileInstance.write();
|
||||
const smartfileInstance2 = await smartfile.SmartFile.fromFilePath(filePath);
|
||||
const retrievedString = smartfileInstance.contents.toString();
|
||||
@@ -212,7 +337,11 @@ tap.test('should create, store and retrieve valid smartfiles', async () => {
|
||||
tap.test('should get a hash', async () => {
|
||||
const fileString = 'hi there';
|
||||
const filePath = './test/testassets/utf8.txt';
|
||||
const smartfileInstance = await smartfile.SmartFile.fromString(filePath, fileString, 'utf8');
|
||||
const smartfileInstance = await smartfile.SmartFile.fromString(
|
||||
filePath,
|
||||
fileString,
|
||||
'utf8',
|
||||
);
|
||||
const hash = await smartfileInstance.getHash();
|
||||
console.log(hash);
|
||||
});
|
||||
|
@@ -1,14 +1,18 @@
|
||||
import { tap, expect } from '@push.rocks/tapbundle';
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
|
||||
import * as smartfile from '../ts/index.js';
|
||||
|
||||
tap.test('should create a virtualdirectory', async () => {
|
||||
const virtualDir = await smartfile.VirtualDirectory.fromFsDirPath('./test/testassets/testfolder');
|
||||
const virtualDir = await smartfile.VirtualDirectory.fromFsDirPath(
|
||||
'./test/testassets/testfolder',
|
||||
);
|
||||
expect(virtualDir.smartfileArray.length).toEqual(4);
|
||||
});
|
||||
|
||||
tap.test('should write to a directory', async () => {
|
||||
const virtualDir = await smartfile.VirtualDirectory.fromFsDirPath('./test/testassets/testfolder');
|
||||
const virtualDir = await smartfile.VirtualDirectory.fromFsDirPath(
|
||||
'./test/testassets/testfolder',
|
||||
);
|
||||
virtualDir.saveToDisk('./test/testassets/test');
|
||||
});
|
||||
|
||||
|
@@ -5,4 +5,3 @@
|
||||
"nestedkey1": "hello"
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
key1: this works
|
||||
key2: this works too
|
||||
key3:
|
||||
nestedkey1: hello
|
||||
nestedkey1: hello
|
||||
|
@@ -5,4 +5,3 @@
|
||||
"nestedkey1": "hello"
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
key1: this works
|
||||
key2: this works too
|
||||
key3:
|
||||
nestedkey1: hello
|
||||
nestedkey1: hello
|
||||
|
@@ -5,4 +5,3 @@
|
||||
"nestedkey1": "hello"
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartfile',
|
||||
version: '11.1.7',
|
||||
version: '11.2.6',
|
||||
description: 'Provides comprehensive tools for efficient file management in Node.js using TypeScript, including handling streams, virtual directories, and various file operations.'
|
||||
}
|
||||
|
@@ -20,7 +20,10 @@ export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
* creates a Smartfile from a filePath
|
||||
* @param filePath
|
||||
*/
|
||||
public static async fromFilePath(filePath: string, baseArg: string = process.cwd()) {
|
||||
public static async fromFilePath(
|
||||
filePath: string,
|
||||
baseArg: string = process.cwd(),
|
||||
) {
|
||||
filePath = plugins.path.resolve(filePath);
|
||||
const fileBuffer = fs.toBufferSync(filePath);
|
||||
const smartfile = new SmartFile({
|
||||
@@ -34,7 +37,7 @@ export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
public static async fromBuffer(
|
||||
filePath: string,
|
||||
contentBufferArg: Buffer,
|
||||
baseArg: string = process.cwd()
|
||||
baseArg: string = process.cwd(),
|
||||
) {
|
||||
const smartfile = new SmartFile({
|
||||
contentBuffer: contentBufferArg,
|
||||
@@ -49,7 +52,7 @@ export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
filePath: string,
|
||||
contentStringArg: string,
|
||||
encodingArg: 'utf8' | 'binary',
|
||||
baseArg = process.cwd()
|
||||
baseArg = process.cwd(),
|
||||
) {
|
||||
const smartfile = new SmartFile({
|
||||
contentBuffer: Buffer.from(contentStringArg, encodingArg),
|
||||
@@ -73,7 +76,7 @@ export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
public static async fromStream(
|
||||
stream: plugins.stream.Readable,
|
||||
filePath: string,
|
||||
baseArg: string = process.cwd()
|
||||
baseArg: string = process.cwd(),
|
||||
): Promise<SmartFile> {
|
||||
return new Promise<SmartFile>((resolve, reject) => {
|
||||
const chunks: Buffer[] = [];
|
||||
@@ -92,8 +95,12 @@ export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
}
|
||||
|
||||
public static async fromUrl(urlArg: string) {
|
||||
const response = await plugins.smartrequest.getBinary(urlArg);
|
||||
const smartfile = await SmartFile.fromBuffer(urlArg, response.body);
|
||||
const response = await plugins.smartrequest.SmartRequest.create()
|
||||
.url(urlArg)
|
||||
.accept('binary')
|
||||
.get();
|
||||
const buffer = Buffer.from(await response.arrayBuffer());
|
||||
const smartfile = await SmartFile.fromBuffer(urlArg, buffer);
|
||||
return smartfile;
|
||||
}
|
||||
|
||||
@@ -159,7 +166,10 @@ export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
* set contents from string
|
||||
* @param contentString
|
||||
*/
|
||||
public setContentsFromString(contentString: string, encodingArg: 'utf8' | 'binary' = 'utf8') {
|
||||
public setContentsFromString(
|
||||
contentString: string,
|
||||
encodingArg: 'utf8' | 'binary' = 'utf8',
|
||||
) {
|
||||
this.contents = Buffer.from(contentString, encodingArg);
|
||||
}
|
||||
|
||||
@@ -169,7 +179,10 @@ export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
* - no argument write to exactly where the file was picked up
|
||||
*/
|
||||
public async write() {
|
||||
let writePath = plugins.smartpath.transform.makeAbsolute(this.path, this.base);
|
||||
let writePath = plugins.smartpath.transform.makeAbsolute(
|
||||
this.path,
|
||||
this.base,
|
||||
);
|
||||
console.log(`writing to ${writePath}`);
|
||||
await memory.toFs(this.contentBuffer, writePath);
|
||||
}
|
||||
@@ -202,7 +215,9 @@ export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
* read file from disk
|
||||
*/
|
||||
public async read() {
|
||||
this.contentBuffer = await fs.toBuffer(plugins.path.join(this.base, this.path));
|
||||
this.contentBuffer = await fs.toBuffer(
|
||||
plugins.path.join(this.base, this.path),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,7 +235,10 @@ export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
* @param writeToDisk (optional) If true, also renames the file on the disk.
|
||||
* @returns The updated file path after renaming.
|
||||
*/
|
||||
public async rename(newName: string, writeToDisk: boolean = false): Promise<string> {
|
||||
public async rename(
|
||||
newName: string,
|
||||
writeToDisk: boolean = false,
|
||||
): Promise<string> {
|
||||
// Validate the new name
|
||||
if (!newName || typeof newName !== 'string') {
|
||||
throw new Error('Invalid new name provided.');
|
||||
@@ -238,8 +256,14 @@ export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
|
||||
// Optionally write the renamed file to disk
|
||||
if (writeToDisk) {
|
||||
const oldAbsolutePath = plugins.smartpath.transform.makeAbsolute(oldFilePath, this.base);
|
||||
const newAbsolutePath = plugins.smartpath.transform.makeAbsolute(newFilePath, this.base);
|
||||
const oldAbsolutePath = plugins.smartpath.transform.makeAbsolute(
|
||||
oldFilePath,
|
||||
this.base,
|
||||
);
|
||||
const newAbsolutePath = plugins.smartpath.transform.makeAbsolute(
|
||||
newFilePath,
|
||||
this.base,
|
||||
);
|
||||
|
||||
// Rename the file on disk
|
||||
await plugins.fsExtra.rename(oldAbsolutePath, newAbsolutePath);
|
||||
@@ -310,8 +334,12 @@ export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
|
||||
public async getHash(typeArg: 'path' | 'content' | 'all' = 'all') {
|
||||
const pathHash = await plugins.smarthash.sha256FromString(this.path);
|
||||
const contentHash = await plugins.smarthash.sha256FromBuffer(this.contentBuffer);
|
||||
const combinedHash = await plugins.smarthash.sha256FromString(pathHash + contentHash);
|
||||
const contentHash = await plugins.smarthash.sha256FromBuffer(
|
||||
this.contentBuffer,
|
||||
);
|
||||
const combinedHash = await plugins.smarthash.sha256FromString(
|
||||
pathHash + contentHash,
|
||||
);
|
||||
switch (typeArg) {
|
||||
case 'path':
|
||||
return pathHash;
|
||||
@@ -329,7 +357,9 @@ export class SmartFile extends plugins.smartjson.Smartjson {
|
||||
this.path = this.path.replace(new RegExp(oldFileName + '$'), fileNameArg);
|
||||
}
|
||||
|
||||
public async editContentAsString(editFuncArg: (fileStringArg: string) => Promise<string>) {
|
||||
public async editContentAsString(
|
||||
editFuncArg: (fileStringArg: string) => Promise<string>,
|
||||
) {
|
||||
const newFileString = await editFuncArg(this.contentBuffer.toString());
|
||||
this.contentBuffer = Buffer.from(newFileString);
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ import * as smartfileFs from './fs.js';
|
||||
import * as smartfileFsStream from './fsstream.js';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
type TStreamSource = (streamFile: StreamFile) => Promise<Readable>;
|
||||
type TStreamSource = (streamFile: StreamFile) => Promise<Readable | ReadableStream>;
|
||||
|
||||
/**
|
||||
* The StreamFile class represents a file as a stream.
|
||||
@@ -13,28 +13,41 @@ export class StreamFile {
|
||||
// STATIC
|
||||
|
||||
public static async fromPath(filePath: string): Promise<StreamFile> {
|
||||
const streamSource: TStreamSource = async (streamFileArg) => smartfileFsStream.createReadStream(filePath);
|
||||
const streamSource: TStreamSource = async (streamFileArg) =>
|
||||
smartfileFsStream.createReadStream(filePath);
|
||||
const streamFile = new StreamFile(streamSource, filePath);
|
||||
streamFile.multiUse = true;
|
||||
streamFile.byteLengthComputeFunction = async () => {
|
||||
const stats = await smartfileFs.stat(filePath);
|
||||
return stats.size;
|
||||
}
|
||||
};
|
||||
return streamFile;
|
||||
}
|
||||
|
||||
public static async fromUrl(url: string): Promise<StreamFile> {
|
||||
const streamSource: TStreamSource = async (streamFileArg) => plugins.smartrequest.getStream(url); // Replace with actual plugin method
|
||||
const streamSource: TStreamSource = async (streamFileArg) => {
|
||||
const response = await plugins.smartrequest.SmartRequest.create()
|
||||
.url(url)
|
||||
.get();
|
||||
return response.stream();
|
||||
};
|
||||
const streamFile = new StreamFile(streamSource);
|
||||
streamFile.multiUse = true;
|
||||
streamFile.byteLengthComputeFunction = async () => {
|
||||
const response = await plugins.smartrequest.getBinary(url); // TODO: switch to future .getBinaryByteLength()
|
||||
return response.body.length;
|
||||
}
|
||||
const response = await plugins.smartrequest.SmartRequest.create()
|
||||
.url(url)
|
||||
.accept('binary')
|
||||
.get();
|
||||
const buffer = Buffer.from(await response.arrayBuffer());
|
||||
return buffer.length;
|
||||
};
|
||||
return streamFile;
|
||||
}
|
||||
|
||||
public static fromBuffer(buffer: Buffer, relativeFilePath?: string): StreamFile {
|
||||
public static fromBuffer(
|
||||
buffer: Buffer,
|
||||
relativeFilePath?: string,
|
||||
): StreamFile {
|
||||
const streamSource: TStreamSource = async (streamFileArg) => {
|
||||
const stream = new Readable();
|
||||
stream.push(buffer);
|
||||
@@ -54,7 +67,11 @@ export class StreamFile {
|
||||
* @param multiUse If true, the stream can be read multiple times, caching its content.
|
||||
* @returns A StreamFile instance.
|
||||
*/
|
||||
public static fromStream(stream: Readable, relativeFilePath?: string, multiUse: boolean = false): StreamFile {
|
||||
public static fromStream(
|
||||
stream: Readable,
|
||||
relativeFilePath?: string,
|
||||
multiUse: boolean = false,
|
||||
): StreamFile {
|
||||
const streamSource: TStreamSource = (streamFileArg) => {
|
||||
if (streamFileArg.multiUse) {
|
||||
// If multi-use is enabled and we have cached content, create a new readable stream from the buffer
|
||||
@@ -86,7 +103,6 @@ export class StreamFile {
|
||||
return streamFile;
|
||||
}
|
||||
|
||||
|
||||
// INSTANCE
|
||||
relativeFilePath?: string;
|
||||
private streamSource: TStreamSource;
|
||||
@@ -115,7 +131,16 @@ export class StreamFile {
|
||||
* Creates a new readable stream from the source.
|
||||
*/
|
||||
public async createReadStream(): Promise<Readable> {
|
||||
return this.streamSource(this);
|
||||
const stream = await this.streamSource(this);
|
||||
|
||||
// Check if it's a Web ReadableStream and convert to Node.js Readable
|
||||
if (stream && typeof (stream as any).getReader === 'function') {
|
||||
// This is a Web ReadableStream, convert it to Node.js Readable
|
||||
return Readable.fromWeb(stream as any);
|
||||
}
|
||||
|
||||
// It's already a Node.js Readable stream
|
||||
return stream as Readable;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,4 +196,4 @@ export class StreamFile {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,33 +2,33 @@ import { SmartFile } from './classes.smartfile.js';
|
||||
import * as plugins from './plugins.js';
|
||||
import * as fs from './fs.js';
|
||||
|
||||
|
||||
export interface IVirtualDirectoryConstructorOptions {
|
||||
mode: ''
|
||||
mode: '';
|
||||
}
|
||||
|
||||
/**
|
||||
* a virtual directory exposes a fs api
|
||||
*/
|
||||
export class VirtualDirectory {
|
||||
|
||||
consstructor(options = {}) {
|
||||
|
||||
}
|
||||
consstructor(options = {}) {}
|
||||
|
||||
// STATIC
|
||||
public static async fromFsDirPath(pathArg: string): Promise<VirtualDirectory> {
|
||||
public static async fromFsDirPath(
|
||||
pathArg: string,
|
||||
): Promise<VirtualDirectory> {
|
||||
const newVirtualDir = new VirtualDirectory();
|
||||
newVirtualDir.addSmartfiles(await fs.fileTreeToObject(pathArg, '**/*'));
|
||||
return newVirtualDir;
|
||||
}
|
||||
|
||||
public static async fromVirtualDirTransferableObject(
|
||||
virtualDirTransferableObjectArg: plugins.smartfileInterfaces.VirtualDirTransferableObject
|
||||
virtualDirTransferableObjectArg: plugins.smartfileInterfaces.VirtualDirTransferableObject,
|
||||
): Promise<VirtualDirectory> {
|
||||
const newVirtualDir = new VirtualDirectory();
|
||||
for (const fileArg of virtualDirTransferableObjectArg.files) {
|
||||
newVirtualDir.addSmartfiles([SmartFile.enfoldFromJson(fileArg) as SmartFile]);
|
||||
newVirtualDir.addSmartfiles([
|
||||
SmartFile.enfoldFromJson(fileArg) as SmartFile,
|
||||
]);
|
||||
}
|
||||
return newVirtualDir;
|
||||
}
|
||||
@@ -52,7 +52,9 @@ export class VirtualDirectory {
|
||||
|
||||
public async toVirtualDirTransferableObject(): Promise<plugins.smartfileInterfaces.VirtualDirTransferableObject> {
|
||||
return {
|
||||
files: this.smartfileArray.map((smartfileArg) => smartfileArg.foldToJson()),
|
||||
files: this.smartfileArray.map((smartfileArg) =>
|
||||
smartfileArg.foldToJson(),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -78,7 +80,10 @@ export class VirtualDirectory {
|
||||
return newVirtualDir;
|
||||
}
|
||||
|
||||
public async addVirtualDirectory(virtualDir: VirtualDirectory, newRoot: string): Promise<void> {
|
||||
public async addVirtualDirectory(
|
||||
virtualDir: VirtualDirectory,
|
||||
newRoot: string,
|
||||
): Promise<void> {
|
||||
for (const file of virtualDir.smartfileArray) {
|
||||
file.path = plugins.path.join(newRoot, file.path);
|
||||
}
|
||||
|
186
ts/fs.ts
186
ts/fs.ts
@@ -74,15 +74,45 @@ 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, {});
|
||||
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);
|
||||
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,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -124,7 +154,10 @@ export const ensureEmptyDirSync = (dirPathArg: string) => {
|
||||
* @returns Promise<void>
|
||||
* @exec ASYNC
|
||||
*/
|
||||
export const ensureFile = async (filePathArg, initFileStringArg): Promise<void> => {
|
||||
export const ensureFile = async (
|
||||
filePathArg,
|
||||
initFileStringArg,
|
||||
): Promise<void> => {
|
||||
ensureFileSync(filePathArg, initFileStringArg);
|
||||
};
|
||||
|
||||
@@ -135,7 +168,10 @@ export const ensureFile = async (filePathArg, initFileStringArg): Promise<void>
|
||||
* @returns Promise<void>
|
||||
* @exec SYNC
|
||||
*/
|
||||
export const ensureFileSync = (filePathArg: string, initFileStringArg: string): void => {
|
||||
export const ensureFileSync = (
|
||||
filePathArg: string,
|
||||
initFileStringArg: string,
|
||||
): void => {
|
||||
if (fileExistsSync(filePathArg)) {
|
||||
return null;
|
||||
} else {
|
||||
@@ -191,7 +227,9 @@ export const removeManySync = (filePathArrayArg: string[]): void => {
|
||||
export const toObjectSync = (filePathArg, fileTypeArg?) => {
|
||||
const fileString = plugins.fsExtra.readFileSync(filePathArg, 'utf8');
|
||||
let fileType;
|
||||
fileTypeArg ? (fileType = fileTypeArg) : (fileType = interpreter.filetype(filePathArg));
|
||||
fileTypeArg
|
||||
? (fileType = fileTypeArg)
|
||||
: (fileType = interpreter.filetype(filePathArg));
|
||||
try {
|
||||
return interpreter.objectFile(fileString, fileType);
|
||||
} catch (err) {
|
||||
@@ -205,7 +243,10 @@ export const toObjectSync = (filePathArg, fileTypeArg?) => {
|
||||
*/
|
||||
export const toStringSync = (filePath: string): string => {
|
||||
const encoding = plugins.smartmime.getEncodingForPathSync(filePath);
|
||||
let fileString: string | Buffer = plugins.fsExtra.readFileSync(filePath, encoding);
|
||||
let fileString: string | Buffer = plugins.fsExtra.readFileSync(
|
||||
filePath,
|
||||
encoding,
|
||||
);
|
||||
if (Buffer.isBuffer(fileString)) {
|
||||
fileString = fileString.toString('binary');
|
||||
}
|
||||
@@ -232,7 +273,10 @@ export const toReadStream = (filePath: string): plugins.fs.ReadStream => {
|
||||
return plugins.fsExtra.createReadStream(filePath);
|
||||
};
|
||||
|
||||
export const fileTreeToHash = async (dirPathArg: string, miniMatchFilter: string) => {
|
||||
export const fileTreeToHash = async (
|
||||
dirPathArg: string,
|
||||
miniMatchFilter: string,
|
||||
) => {
|
||||
const fileTreeObject = await fileTreeToObject(dirPathArg, miniMatchFilter);
|
||||
let combinedString = '';
|
||||
for (const smartfile of fileTreeObject) {
|
||||
@@ -247,7 +291,10 @@ export const fileTreeToHash = async (dirPathArg: string, miniMatchFilter: string
|
||||
* @param dirPathArg the directory to start from
|
||||
* @param miniMatchFilter a minimatch filter of what files to include
|
||||
*/
|
||||
export const fileTreeToObject = async (dirPathArg: string, miniMatchFilter: string) => {
|
||||
export const fileTreeToObject = async (
|
||||
dirPathArg: string,
|
||||
miniMatchFilter: string,
|
||||
) => {
|
||||
// handle absolute miniMatchFilter
|
||||
let dirPath: string;
|
||||
if (plugins.path.isAbsolute(miniMatchFilter)) {
|
||||
@@ -274,7 +321,7 @@ export const fileTreeToObject = async (dirPathArg: string, miniMatchFilter: stri
|
||||
contentBuffer: fileBuffer,
|
||||
base: dirPath,
|
||||
path: filePath,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
return smartfileArray;
|
||||
@@ -284,7 +331,10 @@ export const fileTreeToObject = async (dirPathArg: string, miniMatchFilter: stri
|
||||
* lists Folders in a directory on local disk
|
||||
* @returns Promise with an array that contains the folder names
|
||||
*/
|
||||
export const listFolders = async (pathArg: string, regexFilter?: RegExp): Promise<string[]> => {
|
||||
export const listFolders = async (
|
||||
pathArg: string,
|
||||
regexFilter?: RegExp,
|
||||
): Promise<string[]> => {
|
||||
return listFoldersSync(pathArg, regexFilter);
|
||||
};
|
||||
|
||||
@@ -292,9 +342,14 @@ export const listFolders = async (pathArg: string, regexFilter?: RegExp): Promis
|
||||
* lists Folders SYNCHRONOUSLY in a directory on local disk
|
||||
* @returns an array with the folder names as strings
|
||||
*/
|
||||
export const listFoldersSync = (pathArg: string, regexFilter?: RegExp): string[] => {
|
||||
export const listFoldersSync = (
|
||||
pathArg: string,
|
||||
regexFilter?: RegExp,
|
||||
): string[] => {
|
||||
let folderArray = plugins.fsExtra.readdirSync(pathArg).filter((file) => {
|
||||
return plugins.fsExtra.statSync(plugins.path.join(pathArg, file)).isDirectory();
|
||||
return plugins.fsExtra
|
||||
.statSync(plugins.path.join(pathArg, file))
|
||||
.isDirectory();
|
||||
});
|
||||
if (regexFilter) {
|
||||
folderArray = folderArray.filter((fileItem) => {
|
||||
@@ -308,7 +363,10 @@ export const listFoldersSync = (pathArg: string, regexFilter?: RegExp): string[]
|
||||
* lists Files in a directory on local disk
|
||||
* @returns Promise
|
||||
*/
|
||||
export const listFiles = async (pathArg: string, regexFilter?: RegExp): Promise<string[]> => {
|
||||
export const listFiles = async (
|
||||
pathArg: string,
|
||||
regexFilter?: RegExp,
|
||||
): Promise<string[]> => {
|
||||
return listFilesSync(pathArg, regexFilter);
|
||||
};
|
||||
|
||||
@@ -316,7 +374,10 @@ export const listFiles = async (pathArg: string, regexFilter?: RegExp): Promise<
|
||||
* lists Files SYNCHRONOUSLY in a directory on local disk
|
||||
* @returns an array with the folder names as strings
|
||||
*/
|
||||
export const listFilesSync = (pathArg: string, regexFilter?: RegExp): string[] => {
|
||||
export const listFilesSync = (
|
||||
pathArg: string,
|
||||
regexFilter?: RegExp,
|
||||
): string[] => {
|
||||
let fileArray = plugins.fsExtra.readdirSync(pathArg).filter((file) => {
|
||||
return plugins.fsExtra.statSync(plugins.path.join(pathArg, file)).isFile();
|
||||
});
|
||||
@@ -332,7 +393,10 @@ export const listFilesSync = (pathArg: string, regexFilter?: RegExp): string[] =
|
||||
* lists all items (folders AND files) in a directory on local disk
|
||||
* @returns Promise<string[]>
|
||||
*/
|
||||
export const listAllItems = async (pathArg: string, regexFilter?: RegExp): Promise<string[]> => {
|
||||
export const listAllItems = async (
|
||||
pathArg: string,
|
||||
regexFilter?: RegExp,
|
||||
): Promise<string[]> => {
|
||||
return listAllItemsSync(pathArg, regexFilter);
|
||||
};
|
||||
|
||||
@@ -341,7 +405,10 @@ export const listAllItems = async (pathArg: string, regexFilter?: RegExp): Promi
|
||||
* @returns an array with the folder names as strings
|
||||
* @executes SYNC
|
||||
*/
|
||||
export const listAllItemsSync = (pathArg: string, regexFilter?: RegExp): string[] => {
|
||||
export const listAllItemsSync = (
|
||||
pathArg: string,
|
||||
regexFilter?: RegExp,
|
||||
): string[] => {
|
||||
let allItmesArray = plugins.fsExtra.readdirSync(pathArg).filter((file) => {
|
||||
return plugins.fsExtra.statSync(plugins.path.join(pathArg, file)).isFile();
|
||||
});
|
||||
@@ -361,7 +428,7 @@ export const listAllItemsSync = (pathArg: string, regexFilter?: RegExp): string[
|
||||
export const listFileTree = async (
|
||||
dirPathArg: string,
|
||||
miniMatchFilter: string,
|
||||
absolutePathsBool: boolean = false
|
||||
absolutePathsBool: boolean = false,
|
||||
): Promise<string[]> => {
|
||||
// handle absolute miniMatchFilter
|
||||
let dirPath: string;
|
||||
@@ -377,7 +444,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));
|
||||
@@ -402,7 +490,7 @@ export const listFileTree = async (
|
||||
*/
|
||||
export const waitForFileToBeReady = async (
|
||||
fileOrDirPathArg: string,
|
||||
timeoutMs: number = 60000
|
||||
timeoutMs: number = 60000,
|
||||
): Promise<void> => {
|
||||
const startTime = Date.now();
|
||||
|
||||
@@ -415,7 +503,7 @@ export const waitForFileToBeReady = async (
|
||||
while (true) {
|
||||
try {
|
||||
await plugins.smartpromise.fromCallback((cb) =>
|
||||
plugins.fs.access(pathToCheck, plugins.fs.constants.F_OK, cb)
|
||||
plugins.fs.access(pathToCheck, plugins.fs.constants.F_OK, cb),
|
||||
);
|
||||
return;
|
||||
} catch (err: any) {
|
||||
@@ -436,19 +524,23 @@ export const waitForFileToBeReady = async (
|
||||
* @param filePathArg The path of the file to check.
|
||||
* @returns A promise that resolves once the file stops changing.
|
||||
*/
|
||||
const waitForSingleFileToBeStable = async (filePathArg: string): Promise<void> => {
|
||||
const waitForSingleFileToBeStable = async (
|
||||
filePathArg: string,
|
||||
): Promise<void> => {
|
||||
let lastFileSize = -1;
|
||||
let fileIsStable = false;
|
||||
|
||||
// We'll create a helper for repeated stats-checking logic
|
||||
const checkFileStability = async () => {
|
||||
try {
|
||||
const stats = await plugins.smartpromise.fromCallback<plugins.fs.Stats>((cb) =>
|
||||
plugins.fs.stat(filePathArg, cb)
|
||||
const stats = await plugins.smartpromise.fromCallback<plugins.fs.Stats>(
|
||||
(cb) => plugins.fs.stat(filePathArg, cb),
|
||||
);
|
||||
if (stats.isDirectory()) {
|
||||
// If it unexpectedly turns out to be a directory here, throw
|
||||
throw new Error(`Expected a file but found a directory: ${filePathArg}`);
|
||||
throw new Error(
|
||||
`Expected a file but found a directory: ${filePathArg}`,
|
||||
);
|
||||
}
|
||||
if (stats.size === lastFileSize) {
|
||||
fileIsStable = true;
|
||||
@@ -468,17 +560,23 @@ export const waitForFileToBeReady = async (
|
||||
await ensurePathExists(filePathArg);
|
||||
|
||||
// Set up a watcher on the file itself
|
||||
const fileWatcher = plugins.fs.watch(filePathArg, { persistent: true }, async () => {
|
||||
if (!fileIsStable) {
|
||||
await checkFileStability();
|
||||
}
|
||||
});
|
||||
const fileWatcher = plugins.fs.watch(
|
||||
filePathArg,
|
||||
{ persistent: true },
|
||||
async () => {
|
||||
if (!fileIsStable) {
|
||||
await checkFileStability();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
// Poll until stable or timeout
|
||||
while (!fileIsStable) {
|
||||
if (Date.now() - startTime > timeoutMs) {
|
||||
throw new Error(`Timeout waiting for file to stabilize: ${filePathArg}`);
|
||||
throw new Error(
|
||||
`Timeout waiting for file to stabilize: ${filePathArg}`,
|
||||
);
|
||||
}
|
||||
await checkFileStability();
|
||||
if (!fileIsStable) {
|
||||
@@ -499,7 +597,7 @@ export const waitForFileToBeReady = async (
|
||||
try {
|
||||
await ensurePathExists(fileOrDirPathArg);
|
||||
return await plugins.smartpromise.fromCallback<plugins.fs.Stats>((cb) =>
|
||||
plugins.fs.stat(fileOrDirPathArg, cb)
|
||||
plugins.fs.stat(fileOrDirPathArg, cb),
|
||||
);
|
||||
} catch (err) {
|
||||
// If there's an error (including timeout), just rethrow
|
||||
@@ -519,14 +617,15 @@ export const waitForFileToBeReady = async (
|
||||
// Helper to find the first file in the directory if it exists
|
||||
const getFirstFileInDirectory = async (): Promise<string | null> => {
|
||||
const entries = await plugins.smartpromise.fromCallback<string[]>((cb) =>
|
||||
plugins.fs.readdir(dirPath, cb)
|
||||
plugins.fs.readdir(dirPath, cb),
|
||||
);
|
||||
// We only want actual files, not subdirectories
|
||||
for (const entry of entries) {
|
||||
const entryPath = plugins.path.join(dirPath, entry);
|
||||
const entryStats = await plugins.smartpromise.fromCallback<plugins.fs.Stats>((cb) =>
|
||||
plugins.fs.stat(entryPath, cb)
|
||||
);
|
||||
const entryStats =
|
||||
await plugins.smartpromise.fromCallback<plugins.fs.Stats>((cb) =>
|
||||
plugins.fs.stat(entryPath, cb),
|
||||
);
|
||||
if (entryStats.isFile()) {
|
||||
return entryPath;
|
||||
}
|
||||
@@ -543,7 +642,9 @@ export const waitForFileToBeReady = async (
|
||||
// We'll poll for the existence of a file in that directory
|
||||
while (!firstFilePath) {
|
||||
if (Date.now() - startTime > timeoutMs) {
|
||||
throw new Error(`Timeout waiting for a file to appear in directory: ${dirPath}`);
|
||||
throw new Error(
|
||||
`Timeout waiting for a file to appear in directory: ${dirPath}`,
|
||||
);
|
||||
}
|
||||
firstFilePath = await getFirstFileInDirectory();
|
||||
if (!firstFilePath) {
|
||||
@@ -570,7 +671,7 @@ export let toFs = async (
|
||||
filePathArg: string,
|
||||
optionsArg: {
|
||||
respectRelative?: boolean;
|
||||
} = {}
|
||||
} = {},
|
||||
) => {
|
||||
const done = plugins.smartpromise.defer();
|
||||
|
||||
@@ -600,7 +701,12 @@ export let toFs = async (
|
||||
throw new Error('fileContent is neither string nor Smartfile');
|
||||
}
|
||||
await ensureDir(plugins.path.parse(filePath).dir);
|
||||
plugins.fsExtra.writeFile(filePath, fileContent, { encoding: fileEncoding }, done.resolve);
|
||||
plugins.fsExtra.writeFile(
|
||||
filePath,
|
||||
fileContent,
|
||||
{ encoding: fileEncoding },
|
||||
done.resolve,
|
||||
);
|
||||
return await done.promise;
|
||||
};
|
||||
|
||||
|
@@ -13,17 +13,17 @@ export const createWriteStream = (pathArg: string) => {
|
||||
|
||||
export const processFile = async (
|
||||
filePath: string,
|
||||
asyncFunc: (fileStream: plugins.stream.Readable) => Promise<void>
|
||||
asyncFunc: (fileStream: plugins.stream.Readable) => Promise<void>,
|
||||
): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileStream = createReadStream(filePath);
|
||||
asyncFunc(fileStream).then(resolve).catch(reject);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const processDirectory = async (
|
||||
directoryPath: string,
|
||||
asyncFunc: (fileStream: plugins.stream.Readable) => Promise<void>
|
||||
asyncFunc: (fileStream: plugins.stream.Readable) => Promise<void>,
|
||||
): Promise<void> => {
|
||||
const files = plugins.fs.readdirSync(directoryPath, { withFileTypes: true });
|
||||
|
||||
@@ -41,12 +41,15 @@ export const processDirectory = async (
|
||||
/**
|
||||
* Checks if a file is ready to be streamed (exists and is not empty).
|
||||
*/
|
||||
export const isFileReadyForStreaming = async (filePathArg: string): Promise<boolean> => {
|
||||
export const isFileReadyForStreaming = async (
|
||||
filePathArg: string,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const stats = await plugins.fs.promises.stat(filePathArg);
|
||||
return stats.size > 0;
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') { // File does not exist
|
||||
if (error.code === 'ENOENT') {
|
||||
// File does not exist
|
||||
return false;
|
||||
}
|
||||
throw error; // Rethrow other unexpected errors
|
||||
@@ -56,7 +59,9 @@ export const isFileReadyForStreaming = async (filePathArg: string): Promise<bool
|
||||
/**
|
||||
* Waits for a file to be ready for streaming (exists and is not empty).
|
||||
*/
|
||||
export const waitForFileToBeReadyForStreaming = (filePathArg: string): Promise<void> => {
|
||||
export const waitForFileToBeReadyForStreaming = (
|
||||
filePathArg: string,
|
||||
): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Normalize and resolve the file path
|
||||
const filePath = plugins.path.resolve(filePathArg);
|
||||
@@ -80,11 +85,15 @@ export const waitForFileToBeReadyForStreaming = (filePathArg: string): Promise<v
|
||||
};
|
||||
|
||||
// Set up file watcher
|
||||
const watcher = plugins.fs.watch(filePath, { persistent: false }, (eventType) => {
|
||||
if (eventType === 'change' || eventType === 'rename') {
|
||||
checkFile(resolve, reject);
|
||||
}
|
||||
});
|
||||
const watcher = plugins.fs.watch(
|
||||
filePath,
|
||||
{ persistent: false },
|
||||
(eventType) => {
|
||||
if (eventType === 'change' || eventType === 'rename') {
|
||||
checkFile(resolve, reject);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Check file immediately in case it's already ready
|
||||
checkFile(resolve, reject);
|
||||
@@ -105,7 +114,11 @@ export class SmartReadStream extends plugins.stream.Readable {
|
||||
private endDelay: number;
|
||||
private reading: boolean = false;
|
||||
|
||||
constructor(filePath: string, endDelay = 60000, opts?: plugins.stream.ReadableOptions) {
|
||||
constructor(
|
||||
filePath: string,
|
||||
endDelay = 60000,
|
||||
opts?: plugins.stream.ReadableOptions,
|
||||
) {
|
||||
super(opts);
|
||||
this.filePath = filePath;
|
||||
this.endDelay = endDelay;
|
||||
@@ -165,26 +178,33 @@ export class SmartReadStream extends plugins.stream.Readable {
|
||||
this.emit('error', err);
|
||||
return;
|
||||
}
|
||||
plugins.fs.read(fd, buffer, 0, chunkSize, this.lastReadSize, (err, bytesRead, buffer) => {
|
||||
if (err) {
|
||||
this.emit('error', err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bytesRead > 0) {
|
||||
this.lastReadSize += bytesRead;
|
||||
this.push(buffer.slice(0, bytesRead)); // Push the data onto the stream
|
||||
} else {
|
||||
this.reading = false; // No more data to read for now
|
||||
this.resetEndTimeout();
|
||||
}
|
||||
|
||||
plugins.fs.close(fd, (err) => {
|
||||
plugins.fs.read(
|
||||
fd,
|
||||
buffer,
|
||||
0,
|
||||
chunkSize,
|
||||
this.lastReadSize,
|
||||
(err, bytesRead, buffer) => {
|
||||
if (err) {
|
||||
this.emit('error', err);
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (bytesRead > 0) {
|
||||
this.lastReadSize += bytesRead;
|
||||
this.push(buffer.slice(0, bytesRead)); // Push the data onto the stream
|
||||
} else {
|
||||
this.reading = false; // No more data to read for now
|
||||
this.resetEndTimeout();
|
||||
}
|
||||
|
||||
plugins.fs.close(fd, (err) => {
|
||||
if (err) {
|
||||
this.emit('error', err);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -192,4 +212,4 @@ export class SmartReadStream extends plugins.stream.Readable {
|
||||
this.cleanup();
|
||||
callback(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
14
ts/memory.ts
14
ts/memory.ts
@@ -27,7 +27,7 @@ export interface IToFsOptions {
|
||||
export let toFs = async (
|
||||
fileContentArg: string | Buffer | SmartFile | StreamFile,
|
||||
filePathArg: string,
|
||||
optionsArg: IToFsOptions = {}
|
||||
optionsArg: IToFsOptions = {},
|
||||
) => {
|
||||
const done = plugins.smartpromise.defer();
|
||||
|
||||
@@ -57,7 +57,12 @@ export let toFs = async (
|
||||
throw new Error('fileContent is neither string nor Smartfile');
|
||||
}
|
||||
await smartfileFs.ensureDir(plugins.path.parse(filePath).dir);
|
||||
plugins.fsExtra.writeFile(filePath, fileContent, { encoding: fileEncoding }, done.resolve);
|
||||
plugins.fsExtra.writeFile(
|
||||
filePath,
|
||||
fileContent,
|
||||
{ encoding: fileEncoding },
|
||||
done.resolve,
|
||||
);
|
||||
return await done.promise;
|
||||
};
|
||||
|
||||
@@ -84,7 +89,10 @@ export const toFsSync = (fileArg: string, filePathArg: string) => {
|
||||
plugins.fsExtra.writeFileSync(filePath, fileString, { encoding: 'utf8' });
|
||||
};
|
||||
|
||||
export let smartfileArrayToFs = async (smartfileArrayArg: SmartFile[], dirArg: string) => {
|
||||
export let smartfileArrayToFs = async (
|
||||
smartfileArrayArg: SmartFile[],
|
||||
dirArg: string,
|
||||
) => {
|
||||
await smartfileFs.ensureDir(dirArg);
|
||||
for (const smartfile of smartfileArrayArg) {
|
||||
await toFs(smartfile, dirArg, {
|
||||
|
@@ -6,9 +6,9 @@
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true
|
||||
"verbatimModuleSyntax": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {}
|
||||
},
|
||||
"exclude": [
|
||||
"dist_*/**/*.d.ts"
|
||||
]
|
||||
"exclude": ["dist_*/**/*.d.ts"]
|
||||
}
|
||||
|
Reference in New Issue
Block a user