rename package from @push.rocks/npmextra to @push.rocks/smartconfig
- Rename all source files from npmextra.* to simpler names (classes.appdata.ts, etc.) - Rename Npmextra class to Smartconfig - Config file changed from npmextra.json to smartconfig.json - KV store path changed from ~/.npmextra/kv to ~/.smartconfig/kv - Update all imports, tests, and metadata
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* autocreated commitinfo by @push.rocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/npmextra',
|
||||
name: '@push.rocks/smartconfig',
|
||||
version: '5.3.3',
|
||||
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 { KeyValueStore } from './npmextra.classes.keyvaluestore.js';
|
||||
import * as plugins from './plugins.js';
|
||||
import { KeyValueStore } from './classes.keyvaluestore.js';
|
||||
|
||||
// ============================================================================
|
||||
// Singleton Qenv Provider
|
||||
@@ -25,24 +25,24 @@ function getQenv(): plugins.qenv.Qenv {
|
||||
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,
|
||||
/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
|
||||
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
|
||||
@@ -54,7 +54,7 @@ function redactSensitiveValue(key: string, value: unknown): string {
|
||||
return `${value.substring(0, 50)}...[${value.length} chars total]`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
@@ -67,28 +67,28 @@ function toBoolean(value: unknown): boolean {
|
||||
console.log(` 🔹 toBoolean: value is already boolean: ${value}`);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
// Handle null/undefined
|
||||
if (value == null) {
|
||||
console.log(` 🔹 toBoolean: value is null/undefined, returning false`);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Handle string representations
|
||||
const s = String(value).toLowerCase().trim();
|
||||
|
||||
|
||||
// True values: "true", "1", "yes", "y", "on"
|
||||
if (['true', '1', 'yes', 'y', 'on'].includes(s)) {
|
||||
console.log(` 🔹 toBoolean: converting "${value}" to true`);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// False values: "false", "0", "no", "n", "off"
|
||||
if (['false', '0', 'no', 'n', 'off'].includes(s)) {
|
||||
console.log(` 🔹 toBoolean: converting "${value}" to false`);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Default: non-empty string = true, empty = false
|
||||
const result = s.length > 0;
|
||||
console.log(` 🔹 toBoolean: defaulting "${value}" to ${result}`);
|
||||
@@ -133,7 +133,7 @@ function toString(value: unknown): string | undefined {
|
||||
type Transform = 'boolean' | 'json' | 'base64' | 'number';
|
||||
|
||||
type MappingSpec = {
|
||||
source:
|
||||
source:
|
||||
| { type: 'env'; key: string }
|
||||
| { type: 'hard'; value: string };
|
||||
transforms: Transform[];
|
||||
@@ -153,7 +153,7 @@ const transformRegistry: Record<string, (v: unknown) => unknown> = {
|
||||
function parseMappingSpec(input: string): MappingSpec {
|
||||
const transforms: Transform[] = [];
|
||||
let remaining = input;
|
||||
|
||||
|
||||
// Check for hardcoded prefixes with type conversion
|
||||
if (remaining.startsWith('hard_boolean:')) {
|
||||
return {
|
||||
@@ -161,21 +161,21 @@ function parseMappingSpec(input: string): MappingSpec {
|
||||
transforms: ['boolean']
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (remaining.startsWith('hard_json:')) {
|
||||
return {
|
||||
source: { type: 'hard', value: remaining.slice(10) },
|
||||
transforms: ['json']
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (remaining.startsWith('hard_base64:')) {
|
||||
return {
|
||||
source: { type: 'hard', value: remaining.slice(12) },
|
||||
transforms: ['base64']
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Check for generic hard: prefix
|
||||
if (remaining.startsWith('hard:')) {
|
||||
remaining = remaining.slice(5);
|
||||
@@ -192,7 +192,7 @@ function parseMappingSpec(input: string): MappingSpec {
|
||||
transforms
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Check for env var prefixes
|
||||
if (remaining.startsWith('boolean:')) {
|
||||
transforms.push('boolean');
|
||||
@@ -204,7 +204,7 @@ function parseMappingSpec(input: string): MappingSpec {
|
||||
transforms.push('base64');
|
||||
remaining = remaining.slice(7);
|
||||
}
|
||||
|
||||
|
||||
// Check for legacy suffixes on env vars
|
||||
if (remaining.endsWith('_JSON')) {
|
||||
transforms.push('json');
|
||||
@@ -213,7 +213,7 @@ function parseMappingSpec(input: string): MappingSpec {
|
||||
transforms.push('base64');
|
||||
remaining = remaining.slice(0, -7);
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
source: { type: 'env', key: remaining },
|
||||
transforms
|
||||
@@ -253,19 +253,19 @@ function applyTransforms(value: unknown, transforms: Transform[]): unknown {
|
||||
async function processMappingValue(mappingString: string): Promise<unknown> {
|
||||
const spec = parseMappingSpec(mappingString);
|
||||
const keyName = spec.source.type === 'env' ? spec.source.key : 'hardcoded';
|
||||
|
||||
|
||||
console.log(` 🔍 Processing mapping: "${mappingString}"`);
|
||||
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'}`);
|
||||
|
||||
|
||||
const rawValue = await resolveSource(spec.source);
|
||||
console.log(` Raw value: ${redactSensitiveValue(keyName, rawValue)} (type: ${typeof rawValue})`);
|
||||
|
||||
|
||||
if (rawValue === undefined || rawValue === null) {
|
||||
console.log(` ⚠️ Raw value is undefined/null, returning undefined`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
const result = applyTransforms(rawValue, spec.transforms);
|
||||
console.log(` Final value: ${redactSensitiveValue(keyName, result)} (type: ${typeof result})`);
|
||||
return result;
|
||||
@@ -280,12 +280,12 @@ async function evaluateMappingValue(mappingValue: any): Promise<any> {
|
||||
console.log(` 📌 Value is null, returning null`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// Handle strings (mapping specs)
|
||||
if (typeof mappingValue === 'string') {
|
||||
return processMappingValue(mappingValue);
|
||||
}
|
||||
|
||||
|
||||
// Handle objects (but not arrays or null)
|
||||
if (mappingValue && typeof mappingValue === 'object' && !Array.isArray(mappingValue)) {
|
||||
console.log(` 📂 Processing nested object with ${Object.keys(mappingValue).length} keys`);
|
||||
@@ -304,7 +304,7 @@ async function evaluateMappingValue(mappingValue: any): Promise<any> {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// For any other type (numbers, booleans, etc.), return as-is
|
||||
// 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})`);
|
||||
@@ -322,7 +322,7 @@ export interface IAppDataOptions<T = any> {
|
||||
* Whether keys should be persisted on disk or not
|
||||
*/
|
||||
ephemeral?: boolean;
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated Use 'ephemeral' instead
|
||||
*/
|
||||
@@ -415,13 +415,13 @@ export class AppData<T = any> {
|
||||
*/
|
||||
private async init() {
|
||||
console.log('🚀 Initializing AppData...');
|
||||
|
||||
|
||||
// Handle backward compatibility for typo
|
||||
const isEphemeral = this.options.ephemeral ?? this.options.ephermal ?? false;
|
||||
if (this.options.ephermal && !this.options.ephemeral) {
|
||||
console.warn('⚠️ Option "ephermal" is deprecated, use "ephemeral" instead.');
|
||||
}
|
||||
|
||||
|
||||
if (this.options.dirPath) {
|
||||
console.log(` 📁 Using custom directory: ${this.options.dirPath}`);
|
||||
} else if (isEphemeral) {
|
||||
@@ -456,17 +456,17 @@ export class AppData<T = any> {
|
||||
console.log(`📦 Processing envMapping for AppData...`);
|
||||
const totalKeys = Object.keys(this.options.envMapping).length;
|
||||
let processedCount = 0;
|
||||
|
||||
|
||||
// Process each top-level key in envMapping
|
||||
for (const key in this.options.envMapping) {
|
||||
try {
|
||||
const mappingSpec = this.options.envMapping[key];
|
||||
const specType = mappingSpec === null ? 'null' :
|
||||
typeof mappingSpec === 'string' ? mappingSpec :
|
||||
typeof mappingSpec === 'object' ? 'nested object' :
|
||||
const specType = mappingSpec === null ? 'null' :
|
||||
typeof mappingSpec === 'string' ? mappingSpec :
|
||||
typeof mappingSpec === 'object' ? 'nested object' :
|
||||
typeof mappingSpec;
|
||||
console.log(` → Processing key "${key}" with spec: ${specType}`);
|
||||
|
||||
|
||||
const evaluated = await evaluateMappingValue(mappingSpec);
|
||||
// Important: Don't skip false, 0, empty string, or null values!
|
||||
// Only skip if explicitly undefined
|
||||
@@ -474,10 +474,10 @@ export class AppData<T = any> {
|
||||
await this.kvStore.writeKey(key as keyof T, evaluated);
|
||||
processedCount++;
|
||||
const valueType = evaluated === null ? 'null' :
|
||||
Array.isArray(evaluated) ? 'array' :
|
||||
Array.isArray(evaluated) ? 'array' :
|
||||
typeof evaluated;
|
||||
const valuePreview = evaluated === null ? 'null' :
|
||||
typeof evaluated === 'object' ?
|
||||
typeof evaluated === 'object' ?
|
||||
(Array.isArray(evaluated) ? `[${evaluated.length} items]` : `{${Object.keys(evaluated).length} keys}`) :
|
||||
redactSensitiveValue(key, evaluated);
|
||||
console.log(` ✅ Successfully processed key "${key}" = ${valuePreview} (type: ${valueType})`);
|
||||
@@ -488,7 +488,7 @@ export class AppData<T = any> {
|
||||
console.error(` ❌ Failed to evaluate envMapping for key "${key}":`, err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log(`📊 EnvMapping complete: ${processedCount}/${totalKeys} keys successfully processed`);
|
||||
}
|
||||
|
||||
@@ -496,18 +496,18 @@ export class AppData<T = any> {
|
||||
if (this.options.overwriteObject) {
|
||||
const overwriteKeys = Object.keys(this.options.overwriteObject);
|
||||
console.log(`🔄 Applying overwriteObject with ${overwriteKeys.length} key(s)...`);
|
||||
|
||||
|
||||
for (const key of overwriteKeys) {
|
||||
const value = this.options.overwriteObject[key];
|
||||
const valueType = Array.isArray(value) ? 'array' : typeof value;
|
||||
console.log(` 🔧 Overwriting key "${key}" with ${valueType} value`);
|
||||
|
||||
|
||||
await this.kvStore.writeKey(
|
||||
key as keyof T,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
console.log(`✅ OverwriteObject complete: ${overwriteKeys.length} key(s) overwritten`);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as plugins from './npmextra.plugins.js';
|
||||
import * as paths from './npmextra.paths.js';
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
|
||||
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 './npmextra.classes.keyvaluestore.js';
|
||||
export * from './npmextra.classes.npmextra.js';
|
||||
export * from './classes.appdata.js';
|
||||
export * from './classes.keyvaluestore.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
|
||||
export let cwd = process.cwd();
|
||||
@@ -16,7 +16,7 @@ export let home = plugins.smartpath.get.home();
|
||||
/**
|
||||
* keyValue base path
|
||||
*/
|
||||
export let kvUserHomeDirBase = plugins.path.join(home, '.npmextra/kv');
|
||||
export let kvUserHomeDirBase = plugins.path.join(home, '.smartconfig/kv');
|
||||
|
||||
// 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