Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c4ba7a7d0 | |||
| 4971279a22 | |||
| d82e98cfa1 | |||
| 4a2d625d79 | |||
| 488e78659c | |||
| 22a9aa9f3e | |||
| fdc2420238 | |||
| e3a76ca577 | |||
| 2cc0da4462 | |||
| b1e4ab09db |
Binary file not shown.
@@ -1,32 +0,0 @@
|
|||||||
# Coding Standards for npmextra
|
|
||||||
|
|
||||||
## Naming Conventions
|
|
||||||
- **Interfaces**: Prefix with `I` (e.g., `IAppDataOptions`, `ITestOptions`)
|
|
||||||
- **Types**: Prefix with `T` (e.g., `TKeyValueStore`)
|
|
||||||
- **Filenames**: Always lowercase (e.g., `npmextra.classes.appdata.ts`)
|
|
||||||
- **Module structure**: `npmextra.<type>.<name>.ts` pattern
|
|
||||||
|
|
||||||
## Import/Export Patterns
|
|
||||||
- Use ES module syntax (`import`/`export`)
|
|
||||||
- Import all dependencies through `npmextra.plugins.ts`
|
|
||||||
- Reference with full path: `plugins.moduleName.className()`
|
|
||||||
- Export all public APIs through `index.ts`
|
|
||||||
|
|
||||||
## TypeScript Patterns
|
|
||||||
- Use generic types for flexibility (`<T = any>`)
|
|
||||||
- Leverage TypeScript utility types from `@tsclass/tsclass`
|
|
||||||
- Use async/await for asynchronous operations
|
|
||||||
- Use Promises with smartpromise utilities
|
|
||||||
|
|
||||||
## Testing Standards
|
|
||||||
- Import expect from `@git.zone/tstest/tapbundle`
|
|
||||||
- Test files end with `export default tap.start()`
|
|
||||||
- Use descriptive test names with `tap.test()`
|
|
||||||
- Test file naming: `test.*.ts` pattern
|
|
||||||
|
|
||||||
## Code Quality
|
|
||||||
- Make focused, goal-oriented changes
|
|
||||||
- Preserve necessary complexity
|
|
||||||
- Remove redundancy carefully
|
|
||||||
- Keep async patterns where they add value
|
|
||||||
- No comments unless explicitly requested
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# npmextra Project Overview
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
npmextra is a utility library that enhances npm with additional configuration and tool management capabilities. It provides a key-value store for project setups and centralized configuration management through npmextra.json files.
|
|
||||||
|
|
||||||
## Tech Stack
|
|
||||||
- TypeScript (ES modules)
|
|
||||||
- Node.js
|
|
||||||
- Dependencies:
|
|
||||||
- @push.rocks/qenv - Environment variable management
|
|
||||||
- @push.rocks/smartfile - File system operations
|
|
||||||
- @push.rocks/smartjson - JSON handling
|
|
||||||
- @push.rocks/smartlog - Logging
|
|
||||||
- @push.rocks/smartpath - Path utilities
|
|
||||||
- @push.rocks/smartpromise - Promise utilities
|
|
||||||
- @push.rocks/smartrx - Reactive programming
|
|
||||||
- @push.rocks/taskbuffer - Task management
|
|
||||||
- @tsclass/tsclass - TypeScript utilities
|
|
||||||
|
|
||||||
## Main Components
|
|
||||||
1. **Npmextra** - Main class for managing npmextra.json configurations
|
|
||||||
2. **KeyValueStore** - Persistent key-value storage system
|
|
||||||
3. **AppData** - Advanced data management with environment variable mapping
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
- `ts/` - TypeScript source files
|
|
||||||
- `test/` - Test files using @git.zone/tstest
|
|
||||||
- `dist_ts/` - Compiled JavaScript output
|
|
||||||
- `npmextra.json` - Project configuration
|
|
||||||
- `package.json` - Node.js package configuration
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# Suggested Commands for npmextra Development
|
|
||||||
|
|
||||||
## Build Commands
|
|
||||||
- `pnpm run build` - Build the TypeScript project (uses tsbuild)
|
|
||||||
- `pnpm test` - Run tests using tstest
|
|
||||||
|
|
||||||
## Development Commands
|
|
||||||
- `pnpm install` - Install dependencies
|
|
||||||
- `pnpm install --save-dev <package>` - Add development dependencies
|
|
||||||
- `tsx <script>` - Run TypeScript files directly (tsx is globally available)
|
|
||||||
|
|
||||||
## Git Commands
|
|
||||||
- `git status` - Check current changes
|
|
||||||
- `git add .` - Stage changes
|
|
||||||
- `git commit -m "message"` - Commit changes (only when explicitly requested)
|
|
||||||
- `git mv` - Move/rename files to preserve history
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
- `pnpm test` - Run all tests
|
|
||||||
- `tstest test/test.some.ts --verbose` - Run specific test file
|
|
||||||
- Tests use @git.zone/tstest framework with tap-based structure
|
|
||||||
|
|
||||||
## Type Checking
|
|
||||||
- `pnpm run build` - Type check and build the project
|
|
||||||
- `tsbuild check test/**/* --skiplibcheck` - Type check test files only
|
|
||||||
|
|
||||||
## Directory Structure
|
|
||||||
- Source code in `ts/` directory
|
|
||||||
- Tests in `test/` directory
|
|
||||||
- Built output in `dist_ts/` directory
|
|
||||||
- Temporary/debug files in `.nogit/` directory
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# 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: "npmextra"
|
|
||||||
@@ -1,21 +1,17 @@
|
|||||||
{
|
{
|
||||||
"npmci": {
|
|
||||||
"globalNpmTools": [],
|
|
||||||
"npmAccessLevel": "public"
|
|
||||||
},
|
|
||||||
"npmts": {
|
"npmts": {
|
||||||
"testConfig": {
|
"testConfig": {
|
||||||
"parallel": false
|
"parallel": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gitzone": {
|
"@git.zone/cli": {
|
||||||
"projectType": "npm",
|
"projectType": "npm",
|
||||||
"module": {
|
"module": {
|
||||||
"githost": "code.foss.global",
|
"githost": "code.foss.global",
|
||||||
"gitscope": "push.rocks",
|
"gitscope": "push.rocks",
|
||||||
"gitrepo": "npmextra",
|
"gitrepo": "smartconfig",
|
||||||
"description": "A utility to enhance npm with additional configuration, tool management capabilities, and a key-value store for project setups.",
|
"description": "A comprehensive configuration management library providing key-value storage, environment variable mapping, and tool configuration.",
|
||||||
"npmPackagename": "@push.rocks/npmextra",
|
"npmPackagename": "@push.rocks/smartconfig",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"npm",
|
"npm",
|
||||||
@@ -34,9 +30,19 @@
|
|||||||
"workflow improvement",
|
"workflow improvement",
|
||||||
"persistent storage"
|
"persistent storage"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"registries": [
|
||||||
|
"https://verdaccio.lossless.digital",
|
||||||
|
"https://registry.npmjs.org"
|
||||||
|
],
|
||||||
|
"accessLevel": "public"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tsdoc": {
|
"@git.zone/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"
|
"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"
|
||||||
|
},
|
||||||
|
"@ship.zone/szci": {
|
||||||
|
"globalNpmTools": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"json.schemas": [
|
"json.schemas": [
|
||||||
{
|
{
|
||||||
"fileMatch": ["/npmextra.json"],
|
"fileMatch": ["/.smartconfig.json"],
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
22
changelog.md
22
changelog.md
@@ -1,5 +1,27 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-03-24 - 6.1.0 - feat(docs)
|
||||||
|
refresh project documentation and migrate smartconfig tool configuration keys
|
||||||
|
|
||||||
|
- rewrites the README with a clearer quick start, updated class documentation, and issue reporting guidance
|
||||||
|
- updates .smartconfig.json to use scoped tool configuration keys and adds release registry settings
|
||||||
|
- adds a standalone MIT license file to the repository
|
||||||
|
|
||||||
|
## 2025-08-16 - 5.3.3 - fix(appdata)
|
||||||
|
Redact sensitive values in AppData logs and add redaction tests
|
||||||
|
|
||||||
|
- Add redactSensitiveValue helper in AppData to mask secrets (API keys, tokens, passwords, JWTs, etc.) during logging.
|
||||||
|
- Use redaction when logging raw and final mapping values in processMappingValue and nested key logging to avoid leaking sensitive data.
|
||||||
|
- Improve log output for long or special values (JWT/base64 detection, length-aware previews) while preserving actual stored values.
|
||||||
|
- Add test/test.redaction.ts to verify sensitive environment values are redacted in console output but still stored correctly in the kv store.
|
||||||
|
- Add local config .claude/settings.local.json (editor/CI permissions/settings).
|
||||||
|
|
||||||
|
## 2025-08-16 - 5.3.2 - fix(dependencies)
|
||||||
|
Bump @push.rocks/qenv to ^6.1.3 and add local Claude settings
|
||||||
|
|
||||||
|
- Bumped dependency @push.rocks/qenv from ^6.1.2 to ^6.1.3 in package.json
|
||||||
|
- Added .claude/settings.local.json to provide local Claude permissions and tooling allowances for development/CI helpers
|
||||||
|
|
||||||
## 2025-08-15 - 5.3.1 - fix(AppData/Conversion)
|
## 2025-08-15 - 5.3.1 - fix(AppData/Conversion)
|
||||||
Improve boolean conversion and mapping evaluation in AppData, ensuring falsy values (like false, 0, and empty strings) are correctly handled and logged. Also, reduce test timeout and add local permissions settings for development.
|
Improve boolean conversion and mapping evaluation in AppData, ensuring falsy values (like false, 0, and empty strings) are correctly handled and logged. Also, reduce test timeout and add local permissions settings for development.
|
||||||
|
|
||||||
|
|||||||
21
license
Normal file
21
license
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Task Venture Capital GmbH
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
16
package.json
16
package.json
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/npmextra",
|
"name": "@push.rocks/smartconfig",
|
||||||
"version": "5.3.1",
|
"version": "6.1.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A utility to enhance npm with additional configuration, tool management capabilities, and a key-value store for project setups.",
|
"description": "A comprehensive configuration management library providing key-value storage, environment variable mapping, and tool configuration.",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
"typings": "dist_ts/index.d.ts",
|
"typings": "dist_ts/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -12,16 +12,16 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://code.foss.global/push.rocks/npmextra.git"
|
"url": "https://code.foss.global/push.rocks/smartconfig.git"
|
||||||
},
|
},
|
||||||
"author": "Lossless GmbH",
|
"author": "Lossless GmbH",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://code.foss.global/push.rocks/npmextra/issues"
|
"url": "https://code.foss.global/push.rocks/smartconfig/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://code.foss.global/push.rocks/npmextra#readme",
|
"homepage": "https://code.foss.global/push.rocks/smartconfig#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@push.rocks/qenv": "^6.1.2",
|
"@push.rocks/qenv": "^6.1.3",
|
||||||
"@push.rocks/smartfile": "^11.2.5",
|
"@push.rocks/smartfile": "^11.2.5",
|
||||||
"@push.rocks/smartjson": "^5.0.20",
|
"@push.rocks/smartjson": "^5.0.20",
|
||||||
"@push.rocks/smartlog": "^3.1.8",
|
"@push.rocks/smartlog": "^3.1.8",
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
"dist_ts_web/**/*",
|
"dist_ts_web/**/*",
|
||||||
"assets/**/*",
|
"assets/**/*",
|
||||||
"cli.js",
|
"cli.js",
|
||||||
"npmextra.json",
|
".smartconfig.json",
|
||||||
"readme.md"
|
"readme.md"
|
||||||
],
|
],
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
|
|||||||
42
pnpm-lock.yaml
generated
42
pnpm-lock.yaml
generated
@@ -9,8 +9,8 @@ importers:
|
|||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/qenv':
|
'@push.rocks/qenv':
|
||||||
specifier: ^6.1.2
|
specifier: ^6.1.3
|
||||||
version: 6.1.2
|
version: 6.1.3
|
||||||
'@push.rocks/smartfile':
|
'@push.rocks/smartfile':
|
||||||
specifier: ^11.2.5
|
specifier: ^11.2.5
|
||||||
version: 11.2.5
|
version: 11.2.5
|
||||||
@@ -244,8 +244,8 @@ packages:
|
|||||||
'@borewit/text-codec@0.1.1':
|
'@borewit/text-codec@0.1.1':
|
||||||
resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==}
|
resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==}
|
||||||
|
|
||||||
'@cloudflare/workers-types@4.20250813.0':
|
'@cloudflare/workers-types@4.20250816.0':
|
||||||
resolution: {integrity: sha512-RFFjomDndGR+p7ug1HWDlW21qOJyRZbmI99dUtuR9tmwJbSZhUUnSFmzok9lBYVfkMMrO1O5vmB+IlgiecgLEA==}
|
resolution: {integrity: sha512-R9ADrrINo1CqTwCddH39Tjlsc3grim6KeO7l8yddNbldH3uTkaAXYCzO0WiyLG7irLzLDrZVc4tLhN6BO3tdFw==}
|
||||||
|
|
||||||
'@colors/colors@1.6.0':
|
'@colors/colors@1.6.0':
|
||||||
resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
|
resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
|
||||||
@@ -578,8 +578,8 @@ packages:
|
|||||||
'@push.rocks/mongodump@1.0.8':
|
'@push.rocks/mongodump@1.0.8':
|
||||||
resolution: {integrity: sha512-oDufyjNBg8I50OaJvbHhc0RnRpJQ544dr9her0G6sA8JmI3hD2/amTdcPLVIX1kzYf5GsTUKeWuRaZgdNqz3ew==}
|
resolution: {integrity: sha512-oDufyjNBg8I50OaJvbHhc0RnRpJQ544dr9her0G6sA8JmI3hD2/amTdcPLVIX1kzYf5GsTUKeWuRaZgdNqz3ew==}
|
||||||
|
|
||||||
'@push.rocks/qenv@6.1.2':
|
'@push.rocks/qenv@6.1.3':
|
||||||
resolution: {integrity: sha512-epb5Ey7E3jVCjxvNmQ5bcjPs9+7d1z/5bV/V8+qwrPqZrbgXnslOnsQWOh9usAatO0VJqqZmSvLYSpjnm3NEcA==}
|
resolution: {integrity: sha512-+z2hsAU/7CIgpYLFqvda8cn9rUBMHqLdQLjsFfRn5jPoD7dJ5rFlpkbhfM4Ws8mHMniwWaxGKo+q/YBhtzRBLg==}
|
||||||
|
|
||||||
'@push.rocks/smartarchive@3.0.8':
|
'@push.rocks/smartarchive@3.0.8':
|
||||||
resolution: {integrity: sha512-1jPmR0b7hXmjYQoRiTlRXrIbZcdcFmSdGOfznufjcDpGPe86Km0d8TBnzqghTx4dTihzKC67IxAaz/DM3lvxpA==}
|
resolution: {integrity: sha512-1jPmR0b7hXmjYQoRiTlRXrIbZcdcFmSdGOfznufjcDpGPe86Km0d8TBnzqghTx4dTihzKC67IxAaz/DM3lvxpA==}
|
||||||
@@ -722,8 +722,8 @@ packages:
|
|||||||
'@push.rocks/smarts3@2.2.5':
|
'@push.rocks/smarts3@2.2.5':
|
||||||
resolution: {integrity: sha512-OZjD0jBCUTJCLnwraxBcyZ3he5buXf2OEM1zipiTBChA2EcKUZWKk/a6KR5WT+NlFCIIuB23UG+U+cxsIWM91Q==}
|
resolution: {integrity: sha512-OZjD0jBCUTJCLnwraxBcyZ3he5buXf2OEM1zipiTBChA2EcKUZWKk/a6KR5WT+NlFCIIuB23UG+U+cxsIWM91Q==}
|
||||||
|
|
||||||
'@push.rocks/smartshell@3.2.3':
|
'@push.rocks/smartshell@3.2.4':
|
||||||
resolution: {integrity: sha512-BWA/DH1H9lG7Er23d4uYgirfYaya5dX4g/WpWm2la7mOzuL9o2FnPIhel52DQUKIh7ty3Ql305ApV8YaAb4+/w==}
|
resolution: {integrity: sha512-zZEKfRl3qBaII9BJULk4rB/+EelUpgM2/qHCQLui7c4589HTge4o0nWn+olFw/Hge88qUO77OK1sN7hQFZ6zeg==}
|
||||||
|
|
||||||
'@push.rocks/smartsitemap@2.0.3':
|
'@push.rocks/smartsitemap@2.0.3':
|
||||||
resolution: {integrity: sha512-jIcms8V1b2mt3dS4PKNlLR1DRC8pCDWMRVbnyM/2+snZOJZonQRlQzAyX8No0EfLbfdrfnxv2IjPX13X29Re6g==}
|
resolution: {integrity: sha512-jIcms8V1b2mt3dS4PKNlLR1DRC8pCDWMRVbnyM/2+snZOJZonQRlQzAyX8No0EfLbfdrfnxv2IjPX13X29Re6g==}
|
||||||
@@ -734,8 +734,8 @@ packages:
|
|||||||
'@push.rocks/smartspawn@3.0.3':
|
'@push.rocks/smartspawn@3.0.3':
|
||||||
resolution: {integrity: sha512-DyrGPV69wwOiJgKkyruk5hS3UEGZ99xFAqBE9O2nM8VXCRLbbty3xt1Ug5Z092ZZmJYaaGMSnMw3ijyZJFCT0Q==}
|
resolution: {integrity: sha512-DyrGPV69wwOiJgKkyruk5hS3UEGZ99xFAqBE9O2nM8VXCRLbbty3xt1Ug5Z092ZZmJYaaGMSnMw3ijyZJFCT0Q==}
|
||||||
|
|
||||||
'@push.rocks/smartstate@2.0.25':
|
'@push.rocks/smartstate@2.0.26':
|
||||||
resolution: {integrity: sha512-gWmbDCx5esezHDQnD2nOClxeTiWtvU1wEdP0XbheCcXzaGEv0H8apRjQBksRZJd9FC3ezrJU00GLh0eH9rPyMQ==}
|
resolution: {integrity: sha512-lMcf0ZWWs9jej9wjapuonuIZiQNiD9NcAcvRDFXq7GtQf/HUyr6zr5K1XxGZaCIGyYrbYnBHBpNU+8DBoarHrA==}
|
||||||
|
|
||||||
'@push.rocks/smartstream@2.0.8':
|
'@push.rocks/smartstream@2.0.8':
|
||||||
resolution: {integrity: sha512-GlF/9cCkvBHwKa3DK4DO5wjfSgqkj6gAS4TrY9uD5NMHu9RQv4WiNrElTYj7iCEpnZgUnLO3tzw1JA3NRIMnnA==}
|
resolution: {integrity: sha512-GlF/9cCkvBHwKa3DK4DO5wjfSgqkj6gAS4TrY9uD5NMHu9RQv4WiNrElTYj7iCEpnZgUnLO3tzw1JA3NRIMnnA==}
|
||||||
@@ -3890,7 +3890,7 @@ snapshots:
|
|||||||
'@api.global/typedrequest': 3.1.10
|
'@api.global/typedrequest': 3.1.10
|
||||||
'@api.global/typedrequest-interfaces': 3.0.19
|
'@api.global/typedrequest-interfaces': 3.0.19
|
||||||
'@api.global/typedsocket': 3.0.1
|
'@api.global/typedsocket': 3.0.1
|
||||||
'@cloudflare/workers-types': 4.20250813.0
|
'@cloudflare/workers-types': 4.20250816.0
|
||||||
'@design.estate/dees-comms': 1.0.27
|
'@design.estate/dees-comms': 1.0.27
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
'@push.rocks/smartchok': 1.1.1
|
'@push.rocks/smartchok': 1.1.1
|
||||||
@@ -4511,7 +4511,7 @@ snapshots:
|
|||||||
|
|
||||||
'@borewit/text-codec@0.1.1': {}
|
'@borewit/text-codec@0.1.1': {}
|
||||||
|
|
||||||
'@cloudflare/workers-types@4.20250813.0': {}
|
'@cloudflare/workers-types@4.20250816.0': {}
|
||||||
|
|
||||||
'@colors/colors@1.6.0': {}
|
'@colors/colors@1.6.0': {}
|
||||||
|
|
||||||
@@ -4543,7 +4543,7 @@ snapshots:
|
|||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrouter': 1.3.3
|
'@push.rocks/smartrouter': 1.3.3
|
||||||
'@push.rocks/smartrx': 3.0.10
|
'@push.rocks/smartrx': 3.0.10
|
||||||
'@push.rocks/smartstate': 2.0.25
|
'@push.rocks/smartstate': 2.0.26
|
||||||
'@push.rocks/smartstring': 4.0.15
|
'@push.rocks/smartstring': 4.0.15
|
||||||
'@push.rocks/smarturl': 3.1.0
|
'@push.rocks/smarturl': 3.1.0
|
||||||
'@push.rocks/webrequest': 3.0.37
|
'@push.rocks/webrequest': 3.0.37
|
||||||
@@ -4709,14 +4709,14 @@ snapshots:
|
|||||||
'@push.rocks/smartnpm': 2.0.4
|
'@push.rocks/smartnpm': 2.0.4
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
'@push.rocks/smartrequest': 4.2.1
|
'@push.rocks/smartrequest': 4.2.1
|
||||||
'@push.rocks/smartshell': 3.2.3
|
'@push.rocks/smartshell': 3.2.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- aws-crt
|
- aws-crt
|
||||||
|
|
||||||
'@git.zone/tsrun@1.3.3':
|
'@git.zone/tsrun@1.3.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartfile': 11.2.5
|
'@push.rocks/smartfile': 11.2.5
|
||||||
'@push.rocks/smartshell': 3.2.3
|
'@push.rocks/smartshell': 3.2.4
|
||||||
tsx: 4.20.4
|
tsx: 4.20.4
|
||||||
|
|
||||||
'@git.zone/tstest@2.3.2(@aws-sdk/credential-providers@3.864.0)(socks@2.8.7)(typescript@5.8.3)':
|
'@git.zone/tstest@2.3.2(@aws-sdk/credential-providers@3.864.0)(socks@2.8.7)(typescript@5.8.3)':
|
||||||
@@ -4725,7 +4725,7 @@ snapshots:
|
|||||||
'@git.zone/tsbundle': 2.5.1
|
'@git.zone/tsbundle': 2.5.1
|
||||||
'@git.zone/tsrun': 1.3.3
|
'@git.zone/tsrun': 1.3.3
|
||||||
'@push.rocks/consolecolor': 2.0.3
|
'@push.rocks/consolecolor': 2.0.3
|
||||||
'@push.rocks/qenv': 6.1.2
|
'@push.rocks/qenv': 6.1.3
|
||||||
'@push.rocks/smartbrowser': 2.0.8(typescript@5.8.3)
|
'@push.rocks/smartbrowser': 2.0.8(typescript@5.8.3)
|
||||||
'@push.rocks/smartchok': 1.1.1
|
'@push.rocks/smartchok': 1.1.1
|
||||||
'@push.rocks/smartcrypto': 2.0.4
|
'@push.rocks/smartcrypto': 2.0.4
|
||||||
@@ -4740,7 +4740,7 @@ snapshots:
|
|||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrequest': 2.1.0
|
'@push.rocks/smartrequest': 2.1.0
|
||||||
'@push.rocks/smarts3': 2.2.5
|
'@push.rocks/smarts3': 2.2.5
|
||||||
'@push.rocks/smartshell': 3.2.3
|
'@push.rocks/smartshell': 3.2.4
|
||||||
'@push.rocks/smarttime': 4.1.1
|
'@push.rocks/smarttime': 4.1.1
|
||||||
'@types/ws': 8.18.1
|
'@types/ws': 8.18.1
|
||||||
figures: 6.1.0
|
figures: 6.1.0
|
||||||
@@ -4953,7 +4953,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- aws-crt
|
- aws-crt
|
||||||
|
|
||||||
'@push.rocks/qenv@6.1.2':
|
'@push.rocks/qenv@6.1.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@api.global/typedrequest': 3.1.10
|
'@api.global/typedrequest': 3.1.10
|
||||||
'@configvault.io/interfaces': 1.0.17
|
'@configvault.io/interfaces': 1.0.17
|
||||||
@@ -5319,7 +5319,7 @@ snapshots:
|
|||||||
'@push.rocks/smartpuppeteer@2.0.5(typescript@5.8.3)':
|
'@push.rocks/smartpuppeteer@2.0.5(typescript@5.8.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartshell': 3.2.3
|
'@push.rocks/smartshell': 3.2.4
|
||||||
puppeteer: 24.16.2(typescript@5.8.3)
|
puppeteer: 24.16.2(typescript@5.8.3)
|
||||||
tree-kill: 1.2.2
|
tree-kill: 1.2.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -5368,7 +5368,7 @@ snapshots:
|
|||||||
- aws-crt
|
- aws-crt
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@push.rocks/smartshell@3.2.3':
|
'@push.rocks/smartshell@3.2.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartexit': 1.0.23
|
'@push.rocks/smartexit': 1.0.23
|
||||||
@@ -5420,7 +5420,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@push.rocks/smartstate@2.0.25':
|
'@push.rocks/smartstate@2.0.26':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
'@push.rocks/smarthash': 3.2.3
|
'@push.rocks/smarthash': 3.2.3
|
||||||
|
|||||||
575
readme.md
575
readme.md
@@ -1,517 +1,322 @@
|
|||||||
# @push.rocks/npmextra 🚀
|
# @push.rocks/smartconfig 🚀
|
||||||
|
|
||||||
**Supercharge your npm projects with powerful configuration management, tool orchestration, and persistent key-value storage.**
|
A comprehensive TypeScript configuration management library providing centralized tool configs, persistent key-value storage, and powerful environment variable mapping with automatic type conversions.
|
||||||
|
|
||||||
|
## Issue Reporting and Security
|
||||||
|
|
||||||
|
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who 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.
|
||||||
|
|
||||||
## Install 📦
|
## Install 📦
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Using npm
|
npm install @push.rocks/smartconfig --save
|
||||||
npm install @push.rocks/npmextra --save
|
# or
|
||||||
|
pnpm add @push.rocks/smartconfig
|
||||||
# Using pnpm (recommended)
|
|
||||||
pnpm add @push.rocks/npmextra
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Overview 🎯
|
## Quick Start ⚡
|
||||||
|
|
||||||
`@push.rocks/npmextra` is your Swiss Army knife for npm project configuration. It eliminates configuration sprawl by centralizing tool settings, providing intelligent key-value storage, and offering powerful environment variable mapping with automatic type conversions.
|
|
||||||
|
|
||||||
### Why npmextra?
|
|
||||||
|
|
||||||
- **🎛️ Centralized Configuration**: Manage all your tool configs in one `npmextra.json` file
|
|
||||||
- **💾 Persistent Storage**: Smart key-value store with multiple storage strategies
|
|
||||||
- **🔐 Environment Mapping**: Sophisticated env var handling with automatic type conversion
|
|
||||||
- **🏗️ TypeScript First**: Full type safety and IntelliSense support
|
|
||||||
- **⚡ Zero Config**: Works out of the box with sensible defaults
|
|
||||||
- **🔄 Reactive**: Built-in change detection and observables
|
|
||||||
|
|
||||||
## Core Concepts 🏗️
|
|
||||||
|
|
||||||
### 1. Npmextra Configuration Management
|
|
||||||
|
|
||||||
Stop scattering configuration across dozens of files. Centralize everything in `npmextra.json`:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Npmextra } from '@push.rocks/npmextra';
|
import { Smartconfig, AppData, KeyValueStore } from '@push.rocks/smartconfig';
|
||||||
|
|
||||||
// Initialize with current directory
|
// 1. Read tool config from .smartconfig.json
|
||||||
const npmextra = new Npmextra();
|
const sc = new Smartconfig();
|
||||||
|
const eslintOpts = sc.dataFor('eslint', { extends: 'standard' });
|
||||||
|
|
||||||
// Or specify a custom path
|
// 2. Map env vars to typed config (with auto-conversion)
|
||||||
const npmextra = new Npmextra('/path/to/project');
|
const appData = await AppData.createAndInit<{ port: number; debug: boolean }>({
|
||||||
|
envMapping: {
|
||||||
|
port: 'PORT',
|
||||||
|
debug: 'boolean:DEBUG',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Get merged configuration for any tool
|
// 3. Persist data between runs
|
||||||
const eslintConfig = npmextra.dataFor<EslintConfig>('eslint', {
|
const kv = new KeyValueStore({ typeArg: 'userHomeDir', identityArg: 'myapp' });
|
||||||
// Default values if not in npmextra.json
|
await kv.writeKey('lastRun', Date.now());
|
||||||
extends: 'standard',
|
```
|
||||||
rules: {}
|
|
||||||
|
## Three Core Classes 🏗️
|
||||||
|
|
||||||
|
### 1. `Smartconfig` — Centralized Tool Configuration
|
||||||
|
|
||||||
|
Reads a `.smartconfig.json` file from a project directory and merges its contents with your defaults. One file, every tool.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Smartconfig } from '@push.rocks/smartconfig';
|
||||||
|
|
||||||
|
const sc = new Smartconfig(); // uses cwd
|
||||||
|
const sc2 = new Smartconfig('/my/project'); // or specify a path
|
||||||
|
|
||||||
|
const prettierConfig = sc.dataFor<PrettierConfig>('prettier', {
|
||||||
|
semi: false,
|
||||||
|
singleQuote: true,
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
**npmextra.json example:**
|
**`.smartconfig.json`** example:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"eslint": {
|
|
||||||
"extends": "@company/eslint-config",
|
|
||||||
"rules": {
|
|
||||||
"no-console": "warn"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"semi": false,
|
"semi": true,
|
||||||
"singleQuote": true
|
"printWidth": 120
|
||||||
|
},
|
||||||
|
"eslint": {
|
||||||
|
"extends": "@company/eslint-config"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. KeyValueStore - Persistent Data Storage
|
Values from the file override the defaults you pass in. Missing keys fall back to your defaults.
|
||||||
|
|
||||||
A flexible key-value store that persists data between script executions:
|
**Properties:**
|
||||||
|
- `smartconfigJsonExists: boolean` — whether `.smartconfig.json` was found
|
||||||
|
- `smartconfigJsonData: any` — the parsed JSON contents
|
||||||
|
|
||||||
|
**Methods:**
|
||||||
|
- `dataFor<T>(toolName: string, defaults: T): T` — returns merged config
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. `KeyValueStore<T>` — Persistent Data Storage
|
||||||
|
|
||||||
|
A generic, typed key-value store that persists JSON to disk (or stays in-memory for tests). Supports change detection via RxJS observables.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { KeyValueStore } from '@push.rocks/npmextra';
|
import { KeyValueStore } from '@push.rocks/smartconfig';
|
||||||
|
|
||||||
interface UserSettings {
|
interface Settings {
|
||||||
username: string;
|
username: string;
|
||||||
apiKey: string;
|
|
||||||
preferences: {
|
|
||||||
theme: 'light' | 'dark';
|
theme: 'light' | 'dark';
|
||||||
notifications: boolean;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Different storage strategies
|
// Store in ~/.smartconfig/kv/
|
||||||
const kvStore = new KeyValueStore<UserSettings>({
|
const kv = new KeyValueStore<Settings>({
|
||||||
typeArg: 'userHomeDir', // Store in user's home directory
|
typeArg: 'userHomeDir',
|
||||||
identityArg: 'myApp',
|
identityArg: 'myApp',
|
||||||
mandatoryKeys: ['username', 'apiKey']
|
mandatoryKeys: ['username'],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Write operations
|
await kv.writeKey('username', 'jane');
|
||||||
await kvStore.writeKey('username', 'john_doe');
|
await kv.writeKey('theme', 'dark');
|
||||||
await kvStore.writeKey('preferences', {
|
|
||||||
theme: 'dark',
|
|
||||||
notifications: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Read operations
|
const user = await kv.readKey('username'); // 'jane'
|
||||||
const username = await kvStore.readKey('username');
|
const all = await kv.readAll(); // { username: 'jane', theme: 'dark' }
|
||||||
const allData = await kvStore.readAll();
|
|
||||||
|
|
||||||
// Check for missing mandatory keys
|
// React to changes
|
||||||
const missingKeys = await kvStore.getMissingMandatoryKeys();
|
kv.changeSubject.subscribe((data) => console.log('changed:', data));
|
||||||
if (missingKeys.length > 0) {
|
|
||||||
console.log('Missing required configuration:', missingKeys);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for keys to be present
|
|
||||||
await kvStore.waitForKeysPresent(['apiKey']);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Storage Types:**
|
**Storage types:**
|
||||||
- `userHomeDir`: Store in user's home directory
|
|
||||||
- `custom`: Specify your own path
|
|
||||||
- `ephemeral`: In-memory only (perfect for testing)
|
|
||||||
|
|
||||||
### 3. AppData - Advanced Environment Management 🌟
|
| `typeArg` | Where it goes | Use case |
|
||||||
|
|-----------|--------------|----------|
|
||||||
|
| `'userHomeDir'` | `~/.smartconfig/kv/<identity>.json` | CLI tools, per-user state |
|
||||||
|
| `'custom'` | Your path (file or directory) | App data, project-local state |
|
||||||
|
| `'ephemeral'` | Memory only — nothing on disk | Tests |
|
||||||
|
|
||||||
The crown jewel of npmextra - sophisticated environment variable mapping with automatic type conversion:
|
**Methods:**
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|------------|
|
||||||
|
| `readKey(key)` | Read a single value |
|
||||||
|
| `writeKey(key, value)` | Write a single value |
|
||||||
|
| `readAll()` | Read everything |
|
||||||
|
| `writeAll(obj)` | Merge an object into the store |
|
||||||
|
| `deleteKey(key)` | Remove a key |
|
||||||
|
| `reset()` | Wipe all keys (synced to disk) |
|
||||||
|
| `wipe()` | Delete the backing file entirely |
|
||||||
|
| `getMissingMandatoryKeys()` | Returns keys declared mandatory but not yet set |
|
||||||
|
| `waitForKeysPresent(keys)` | Returns a Promise that resolves once all listed keys exist |
|
||||||
|
| `waitForAndGetKey(key)` | Waits for a key, then returns its value |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. `AppData<T>` — Environment Variable Mapping 🌟
|
||||||
|
|
||||||
|
The flagship class. Maps environment variables (or hardcoded values) into a typed config object with automatic type conversions, nested object support, and smart storage path selection.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { AppData } from '@push.rocks/npmextra';
|
import { AppData } from '@push.rocks/smartconfig';
|
||||||
|
|
||||||
interface AppConfig {
|
interface Config {
|
||||||
apiUrl: string;
|
apiUrl: string;
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
port: number;
|
|
||||||
features: {
|
features: {
|
||||||
analytics: boolean;
|
analytics: boolean;
|
||||||
payment: boolean;
|
payment: boolean;
|
||||||
};
|
};
|
||||||
cache: {
|
|
||||||
ttl: number;
|
|
||||||
redis: {
|
redis: {
|
||||||
host: string;
|
host: string;
|
||||||
password: string;
|
password: string;
|
||||||
};
|
};
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const appData = await AppData.createAndInit<AppConfig>({
|
const appData = await AppData.createAndInit<Config>({
|
||||||
dirPath: '/app/config', // Optional: defaults to smart path selection
|
requiredKeys: ['apiKey'],
|
||||||
requiredKeys: ['apiKey', 'apiUrl'],
|
|
||||||
envMapping: {
|
envMapping: {
|
||||||
apiUrl: 'API_URL', // Simple mapping
|
apiUrl: 'API_URL', // plain env var
|
||||||
apiKey: 'hard:development-key-123', // Hardcoded value
|
apiKey: 'hard:dev-key-123', // hardcoded fallback
|
||||||
port: 'hard:3000', // Hardcoded number
|
|
||||||
features: {
|
features: {
|
||||||
analytics: 'boolean:ENABLE_ANALYTICS', // Force boolean conversion
|
analytics: 'boolean:ENABLE_ANALYTICS', // converts "true"/"false" → boolean
|
||||||
payment: 'hard_boolean:true' // Hardcoded boolean
|
payment: 'hard_boolean:true', // hardcoded boolean
|
||||||
},
|
},
|
||||||
cache: {
|
|
||||||
ttl: 'json:CACHE_CONFIG', // Parse JSON from env var
|
|
||||||
redis: {
|
redis: {
|
||||||
host: 'REDIS_HOST',
|
host: 'REDIS_HOST',
|
||||||
password: 'base64:REDIS_PASSWORD_B64' // Decode base64
|
password: 'base64:REDIS_PASSWORD_B64', // base64-decode at load time
|
||||||
}
|
},
|
||||||
}
|
|
||||||
},
|
},
|
||||||
overwriteObject: {
|
overwriteObject: {
|
||||||
// Force these values regardless of env vars
|
apiUrl: 'http://localhost:3000', // force override after env mapping
|
||||||
port: 8080
|
},
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const store = await appData.getKvStore();
|
const store = await appData.getKvStore();
|
||||||
const apiUrl = await store.readKey('apiUrl');
|
const url = await store.readKey('apiUrl');
|
||||||
```
|
```
|
||||||
|
|
||||||
## AppData Special Cases & Conversions 🎯
|
#### Mapping Prefixes
|
||||||
|
|
||||||
### Environment Variable Prefixes
|
| Prefix | What it does | Example mapping | Result |
|
||||||
|
|--------|-------------|-----------------|--------|
|
||||||
|
| *(none)* | Raw env var as string | `'MY_VAR'` | `process.env.MY_VAR` |
|
||||||
|
| `hard:` | Hardcoded string | `'hard:hello'` | `"hello"` |
|
||||||
|
| `boolean:` | Env var → `true`/`false` | `'boolean:FLAG'` | `true` or `false` |
|
||||||
|
| `json:` | Env var → `JSON.parse()` | `'json:CONFIG'` | parsed object |
|
||||||
|
| `base64:` | Env var → base64 decode | `'base64:SECRET'` | decoded string |
|
||||||
|
| `hard_boolean:` | Hardcoded boolean | `'hard_boolean:false'` | `false` |
|
||||||
|
| `hard_json:` | Hardcoded JSON | `'hard_json:{"a":1}'` | `{ a: 1 }` |
|
||||||
|
| `hard_base64:` | Hardcoded base64 | `'hard_base64:SGVsbG8='` | `"Hello"` |
|
||||||
|
|
||||||
AppData supports sophisticated type conversion through prefixes:
|
Suffix detection also works: a mapping ending in `_JSON` or `_BASE64` triggers the corresponding transform automatically.
|
||||||
|
|
||||||
| Prefix | Description | Example | Result |
|
#### Boolean Conversion Rules
|
||||||
|--------|-------------|---------|--------|
|
|
||||||
| `hard:` | Hardcoded value | `hard:myvalue` | `"myvalue"` |
|
|
||||||
| `hard_boolean:` | Hardcoded boolean | `hard_boolean:true` | `true` |
|
|
||||||
| `hard_json:` | Hardcoded JSON | `hard_json:{"key":"value"}` | `{key: "value"}` |
|
|
||||||
| `hard_base64:` | Hardcoded base64 | `hard_base64:SGVsbG8=` | `"Hello"` |
|
|
||||||
| `boolean:` | Env var as boolean | `boolean:FEATURE_FLAG` | `true/false` |
|
|
||||||
| `json:` | Parse env var as JSON | `json:CONFIG_JSON` | Parsed object |
|
|
||||||
| `base64:` | Decode env var from base64 | `base64:SECRET_B64` | Decoded string |
|
|
||||||
|
|
||||||
### Automatic Suffix Detection
|
The `boolean:` prefix (and `hard_boolean:`) recognizes:
|
||||||
|
|
||||||
Variables ending with certain suffixes get automatic conversion:
|
- **true**: `"true"`, `"1"`, `"yes"`, `"y"`, `"on"` (case-insensitive)
|
||||||
|
- **false**: `"false"`, `"0"`, `"no"`, `"n"`, `"off"` (case-insensitive)
|
||||||
|
|
||||||
|
#### Nested Objects
|
||||||
|
|
||||||
|
Mapping values can be objects — they are resolved recursively:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
{
|
envMapping: {
|
||||||
envMapping: {
|
|
||||||
// Automatically parsed as JSON if MY_CONFIG_JSON="{"enabled":true}"
|
|
||||||
config: 'MY_CONFIG_JSON',
|
|
||||||
|
|
||||||
// Automatically decoded from base64 if SECRET_KEY_BASE64="SGVsbG8="
|
|
||||||
secret: 'SECRET_KEY_BASE64'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Complex Examples
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const appData = await AppData.createAndInit({
|
|
||||||
envMapping: {
|
|
||||||
// Simple environment variable
|
|
||||||
apiUrl: 'API_URL',
|
|
||||||
|
|
||||||
// Hardcoded values with type conversion
|
|
||||||
debugMode: 'hard_boolean:false',
|
|
||||||
maxRetries: 'hard:5',
|
|
||||||
defaultConfig: 'hard_json:{"timeout":30,"retries":3}',
|
|
||||||
|
|
||||||
// Environment variables with conversion
|
|
||||||
features: 'json:FEATURE_FLAGS', // Expects: {"feature1":true,"feature2":false}
|
|
||||||
isProduction: 'boolean:IS_PROD', // Expects: "true" or "false"
|
|
||||||
apiSecret: 'base64:API_SECRET', // Expects: base64 encoded string
|
|
||||||
|
|
||||||
// Nested structures
|
|
||||||
database: {
|
database: {
|
||||||
host: 'DB_HOST',
|
host: 'DB_HOST',
|
||||||
port: 'hard:5432',
|
port: 'hard:5432',
|
||||||
credentials: {
|
credentials: {
|
||||||
user: 'DB_USER',
|
user: 'DB_USER',
|
||||||
password: 'base64:DB_PASSWORD_ENCODED',
|
password: 'base64:DB_PASS_B64',
|
||||||
ssl: 'boolean:DB_USE_SSL'
|
ssl: 'boolean:DB_SSL',
|
||||||
}
|
},
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Override any env mappings
|
|
||||||
overwriteObject: {
|
|
||||||
debugMode: true, // Force debug mode regardless of env
|
|
||||||
database: {
|
|
||||||
host: 'localhost' // Force localhost for development
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Boolean Conversion Rules
|
|
||||||
|
|
||||||
AppData intelligently handles boolean conversions:
|
|
||||||
|
|
||||||
1. **String "true"/"false"**: Converted to boolean
|
|
||||||
2. **With `boolean:` prefix**: Any env var value is converted (`"true"` → `true`, anything else → `false`)
|
|
||||||
3. **With `hard_boolean:` prefix**: Hardcoded boolean value
|
|
||||||
4. **Regular env vars**: Strings remain strings unless prefixed
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Environment: FEATURE_A="true", FEATURE_B="yes", FEATURE_C="1"
|
|
||||||
{
|
|
||||||
envMapping: {
|
|
||||||
featureA: 'FEATURE_A', // Result: "true" (string)
|
|
||||||
featureB: 'boolean:FEATURE_B', // Result: false (only "true" → true)
|
|
||||||
featureC: 'boolean:FEATURE_C', // Result: false (only "true" → true)
|
|
||||||
featureD: 'hard_boolean:true' // Result: true (hardcoded)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Static Helper Functions
|
#### Smart Storage Path
|
||||||
|
|
||||||
AppData provides convenient static methods for directly accessing and converting environment variables without creating an instance:
|
When no `dirPath` is specified, AppData auto-selects:
|
||||||
|
|
||||||
|
1. `/app/data` — if it exists (containers)
|
||||||
|
2. `/data` — if it exists (alternate container path)
|
||||||
|
3. `.nogit/appdata` — local dev fallback
|
||||||
|
|
||||||
|
Or pass `ephemeral: true` for zero disk I/O (great for tests).
|
||||||
|
|
||||||
|
#### Static Helpers
|
||||||
|
|
||||||
|
Quick one-shot env var reads without creating an AppData instance:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { AppData } from '@push.rocks/npmextra';
|
const isEnabled = await AppData.valueAsBoolean('FEATURE_FLAG');
|
||||||
|
const config = await AppData.valueAsJson<MyType>('CONFIG_JSON');
|
||||||
// Get environment variable as boolean
|
|
||||||
const isEnabled = await AppData.valueAsBoolean('FEATURE_ENABLED');
|
|
||||||
// Returns: true if "true", false otherwise
|
|
||||||
|
|
||||||
// Get environment variable as parsed JSON
|
|
||||||
interface Config {
|
|
||||||
timeout: number;
|
|
||||||
retries: number;
|
|
||||||
}
|
|
||||||
const config = await AppData.valueAsJson<Config>('SERVICE_CONFIG');
|
|
||||||
// Returns: Parsed object or undefined
|
|
||||||
|
|
||||||
// Get environment variable as base64 decoded string
|
|
||||||
const secret = await AppData.valueAsBase64('ENCODED_SECRET');
|
const secret = await AppData.valueAsBase64('ENCODED_SECRET');
|
||||||
// Returns: Decoded string or undefined
|
const url = await AppData.valueAsString('API_URL');
|
||||||
|
|
||||||
// Get environment variable as string
|
|
||||||
const apiUrl = await AppData.valueAsString('API_URL');
|
|
||||||
// Returns: String value or undefined
|
|
||||||
|
|
||||||
// Get environment variable as number
|
|
||||||
const port = await AppData.valueAsNumber('PORT');
|
const port = await AppData.valueAsNumber('PORT');
|
||||||
// Returns: Number value or undefined
|
|
||||||
```
|
```
|
||||||
|
|
||||||
These static methods are perfect for:
|
#### Instance Methods
|
||||||
- Quick environment variable access without setup
|
|
||||||
- Simple type conversions in utility functions
|
|
||||||
- One-off configuration checks
|
|
||||||
- Scenarios where you don't need the full AppData instance
|
|
||||||
|
|
||||||
## Advanced Patterns 🎨
|
| Method | Description |
|
||||||
|
|--------|------------|
|
||||||
|
| `getKvStore()` | Returns the underlying `KeyValueStore<T>` |
|
||||||
|
| `logMissingKeys()` | Logs and returns any required keys that are missing |
|
||||||
|
| `waitForAndGetKey(key)` | Blocks until a key is present, then returns it |
|
||||||
|
|
||||||
### Reactive Configuration
|
---
|
||||||
|
|
||||||
Subscribe to configuration changes:
|
## Security 🔐
|
||||||
|
|
||||||
```typescript
|
AppData automatically redacts sensitive values in its console logs. Keys matching patterns like `secret`, `token`, `password`, `api`, `auth`, `jwt`, etc. are truncated. JWT tokens (starting with `eyJ`) are also detected and shortened. Your actual stored values are never modified — only log output is redacted.
|
||||||
const kvStore = new KeyValueStore<Config>({
|
|
||||||
typeArg: 'custom',
|
|
||||||
identityArg: 'myApp'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Subscribe to changes
|
---
|
||||||
kvStore.changeSubject.subscribe((newData) => {
|
|
||||||
console.log('Configuration changed:', newData);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Changes trigger notifications
|
|
||||||
await kvStore.writeKey('theme', 'dark');
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing with Ephemeral Storage
|
|
||||||
|
|
||||||
Perfect for unit tests - no file system pollution:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const testStore = new KeyValueStore<TestData>({
|
|
||||||
typeArg: 'ephemeral',
|
|
||||||
identityArg: 'test'
|
|
||||||
});
|
|
||||||
|
|
||||||
// All operations work normally, but nothing persists to disk
|
|
||||||
await testStore.writeKey('testKey', 'testValue');
|
|
||||||
```
|
|
||||||
|
|
||||||
### Smart Path Resolution
|
|
||||||
|
|
||||||
AppData automatically selects the best storage location:
|
|
||||||
|
|
||||||
1. Checks for `/app/data` (containerized environments)
|
|
||||||
2. Falls back to `/data` (alternate container path)
|
|
||||||
3. Uses `.nogit/appdata` (local development)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Automatic path selection
|
|
||||||
const appData = await AppData.createAndInit({
|
|
||||||
// No dirPath specified - smart detection
|
|
||||||
requiredKeys: ['apiKey']
|
|
||||||
});
|
|
||||||
|
|
||||||
// Or force ephemeral for testing
|
|
||||||
const testData = await AppData.createAndInit({
|
|
||||||
ephemeral: true, // No disk persistence
|
|
||||||
requiredKeys: ['testKey']
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Waiting for Configuration
|
|
||||||
|
|
||||||
Block until required configuration is available:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const appData = await AppData.createAndInit<Config>({
|
|
||||||
requiredKeys: ['apiKey', 'apiUrl']
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wait for specific key
|
|
||||||
const apiKey = await appData.waitForAndGetKey('apiKey');
|
|
||||||
|
|
||||||
// Check missing keys
|
|
||||||
const missingKeys = await appData.logMissingKeys();
|
|
||||||
// Logs: "The following mandatory keys are missing in the appdata:
|
|
||||||
// -> apiKey,
|
|
||||||
// -> apiUrl"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Real-World Example 🌍
|
## Real-World Example 🌍
|
||||||
|
|
||||||
Here's a complete example of a CLI tool using npmextra:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Npmextra, AppData, KeyValueStore } from '@push.rocks/npmextra';
|
import { Smartconfig, AppData, KeyValueStore } from '@push.rocks/smartconfig';
|
||||||
|
|
||||||
interface CliConfig {
|
interface CliConfig {
|
||||||
githubToken: string;
|
githubToken: string;
|
||||||
openaiKey: string;
|
|
||||||
model: 'gpt-3' | 'gpt-4';
|
model: 'gpt-3' | 'gpt-4';
|
||||||
cache: {
|
cache: { enabled: boolean; ttl: number };
|
||||||
enabled: boolean;
|
|
||||||
ttl: number;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyCLI {
|
// Tool-level config from .smartconfig.json
|
||||||
private npmextra: Npmextra;
|
const sc = new Smartconfig();
|
||||||
private appData: AppData<CliConfig>;
|
const toolDefaults = sc.dataFor('mycli', { defaultModel: 'gpt-3' });
|
||||||
private cache: KeyValueStore<{[key: string]: any}>;
|
|
||||||
|
|
||||||
async initialize() {
|
// Env-mapped runtime config
|
||||||
// Load tool configuration
|
const appData = await AppData.createAndInit<CliConfig>({
|
||||||
this.npmextra = new Npmextra();
|
requiredKeys: ['githubToken'],
|
||||||
const config = this.npmextra.dataFor<any>('mycli', {
|
|
||||||
defaultModel: 'gpt-3'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Setup app data with env mapping
|
|
||||||
this.appData = await AppData.createAndInit<CliConfig>({
|
|
||||||
requiredKeys: ['githubToken', 'openaiKey'],
|
|
||||||
envMapping: {
|
envMapping: {
|
||||||
githubToken: 'GITHUB_TOKEN',
|
githubToken: 'GITHUB_TOKEN',
|
||||||
openaiKey: 'base64:OPENAI_KEY_ENCODED',
|
|
||||||
model: 'hard:gpt-4',
|
model: 'hard:gpt-4',
|
||||||
cache: {
|
cache: {
|
||||||
enabled: 'boolean:ENABLE_CACHE',
|
enabled: 'boolean:ENABLE_CACHE',
|
||||||
ttl: 'hard:3600'
|
ttl: 'hard:3600',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize cache
|
// Persistent user-level cache
|
||||||
this.cache = new KeyValueStore({
|
const cache = new KeyValueStore({
|
||||||
typeArg: 'userHomeDir',
|
typeArg: 'userHomeDir',
|
||||||
identityArg: 'mycli-cache'
|
identityArg: 'mycli-cache',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check for missing configuration
|
// Check mandatory keys
|
||||||
const missingKeys = await this.appData.logMissingKeys();
|
const missing = await appData.logMissingKeys();
|
||||||
if (missingKeys.length > 0) {
|
if (missing.length > 0) {
|
||||||
console.error('Please configure the missing keys');
|
console.error('Missing config — set these env vars and retry.');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async run() {
|
|
||||||
await this.initialize();
|
|
||||||
|
|
||||||
const config = await this.appData.getKvStore();
|
|
||||||
const settings = await config.readAll();
|
|
||||||
|
|
||||||
console.log(`Using model: ${settings.model}`);
|
|
||||||
console.log(`Cache enabled: ${settings.cache.enabled}`);
|
|
||||||
|
|
||||||
// Use the configuration...
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the CLI
|
const store = await appData.getKvStore();
|
||||||
const cli = new MyCLI();
|
const settings = await store.readAll();
|
||||||
cli.run();
|
console.log(`Model: ${settings.model}, Cache: ${settings.cache.enabled}`);
|
||||||
```
|
```
|
||||||
|
|
||||||
## API Reference 📚
|
|
||||||
|
|
||||||
### Npmextra Class
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
new Npmextra(cwdArg?: string)
|
|
||||||
```
|
|
||||||
- `cwdArg`: Optional working directory path
|
|
||||||
|
|
||||||
**Methods:**
|
|
||||||
- `dataFor<T>(toolName: string, defaultOptions: T): T` - Get merged configuration
|
|
||||||
|
|
||||||
### KeyValueStore Class
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
new KeyValueStore<T>(options: {
|
|
||||||
typeArg: 'custom' | 'userHomeDir' | 'ephemeral';
|
|
||||||
identityArg: string;
|
|
||||||
customPath?: string;
|
|
||||||
mandatoryKeys?: Array<keyof T>;
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
**Methods:**
|
|
||||||
- `readKey(key: string): Promise<T>` - Read single value
|
|
||||||
- `writeKey(key: string, value: T): Promise<void>` - Write single value
|
|
||||||
- `readAll(): Promise<T>` - Read all values
|
|
||||||
- `writeAll(data: T): Promise<void>` - Write all values
|
|
||||||
- `deleteKey(key: string): Promise<void>` - Delete a key
|
|
||||||
- `getMissingMandatoryKeys(): Promise<string[]>` - Check missing required keys
|
|
||||||
- `waitForKeysPresent(keys: string[]): Promise<void>` - Wait for keys
|
|
||||||
|
|
||||||
### AppData Class
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
await AppData.createAndInit<T>(options: {
|
|
||||||
dirPath?: string;
|
|
||||||
requiredKeys?: Array<keyof T>;
|
|
||||||
ephemeral?: boolean;
|
|
||||||
envMapping?: PartialDeep<T>;
|
|
||||||
overwriteObject?: PartialDeep<T>;
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
**Methods:**
|
|
||||||
- `getKvStore(): Promise<KeyValueStore<T>>` - Get underlying store
|
|
||||||
- `logMissingKeys(): Promise<Array<keyof T>>` - Log and return missing keys
|
|
||||||
- `waitForAndGetKey<K>(key: K): Promise<T[K]>` - Wait for and retrieve key
|
|
||||||
|
|
||||||
## License and Legal Information
|
## 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.
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
||||||
|
|
||||||
**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.
|
**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
|
### 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.
|
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 or third parties, 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 or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
||||||
|
|
||||||
### Company Information
|
### Company Information
|
||||||
|
|
||||||
Task Venture Capital GmbH
|
Task Venture Capital GmbH
|
||||||
Registered at District court Bremen HRB 35230 HB, Germany
|
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.
|
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||||
|
|
||||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
|
|
||||||
// module to test
|
// module to test
|
||||||
import * as npmextra from '../ts/index.js';
|
import * as smartconfig from '../ts/index.js';
|
||||||
|
|
||||||
interface ITestOptions {
|
interface ITestOptions {
|
||||||
hi: string;
|
hi: string;
|
||||||
@@ -11,10 +11,10 @@ interface ITestOptions {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let testAppdata: npmextra.AppData<ITestOptions>;
|
let testAppdata: smartconfig.AppData<ITestOptions>;
|
||||||
|
|
||||||
tap.test('should create a valid AppData', async () => {
|
tap.test('should create a valid AppData', async () => {
|
||||||
testAppdata = new npmextra.AppData<ITestOptions>({
|
testAppdata = new smartconfig.AppData<ITestOptions>({
|
||||||
envMapping: {
|
envMapping: {
|
||||||
deep: {
|
deep: {
|
||||||
deep1: '',
|
deep1: '',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import * as npmextra from '../ts/index.js';
|
import * as smartconfig from '../ts/index.js';
|
||||||
|
|
||||||
// Test environment variable with boolean false value
|
// Test environment variable with boolean false value
|
||||||
tap.test('should handle boolean false values in nested objects correctly', async () => {
|
tap.test('should handle boolean false values in nested objects correctly', async () => {
|
||||||
@@ -11,7 +11,7 @@ tap.test('should handle boolean false values in nested objects correctly', async
|
|||||||
process.env['S3_ACCESSSECRET'] = 'test-secret';
|
process.env['S3_ACCESSSECRET'] = 'test-secret';
|
||||||
|
|
||||||
// Create AppData with nested object structure similar to CloudlyConfig
|
// Create AppData with nested object structure similar to CloudlyConfig
|
||||||
const appData = await npmextra.AppData.createAndInit({
|
const appData = await smartconfig.AppData.createAndInit({
|
||||||
ephemeral: true, // Use in-memory storage for testing
|
ephemeral: true, // Use in-memory storage for testing
|
||||||
envMapping: {
|
envMapping: {
|
||||||
s3Descriptor: {
|
s3Descriptor: {
|
||||||
@@ -69,7 +69,7 @@ tap.test('should handle various boolean representations correctly', async () =>
|
|||||||
for (const testCase of testCases) {
|
for (const testCase of testCases) {
|
||||||
process.env['TEST_BOOL'] = testCase.env;
|
process.env['TEST_BOOL'] = testCase.env;
|
||||||
|
|
||||||
const appData = await npmextra.AppData.createAndInit({
|
const appData = await smartconfig.AppData.createAndInit({
|
||||||
ephemeral: true,
|
ephemeral: true,
|
||||||
envMapping: {
|
envMapping: {
|
||||||
testBool: 'boolean:TEST_BOOL'
|
testBool: 'boolean:TEST_BOOL'
|
||||||
@@ -86,7 +86,7 @@ tap.test('should handle various boolean representations correctly', async () =>
|
|||||||
|
|
||||||
tap.test('should handle hardcoded boolean false values', async () => {
|
tap.test('should handle hardcoded boolean false values', async () => {
|
||||||
// Test with hardcoded boolean false value
|
// Test with hardcoded boolean false value
|
||||||
const appData = await npmextra.AppData.createAndInit({
|
const appData = await smartconfig.AppData.createAndInit({
|
||||||
ephemeral: true,
|
ephemeral: true,
|
||||||
envMapping: {
|
envMapping: {
|
||||||
boolValue: 'hard_boolean:false'
|
boolValue: 'hard_boolean:false'
|
||||||
@@ -109,7 +109,7 @@ tap.test('should not filter out other falsy values', async () => {
|
|||||||
process.env['ZERO_VALUE'] = '0';
|
process.env['ZERO_VALUE'] = '0';
|
||||||
process.env['EMPTY_STRING'] = ''; // This should be preserved as empty string
|
process.env['EMPTY_STRING'] = ''; // This should be preserved as empty string
|
||||||
|
|
||||||
const appData = await npmextra.AppData.createAndInit({
|
const appData = await smartconfig.AppData.createAndInit({
|
||||||
ephemeral: true,
|
ephemeral: true,
|
||||||
envMapping: {
|
envMapping: {
|
||||||
nested: {
|
nested: {
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
|
|
||||||
import * as npmextra from '../ts/index.js';
|
import * as smartconfig from '../ts/index.js';
|
||||||
|
|
||||||
let myKeyValueStore: npmextra.KeyValueStore<any>;
|
let myKeyValueStore: smartconfig.KeyValueStore<any>;
|
||||||
|
|
||||||
tap.test('should create a keyValueStore', async () => {
|
tap.test('should create a keyValueStore', async () => {
|
||||||
myKeyValueStore = new npmextra.KeyValueStore<any>({
|
myKeyValueStore = new smartconfig.KeyValueStore<any>({
|
||||||
typeArg: 'custom',
|
typeArg: 'custom',
|
||||||
identityArg: 'test',
|
identityArg: 'test',
|
||||||
customPath: 'test/somekv.json',
|
customPath: 'test/somekv.json',
|
||||||
});
|
});
|
||||||
expect(myKeyValueStore).toBeInstanceOf(npmextra.KeyValueStore);
|
expect(myKeyValueStore).toBeInstanceOf(smartconfig.KeyValueStore);
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should reset the keyValueStore', async () => {
|
tap.test('should reset the keyValueStore', async () => {
|
||||||
|
|||||||
85
test/test.redaction.ts
Normal file
85
test/test.redaction.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
|
import * as smartconfig from '../ts/index.js';
|
||||||
|
|
||||||
|
// Test that sensitive values are properly redacted in logs
|
||||||
|
tap.test('should redact sensitive values in console output', async () => {
|
||||||
|
// Capture console.log output
|
||||||
|
const originalLog = console.log;
|
||||||
|
const logOutput: string[] = [];
|
||||||
|
console.log = (...args: any[]) => {
|
||||||
|
logOutput.push(args.join(' '));
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Set up environment variables with sensitive data
|
||||||
|
process.env['API_KEY'] = 'super-secret-api-key-12345';
|
||||||
|
process.env['DATABASE_PASSWORD'] = 'myP@ssw0rd123';
|
||||||
|
process.env['AUTH_TOKEN'] = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ';
|
||||||
|
process.env['PUBLIC_URL'] = 'https://example.com';
|
||||||
|
process.env['DEBUG_MODE'] = 'true';
|
||||||
|
|
||||||
|
// Create AppData with sensitive environment mappings
|
||||||
|
const appData = await smartconfig.AppData.createAndInit({
|
||||||
|
ephemeral: true,
|
||||||
|
envMapping: {
|
||||||
|
apiKey: 'API_KEY',
|
||||||
|
dbPassword: 'DATABASE_PASSWORD',
|
||||||
|
authToken: 'AUTH_TOKEN',
|
||||||
|
publicUrl: 'PUBLIC_URL',
|
||||||
|
debugMode: 'boolean:DEBUG_MODE',
|
||||||
|
nestedConfig: {
|
||||||
|
secretKey: 'API_KEY',
|
||||||
|
nonSecret: 'PUBLIC_URL'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Restore console.log
|
||||||
|
console.log = originalLog;
|
||||||
|
|
||||||
|
// Check that sensitive values were redacted in logs
|
||||||
|
const combinedOutput = logOutput.join('\n');
|
||||||
|
|
||||||
|
// API_KEY should be redacted
|
||||||
|
expect(combinedOutput).toContain('sup...[26 chars]');
|
||||||
|
expect(combinedOutput).not.toContain('super-secret-api-key-12345');
|
||||||
|
|
||||||
|
// DATABASE_PASSWORD should be redacted
|
||||||
|
expect(combinedOutput).toContain('myP...[13 chars]');
|
||||||
|
expect(combinedOutput).not.toContain('myP@ssw0rd123');
|
||||||
|
|
||||||
|
// AUTH_TOKEN should be redacted (JWT tokens starting with eyJ)
|
||||||
|
expect(combinedOutput).toContain('eyJ...[');
|
||||||
|
expect(combinedOutput).not.toContain('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9');
|
||||||
|
|
||||||
|
// PUBLIC_URL should not be redacted (not sensitive)
|
||||||
|
expect(combinedOutput).toContain('https://example.com');
|
||||||
|
|
||||||
|
// DEBUG_MODE should not be redacted (not sensitive)
|
||||||
|
expect(combinedOutput).toContain('true');
|
||||||
|
|
||||||
|
// Verify data is still stored correctly (not redacted in actual storage)
|
||||||
|
const kvStore = await appData.getKvStore();
|
||||||
|
const apiKey = await kvStore.readKey('apiKey');
|
||||||
|
const dbPassword = await kvStore.readKey('dbPassword');
|
||||||
|
const publicUrl = await kvStore.readKey('publicUrl');
|
||||||
|
|
||||||
|
// Actual values should be stored correctly
|
||||||
|
expect(apiKey).toEqual('super-secret-api-key-12345');
|
||||||
|
expect(dbPassword).toEqual('myP@ssw0rd123');
|
||||||
|
expect(publicUrl).toEqual('https://example.com');
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
// Restore console.log in case of test failure
|
||||||
|
console.log = originalLog;
|
||||||
|
|
||||||
|
// Clean up environment variables
|
||||||
|
delete process.env['API_KEY'];
|
||||||
|
delete process.env['DATABASE_PASSWORD'];
|
||||||
|
delete process.env['AUTH_TOKEN'];
|
||||||
|
delete process.env['PUBLIC_URL'];
|
||||||
|
delete process.env['DEBUG_MODE'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
18
test/test.ts
18
test/test.ts
@@ -1,31 +1,31 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
|
|
||||||
// module to test
|
// module to test
|
||||||
import * as npmextra from '../ts/index.js';
|
import * as smartconfig from '../ts/index.js';
|
||||||
|
|
||||||
let testNpmextra: npmextra.Npmextra;
|
let testSmartconfig: smartconfig.Smartconfig;
|
||||||
|
|
||||||
tap.test('should create a new Npmtextra instance', async () => {
|
tap.test('should create a new Smartconfig instance', async () => {
|
||||||
testNpmextra = new npmextra.Npmextra('./test/');
|
testSmartconfig = new smartconfig.Smartconfig('./test/');
|
||||||
expect(testNpmextra).toBeInstanceOf(npmextra.Npmextra);
|
expect(testSmartconfig).toBeInstanceOf(smartconfig.Smartconfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should state wether a npmextra.json exists', async () => {
|
tap.test('should state wether a smartconfig.json exists', async () => {
|
||||||
// tslint:disable-next-line:no-unused-expression
|
// tslint:disable-next-line:no-unused-expression
|
||||||
expect(testNpmextra.npmextraJsonExists).toBeTrue();
|
expect(testSmartconfig.smartconfigJsonExists).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test(
|
tap.test(
|
||||||
'should pass through default value, if not overriden by config from file',
|
'should pass through default value, if not overriden by config from file',
|
||||||
async () => {
|
async () => {
|
||||||
let testData = testNpmextra.dataFor('testTool', { someKey2: 'someValue2' });
|
let testData = testSmartconfig.dataFor('testTool', { someKey2: 'someValue2' });
|
||||||
console.log(testData);
|
console.log(testData);
|
||||||
expect(testData).toHaveProperty('someKey2');
|
expect(testData).toHaveProperty('someKey2');
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
tap.test('should read a config file', async () => {
|
tap.test('should read a config file', async () => {
|
||||||
let testData = testNpmextra.dataFor<any>('testTool', {
|
let testData = testSmartconfig.dataFor<any>('testTool', {
|
||||||
someKey2: 'someValue2',
|
someKey2: 'someValue2',
|
||||||
});
|
});
|
||||||
expect(testData).toHaveProperty('someKey2');
|
expect(testData).toHaveProperty('someKey2');
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* autocreated commitinfo by @push.rocks/commitinfo
|
* autocreated commitinfo by @push.rocks/commitinfo
|
||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/npmextra',
|
name: '@push.rocks/smartconfig',
|
||||||
version: '5.3.1',
|
version: '6.1.0',
|
||||||
description: 'A utility to enhance npm with additional configuration, tool management capabilities, and a key-value store for project setups.'
|
description: 'A comprehensive configuration management library providing key-value storage, environment variable mapping, and tool configuration.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as plugins from './npmextra.plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import { KeyValueStore } from './npmextra.classes.keyvaluestore.js';
|
import { KeyValueStore } from './classes.keyvaluestore.js';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Singleton Qenv Provider
|
// Singleton Qenv Provider
|
||||||
@@ -16,6 +16,48 @@ function getQenv(): plugins.qenv.Qenv {
|
|||||||
return sharedQenv;
|
return sharedQenv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Security - Redaction for sensitive data
|
||||||
|
// ============================================================================
|
||||||
|
/**
|
||||||
|
* Redacts sensitive values in logs to prevent exposure of secrets
|
||||||
|
*/
|
||||||
|
function redactSensitiveValue(key: string, value: unknown): string {
|
||||||
|
// List of patterns that indicate sensitive data
|
||||||
|
const sensitivePatterns = [
|
||||||
|
/secret/i, /token/i, /key/i, /password/i, /pass/i,
|
||||||
|
/api/i, /credential/i, /auth/i, /private/i, /jwt/i,
|
||||||
|
/cert/i, /signature/i, /bearer/i
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check if key contains sensitive pattern
|
||||||
|
const isSensitive = sensitivePatterns.some(pattern => pattern.test(key));
|
||||||
|
|
||||||
|
if (isSensitive) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
// Show first 3 chars and length for debugging
|
||||||
|
return value.length > 3
|
||||||
|
? `${value.substring(0, 3)}...[${value.length} chars]`
|
||||||
|
: '[redacted]';
|
||||||
|
}
|
||||||
|
return '[redacted]';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if value looks like a JWT token or base64 secret
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
// JWT tokens start with eyJ
|
||||||
|
if (value.startsWith('eyJ')) {
|
||||||
|
return `eyJ...[${value.length} chars]`;
|
||||||
|
}
|
||||||
|
// Very long strings might be encoded secrets
|
||||||
|
if (value.length > 100) {
|
||||||
|
return `${value.substring(0, 50)}...[${value.length} chars total]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(value);
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Type Converters - Centralized conversion logic
|
// Type Converters - Centralized conversion logic
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -210,12 +252,14 @@ function applyTransforms(value: unknown, transforms: Transform[]): unknown {
|
|||||||
*/
|
*/
|
||||||
async function processMappingValue(mappingString: string): Promise<unknown> {
|
async function processMappingValue(mappingString: string): Promise<unknown> {
|
||||||
const spec = parseMappingSpec(mappingString);
|
const spec = parseMappingSpec(mappingString);
|
||||||
|
const keyName = spec.source.type === 'env' ? spec.source.key : 'hardcoded';
|
||||||
|
|
||||||
console.log(` 🔍 Processing mapping: "${mappingString}"`);
|
console.log(` 🔍 Processing mapping: "${mappingString}"`);
|
||||||
console.log(` Source: ${spec.source.type === 'env' ? `env:${spec.source.key}` : `hard:${spec.source.value}`}`);
|
console.log(` Source: ${spec.source.type === 'env' ? `env:${spec.source.key}` : `hard:${spec.source.value}`}`);
|
||||||
console.log(` Transforms: ${spec.transforms.length > 0 ? spec.transforms.join(', ') : 'none'}`);
|
console.log(` Transforms: ${spec.transforms.length > 0 ? spec.transforms.join(', ') : 'none'}`);
|
||||||
|
|
||||||
const rawValue = await resolveSource(spec.source);
|
const rawValue = await resolveSource(spec.source);
|
||||||
console.log(` Raw value: ${JSON.stringify(rawValue)} (type: ${typeof rawValue})`);
|
console.log(` Raw value: ${redactSensitiveValue(keyName, rawValue)} (type: ${typeof rawValue})`);
|
||||||
|
|
||||||
if (rawValue === undefined || rawValue === null) {
|
if (rawValue === undefined || rawValue === null) {
|
||||||
console.log(` ⚠️ Raw value is undefined/null, returning undefined`);
|
console.log(` ⚠️ Raw value is undefined/null, returning undefined`);
|
||||||
@@ -223,7 +267,7 @@ async function processMappingValue(mappingString: string): Promise<unknown> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = applyTransforms(rawValue, spec.transforms);
|
const result = applyTransforms(rawValue, spec.transforms);
|
||||||
console.log(` Final value: ${JSON.stringify(result)} (type: ${typeof result})`);
|
console.log(` Final value: ${redactSensitiveValue(keyName, result)} (type: ${typeof result})`);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +297,7 @@ async function evaluateMappingValue(mappingValue: any): Promise<any> {
|
|||||||
// Only skip if explicitly undefined
|
// Only skip if explicitly undefined
|
||||||
if (evaluated !== undefined) {
|
if (evaluated !== undefined) {
|
||||||
result[key] = evaluated;
|
result[key] = evaluated;
|
||||||
console.log(` ✓ Nested key "${key}" = ${JSON.stringify(evaluated)} (type: ${typeof evaluated})`);
|
console.log(` ✓ Nested key "${key}" = ${redactSensitiveValue(key, evaluated)} (type: ${typeof evaluated})`);
|
||||||
} else {
|
} else {
|
||||||
console.log(` ⚠️ Nested key "${key}" evaluated to undefined, skipping`);
|
console.log(` ⚠️ Nested key "${key}" evaluated to undefined, skipping`);
|
||||||
}
|
}
|
||||||
@@ -262,7 +306,8 @@ async function evaluateMappingValue(mappingValue: any): Promise<any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For any other type (numbers, booleans, etc.), return as-is
|
// For any other type (numbers, booleans, etc.), return as-is
|
||||||
console.log(` 📎 Returning value as-is: ${JSON.stringify(mappingValue)} (type: ${typeof mappingValue})`);
|
// Note: We don't have key context here, so we'll just indicate the type
|
||||||
|
console.log(` 📎 Returning value as-is: [value] (type: ${typeof mappingValue})`);
|
||||||
return mappingValue;
|
return mappingValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,7 +479,7 @@ export class AppData<T = any> {
|
|||||||
const valuePreview = evaluated === null ? 'null' :
|
const valuePreview = evaluated === null ? 'null' :
|
||||||
typeof evaluated === 'object' ?
|
typeof evaluated === 'object' ?
|
||||||
(Array.isArray(evaluated) ? `[${evaluated.length} items]` : `{${Object.keys(evaluated).length} keys}`) :
|
(Array.isArray(evaluated) ? `[${evaluated.length} items]` : `{${Object.keys(evaluated).length} keys}`) :
|
||||||
JSON.stringify(evaluated);
|
redactSensitiveValue(key, evaluated);
|
||||||
console.log(` ✅ Successfully processed key "${key}" = ${valuePreview} (type: ${valueType})`);
|
console.log(` ✅ Successfully processed key "${key}" = ${valuePreview} (type: ${valueType})`);
|
||||||
} else {
|
} else {
|
||||||
console.log(` ⚠️ Key "${key}" evaluated to undefined, skipping`);
|
console.log(` ⚠️ Key "${key}" evaluated to undefined, skipping`);
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as plugins from './npmextra.plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import * as paths from './npmextra.paths.js';
|
import * as paths from './paths.js';
|
||||||
|
|
||||||
import { Task } from '@push.rocks/taskbuffer';
|
import { Task } from '@push.rocks/taskbuffer';
|
||||||
|
|
||||||
79
ts/classes.smartconfig.ts
Normal file
79
ts/classes.smartconfig.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import * as paths from './paths.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smartconfig class allows easy configuration of tools
|
||||||
|
*/
|
||||||
|
export class Smartconfig {
|
||||||
|
cwd: string;
|
||||||
|
lookupPath: string;
|
||||||
|
smartconfigJsonExists: boolean;
|
||||||
|
smartconfigJsonData: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates instance of Smartconfig
|
||||||
|
*/
|
||||||
|
constructor(cwdArg?: string) {
|
||||||
|
if (cwdArg) {
|
||||||
|
this.cwd = cwdArg;
|
||||||
|
} else {
|
||||||
|
this.cwd = paths.cwd;
|
||||||
|
}
|
||||||
|
this.checkLookupPath();
|
||||||
|
this.checkSmartconfigJsonExists();
|
||||||
|
this.checkSmartconfigJsonData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* merges the supplied options with the ones from smartconfig.json
|
||||||
|
*/
|
||||||
|
dataFor<IToolConfig>(
|
||||||
|
toolnameArg: string,
|
||||||
|
defaultOptionsArg: any,
|
||||||
|
): IToolConfig {
|
||||||
|
let smartconfigToolOptions;
|
||||||
|
if (this.smartconfigJsonData[toolnameArg]) {
|
||||||
|
smartconfigToolOptions = this.smartconfigJsonData[toolnameArg];
|
||||||
|
} else {
|
||||||
|
smartconfigToolOptions = {};
|
||||||
|
}
|
||||||
|
let mergedOptions = {
|
||||||
|
...defaultOptionsArg,
|
||||||
|
...smartconfigToolOptions,
|
||||||
|
};
|
||||||
|
return mergedOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* checks if the JSON exists
|
||||||
|
*/
|
||||||
|
private checkSmartconfigJsonExists() {
|
||||||
|
this.smartconfigJsonExists = plugins.smartfile.fs.fileExistsSync(
|
||||||
|
this.lookupPath,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets lookupPath
|
||||||
|
*/
|
||||||
|
private checkLookupPath() {
|
||||||
|
if (this.cwd) {
|
||||||
|
this.lookupPath = plugins.path.join(this.cwd, '.smartconfig.json');
|
||||||
|
} else {
|
||||||
|
this.lookupPath = paths.configFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get smartconfigJsonData
|
||||||
|
*/
|
||||||
|
private checkSmartconfigJsonData() {
|
||||||
|
if (this.smartconfigJsonExists) {
|
||||||
|
this.smartconfigJsonData = plugins.smartfile.fs.toObjectSync(
|
||||||
|
this.lookupPath,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.smartconfigJsonData = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
export * from './npmextra.classes.appdata.js';
|
export * from './classes.appdata.js';
|
||||||
export * from './npmextra.classes.keyvaluestore.js';
|
export * from './classes.keyvaluestore.js';
|
||||||
export * from './npmextra.classes.npmextra.js';
|
export * from './classes.smartconfig.js';
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
import * as plugins from './npmextra.plugins.js';
|
|
||||||
import * as paths from './npmextra.paths.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Npmextra class allows easy configuration of tools
|
|
||||||
*/
|
|
||||||
export class Npmextra {
|
|
||||||
cwd: string;
|
|
||||||
lookupPath: string;
|
|
||||||
npmextraJsonExists: boolean;
|
|
||||||
npmextraJsonData: any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* creates instance of Npmextra
|
|
||||||
*/
|
|
||||||
constructor(cwdArg?: string) {
|
|
||||||
if (cwdArg) {
|
|
||||||
this.cwd = cwdArg;
|
|
||||||
} else {
|
|
||||||
this.cwd = paths.cwd;
|
|
||||||
}
|
|
||||||
this.checkLookupPath();
|
|
||||||
this.checkNpmextraJsonExists();
|
|
||||||
this.checkNpmextraJsonData();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* merges the supplied options with the ones from npmextra.json
|
|
||||||
*/
|
|
||||||
dataFor<IToolConfig>(
|
|
||||||
toolnameArg: string,
|
|
||||||
defaultOptionsArg: any,
|
|
||||||
): IToolConfig {
|
|
||||||
let npmextraToolOptions;
|
|
||||||
if (this.npmextraJsonData[toolnameArg]) {
|
|
||||||
npmextraToolOptions = this.npmextraJsonData[toolnameArg];
|
|
||||||
} else {
|
|
||||||
npmextraToolOptions = {};
|
|
||||||
}
|
|
||||||
let mergedOptions = {
|
|
||||||
...defaultOptionsArg,
|
|
||||||
...npmextraToolOptions,
|
|
||||||
};
|
|
||||||
return mergedOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* checks if the JSON exists
|
|
||||||
*/
|
|
||||||
private checkNpmextraJsonExists() {
|
|
||||||
this.npmextraJsonExists = plugins.smartfile.fs.fileExistsSync(
|
|
||||||
this.lookupPath,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* gets lookupPath
|
|
||||||
*/
|
|
||||||
private checkLookupPath() {
|
|
||||||
if (this.cwd) {
|
|
||||||
this.lookupPath = plugins.path.join(this.cwd, 'npmextra.json');
|
|
||||||
} else {
|
|
||||||
this.lookupPath = paths.configFile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get npmextraJsonData
|
|
||||||
*/
|
|
||||||
private checkNpmextraJsonData() {
|
|
||||||
if (this.npmextraJsonExists) {
|
|
||||||
this.npmextraJsonData = plugins.smartfile.fs.toObjectSync(
|
|
||||||
this.lookupPath,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.npmextraJsonData = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as plugins from './npmextra.plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
// directories
|
// directories
|
||||||
export let cwd = process.cwd();
|
export let cwd = process.cwd();
|
||||||
@@ -16,7 +16,7 @@ export let home = plugins.smartpath.get.home();
|
|||||||
/**
|
/**
|
||||||
* keyValue base path
|
* keyValue base path
|
||||||
*/
|
*/
|
||||||
export let kvUserHomeDirBase = plugins.path.join(home, '.npmextra/kv');
|
export let kvUserHomeDirBase = plugins.path.join(home, '.smartconfig/kv');
|
||||||
|
|
||||||
// files
|
// files
|
||||||
export let configFile = plugins.path.join(cwd, 'npmextra.json');
|
export let configFile = plugins.path.join(cwd, '.smartconfig.json');
|
||||||
Reference in New Issue
Block a user