fix(storage): migrate filesystem operations to smartfs and tighten TypeScript initialization checks

This commit is contained in:
2026-04-30 10:12:32 +00:00
parent 1c4ba7a7d0
commit 6084ffb0ce
9 changed files with 2827 additions and 3656 deletions
+8
View File
@@ -1,5 +1,13 @@
# Changelog # Changelog
## 2026-04-30 - 6.1.1 - fix(storage)
migrate filesystem operations to smartfs and tighten TypeScript initialization checks
- replace @push.rocks/smartfile usage with smartfs and native node:fs across app data, key value store, and smartconfig handling
- improve JSON file read/write and deletion logic for persistent key value storage
- enable noImplicitAny in TypeScript config and add explicit initialization/type assertions where required
- update build tooling and runtime dependencies to match the new filesystem implementation
## 2026-03-24 - 6.1.0 - feat(docs) ## 2026-03-24 - 6.1.0 - feat(docs)
refresh project documentation and migrate smartconfig tool configuration keys refresh project documentation and migrate smartconfig tool configuration keys
+13 -14
View File
@@ -7,7 +7,7 @@
"typings": "dist_ts/index.d.ts", "typings": "dist_ts/index.d.ts",
"scripts": { "scripts": {
"test": "(tstest test/ --verbose --logfile --timeout 20)", "test": "(tstest test/ --verbose --logfile --timeout 20)",
"build": "(tsbuild --web --allowimplicitany)", "build": "(tsbuild --web)",
"buildDocs": "tsdoc" "buildDocs": "tsdoc"
}, },
"repository": { "repository": {
@@ -22,20 +22,21 @@
"homepage": "https://code.foss.global/push.rocks/smartconfig#readme", "homepage": "https://code.foss.global/push.rocks/smartconfig#readme",
"dependencies": { "dependencies": {
"@push.rocks/qenv": "^6.1.3", "@push.rocks/qenv": "^6.1.3",
"@push.rocks/smartfile": "^11.2.5", "@push.rocks/smartfs": "^1.5.1",
"@push.rocks/smartjson": "^5.0.20", "@push.rocks/smartjson": "^6.0.1",
"@push.rocks/smartlog": "^3.1.8", "@push.rocks/smartlog": "^3.2.2",
"@push.rocks/smartpath": "^6.0.0", "@push.rocks/smartpath": "^6.0.0",
"@push.rocks/smartpromise": "^4.2.3", "@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartrx": "^3.0.10", "@push.rocks/smartrx": "^3.0.10",
"@push.rocks/taskbuffer": "^3.1.7", "@push.rocks/taskbuffer": "^8.0.2",
"@tsclass/tsclass": "^9.2.0" "@tsclass/tsclass": "^9.5.1"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^2.6.4", "@git.zone/tsbuild": "^4.4.0",
"@git.zone/tsrun": "^1.3.3", "@git.zone/tsrun": "^2.0.2",
"@git.zone/tstest": "^2.3.2", "@git.zone/tstest": "^3.6.3",
"@types/node": "^22" "@types/lodash.clonedeep": "^4.5.9",
"@types/node": "^25.6.0"
}, },
"files": [ "files": [
"ts/**/*", "ts/**/*",
@@ -47,6 +48,7 @@
"assets/**/*", "assets/**/*",
"cli.js", "cli.js",
".smartconfig.json", ".smartconfig.json",
"license",
"readme.md" "readme.md"
], ],
"browserslist": [ "browserslist": [
@@ -70,8 +72,5 @@
"workflow improvement", "workflow improvement",
"persistent storage" "persistent storage"
], ],
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977", "packageManager": "pnpm@10.28.2"
"pnpm": {
"overrides": {}
}
} }
+2767 -3611
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/smartconfig', name: '@push.rocks/smartconfig',
version: '6.1.0', version: '6.1.1',
description: 'A comprehensive configuration management library providing key-value storage, environment variable mapping, and tool configuration.' description: 'A comprehensive configuration management library providing key-value storage, environment variable mapping, and tool configuration.'
} }
+8 -7
View File
@@ -403,7 +403,7 @@ export class AppData<T = any> {
// instance // instance
public readyDeferred = plugins.smartpromise.defer<void>(); public readyDeferred = plugins.smartpromise.defer<void>();
public options: IAppDataOptions<T>; public options: IAppDataOptions<T>;
private kvStore: KeyValueStore<T>; private kvStore!: KeyValueStore<T>;
constructor(optionsArg: IAppDataOptions<T> = {}) { constructor(optionsArg: IAppDataOptions<T> = {}) {
this.options = optionsArg; this.options = optionsArg;
@@ -430,8 +430,8 @@ export class AppData<T = any> {
const appDataDir = '/app/data'; const appDataDir = '/app/data';
const dataDir = '/data'; const dataDir = '/data';
const nogitAppData = '.nogit/appdata'; const nogitAppData = '.nogit/appdata';
const appDataExists = plugins.smartfile.fs.isDirectory(appDataDir); const appDataExists = plugins.nodeFs.existsSync(appDataDir) && plugins.nodeFs.statSync(appDataDir).isDirectory();
const dataExists = plugins.smartfile.fs.isDirectory(dataDir); const dataExists = plugins.nodeFs.existsSync(dataDir) && plugins.nodeFs.statSync(dataDir).isDirectory();
if (appDataExists) { if (appDataExists) {
this.options.dirPath = appDataDir; this.options.dirPath = appDataDir;
console.log(` 📁 Auto-selected container directory: ${appDataDir}`); console.log(` 📁 Auto-selected container directory: ${appDataDir}`);
@@ -439,7 +439,7 @@ export class AppData<T = any> {
this.options.dirPath = dataDir; this.options.dirPath = dataDir;
console.log(` 📁 Auto-selected data directory: ${dataDir}`); console.log(` 📁 Auto-selected data directory: ${dataDir}`);
} else { } else {
await plugins.smartfile.fs.ensureDir(nogitAppData); await plugins.smartFs.directory(nogitAppData).create();
this.options.dirPath = nogitAppData; this.options.dirPath = nogitAppData;
console.log(` 📁 Auto-selected local directory: ${nogitAppData}`); console.log(` 📁 Auto-selected local directory: ${nogitAppData}`);
} }
@@ -494,17 +494,18 @@ export class AppData<T = any> {
// Apply overwrite object after env mapping // Apply overwrite object after env mapping
if (this.options.overwriteObject) { if (this.options.overwriteObject) {
const overwriteKeys = Object.keys(this.options.overwriteObject); const overwriteObject = this.options.overwriteObject as Record<string, unknown>;
const overwriteKeys = Object.keys(overwriteObject);
console.log(`🔄 Applying overwriteObject with ${overwriteKeys.length} key(s)...`); console.log(`🔄 Applying overwriteObject with ${overwriteKeys.length} key(s)...`);
for (const key of overwriteKeys) { for (const key of overwriteKeys) {
const value = this.options.overwriteObject[key]; const value = overwriteObject[key];
const valueType = Array.isArray(value) ? 'array' : typeof value; const valueType = Array.isArray(value) ? 'array' : typeof value;
console.log(` 🔧 Overwriting key "${key}" with ${valueType} value`); console.log(` 🔧 Overwriting key "${key}" with ${valueType} value`);
await this.kvStore.writeKey( await this.kvStore.writeKey(
key as keyof T, key as keyof T,
value, value as T[keyof T],
); );
} }
+17 -12
View File
@@ -26,21 +26,18 @@ export class KeyValueStore<T = any> {
name: 'syncTask', name: 'syncTask',
buffered: true, buffered: true,
bufferMax: 1, bufferMax: 1,
execDelay: 0,
taskFunction: async () => { taskFunction: async () => {
if (this.type !== 'ephemeral') { if (this.type !== 'ephemeral') {
const storedJson = await plugins.smartFs.file(this.filePath!).encoding('utf8').read() as string;
this.dataObject = { this.dataObject = {
...plugins.smartfile.fs.toObjectSync(this.filePath), ...plugins.smartjson.parse(storedJson || '{}'),
...this.dataObject, ...this.dataObject,
}; };
for (const key of Object.keys(this.deletedObject) as Array<keyof T>) { for (const key of Object.keys(this.deletedObject) as Array<keyof T>) {
delete this.dataObject[key]; delete this.dataObject[key];
} }
this.deletedObject = {}; this.deletedObject = {};
await plugins.smartfile.memory.toFs( await plugins.smartFs.file(this.filePath!).encoding('utf8').write(plugins.smartjson.stringifyPretty(this.dataObject));
plugins.smartjson.stringifyPretty(this.dataObject),
this.filePath,
);
} }
const newStateString = plugins.smartjson.stringify(this.dataObject); const newStateString = plugins.smartjson.stringify(this.dataObject);
@@ -67,13 +64,16 @@ export class KeyValueStore<T = any> {
paths.cwd, paths.cwd,
); );
this.filePath = absolutePath; this.filePath = absolutePath;
if (plugins.smartfile.fs.isDirectorySync(this.filePath)) { if (plugins.nodeFs.existsSync(this.filePath) && plugins.nodeFs.statSync(this.filePath).isDirectory()) {
this.filePath = plugins.path.join( this.filePath = plugins.path.join(
this.filePath, this.filePath,
this.identity + '.json', this.identity + '.json',
); );
} }
plugins.smartfile.fs.ensureFileSync(this.filePath, '{}'); plugins.nodeFs.mkdirSync(plugins.path.dirname(this.filePath), { recursive: true });
if (!plugins.nodeFs.existsSync(this.filePath)) {
plugins.nodeFs.writeFileSync(this.filePath, '{}');
}
return; return;
} }
@@ -84,8 +84,10 @@ export class KeyValueStore<T = any> {
throw new Error('kv type not supported'); throw new Error('kv type not supported');
} }
this.filePath = plugins.path.join(baseDir, this.identity + '.json'); this.filePath = plugins.path.join(baseDir, this.identity + '.json');
plugins.smartfile.fs.ensureDirSync(baseDir); plugins.nodeFs.mkdirSync(baseDir, { recursive: true });
plugins.smartfile.fs.ensureFileSync(this.filePath, '{}'); if (!plugins.nodeFs.existsSync(this.filePath)) {
plugins.nodeFs.writeFileSync(this.filePath, '{}');
}
}; };
// if no custom path is provided, try to store at home directory // if no custom path is provided, try to store at home directory
@@ -162,8 +164,11 @@ export class KeyValueStore<T = any> {
*/ */
public async wipe(): Promise<void> { public async wipe(): Promise<void> {
this.dataObject = {}; this.dataObject = {};
if (this.type !== 'ephemeral') { if (this.type !== 'ephemeral' && this.filePath) {
await plugins.smartfile.fs.remove(this.filePath); const file = plugins.smartFs.file(this.filePath);
if (await file.exists()) {
await file.delete();
}
} }
} }
+5 -7
View File
@@ -6,8 +6,8 @@ import * as paths from './paths.js';
*/ */
export class Smartconfig { export class Smartconfig {
cwd: string; cwd: string;
lookupPath: string; lookupPath!: string;
smartconfigJsonExists: boolean; smartconfigJsonExists = false;
smartconfigJsonData: any; smartconfigJsonData: any;
/** /**
@@ -48,9 +48,7 @@ export class Smartconfig {
* checks if the JSON exists * checks if the JSON exists
*/ */
private checkSmartconfigJsonExists() { private checkSmartconfigJsonExists() {
this.smartconfigJsonExists = plugins.smartfile.fs.fileExistsSync( this.smartconfigJsonExists = plugins.nodeFs.existsSync(this.lookupPath);
this.lookupPath,
);
} }
/** /**
@@ -69,8 +67,8 @@ export class Smartconfig {
*/ */
private checkSmartconfigJsonData() { private checkSmartconfigJsonData() {
if (this.smartconfigJsonExists) { if (this.smartconfigJsonExists) {
this.smartconfigJsonData = plugins.smartfile.fs.toObjectSync( this.smartconfigJsonData = plugins.smartjson.parse(
this.lookupPath, plugins.nodeFs.readFileSync(this.lookupPath, 'utf8'),
); );
} else { } else {
this.smartconfigJsonData = {}; this.smartconfigJsonData = {};
+6 -2
View File
@@ -4,19 +4,23 @@ export { 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';
import * as nodeFs from 'node:fs';
import * as path from 'path'; import * as path from 'path';
import * as smartfile from '@push.rocks/smartfile'; import * as smartfs from '@push.rocks/smartfs';
import * as smartjson from '@push.rocks/smartjson'; import * as smartjson from '@push.rocks/smartjson';
import * as smartpath from '@push.rocks/smartpath'; import * as smartpath from '@push.rocks/smartpath';
import * as smartpromise from '@push.rocks/smartpromise'; import * as smartpromise from '@push.rocks/smartpromise';
import * as 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 const smartFs = new smartfs.SmartFs(new smartfs.SmartFsProviderNode());
export { export {
qenv, qenv,
smartlog, smartlog,
nodeFs,
path, path,
smartfile, smartfs,
smartjson, smartjson,
smartpath, smartpath,
smartpromise, smartpromise,
+2 -2
View File
@@ -6,10 +6,10 @@
"target": "ES2022", "target": "ES2022",
"module": "NodeNext", "module": "NodeNext",
"moduleResolution": "NodeNext", "moduleResolution": "NodeNext",
"noImplicitAny": true,
"esModuleInterop": true, "esModuleInterop": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"baseUrl": ".", "types": ["node"]
"paths": {}
}, },
"exclude": ["dist_*/**/*.d.ts"] "exclude": ["dist_*/**/*.d.ts"]
} }