kist 0.1.30 → 0.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/js/actions/CoreActions.js +4 -0
- package/js/actions/RunScriptAction/RunScriptAction.d.ts +22 -0
- package/js/actions/RunScriptAction/RunScriptAction.js +77 -0
- package/js/actions/RunScriptAction/index.d.ts +2 -0
- package/js/actions/RunScriptAction/index.js +8 -0
- package/js/actions/TemplateRenderAction/TemplateRenderAction.d.ts +25 -0
- package/js/actions/TemplateRenderAction/TemplateRenderAction.js +91 -0
- package/js/actions/TemplateRenderAction/index.d.ts +2 -0
- package/js/actions/TemplateRenderAction/index.js +8 -0
- package/js/actions/TemplateRenderAction/nunjucks.config.d.ts +13 -0
- package/js/actions/TemplateRenderAction/nunjucks.config.js +23 -0
- package/js/core/config/ConfigStore.d.ts +11 -27
- package/js/core/config/ConfigStore.js +35 -67
- package/package.json +1 -1
- package/ts/actions/CoreActions.ts +4 -0
- package/ts/actions/RunScriptAction/RunScriptAction.ts +71 -0
- package/ts/actions/RunScriptAction/index.ts +11 -0
- package/ts/actions/TemplateRenderAction/TemplateRenderAction.ts +98 -0
- package/ts/actions/TemplateRenderAction/index.ts +11 -0
- package/ts/actions/TemplateRenderAction/nunjucks.config.ts +32 -0
- package/ts/core/config/ConfigStore copy.ts +201 -0
- package/ts/core/config/ConfigStore.ts +41 -85
|
@@ -13,11 +13,13 @@ const FileRenameAction_1 = require("../actions/FileRenameAction");
|
|
|
13
13
|
const JavaScriptMinifyAction_1 = require("../actions/JavaScriptMinifyAction");
|
|
14
14
|
const LintAction_1 = require("../actions/LintAction");
|
|
15
15
|
const PackageManagerAction_1 = require("../actions/PackageManagerAction");
|
|
16
|
+
const RunScriptAction_1 = require("../actions/RunScriptAction");
|
|
16
17
|
const StyleProcessingAction_1 = require("../actions/StyleProcessingAction");
|
|
17
18
|
const SvgPackagerAction_1 = require("../actions/SvgPackagerAction");
|
|
18
19
|
const SvgReaderAction_1 = require("../actions/SvgReaderAction");
|
|
19
20
|
const SvgSpriteAction_1 = require("../actions/SvgSpriteAction");
|
|
20
21
|
const SvgToPngAction_1 = require("../actions/SvgToPngAction");
|
|
22
|
+
const TemplateRenderAction_1 = require("../actions/TemplateRenderAction");
|
|
21
23
|
const TypeScriptCompilerAction_1 = require("../actions/TypeScriptCompilerAction");
|
|
22
24
|
const VersionWriteAction_1 = require("../actions/VersionWriteAction");
|
|
23
25
|
// ============================================================================
|
|
@@ -37,11 +39,13 @@ exports.coreActions = {
|
|
|
37
39
|
[new JavaScriptMinifyAction_1.JavaScriptMinifyAction().name]: JavaScriptMinifyAction_1.JavaScriptMinifyAction,
|
|
38
40
|
[new LintAction_1.LintAction().name]: LintAction_1.LintAction,
|
|
39
41
|
[new PackageManagerAction_1.PackageManagerAction().name]: PackageManagerAction_1.PackageManagerAction,
|
|
42
|
+
[new RunScriptAction_1.RunScriptAction().name]: RunScriptAction_1.RunScriptAction,
|
|
40
43
|
[new StyleProcessingAction_1.StyleProcessingAction().name]: StyleProcessingAction_1.StyleProcessingAction,
|
|
41
44
|
[new SvgPackagerAction_1.SvgPackagerAction().name]: SvgPackagerAction_1.SvgPackagerAction,
|
|
42
45
|
[new SvgReaderAction_1.SvgReaderAction().name]: SvgReaderAction_1.SvgReaderAction,
|
|
43
46
|
[new SvgSpriteAction_1.SvgSpriteAction().name]: SvgSpriteAction_1.SvgSpriteAction,
|
|
44
47
|
[new SvgToPngAction_1.SvgToPngAction().name]: SvgToPngAction_1.SvgToPngAction,
|
|
48
|
+
[new TemplateRenderAction_1.TemplateRenderAction().name]: TemplateRenderAction_1.TemplateRenderAction,
|
|
45
49
|
[new TypeScriptCompilerAction_1.TypeScriptCompilerAction().name]: TypeScriptCompilerAction_1.TypeScriptCompilerAction,
|
|
46
50
|
[new VersionWriteAction_1.VersionWriteAction().name]: VersionWriteAction_1.VersionWriteAction,
|
|
47
51
|
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Action } from "../../core/pipeline/Action";
|
|
2
|
+
import { ActionOptionsType } from "../../types/ActionOptionsType";
|
|
3
|
+
/**
|
|
4
|
+
* RunScriptAction executes an external JavaScript file as part of a KIST pipeline.
|
|
5
|
+
* The script is executed in a separate process to avoid blocking the main application.
|
|
6
|
+
*/
|
|
7
|
+
export declare class RunScriptAction extends Action {
|
|
8
|
+
/**
|
|
9
|
+
* Executes the external script file.
|
|
10
|
+
*
|
|
11
|
+
* @param options - The options specifying the script file to execute.
|
|
12
|
+
* @returns A Promise that resolves when the script execution completes.
|
|
13
|
+
* @throws {Error} If the script execution fails.
|
|
14
|
+
*/
|
|
15
|
+
execute(options: ActionOptionsType): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Provides a description of the action.
|
|
18
|
+
*
|
|
19
|
+
* @returns A string description of the action.
|
|
20
|
+
*/
|
|
21
|
+
describe(): string;
|
|
22
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// Imports
|
|
4
|
+
// ============================================================================
|
|
5
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
6
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
7
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
8
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
9
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
10
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
11
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
+
};
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.RunScriptAction = void 0;
|
|
19
|
+
const child_process_1 = require("child_process");
|
|
20
|
+
const path_1 = __importDefault(require("path"));
|
|
21
|
+
const util_1 = __importDefault(require("util"));
|
|
22
|
+
const Action_1 = require("../../core/pipeline/Action");
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Constants
|
|
25
|
+
// ============================================================================
|
|
26
|
+
const execFileAsync = util_1.default.promisify(child_process_1.execFile);
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Classes
|
|
29
|
+
// ============================================================================
|
|
30
|
+
/**
|
|
31
|
+
* RunScriptAction executes an external JavaScript file as part of a KIST pipeline.
|
|
32
|
+
* The script is executed in a separate process to avoid blocking the main application.
|
|
33
|
+
*/
|
|
34
|
+
class RunScriptAction extends Action_1.Action {
|
|
35
|
+
/**
|
|
36
|
+
* Executes the external script file.
|
|
37
|
+
*
|
|
38
|
+
* @param options - The options specifying the script file to execute.
|
|
39
|
+
* @returns A Promise that resolves when the script execution completes.
|
|
40
|
+
* @throws {Error} If the script execution fails.
|
|
41
|
+
*/
|
|
42
|
+
execute(options) {
|
|
43
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
44
|
+
const { scriptPath, args = [] } = options;
|
|
45
|
+
if (!scriptPath) {
|
|
46
|
+
throw new Error("Invalid options: 'scriptPath' is required.");
|
|
47
|
+
}
|
|
48
|
+
const resolvedScriptPath = path_1.default.resolve(scriptPath);
|
|
49
|
+
this.logInfo(`Executing external script: ${resolvedScriptPath}...`);
|
|
50
|
+
try {
|
|
51
|
+
const { stdout, stderr } = yield execFileAsync("node", [
|
|
52
|
+
resolvedScriptPath,
|
|
53
|
+
...args,
|
|
54
|
+
]);
|
|
55
|
+
if (stderr) {
|
|
56
|
+
this.logError(`Script execution failed: ${stderr}`);
|
|
57
|
+
throw new Error(stderr);
|
|
58
|
+
}
|
|
59
|
+
this.logInfo(stdout);
|
|
60
|
+
this.logInfo("Script executed successfully.");
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
this.logError("Error occurred while executing the script.", error);
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Provides a description of the action.
|
|
70
|
+
*
|
|
71
|
+
* @returns A string description of the action.
|
|
72
|
+
*/
|
|
73
|
+
describe() {
|
|
74
|
+
return "Executes an external JavaScript file as part of the KIST pipeline.";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.RunScriptAction = RunScriptAction;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// Import
|
|
4
|
+
// ============================================================================
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RunScriptAction = void 0;
|
|
7
|
+
const RunScriptAction_1 = require("./RunScriptAction");
|
|
8
|
+
Object.defineProperty(exports, "RunScriptAction", { enumerable: true, get: function () { return RunScriptAction_1.RunScriptAction; } });
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Action } from "../../core/pipeline/Action";
|
|
2
|
+
import { ActionOptionsType } from "../../types/ActionOptionsType";
|
|
3
|
+
/**
|
|
4
|
+
* TemplateRenderAction is responsible for rendering and writing multiple files
|
|
5
|
+
* from Nunjucks templates in a single action. It allows batch generation of
|
|
6
|
+
* output files based on a shared template context.
|
|
7
|
+
*/
|
|
8
|
+
export declare class TemplateRenderAction extends Action {
|
|
9
|
+
/**
|
|
10
|
+
* Executes the template rendering process for multiple templates.
|
|
11
|
+
*
|
|
12
|
+
* @param options - The options specifying the template directory,
|
|
13
|
+
* template-output mapping, and rendering context.
|
|
14
|
+
* @returns A Promise that resolves when all templates have been rendered.
|
|
15
|
+
* @throws {Error} Throws an error if template processing fails.
|
|
16
|
+
*/
|
|
17
|
+
execute(options: ActionOptionsType): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Provides a description of the action.
|
|
20
|
+
*
|
|
21
|
+
* @returns A string description of the action.
|
|
22
|
+
*/
|
|
23
|
+
describe(): string;
|
|
24
|
+
}
|
|
25
|
+
export default TemplateRenderAction;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// Imports
|
|
4
|
+
// ============================================================================
|
|
5
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
6
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
7
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
8
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
9
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
10
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
11
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
+
};
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.TemplateRenderAction = void 0;
|
|
19
|
+
const promises_1 = require("fs/promises");
|
|
20
|
+
const nunjucks_1 = __importDefault(require("nunjucks"));
|
|
21
|
+
const path_1 = __importDefault(require("path"));
|
|
22
|
+
const Action_1 = require("../../core/pipeline/Action");
|
|
23
|
+
const nunjucks_config_js_1 = __importDefault(require("./nunjucks.config.js"));
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Classes
|
|
26
|
+
// ============================================================================
|
|
27
|
+
/**
|
|
28
|
+
* TemplateRenderAction is responsible for rendering and writing multiple files
|
|
29
|
+
* from Nunjucks templates in a single action. It allows batch generation of
|
|
30
|
+
* output files based on a shared template context.
|
|
31
|
+
*/
|
|
32
|
+
class TemplateRenderAction extends Action_1.Action {
|
|
33
|
+
/**
|
|
34
|
+
* Executes the template rendering process for multiple templates.
|
|
35
|
+
*
|
|
36
|
+
* @param options - The options specifying the template directory,
|
|
37
|
+
* template-output mapping, and rendering context.
|
|
38
|
+
* @returns A Promise that resolves when all templates have been rendered.
|
|
39
|
+
* @throws {Error} Throws an error if template processing fails.
|
|
40
|
+
*/
|
|
41
|
+
execute(options) {
|
|
42
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
43
|
+
const { templatesDir = "./templates", templates = [], context = {}, customConfig = {}, } = options;
|
|
44
|
+
if (!Array.isArray(templates) || templates.length === 0) {
|
|
45
|
+
throw new Error("Invalid options: 'templates' must be an array containing template-output pairs.");
|
|
46
|
+
}
|
|
47
|
+
this.logInfo(`Rendering ${templates.length} templates...`);
|
|
48
|
+
try {
|
|
49
|
+
// Merge Nunjucks configurations
|
|
50
|
+
const config = Object.assign(Object.assign({}, nunjucks_config_js_1.default), customConfig);
|
|
51
|
+
nunjucks_1.default.configure(templatesDir, config);
|
|
52
|
+
// Render each template and save to its corresponding output path
|
|
53
|
+
for (const { template, outputFile } of templates) {
|
|
54
|
+
if (!template || !outputFile) {
|
|
55
|
+
throw new Error(`Invalid template entry: ${JSON.stringify({
|
|
56
|
+
template,
|
|
57
|
+
outputFile,
|
|
58
|
+
})}`);
|
|
59
|
+
}
|
|
60
|
+
this.logInfo(`Rendering: ${template} → ${outputFile}`);
|
|
61
|
+
// Render template content
|
|
62
|
+
const content = nunjucks_1.default.render(template, context);
|
|
63
|
+
// Ensure the output directory exists
|
|
64
|
+
const dir = path_1.default.dirname(outputFile);
|
|
65
|
+
yield (0, promises_1.mkdir)(dir, { recursive: true });
|
|
66
|
+
// Write the rendered template to file
|
|
67
|
+
yield (0, promises_1.writeFile)(outputFile, content, "utf-8");
|
|
68
|
+
this.logInfo(`✓ Successfully rendered: ${outputFile}`);
|
|
69
|
+
}
|
|
70
|
+
this.logInfo("All templates rendered successfully.");
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
this.logError("Error rendering templates.", error);
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Provides a description of the action.
|
|
80
|
+
*
|
|
81
|
+
* @returns A string description of the action.
|
|
82
|
+
*/
|
|
83
|
+
describe() {
|
|
84
|
+
return "Renders multiple Nunjucks templates into files using a shared context.";
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.TemplateRenderAction = TemplateRenderAction;
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// Export
|
|
90
|
+
// ============================================================================
|
|
91
|
+
exports.default = TemplateRenderAction;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// Import
|
|
4
|
+
// ============================================================================
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TemplateRenderAction = void 0;
|
|
7
|
+
const TemplateRenderAction_1 = require("./TemplateRenderAction");
|
|
8
|
+
Object.defineProperty(exports, "TemplateRenderAction", { enumerable: true, get: function () { return TemplateRenderAction_1.TemplateRenderAction; } });
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration options for Nunjucks to ensure safe and efficient template rendering.
|
|
3
|
+
* This setup is ideal for both development and production environments, providing a balance
|
|
4
|
+
* between performance optimizations and security best practices.
|
|
5
|
+
*/
|
|
6
|
+
declare const nunjucksConfig: {
|
|
7
|
+
autoescape: boolean;
|
|
8
|
+
throwOnUndefined: boolean;
|
|
9
|
+
trimBlocks: boolean;
|
|
10
|
+
lstripBlocks: boolean;
|
|
11
|
+
noCache: boolean;
|
|
12
|
+
};
|
|
13
|
+
export default nunjucksConfig;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This configuration is tailored to a typical web application setup. Adjust the `noCache` option
|
|
3
|
+
// according to your caching strategy for production environments to optimize performance.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// Constants
|
|
7
|
+
// ============================================================================
|
|
8
|
+
/**
|
|
9
|
+
* Configuration options for Nunjucks to ensure safe and efficient template rendering.
|
|
10
|
+
* This setup is ideal for both development and production environments, providing a balance
|
|
11
|
+
* between performance optimizations and security best practices.
|
|
12
|
+
*/
|
|
13
|
+
const nunjucksConfig = {
|
|
14
|
+
autoescape: true, // Controls if output with dangerous characters are escaped automatically
|
|
15
|
+
throwOnUndefined: false, // Throw errors when outputting a null/undefined value
|
|
16
|
+
trimBlocks: true, // Automatically remove trailing newlines from a block/tag
|
|
17
|
+
lstripBlocks: true, // Automatically remove leading whitespace from a block/tag
|
|
18
|
+
noCache: true,
|
|
19
|
+
};
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Export
|
|
22
|
+
// ============================================================================
|
|
23
|
+
exports.default = nunjucksConfig;
|
|
@@ -1,68 +1,52 @@
|
|
|
1
1
|
import { ConfigInterface } from "../../interface/ConfigInterface";
|
|
2
2
|
import { AbstractProcess } from "../abstract/AbstractProcess";
|
|
3
3
|
/**
|
|
4
|
-
* ConfigStore is a singleton that loads and manages the application's
|
|
5
|
-
* configuration.
|
|
4
|
+
* ConfigStore is a singleton that loads and manages the application's configuration.
|
|
6
5
|
* It prioritizes CLI arguments over configuration file values.
|
|
7
6
|
*/
|
|
8
7
|
export declare class ConfigStore extends AbstractProcess {
|
|
9
|
-
/**
|
|
10
|
-
* Singleton instance of the ConfigStore.
|
|
11
|
-
*/
|
|
12
8
|
private static instance;
|
|
13
|
-
/**
|
|
14
|
-
* The current configuration stored in the ConfigStore.
|
|
15
|
-
*/
|
|
16
9
|
private config;
|
|
17
|
-
/**
|
|
18
|
-
* Private constructor to enforce the singleton pattern.
|
|
19
|
-
* Initializes the store with the default configuration.
|
|
20
|
-
*/
|
|
21
10
|
private constructor();
|
|
22
11
|
/**
|
|
23
|
-
* Retrieves the singleton instance of ConfigStore
|
|
24
|
-
* necessary.
|
|
12
|
+
* Retrieves the singleton instance of ConfigStore.
|
|
25
13
|
* @returns The singleton instance of ConfigStore.
|
|
26
14
|
*/
|
|
27
15
|
static getInstance(): ConfigStore;
|
|
28
16
|
/**
|
|
29
|
-
* Retrieves a value from the configuration
|
|
30
|
-
* Supports nested keys using dot notation (e.g., "options.logLevel").
|
|
17
|
+
* Retrieves a value from the configuration using dot notation.
|
|
31
18
|
*
|
|
32
19
|
* @param key - The key of the configuration to retrieve.
|
|
33
20
|
* @returns The configuration value or undefined if not found.
|
|
34
21
|
*/
|
|
35
22
|
get<T>(key: string): T | undefined;
|
|
36
23
|
/**
|
|
37
|
-
* Sets a value in the configuration
|
|
38
|
-
* Supports nested keys using dot notation (e.g., "options.logLevel").
|
|
24
|
+
* Sets a value in the configuration using dot notation.
|
|
39
25
|
*
|
|
40
26
|
* @param key - The key of the configuration to set.
|
|
41
27
|
* @param value - The value to set.
|
|
42
28
|
*/
|
|
43
29
|
set(key: string, value: unknown): void;
|
|
44
30
|
/**
|
|
45
|
-
* Merges the provided configuration into the existing configuration.
|
|
46
|
-
* Uses a deep merge strategy to combine objects and overwrite primitives.
|
|
31
|
+
* Merges the provided configuration into the existing configuration using deep merge.
|
|
47
32
|
*
|
|
48
33
|
* @param newConfig - The new configuration to merge.
|
|
49
34
|
*/
|
|
50
35
|
merge(newConfig: Partial<ConfigInterface>): void;
|
|
51
36
|
/**
|
|
52
|
-
* Retrieves the current configuration.
|
|
53
|
-
*
|
|
54
|
-
* @returns The current configuration object.
|
|
37
|
+
* Retrieves the current configuration object.
|
|
38
|
+
* @returns The current configuration.
|
|
55
39
|
*/
|
|
56
40
|
getConfig(): ConfigInterface;
|
|
57
41
|
/**
|
|
58
|
-
* Prints the current configuration to the console
|
|
42
|
+
* Prints the current configuration to the console.
|
|
59
43
|
*/
|
|
60
44
|
print(): void;
|
|
61
45
|
/**
|
|
62
|
-
* Deeply merges two objects.
|
|
46
|
+
* Deeply merges two objects, preventing prototype pollution.
|
|
63
47
|
*
|
|
64
|
-
* @param target - The target object
|
|
65
|
-
* @param source - The source object
|
|
48
|
+
* @param target - The target object.
|
|
49
|
+
* @param source - The source object.
|
|
66
50
|
* @returns The merged object.
|
|
67
51
|
*/
|
|
68
52
|
private deepMerge;
|
|
@@ -10,27 +10,18 @@ const defaultConfig_1 = require("./defaultConfig");
|
|
|
10
10
|
// Class
|
|
11
11
|
// ============================================================================
|
|
12
12
|
/**
|
|
13
|
-
* ConfigStore is a singleton that loads and manages the application's
|
|
14
|
-
* configuration.
|
|
13
|
+
* ConfigStore is a singleton that loads and manages the application's configuration.
|
|
15
14
|
* It prioritizes CLI arguments over configuration file values.
|
|
16
15
|
*/
|
|
17
16
|
class ConfigStore extends AbstractProcess_1.AbstractProcess {
|
|
18
|
-
// Constructor
|
|
19
|
-
// ========================================================================
|
|
20
|
-
/**
|
|
21
|
-
* Private constructor to enforce the singleton pattern.
|
|
22
|
-
* Initializes the store with the default configuration.
|
|
23
|
-
*/
|
|
17
|
+
// Constructor (Private to enforce Singleton Pattern)
|
|
24
18
|
constructor() {
|
|
25
19
|
super();
|
|
26
20
|
this.config = defaultConfig_1.defaultConfig;
|
|
27
21
|
this.logDebug("ConfigStore initialized with default configuration.");
|
|
28
22
|
}
|
|
29
|
-
// Static Methods
|
|
30
|
-
// ========================================================================
|
|
31
23
|
/**
|
|
32
|
-
* Retrieves the singleton instance of ConfigStore
|
|
33
|
-
* necessary.
|
|
24
|
+
* Retrieves the singleton instance of ConfigStore.
|
|
34
25
|
* @returns The singleton instance of ConfigStore.
|
|
35
26
|
*/
|
|
36
27
|
static getInstance() {
|
|
@@ -39,11 +30,8 @@ class ConfigStore extends AbstractProcess_1.AbstractProcess {
|
|
|
39
30
|
}
|
|
40
31
|
return ConfigStore.instance;
|
|
41
32
|
}
|
|
42
|
-
// Instance Methods
|
|
43
|
-
// ========================================================================
|
|
44
33
|
/**
|
|
45
|
-
* Retrieves a value from the configuration
|
|
46
|
-
* Supports nested keys using dot notation (e.g., "options.logLevel").
|
|
34
|
+
* Retrieves a value from the configuration using dot notation.
|
|
47
35
|
*
|
|
48
36
|
* @param key - The key of the configuration to retrieve.
|
|
49
37
|
* @returns The configuration value or undefined if not found.
|
|
@@ -57,12 +45,11 @@ class ConfigStore extends AbstractProcess_1.AbstractProcess {
|
|
|
57
45
|
}
|
|
58
46
|
current = current[k];
|
|
59
47
|
}
|
|
60
|
-
this.logDebug(`Configuration key "${key}" retrieved
|
|
48
|
+
this.logDebug(`Configuration key "${key}" retrieved.`);
|
|
61
49
|
return current;
|
|
62
50
|
}
|
|
63
51
|
/**
|
|
64
|
-
* Sets a value in the configuration
|
|
65
|
-
* Supports nested keys using dot notation (e.g., "options.logLevel").
|
|
52
|
+
* Sets a value in the configuration using dot notation.
|
|
66
53
|
*
|
|
67
54
|
* @param key - The key of the configuration to set.
|
|
68
55
|
* @param value - The value to set.
|
|
@@ -72,17 +59,28 @@ class ConfigStore extends AbstractProcess_1.AbstractProcess {
|
|
|
72
59
|
let current = this.config;
|
|
73
60
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
74
61
|
const k = keys[i];
|
|
75
|
-
|
|
76
|
-
|
|
62
|
+
// Prevent prototype pollution by blocking reserved keywords
|
|
63
|
+
if (["__proto__", "constructor", "prototype"].includes(k)) {
|
|
64
|
+
this.logWarn(`Attempted prototype pollution detected: "${k}"`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Ensure property exists and is an object
|
|
68
|
+
if (!Object.prototype.hasOwnProperty.call(current, k) || typeof current[k] !== "object") {
|
|
69
|
+
current[k] = Object.create(null); // Use a null prototype object
|
|
77
70
|
}
|
|
78
71
|
current = current[k];
|
|
79
72
|
}
|
|
80
|
-
|
|
73
|
+
const finalKey = keys[keys.length - 1];
|
|
74
|
+
// Prevent prototype pollution at the final assignment
|
|
75
|
+
if (["__proto__", "constructor", "prototype"].includes(finalKey)) {
|
|
76
|
+
this.logWarn(`Attempted prototype pollution detected: "${finalKey}"`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
current[finalKey] = value;
|
|
81
80
|
this.logDebug(`Set configuration key "${key}" to: ${JSON.stringify(value)}`);
|
|
82
81
|
}
|
|
83
82
|
/**
|
|
84
|
-
* Merges the provided configuration into the existing configuration.
|
|
85
|
-
* Uses a deep merge strategy to combine objects and overwrite primitives.
|
|
83
|
+
* Merges the provided configuration into the existing configuration using deep merge.
|
|
86
84
|
*
|
|
87
85
|
* @param newConfig - The new configuration to merge.
|
|
88
86
|
*/
|
|
@@ -91,64 +89,38 @@ class ConfigStore extends AbstractProcess_1.AbstractProcess {
|
|
|
91
89
|
this.logDebug("Configuration successfully merged.");
|
|
92
90
|
}
|
|
93
91
|
/**
|
|
94
|
-
* Retrieves the current configuration.
|
|
95
|
-
*
|
|
96
|
-
* @returns The current configuration object.
|
|
92
|
+
* Retrieves the current configuration object.
|
|
93
|
+
* @returns The current configuration.
|
|
97
94
|
*/
|
|
98
95
|
getConfig() {
|
|
99
96
|
return this.config;
|
|
100
97
|
}
|
|
101
98
|
/**
|
|
102
|
-
* Prints the current configuration to the console
|
|
99
|
+
* Prints the current configuration to the console.
|
|
103
100
|
*/
|
|
104
101
|
print() {
|
|
105
|
-
console.log("Current Configuration:");
|
|
106
|
-
console.log(JSON.stringify(this.config, null, 2));
|
|
102
|
+
console.log("Current Configuration:", JSON.stringify(this.config, null, 2));
|
|
107
103
|
}
|
|
108
104
|
/**
|
|
109
|
-
* Deeply merges two objects.
|
|
105
|
+
* Deeply merges two objects, preventing prototype pollution.
|
|
110
106
|
*
|
|
111
|
-
* @param target - The target object
|
|
112
|
-
* @param source - The source object
|
|
107
|
+
* @param target - The target object.
|
|
108
|
+
* @param source - The source object.
|
|
113
109
|
* @returns The merged object.
|
|
114
110
|
*/
|
|
115
|
-
// private deepMerge(target: any, source: any): any {
|
|
116
|
-
// if (typeof target !== "object" || target === null) {
|
|
117
|
-
// return source;
|
|
118
|
-
// }
|
|
119
|
-
// for (const key of Object.keys(source)) {
|
|
120
|
-
// if (
|
|
121
|
-
// source[key] &&
|
|
122
|
-
// typeof source[key] === "object" &&
|
|
123
|
-
// !Array.isArray(source[key])
|
|
124
|
-
// ) {
|
|
125
|
-
// if (!target[key] || typeof target[key] !== "object") {
|
|
126
|
-
// target[key] = {};
|
|
127
|
-
// }
|
|
128
|
-
// target[key] = this.deepMerge(target[key], source[key]);
|
|
129
|
-
// } else {
|
|
130
|
-
// target[key] = source[key];
|
|
131
|
-
// }
|
|
132
|
-
// }
|
|
133
|
-
// return target;
|
|
134
|
-
// }
|
|
135
111
|
deepMerge(target, source) {
|
|
136
112
|
if (typeof target !== "object" || target === null) {
|
|
137
113
|
return source;
|
|
138
114
|
}
|
|
139
115
|
for (const key of Object.keys(source)) {
|
|
140
116
|
// Prevent prototype pollution
|
|
141
|
-
if (
|
|
142
|
-
key
|
|
143
|
-
key === "prototype") {
|
|
144
|
-
this.logWarn(`Skipping potentially unsafe key: "${key}"`);
|
|
117
|
+
if (["__proto__", "constructor", "prototype"].includes(key)) {
|
|
118
|
+
this.logWarn(`Skipping unsafe key during merge: "${key}"`);
|
|
145
119
|
continue;
|
|
146
120
|
}
|
|
147
|
-
if (source[key] &&
|
|
148
|
-
typeof
|
|
149
|
-
|
|
150
|
-
if (!target[key] || typeof target[key] !== "object") {
|
|
151
|
-
target[key] = {};
|
|
121
|
+
if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
|
|
122
|
+
if (!Object.prototype.hasOwnProperty.call(target, key) || typeof target[key] !== "object") {
|
|
123
|
+
target[key] = Object.create(null);
|
|
152
124
|
}
|
|
153
125
|
target[key] = this.deepMerge(target[key], source[key]);
|
|
154
126
|
}
|
|
@@ -160,9 +132,5 @@ class ConfigStore extends AbstractProcess_1.AbstractProcess {
|
|
|
160
132
|
}
|
|
161
133
|
}
|
|
162
134
|
exports.ConfigStore = ConfigStore;
|
|
163
|
-
//
|
|
164
|
-
// ========================================================================
|
|
165
|
-
/**
|
|
166
|
-
* Singleton instance of the ConfigStore.
|
|
167
|
-
*/
|
|
135
|
+
// Singleton instance
|
|
168
136
|
ConfigStore.instance = null;
|
package/package.json
CHANGED
|
@@ -15,6 +15,7 @@ import { JavaScriptMinifyAction } from "../actions/JavaScriptMinifyAction";
|
|
|
15
15
|
import { LintAction } from "../actions/LintAction";
|
|
16
16
|
|
|
17
17
|
import { PackageManagerAction } from "../actions/PackageManagerAction";
|
|
18
|
+
import { RunScriptAction } from "../actions/RunScriptAction";
|
|
18
19
|
|
|
19
20
|
import { StyleProcessingAction } from "../actions/StyleProcessingAction";
|
|
20
21
|
|
|
@@ -23,6 +24,7 @@ import { SvgReaderAction } from "../actions/SvgReaderAction";
|
|
|
23
24
|
import { SvgSpriteAction } from "../actions/SvgSpriteAction";
|
|
24
25
|
import { SvgToPngAction } from "../actions/SvgToPngAction";
|
|
25
26
|
|
|
27
|
+
import { TemplateRenderAction } from "../actions/TemplateRenderAction";
|
|
26
28
|
import { TypeScriptCompilerAction } from "../actions/TypeScriptCompilerAction";
|
|
27
29
|
|
|
28
30
|
import { VersionWriteAction } from "../actions/VersionWriteAction";
|
|
@@ -51,6 +53,7 @@ export const coreActions: Record<string, new () => ActionInterface> = {
|
|
|
51
53
|
[new LintAction().name]: LintAction,
|
|
52
54
|
|
|
53
55
|
[new PackageManagerAction().name]: PackageManagerAction,
|
|
56
|
+
[new RunScriptAction().name]: RunScriptAction,
|
|
54
57
|
[new StyleProcessingAction().name]: StyleProcessingAction,
|
|
55
58
|
|
|
56
59
|
[new SvgPackagerAction().name]: SvgPackagerAction,
|
|
@@ -58,6 +61,7 @@ export const coreActions: Record<string, new () => ActionInterface> = {
|
|
|
58
61
|
[new SvgSpriteAction().name]: SvgSpriteAction,
|
|
59
62
|
[new SvgToPngAction().name]: SvgToPngAction,
|
|
60
63
|
|
|
64
|
+
[new TemplateRenderAction().name]: TemplateRenderAction,
|
|
61
65
|
[new TypeScriptCompilerAction().name]: TypeScriptCompilerAction,
|
|
62
66
|
|
|
63
67
|
[new VersionWriteAction().name]: VersionWriteAction,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Imports
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { execFile } from "child_process";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import util from "util";
|
|
8
|
+
import { Action } from "../../core/pipeline/Action";
|
|
9
|
+
import { ActionOptionsType } from "../../types/ActionOptionsType";
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Constants
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
const execFileAsync = util.promisify(execFile);
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Classes
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* RunScriptAction executes an external JavaScript file as part of a KIST pipeline.
|
|
23
|
+
* The script is executed in a separate process to avoid blocking the main application.
|
|
24
|
+
*/
|
|
25
|
+
export class RunScriptAction extends Action {
|
|
26
|
+
/**
|
|
27
|
+
* Executes the external script file.
|
|
28
|
+
*
|
|
29
|
+
* @param options - The options specifying the script file to execute.
|
|
30
|
+
* @returns A Promise that resolves when the script execution completes.
|
|
31
|
+
* @throws {Error} If the script execution fails.
|
|
32
|
+
*/
|
|
33
|
+
async execute(options: ActionOptionsType): Promise<void> {
|
|
34
|
+
const { scriptPath, args = [] } = options;
|
|
35
|
+
|
|
36
|
+
if (!scriptPath) {
|
|
37
|
+
throw new Error("Invalid options: 'scriptPath' is required.");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const resolvedScriptPath = path.resolve(scriptPath);
|
|
41
|
+
|
|
42
|
+
this.logInfo(`Executing external script: ${resolvedScriptPath}...`);
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const { stdout, stderr } = await execFileAsync("node", [
|
|
46
|
+
resolvedScriptPath,
|
|
47
|
+
...args,
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
if (stderr) {
|
|
51
|
+
this.logError(`Script execution failed: ${stderr}`);
|
|
52
|
+
throw new Error(stderr);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.logInfo(stdout);
|
|
56
|
+
this.logInfo("Script executed successfully.");
|
|
57
|
+
} catch (error) {
|
|
58
|
+
this.logError("Error occurred while executing the script.", error);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Provides a description of the action.
|
|
65
|
+
*
|
|
66
|
+
* @returns A string description of the action.
|
|
67
|
+
*/
|
|
68
|
+
describe(): string {
|
|
69
|
+
return "Executes an external JavaScript file as part of the KIST pipeline.";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Import
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { RunScriptAction } from "./RunScriptAction";
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Export
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
export { RunScriptAction };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Imports
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
6
|
+
import nunjucks from "nunjucks";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { Action } from "../../core/pipeline/Action";
|
|
9
|
+
import { ActionOptionsType } from "../../types/ActionOptionsType";
|
|
10
|
+
import nunjucksConfig from "./nunjucks.config.js";
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Classes
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* TemplateRenderAction is responsible for rendering and writing multiple files
|
|
18
|
+
* from Nunjucks templates in a single action. It allows batch generation of
|
|
19
|
+
* output files based on a shared template context.
|
|
20
|
+
*/
|
|
21
|
+
export class TemplateRenderAction extends Action {
|
|
22
|
+
/**
|
|
23
|
+
* Executes the template rendering process for multiple templates.
|
|
24
|
+
*
|
|
25
|
+
* @param options - The options specifying the template directory,
|
|
26
|
+
* template-output mapping, and rendering context.
|
|
27
|
+
* @returns A Promise that resolves when all templates have been rendered.
|
|
28
|
+
* @throws {Error} Throws an error if template processing fails.
|
|
29
|
+
*/
|
|
30
|
+
async execute(options: ActionOptionsType): Promise<void> {
|
|
31
|
+
const {
|
|
32
|
+
templatesDir = "./templates",
|
|
33
|
+
templates = [],
|
|
34
|
+
context = {},
|
|
35
|
+
customConfig = {},
|
|
36
|
+
} = options;
|
|
37
|
+
|
|
38
|
+
if (!Array.isArray(templates) || templates.length === 0) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
"Invalid options: 'templates' must be an array containing template-output pairs."
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.logInfo(`Rendering ${templates.length} templates...`);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// Merge Nunjucks configurations
|
|
48
|
+
const config = { ...nunjucksConfig, ...customConfig };
|
|
49
|
+
nunjucks.configure(templatesDir, config);
|
|
50
|
+
|
|
51
|
+
// Render each template and save to its corresponding output path
|
|
52
|
+
for (const { template, outputFile } of templates) {
|
|
53
|
+
if (!template || !outputFile) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Invalid template entry: ${JSON.stringify({
|
|
56
|
+
template,
|
|
57
|
+
outputFile,
|
|
58
|
+
})}`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.logInfo(`Rendering: ${template} → ${outputFile}`);
|
|
63
|
+
|
|
64
|
+
// Render template content
|
|
65
|
+
const content = nunjucks.render(template, context);
|
|
66
|
+
|
|
67
|
+
// Ensure the output directory exists
|
|
68
|
+
const dir = path.dirname(outputFile);
|
|
69
|
+
await mkdir(dir, { recursive: true });
|
|
70
|
+
|
|
71
|
+
// Write the rendered template to file
|
|
72
|
+
await writeFile(outputFile, content, "utf-8");
|
|
73
|
+
|
|
74
|
+
this.logInfo(`✓ Successfully rendered: ${outputFile}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this.logInfo("All templates rendered successfully.");
|
|
78
|
+
} catch (error) {
|
|
79
|
+
this.logError("Error rendering templates.", error);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Provides a description of the action.
|
|
86
|
+
*
|
|
87
|
+
* @returns A string description of the action.
|
|
88
|
+
*/
|
|
89
|
+
describe(): string {
|
|
90
|
+
return "Renders multiple Nunjucks templates into files using a shared context.";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// Export
|
|
96
|
+
// ============================================================================
|
|
97
|
+
|
|
98
|
+
export default TemplateRenderAction;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Import
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { TemplateRenderAction } from "./TemplateRenderAction";
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Export
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
export { TemplateRenderAction };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// This configuration is tailored to a typical web application setup. Adjust the `noCache` option
|
|
2
|
+
// according to your caching strategy for production environments to optimize performance.
|
|
3
|
+
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Import
|
|
6
|
+
// ============================================================================
|
|
7
|
+
|
|
8
|
+
// Importing path for potential future use in specifying template directories or other file paths
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Constants
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Configuration options for Nunjucks to ensure safe and efficient template rendering.
|
|
17
|
+
* This setup is ideal for both development and production environments, providing a balance
|
|
18
|
+
* between performance optimizations and security best practices.
|
|
19
|
+
*/
|
|
20
|
+
const nunjucksConfig = {
|
|
21
|
+
autoescape: true, // Controls if output with dangerous characters are escaped automatically
|
|
22
|
+
throwOnUndefined: false, // Throw errors when outputting a null/undefined value
|
|
23
|
+
trimBlocks: true, // Automatically remove trailing newlines from a block/tag
|
|
24
|
+
lstripBlocks: true, // Automatically remove leading whitespace from a block/tag
|
|
25
|
+
noCache: true,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Export
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
export default nunjucksConfig;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Import
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { ConfigInterface } from "../../interface/ConfigInterface";
|
|
6
|
+
import { AbstractProcess } from "../abstract/AbstractProcess";
|
|
7
|
+
import { defaultConfig } from "./defaultConfig";
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Class
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* ConfigStore is a singleton that loads and manages the application's
|
|
15
|
+
* configuration.
|
|
16
|
+
* It prioritizes CLI arguments over configuration file values.
|
|
17
|
+
*/
|
|
18
|
+
export class ConfigStore extends AbstractProcess {
|
|
19
|
+
// Parameters
|
|
20
|
+
// ========================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Singleton instance of the ConfigStore.
|
|
24
|
+
*/
|
|
25
|
+
private static instance: ConfigStore | null = null;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The current configuration stored in the ConfigStore.
|
|
29
|
+
*/
|
|
30
|
+
private config: ConfigInterface;
|
|
31
|
+
|
|
32
|
+
// Constructor
|
|
33
|
+
// ========================================================================
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Private constructor to enforce the singleton pattern.
|
|
37
|
+
* Initializes the store with the default configuration.
|
|
38
|
+
*/
|
|
39
|
+
private constructor() {
|
|
40
|
+
super();
|
|
41
|
+
this.config = defaultConfig;
|
|
42
|
+
this.logDebug("ConfigStore initialized with default configuration.");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Static Methods
|
|
46
|
+
// ========================================================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Retrieves the singleton instance of ConfigStore, initializing it if
|
|
50
|
+
* necessary.
|
|
51
|
+
* @returns The singleton instance of ConfigStore.
|
|
52
|
+
*/
|
|
53
|
+
public static getInstance(): ConfigStore {
|
|
54
|
+
if (!ConfigStore.instance) {
|
|
55
|
+
ConfigStore.instance = new ConfigStore();
|
|
56
|
+
}
|
|
57
|
+
return ConfigStore.instance;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Instance Methods
|
|
61
|
+
// ========================================================================
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Retrieves a value from the configuration by key.
|
|
65
|
+
* Supports nested keys using dot notation (e.g., "options.logLevel").
|
|
66
|
+
*
|
|
67
|
+
* @param key - The key of the configuration to retrieve.
|
|
68
|
+
* @returns The configuration value or undefined if not found.
|
|
69
|
+
*/
|
|
70
|
+
public get<T>(key: string): T | undefined {
|
|
71
|
+
const keys = key.split(".");
|
|
72
|
+
let current: any = this.config;
|
|
73
|
+
|
|
74
|
+
for (const k of keys) {
|
|
75
|
+
if (current[k] === undefined) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
current = current[k];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.logDebug(
|
|
82
|
+
`Configuration key "${key}" retrieved with value: ${JSON.stringify(current)}`,
|
|
83
|
+
);
|
|
84
|
+
return current as T;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Sets a value in the configuration by key.
|
|
89
|
+
* Supports nested keys using dot notation (e.g., "options.logLevel").
|
|
90
|
+
*
|
|
91
|
+
* @param key - The key of the configuration to set.
|
|
92
|
+
* @param value - The value to set.
|
|
93
|
+
*/
|
|
94
|
+
public set(key: string, value: unknown): void {
|
|
95
|
+
const keys = key.split(".");
|
|
96
|
+
let current: any = this.config;
|
|
97
|
+
|
|
98
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
99
|
+
const k = keys[i];
|
|
100
|
+
if (!current[k] || typeof current[k] !== "object") {
|
|
101
|
+
current[k] = {};
|
|
102
|
+
}
|
|
103
|
+
current = current[k];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
current[keys[keys.length - 1]] = value;
|
|
107
|
+
this.logDebug(
|
|
108
|
+
`Set configuration key "${key}" to: ${JSON.stringify(value)}`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Merges the provided configuration into the existing configuration.
|
|
114
|
+
* Uses a deep merge strategy to combine objects and overwrite primitives.
|
|
115
|
+
*
|
|
116
|
+
* @param newConfig - The new configuration to merge.
|
|
117
|
+
*/
|
|
118
|
+
public merge(newConfig: Partial<ConfigInterface>): void {
|
|
119
|
+
this.config = this.deepMerge(this.config, newConfig);
|
|
120
|
+
this.logDebug("Configuration successfully merged.");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Retrieves the current configuration.
|
|
125
|
+
*
|
|
126
|
+
* @returns The current configuration object.
|
|
127
|
+
*/
|
|
128
|
+
public getConfig(): ConfigInterface {
|
|
129
|
+
return this.config;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Prints the current configuration to the console in a readable format.
|
|
134
|
+
*/
|
|
135
|
+
public print(): void {
|
|
136
|
+
console.log("Current Configuration:");
|
|
137
|
+
console.log(JSON.stringify(this.config, null, 2));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Deeply merges two objects.
|
|
142
|
+
*
|
|
143
|
+
* @param target - The target object to merge into.
|
|
144
|
+
* @param source - The source object to merge from.
|
|
145
|
+
* @returns The merged object.
|
|
146
|
+
*/
|
|
147
|
+
// private deepMerge(target: any, source: any): any {
|
|
148
|
+
// if (typeof target !== "object" || target === null) {
|
|
149
|
+
// return source;
|
|
150
|
+
// }
|
|
151
|
+
|
|
152
|
+
// for (const key of Object.keys(source)) {
|
|
153
|
+
// if (
|
|
154
|
+
// source[key] &&
|
|
155
|
+
// typeof source[key] === "object" &&
|
|
156
|
+
// !Array.isArray(source[key])
|
|
157
|
+
// ) {
|
|
158
|
+
// if (!target[key] || typeof target[key] !== "object") {
|
|
159
|
+
// target[key] = {};
|
|
160
|
+
// }
|
|
161
|
+
// target[key] = this.deepMerge(target[key], source[key]);
|
|
162
|
+
// } else {
|
|
163
|
+
// target[key] = source[key];
|
|
164
|
+
// }
|
|
165
|
+
// }
|
|
166
|
+
|
|
167
|
+
// return target;
|
|
168
|
+
// }
|
|
169
|
+
private deepMerge(target: any, source: any): any {
|
|
170
|
+
if (typeof target !== "object" || target === null) {
|
|
171
|
+
return source;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
for (const key of Object.keys(source)) {
|
|
175
|
+
// Prevent prototype pollution
|
|
176
|
+
if (
|
|
177
|
+
key === "__proto__" ||
|
|
178
|
+
key === "constructor" ||
|
|
179
|
+
key === "prototype"
|
|
180
|
+
) {
|
|
181
|
+
this.logWarn(`Skipping potentially unsafe key: "${key}"`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (
|
|
186
|
+
source[key] &&
|
|
187
|
+
typeof source[key] === "object" &&
|
|
188
|
+
!Array.isArray(source[key])
|
|
189
|
+
) {
|
|
190
|
+
if (!target[key] || typeof target[key] !== "object") {
|
|
191
|
+
target[key] = {};
|
|
192
|
+
}
|
|
193
|
+
target[key] = this.deepMerge(target[key], source[key]);
|
|
194
|
+
} else {
|
|
195
|
+
target[key] = source[key];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return target;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -11,43 +11,25 @@ import { defaultConfig } from "./defaultConfig";
|
|
|
11
11
|
// ============================================================================
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* ConfigStore is a singleton that loads and manages the application's
|
|
15
|
-
* configuration.
|
|
14
|
+
* ConfigStore is a singleton that loads and manages the application's configuration.
|
|
16
15
|
* It prioritizes CLI arguments over configuration file values.
|
|
17
16
|
*/
|
|
18
17
|
export class ConfigStore extends AbstractProcess {
|
|
19
|
-
//
|
|
20
|
-
// ========================================================================
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Singleton instance of the ConfigStore.
|
|
24
|
-
*/
|
|
18
|
+
// Singleton instance
|
|
25
19
|
private static instance: ConfigStore | null = null;
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
* The current configuration stored in the ConfigStore.
|
|
29
|
-
*/
|
|
21
|
+
// The current configuration stored in the ConfigStore.
|
|
30
22
|
private config: ConfigInterface;
|
|
31
23
|
|
|
32
|
-
// Constructor
|
|
33
|
-
// ========================================================================
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Private constructor to enforce the singleton pattern.
|
|
37
|
-
* Initializes the store with the default configuration.
|
|
38
|
-
*/
|
|
24
|
+
// Constructor (Private to enforce Singleton Pattern)
|
|
39
25
|
private constructor() {
|
|
40
26
|
super();
|
|
41
27
|
this.config = defaultConfig;
|
|
42
28
|
this.logDebug("ConfigStore initialized with default configuration.");
|
|
43
29
|
}
|
|
44
30
|
|
|
45
|
-
// Static Methods
|
|
46
|
-
// ========================================================================
|
|
47
|
-
|
|
48
31
|
/**
|
|
49
|
-
* Retrieves the singleton instance of ConfigStore
|
|
50
|
-
* necessary.
|
|
32
|
+
* Retrieves the singleton instance of ConfigStore.
|
|
51
33
|
* @returns The singleton instance of ConfigStore.
|
|
52
34
|
*/
|
|
53
35
|
public static getInstance(): ConfigStore {
|
|
@@ -57,12 +39,8 @@ export class ConfigStore extends AbstractProcess {
|
|
|
57
39
|
return ConfigStore.instance;
|
|
58
40
|
}
|
|
59
41
|
|
|
60
|
-
// Instance Methods
|
|
61
|
-
// ========================================================================
|
|
62
|
-
|
|
63
42
|
/**
|
|
64
|
-
* Retrieves a value from the configuration
|
|
65
|
-
* Supports nested keys using dot notation (e.g., "options.logLevel").
|
|
43
|
+
* Retrieves a value from the configuration using dot notation.
|
|
66
44
|
*
|
|
67
45
|
* @param key - The key of the configuration to retrieve.
|
|
68
46
|
* @returns The configuration value or undefined if not found.
|
|
@@ -78,15 +56,12 @@ export class ConfigStore extends AbstractProcess {
|
|
|
78
56
|
current = current[k];
|
|
79
57
|
}
|
|
80
58
|
|
|
81
|
-
this.logDebug(
|
|
82
|
-
`Configuration key "${key}" retrieved with value: ${JSON.stringify(current)}`,
|
|
83
|
-
);
|
|
59
|
+
this.logDebug(`Configuration key "${key}" retrieved.`);
|
|
84
60
|
return current as T;
|
|
85
61
|
}
|
|
86
62
|
|
|
87
63
|
/**
|
|
88
|
-
* Sets a value in the configuration
|
|
89
|
-
* Supports nested keys using dot notation (e.g., "options.logLevel").
|
|
64
|
+
* Sets a value in the configuration using dot notation.
|
|
90
65
|
*
|
|
91
66
|
* @param key - The key of the configuration to set.
|
|
92
67
|
* @param value - The value to set.
|
|
@@ -97,21 +72,34 @@ export class ConfigStore extends AbstractProcess {
|
|
|
97
72
|
|
|
98
73
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
99
74
|
const k = keys[i];
|
|
100
|
-
|
|
101
|
-
|
|
75
|
+
|
|
76
|
+
// Prevent prototype pollution by blocking reserved keywords
|
|
77
|
+
if (["__proto__", "constructor", "prototype"].includes(k)) {
|
|
78
|
+
this.logWarn(`Attempted prototype pollution detected: "${k}"`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Ensure property exists and is an object
|
|
83
|
+
if (!Object.prototype.hasOwnProperty.call(current, k) || typeof current[k] !== "object") {
|
|
84
|
+
current[k] = Object.create(null); // Use a null prototype object
|
|
102
85
|
}
|
|
103
86
|
current = current[k];
|
|
104
87
|
}
|
|
105
88
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
)
|
|
89
|
+
const finalKey = keys[keys.length - 1];
|
|
90
|
+
|
|
91
|
+
// Prevent prototype pollution at the final assignment
|
|
92
|
+
if (["__proto__", "constructor", "prototype"].includes(finalKey)) {
|
|
93
|
+
this.logWarn(`Attempted prototype pollution detected: "${finalKey}"`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
current[finalKey] = value;
|
|
98
|
+
this.logDebug(`Set configuration key "${key}" to: ${JSON.stringify(value)}`);
|
|
110
99
|
}
|
|
111
100
|
|
|
112
101
|
/**
|
|
113
|
-
* Merges the provided configuration into the existing configuration.
|
|
114
|
-
* Uses a deep merge strategy to combine objects and overwrite primitives.
|
|
102
|
+
* Merges the provided configuration into the existing configuration using deep merge.
|
|
115
103
|
*
|
|
116
104
|
* @param newConfig - The new configuration to merge.
|
|
117
105
|
*/
|
|
@@ -121,51 +109,27 @@ export class ConfigStore extends AbstractProcess {
|
|
|
121
109
|
}
|
|
122
110
|
|
|
123
111
|
/**
|
|
124
|
-
* Retrieves the current configuration.
|
|
125
|
-
*
|
|
126
|
-
* @returns The current configuration object.
|
|
112
|
+
* Retrieves the current configuration object.
|
|
113
|
+
* @returns The current configuration.
|
|
127
114
|
*/
|
|
128
115
|
public getConfig(): ConfigInterface {
|
|
129
116
|
return this.config;
|
|
130
117
|
}
|
|
131
118
|
|
|
132
119
|
/**
|
|
133
|
-
* Prints the current configuration to the console
|
|
120
|
+
* Prints the current configuration to the console.
|
|
134
121
|
*/
|
|
135
122
|
public print(): void {
|
|
136
|
-
console.log("Current Configuration:");
|
|
137
|
-
console.log(JSON.stringify(this.config, null, 2));
|
|
123
|
+
console.log("Current Configuration:", JSON.stringify(this.config, null, 2));
|
|
138
124
|
}
|
|
139
125
|
|
|
140
126
|
/**
|
|
141
|
-
* Deeply merges two objects.
|
|
127
|
+
* Deeply merges two objects, preventing prototype pollution.
|
|
142
128
|
*
|
|
143
|
-
* @param target - The target object
|
|
144
|
-
* @param source - The source object
|
|
129
|
+
* @param target - The target object.
|
|
130
|
+
* @param source - The source object.
|
|
145
131
|
* @returns The merged object.
|
|
146
132
|
*/
|
|
147
|
-
// private deepMerge(target: any, source: any): any {
|
|
148
|
-
// if (typeof target !== "object" || target === null) {
|
|
149
|
-
// return source;
|
|
150
|
-
// }
|
|
151
|
-
|
|
152
|
-
// for (const key of Object.keys(source)) {
|
|
153
|
-
// if (
|
|
154
|
-
// source[key] &&
|
|
155
|
-
// typeof source[key] === "object" &&
|
|
156
|
-
// !Array.isArray(source[key])
|
|
157
|
-
// ) {
|
|
158
|
-
// if (!target[key] || typeof target[key] !== "object") {
|
|
159
|
-
// target[key] = {};
|
|
160
|
-
// }
|
|
161
|
-
// target[key] = this.deepMerge(target[key], source[key]);
|
|
162
|
-
// } else {
|
|
163
|
-
// target[key] = source[key];
|
|
164
|
-
// }
|
|
165
|
-
// }
|
|
166
|
-
|
|
167
|
-
// return target;
|
|
168
|
-
// }
|
|
169
133
|
private deepMerge(target: any, source: any): any {
|
|
170
134
|
if (typeof target !== "object" || target === null) {
|
|
171
135
|
return source;
|
|
@@ -173,22 +137,14 @@ export class ConfigStore extends AbstractProcess {
|
|
|
173
137
|
|
|
174
138
|
for (const key of Object.keys(source)) {
|
|
175
139
|
// Prevent prototype pollution
|
|
176
|
-
if (
|
|
177
|
-
key
|
|
178
|
-
key === "constructor" ||
|
|
179
|
-
key === "prototype"
|
|
180
|
-
) {
|
|
181
|
-
this.logWarn(`Skipping potentially unsafe key: "${key}"`);
|
|
140
|
+
if (["__proto__", "constructor", "prototype"].includes(key)) {
|
|
141
|
+
this.logWarn(`Skipping unsafe key during merge: "${key}"`);
|
|
182
142
|
continue;
|
|
183
143
|
}
|
|
184
144
|
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
!Array.isArray(source[key])
|
|
189
|
-
) {
|
|
190
|
-
if (!target[key] || typeof target[key] !== "object") {
|
|
191
|
-
target[key] = {};
|
|
145
|
+
if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
|
|
146
|
+
if (!Object.prototype.hasOwnProperty.call(target, key) || typeof target[key] !== "object") {
|
|
147
|
+
target[key] = Object.create(null);
|
|
192
148
|
}
|
|
193
149
|
target[key] = this.deepMerge(target[key], source[key]);
|
|
194
150
|
} else {
|