Compare commits

...

6 Commits

Author SHA1 Message Date
c74ded8d5d 5.1.3
Some checks failed
Default (tags) / security (push) Successful in 41s
Default (tags) / test (push) Failing after 4m3s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-08-15 12:12:26 +00:00
62e61168a0 fix(appdata): Fix iteration over overwriteObject in AppData and update configuration for dependency and path handling 2025-08-15 12:12:26 +00:00
5e0edecf18 5.1.2
Some checks failed
Default (tags) / security (push) Failing after 15s
Default (tags) / test (push) Failing after 11s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2024-11-06 03:48:39 +01:00
70cefc00fa fix(appdata): Fix iteration over overwriteObject in AppData class 2024-11-06 03:48:39 +01:00
6f14c73b5f 5.1.1 2024-11-05 21:29:26 +01:00
1e6f636608 fix(AppData): Fix issue with overwrite object handling in AppData class 2024-11-05 21:29:26 +01:00
25 changed files with 5835 additions and 2837 deletions

View File

@@ -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

View File

@@ -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

3
.gitignore vendored
View File

@@ -3,7 +3,6 @@
# artifacts # artifacts
coverage/ coverage/
public/ public/
pages/
# installs # installs
node_modules/ node_modules/
@@ -17,4 +16,4 @@ node_modules/
dist/ dist/
dist_*/ dist_*/
# custom #------# custom

Binary file not shown.

View File

@@ -0,0 +1,32 @@
# 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

View File

@@ -0,0 +1,30 @@
# 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

View File

@@ -0,0 +1,31 @@
# 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

68
.serena/project.yml Normal file
View 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: "npmextra"

View File

@@ -1,47 +1,76 @@
# Changelog # Changelog
## 2025-08-15 - 5.1.3 - fix(appdata)
Fix iteration over overwriteObject in AppData and update configuration for dependency and path handling
- Replaced incorrect looping constructs in the AppData class to properly iterate over overwriteObject keys
- Improved environment variable mapping and file path resolution in multiple TS modules
- Updated dependency versions and adjusted git workflow configurations
- Enhanced project configuration including TS config and build script adjustments
## 2024-11-06 - 5.1.2 - fix(appdata)
Fix iteration over overwriteObject in AppData class
- Corrected the for loop from in to of inside the AppData class for iterating over overwriteObject keys.
## 2024-11-05 - 5.1.1 - fix(AppData)
Fix issue with overwrite object handling in AppData class
- Corrected logic to handle cases when overwriteObject is undefined.
## 2024-11-05 - 5.1.0 - feat(appdata) ## 2024-11-05 - 5.1.0 - feat(appdata)
Add support for overwriting keys using the overwriteObject option in AppData Add support for overwriting keys using the overwriteObject option in AppData
- Introduced the overwriteObject option in IAppDataOptions to allow overwriting specific keys in the AppData class. - Introduced the overwriteObject option in IAppDataOptions to allow overwriting specific keys in the AppData class.
## 2024-06-19 - 5.0.17 - 5.0.23 - Core Updates ## 2024-06-19 - 5.0.17 - 5.0.23 - Core Updates
Routine maintenance and updates to the core components. Routine maintenance and updates to the core components.
- Multiple core updates and fixes improving stability - Multiple core updates and fixes improving stability
## 2024-06-12 - 5.0.13 - 5.0.16 - Core Updates ## 2024-06-12 - 5.0.13 - 5.0.16 - Core Updates
Maintenance focus on core systems with enhancements and problem resolutions. Maintenance focus on core systems with enhancements and problem resolutions.
- Enhancements and updates in the core functionality - Enhancements and updates in the core functionality
## 2024-05-29 - 5.0.13 - Documentation Update ## 2024-05-29 - 5.0.13 - Documentation Update
Descriptive improvements aligned with current features. Descriptive improvements aligned with current features.
- Updated core description for better clarity in documentation - Updated core description for better clarity in documentation
## 2024-04-01 - 5.0.10 - Configuration Update ## 2024-04-01 - 5.0.10 - Configuration Update
Improved configuration management for build processes. Improved configuration management for build processes.
- Updated `npmextra.json` to reflect changes in git repository management - Updated `npmextra.json` to reflect changes in git repository management
## 2024-02-12 - 5.0.0 - 5.0.9 - Major Core Enhancements ## 2024-02-12 - 5.0.0 - 5.0.9 - Major Core Enhancements
A series of critical updates with resolved issues in the core components. A series of critical updates with resolved issues in the core components.
- Introduction of new core features - Introduction of new core features
- Several core system updates - Several core system updates
## 2024-02-12 - 4.0.16 - Major Version Transition ## 2024-02-12 - 4.0.16 - Major Version Transition
Migration to the new major version with impactful changes. Migration to the new major version with impactful changes.
- BREAKING CHANGE: Significant updates requiring attention for smooth transition - BREAKING CHANGE: Significant updates requiring attention for smooth transition
## 2023-08-24 - 3.0.9 - 4.0.16 - Organization Updates ## 2023-08-24 - 3.0.9 - 4.0.16 - Organization Updates
Formatted updates with attention to organizational standards and practice. Formatted updates with attention to organizational standards and practice.
- SWITCH to a new organizational scheme - SWITCH to a new organizational scheme
## 2023-07-11 - 3.0.9 - Organizational Enhancement ## 2023-07-11 - 3.0.9 - Organizational Enhancement
Shifts aligning with contemporary structuring and logistics. Shifts aligning with contemporary structuring and logistics.
- Strategic realignment with organizational principles - Strategic realignment with organizational principles

