Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e609c023bc | |||
| 634c204a00 | |||
| 7ccd210c45 | |||
| fe90de56d6 | |||
| d9251fa1a5 | |||
| ec58b9cdc5 | |||
| 9dbb7d9731 | |||
| 4428638170 | |||
| 1af585594c | |||
| 780db4921e | |||
| ed5f590b5f | |||
| a32ed0facd | |||
| b5a3793ed5 | |||
| be1bc958d8 | |||
| 21434622dd | |||
| 50c0368ac7 | |||
| 78b3fcfd83 | |||
| 61caf51f4e | |||
| be4e2cdae7 | |||
| 1cb8331666 | |||
| aa203c5ab2 | |||
| 87aedd5ef5 | |||
| 64431703b5 | |||
| 120fd0b321 | |||
| e6192c418e | |||
| 00a039936f | |||
| e03c3a62b1 | |||
| 538eced73b | |||
| fe8a5713f0 | |||
| 37e8a1d0f7 | |||
| 5143cd098d | |||
| 4d5ea812af | |||
| 500ef01ded | |||
| 3196a02835 | |||
| de1c46ed0a | |||
| b4c7b065fa | |||
| 93da11a951 | |||
| ecd7f6d419 | |||
| a3ecfe4d99 | |||
| f99f6d96c5 | |||
| b4be70f43a | |||
| 785e26e72d | |||
| e1891a6aa3 | |||
| f257c0c5a4 | |||
| 725546e409 | |||
| b9645dfb99 | |||
| b860aca103 | |||
| 39fb6e8ad1 | |||
| 04968a80b0 | |||
| e4a2c143bc | |||
| ed6d186a85 | |||
| 553c5dfe99 | |||
| 5d94efb9ee | |||
| c978ca107b | |||
| 876c8ce9d8 | |||
| 7327bf1bd0 | |||
| 2dcb10d233 | |||
| d53c46fa82 | |||
| 25e847a9ea | |||
| cc0ecb3f16 | |||
| 2cd0846c74 | |||
| 49ab40af09 | |||
| 5ff51ff88d | |||
| c578a3fdc1 | |||
| ad0352a712 | |||
| f921338fd6 | |||
| 614dae5ade | |||
| f87359fb97 | |||
| 21da75c09a | |||
| fe49d25765 | |||
| 5b693c6143 | |||
| 3206738da5 | |||
| f709421621 | |||
| 75be95fe45 | |||
| 1113020e17 | |||
| adf4bb64ad |
@@ -6,8 +6,8 @@ on:
|
|||||||
- '**'
|
- '**'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
|
||||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
- name: Install pnpm and npmci
|
- name: Install pnpm and npmci
|
||||||
run: |
|
run: |
|
||||||
pnpm install -g pnpm
|
pnpm install -g pnpm
|
||||||
pnpm install -g @shipzone/npmci
|
pnpm install -g @ship.zone/npmci
|
||||||
|
|
||||||
- name: Run npm prepare
|
- name: Run npm prepare
|
||||||
run: npmci npm prepare
|
run: npmci npm prepare
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ on:
|
|||||||
- '*'
|
- '*'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
|
||||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
- name: Prepare
|
- name: Prepare
|
||||||
run: |
|
run: |
|
||||||
pnpm install -g pnpm
|
pnpm install -g pnpm
|
||||||
pnpm install -g @shipzone/npmci
|
pnpm install -g @ship.zone/npmci
|
||||||
npmci npm prepare
|
npmci npm prepare
|
||||||
|
|
||||||
- name: Audit production dependencies
|
- name: Audit production dependencies
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
- name: Prepare
|
- name: Prepare
|
||||||
run: |
|
run: |
|
||||||
pnpm install -g pnpm
|
pnpm install -g pnpm
|
||||||
pnpm install -g @shipzone/npmci
|
pnpm install -g @ship.zone/npmci
|
||||||
npmci npm prepare
|
npmci npm prepare
|
||||||
|
|
||||||
- name: Test stable
|
- name: Test stable
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
- name: Prepare
|
- name: Prepare
|
||||||
run: |
|
run: |
|
||||||
pnpm install -g pnpm
|
pnpm install -g pnpm
|
||||||
pnpm install -g @shipzone/npmci
|
pnpm install -g @ship.zone/npmci
|
||||||
npmci npm prepare
|
npmci npm prepare
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
@@ -104,7 +104,7 @@ jobs:
|
|||||||
- name: Prepare
|
- name: Prepare
|
||||||
run: |
|
run: |
|
||||||
pnpm install -g pnpm
|
pnpm install -g pnpm
|
||||||
pnpm install -g @shipzone/npmci
|
pnpm install -g @ship.zone/npmci
|
||||||
npmci npm prepare
|
npmci npm prepare
|
||||||
|
|
||||||
- name: Code quality
|
- name: Code quality
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -3,7 +3,6 @@
|
|||||||
# artifacts
|
# artifacts
|
||||||
coverage/
|
coverage/
|
||||||
public/
|
public/
|
||||||
pages/
|
|
||||||
|
|
||||||
# installs
|
# installs
|
||||||
node_modules/
|
node_modules/
|
||||||
@@ -17,4 +16,8 @@ node_modules/
|
|||||||
dist/
|
dist/
|
||||||
dist_*/
|
dist_*/
|
||||||
|
|
||||||
# custom
|
# AI
|
||||||
|
.claude/
|
||||||
|
.serena/
|
||||||
|
|
||||||
|
#------# custom
|
||||||
BIN
.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl
vendored
Normal file
BIN
.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl
vendored
Normal file
Binary file not shown.
68
.serena/project.yml
Normal file
68
.serena/project.yml
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# language of the project (csharp, python, rust, java, typescript, go, cpp, or ruby)
|
||||||
|
# * For C, use cpp
|
||||||
|
# * For JavaScript, use typescript
|
||||||
|
# Special requirements:
|
||||||
|
# * csharp: Requires the presence of a .sln file in the project folder.
|
||||||
|
language: typescript
|
||||||
|
|
||||||
|
# whether to use the project's gitignore file to ignore files
|
||||||
|
# Added on 2025-04-07
|
||||||
|
ignore_all_files_in_gitignore: true
|
||||||
|
# list of additional paths to ignore
|
||||||
|
# same syntax as gitignore, so you can use * and **
|
||||||
|
# Was previously called `ignored_dirs`, please update your config if you are using that.
|
||||||
|
# Added (renamed) on 2025-04-07
|
||||||
|
ignored_paths: []
|
||||||
|
|
||||||
|
# whether the project is in read-only mode
|
||||||
|
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
|
||||||
|
# Added on 2025-04-18
|
||||||
|
read_only: false
|
||||||
|
|
||||||
|
|
||||||
|
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
|
||||||
|
# Below is the complete list of tools for convenience.
|
||||||
|
# To make sure you have the latest list of tools, and to view their descriptions,
|
||||||
|
# execute `uv run scripts/print_tool_overview.py`.
|
||||||
|
#
|
||||||
|
# * `activate_project`: Activates a project by name.
|
||||||
|
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
|
||||||
|
# * `create_text_file`: Creates/overwrites a file in the project directory.
|
||||||
|
# * `delete_lines`: Deletes a range of lines within a file.
|
||||||
|
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
|
||||||
|
# * `execute_shell_command`: Executes a shell command.
|
||||||
|
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
|
||||||
|
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
|
||||||
|
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
|
||||||
|
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
|
||||||
|
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
|
||||||
|
# * `initial_instructions`: Gets the initial instructions for the current project.
|
||||||
|
# Should only be used in settings where the system prompt cannot be set,
|
||||||
|
# e.g. in clients you have no control over, like Claude Desktop.
|
||||||
|
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
|
||||||
|
# * `insert_at_line`: Inserts content at a given line in a file.
|
||||||
|
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
|
||||||
|
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
|
||||||
|
# * `list_memories`: Lists memories in Serena's project-specific memory store.
|
||||||
|
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
|
||||||
|
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
|
||||||
|
# * `read_file`: Reads a file within the project directory.
|
||||||
|
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
|
||||||
|
# * `remove_project`: Removes a project from the Serena configuration.
|
||||||
|
# * `replace_lines`: Replaces a range of lines within a file with new content.
|
||||||
|
# * `replace_symbol_body`: Replaces the full definition of a symbol.
|
||||||
|
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
|
||||||
|
# * `search_for_pattern`: Performs a search for a pattern in the project.
|
||||||
|
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
|
||||||
|
# * `switch_modes`: Activates modes by providing a list of their names
|
||||||
|
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
|
||||||
|
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
|
||||||
|
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
|
||||||
|
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
|
||||||
|
excluded_tools: []
|
||||||
|
|
||||||
|
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
||||||
|
# (contrary to the memories, which are loaded on demand).
|
||||||
|
initial_prompt: ""
|
||||||
|
|
||||||
|
project_name: "smartarchive"
|
||||||
123
changelog.md
Normal file
123
changelog.md
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-11-25 - 4.2.4 - fix(plugins)
|
||||||
|
Migrate filesystem usage to Node fs/fsPromises and upgrade smartfile to v13; add listFileTree helper and update tests
|
||||||
|
|
||||||
|
- Bumped dependency @push.rocks/smartfile to ^13.0.0 and removed unused dependency `through`
|
||||||
|
- Replaced usages of smartfile.fs and smartfile.fsStream with Node native fs and fs/promises (createReadStream/createWriteStream, mkdir({recursive:true}), stat, readFile)
|
||||||
|
- Added plugins.listFileTree helper (recursive directory lister) and used it in TarTools.packDirectory and tests
|
||||||
|
- Updated SmartArchive.exportToFs to use plugins.fs and plugins.fsPromises for directory creation and file writes
|
||||||
|
- Updated TarTools to use plugins.fs.createReadStream and plugins.fsPromises.stat when packing directories
|
||||||
|
- Converted/updated tests to a Node/Deno-friendly test file (test.node+deno.ts) and switched test helpers to use fsPromises
|
||||||
|
- Added readme.hints.md with migration notes for Smartfile v13 and architecture/dependency notes
|
||||||
|
|
||||||
|
## 2025-11-25 - 4.2.3 - fix(build)
|
||||||
|
Upgrade dev tooling: bump @git.zone/tsbuild, @git.zone/tsrun and @git.zone/tstest versions
|
||||||
|
|
||||||
|
- Bump @git.zone/tsbuild from ^2.6.6 to ^3.1.0
|
||||||
|
- Bump @git.zone/tsrun from ^1.3.3 to ^2.0.0
|
||||||
|
- Bump @git.zone/tstest from ^2.3.4 to ^3.1.3
|
||||||
|
|
||||||
|
## 2025-08-18 - 4.2.2 - fix(smartarchive)
|
||||||
|
Improve tar entry streaming handling and add in-memory gzip/tgz tests
|
||||||
|
|
||||||
|
- Fix tar entry handling: properly consume directory entries (resume stream) and wait for entry end before continuing to next header
|
||||||
|
- Wrap tar file entries with a PassThrough so extracted StreamFile instances can be consumed while the tar extractor continues
|
||||||
|
- Handle nested archives correctly by piping resultStream -> decompressionStream -> analyzer -> unpacker, avoiding premature end signals
|
||||||
|
- Add and expand tests in test/test.gzip.ts: verify package.json and TS/license files after extraction, add in-memory gzip extraction test, and add real tgz-in-memory download+extraction test
|
||||||
|
- Minor logging improvements for tar extraction flow
|
||||||
|
|
||||||
|
## 2025-08-18 - 4.2.1 - fix(gzip)
|
||||||
|
Improve gzip streaming decompression, archive analysis and unpacking; add gzip tests
|
||||||
|
|
||||||
|
- Add a streaming DecompressGunzipTransform using fflate.Gunzip with proper _flush handling to support chunked gzip input and avoid buffering issues.
|
||||||
|
- Refactor ArchiveAnalyzer: introduce IAnalyzedResult, getAnalyzedStream(), and getDecompressionStream() to better detect mime types and wire appropriate decompression streams (gzip, zip, bzip2, tar).
|
||||||
|
- Use SmartRequest response streams converted via stream.Readable.fromWeb for URL sources in SmartArchive.getArchiveStream() to improve remote archive handling.
|
||||||
|
- Improve nested archive unpacking and SmartArchive export pipeline: more robust tar/zip handling, consistent SmartDuplex usage and backpressure handling.
|
||||||
|
- Enhance exportToFs: ensure directories, improved logging for relative paths, and safer write-stream wiring.
|
||||||
|
- Add comprehensive gzip-focused tests (test/test.gzip.ts) covering file extraction, stream extraction, header filename handling, large files, and a real-world tgz-from-URL extraction scenario.
|
||||||
|
|
||||||
|
## 2025-08-18 - 4.2.0 - feat(classes.smartarchive)
|
||||||
|
Support URL streams, recursive archive unpacking and filesystem export; improve ZIP/GZIP/BZIP2 robustness; CI and package metadata updates
|
||||||
|
|
||||||
|
- Add exportToFs(targetDir, fileName?) to write extracted StreamFile objects to the filesystem (ensures directories, logs relative paths, waits for write completion).
|
||||||
|
- Implement exportToStreamOfStreamFiles with recursive unpacking pipeline that handles application/x-tar (tar-stream Extract), application/zip (fflate Unzip), nested archives and StreamIntake for StreamFile results.
|
||||||
|
- Enhance getArchiveStream() to support URL/web streams (SmartRequest) and return Node Readable streams for remote archives.
|
||||||
|
- Make ZIP decompression more robust: accept ArrayBuffer-like chunks, coerce to Buffer before pushing to fflate.Unzip, and ensure SmartDuplex handling of results.
|
||||||
|
- Fixes and improvements to bzip2/gzip/tar tool implementations (various bug/formatting fixes, safer CRC and stream handling).
|
||||||
|
- Update CI workflows to use new registry image and adjust npmci install path; minor .gitignore additions.
|
||||||
|
- Package metadata tweaks: bugs URL and homepage updated, packageManager/pnpm fields adjusted.
|
||||||
|
- Documentation/readme expanded and polished with quick start, examples and API reference updates.
|
||||||
|
- Small test and plugin export cleanups (formatting and trailing commas removed/added).
|
||||||
|
- TypeScript/formatting fixes across many files (consistent casing, trailing commas, typings, tsconfig additions).
|
||||||
|
|
||||||
|
## 2025-08-18 - 4.1.0 - feat(classes.smartarchive)
|
||||||
|
|
||||||
|
Support URL web streams, add recursive archive unpacking and filesystem export, and improve ZIP decompression robustness
|
||||||
|
|
||||||
|
- ts/classes.smartarchive.ts: add exportToFs(targetDir, fileName?) to write extracted StreamFile objects to the filesystem (ensures directories, logs relative paths, waits for write completion).
|
||||||
|
- ts/classes.smartarchive.ts: implement exportToStreamOfStreamFiles with recursive unpacking pipeline that handles application/x-tar (tar-stream Extract), application/zip (fflate unzip), nested archives and StreamIntake for StreamFile results.
|
||||||
|
- ts/classes.smartarchive.ts: improve getArchiveStream() for URL sources by using SmartRequest.create().url(...).get() and converting the returned Web stream into a Node Readable stream.
|
||||||
|
- ts/classes.ziptools.ts: make ZIP decompression writeFunction more robust — accept non-Buffer chunks, coerce to Buffer before pushing to fflate.Unzip, and loosen the writeFunction typing to handle incoming ArrayBuffer-like data.
|
||||||
|
|
||||||
|
## 2024-10-13 - 4.0.39 - fix(core)
|
||||||
|
|
||||||
|
Fix dependencies and update documentation.
|
||||||
|
|
||||||
|
- Ensure package uses the latest dependencies
|
||||||
|
- Reviewed and grouped imports in TypeScript files
|
||||||
|
- Updated readme with advanced usage examples
|
||||||
|
|
||||||
|
## 2024-10-13 - 4.0.38 - fix(dependencies)
|
||||||
|
|
||||||
|
Update dependencies to latest versions
|
||||||
|
|
||||||
|
- Updated @push.rocks/smartfile to version 11.0.21
|
||||||
|
- Updated @push.rocks/smartpromise to version 4.0.4
|
||||||
|
- Updated @push.rocks/smartstream to version 3.0.46
|
||||||
|
- Updated @push.rocks/smarturl to version 3.1.0
|
||||||
|
- Updated file-type to version 19.5.0
|
||||||
|
- Updated @git.zone/tsbuild to version 2.1.84
|
||||||
|
- Updated @git.zone/tsrun to version 1.2.49
|
||||||
|
- Updated @push.rocks/tapbundle to version 5.3.0
|
||||||
|
|
||||||
|
## 2024-06-08 - 4.0.24 to 4.0.37 - Fixes and Updates
|
||||||
|
|
||||||
|
Core updates and bug fixes were implemented in versions 4.0.24 through 4.0.37.
|
||||||
|
|
||||||
|
- Repeated core updates and fixes applied consistently across multiple versions.
|
||||||
|
|
||||||
|
## 2024-06-06 - 4.0.22 to 4.0.23 - Descriptions and Fixes Updates
|
||||||
|
|
||||||
|
Efforts to update documentation and core features.
|
||||||
|
|
||||||
|
- "update description" in 4.0.22
|
||||||
|
- Updates to `tsconfig` and `npmextra.json` were performed.
|
||||||
|
- Ongoing core fixes.
|
||||||
|
|
||||||
|
## 2023-11-06 - 4.0.0 - Major Update with Breaking Changes
|
||||||
|
|
||||||
|
Introduction of significant updates and breaking changes.
|
||||||
|
|
||||||
|
- Transition to new version 4.0.0 with core updates.
|
||||||
|
- Break in compatibility due to major structural changes with core functionalities.
|
||||||
|
|
||||||
|
## 2023-07-11 - 3.0.6 - Organizational Changes
|
||||||
|
|
||||||
|
Structural reorganization and updates to the organization schema.
|
||||||
|
|
||||||
|
- Switch to new organizational schema implemented.
|
||||||
|
|
||||||
|
## 2022-04-04 - 3.0.0 - Build Updates and Breaking Changes
|
||||||
|
|
||||||
|
Major build update introducing breaking changes.
|
||||||
|
|
||||||
|
- Introduction of ESM structure with breaking changes.
|
||||||
|
|
||||||
|
## 2016-01-18 - 0.0.0 to 1.0.0 - Initial Development and Launch
|
||||||
|
|
||||||
|
Initial software development and establishment of core features.
|
||||||
|
|
||||||
|
- Project set-up including Travis CI integration.
|
||||||
|
- Launch of the first full version with code restructuring.
|
||||||
|
- Added callback support.
|
||||||
3
dist_ts/index.d.ts
vendored
3
dist_ts/index.d.ts
vendored
@@ -1 +1,4 @@
|
|||||||
export * from './classes.smartarchive.js';
|
export * from './classes.smartarchive.js';
|
||||||
|
export * from './classes.tartools.js';
|
||||||
|
export * from './classes.ziptools.js';
|
||||||
|
export * from './classes.gziptools.js';
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
export * from './classes.smartarchive.js';
|
export * from './classes.smartarchive.js';
|
||||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLDJCQUEyQixDQUFDIn0=
|
export * from './classes.tartools.js';
|
||||||
|
export * from './classes.ziptools.js';
|
||||||
|
export * from './classes.gziptools.js';
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLDJCQUEyQixDQUFDO0FBQzFDLGNBQWMsdUJBQXVCLENBQUM7QUFDdEMsY0FBYyx1QkFBdUIsQ0FBQztBQUN0QyxjQUFjLHdCQUF3QixDQUFDIn0=
|
||||||
@@ -6,12 +6,28 @@
|
|||||||
"gitzone": {
|
"gitzone": {
|
||||||
"projectType": "npm",
|
"projectType": "npm",
|
||||||
"module": {
|
"module": {
|
||||||
"githost": "gitlab.com",
|
"githost": "code.foss.global",
|
||||||
"gitscope": "push.rocks",
|
"gitscope": "push.rocks",
|
||||||
"gitrepo": "smartarchive",
|
"gitrepo": "smartarchive",
|
||||||
"description": "work with archives",
|
"description": "A library for working with archive files, providing utilities for compressing and decompressing data.",
|
||||||
"npmPackagename": "@push.rocks/smartarchive",
|
"npmPackagename": "@push.rocks/smartarchive",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"keywords": [
|
||||||
|
"archive",
|
||||||
|
"compression",
|
||||||
|
"decompression",
|
||||||
|
"zip",
|
||||||
|
"tar",
|
||||||
|
"gzip",
|
||||||
|
"bzip2",
|
||||||
|
"file extraction",
|
||||||
|
"file creation",
|
||||||
|
"data analysis",
|
||||||
|
"file stream"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"tsdoc": {
|
||||||
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
64
package.json
64
package.json
@@ -1,45 +1,44 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartarchive",
|
"name": "@push.rocks/smartarchive",
|
||||||
"version": "4.0.10",
|
"version": "4.2.4",
|
||||||
"description": "work with archives",
|
"description": "A library for working with archive files, providing utilities for compressing and decompressing data.",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
"typings": "dist_ts/index.d.ts",
|
"typings": "dist_ts/index.d.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "(tstest test/ --web)",
|
"test": "(tstest test/ --verbose)",
|
||||||
"build": "tsbuild --web --allowimplicitany",
|
"build": "tsbuild --web --allowimplicitany",
|
||||||
"buildDocs": "tsdoc"
|
"buildDocs": "tsdoc"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/pushrocks/smartarchive.git"
|
"url": "https://code.foss.global/push.rocks/smartarchive.git"
|
||||||
},
|
},
|
||||||
"author": "Lossless GmbH",
|
"author": "Lossless GmbH",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/pushrocks/smartarchive/issues"
|
"url": "https://code.foss.global/push.rocks/smartarchive/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/pushrocks/smartarchive#readme",
|
"homepage": "https://code.foss.global/push.rocks/smartarchive#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@push.rocks/smartfile": "^11.0.0",
|
"@push.rocks/smartdelay": "^3.0.5",
|
||||||
"@push.rocks/smartpath": "^5.0.11",
|
"@push.rocks/smartfile": "^13.0.0",
|
||||||
"@push.rocks/smartpromise": "^4.0.3",
|
"@push.rocks/smartpath": "^6.0.0",
|
||||||
"@push.rocks/smartrequest": "^2.0.21",
|
"@push.rocks/smartpromise": "^4.2.3",
|
||||||
"@push.rocks/smartrx": "^3.0.7",
|
"@push.rocks/smartrequest": "^4.2.2",
|
||||||
"@push.rocks/smartstream": "^3.0.15",
|
"@push.rocks/smartrx": "^3.0.10",
|
||||||
"@push.rocks/smartunique": "^3.0.6",
|
"@push.rocks/smartstream": "^3.2.5",
|
||||||
"@push.rocks/smarturl": "^3.0.7",
|
"@push.rocks/smartunique": "^3.0.9",
|
||||||
"@types/tar-stream": "^3.1.3",
|
"@push.rocks/smarturl": "^3.1.0",
|
||||||
"fflate": "^0.8.1",
|
"@types/tar-stream": "^3.1.4",
|
||||||
"file-type": "^18.7.0",
|
"fflate": "^0.8.2",
|
||||||
"tar-stream": "^3.1.6",
|
"file-type": "^21.0.0",
|
||||||
"through": "^2.3.8"
|
"tar-stream": "^3.1.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.1.66",
|
"@git.zone/tsbuild": "^3.1.0",
|
||||||
"@git.zone/tsrun": "^1.2.44",
|
"@git.zone/tsrun": "^2.0.0",
|
||||||
"@git.zone/tstest": "^1.0.84",
|
"@git.zone/tstest": "^3.1.3"
|
||||||
"@push.rocks/tapbundle": "^5.0.15"
|
|
||||||
},
|
},
|
||||||
"private": false,
|
"private": false,
|
||||||
"files": [
|
"files": [
|
||||||
@@ -56,5 +55,22 @@
|
|||||||
],
|
],
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"last 1 chrome versions"
|
"last 1 chrome versions"
|
||||||
]
|
],
|
||||||
|
"keywords": [
|
||||||
|
"archive",
|
||||||
|
"compression",
|
||||||
|
"decompression",
|
||||||
|
"zip",
|
||||||
|
"tar",
|
||||||
|
"gzip",
|
||||||
|
"bzip2",
|
||||||
|
"file extraction",
|
||||||
|
"file creation",
|
||||||
|
"data analysis",
|
||||||
|
"file stream"
|
||||||
|
],
|
||||||
|
"packageManager": "pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748",
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13274
pnpm-lock.yaml
generated
13274
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
4
pnpm-workspace.yaml
Normal file
4
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
onlyBuiltDependencies:
|
||||||
|
- esbuild
|
||||||
|
- mongodb-memory-server
|
||||||
|
- puppeteer
|
||||||
38
readme.hints.md
Normal file
38
readme.hints.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Smartarchive Development Hints
|
||||||
|
|
||||||
|
## Dependency Upgrades (2025-01-25)
|
||||||
|
|
||||||
|
### Completed Upgrades
|
||||||
|
- **@git.zone/tsbuild**: ^2.6.6 → ^3.1.0
|
||||||
|
- **@git.zone/tsrun**: ^1.3.3 → ^2.0.0
|
||||||
|
- **@git.zone/tstest**: ^2.3.4 → ^3.1.3
|
||||||
|
- **@push.rocks/smartfile**: ^11.2.7 → ^13.0.0
|
||||||
|
|
||||||
|
### Migration Notes
|
||||||
|
|
||||||
|
#### Smartfile v13 Migration
|
||||||
|
Smartfile v13 removed filesystem operations (`fs`, `memory`, `fsStream` namespaces). These were replaced with Node.js native `fs` and `fs/promises`:
|
||||||
|
|
||||||
|
**Replacements made:**
|
||||||
|
- `smartfile.fs.ensureDir(path)` → `fsPromises.mkdir(path, { recursive: true })`
|
||||||
|
- `smartfile.fs.stat(path)` → `fsPromises.stat(path)`
|
||||||
|
- `smartfile.fs.toReadStream(path)` → `fs.createReadStream(path)`
|
||||||
|
- `smartfile.fs.toStringSync(path)` → `fsPromises.readFile(path, 'utf8')`
|
||||||
|
- `smartfile.fs.listFileTree(dir, pattern)` → custom `listFileTree()` helper
|
||||||
|
- `smartfile.fsStream.createReadStream(path)` → `fs.createReadStream(path)`
|
||||||
|
- `smartfile.fsStream.createWriteStream(path)` → `fs.createWriteStream(path)`
|
||||||
|
- `smartfile.memory.toFs(content, path)` → `fsPromises.writeFile(path, content)`
|
||||||
|
|
||||||
|
**Still using from smartfile v13:**
|
||||||
|
- `SmartFile` class (in-memory file representation)
|
||||||
|
- `StreamFile` class (streaming file handling)
|
||||||
|
|
||||||
|
### Removed Dependencies
|
||||||
|
- `through@2.3.8` - was unused in the codebase
|
||||||
|
|
||||||
|
## Architecture Notes
|
||||||
|
|
||||||
|
- Uses `fflate` for ZIP/GZIP compression (pure JS, works in browser)
|
||||||
|
- Uses `tar-stream` for TAR archive handling
|
||||||
|
- Uses `file-type` for MIME type detection
|
||||||
|
- Custom BZIP2 implementation in `ts/bzip2/` directory
|
||||||
486
readme.md
486
readme.md
@@ -1,54 +1,460 @@
|
|||||||
# @push.rocks/smartarchive
|
# @push.rocks/smartarchive 📦
|
||||||
work with archives
|
|
||||||
|
|
||||||
## Availabililty and Links
|
Powerful archive manipulation for modern Node.js applications.
|
||||||
* [npmjs.org (npm package)](https://www.npmjs.com/package/@push.rocks/smartarchive)
|
|
||||||
* [gitlab.com (source)](https://gitlab.com/push.rocks/smartarchive)
|
|
||||||
* [github.com (source mirror)](https://github.com/push.rocks/smartarchive)
|
|
||||||
* [docs (typedoc)](https://push.rocks.gitlab.io/smartarchive/)
|
|
||||||
|
|
||||||
## Status for master
|
`@push.rocks/smartarchive` is a versatile library for handling archive files with a focus on developer experience. Work with **zip**, **tar**, **gzip**, and **bzip2** formats through a unified, streaming-optimized API.
|
||||||
|
|
||||||
Status Category | Status Badge
|
## Issue Reporting and Security
|
||||||
-- | --
|
|
||||||
GitLab Pipelines | [](https://lossless.cloud)
|
|
||||||
GitLab Pipline Test Coverage | [](https://lossless.cloud)
|
|
||||||
npm | [](https://lossless.cloud)
|
|
||||||
Snyk | [](https://lossless.cloud)
|
|
||||||
TypeScript Support | [](https://lossless.cloud)
|
|
||||||
node Support | [](https://nodejs.org/dist/latest-v10.x/docs/api/)
|
|
||||||
Code Style | [](https://lossless.cloud)
|
|
||||||
PackagePhobia (total standalone install weight) | [](https://lossless.cloud)
|
|
||||||
PackagePhobia (package size on registry) | [](https://lossless.cloud)
|
|
||||||
BundlePhobia (total size when bundled) | [](https://lossless.cloud)
|
|
||||||
|
|
||||||
## Usage
|
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
||||||
|
|
||||||
Use TypeScript for best in class instellisense.
|
## Features 🚀
|
||||||
|
|
||||||
```javascript
|
- 📁 **Multi-format support** – Handle `.zip`, `.tar`, `.tar.gz`, `.tgz`, and `.bz2` archives
|
||||||
import * as smartarchive from 'smartarchive';
|
- 🌊 **Streaming-first architecture** – Process large archives without memory constraints
|
||||||
smartarchive
|
- 🔄 **Unified API** – Consistent interface across different archive formats
|
||||||
.get({
|
- 🎯 **Smart detection** – Automatically identifies archive types via magic bytes
|
||||||
from: 'https://example.com/example.zip',
|
- ⚡ **High performance** – Built on `tar-stream` and `fflate` for speed
|
||||||
toPath: '/some/local/absolute/path',
|
- 🔧 **Flexible I/O** – Work with files, URLs, and streams seamlessly
|
||||||
})
|
- 🛠️ **Modern TypeScript** – Full type safety and excellent IDE support
|
||||||
.then(/*...*/);
|
|
||||||
|
## Installation 📥
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Using pnpm (recommended)
|
||||||
|
pnpm add @push.rocks/smartarchive
|
||||||
|
|
||||||
|
# Using npm
|
||||||
|
npm install @push.rocks/smartarchive
|
||||||
|
|
||||||
|
# Using yarn
|
||||||
|
yarn add @push.rocks/smartarchive
|
||||||
```
|
```
|
||||||
|
|
||||||
For further information read the linked docs at the top of this README.
|
## Quick Start 🎯
|
||||||
|
|
||||||
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
|
### Extract an archive from URL
|
||||||
> | By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy.html)
|
|
||||||
|
|
||||||
[](https://push.rocks)
|
```typescript
|
||||||
|
import { SmartArchive } from '@push.rocks/smartarchive';
|
||||||
|
|
||||||
## Contribution
|
// Extract a .tar.gz archive from a URL directly to the filesystem
|
||||||
|
const archive = await SmartArchive.fromArchiveUrl(
|
||||||
|
'https://registry.npmjs.org/some-package/-/some-package-1.0.0.tgz'
|
||||||
|
);
|
||||||
|
await archive.exportToFs('./extracted');
|
||||||
|
```
|
||||||
|
|
||||||
We are always happy for code contributions. If you are not the code contributing type that is ok. Still, maintaining Open Source repositories takes considerable time and thought. If you like the quality of what we do and our modules are useful to you we would appreciate a little monthly contribution: You can [contribute one time](https://lossless.link/contribute-onetime) or [contribute monthly](https://lossless.link/contribute). :)
|
### Process archive as a stream
|
||||||
|
|
||||||
For further information read the linked docs at the top of this readme.
|
```typescript
|
||||||
|
import { SmartArchive } from '@push.rocks/smartarchive';
|
||||||
|
|
||||||
## Legal
|
// Stream-based processing for memory efficiency
|
||||||
> MIT licensed | **©** [Task Venture Capital GmbH](https://task.vc)
|
const archive = await SmartArchive.fromArchiveFile('./large-archive.zip');
|
||||||
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
|
const streamOfFiles = await archive.exportToStreamOfStreamFiles();
|
||||||
|
|
||||||
|
// Process each file in the archive
|
||||||
|
streamOfFiles.on('data', async (streamFile) => {
|
||||||
|
console.log(`Processing ${streamFile.relativeFilePath}`);
|
||||||
|
const readStream = await streamFile.createReadStream();
|
||||||
|
// Handle individual file stream
|
||||||
|
});
|
||||||
|
|
||||||
|
streamOfFiles.on('end', () => {
|
||||||
|
console.log('Extraction complete');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Concepts 💡
|
||||||
|
|
||||||
|
### Archive Sources
|
||||||
|
|
||||||
|
`SmartArchive` accepts archives from three sources:
|
||||||
|
|
||||||
|
| Source | Method | Use Case |
|
||||||
|
|--------|--------|----------|
|
||||||
|
| **URL** | `SmartArchive.fromArchiveUrl(url)` | Download and process archives from the web |
|
||||||
|
| **File** | `SmartArchive.fromArchiveFile(path)` | Load archives from the local filesystem |
|
||||||
|
| **Stream** | `SmartArchive.fromArchiveStream(stream)` | Process archives from any Node.js stream |
|
||||||
|
|
||||||
|
### Export Destinations
|
||||||
|
|
||||||
|
| Destination | Method | Use Case |
|
||||||
|
|-------------|--------|----------|
|
||||||
|
| **Filesystem** | `exportToFs(targetDir, fileName?)` | Extract directly to a directory |
|
||||||
|
| **Stream of files** | `exportToStreamOfStreamFiles()` | Process files individually as `StreamFile` objects |
|
||||||
|
|
||||||
|
## Usage Examples 🔨
|
||||||
|
|
||||||
|
### Working with ZIP files
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { SmartArchive } from '@push.rocks/smartarchive';
|
||||||
|
|
||||||
|
// Extract a ZIP file
|
||||||
|
const zipArchive = await SmartArchive.fromArchiveFile('./archive.zip');
|
||||||
|
await zipArchive.exportToFs('./output');
|
||||||
|
|
||||||
|
// Stream ZIP contents for processing
|
||||||
|
const fileStream = await zipArchive.exportToStreamOfStreamFiles();
|
||||||
|
|
||||||
|
fileStream.on('data', async (streamFile) => {
|
||||||
|
if (streamFile.relativeFilePath.endsWith('.json')) {
|
||||||
|
const readStream = await streamFile.createReadStream();
|
||||||
|
// Process JSON files from the archive
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Working with TAR archives
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { SmartArchive, TarTools } from '@push.rocks/smartarchive';
|
||||||
|
|
||||||
|
// Extract a .tar.gz file
|
||||||
|
const tarGzArchive = await SmartArchive.fromArchiveFile('./archive.tar.gz');
|
||||||
|
await tarGzArchive.exportToFs('./extracted');
|
||||||
|
|
||||||
|
// Create a TAR archive using TarTools directly
|
||||||
|
const tarTools = new TarTools();
|
||||||
|
const pack = await tarTools.getPackStream();
|
||||||
|
|
||||||
|
// Add files to the pack
|
||||||
|
await tarTools.addFileToPack(pack, {
|
||||||
|
fileName: 'hello.txt',
|
||||||
|
content: 'Hello, World!'
|
||||||
|
});
|
||||||
|
|
||||||
|
await tarTools.addFileToPack(pack, {
|
||||||
|
fileName: 'data.json',
|
||||||
|
content: Buffer.from(JSON.stringify({ foo: 'bar' }))
|
||||||
|
});
|
||||||
|
|
||||||
|
// Finalize and pipe to destination
|
||||||
|
pack.finalize();
|
||||||
|
pack.pipe(createWriteStream('./output.tar'));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pack a directory into TAR
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { TarTools } from '@push.rocks/smartarchive';
|
||||||
|
import { createWriteStream } from 'fs';
|
||||||
|
|
||||||
|
const tarTools = new TarTools();
|
||||||
|
|
||||||
|
// Pack an entire directory
|
||||||
|
const pack = await tarTools.packDirectory('./src');
|
||||||
|
pack.finalize();
|
||||||
|
pack.pipe(createWriteStream('./source.tar'));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extracting from URLs
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { SmartArchive } from '@push.rocks/smartarchive';
|
||||||
|
|
||||||
|
// Download and extract npm packages
|
||||||
|
const npmPackage = await SmartArchive.fromArchiveUrl(
|
||||||
|
'https://registry.npmjs.org/@push.rocks/smartfile/-/smartfile-11.2.7.tgz'
|
||||||
|
);
|
||||||
|
await npmPackage.exportToFs('./node_modules/@push.rocks/smartfile');
|
||||||
|
|
||||||
|
// Or process as stream for memory efficiency
|
||||||
|
const stream = await npmPackage.exportToStreamOfStreamFiles();
|
||||||
|
stream.on('data', async (file) => {
|
||||||
|
console.log(`Extracted: ${file.relativeFilePath}`);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Working with GZIP files
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { SmartArchive, GzipTools } from '@push.rocks/smartarchive';
|
||||||
|
import { createReadStream, createWriteStream } from 'fs';
|
||||||
|
|
||||||
|
// Decompress a .gz file - provide filename since gzip doesn't store it
|
||||||
|
const gzipArchive = await SmartArchive.fromArchiveFile('./data.json.gz');
|
||||||
|
await gzipArchive.exportToFs('./decompressed', 'data.json');
|
||||||
|
|
||||||
|
// Use GzipTools directly for streaming decompression
|
||||||
|
const gzipTools = new GzipTools();
|
||||||
|
const decompressStream = gzipTools.getDecompressionStream();
|
||||||
|
|
||||||
|
createReadStream('./compressed.gz')
|
||||||
|
.pipe(decompressStream)
|
||||||
|
.pipe(createWriteStream('./decompressed.txt'));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Working with BZIP2 files
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { SmartArchive } from '@push.rocks/smartarchive';
|
||||||
|
|
||||||
|
// Handle .bz2 files
|
||||||
|
const bzipArchive = await SmartArchive.fromArchiveUrl(
|
||||||
|
'https://example.com/data.bz2'
|
||||||
|
);
|
||||||
|
await bzipArchive.exportToFs('./extracted', 'data.txt');
|
||||||
|
```
|
||||||
|
|
||||||
|
### In-memory processing (no filesystem)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { SmartArchive } from '@push.rocks/smartarchive';
|
||||||
|
import { Readable } from 'stream';
|
||||||
|
|
||||||
|
// Process archives entirely in memory
|
||||||
|
const compressedBuffer = await fetchCompressedData();
|
||||||
|
const memoryStream = Readable.from(compressedBuffer);
|
||||||
|
|
||||||
|
const archive = await SmartArchive.fromArchiveStream(memoryStream);
|
||||||
|
const streamFiles = await archive.exportToStreamOfStreamFiles();
|
||||||
|
|
||||||
|
const extractedFiles: Array<{ name: string; content: Buffer }> = [];
|
||||||
|
|
||||||
|
streamFiles.on('data', async (streamFile) => {
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
const readStream = await streamFile.createReadStream();
|
||||||
|
|
||||||
|
for await (const chunk of readStream) {
|
||||||
|
chunks.push(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
extractedFiles.push({
|
||||||
|
name: streamFile.relativeFilePath,
|
||||||
|
content: Buffer.concat(chunks)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise((resolve) => streamFiles.on('end', resolve));
|
||||||
|
console.log(`Extracted ${extractedFiles.length} files in memory`);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nested archive handling (e.g., .tar.gz)
|
||||||
|
|
||||||
|
The library automatically handles nested compression. A `.tar.gz` file is:
|
||||||
|
1. First decompressed from gzip
|
||||||
|
2. Then unpacked from tar
|
||||||
|
|
||||||
|
This happens transparently:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { SmartArchive } from '@push.rocks/smartarchive';
|
||||||
|
|
||||||
|
// Automatically handles gzip → tar extraction chain
|
||||||
|
const tgzArchive = await SmartArchive.fromArchiveFile('./package.tar.gz');
|
||||||
|
await tgzArchive.exportToFs('./extracted');
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference 📚
|
||||||
|
|
||||||
|
### SmartArchive Class
|
||||||
|
|
||||||
|
The main entry point for archive operations.
|
||||||
|
|
||||||
|
#### Static Factory Methods
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Create from URL - downloads and processes archive
|
||||||
|
SmartArchive.fromArchiveUrl(url: string): Promise<SmartArchive>
|
||||||
|
|
||||||
|
// Create from local file path
|
||||||
|
SmartArchive.fromArchiveFile(path: string): Promise<SmartArchive>
|
||||||
|
|
||||||
|
// Create from any Node.js readable stream
|
||||||
|
SmartArchive.fromArchiveStream(stream: Readable | Duplex | Transform): Promise<SmartArchive>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Instance Methods
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Extract all files to a directory
|
||||||
|
// fileName is optional - used for single-file archives (like .gz) that don't store filename
|
||||||
|
exportToFs(targetDir: string, fileName?: string): Promise<void>
|
||||||
|
|
||||||
|
// Get a stream that emits StreamFile objects for each file in the archive
|
||||||
|
exportToStreamOfStreamFiles(): Promise<StreamIntake<StreamFile>>
|
||||||
|
|
||||||
|
// Get the raw archive stream (useful for piping)
|
||||||
|
getArchiveStream(): Promise<Readable>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Instance Properties
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
archive.tarTools // TarTools instance for TAR-specific operations
|
||||||
|
archive.zipTools // ZipTools instance for ZIP-specific operations
|
||||||
|
archive.gzipTools // GzipTools instance for GZIP-specific operations
|
||||||
|
archive.bzip2Tools // Bzip2Tools instance for BZIP2-specific operations
|
||||||
|
archive.archiveAnalyzer // ArchiveAnalyzer for inspecting archive type
|
||||||
|
```
|
||||||
|
|
||||||
|
### TarTools Class
|
||||||
|
|
||||||
|
TAR-specific operations for creating and extracting TAR archives.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { TarTools } from '@push.rocks/smartarchive';
|
||||||
|
|
||||||
|
const tarTools = new TarTools();
|
||||||
|
|
||||||
|
// Get a tar pack stream for creating archives
|
||||||
|
const pack = await tarTools.getPackStream();
|
||||||
|
|
||||||
|
// Add files to a pack stream
|
||||||
|
await tarTools.addFileToPack(pack, {
|
||||||
|
fileName: 'file.txt', // Name in archive
|
||||||
|
content: 'Hello World', // String, Buffer, Readable, SmartFile, or StreamFile
|
||||||
|
byteLength?: number, // Optional: specify size for streams
|
||||||
|
filePath?: string // Optional: path to file on disk
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pack an entire directory
|
||||||
|
const pack = await tarTools.packDirectory('./src');
|
||||||
|
|
||||||
|
// Get extraction stream
|
||||||
|
const extract = tarTools.getDecompressionStream();
|
||||||
|
```
|
||||||
|
|
||||||
|
### ZipTools Class
|
||||||
|
|
||||||
|
ZIP-specific operations.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ZipTools } from '@push.rocks/smartarchive';
|
||||||
|
|
||||||
|
const zipTools = new ZipTools();
|
||||||
|
|
||||||
|
// Get compression stream (for creating ZIP)
|
||||||
|
const compressor = zipTools.getCompressionStream();
|
||||||
|
|
||||||
|
// Get decompression stream (for extracting ZIP)
|
||||||
|
const decompressor = zipTools.getDecompressionStream();
|
||||||
|
```
|
||||||
|
|
||||||
|
### GzipTools Class
|
||||||
|
|
||||||
|
GZIP compression/decompression streams.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { GzipTools } from '@push.rocks/smartarchive';
|
||||||
|
|
||||||
|
const gzipTools = new GzipTools();
|
||||||
|
|
||||||
|
// Get compression stream
|
||||||
|
const compressor = gzipTools.getCompressionStream();
|
||||||
|
|
||||||
|
// Get decompression stream
|
||||||
|
const decompressor = gzipTools.getDecompressionStream();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supported Formats 📋
|
||||||
|
|
||||||
|
| Format | Extension(s) | Extract | Create |
|
||||||
|
|--------|--------------|---------|--------|
|
||||||
|
| TAR | `.tar` | ✅ | ✅ |
|
||||||
|
| TAR.GZ / TGZ | `.tar.gz`, `.tgz` | ✅ | ⚠️ |
|
||||||
|
| ZIP | `.zip` | ✅ | ⚠️ |
|
||||||
|
| GZIP | `.gz` | ✅ | ✅ |
|
||||||
|
| BZIP2 | `.bz2` | ✅ | ❌ |
|
||||||
|
|
||||||
|
✅ Full support | ⚠️ Partial/basic support | ❌ Not supported
|
||||||
|
|
||||||
|
## Performance Tips 🏎️
|
||||||
|
|
||||||
|
1. **Use streaming for large files** – Avoid loading entire archives into memory with `exportToStreamOfStreamFiles()`
|
||||||
|
2. **Provide byte lengths when known** – When adding streams to TAR, provide `byteLength` for better performance
|
||||||
|
3. **Process files as they stream** – Don't collect all files into an array unless necessary
|
||||||
|
4. **Choose the right format** – TAR.GZ for Unix/compression, ZIP for cross-platform compatibility
|
||||||
|
|
||||||
|
## Error Handling 🛡️
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { SmartArchive } from '@push.rocks/smartarchive';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const archive = await SmartArchive.fromArchiveUrl('https://example.com/file.zip');
|
||||||
|
await archive.exportToFs('./output');
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === 'ENOENT') {
|
||||||
|
console.error('Archive file not found');
|
||||||
|
} else if (error.code === 'EACCES') {
|
||||||
|
console.error('Permission denied');
|
||||||
|
} else if (error.message.includes('fetch')) {
|
||||||
|
console.error('Network error downloading archive');
|
||||||
|
} else {
|
||||||
|
console.error('Archive extraction failed:', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Real-World Use Cases 🌍
|
||||||
|
|
||||||
|
### CI/CD: Download & Extract Build Artifacts
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const artifacts = await SmartArchive.fromArchiveUrl(
|
||||||
|
`${CI_SERVER}/artifacts/build-${BUILD_ID}.zip`
|
||||||
|
);
|
||||||
|
await artifacts.exportToFs('./dist');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backup System: Restore from Archive
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const backup = await SmartArchive.fromArchiveFile('./backup-2024.tar.gz');
|
||||||
|
await backup.exportToFs('/restore/location');
|
||||||
|
```
|
||||||
|
|
||||||
|
### NPM Package Inspection
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const pkg = await SmartArchive.fromArchiveUrl(
|
||||||
|
'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz'
|
||||||
|
);
|
||||||
|
const files = await pkg.exportToStreamOfStreamFiles();
|
||||||
|
|
||||||
|
files.on('data', async (file) => {
|
||||||
|
if (file.relativeFilePath.includes('package.json')) {
|
||||||
|
const stream = await file.createReadStream();
|
||||||
|
// Read and analyze package.json
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Pipeline: Process Compressed Datasets
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const dataset = await SmartArchive.fromArchiveUrl(
|
||||||
|
'https://data.source/dataset.tar.gz'
|
||||||
|
);
|
||||||
|
|
||||||
|
const files = await dataset.exportToStreamOfStreamFiles();
|
||||||
|
files.on('data', async (file) => {
|
||||||
|
if (file.relativeFilePath.endsWith('.csv')) {
|
||||||
|
const stream = await file.createReadStream();
|
||||||
|
// Stream CSV processing
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
### Trademarks
|
||||||
|
|
||||||
|
This 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.
|
||||||
|
|
||||||
|
### Company Information
|
||||||
|
|
||||||
|
Task Venture Capital GmbH
|
||||||
|
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.
|
||||||
|
|||||||
@@ -1,13 +1,33 @@
|
|||||||
import * as path from 'path';
|
import * as path from 'node:path';
|
||||||
|
import * as fs from 'node:fs';
|
||||||
|
import * as fsPromises from 'node:fs/promises';
|
||||||
import * as smartpath from '@push.rocks/smartpath';
|
import * as smartpath from '@push.rocks/smartpath';
|
||||||
import * as smartfile from '@push.rocks/smartfile';
|
import * as smartfile from '@push.rocks/smartfile';
|
||||||
import * as smartrequest from '@push.rocks/smartrequest';
|
import * as smartrequest from '@push.rocks/smartrequest';
|
||||||
import * as smartstream from '@push.rocks/smartstream';
|
import * as smartstream from '@push.rocks/smartstream';
|
||||||
|
|
||||||
export {
|
export { path, fs, fsPromises, smartpath, smartfile, smartrequest, smartstream };
|
||||||
path,
|
|
||||||
smartpath,
|
/**
|
||||||
smartfile,
|
* List files in a directory recursively, returning relative paths
|
||||||
smartrequest,
|
*/
|
||||||
smartstream,
|
export async function listFileTree(dirPath: string, _pattern: string = '**/*'): Promise<string[]> {
|
||||||
|
const results: string[] = [];
|
||||||
|
|
||||||
|
async function walkDir(currentPath: string, relativePath: string = '') {
|
||||||
|
const entries = await fsPromises.readdir(currentPath, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
const entryRelPath = relativePath ? path.join(relativePath, entry.name) : entry.name;
|
||||||
|
const entryFullPath = path.join(currentPath, entry.name);
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
await walkDir(entryFullPath, entryRelPath);
|
||||||
|
} else if (entry.isFile()) {
|
||||||
|
results.push(entryRelPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await walkDir(dirPath);
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
|
|||||||
401
test/test.gzip.node+deno.ts
Normal file
401
test/test.gzip.node+deno.ts
Normal file
@@ -0,0 +1,401 @@
|
|||||||
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import * as smartarchive from '../ts/index.js';
|
||||||
|
|
||||||
|
const testPaths = {
|
||||||
|
nogitDir: plugins.path.join(
|
||||||
|
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
||||||
|
'../.nogit/',
|
||||||
|
),
|
||||||
|
gzipTestDir: plugins.path.join(
|
||||||
|
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
||||||
|
'../.nogit/gzip-test',
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
tap.preTask('should prepare test directories', async () => {
|
||||||
|
await plugins.fsPromises.mkdir(testPaths.gzipTestDir, { recursive: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should create and extract a gzip file', async () => {
|
||||||
|
// Create test data
|
||||||
|
const testContent = 'This is a test file for gzip compression and decompression.\n'.repeat(100);
|
||||||
|
const testFileName = 'test-file.txt';
|
||||||
|
const gzipFileName = 'test-file.txt.gz';
|
||||||
|
|
||||||
|
// Write the original file
|
||||||
|
await plugins.fsPromises.writeFile(
|
||||||
|
plugins.path.join(testPaths.gzipTestDir, testFileName),
|
||||||
|
testContent
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create gzip compressed version using fflate directly
|
||||||
|
const fflate = await import('fflate');
|
||||||
|
const compressed = fflate.gzipSync(Buffer.from(testContent));
|
||||||
|
await plugins.fsPromises.writeFile(
|
||||||
|
plugins.path.join(testPaths.gzipTestDir, gzipFileName),
|
||||||
|
Buffer.from(compressed)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now test extraction using SmartArchive
|
||||||
|
const gzipArchive = await smartarchive.SmartArchive.fromArchiveFile(
|
||||||
|
plugins.path.join(testPaths.gzipTestDir, gzipFileName)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Export to a new location
|
||||||
|
const extractPath = plugins.path.join(testPaths.gzipTestDir, 'extracted');
|
||||||
|
await plugins.fsPromises.mkdir(extractPath, { recursive: true });
|
||||||
|
// Provide a filename since gzip doesn't contain filename metadata
|
||||||
|
await gzipArchive.exportToFs(extractPath, 'test-file.txt');
|
||||||
|
|
||||||
|
// Read the extracted file
|
||||||
|
const extractedContent = await plugins.fsPromises.readFile(
|
||||||
|
plugins.path.join(extractPath, 'test-file.txt'),
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify the content matches
|
||||||
|
expect(extractedContent).toEqual(testContent);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should handle gzip stream extraction', async () => {
|
||||||
|
// Create test data
|
||||||
|
const testContent = 'Stream test data for gzip\n'.repeat(50);
|
||||||
|
const gzipFileName = 'stream-test.txt.gz';
|
||||||
|
|
||||||
|
// Create gzip compressed version
|
||||||
|
const fflate = await import('fflate');
|
||||||
|
const compressed = fflate.gzipSync(Buffer.from(testContent));
|
||||||
|
await plugins.fsPromises.writeFile(
|
||||||
|
plugins.path.join(testPaths.gzipTestDir, gzipFileName),
|
||||||
|
Buffer.from(compressed)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a read stream for the gzip file
|
||||||
|
const gzipStream = plugins.fs.createReadStream(
|
||||||
|
plugins.path.join(testPaths.gzipTestDir, gzipFileName)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test extraction using SmartArchive from stream
|
||||||
|
const gzipArchive = await smartarchive.SmartArchive.fromArchiveStream(gzipStream);
|
||||||
|
|
||||||
|
// Export to stream and collect the result
|
||||||
|
const streamFiles: any[] = [];
|
||||||
|
const resultStream = await gzipArchive.exportToStreamOfStreamFiles();
|
||||||
|
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
resultStream.on('data', (streamFile) => {
|
||||||
|
streamFiles.push(streamFile);
|
||||||
|
});
|
||||||
|
resultStream.on('end', resolve);
|
||||||
|
resultStream.on('error', reject);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify we got the expected file
|
||||||
|
expect(streamFiles.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Read content from the stream file
|
||||||
|
if (streamFiles[0]) {
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
const readStream = await streamFiles[0].createReadStream();
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
readStream.on('data', (chunk: Buffer) => chunks.push(chunk));
|
||||||
|
readStream.on('end', resolve);
|
||||||
|
readStream.on('error', reject);
|
||||||
|
});
|
||||||
|
|
||||||
|
const extractedContent = Buffer.concat(chunks).toString();
|
||||||
|
expect(extractedContent).toEqual(testContent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should handle gzip files with original filename in header', async () => {
|
||||||
|
// Test with a real-world gzip file that includes filename in header
|
||||||
|
const testContent = 'File with name in gzip header\n'.repeat(30);
|
||||||
|
const originalFileName = 'original-name.log';
|
||||||
|
const gzipFileName = 'compressed.gz';
|
||||||
|
|
||||||
|
// Create a proper gzip with filename header using Node's zlib
|
||||||
|
const zlib = await import('node:zlib');
|
||||||
|
const gzipBuffer = await new Promise<Buffer>((resolve, reject) => {
|
||||||
|
zlib.gzip(Buffer.from(testContent), {
|
||||||
|
level: 9,
|
||||||
|
// Note: Node's zlib doesn't support embedding filename directly,
|
||||||
|
// but we can test the extraction anyway
|
||||||
|
}, (err, result) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
else resolve(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await plugins.fsPromises.writeFile(
|
||||||
|
plugins.path.join(testPaths.gzipTestDir, gzipFileName),
|
||||||
|
gzipBuffer
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test extraction
|
||||||
|
const gzipArchive = await smartarchive.SmartArchive.fromArchiveFile(
|
||||||
|
plugins.path.join(testPaths.gzipTestDir, gzipFileName)
|
||||||
|
);
|
||||||
|
|
||||||
|
const extractPath = plugins.path.join(testPaths.gzipTestDir, 'header-test');
|
||||||
|
await plugins.fsPromises.mkdir(extractPath, { recursive: true });
|
||||||
|
// Provide a filename since gzip doesn't reliably contain filename metadata
|
||||||
|
await gzipArchive.exportToFs(extractPath, 'compressed.txt');
|
||||||
|
|
||||||
|
// Check if file was extracted (name might be derived from archive name)
|
||||||
|
const files = await plugins.listFileTree(extractPath, '**/*');
|
||||||
|
expect(files.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Read and verify content
|
||||||
|
const extractedFile = files[0];
|
||||||
|
const extractedContent = await plugins.fsPromises.readFile(
|
||||||
|
plugins.path.join(extractPath, extractedFile || 'compressed.txt'),
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
expect(extractedContent).toEqual(testContent);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should handle large gzip files', async () => {
|
||||||
|
// Create a larger test file
|
||||||
|
const largeContent = 'x'.repeat(1024 * 1024); // 1MB of 'x' characters
|
||||||
|
const gzipFileName = 'large-file.txt.gz';
|
||||||
|
|
||||||
|
// Compress the large file
|
||||||
|
const fflate = await import('fflate');
|
||||||
|
const compressed = fflate.gzipSync(Buffer.from(largeContent));
|
||||||
|
await plugins.fsPromises.writeFile(
|
||||||
|
plugins.path.join(testPaths.gzipTestDir, gzipFileName),
|
||||||
|
Buffer.from(compressed)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test extraction
|
||||||
|
const gzipArchive = await smartarchive.SmartArchive.fromArchiveFile(
|
||||||
|
plugins.path.join(testPaths.gzipTestDir, gzipFileName)
|
||||||
|
);
|
||||||
|
|
||||||
|
const extractPath = plugins.path.join(testPaths.gzipTestDir, 'large-extracted');
|
||||||
|
await plugins.fsPromises.mkdir(extractPath, { recursive: true });
|
||||||
|
// Provide a filename since gzip doesn't contain filename metadata
|
||||||
|
await gzipArchive.exportToFs(extractPath, 'large-file.txt');
|
||||||
|
|
||||||
|
// Verify the extracted content
|
||||||
|
const files = await plugins.listFileTree(extractPath, '**/*');
|
||||||
|
expect(files.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
const extractedContent = await plugins.fsPromises.readFile(
|
||||||
|
plugins.path.join(extractPath, files[0] || 'large-file.txt'),
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
expect(extractedContent.length).toEqual(largeContent.length);
|
||||||
|
expect(extractedContent).toEqual(largeContent);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should handle real-world multi-chunk gzip from URL', async () => {
|
||||||
|
// Test with a real tgz file that will be processed in multiple chunks
|
||||||
|
const testUrl = 'https://registry.npmjs.org/@push.rocks/smartfile/-/smartfile-11.2.7.tgz';
|
||||||
|
|
||||||
|
// Download and extract the archive
|
||||||
|
const testArchive = await smartarchive.SmartArchive.fromArchiveUrl(testUrl);
|
||||||
|
|
||||||
|
const extractPath = plugins.path.join(testPaths.gzipTestDir, 'real-world-test');
|
||||||
|
await plugins.fsPromises.mkdir(extractPath, { recursive: true });
|
||||||
|
|
||||||
|
// This will test multi-chunk decompression as the file is larger
|
||||||
|
await testArchive.exportToFs(extractPath);
|
||||||
|
|
||||||
|
// Verify extraction worked
|
||||||
|
const files = await plugins.listFileTree(extractPath, '**/*');
|
||||||
|
expect(files.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Check for expected package structure
|
||||||
|
const hasPackageJson = files.some(f => f.includes('package.json'));
|
||||||
|
expect(hasPackageJson).toBeTrue();
|
||||||
|
|
||||||
|
// Read and verify package.json content
|
||||||
|
const packageJsonPath = files.find(f => f.includes('package.json'));
|
||||||
|
if (packageJsonPath) {
|
||||||
|
const packageJsonContent = await plugins.fsPromises.readFile(
|
||||||
|
plugins.path.join(extractPath, packageJsonPath),
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
const packageJson = JSON.parse(packageJsonContent);
|
||||||
|
expect(packageJson.name).toEqual('@push.rocks/smartfile');
|
||||||
|
expect(packageJson.version).toEqual('11.2.7');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and verify a TypeScript file
|
||||||
|
const tsFilePath = files.find(f => f.endsWith('.ts'));
|
||||||
|
if (tsFilePath) {
|
||||||
|
const tsFileContent = await plugins.fsPromises.readFile(
|
||||||
|
plugins.path.join(extractPath, tsFilePath),
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
// TypeScript files should have content
|
||||||
|
expect(tsFileContent.length).toBeGreaterThan(10);
|
||||||
|
console.log(` ✓ TypeScript file ${tsFilePath} has ${tsFileContent.length} bytes`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and verify license file
|
||||||
|
const licensePath = files.find(f => f.includes('license'));
|
||||||
|
if (licensePath) {
|
||||||
|
const licenseContent = await plugins.fsPromises.readFile(
|
||||||
|
plugins.path.join(extractPath, licensePath),
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
expect(licenseContent).toContain('MIT');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify we can read multiple files without corruption
|
||||||
|
const readableFiles = files.filter(f =>
|
||||||
|
f.endsWith('.json') || f.endsWith('.md') || f.endsWith('.ts') || f.endsWith('.js')
|
||||||
|
).slice(0, 5); // Test first 5 readable files
|
||||||
|
|
||||||
|
for (const file of readableFiles) {
|
||||||
|
const content = await plugins.fsPromises.readFile(
|
||||||
|
plugins.path.join(extractPath, file),
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
expect(content).toBeDefined();
|
||||||
|
expect(content.length).toBeGreaterThan(0);
|
||||||
|
console.log(` ✓ Successfully read ${file} (${content.length} bytes)`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should handle gzip extraction fully in memory', async () => {
|
||||||
|
// Create test data in memory
|
||||||
|
const testContent = 'This is test data for in-memory gzip processing\n'.repeat(100);
|
||||||
|
|
||||||
|
// Compress using fflate in memory
|
||||||
|
const fflate = await import('fflate');
|
||||||
|
const compressed = fflate.gzipSync(Buffer.from(testContent));
|
||||||
|
|
||||||
|
// Create a stream from the compressed data
|
||||||
|
const { Readable } = await import('node:stream');
|
||||||
|
const compressedStream = Readable.from(Buffer.from(compressed));
|
||||||
|
|
||||||
|
// Process through SmartArchive without touching filesystem
|
||||||
|
const gzipArchive = await smartarchive.SmartArchive.fromArchiveStream(compressedStream);
|
||||||
|
|
||||||
|
// Export to stream of stream files (in memory)
|
||||||
|
const streamFiles: plugins.smartfile.StreamFile[] = [];
|
||||||
|
const resultStream = await gzipArchive.exportToStreamOfStreamFiles();
|
||||||
|
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
resultStream.on('data', (streamFile: plugins.smartfile.StreamFile) => {
|
||||||
|
streamFiles.push(streamFile);
|
||||||
|
});
|
||||||
|
resultStream.on('end', resolve);
|
||||||
|
resultStream.on('error', reject);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify we got a file
|
||||||
|
expect(streamFiles.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Read the content from memory without filesystem
|
||||||
|
const firstFile = streamFiles[0];
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
const readStream = await firstFile.createReadStream();
|
||||||
|
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
readStream.on('data', (chunk: Buffer) => chunks.push(chunk));
|
||||||
|
readStream.on('end', resolve);
|
||||||
|
readStream.on('error', reject);
|
||||||
|
});
|
||||||
|
|
||||||
|
const extractedContent = Buffer.concat(chunks).toString();
|
||||||
|
expect(extractedContent).toEqual(testContent);
|
||||||
|
console.log(` ✓ In-memory extraction successful (${extractedContent.length} bytes)`);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should handle real tgz file fully in memory', async (tools) => {
|
||||||
|
await tools.timeout(10000); // Set 10 second timeout
|
||||||
|
// Download tgz file into memory
|
||||||
|
const response = await plugins.smartrequest.SmartRequest.create()
|
||||||
|
.url('https://registry.npmjs.org/@push.rocks/smartfile/-/smartfile-11.2.7.tgz')
|
||||||
|
.get();
|
||||||
|
|
||||||
|
const tgzBuffer = Buffer.from(await response.arrayBuffer());
|
||||||
|
console.log(` Downloaded ${tgzBuffer.length} bytes into memory`);
|
||||||
|
|
||||||
|
// Create stream from buffer
|
||||||
|
const { Readable: Readable2 } = await import('node:stream');
|
||||||
|
const tgzStream = Readable2.from(tgzBuffer);
|
||||||
|
|
||||||
|
// Process through SmartArchive in memory
|
||||||
|
const archive = await smartarchive.SmartArchive.fromArchiveStream(tgzStream);
|
||||||
|
|
||||||
|
// Export to stream of stream files (in memory)
|
||||||
|
const streamFiles: plugins.smartfile.StreamFile[] = [];
|
||||||
|
const resultStream = await archive.exportToStreamOfStreamFiles();
|
||||||
|
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
let timeout: NodeJS.Timeout;
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
};
|
||||||
|
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
cleanup();
|
||||||
|
resolve(); // Resolve after timeout if stream doesn't end
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
resultStream.on('data', (streamFile: plugins.smartfile.StreamFile) => {
|
||||||
|
streamFiles.push(streamFile);
|
||||||
|
});
|
||||||
|
|
||||||
|
resultStream.on('end', () => {
|
||||||
|
cleanup();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
resultStream.on('error', (err) => {
|
||||||
|
cleanup();
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(` Extracted ${streamFiles.length} files in memory`);
|
||||||
|
// At minimum we should have extracted something
|
||||||
|
expect(streamFiles.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Find and read package.json from memory
|
||||||
|
const packageJsonFile = streamFiles.find(f => f.relativeFilePath?.includes('package.json'));
|
||||||
|
|
||||||
|
if (packageJsonFile) {
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
const readStream = await packageJsonFile.createReadStream();
|
||||||
|
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
readStream.on('data', (chunk: Buffer) => chunks.push(chunk));
|
||||||
|
readStream.on('end', resolve);
|
||||||
|
readStream.on('error', reject);
|
||||||
|
});
|
||||||
|
|
||||||
|
const packageJsonContent = Buffer.concat(chunks).toString();
|
||||||
|
const packageJson = JSON.parse(packageJsonContent);
|
||||||
|
expect(packageJson.name).toEqual('@push.rocks/smartfile');
|
||||||
|
expect(packageJson.version).toEqual('11.2.7');
|
||||||
|
console.log(` ✓ Read package.json from memory: ${packageJson.name}@${packageJson.version}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read a few more files to verify integrity
|
||||||
|
const filesToCheck = streamFiles.slice(0, 3);
|
||||||
|
for (const file of filesToCheck) {
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
const readStream = await file.createReadStream();
|
||||||
|
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
readStream.on('data', (chunk: Buffer) => chunks.push(chunk));
|
||||||
|
readStream.on('end', resolve);
|
||||||
|
readStream.on('error', reject);
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = Buffer.concat(chunks);
|
||||||
|
expect(content.length).toBeGreaterThan(0);
|
||||||
|
console.log(` ✓ Read ${file.relativeFilePath} from memory (${content.length} bytes)`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
@@ -1,50 +1,52 @@
|
|||||||
import { tap, expect } from '@push.rocks/tapbundle';
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
|
||||||
import * as plugins from './plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
const testPaths = {
|
const testPaths = {
|
||||||
nogitDir: plugins.path.join(
|
nogitDir: plugins.path.join(
|
||||||
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
||||||
'../.nogit/'
|
'../.nogit/',
|
||||||
),
|
),
|
||||||
remoteDir: plugins.path.join(
|
remoteDir: plugins.path.join(
|
||||||
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
||||||
'../.nogit/remote'
|
'../.nogit/remote',
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
import * as smartarchive from '../ts/index.js';
|
import * as smartarchive from '../ts/index.js';
|
||||||
|
|
||||||
tap.preTask('should prepare .nogit dir', async () => {
|
tap.preTask('should prepare .nogit dir', async () => {
|
||||||
await plugins.smartfile.fs.ensureDir(testPaths.remoteDir);
|
await plugins.fsPromises.mkdir(testPaths.remoteDir, { recursive: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.preTask('should prepare downloads', async (tools) => {
|
tap.preTask('should prepare downloads', async (tools) => {
|
||||||
const downloadedFile: Buffer = (
|
const response = await plugins.smartrequest.SmartRequest.create()
|
||||||
await plugins.smartrequest.getBinary(
|
.url(
|
||||||
'https://verdaccio.lossless.digital/@pushrocks%2fwebsetup/-/websetup-2.0.14.tgz'
|
'https://verdaccio.lossless.digital/@pushrocks%2fwebsetup/-/websetup-2.0.14.tgz',
|
||||||
)
|
)
|
||||||
).body;
|
.get();
|
||||||
await plugins.smartfile.memory.toFs(
|
const downloadedFile: Buffer = Buffer.from(await response.arrayBuffer());
|
||||||
|
await plugins.fsPromises.writeFile(
|
||||||
|
plugins.path.join(testPaths.nogitDir, 'test.tgz'),
|
||||||
downloadedFile,
|
downloadedFile,
|
||||||
plugins.path.join(testPaths.nogitDir, 'test.tgz')
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should extract existing files on disk', async () => {
|
tap.test('should extract existing files on disk', async () => {
|
||||||
const testSmartarchive = await smartarchive.SmartArchive.fromArchiveUrl(
|
const testSmartarchive = await smartarchive.SmartArchive.fromArchiveUrl(
|
||||||
'https://verdaccio.lossless.digital/@pushrocks%2fwebsetup/-/websetup-2.0.14.tgz'
|
'https://verdaccio.lossless.digital/@pushrocks%2fwebsetup/-/websetup-2.0.14.tgz',
|
||||||
);
|
);
|
||||||
await testSmartarchive.exportToFs(testPaths.nogitDir);
|
await testSmartarchive.exportToFs(testPaths.nogitDir);
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.skip.test('should extract a b2zip', async () => {
|
tap.skip.test('should extract a b2zip', async () => {
|
||||||
const dataUrl = 'https://daten.offeneregister.de/de_companies_ocdata.jsonl.bz2';
|
const dataUrl =
|
||||||
|
'https://daten.offeneregister.de/de_companies_ocdata.jsonl.bz2';
|
||||||
const testArchive = await smartarchive.SmartArchive.fromArchiveUrl(dataUrl);
|
const testArchive = await smartarchive.SmartArchive.fromArchiveUrl(dataUrl);
|
||||||
await testArchive.exportToFs(
|
await testArchive.exportToFs(
|
||||||
plugins.path.join(testPaths.nogitDir, 'de_companies_ocdata.jsonl'),
|
plugins.path.join(testPaths.nogitDir, 'de_companies_ocdata.jsonl'),
|
||||||
'data.jsonl',
|
'data.jsonl',
|
||||||
);
|
);
|
||||||
})
|
});
|
||||||
|
|
||||||
tap.start();
|
await tap.start();
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* autocreated commitinfo by @pushrocks/commitinfo
|
* autocreated commitinfo by @push.rocks/commitinfo
|
||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartarchive',
|
name: '@push.rocks/smartarchive',
|
||||||
version: '4.0.10',
|
version: '4.2.4',
|
||||||
description: 'work with archives'
|
description: 'A library for working with archive files, providing utilities for compressing and decompressing data.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,44 @@
|
|||||||
var BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF];
|
var BITMASK = [0, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff];
|
||||||
|
|
||||||
// returns a function that reads bits.
|
// returns a function that reads bits.
|
||||||
// takes a buffer iterator as input
|
// takes a buffer iterator as input
|
||||||
export function bitIterator(nextBuffer: () => Buffer) {
|
export function bitIterator(nextBuffer: () => Buffer) {
|
||||||
var bit = 0, byte = 0;
|
var bit = 0,
|
||||||
var bytes = nextBuffer();
|
byte = 0;
|
||||||
var f = function(n) {
|
var bytes = nextBuffer();
|
||||||
if (n === null && bit != 0) { // align to byte boundary
|
var f = function (n) {
|
||||||
bit = 0
|
if (n === null && bit != 0) {
|
||||||
byte++;
|
// align to byte boundary
|
||||||
return;
|
bit = 0;
|
||||||
}
|
byte++;
|
||||||
var result = 0;
|
return;
|
||||||
while(n > 0) {
|
}
|
||||||
if (byte >= bytes.length) {
|
var result = 0;
|
||||||
byte = 0;
|
while (n > 0) {
|
||||||
bytes = nextBuffer();
|
if (byte >= bytes.length) {
|
||||||
}
|
byte = 0;
|
||||||
var left = 8 - bit;
|
bytes = nextBuffer();
|
||||||
if (bit === 0 && n > 0)
|
}
|
||||||
// @ts-ignore
|
var left = 8 - bit;
|
||||||
f.bytesRead++;
|
if (bit === 0 && n > 0)
|
||||||
if (n >= left) {
|
// @ts-ignore
|
||||||
result <<= left;
|
f.bytesRead++;
|
||||||
result |= (BITMASK[left] & bytes[byte++]);
|
if (n >= left) {
|
||||||
bit = 0;
|
result <<= left;
|
||||||
n -= left;
|
result |= BITMASK[left] & bytes[byte++];
|
||||||
} else {
|
bit = 0;
|
||||||
result <<= n;
|
n -= left;
|
||||||
result |= ((bytes[byte] & (BITMASK[n] << (8 - n - bit))) >> (8 - n - bit));
|
} else {
|
||||||
bit += n;
|
result <<= n;
|
||||||
n = 0;
|
result |=
|
||||||
}
|
(bytes[byte] & (BITMASK[n] << (8 - n - bit))) >> (8 - n - bit);
|
||||||
}
|
bit += n;
|
||||||
return result;
|
n = 0;
|
||||||
};
|
}
|
||||||
// @ts-ignore
|
}
|
||||||
f.bytesRead = 0;
|
return result;
|
||||||
return f;
|
};
|
||||||
};
|
// @ts-ignore
|
||||||
|
f.bytesRead = 0;
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export class Bzip2Error extends Error {
|
export class Bzip2Error extends Error {
|
||||||
public name: string = 'Bzip2Error';
|
public name: string = 'Bzip2Error';
|
||||||
public message: string;
|
public message: string;
|
||||||
public stack = (new Error()).stack;
|
public stack = new Error().stack;
|
||||||
|
|
||||||
constructor(messageArg: string) {
|
constructor(messageArg: string) {
|
||||||
super();
|
super();
|
||||||
@@ -10,326 +10,322 @@ export class Bzip2Error extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var messageArg = {
|
var messageArg = {
|
||||||
Error: function(message) {throw new Bzip2Error(message);}
|
Error: function (message) {
|
||||||
|
throw new Bzip2Error(message);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Bzip2 {
|
export class Bzip2 {
|
||||||
public Bzip2Error = Bzip2Error;
|
public Bzip2Error = Bzip2Error;
|
||||||
public crcTable =
|
public crcTable = [
|
||||||
[
|
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
|
||||||
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
|
0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
|
||||||
0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
|
0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7,
|
||||||
0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
|
0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
|
||||||
0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
|
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3,
|
||||||
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
|
0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
|
||||||
0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
|
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef,
|
||||||
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
|
0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
|
||||||
0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
|
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb,
|
||||||
0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
|
0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
|
||||||
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
|
0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
|
||||||
0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
|
0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
|
||||||
0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
|
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4,
|
||||||
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
|
0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
|
||||||
0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
|
0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08,
|
||||||
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
|
0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
|
||||||
0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
|
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc,
|
||||||
0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
|
0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
|
||||||
0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
|
0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050,
|
||||||
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
|
0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
|
||||||
0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
|
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
|
||||||
0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
|
0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
|
||||||
0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
|
0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1,
|
||||||
0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
|
0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
|
||||||
0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
|
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5,
|
||||||
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
|
0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
|
||||||
0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
|
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9,
|
||||||
0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
|
0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
|
||||||
0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
|
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd,
|
||||||
0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
|
0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
|
||||||
0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
|
0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
|
||||||
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
|
0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
|
||||||
0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
|
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2,
|
||||||
0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
|
0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
|
||||||
0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
|
0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e,
|
||||||
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
|
0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
|
||||||
0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
|
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a,
|
||||||
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
|
0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
|
||||||
0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
|
0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676,
|
||||||
0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
|
0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
|
||||||
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
|
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
|
||||||
0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
|
0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
|
||||||
0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
|
0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4,
|
||||||
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
|
|
||||||
0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
|
|
||||||
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
|
|
||||||
0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
|
|
||||||
0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
|
|
||||||
0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
|
|
||||||
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
|
|
||||||
0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
|
|
||||||
0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
|
|
||||||
0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
|
|
||||||
0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
|
|
||||||
0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
|
|
||||||
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
|
|
||||||
0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
|
|
||||||
0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
|
|
||||||
0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
|
|
||||||
0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
|
|
||||||
0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
|
|
||||||
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
|
|
||||||
0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
|
|
||||||
0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
|
|
||||||
0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
|
|
||||||
];
|
];
|
||||||
|
|
||||||
array = function(bytes) {
|
array = function (bytes) {
|
||||||
var bit = 0, byte = 0;
|
var bit = 0,
|
||||||
var BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF ];
|
byte = 0;
|
||||||
return function(n) {
|
var BITMASK = [0, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff];
|
||||||
var result = 0;
|
return function (n) {
|
||||||
while(n > 0) {
|
var result = 0;
|
||||||
var left = 8 - bit;
|
while (n > 0) {
|
||||||
if (n >= left) {
|
var left = 8 - bit;
|
||||||
result <<= left;
|
if (n >= left) {
|
||||||
result |= (BITMASK[left] & bytes[byte++]);
|
result <<= left;
|
||||||
bit = 0;
|
result |= BITMASK[left] & bytes[byte++];
|
||||||
n -= left;
|
bit = 0;
|
||||||
} else {
|
n -= left;
|
||||||
result <<= n;
|
} else {
|
||||||
result |= ((bytes[byte] & (BITMASK[n] << (8 - n - bit))) >> (8 - n - bit));
|
result <<= n;
|
||||||
bit += n;
|
result |=
|
||||||
n = 0;
|
(bytes[byte] & (BITMASK[n] << (8 - n - bit))) >> (8 - n - bit);
|
||||||
}
|
bit += n;
|
||||||
|
n = 0;
|
||||||
}
|
}
|
||||||
return result;
|
}
|
||||||
}
|
return result;
|
||||||
}
|
};
|
||||||
|
};
|
||||||
|
|
||||||
simple = function(srcbuffer, stream) {
|
simple = function (srcbuffer, stream) {
|
||||||
var bits = this.array(srcbuffer);
|
var bits = this.array(srcbuffer);
|
||||||
var size = this.header(bits);
|
var size = this.header(bits);
|
||||||
var ret = false;
|
var ret = false;
|
||||||
var bufsize = 100000 * size;
|
var bufsize = 100000 * size;
|
||||||
var buf = new Int32Array(bufsize);
|
var buf = new Int32Array(bufsize);
|
||||||
|
|
||||||
do {
|
|
||||||
ret = this.decompress(bits, stream, buf, bufsize);
|
|
||||||
} while(!ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
header = function(bits) {
|
do {
|
||||||
|
ret = this.decompress(bits, stream, buf, bufsize);
|
||||||
|
} while (!ret);
|
||||||
|
};
|
||||||
|
|
||||||
|
header = function (bits) {
|
||||||
this.byteCount = new Int32Array(256);
|
this.byteCount = new Int32Array(256);
|
||||||
this.symToByte = new Uint8Array(256);
|
this.symToByte = new Uint8Array(256);
|
||||||
this.mtfSymbol = new Int32Array(256);
|
this.mtfSymbol = new Int32Array(256);
|
||||||
this.selectors = new Uint8Array(0x8000);
|
this.selectors = new Uint8Array(0x8000);
|
||||||
|
|
||||||
if (bits(8*3) != 4348520) messageArg.Error("No magic number found");
|
if (bits(8 * 3) != 4348520) messageArg.Error('No magic number found');
|
||||||
|
|
||||||
var i = bits(8) - 48;
|
var i = bits(8) - 48;
|
||||||
if (i < 1 || i > 9) messageArg.Error("Not a BZIP archive");
|
if (i < 1 || i > 9) messageArg.Error('Not a BZIP archive');
|
||||||
return i;
|
return i;
|
||||||
};
|
};
|
||||||
|
|
||||||
decompress = function(bits, stream, buf, bufsize, streamCRC) {
|
decompress = function (bits, stream, buf, bufsize, streamCRC) {
|
||||||
var MAX_HUFCODE_BITS = 20;
|
var MAX_HUFCODE_BITS = 20;
|
||||||
var MAX_SYMBOLS = 258;
|
var MAX_SYMBOLS = 258;
|
||||||
var SYMBOL_RUNA = 0;
|
var SYMBOL_RUNA = 0;
|
||||||
var SYMBOL_RUNB = 1;
|
var SYMBOL_RUNB = 1;
|
||||||
var GROUP_SIZE = 50;
|
var GROUP_SIZE = 50;
|
||||||
var crc = 0 ^ (-1);
|
var crc = 0 ^ -1;
|
||||||
|
|
||||||
for(var h = '', i = 0; i < 6; i++) h += bits(8).toString(16);
|
for (var h = '', i = 0; i < 6; i++) h += bits(8).toString(16);
|
||||||
if (h == "177245385090") {
|
if (h == '177245385090') {
|
||||||
var finalCRC = bits(32)|0;
|
var finalCRC = bits(32) | 0;
|
||||||
if (finalCRC !== streamCRC) messageArg.Error("Error in bzip2: crc32 do not match");
|
if (finalCRC !== streamCRC)
|
||||||
|
messageArg.Error('Error in bzip2: crc32 do not match');
|
||||||
// align stream to byte
|
// align stream to byte
|
||||||
bits(null);
|
bits(null);
|
||||||
return null; // reset streamCRC for next call
|
return null; // reset streamCRC for next call
|
||||||
}
|
}
|
||||||
if (h != "314159265359") messageArg.Error("eek not valid bzip data");
|
if (h != '314159265359') messageArg.Error('eek not valid bzip data');
|
||||||
var crcblock = bits(32)|0; // CRC code
|
var crcblock = bits(32) | 0; // CRC code
|
||||||
if (bits(1)) messageArg.Error("unsupported obsolete version");
|
if (bits(1)) messageArg.Error('unsupported obsolete version');
|
||||||
var origPtr = bits(24);
|
var origPtr = bits(24);
|
||||||
if (origPtr > bufsize) messageArg.Error("Initial position larger than buffer size");
|
if (origPtr > bufsize)
|
||||||
|
messageArg.Error('Initial position larger than buffer size');
|
||||||
var t = bits(16);
|
var t = bits(16);
|
||||||
var symTotal = 0;
|
var symTotal = 0;
|
||||||
for (i = 0; i < 16; i++) {
|
for (i = 0; i < 16; i++) {
|
||||||
if (t & (1 << (15 - i))) {
|
if (t & (1 << (15 - i))) {
|
||||||
var k = bits(16);
|
var k = bits(16);
|
||||||
for(j = 0; j < 16; j++) {
|
for (j = 0; j < 16; j++) {
|
||||||
if (k & (1 << (15 - j))) {
|
if (k & (1 << (15 - j))) {
|
||||||
this.symToByte[symTotal++] = (16 * i) + j;
|
this.symToByte[symTotal++] = 16 * i + j;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var groupCount = bits(3);
|
var groupCount = bits(3);
|
||||||
if (groupCount < 2 || groupCount > 6) messageArg.Error("another error");
|
if (groupCount < 2 || groupCount > 6) messageArg.Error('another error');
|
||||||
var nSelectors = bits(15);
|
var nSelectors = bits(15);
|
||||||
if (nSelectors == 0) messageArg.Error("meh");
|
if (nSelectors == 0) messageArg.Error('meh');
|
||||||
for(var i = 0; i < groupCount; i++) this.mtfSymbol[i] = i;
|
for (var i = 0; i < groupCount; i++) this.mtfSymbol[i] = i;
|
||||||
|
|
||||||
for(var i = 0; i < nSelectors; i++) {
|
for (var i = 0; i < nSelectors; i++) {
|
||||||
for(var j = 0; bits(1); j++) if (j >= groupCount) messageArg.Error("whoops another error");
|
for (var j = 0; bits(1); j++)
|
||||||
var uc = this.mtfSymbol[j];
|
if (j >= groupCount) messageArg.Error('whoops another error');
|
||||||
for(var k: any = j-1; k>=0; k--) {
|
var uc = this.mtfSymbol[j];
|
||||||
this.mtfSymbol[k+1] = this.mtfSymbol[k];
|
for (var k: any = j - 1; k >= 0; k--) {
|
||||||
}
|
this.mtfSymbol[k + 1] = this.mtfSymbol[k];
|
||||||
this.mtfSymbol[0] = uc;
|
}
|
||||||
this.selectors[i] = uc;
|
this.mtfSymbol[0] = uc;
|
||||||
|
this.selectors[i] = uc;
|
||||||
}
|
}
|
||||||
|
|
||||||
var symCount = symTotal + 2;
|
var symCount = symTotal + 2;
|
||||||
var groups = [];
|
var groups = [];
|
||||||
var length = new Uint8Array(MAX_SYMBOLS),
|
var length = new Uint8Array(MAX_SYMBOLS),
|
||||||
temp = new Uint16Array(MAX_HUFCODE_BITS+1);
|
temp = new Uint16Array(MAX_HUFCODE_BITS + 1);
|
||||||
|
|
||||||
var hufGroup;
|
var hufGroup;
|
||||||
|
|
||||||
for(var j = 0; j < groupCount; j++) {
|
for (var j = 0; j < groupCount; j++) {
|
||||||
t = bits(5); //lengths
|
t = bits(5); //lengths
|
||||||
for(var i = 0; i < symCount; i++) {
|
for (var i = 0; i < symCount; i++) {
|
||||||
while(true){
|
while (true) {
|
||||||
if (t < 1 || t > MAX_HUFCODE_BITS) messageArg.Error("I gave up a while ago on writing error messages");
|
if (t < 1 || t > MAX_HUFCODE_BITS)
|
||||||
if (!bits(1)) break;
|
messageArg.Error('I gave up a while ago on writing error messages');
|
||||||
if (!bits(1)) t++;
|
if (!bits(1)) break;
|
||||||
else t--;
|
if (!bits(1)) t++;
|
||||||
}
|
else t--;
|
||||||
length[i] = t;
|
|
||||||
}
|
}
|
||||||
var minLen, maxLen;
|
length[i] = t;
|
||||||
minLen = maxLen = length[0];
|
}
|
||||||
for(var i = 1; i < symCount; i++) {
|
var minLen, maxLen;
|
||||||
if (length[i] > maxLen) maxLen = length[i];
|
minLen = maxLen = length[0];
|
||||||
else if (length[i] < minLen) minLen = length[i];
|
for (var i = 1; i < symCount; i++) {
|
||||||
}
|
if (length[i] > maxLen) maxLen = length[i];
|
||||||
hufGroup = groups[j] = {};
|
else if (length[i] < minLen) minLen = length[i];
|
||||||
hufGroup.permute = new Int32Array(MAX_SYMBOLS);
|
}
|
||||||
hufGroup.limit = new Int32Array(MAX_HUFCODE_BITS + 1);
|
hufGroup = groups[j] = {};
|
||||||
hufGroup.base = new Int32Array(MAX_HUFCODE_BITS + 1);
|
hufGroup.permute = new Int32Array(MAX_SYMBOLS);
|
||||||
|
hufGroup.limit = new Int32Array(MAX_HUFCODE_BITS + 1);
|
||||||
hufGroup.minLen = minLen;
|
hufGroup.base = new Int32Array(MAX_HUFCODE_BITS + 1);
|
||||||
hufGroup.maxLen = maxLen;
|
|
||||||
var base = hufGroup.base;
|
hufGroup.minLen = minLen;
|
||||||
var limit = hufGroup.limit;
|
hufGroup.maxLen = maxLen;
|
||||||
var pp = 0;
|
var base = hufGroup.base;
|
||||||
for(var i: number = minLen; i <= maxLen; i++)
|
var limit = hufGroup.limit;
|
||||||
for(var t: any = 0; t < symCount; t++)
|
var pp = 0;
|
||||||
if (length[t] == i) hufGroup.permute[pp++] = t;
|
for (var i: number = minLen; i <= maxLen; i++)
|
||||||
for(i = minLen; i <= maxLen; i++) temp[i] = limit[i] = 0;
|
for (var t: any = 0; t < symCount; t++)
|
||||||
for(i = 0; i < symCount; i++) temp[length[i]]++;
|
if (length[t] == i) hufGroup.permute[pp++] = t;
|
||||||
pp = t = 0;
|
for (i = minLen; i <= maxLen; i++) temp[i] = limit[i] = 0;
|
||||||
for(i = minLen; i < maxLen; i++) {
|
for (i = 0; i < symCount; i++) temp[length[i]]++;
|
||||||
pp += temp[i];
|
pp = t = 0;
|
||||||
limit[i] = pp - 1;
|
for (i = minLen; i < maxLen; i++) {
|
||||||
pp <<= 1;
|
pp += temp[i];
|
||||||
base[i+1] = pp - (t += temp[i]);
|
limit[i] = pp - 1;
|
||||||
}
|
pp <<= 1;
|
||||||
limit[maxLen] = pp + temp[maxLen] - 1;
|
base[i + 1] = pp - (t += temp[i]);
|
||||||
base[minLen] = 0;
|
}
|
||||||
|
limit[maxLen] = pp + temp[maxLen] - 1;
|
||||||
|
base[minLen] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(var i = 0; i < 256; i++) {
|
for (var i = 0; i < 256; i++) {
|
||||||
this.mtfSymbol[i] = i;
|
this.mtfSymbol[i] = i;
|
||||||
this.byteCount[i] = 0;
|
this.byteCount[i] = 0;
|
||||||
}
|
}
|
||||||
var runPos, count, symCount: number, selector;
|
var runPos, count, symCount: number, selector;
|
||||||
runPos = count = symCount = selector = 0;
|
runPos = count = symCount = selector = 0;
|
||||||
while(true) {
|
while (true) {
|
||||||
if (!(symCount--)) {
|
if (!symCount--) {
|
||||||
symCount = GROUP_SIZE - 1;
|
symCount = GROUP_SIZE - 1;
|
||||||
if (selector >= nSelectors) messageArg.Error("meow i'm a kitty, that's an error");
|
if (selector >= nSelectors)
|
||||||
hufGroup = groups[this.selectors[selector++]];
|
messageArg.Error("meow i'm a kitty, that's an error");
|
||||||
base = hufGroup.base;
|
hufGroup = groups[this.selectors[selector++]];
|
||||||
limit = hufGroup.limit;
|
base = hufGroup.base;
|
||||||
|
limit = hufGroup.limit;
|
||||||
|
}
|
||||||
|
i = hufGroup.minLen;
|
||||||
|
j = bits(i);
|
||||||
|
while (true) {
|
||||||
|
if (i > hufGroup.maxLen) messageArg.Error("rawr i'm a dinosaur");
|
||||||
|
if (j <= limit[i]) break;
|
||||||
|
i++;
|
||||||
|
j = (j << 1) | bits(1);
|
||||||
|
}
|
||||||
|
j -= base[i];
|
||||||
|
if (j < 0 || j >= MAX_SYMBOLS) messageArg.Error("moo i'm a cow");
|
||||||
|
var nextSym = hufGroup.permute[j];
|
||||||
|
if (nextSym == SYMBOL_RUNA || nextSym == SYMBOL_RUNB) {
|
||||||
|
if (!runPos) {
|
||||||
|
runPos = 1;
|
||||||
|
t = 0;
|
||||||
}
|
}
|
||||||
i = hufGroup.minLen;
|
if (nextSym == SYMBOL_RUNA) t += runPos;
|
||||||
j = bits(i);
|
else t += 2 * runPos;
|
||||||
while(true) {
|
runPos <<= 1;
|
||||||
if (i > hufGroup.maxLen) messageArg.Error("rawr i'm a dinosaur");
|
continue;
|
||||||
if (j <= limit[i]) break;
|
}
|
||||||
i++;
|
if (runPos) {
|
||||||
j = (j << 1) | bits(1);
|
runPos = 0;
|
||||||
}
|
if (count + t > bufsize) messageArg.Error('Boom.');
|
||||||
j -= base[i];
|
uc = this.symToByte[this.mtfSymbol[0]];
|
||||||
if (j < 0 || j >= MAX_SYMBOLS) messageArg.Error("moo i'm a cow");
|
this.byteCount[uc] += t;
|
||||||
var nextSym = hufGroup.permute[j];
|
while (t--) buf[count++] = uc;
|
||||||
if (nextSym == SYMBOL_RUNA || nextSym == SYMBOL_RUNB) {
|
}
|
||||||
if (!runPos){
|
if (nextSym > symTotal) break;
|
||||||
runPos = 1;
|
if (count >= bufsize)
|
||||||
t = 0;
|
messageArg.Error("I can't think of anything. Error");
|
||||||
}
|
i = nextSym - 1;
|
||||||
if (nextSym == SYMBOL_RUNA) t += runPos;
|
uc = this.mtfSymbol[i];
|
||||||
else t += 2 * runPos;
|
for (var k: any = i - 1; k >= 0; k--) {
|
||||||
runPos <<= 1;
|
this.mtfSymbol[k + 1] = this.mtfSymbol[k];
|
||||||
continue;
|
}
|
||||||
}
|
this.mtfSymbol[0] = uc;
|
||||||
if (runPos) {
|
uc = this.symToByte[uc];
|
||||||
runPos = 0;
|
this.byteCount[uc]++;
|
||||||
if (count + t > bufsize) messageArg.Error("Boom.");
|
buf[count++] = uc;
|
||||||
uc = this.symToByte[this.mtfSymbol[0]];
|
|
||||||
this.byteCount[uc] += t;
|
|
||||||
while(t--) buf[count++] = uc;
|
|
||||||
}
|
|
||||||
if (nextSym > symTotal) break;
|
|
||||||
if (count >= bufsize) messageArg.Error("I can't think of anything. Error");
|
|
||||||
i = nextSym - 1;
|
|
||||||
uc = this.mtfSymbol[i];
|
|
||||||
for(var k: any = i-1; k>=0; k--) {
|
|
||||||
this.mtfSymbol[k+1] = this.mtfSymbol[k];
|
|
||||||
}
|
|
||||||
this.mtfSymbol[0] = uc
|
|
||||||
uc = this.symToByte[uc];
|
|
||||||
this.byteCount[uc]++;
|
|
||||||
buf[count++] = uc;
|
|
||||||
}
|
}
|
||||||
if (origPtr < 0 || origPtr >= count) messageArg.Error("I'm a monkey and I'm throwing something at someone, namely you");
|
if (origPtr < 0 || origPtr >= count)
|
||||||
|
messageArg.Error(
|
||||||
|
"I'm a monkey and I'm throwing something at someone, namely you",
|
||||||
|
);
|
||||||
var j = 0;
|
var j = 0;
|
||||||
for(var i = 0; i < 256; i++) {
|
for (var i = 0; i < 256; i++) {
|
||||||
k = j + this.byteCount[i];
|
k = j + this.byteCount[i];
|
||||||
this.byteCount[i] = j;
|
this.byteCount[i] = j;
|
||||||
j = k;
|
j = k;
|
||||||
}
|
}
|
||||||
for(var i = 0; i < count; i++) {
|
for (var i = 0; i < count; i++) {
|
||||||
uc = buf[i] & 0xff;
|
uc = buf[i] & 0xff;
|
||||||
buf[this.byteCount[uc]] |= (i << 8);
|
buf[this.byteCount[uc]] |= i << 8;
|
||||||
this.byteCount[uc]++;
|
this.byteCount[uc]++;
|
||||||
}
|
}
|
||||||
var pos = 0, current = 0, run = 0;
|
var pos = 0,
|
||||||
|
current = 0,
|
||||||
|
run = 0;
|
||||||
if (count) {
|
if (count) {
|
||||||
pos = buf[origPtr];
|
pos = buf[origPtr];
|
||||||
current = (pos & 0xff);
|
current = pos & 0xff;
|
||||||
pos >>= 8;
|
pos >>= 8;
|
||||||
run = -1;
|
run = -1;
|
||||||
}
|
}
|
||||||
count = count;
|
count = count;
|
||||||
var copies, previous, outbyte;
|
var copies, previous, outbyte;
|
||||||
while(count) {
|
while (count) {
|
||||||
count--;
|
count--;
|
||||||
previous = current;
|
previous = current;
|
||||||
pos = buf[pos];
|
pos = buf[pos];
|
||||||
current = pos & 0xff;
|
current = pos & 0xff;
|
||||||
pos >>= 8;
|
pos >>= 8;
|
||||||
if (run++ == 3) {
|
if (run++ == 3) {
|
||||||
copies = current;
|
copies = current;
|
||||||
outbyte = previous;
|
outbyte = previous;
|
||||||
current = -1;
|
current = -1;
|
||||||
} else {
|
} else {
|
||||||
copies = 1;
|
copies = 1;
|
||||||
outbyte = current;
|
outbyte = current;
|
||||||
}
|
}
|
||||||
while(copies--) {
|
while (copies--) {
|
||||||
crc = ((crc << 8) ^ this.crcTable[((crc>>24) ^ outbyte) & 0xFF])&0xFFFFFFFF; // crc32
|
crc =
|
||||||
stream(outbyte);
|
((crc << 8) ^ this.crcTable[((crc >> 24) ^ outbyte) & 0xff]) &
|
||||||
}
|
0xffffffff; // crc32
|
||||||
if (current != previous) run = 0;
|
stream(outbyte);
|
||||||
|
}
|
||||||
|
if (current != previous) run = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
crc = (crc ^ (-1)) >>> 0;
|
crc = (crc ^ -1) >>> 0;
|
||||||
if ((crc|0) != (crcblock|0)) messageArg.Error("Error in bzip2: crc32 do not match");
|
if ((crc | 0) != (crcblock | 0))
|
||||||
streamCRC = (crc ^ ((streamCRC << 1) | (streamCRC >>> 31))) & 0xFFFFFFFF;
|
messageArg.Error('Error in bzip2: crc32 do not match');
|
||||||
|
streamCRC = (crc ^ ((streamCRC << 1) | (streamCRC >>> 31))) & 0xffffffff;
|
||||||
return streamCRC;
|
return streamCRC;
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -26,7 +26,13 @@ export function unbzip2Stream() {
|
|||||||
chunk.push(b);
|
chunk.push(b);
|
||||||
};
|
};
|
||||||
|
|
||||||
streamCRC = bzip2Instance.decompress(bitReader, f, buf, bufsize, streamCRC);
|
streamCRC = bzip2Instance.decompress(
|
||||||
|
bitReader,
|
||||||
|
f,
|
||||||
|
buf,
|
||||||
|
bufsize,
|
||||||
|
streamCRC,
|
||||||
|
);
|
||||||
if (streamCRC === null) {
|
if (streamCRC === null) {
|
||||||
// reset for next bzip2 header
|
// reset for next bzip2 header
|
||||||
blockSize = 0;
|
blockSize = 0;
|
||||||
@@ -38,26 +44,27 @@ export function unbzip2Stream() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var outlength = 0;
|
var outlength = 0;
|
||||||
const decompressAndPush = async (smartDuplexStream: plugins.smartstream.SmartDuplex) => {
|
const decompressAndPush = async () => {
|
||||||
if (broken) return;
|
if (broken) return;
|
||||||
try {
|
try {
|
||||||
const resultChunk = decompressBlock();
|
const resultChunk = decompressBlock();
|
||||||
smartDuplexStream.push(resultChunk);
|
|
||||||
if (resultChunk) {
|
if (resultChunk) {
|
||||||
//console.error('write at', outlength.toString(16));
|
|
||||||
outlength += resultChunk.length;
|
outlength += resultChunk.length;
|
||||||
}
|
}
|
||||||
|
return resultChunk;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
smartDuplexStream.emit('error', e);
|
|
||||||
broken = true;
|
broken = true;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let counter = 0;
|
||||||
return new plugins.smartstream.SmartDuplex({
|
return new plugins.smartstream.SmartDuplex({
|
||||||
|
objectMode: true,
|
||||||
|
name: 'bzip2',
|
||||||
|
debug: false,
|
||||||
|
highWaterMark: 1,
|
||||||
writeFunction: async function (data, streamTools) {
|
writeFunction: async function (data, streamTools) {
|
||||||
//console.error('received', data.length,'bytes in', typeof data);
|
// console.log(`got chunk ${counter++}`)
|
||||||
bufferQueue.push(data);
|
bufferQueue.push(data);
|
||||||
hasBytes += data.length;
|
hasBytes += data.length;
|
||||||
if (bitReader === null) {
|
if (bitReader === null) {
|
||||||
@@ -65,19 +72,31 @@ export function unbzip2Stream() {
|
|||||||
return bufferQueue.shift();
|
return bufferQueue.shift();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
while (!broken && hasBytes - bitReader.bytesRead + 1 >= (25000 + 100000 * blockSize || 4)) {
|
while (
|
||||||
|
!broken &&
|
||||||
|
hasBytes - bitReader.bytesRead + 1 >= (25000 + 100000 * blockSize || 4)
|
||||||
|
) {
|
||||||
//console.error('decompressing with', hasBytes - bitReader.bytesRead + 1, 'bytes in buffer');
|
//console.error('decompressing with', hasBytes - bitReader.bytesRead + 1, 'bytes in buffer');
|
||||||
await decompressAndPush(this);
|
const result = await decompressAndPush();
|
||||||
|
if (!result) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// console.log(result.toString());
|
||||||
|
await streamTools.push(result);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
finalFunction: async function (streamTools) {
|
finalFunction: async function (streamTools) {
|
||||||
//console.error(x,'last compressing with', hasBytes, 'bytes in buffer');
|
//console.error(x,'last compressing with', hasBytes, 'bytes in buffer');
|
||||||
while (!broken && bitReader && hasBytes > bitReader.bytesRead) {
|
while (!broken && bitReader && hasBytes > bitReader.bytesRead) {
|
||||||
await decompressAndPush(this);
|
const result = await decompressAndPush();
|
||||||
|
if (!result) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await streamTools.push(result);
|
||||||
}
|
}
|
||||||
if (!broken) {
|
if (!broken) {
|
||||||
if (streamCRC !== null) this.emit('error', new Error('input stream ended prematurely'));
|
if (streamCRC !== null)
|
||||||
this.queue(null);
|
this.emit('error', new Error('input stream ended prematurely'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ import * as plugins from './plugins.js';
|
|||||||
export interface IAnalyzedResult {
|
export interface IAnalyzedResult {
|
||||||
fileType: plugins.fileType.FileTypeResult;
|
fileType: plugins.fileType.FileTypeResult;
|
||||||
isArchive: boolean;
|
isArchive: boolean;
|
||||||
resultStream: plugins.smartstream.PassThrough;
|
resultStream: plugins.smartstream.SmartDuplex;
|
||||||
decompressionStream: plugins.stream.Transform | plugins.stream.Duplex | plugins.tarStream.Extract;
|
decompressionStream:
|
||||||
|
| plugins.stream.Transform
|
||||||
|
| plugins.stream.Duplex
|
||||||
|
| plugins.tarStream.Extract;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ArchiveAnalyzer {
|
export class ArchiveAnalyzer {
|
||||||
@@ -25,53 +28,63 @@ export class ArchiveAnalyzer {
|
|||||||
'application/x-bzip2',
|
'application/x-bzip2',
|
||||||
// Add other archive mime types here
|
// Add other archive mime types here
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return archiveMimeTypes.has(mimeType);
|
return archiveMimeTypes.has(mimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async getDecompressionStream(
|
private async getDecompressionStream(
|
||||||
mimeTypeArg: plugins.fileType.FileTypeResult['mime']
|
mimeTypeArg: plugins.fileType.FileTypeResult['mime'],
|
||||||
): Promise<plugins.stream.Transform | plugins.stream.Duplex | plugins.tarStream.Extract> {
|
): Promise<
|
||||||
|
plugins.stream.Transform | plugins.stream.Duplex | plugins.tarStream.Extract
|
||||||
|
> {
|
||||||
switch (mimeTypeArg) {
|
switch (mimeTypeArg) {
|
||||||
case 'application/gzip':
|
case 'application/gzip':
|
||||||
return this.smartArchiveRef.gzipTools.getDecompressionStream();
|
return this.smartArchiveRef.gzipTools.getDecompressionStream();
|
||||||
|
case 'application/zip':
|
||||||
|
return this.smartArchiveRef.zipTools.getDecompressionStream();
|
||||||
case 'application/x-bzip2':
|
case 'application/x-bzip2':
|
||||||
return await this.smartArchiveRef.bzip2Tools.getDecompressionStream(); // replace with your own bzip2 decompression stream
|
return await this.smartArchiveRef.bzip2Tools.getDecompressionStream(); // replace with your own bzip2 decompression stream
|
||||||
case 'application/x-tar':
|
case 'application/x-tar':
|
||||||
return this.smartArchiveRef.tarTools.getDecompressionStream(); // replace with your own tar decompression stream
|
return this.smartArchiveRef.tarTools.getDecompressionStream(); // replace with your own tar decompression stream
|
||||||
default:
|
default:
|
||||||
// Handle unsupported formats or no decompression needed
|
// Handle unsupported formats or no decompression needed
|
||||||
return new plugins.smartstream.PassThrough();
|
return plugins.smartstream.createPassThrough();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAnalyzedStream() {
|
public getAnalyzedStream() {
|
||||||
let firstRun = true;
|
let firstRun = true;
|
||||||
const resultStream = new plugins.smartstream.PassThrough();
|
const resultStream = plugins.smartstream.createPassThrough();
|
||||||
const analyzerstream = new plugins.smartstream.SmartDuplex<Buffer, IAnalyzedResult>({
|
const analyzerstream = new plugins.smartstream.SmartDuplex<
|
||||||
|
Buffer,
|
||||||
|
IAnalyzedResult
|
||||||
|
>({
|
||||||
readableObjectMode: true,
|
readableObjectMode: true,
|
||||||
writeFunction: async (chunkArg: Buffer, streamtools) => {
|
writeFunction: async (chunkArg: Buffer, streamtools) => {
|
||||||
const fileType = await plugins.fileType.fileTypeFromBuffer(chunkArg);
|
|
||||||
const decompressionStream = await this.getDecompressionStream(fileType?.mime as any);
|
|
||||||
resultStream.push(chunkArg);
|
|
||||||
if (firstRun) {
|
if (firstRun) {
|
||||||
firstRun = false;
|
firstRun = false;
|
||||||
|
const fileType = await plugins.fileType.fileTypeFromBuffer(chunkArg);
|
||||||
|
const decompressionStream = await this.getDecompressionStream(
|
||||||
|
fileType?.mime as any,
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* analyzed stream emits once with this object
|
||||||
|
*/
|
||||||
const result: IAnalyzedResult = {
|
const result: IAnalyzedResult = {
|
||||||
fileType,
|
fileType,
|
||||||
isArchive: await this.mimeTypeIsArchive(fileType?.mime),
|
isArchive: await this.mimeTypeIsArchive(fileType?.mime),
|
||||||
resultStream,
|
resultStream,
|
||||||
decompressionStream,
|
decompressionStream,
|
||||||
};
|
};
|
||||||
streamtools.push(result);
|
await streamtools.push(result);
|
||||||
streamtools.push(null);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
await resultStream.backpressuredPush(chunkArg);
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
finalFunction: async (tools) => {
|
finalFunction: async (tools) => {
|
||||||
resultStream.push(null);
|
resultStream.push(null);
|
||||||
return null;
|
return null;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
return analyzerstream;
|
return analyzerstream;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,4 +13,4 @@ export class Bzip2Tools {
|
|||||||
getDecompressionStream() {
|
getDecompressionStream() {
|
||||||
return unbzip2Stream();
|
return unbzip2Stream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { SmartArchive } from './classes.smartarchive.js';
|
import type { SmartArchive } from './classes.smartarchive.js';
|
||||||
import * as plugins from './plugins.js'
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
// This class wraps fflate's gunzip in a Node.js Transform stream
|
// This class wraps fflate's gunzip in a Node.js Transform stream
|
||||||
export class CompressGunzipTransform extends plugins.stream.Transform {
|
export class CompressGunzipTransform extends plugins.stream.Transform {
|
||||||
@@ -7,7 +7,11 @@ export class CompressGunzipTransform extends plugins.stream.Transform {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
_transform(chunk: Buffer, encoding: BufferEncoding, callback: plugins.stream.TransformCallback) {
|
_transform(
|
||||||
|
chunk: Buffer,
|
||||||
|
encoding: BufferEncoding,
|
||||||
|
callback: plugins.stream.TransformCallback,
|
||||||
|
) {
|
||||||
plugins.fflate.gunzip(chunk, (err, decompressed) => {
|
plugins.fflate.gunzip(chunk, (err, decompressed) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
@@ -22,32 +26,49 @@ export class CompressGunzipTransform extends plugins.stream.Transform {
|
|||||||
// DecompressGunzipTransform class that extends the Node.js Transform stream to
|
// DecompressGunzipTransform class that extends the Node.js Transform stream to
|
||||||
// create a stream that decompresses GZip-compressed data using fflate's gunzip function
|
// create a stream that decompresses GZip-compressed data using fflate's gunzip function
|
||||||
export class DecompressGunzipTransform extends plugins.stream.Transform {
|
export class DecompressGunzipTransform extends plugins.stream.Transform {
|
||||||
|
private gunzip: any; // fflate.Gunzip instance
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
}
|
|
||||||
|
// Create a streaming Gunzip decompressor
|
||||||
_transform(chunk: Buffer, encoding: BufferEncoding, callback: plugins.stream.TransformCallback) {
|
this.gunzip = new plugins.fflate.Gunzip((chunk, final) => {
|
||||||
// Use fflate's gunzip function to decompress the chunk
|
// Push decompressed chunks to the output stream
|
||||||
plugins.fflate.gunzip(chunk, (err, decompressed) => {
|
this.push(Buffer.from(chunk));
|
||||||
if (err) {
|
if (final) {
|
||||||
// If an error occurs during decompression, pass the error to the callback
|
// Signal end of stream when decompression is complete
|
||||||
callback(err);
|
this.push(null);
|
||||||
} else {
|
|
||||||
// If decompression is successful, push the decompressed data into the stream
|
|
||||||
this.push(decompressed);
|
|
||||||
callback();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_transform(
|
||||||
|
chunk: Buffer,
|
||||||
|
encoding: BufferEncoding,
|
||||||
|
callback: plugins.stream.TransformCallback,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// Feed chunks to the gunzip stream
|
||||||
|
this.gunzip.push(chunk, false);
|
||||||
|
callback();
|
||||||
|
} catch (err) {
|
||||||
|
callback(err as Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_flush(callback: plugins.stream.TransformCallback) {
|
||||||
|
try {
|
||||||
|
// Signal end of input to gunzip
|
||||||
|
this.gunzip.push(new Uint8Array(0), true);
|
||||||
|
callback();
|
||||||
|
} catch (err) {
|
||||||
|
callback(err as Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class GzipTools {
|
export class GzipTools {
|
||||||
smartArchiveRef: SmartArchive;
|
constructor() {}
|
||||||
|
|
||||||
constructor(smartArchiveRefArg: SmartArchive) {
|
|
||||||
this.smartArchiveRef = smartArchiveRefArg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCompressionStream() {
|
public getCompressionStream() {
|
||||||
return new CompressGunzipTransform();
|
return new CompressGunzipTransform();
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import * as paths from './paths.js';
|
import * as paths from './paths.js';
|
||||||
|
|
||||||
|
import { Bzip2Tools } from './classes.bzip2tools.js';
|
||||||
import { GzipTools } from './classes.gziptools.js';
|
import { GzipTools } from './classes.gziptools.js';
|
||||||
import { TarTools } from './classes.tartools.js';
|
import { TarTools } from './classes.tartools.js';
|
||||||
import { Bzip2Tools } from './classes.bzip2tools.js';
|
import { ZipTools } from './classes.ziptools.js';
|
||||||
|
|
||||||
import { ArchiveAnalyzer, type IAnalyzedResult } from './classes.archiveanalyzer.js';
|
import {
|
||||||
|
ArchiveAnalyzer,
|
||||||
|
type IAnalyzedResult,
|
||||||
|
} from './classes.archiveanalyzer.js';
|
||||||
|
|
||||||
import type { from } from '@push.rocks/smartrx/dist_ts/smartrx.plugins.rxjs.js';
|
import type { from } from '@push.rocks/smartrx/dist_ts/smartrx.plugins.rxjs.js';
|
||||||
|
|
||||||
@@ -17,14 +21,19 @@ export class SmartArchive {
|
|||||||
return smartArchiveInstance;
|
return smartArchiveInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async fromArchiveFile(filePathArg: string): Promise<SmartArchive> {
|
public static async fromArchiveFile(
|
||||||
|
filePathArg: string,
|
||||||
|
): Promise<SmartArchive> {
|
||||||
const smartArchiveInstance = new SmartArchive();
|
const smartArchiveInstance = new SmartArchive();
|
||||||
smartArchiveInstance.sourceFilePath = filePathArg;
|
smartArchiveInstance.sourceFilePath = filePathArg;
|
||||||
return smartArchiveInstance;
|
return smartArchiveInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async fromArchiveStream(
|
public static async fromArchiveStream(
|
||||||
streamArg: plugins.stream.Readable | plugins.stream.Duplex | plugins.stream.Transform
|
streamArg:
|
||||||
|
| plugins.stream.Readable
|
||||||
|
| plugins.stream.Duplex
|
||||||
|
| plugins.stream.Transform,
|
||||||
): Promise<SmartArchive> {
|
): Promise<SmartArchive> {
|
||||||
const smartArchiveInstance = new SmartArchive();
|
const smartArchiveInstance = new SmartArchive();
|
||||||
smartArchiveInstance.sourceStream = streamArg;
|
smartArchiveInstance.sourceStream = streamArg;
|
||||||
@@ -32,20 +41,27 @@ export class SmartArchive {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// INSTANCE
|
// INSTANCE
|
||||||
public tarTools = new TarTools(this);
|
public tarTools = new TarTools();
|
||||||
public gzipTools = new GzipTools(this);
|
public zipTools = new ZipTools();
|
||||||
|
public gzipTools = new GzipTools();
|
||||||
public bzip2Tools = new Bzip2Tools(this);
|
public bzip2Tools = new Bzip2Tools(this);
|
||||||
public archiveAnalyzer = new ArchiveAnalyzer(this);
|
public archiveAnalyzer = new ArchiveAnalyzer(this);
|
||||||
|
|
||||||
public sourceUrl: string;
|
public sourceUrl: string;
|
||||||
public sourceFilePath: string;
|
public sourceFilePath: string;
|
||||||
public sourceStream: plugins.stream.Readable | plugins.stream.Duplex | plugins.stream.Transform;
|
public sourceStream:
|
||||||
|
| plugins.stream.Readable
|
||||||
|
| plugins.stream.Duplex
|
||||||
|
| plugins.stream.Transform;
|
||||||
|
|
||||||
public archiveName: string;
|
public archiveName: string;
|
||||||
public singleFileMode: boolean = false;
|
public singleFileMode: boolean = false;
|
||||||
|
|
||||||
public addedDirectories: string[] = [];
|
public addedDirectories: string[] = [];
|
||||||
public addedFiles: (plugins.smartfile.SmartFile | plugins.smartfile.StreamFile)[] = [];
|
public addedFiles: (
|
||||||
|
| plugins.smartfile.SmartFile
|
||||||
|
| plugins.smartfile.StreamFile
|
||||||
|
)[] = [];
|
||||||
public addedUrls: string[] = [];
|
public addedUrls: string[] = [];
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
@@ -58,11 +74,16 @@ export class SmartArchive {
|
|||||||
return this.sourceStream;
|
return this.sourceStream;
|
||||||
}
|
}
|
||||||
if (this.sourceUrl) {
|
if (this.sourceUrl) {
|
||||||
const urlStream = await plugins.smartrequest.getStream(this.sourceUrl);
|
const response = await plugins.smartrequest.SmartRequest.create()
|
||||||
|
.url(this.sourceUrl)
|
||||||
|
.get();
|
||||||
|
const webStream = response.stream();
|
||||||
|
// @ts-ignore - Web stream to Node.js stream conversion
|
||||||
|
const urlStream = plugins.stream.Readable.fromWeb(webStream);
|
||||||
return urlStream;
|
return urlStream;
|
||||||
}
|
}
|
||||||
if (this.sourceFilePath) {
|
if (this.sourceFilePath) {
|
||||||
const fileStream = plugins.smartfile.fs.toReadStream(this.sourceFilePath);
|
const fileStream = plugins.fs.createReadStream(this.sourceFilePath);
|
||||||
return fileStream;
|
return fileStream;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,37 +95,53 @@ export class SmartArchive {
|
|||||||
// return archiveStream;
|
// return archiveStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async exportToFs(targetDir: string, fileNameArg?: string): Promise<void> {
|
public async exportToFs(
|
||||||
|
targetDir: string,
|
||||||
|
fileNameArg?: string,
|
||||||
|
): Promise<void> {
|
||||||
const done = plugins.smartpromise.defer<void>();
|
const done = plugins.smartpromise.defer<void>();
|
||||||
const streamFileStream = await this.exportToStreamOfStreamFiles();
|
const streamFileStream = await this.exportToStreamOfStreamFiles();
|
||||||
streamFileStream.pipe(new plugins.smartstream.SmartDuplex({
|
streamFileStream.pipe(
|
||||||
objectMode: true,
|
new plugins.smartstream.SmartDuplex({
|
||||||
writeFunction: async (chunkArg: plugins.smartfile.StreamFile, streamtools) => {
|
objectMode: true,
|
||||||
const done = plugins.smartpromise.defer<void>();
|
writeFunction: async (
|
||||||
console.log(chunkArg.relativeFilePath);
|
streamFileArg: plugins.smartfile.StreamFile,
|
||||||
const streamFile = chunkArg;
|
streamtools,
|
||||||
const readStream = await streamFile.createReadStream();
|
) => {
|
||||||
await plugins.smartfile.fs.ensureDir(targetDir);
|
const done = plugins.smartpromise.defer<void>();
|
||||||
const writePath = plugins.path.join(targetDir, (streamFile.relativeFilePath || fileNameArg));
|
console.log(
|
||||||
await plugins.smartfile.fs.ensureDir(plugins.path.dirname(writePath));
|
streamFileArg.relativeFilePath
|
||||||
const writeStream = plugins.smartfile.fsStream.createWriteStream(writePath);
|
? streamFileArg.relativeFilePath
|
||||||
readStream.pipe(writeStream);
|
: 'no relative path',
|
||||||
writeStream.on('finish', () => {
|
);
|
||||||
|
const streamFile = streamFileArg;
|
||||||
|
const readStream = await streamFile.createReadStream();
|
||||||
|
await plugins.fsPromises.mkdir(targetDir, { recursive: true });
|
||||||
|
const writePath = plugins.path.join(
|
||||||
|
targetDir,
|
||||||
|
streamFile.relativeFilePath || fileNameArg,
|
||||||
|
);
|
||||||
|
await plugins.fsPromises.mkdir(plugins.path.dirname(writePath), { recursive: true });
|
||||||
|
const writeStream = plugins.fs.createWriteStream(writePath);
|
||||||
|
readStream.pipe(writeStream);
|
||||||
|
writeStream.on('finish', () => {
|
||||||
|
done.resolve();
|
||||||
|
});
|
||||||
|
await done.promise;
|
||||||
|
},
|
||||||
|
finalFunction: async () => {
|
||||||
done.resolve();
|
done.resolve();
|
||||||
})
|
},
|
||||||
await done.promise;
|
}),
|
||||||
},
|
);
|
||||||
finalFunction: async () => {
|
|
||||||
done.resolve();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
return done.promise;
|
return done.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async exportToStreamOfStreamFiles() {
|
public async exportToStreamOfStreamFiles() {
|
||||||
const streamFileIntake = new plugins.smartstream.StreamIntake<plugins.smartfile.StreamFile>({
|
const streamFileIntake =
|
||||||
objectMode: true,
|
new plugins.smartstream.StreamIntake<plugins.smartfile.StreamFile>({
|
||||||
});
|
objectMode: true,
|
||||||
|
});
|
||||||
const archiveStream = await this.getArchiveStream();
|
const archiveStream = await this.getArchiveStream();
|
||||||
const createAnalyzedStream = () => this.archiveAnalyzer.getAnalyzedStream();
|
const createAnalyzedStream = () => this.archiveAnalyzer.getAnalyzedStream();
|
||||||
|
|
||||||
@@ -113,38 +150,86 @@ export class SmartArchive {
|
|||||||
plugins.smartstream.createTransformFunction<IAnalyzedResult, any>(
|
plugins.smartstream.createTransformFunction<IAnalyzedResult, any>(
|
||||||
async (analyzedResultChunk) => {
|
async (analyzedResultChunk) => {
|
||||||
if (analyzedResultChunk.fileType?.mime === 'application/x-tar') {
|
if (analyzedResultChunk.fileType?.mime === 'application/x-tar') {
|
||||||
const tarStream = analyzedResultChunk.decompressionStream as plugins.tarStream.Extract;
|
const tarStream =
|
||||||
tarStream.on(
|
analyzedResultChunk.decompressionStream as plugins.tarStream.Extract;
|
||||||
'entry',
|
tarStream.on('entry', async (header, stream, next) => {
|
||||||
async (header, stream, next) => {
|
if (header.type === 'directory') {
|
||||||
const streamfile = plugins.smartfile.StreamFile.fromStream(stream, header.name);
|
console.log(
|
||||||
streamFileIntake.push(streamfile);
|
`tar stream directory: ${header.name} ... skipping!`,
|
||||||
stream.on('end', function () {
|
);
|
||||||
next(); // ready for next entry
|
stream.resume(); // Consume directory stream
|
||||||
});
|
stream.on('end', () => next());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
);
|
console.log(`tar stream file: ${header.name}`);
|
||||||
|
|
||||||
|
// Create a PassThrough stream to buffer the data
|
||||||
|
const passThrough = new plugins.stream.PassThrough();
|
||||||
|
const streamfile = plugins.smartfile.StreamFile.fromStream(
|
||||||
|
passThrough,
|
||||||
|
header.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Push the streamfile immediately
|
||||||
|
streamFileIntake.push(streamfile);
|
||||||
|
|
||||||
|
// Pipe the tar entry stream to the passthrough
|
||||||
|
stream.pipe(passThrough);
|
||||||
|
|
||||||
|
// Move to next entry when this one ends
|
||||||
|
stream.on('end', () => {
|
||||||
|
passThrough.end();
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
tarStream.on('finish', function () {
|
tarStream.on('finish', function () {
|
||||||
console.log('finished');
|
console.log('tar extraction finished');
|
||||||
|
// Only signal end if this is the final stream
|
||||||
streamFileIntake.signalEnd();
|
streamFileIntake.signalEnd();
|
||||||
});
|
});
|
||||||
analyzedResultChunk.resultStream.pipe(analyzedResultChunk.decompressionStream);
|
analyzedResultChunk.resultStream.pipe(
|
||||||
} else if (analyzedResultChunk.isArchive && analyzedResultChunk.decompressionStream) {
|
analyzedResultChunk.decompressionStream,
|
||||||
|
);
|
||||||
|
} else if (analyzedResultChunk.fileType?.mime === 'application/zip') {
|
||||||
analyzedResultChunk.resultStream
|
analyzedResultChunk.resultStream
|
||||||
|
.pipe(analyzedResultChunk.decompressionStream)
|
||||||
|
.pipe(
|
||||||
|
new plugins.smartstream.SmartDuplex({
|
||||||
|
objectMode: true,
|
||||||
|
writeFunction: async (
|
||||||
|
streamFileArg: plugins.smartfile.StreamFile,
|
||||||
|
streamtools,
|
||||||
|
) => {
|
||||||
|
streamFileIntake.push(streamFileArg);
|
||||||
|
},
|
||||||
|
finalFunction: async () => {
|
||||||
|
streamFileIntake.signalEnd();
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
analyzedResultChunk.isArchive &&
|
||||||
|
analyzedResultChunk.decompressionStream
|
||||||
|
) {
|
||||||
|
// For nested archives (like gzip containing tar)
|
||||||
|
const nestedStream = analyzedResultChunk.resultStream
|
||||||
.pipe(analyzedResultChunk.decompressionStream)
|
.pipe(analyzedResultChunk.decompressionStream)
|
||||||
.pipe(createAnalyzedStream())
|
.pipe(createAnalyzedStream())
|
||||||
.pipe(createUnpackStream());
|
.pipe(createUnpackStream());
|
||||||
|
|
||||||
|
// Don't signal end here - let the nested unpacker handle it
|
||||||
} else {
|
} else {
|
||||||
const streamFile = plugins.smartfile.StreamFile.fromStream(
|
const streamFile = plugins.smartfile.StreamFile.fromStream(
|
||||||
analyzedResultChunk.resultStream,
|
analyzedResultChunk.resultStream,
|
||||||
analyzedResultChunk.fileType?.ext
|
analyzedResultChunk.fileType?.ext,
|
||||||
);
|
);
|
||||||
streamFileIntake.push(streamFile);
|
streamFileIntake.push(streamFile);
|
||||||
streamFileIntake.signalEnd();
|
streamFileIntake.signalEnd();
|
||||||
}
|
}
|
||||||
}, {
|
},
|
||||||
objectMode: true
|
{
|
||||||
}
|
objectMode: true,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
archiveStream.pipe(createAnalyzedStream()).pipe(createUnpackStream());
|
archiveStream.pipe(createAnalyzedStream()).pipe(createUnpackStream());
|
||||||
|
|||||||
@@ -2,28 +2,128 @@ import type { SmartArchive } from './classes.smartarchive.js';
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
export class TarTools {
|
export class TarTools {
|
||||||
smartArchiveRef: SmartArchive;
|
// INSTANCE
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
constructor(smartArchiveRefArg: SmartArchive) {
|
|
||||||
this.smartArchiveRef = smartArchiveRefArg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// packing
|
// packing
|
||||||
public addFileToPack(pack: plugins.tarStream.Pack, fileName: string, content: string | Buffer) {
|
public async addFileToPack(
|
||||||
return new Promise<void>((resolve, reject) => {
|
pack: plugins.tarStream.Pack,
|
||||||
const entry = pack.entry({ name: fileName, size: content.length }, (err: Error) => {
|
optionsArg: {
|
||||||
if (err) {
|
fileName?: string;
|
||||||
reject(err);
|
content?:
|
||||||
} else {
|
| string
|
||||||
resolve();
|
| Buffer
|
||||||
}
|
| plugins.smartstream.stream.Readable
|
||||||
});
|
| plugins.smartfile.SmartFile
|
||||||
|
| plugins.smartfile.StreamFile;
|
||||||
|
byteLength?: number;
|
||||||
|
filePath?: string;
|
||||||
|
},
|
||||||
|
): Promise<void> {
|
||||||
|
return new Promise<void>(async (resolve, reject) => {
|
||||||
|
let fileName: string | null = null;
|
||||||
|
|
||||||
entry.write(content);
|
if (optionsArg.fileName) {
|
||||||
entry.end();
|
fileName = optionsArg.fileName;
|
||||||
|
} else if (optionsArg.content instanceof plugins.smartfile.SmartFile) {
|
||||||
|
fileName = (optionsArg.content as plugins.smartfile.SmartFile).relative;
|
||||||
|
} else if (optionsArg.content instanceof plugins.smartfile.StreamFile) {
|
||||||
|
fileName = (optionsArg.content as plugins.smartfile.StreamFile)
|
||||||
|
.relativeFilePath;
|
||||||
|
} else if (optionsArg.filePath) {
|
||||||
|
fileName = optionsArg.filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* contentByteLength is used to set the size of the entry in the tar file
|
||||||
|
*/
|
||||||
|
let contentByteLength: number;
|
||||||
|
if (optionsArg.byteLength) {
|
||||||
|
contentByteLength = optionsArg.byteLength;
|
||||||
|
} else if (typeof optionsArg.content === 'string') {
|
||||||
|
contentByteLength = Buffer.byteLength(optionsArg.content, 'utf8');
|
||||||
|
} else if (Buffer.isBuffer(optionsArg.content)) {
|
||||||
|
contentByteLength = optionsArg.content.length;
|
||||||
|
} else if (optionsArg.content instanceof plugins.smartfile.SmartFile) {
|
||||||
|
contentByteLength = await optionsArg.content.getSize(); // assuming SmartFile has getSize method
|
||||||
|
} else if (optionsArg.content instanceof plugins.smartfile.StreamFile) {
|
||||||
|
contentByteLength = await optionsArg.content.getSize(); // assuming StreamFile has getSize method
|
||||||
|
} else if (
|
||||||
|
optionsArg.content instanceof plugins.smartstream.stream.Readable
|
||||||
|
) {
|
||||||
|
console.warn(
|
||||||
|
'@push.rocks/smartarchive: When streaming, it is recommended to provide byteLength, if known.',
|
||||||
|
);
|
||||||
|
} else if (optionsArg.filePath) {
|
||||||
|
const fileStat = await plugins.fsPromises.stat(optionsArg.filePath);
|
||||||
|
contentByteLength = fileStat.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* here we try to harmonize all kind of entries towards a readable stream
|
||||||
|
*/
|
||||||
|
let content: plugins.smartstream.stream.Readable;
|
||||||
|
if (Buffer.isBuffer(optionsArg.content)) {
|
||||||
|
content = plugins.smartstream.stream.Readable.from(optionsArg.content);
|
||||||
|
} else if (typeof optionsArg.content === 'string') {
|
||||||
|
content = plugins.smartstream.stream.Readable.from(
|
||||||
|
Buffer.from(optionsArg.content),
|
||||||
|
);
|
||||||
|
} else if (optionsArg.content instanceof plugins.smartfile.SmartFile) {
|
||||||
|
content = plugins.smartstream.stream.Readable.from(
|
||||||
|
optionsArg.content.contents,
|
||||||
|
);
|
||||||
|
} else if (optionsArg.content instanceof plugins.smartfile.StreamFile) {
|
||||||
|
content = await optionsArg.content.createReadStream();
|
||||||
|
} else if (
|
||||||
|
optionsArg.content instanceof plugins.smartstream.stream.Readable
|
||||||
|
) {
|
||||||
|
content = optionsArg.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = pack.entry(
|
||||||
|
{
|
||||||
|
name: fileName,
|
||||||
|
...(contentByteLength
|
||||||
|
? {
|
||||||
|
size: contentByteLength,
|
||||||
|
}
|
||||||
|
: null),
|
||||||
|
},
|
||||||
|
(err: Error) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
content.pipe(entry);
|
||||||
|
resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* packs a directory from disk into a tar stream
|
||||||
|
* @param directoryPath
|
||||||
|
*/
|
||||||
|
public async packDirectory(directoryPath: string) {
|
||||||
|
const fileTree = await plugins.listFileTree(directoryPath, '**/*');
|
||||||
|
const pack = await this.getPackStream();
|
||||||
|
for (const filePath of fileTree) {
|
||||||
|
const absolutePath = plugins.path.join(directoryPath, filePath);
|
||||||
|
const fileStat = await plugins.fsPromises.stat(absolutePath);
|
||||||
|
await this.addFileToPack(pack, {
|
||||||
|
byteLength: fileStat.size,
|
||||||
|
filePath: absolutePath,
|
||||||
|
fileName: filePath,
|
||||||
|
content: plugins.fs.createReadStream(absolutePath),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return pack;
|
||||||
|
}
|
||||||
|
|
||||||
public async getPackStream() {
|
public async getPackStream() {
|
||||||
const pack = plugins.tarStream.pack();
|
const pack = plugins.tarStream.pack();
|
||||||
return pack;
|
return pack;
|
||||||
|
|||||||
83
ts/classes.ziptools.ts
Normal file
83
ts/classes.ziptools.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import type { SmartArchive } from './classes.smartarchive.js';
|
||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
class DecompressZipTransform extends plugins.smartstream
|
||||||
|
.SmartDuplex<ArrayBufferLike> {
|
||||||
|
private streamtools: plugins.smartstream.IStreamTools;
|
||||||
|
private unzipper = new plugins.fflate.Unzip(async (fileArg) => {
|
||||||
|
let resultBuffer: Buffer;
|
||||||
|
fileArg.ondata = async (flateError, dat, final) => {
|
||||||
|
resultBuffer
|
||||||
|
? (resultBuffer = Buffer.concat([resultBuffer, Buffer.from(dat)]))
|
||||||
|
: (resultBuffer = Buffer.from(dat));
|
||||||
|
if (final) {
|
||||||
|
const streamFile =
|
||||||
|
plugins.smartfile.StreamFile.fromBuffer(resultBuffer);
|
||||||
|
streamFile.relativeFilePath = fileArg.name;
|
||||||
|
this.streamtools.push(streamFile);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fileArg.start();
|
||||||
|
});
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
objectMode: true,
|
||||||
|
writeFunction: async (chunkArg, streamtoolsArg) => {
|
||||||
|
this.streamtools ? null : (this.streamtools = streamtoolsArg);
|
||||||
|
this.unzipper.push(
|
||||||
|
Buffer.isBuffer(chunkArg) ? chunkArg : Buffer.from(chunkArg),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
finalFunction: async () => {
|
||||||
|
this.unzipper.push(Buffer.from(''), true);
|
||||||
|
await plugins.smartdelay.delayFor(0);
|
||||||
|
await this.streamtools.push(null);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.unzipper.register(plugins.fflate.UnzipInflate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This class wraps fflate's zip in a Node.js Transform stream for compression
|
||||||
|
export class CompressZipTransform extends plugins.stream.Transform {
|
||||||
|
files: { [fileName: string]: Uint8Array };
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.files = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
_transform(
|
||||||
|
chunk: Buffer,
|
||||||
|
encoding: BufferEncoding,
|
||||||
|
callback: plugins.stream.TransformCallback,
|
||||||
|
) {
|
||||||
|
// Simple example: storing chunks in memory before finalizing ZIP in _flush
|
||||||
|
this.files['file.txt'] = new Uint8Array(chunk);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
_flush(callback: plugins.stream.TransformCallback) {
|
||||||
|
plugins.fflate.zip(this.files, (err, zipped) => {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
} else {
|
||||||
|
this.push(Buffer.from(zipped));
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ZipTools {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
public getCompressionStream() {
|
||||||
|
return new CompressZipTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDecompressionStream() {
|
||||||
|
return new DecompressZipTransform();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,4 @@
|
|||||||
export * from './classes.smartarchive.js';
|
export * from './classes.smartarchive.js';
|
||||||
|
export * from './classes.tartools.js';
|
||||||
|
export * from './classes.ziptools.js';
|
||||||
|
export * from './classes.gziptools.js';
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ import * as plugins from './plugins.js';
|
|||||||
|
|
||||||
export const packageDir = plugins.path.join(
|
export const packageDir = plugins.path.join(
|
||||||
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
||||||
'../'
|
'../',
|
||||||
);
|
);
|
||||||
export const nogitDir = plugins.path.join(packageDir, './.nogit');
|
export const nogitDir = plugins.path.join(packageDir, './.nogit');
|
||||||
|
|||||||
@@ -1,11 +1,38 @@
|
|||||||
// node native scope
|
// node native scope
|
||||||
import * as path from 'path';
|
import * as path from 'node:path';
|
||||||
import * as stream from 'stream';
|
import * as stream from 'node:stream';
|
||||||
|
import * as fs from 'node:fs';
|
||||||
|
import * as fsPromises from 'node:fs/promises';
|
||||||
|
|
||||||
export { path, stream };
|
export { path, stream, fs, fsPromises };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List files in a directory recursively, returning relative paths
|
||||||
|
*/
|
||||||
|
export async function listFileTree(dirPath: string, _pattern: string = '**/*'): Promise<string[]> {
|
||||||
|
const results: string[] = [];
|
||||||
|
|
||||||
|
async function walkDir(currentPath: string, relativePath: string = '') {
|
||||||
|
const entries = await fsPromises.readdir(currentPath, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
const entryRelPath = relativePath ? path.join(relativePath, entry.name) : entry.name;
|
||||||
|
const entryFullPath = path.join(currentPath, entry.name);
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
await walkDir(entryFullPath, entryRelPath);
|
||||||
|
} else if (entry.isFile()) {
|
||||||
|
results.push(entryRelPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await walkDir(dirPath);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
// @pushrocks scope
|
// @pushrocks scope
|
||||||
import * as smartfile from '@push.rocks/smartfile';
|
import * as smartfile from '@push.rocks/smartfile';
|
||||||
|
import * as smartdelay from '@push.rocks/smartdelay';
|
||||||
import * as smartpath from '@push.rocks/smartpath';
|
import * as smartpath from '@push.rocks/smartpath';
|
||||||
import * as smartpromise from '@push.rocks/smartpromise';
|
import * as smartpromise from '@push.rocks/smartpromise';
|
||||||
import * as smartrequest from '@push.rocks/smartrequest';
|
import * as smartrequest from '@push.rocks/smartrequest';
|
||||||
@@ -14,7 +41,17 @@ import * as smartstream from '@push.rocks/smartstream';
|
|||||||
import * as smartrx from '@push.rocks/smartrx';
|
import * as smartrx from '@push.rocks/smartrx';
|
||||||
import * as smarturl from '@push.rocks/smarturl';
|
import * as smarturl from '@push.rocks/smarturl';
|
||||||
|
|
||||||
export { smartfile, smartpath, smartpromise, smartrequest, smartunique, smartstream, smartrx, smarturl };
|
export {
|
||||||
|
smartfile,
|
||||||
|
smartdelay,
|
||||||
|
smartpath,
|
||||||
|
smartpromise,
|
||||||
|
smartrequest,
|
||||||
|
smartunique,
|
||||||
|
smartstream,
|
||||||
|
smartrx,
|
||||||
|
smarturl,
|
||||||
|
};
|
||||||
|
|
||||||
// third party scope
|
// third party scope
|
||||||
import * as fileType from 'file-type';
|
import * as fileType from 'file-type';
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
"module": "NodeNext",
|
"module": "NodeNext",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"verbatimModuleSyntax": true
|
"verbatimModuleSyntax": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {}
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": ["dist_*/**/*.d.ts"]
|
||||||
"dist_*/**/*.d.ts"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user