View File

@@ -1,12 +1,12 @@
{ {
"name": "@push.rocks/npmextra", "name": "@push.rocks/npmextra",
"version": "5.1.0", "version": "5.1.3",
"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 utility to enhance npm with additional configuration, tool management capabilities, and a key-value store for project setups.",
"main": "dist_ts/index.js", "main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts", "typings": "dist_ts/index.d.ts",
"scripts": { "scripts": {
"test": "(tstest test/ --web)", "test": "(tstest test/ --verbose --logfile --timeout 60)",
"build": "(tsbuild --web --allowimplicitany)", "build": "(tsbuild --web --allowimplicitany)",
"buildDocs": "tsdoc" "buildDocs": "tsdoc"
}, },
@@ -17,25 +17,24 @@
"author": "Lossless GmbH", "author": "Lossless GmbH",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {
"url": "https://gitlab.com/pushrocks/npmextra/issues" "url": "https://code.foss.global/push.rocks/npmextra/issues"
}, },
"homepage": "https://code.foss.global/push.rocks/npmextra", "homepage": "https://code.foss.global/push.rocks/npmextra#readme",
"dependencies": { "dependencies": {
"@push.rocks/qenv": "^6.0.5", "@push.rocks/qenv": "^6.1.2",
"@push.rocks/smartfile": "^11.0.20", "@push.rocks/smartfile": "^11.2.5",
"@push.rocks/smartjson": "^5.0.20", "@push.rocks/smartjson": "^5.0.20",
"@push.rocks/smartlog": "^3.0.7", "@push.rocks/smartlog": "^3.1.8",
"@push.rocks/smartpath": "^5.0.18", "@push.rocks/smartpath": "^6.0.0",
"@push.rocks/smartpromise": "^4.0.2", "@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartrx": "^3.0.7", "@push.rocks/smartrx": "^3.0.10",
"@push.rocks/taskbuffer": "^3.1.7", "@push.rocks/taskbuffer": "^3.1.7",
"@tsclass/tsclass": "^4.0.59" "@tsclass/tsclass": "^9.2.0"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^2.1.80", "@git.zone/tsbuild": "^2.6.4",
"@git.zone/tsrun": "^1.2.44", "@git.zone/tsrun": "^1.3.3",
"@git.zone/tstest": "^1.0.90", "@git.zone/tstest": "^2.3.2",
"@push.rocks/tapbundle": "^5.0.23",
"@types/node": "^20.14.5" "@types/node": "^20.14.5"
}, },
"files": [ "files": [
@@ -70,5 +69,9 @@
"smart file handling", "smart file handling",
"workflow improvement", "workflow improvement",
"persistent storage" "persistent storage"
] ],
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977",
"pnpm": {
"overrides": {}
}
} }

7655
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

4
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,4 @@
onlyBuiltDependencies:
- esbuild
- mongodb-memory-server
- puppeteer

586
readme.md
View File

@@ -1,295 +1,463 @@
# @push.rocks/npmextra # @push.rocks/npmextra 🚀
A utility to enhance npm with additional configuration, tool management capabilities, and a key-value store for project setups.
## Install **Supercharge your npm projects with powerful configuration management, tool orchestration, and persistent key-value storage.**
To install `@push.rocks/npmextra`, use the following npm command:
## Install 📦
```bash ```bash
# Using npm
npm install @push.rocks/npmextra --save npm install @push.rocks/npmextra --save
# Using pnpm (recommended)
pnpm add @push.rocks/npmextra
``` ```
This package is available on [npm](https://www.npmjs.com/package/@push.rocks/npmextra) and can be installed into your project as a dependency to enhance npm with additional configuration and tool management capabilities. ## Overview 🎯
## Usage `@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.
`@push.rocks/npmextra` is designed to supplement npm functionalities with enhanced configuration and tool management. It facilitates the management of project configurations and tool setups in a consolidated manner, enabling a smoother workflow and maintenance process. Below are detailed use cases and examples implemented with ESM syntax and TypeScript.
### Initial Setup and Configuration ### Why npmextra?
To start using `npmextra` in your project, first include it with an import statement:
- **🎛️ 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 { Npmextra } from '@push.rocks/npmextra';
// Initialize with current directory
const npmextra = new Npmextra();
// Or specify a custom path
const npmextra = new Npmextra('/path/to/project');
// Get merged configuration for any tool
const eslintConfig = npmextra.dataFor<EslintConfig>('eslint', {
// Default values if not in npmextra.json
extends: 'standard',
rules: {}
});
``` ```
Instantiate the `Npmextra` class optionally with a custom path to your project's working directory. If no path is provided, the current working directory (`process.cwd()`) is used. **npmextra.json example:**
```typescript
const npmExtraInstance = new Npmextra('/path/to/your/project');
```
### Managing Tool Configurations with `npmextra.json`
`@push.rocks/npmextra` excels in unifying tool configurations through a single `npmextra.json` file. Instead of scattering configurations across multiple files, `npmextra` enables you to define tool-specific settings within this centralized configuration file, which can then be accessed programmatically.
#### Creating and Utilizing `npmextra.json`
Create a `npmextra.json` in your project root with the following structure:
```json ```json
{ {
"toolname": { "eslint": {
"setting1": "value1", "extends": "@company/eslint-config",
"setting2": "value2" "rules": {
"no-console": "warn"
}
},
"prettier": {
"semi": false,
"singleQuote": true
} }
} }
``` ```
For example, to configure a hypothetical tool named `toolname`, define its settings as shown above. ### 2. KeyValueStore - Persistent Data Storage
#### Accessing Configuration in Your Project A flexible key-value store that persists data between script executions:
With the configuration defined, you can easily access these settings in your TypeScript code as follows:
```typescript
// Import the npmextra module
import { Npmextra } from '@push.rocks/npmextra';
// Create an instance pointing at the current directory
const npmExtraInstance = new Npmextra();
// Retrieve the configuration for 'toolname', merging defaults with any found in npmextra.json
const toolConfig = npmExtraInstance.dataFor<{ setting1: string, setting2: string }>('toolname', {
setting1: 'defaultValue1',
setting2: 'defaultValue2'
});
// toolConfig now contains the merged settings from npmextra.json and provided defaults
console.log(toolConfig);
```
### Key-Value Store Management
`@push.rocks/npmextra` also includes a Key-Value Store (KeyValueStore) functionality enabling persistent storage of key-value pairs between script executions.
#### Setting Up KeyValueStore
To utilize the KeyValueStore, create an instance specifying its scope (e.g., 'userHomeDir') and a unique identity for your application or tool:
```typescript ```typescript
import { KeyValueStore } from '@push.rocks/npmextra'; import { KeyValueStore } from '@push.rocks/npmextra';
const kvStore = new KeyValueStore<'userHomeDir'>({ interface UserSettings {
typeArg: 'userHomeDir', username: string;
identityArg: 'myUniqueAppName' apiKey: string;
preferences: {
theme: 'light' | 'dark';
notifications: boolean;
};
}
// Different storage strategies
const kvStore = new KeyValueStore<UserSettings>({
typeArg: 'userHomeDir', // Store in user's home directory
identityArg: 'myApp',
mandatoryKeys: ['username', 'apiKey']
}); });
```
You can then use the `writeKey`, `readKey`, `writeAll`, and `readAll` methods to manage your store respectively. // Write operations
await kvStore.writeKey('username', 'john_doe');
await kvStore.writeKey('preferences', {
theme: 'dark',
notifications: true
});
#### Example: Storing and Retrieving Data // Read operations
```typescript
// Write a single key-value pair
await kvStore.writeKey('username', 'johnDoe');
// Read a single key
const username = await kvStore.readKey('username'); const username = await kvStore.readKey('username');
console.log(username); // Outputs: johnDoe
// Write multiple key-value pairs
await kvStore.writeAll({
email: 'john@example.com',
isAdmin: true
});
// Read all key-value pairs
const allData = await kvStore.readAll(); const allData = await kvStore.readAll();
console.log(allData); // Outputs the entire store's contents
```
### Advanced Key-Value Store Management // Check for missing mandatory keys
In addition to basic read/write operations, `npmextra`s `KeyValueStore` supports advanced scenarios like mandatory keys and custom file paths.
#### Example: Mandatory Keys and Custom Paths
Consider a scenario where your application requires specific keys to be present in the KeyValueStore. You can define mandatory keys and use a custom path for your store like this:
```typescript
import { KeyValueStore } from '@push.rocks/npmextra';
interface CustomData {
key1: string;
key2: number;
key3?: boolean;
}
const kvStore = new KeyValueStore<CustomData>({
typeArg: 'custom',
identityArg: 'customApp',
customPath: '/custom/path/to/store.json',
mandatoryKeys: ['key1', 'key2']
});
// Ensure all mandatory keys are present
const missingKeys = await kvStore.getMissingMandatoryKeys(); const missingKeys = await kvStore.getMissingMandatoryKeys();
if (missingKeys.length) { if (missingKeys.length > 0) {
console.log(`Missing mandatory keys: ${missingKeys.join(', ')}`); console.log('Missing required configuration:', missingKeys);
} }
// Use the KeyValueStore // Wait for keys to be present
await kvStore.writeKey('key1', 'value1'); await kvStore.waitForKeysPresent(['apiKey']);
await kvStore.writeKey('key2', 123);
const key1Value = await kvStore.readKey('key1');
const allData = await kvStore.readAll();
console.log(key1Value); // Outputs: value1
console.log(allData); // Outputs: { key1: 'value1', key2: 123 }
``` ```
### Combining AppData and KeyValueStore **Storage Types:**
- `userHomeDir`: Store in user's home directory
- `custom`: Specify your own path
- `ephemeral`: In-memory only (perfect for testing)
The `AppData` class extends the functionality of `KeyValueStore` by integrating environmental variables, specifying additional configurations, and providing a more structured approach to data management. ### 3. AppData - Advanced Environment Management 🌟
#### Example: AppData Usage The crown jewel of npmextra - sophisticated environment variable mapping with automatic type conversion:
```typescript ```typescript
import { AppData } from '@push.rocks/npmextra'; import { AppData } from '@push.rocks/npmextra';
interface AppSettings { interface AppConfig {
settingA: string; apiUrl: string;
settingB: number; apiKey: string;
nestedSetting: { port: number;
innerSetting: boolean; features: {
} analytics: boolean;
payment: boolean;
};
cache: {
ttl: number;
redis: {
host: string;
password: string;
};
};
} }
const appDataInstance = await AppData.createAndInit<AppSettings>({ const appData = await AppData.createAndInit<AppConfig>({
dirPath: '/custom/path/to/appdata', dirPath: '/app/config', // Optional: defaults to smart path selection
requiredKeys: ['settingA', 'settingB'], requiredKeys: ['apiKey', 'apiUrl'],
envMapping: { envMapping: {
settingA: 'MY_ENV_A', apiUrl: 'API_URL', // Simple mapping
settingB: 'hard:42', apiKey: 'hard:development-key-123', // Hardcoded value
nestedSetting: { port: 'hard:3000', // Hardcoded number
innerSetting: 'MY_ENV_INNER' features: {
analytics: 'boolean:ENABLE_ANALYTICS', // Force boolean conversion
payment: 'hard_boolean:true' // Hardcoded boolean
},
cache: {
ttl: 'json:CACHE_CONFIG', // Parse JSON from env var
redis: {
host: 'REDIS_HOST',
password: 'base64:REDIS_PASSWORD_B64' // Decode base64
} }
} }
},
overwriteObject: {
// Force these values regardless of env vars
port: 8080
}
}); });
const appDataKvStore = await appDataInstance.getKvStore(); const store = await appData.getKvStore();
const apiUrl = await store.readKey('apiUrl');
// Writing values
await appDataKvStore.writeKey('settingA', 'exampleValue');
await appDataKvStore.writeKey('settingB', 100);
await appDataKvStore.writeKey('nestedSetting', { innerSetting: true });
// Reading values
const settingA = await appDataKvStore.readKey('settingA');
const allSettings = await appDataKvStore.readAll();
console.log(settingA); // Outputs: 'exampleValue'
console.log(allSettings); // Outputs: { settingA: 'exampleValue', settingB: 100, nestedSetting: { innerSetting: true } }
``` ```
### Error Handling and Debugging ## AppData Special Cases & Conversions 🎯
Proper error handling ensures your integrations with `npmextra` are robust and stable. Below are some strategies for error handling and debugging potential issues. ### Environment Variable Prefixes
#### Example: Error Handling in KeyValueStore AppData supports sophisticated type conversion through prefixes:
| Prefix | Description | Example | Result |
|--------|-------------|---------|--------|
| `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
Variables ending with certain suffixes get automatic conversion:
```typescript ```typescript
import { KeyValueStore } from '@push.rocks/npmextra'; {
envMapping: {
// Automatically parsed as JSON if MY_CONFIG_JSON="{"enabled":true}"
config: 'MY_CONFIG_JSON',
const kvStore = new KeyValueStore('userHomeDir', 'errorHandlingApp'); // Automatically decoded from base64 if SECRET_KEY_BASE64="SGVsbG8="
secret: 'SECRET_KEY_BASE64'
try { }
await kvStore.writeKey('importantKey', 'importantValue');
const value = await kvStore.readKey('importantKey');
console.log(value); // Outputs: importantValue
} catch (error) {
console.error('Error managing key-value store:', error);
} }
``` ```
#### Debugging Configuration Issues in `npmextra.json` ### Complex Examples
To debug configuration issues, you can utilize conditional logging and checks:
```typescript ```typescript
import { Npmextra } from '@push.rocks/npmextra'; const appData = await AppData.createAndInit({
envMapping: {
// Simple environment variable
apiUrl: 'API_URL',
const npmExtraInstance = new Npmextra(); // Hardcoded values with type conversion
const toolConfig = npmExtraInstance.dataFor('toolname', { debugMode: 'hard_boolean:false',
configKey1: 'defaultValue1', maxRetries: 'hard:5',
configKey2: 'defaultValue2' 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: {
host: 'DB_HOST',
port: 'hard:5432',
credentials: {
user: 'DB_USER',
password: 'base64:DB_PASSWORD_ENCODED',
ssl: 'boolean:DB_USE_SSL'
}
}
},
// Override any env mappings
overwriteObject: {
debugMode: true, // Force debug mode regardless of env
database: {
host: 'localhost' // Force localhost for development
}
}
}); });
if (!toolConfig.configKey1) {
console.error('configKey1 is missing in npmextra.json');
}
console.log(toolConfig);
``` ```
### Integration Tests ### Boolean Conversion Rules
Writing tests ensures that your integration with `npmextra` works as expected. Below are examples of integration tests for both `Npmextra` and `KeyValueStore`. AppData intelligently handles boolean conversions:
#### Example: Testing `Npmextra` Class 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 ```typescript
import { expect, tap } from '@push.rocks/tapbundle'; // Environment: FEATURE_A="true", FEATURE_B="yes", FEATURE_C="1"
import { Npmextra } from '@push.rocks/npmextra'; {
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)
}
}
```
let npmExtraInstance: Npmextra; ## Advanced Patterns 🎨
tap.test('should create an instance of Npmextra', async () => { ### Reactive Configuration
npmExtraInstance = new Npmextra();
expect(npmExtraInstance).toBeInstanceOf(Npmextra); Subscribe to configuration changes:
```typescript
const kvStore = new KeyValueStore<Config>({
typeArg: 'custom',
identityArg: 'myApp'
}); });
tap.test('should load configuration from npmextra.json', async () => { // Subscribe to changes
const config = npmExtraInstance.dataFor('toolname', { kvStore.changeSubject.subscribe((newData) => {
defaultKey1: 'defaultValue1', 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 🌍
Here's a complete example of a CLI tool using npmextra:
```typescript
import { Npmextra, AppData, KeyValueStore } from '@push.rocks/npmextra';
interface CliConfig {
githubToken: string;
openaiKey: string;
model: 'gpt-3' | 'gpt-4';
cache: {
enabled: boolean;
ttl: number;
};
}
class MyCLI {
private npmextra: Npmextra;
private appData: AppData<CliConfig>;
private cache: KeyValueStore<{[key: string]: any}>;
async initialize() {
// Load tool configuration
this.npmextra = new Npmextra();
const config = this.npmextra.dataFor<any>('mycli', {
defaultModel: 'gpt-3'
}); });
expect(config).toHaveProperty('defaultKey1');
});
tap.start(); // Setup app data with env mapping
``` this.appData = await AppData.createAndInit<CliConfig>({
requiredKeys: ['githubToken', 'openaiKey'],
envMapping: {
githubToken: 'GITHUB_TOKEN',
openaiKey: 'base64:OPENAI_KEY_ENCODED',
model: 'hard:gpt-4',
cache: {
enabled: 'boolean:ENABLE_CACHE',
ttl: 'hard:3600'
}
}
});
#### Example: Testing `KeyValueStore` Class // Initialize cache
this.cache = new KeyValueStore({
```typescript
import { expect, tap } from '@push.rocks/tapbundle';
import { KeyValueStore } from '@push.rocks/npmextra';
let kvStore: KeyValueStore<{ key1: string, key2: number }>;
tap.test('should create a KeyValueStore instance', async () => {
kvStore = new KeyValueStore({
typeArg: 'userHomeDir', typeArg: 'userHomeDir',
identityArg: 'testApp' identityArg: 'mycli-cache'
}); });
expect(kvStore).toBeInstanceOf(KeyValueStore);
});
tap.test('should write and read back a value', async () => { // Check for missing configuration
await kvStore.writeKey('key1', 'value1'); const missingKeys = await this.appData.logMissingKeys();
const result = await kvStore.readKey('key1'); if (missingKeys.length > 0) {
expect(result).toEqual('value1'); console.error('Please configure the missing keys');
}); process.exit(1);
}
}
tap.test('should write and read back multiple values', async () => { async run() {
await kvStore.writeAll({ key1: 'updatedValue1', key2: 2 }); await this.initialize();
const result = await kvStore.readAll();
expect(result).toEqual({ key1: 'updatedValue1', key2: 2 });
});
tap.start(); 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 cli = new MyCLI();
cli.run();
``` ```
### Summary ## API Reference 📚
By centralizing configuration management and offering a versatile key-value store, `@push.rocks/npmextra` significantly simplifies the setup and management of tools and settings in modern JavaScript and TypeScript projects. Whether you're managing project-wide configurations or need persistent storage for key-value pairs, `npmextra` provides an efficient and streamlined solution. Leveraging these robust features will ensure your project is well-configured and maintainable. ### 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

View File

@@ -1,5 +1,5 @@
{ {
"testTool":{ "testTool": {
"testValue":2 "testValue": 2
} }
} }

View File

@@ -1,5 +1,4 @@
import { expect, tap } from '@push.rocks/tapbundle'; import { expect, tap } from '@git.zone/tstest/tapbundle';
import path = require('path');
// module to test // module to test
import * as npmextra from '../ts/index.js'; import * as npmextra from '../ts/index.js';

View File

@@ -1,4 +1,4 @@
import { expect, tap } from '@push.rocks/tapbundle'; import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as npmextra from '../ts/index.js'; import * as npmextra from '../ts/index.js';
@@ -29,4 +29,4 @@ tap.test('expect to add an object to the kv Store', async () => {
await expect(await myKeyValueStore.readKey('myKey')).toEqual('myValue'); await expect(await myKeyValueStore.readKey('myKey')).toEqual('myValue');
}); });
tap.start(); export default tap.start();

View File

@@ -1,5 +1,4 @@
import { expect, tap } from '@push.rocks/tapbundle'; import { expect, tap } from '@git.zone/tstest/tapbundle';
import path = require('path');
// module to test // module to test
import * as npmextra from '../ts/index.js'; import * as npmextra from '../ts/index.js';
@@ -16,11 +15,14 @@ tap.test('should state wether a npmextra.json exists', async () => {
expect(testNpmextra.npmextraJsonExists).toBeTrue(); expect(testNpmextra.npmextraJsonExists).toBeTrue();
}); });
tap.test('should pass through default value, if not overriden by config from file', async () => { tap.test(
'should pass through default value, if not overriden by config from file',
async () => {
let testData = testNpmextra.dataFor('testTool', { someKey2: 'someValue2' }); let testData = testNpmextra.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 = testNpmextra.dataFor<any>('testTool', {

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/npmextra', name: '@push.rocks/npmextra',
version: '5.1.0', version: '5.1.3',
description: 'A utility to enhance npm with additional configuration, tool management capabilities, and a key-value store for project setups.' description: 'A utility to enhance npm with additional configuration, tool management capabilities, and a key-value store for project setups.'
} }

View File

@@ -26,7 +26,7 @@ export class AppData<T = any> {
* @returns * @returns
*/ */
public static async createAndInit<T = any>( public static async createAndInit<T = any>(
optionsArg: IAppDataOptions<T> = {} optionsArg: IAppDataOptions<T> = {},
): Promise<AppData<T>> { ): Promise<AppData<T>> {
const appData = new AppData<T>(optionsArg); const appData = new AppData<T>(optionsArg);
await appData.readyDeferred.promise; await appData.readyDeferred.promise;
@@ -76,14 +76,14 @@ export class AppData<T = any> {
if (this.options.envMapping) { if (this.options.envMapping) {
const qenvInstance = new plugins.qenv.Qenv( const qenvInstance = new plugins.qenv.Qenv(
process.cwd(), process.cwd(),
plugins.path.join(process.cwd(), '.nogit') plugins.path.join(process.cwd(), '.nogit'),
); );
// Recursive function to handle nested objects, now includes key parameter // Recursive function to handle nested objects, now includes key parameter
const processEnvMapping = async ( const processEnvMapping = async (
key: keyof T, key: keyof T,
mappingValue: any, mappingValue: any,
parentKey: keyof T | '' = '' parentKey: keyof T | '' = '',
): Promise<any> => { ): Promise<any> => {
if (typeof mappingValue === 'string') { if (typeof mappingValue === 'string') {
let envValue: string | boolean | T[keyof T]; let envValue: string | boolean | T[keyof T];
@@ -97,36 +97,40 @@ export class AppData<T = any> {
convert = 'boolean'; convert = 'boolean';
break; break;
case mappingValue.startsWith('hard_json:'): case mappingValue.startsWith('hard_json:'):
envValue = JSON.parse(mappingValue.replace('hard_json:', '')) as T[keyof T]; envValue = JSON.parse(
mappingValue.replace('hard_json:', ''),
) as T[keyof T];
convert = 'json'; convert = 'json';
break; break;
case mappingValue.startsWith('hard_base64:'): case mappingValue.startsWith('hard_base64:'):
envValue = Buffer.from( envValue = Buffer.from(
mappingValue.replace('hard_base64:', ''), mappingValue.replace('hard_base64:', ''),
'base64' 'base64',
).toString() as T[keyof T]; ).toString() as T[keyof T];
convert = 'base64'; convert = 'base64';
break; break;
case mappingValue.startsWith('boolean:'): case mappingValue.startsWith('boolean:'):
envValue = (await qenvInstance.getEnvVarOnDemand( envValue = (await qenvInstance.getEnvVarOnDemand(
mappingValue.replace('boolean:', '') mappingValue.replace('boolean:', ''),
)) as T[keyof T]; )) as T[keyof T];
convert = 'boolean'; convert = 'boolean';
break; break;
case mappingValue.startsWith('json:'): case mappingValue.startsWith('json:'):
envValue = (await qenvInstance.getEnvVarOnDemand( envValue = (await qenvInstance.getEnvVarOnDemand(
mappingValue.replace('json:', '') mappingValue.replace('json:', ''),
)) as T[keyof T]; )) as T[keyof T];
convert = 'json'; convert = 'json';
break; break;
case mappingValue.startsWith('base64:'): case mappingValue.startsWith('base64:'):
envValue = (await qenvInstance.getEnvVarOnDemand( envValue = (await qenvInstance.getEnvVarOnDemand(
mappingValue.replace('base64:', '') mappingValue.replace('base64:', ''),
)) as T[keyof T]; )) as T[keyof T];
convert = 'base64'; convert = 'base64';
break; break;
default: default:
envValue = (await qenvInstance.getEnvVarOnDemand(mappingValue)) as T[keyof T]; envValue = (await qenvInstance.getEnvVarOnDemand(
mappingValue,
)) as T[keyof T];
break; break;
} }
@@ -160,7 +164,11 @@ export class AppData<T = any> {
for (const innerKey in mappingValue) { for (const innerKey in mappingValue) {
const nestedValue = mappingValue[innerKey]; const nestedValue = mappingValue[innerKey];
// For nested objects, call recursively but do not immediately write to kvStore // For nested objects, call recursively but do not immediately write to kvStore
const nestedResult = await processEnvMapping(innerKey as keyof T, nestedValue, key); const nestedResult = await processEnvMapping(
innerKey as keyof T,
nestedValue,
key,
);
resultObject[innerKey as keyof T] = nestedResult; resultObject[innerKey as keyof T] = nestedResult;
} }
if (parentKey === '') { if (parentKey === '') {
@@ -177,9 +185,16 @@ export class AppData<T = any> {
await processEnvMapping(key as keyof T, this.options.envMapping[key]); await processEnvMapping(key as keyof T, this.options.envMapping[key]);
} }
for (const key in Object.keys(this.options.overwriteObject)) { if (this.options.overwriteObject) {
console.log(`-> heads up: overwriting key ${key} from options.overwriteObject`); for (const key of Object.keys(this.options.overwriteObject)) {
await this.kvStore.writeKey(key as keyof T, this.options.overwriteObject[key]); console.log(
`-> heads up: overwriting key ${key} from options.overwriteObject`,
);
await this.kvStore.writeKey(
key as keyof T,
this.options.overwriteObject[key],
);
}
} }
} }
@@ -200,8 +215,8 @@ export class AppData<T = any> {
if (missingMandatoryKeys.length > 0) { if (missingMandatoryKeys.length > 0) {
console.log( console.log(
`The following mandatory keys are missing in the appdata:\n -> ${missingMandatoryKeys.join( `The following mandatory keys are missing in the appdata:\n -> ${missingMandatoryKeys.join(
',\n -> ' ',\n -> ',
)}` )}`,
); );
} else { } else {
console.log('All mandatory keys are present in the appdata'); console.log('All mandatory keys are present in the appdata');
@@ -209,7 +224,9 @@ export class AppData<T = any> {
return missingMandatoryKeys; return missingMandatoryKeys;
} }
public async waitForAndGetKey<K extends keyof T>(keyArg: K): Promise<T[K] | undefined> { public async waitForAndGetKey<K extends keyof T>(
keyArg: K,
): Promise<T[K] | undefined> {
await this.readyDeferred.promise; await this.readyDeferred.promise;
await this.kvStore.waitForKeysPresent([keyArg]); await this.kvStore.waitForKeysPresent([keyArg]);
return this.kvStore.readKey(keyArg); return this.kvStore.readKey(keyArg);

View File

@@ -39,7 +39,7 @@ export class KeyValueStore<T = any> {
this.deletedObject = {}; this.deletedObject = {};
await plugins.smartfile.memory.toFs( await plugins.smartfile.memory.toFs(
plugins.smartjson.stringifyPretty(this.dataObject), plugins.smartjson.stringifyPretty(this.dataObject),
this.filePath this.filePath,
); );
} }
const newStateString = plugins.smartjson.stringify(this.dataObject); const newStateString = plugins.smartjson.stringify(this.dataObject);
@@ -62,10 +62,16 @@ export class KeyValueStore<T = any> {
} }
if (this.customPath) { if (this.customPath) {
// Use custom path if provided // Use custom path if provided
const absolutePath = plugins.smartpath.transform.makeAbsolute(this.customPath, paths.cwd); const absolutePath = plugins.smartpath.transform.makeAbsolute(
this.customPath,
paths.cwd,
);
this.filePath = absolutePath; this.filePath = absolutePath;
if (plugins.smartfile.fs.isDirectorySync(this.filePath)) { if (plugins.smartfile.fs.isDirectorySync(this.filePath)) {
this.filePath = plugins.path.join(this.filePath, this.identity + '.json'); this.filePath = plugins.path.join(
this.filePath,
this.identity + '.json',
);
} }
plugins.smartfile.fs.ensureFileSync(this.filePath, '{}'); plugins.smartfile.fs.ensureFileSync(this.filePath, '{}');
return; return;
@@ -129,7 +135,10 @@ export class KeyValueStore<T = any> {
/** /**
* writes a specific key to the keyValueStore * writes a specific key to the keyValueStore
*/ */
public async writeKey<K extends keyof T>(keyArg: K, valueArg: T[K]): Promise<void> { public async writeKey<K extends keyof T>(
keyArg: K,
valueArg: T[K],
): Promise<void> {
await this.writeAll({ await this.writeAll({
[keyArg]: valueArg, [keyArg]: valueArg,
} as unknown as Partial<T>); } as unknown as Partial<T>);
@@ -174,22 +183,28 @@ export class KeyValueStore<T = any> {
} }
private setMandatoryKeys(keys: Array<keyof T>) { private setMandatoryKeys(keys: Array<keyof T>) {
keys.forEach(key => this.mandatoryKeys.add(key)); keys.forEach((key) => this.mandatoryKeys.add(key));
} }
public async getMissingMandatoryKeys(): Promise<Array<keyof T>> { public async getMissingMandatoryKeys(): Promise<Array<keyof T>> {
await this.readAll(); await this.readAll();
return Array.from(this.mandatoryKeys).filter(key => !(key in this.dataObject)); return Array.from(this.mandatoryKeys).filter(
(key) => !(key in this.dataObject),
);
} }
public async waitForKeysPresent<K extends keyof T>(keysArg: K[]): Promise<void> { public async waitForKeysPresent<K extends keyof T>(
const missingKeys = keysArg.filter(keyArg => !this.dataObject[keyArg]); keysArg: K[],
): Promise<void> {
const missingKeys = keysArg.filter((keyArg) => !this.dataObject[keyArg]);
if (missingKeys.length === 0) { if (missingKeys.length === 0) {
return; return;
} }
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const subscription = this.changeSubject.subscribe(() => { const subscription = this.changeSubject.subscribe(() => {
const missingKeys = keysArg.filter(keyArg => !this.dataObject[keyArg]); const missingKeys = keysArg.filter(
(keyArg) => !this.dataObject[keyArg],
);
if (missingKeys.length === 0) { if (missingKeys.length === 0) {
subscription.unsubscribe(); subscription.unsubscribe();
resolve(); resolve();
@@ -198,7 +213,9 @@ export class KeyValueStore<T = any> {
}); });
} }
public async waitForAndGetKey<K extends keyof T>(keyArg: K): Promise<T[K] | undefined> { public async waitForAndGetKey<K extends keyof T>(
keyArg: K,
): Promise<T[K] | undefined> {
await this.waitForKeysPresent([keyArg]); await this.waitForKeysPresent([keyArg]);
return this.readKey(keyArg); return this.readKey(keyArg);
} }

View File

@@ -27,7 +27,10 @@ export class Npmextra {
/** /**
* merges the supplied options with the ones from npmextra.json * merges the supplied options with the ones from npmextra.json
*/ */
dataFor<IToolConfig>(toolnameArg: string, defaultOptionsArg: any): IToolConfig { dataFor<IToolConfig>(
toolnameArg: string,
defaultOptionsArg: any,
): IToolConfig {
let npmextraToolOptions; let npmextraToolOptions;
if (this.npmextraJsonData[toolnameArg]) { if (this.npmextraJsonData[toolnameArg]) {
npmextraToolOptions = this.npmextraJsonData[toolnameArg]; npmextraToolOptions = this.npmextraJsonData[toolnameArg];
@@ -45,7 +48,9 @@ export class Npmextra {
* checks if the JSON exists * checks if the JSON exists
*/ */
private checkNpmextraJsonExists() { private checkNpmextraJsonExists() {
this.npmextraJsonExists = plugins.smartfile.fs.fileExistsSync(this.lookupPath); this.npmextraJsonExists = plugins.smartfile.fs.fileExistsSync(
this.lookupPath,
);
} }
/** /**
@@ -64,7 +69,9 @@ export class Npmextra {
*/ */
private checkNpmextraJsonData() { private checkNpmextraJsonData() {
if (this.npmextraJsonExists) { if (this.npmextraJsonExists) {
this.npmextraJsonData = plugins.smartfile.fs.toObjectSync(this.lookupPath); this.npmextraJsonData = plugins.smartfile.fs.toObjectSync(
this.lookupPath,
);
} else { } else {
this.npmextraJsonData = {}; this.npmextraJsonData = {};
} }

View File

@@ -4,7 +4,7 @@ import * as plugins from './npmextra.plugins.js';
export let cwd = process.cwd(); export let cwd = process.cwd();
export let packageDir = plugins.path.join( export let packageDir = plugins.path.join(
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
'../' '../',
); );
// ---------------------- // ----------------------

View File

@@ -1,8 +1,6 @@
import * as tsclass from '@tsclass/tsclass'; import * as tsclass from '@tsclass/tsclass';
export { export { tsclass };
tsclass
}
import * as qenv from '@push.rocks/qenv'; import * as qenv from '@push.rocks/qenv';
import * as smartlog from '@push.rocks/smartlog'; import * as smartlog from '@push.rocks/smartlog';
@@ -14,4 +12,14 @@ import * as smartpromise from '@push.rocks/smartpromise';
import * as smartrx from '@push.rocks/smartrx'; import * as smartrx from '@push.rocks/smartrx';
import * as taskbuffer from '@push.rocks/taskbuffer'; import * as taskbuffer from '@push.rocks/taskbuffer';
export { qenv, smartlog, path, smartfile, smartjson, smartpath, smartpromise, smartrx, taskbuffer }; export {
qenv,
smartlog,
path,
smartfile,
smartjson,
smartpath,
smartpromise,
smartrx,
taskbuffer,
};

View File

@@ -1,14 +1,15 @@
{ {
"compilerOptions": { "compilerOptions": {
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true,
"useDefineForClassFields": false, "useDefineForClassFields": false,
"target": "ES2022", "target": "ES2022",
"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"
]
} }