casper-context 0.1.0

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.
Files changed (44) hide show
  1. package/README.md +253 -0
  2. package/dist/index.js +9 -0
  3. package/dist/lifecycle/post.js +239 -0
  4. package/dist/lifecycle/pre.js +113 -0
  5. package/dist/plugin.js +57 -0
  6. package/dist/transforms/autoContextTransform.js +1 -0
  7. package/dist/transforms/contextTransform.js +1 -0
  8. package/dist/transforms/stateTransform.js +1 -0
  9. package/dist/types/plugin.d.js +1 -0
  10. package/dist/utils/astHelpers.js +644 -0
  11. package/dist/utils/constants.js +111 -0
  12. package/dist/utils/names.js +1 -0
  13. package/dist/utils/scope.js +1 -0
  14. package/dist/utils/utilityHelpers.js +606 -0
  15. package/dist/visitors/AssignmentExpression.js +104 -0
  16. package/dist/visitors/CallExpression.js +1 -0
  17. package/dist/visitors/FunctionDeclaration.js +116 -0
  18. package/dist/visitors/Identifier.js +123 -0
  19. package/dist/visitors/JSXElement.js +1 -0
  20. package/dist/visitors/Program.js +278 -0
  21. package/dist/visitors/ReturnStatement.js +81 -0
  22. package/dist/visitors/VariableDeclaration.js +209 -0
  23. package/package.json +60 -0
  24. package/src/index.js +2 -0
  25. package/src/lifecycle/post.js +237 -0
  26. package/src/lifecycle/pre.js +103 -0
  27. package/src/plugin.js +51 -0
  28. package/src/transforms/autoContextTransform.js +0 -0
  29. package/src/transforms/contextTransform.js +0 -0
  30. package/src/transforms/stateTransform.js +0 -0
  31. package/src/types/plugin.d.ts +0 -0
  32. package/src/utils/astHelpers.js +767 -0
  33. package/src/utils/constants.js +102 -0
  34. package/src/utils/names.js +0 -0
  35. package/src/utils/scope.js +0 -0
  36. package/src/utils/utilityHelpers.js +636 -0
  37. package/src/visitors/AssignmentExpression.js +100 -0
  38. package/src/visitors/CallExpression.js +0 -0
  39. package/src/visitors/FunctionDeclaration.js +114 -0
  40. package/src/visitors/Identifier.js +142 -0
  41. package/src/visitors/JSXElement.js +0 -0
  42. package/src/visitors/Program.js +280 -0
  43. package/src/visitors/ReturnStatement.js +75 -0
  44. package/src/visitors/VariableDeclaration.js +216 -0
@@ -0,0 +1,606 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isContextInstanceDeclare = exports.getInsertionIndex = exports.getFilePathHASH = exports.findContextByVar = exports.ESLINT_GLOBAL_JS_PATH = exports.CONTEXT_FILE_PATH = exports.CASPER_CONFIG_PATH = void 0;
7
+ exports.isExcludeFile = isExcludeFile;
8
+ exports.log = log;
9
+ exports.readCasperConfig = readCasperConfig;
10
+ exports.registerVariable = void 0;
11
+ exports.resetVarsForFile = resetVarsForFile;
12
+ exports.resolveReact = resolveReact;
13
+ var _path = _interopRequireDefault(require("path"));
14
+ var _fs = _interopRequireDefault(require("fs"));
15
+ var _crypto = _interopRequireDefault(require("crypto"));
16
+ var _constants = require("./constants");
17
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
18
+ /**
19
+ * @fileoverview Path resolution and configuration management for Casper Context.
20
+ * This module handles the physical location of generated context files,
21
+ * ESLint configurations, and the initialization of the library's runtime settings.
22
+ */
23
+
24
+ /**
25
+ * Core Node.js modules used throughout the Casper Babel utilities:
26
+ * - `path` for resolving and normalizing file system paths.
27
+ * - `fs` for reading, writing, and checking the existence of files.
28
+ * - `crypto` for generating file-based hashes and unique identifiers.
29
+ */
30
+
31
+ /**
32
+ * Casper-specific constants imported from './constants':
33
+ *
34
+ * - Context and file naming:
35
+ * - `CONTEXT_FOLDER_NAME`, `CONTEXT_FILE_NAME`
36
+ * - File and hash utilities:
37
+ * - `NO_FILE`, `FILE_HASH_MD5`, `UNICODE_UTF8`, `DIGEST_HEX`
38
+ * - React & component prefixes:
39
+ * - `_CCTX_CMP_NAME_PREFIX`, `_CCTX_UNDUS_CORE_REACT`
40
+ * - File/folder exclusions:
41
+ * - `GBL_CONTEXT_JS`, `NODE_MODULES`, `FOLDER_DIST`, `FOLDER_BUILD`
42
+ * - ESLint & config integration:
43
+ * - `CASPER_ESLINT_GLOBAL_JS`, `CASPER_CONFIG_FILE`, `GLOBAL_PREFIX`
44
+ * - Debugging utilities:
45
+ * - `CASPER_DEBUG_LOG_FILE_NAME`, `CASPER_STRING_TYPE`, `COLORS`, `CONTEXT_NAMESPACE`,
46
+ * - `CASPER_LOG_TYPE_ERROR`
47
+ * - JavaScript directive:
48
+ * - `USE_STRICT`
49
+ */
50
+
51
+ /**
52
+ * The absolute system path to the generated global context file.
53
+ * @type {string}
54
+ * @description Typically resolves to '[root]/src/scopeContext/gblContext.js'.
55
+ */
56
+ const CONTEXT_FILE_PATH = exports.CONTEXT_FILE_PATH = _path.default.join(process.cwd(), _constants.CONTEXT_FOLDER_NAME, _constants.CONTEXT_FILE_NAME);
57
+
58
+ /**
59
+ * The absolute system path to the auto-generated ESLint globals file.
60
+ * @type {string}
61
+ * @description Used to inject `_$_` variables into the ESLint environment to prevent 'undefined variable' errors.
62
+ */
63
+ const ESLINT_GLOBAL_JS_PATH = exports.ESLINT_GLOBAL_JS_PATH = _path.default.resolve(process.cwd(), _constants.CASPER_ESLINT_GLOBAL_JS);
64
+
65
+ /**
66
+ * The absolute system path to the Casper Context configuration file (.casperctxrc.json).
67
+ * @type {string}
68
+ */
69
+ const CASPER_CONFIG_PATH = exports.CASPER_CONFIG_PATH = _path.default.join(process.cwd(), _constants.CASPER_CONFIG_FILE);
70
+
71
+ /**
72
+ * Default fallback configuration for Casper Context plugin.
73
+ *
74
+ * This configuration is automatically applied when a project does not
75
+ * provide a `.casperctxrc.json` configuration file in its root directory.
76
+ *
77
+ * @property {string} prefix - Prefix used to identify Casper context variables.
78
+ * Context variables must begin with this sequence (Default: '_$_').
79
+ *
80
+ * @property {boolean} debug - Enables or disables verbose logging during
81
+ * the Babel transformation process. Useful for debugging plugin behavior.
82
+ *
83
+ * @property {string[]} debug_levels - List of log levels allowed when debug mode
84
+ * is enabled. This controls which types of log messages are displayed.
85
+ * possiable values: 'reset','info','warn','error', 'debug', 'trace'
86
+ * @private
87
+ */
88
+ const DEFAULT_CONFIG = {
89
+ prefix: _constants.GLOBAL_PREFIX,
90
+ debug: false,
91
+ debug_levels: [_constants.CASPER_LOG_TYPE_ERROR]
92
+ };
93
+ const IS_PROD = process.env.NODE_ENV === "production";
94
+
95
+ /**
96
+ * Generates a short, deterministic hash based on a file’s absolute path.
97
+ *
98
+ * This function resolves the given file path to an absolute path and produces
99
+ * a truncated MD5 hash. It is commonly used to create stable, filesystem-based
100
+ * identifiers for files that remain consistent across executions.
101
+ *
102
+ * @param {string} fileName - The file path to hash. Can be relative or absolute.
103
+ *
104
+ * @returns {string} An 8-character hexadecimal hash derived from the file’s
105
+ * absolute path, or `NO_FILE` if hashing fails.
106
+ *
107
+ * @important
108
+ * - Uses the absolute path (`path.resolve`) to ensure consistency regardless
109
+ * of the current working directory.
110
+ * - The hash is generated using MD5 and truncated to the first 8 characters
111
+ * for compactness (not intended for cryptographic security).
112
+ * - `FILE_HASH_MD5`, `UNICODE_UTF8`, `DIGEST_HEX`, and `NO_FILE` are assumed
113
+ * to be predefined constants.
114
+ * - Errors (invalid path, filesystem issues, crypto failures) are safely
115
+ * caught and result in returning `NO_FILE`.
116
+ * - This function is deterministic: the same file path will always produce
117
+ * the same hash.
118
+ */
119
+ const getFilePathHASH = fileName => {
120
+ try {
121
+ const abs = _path.default.resolve(fileName);
122
+ return _crypto.default.createHash(_constants.FILE_HASH_MD5).update(abs, _constants.UNICODE_UTF8).digest(_constants.DIGEST_HEX).slice(0, 8);
123
+ } catch (e) {
124
+ return _constants.NO_FILE;
125
+ }
126
+ };
127
+
128
+ /**
129
+ * Registers a component-scoped variable in a virtual registry and returns its context name.
130
+ *
131
+ * This function ensures that variables belonging to the same component are grouped
132
+ * under a single generated context name. If the variable is already registered for
133
+ * the given component hash, the existing context name is returned without mutation.
134
+ * Otherwise, the variable and its default value are recorded in the registry.
135
+ *
136
+ * @param {string} componentNameHash - A unique, deterministic hash representing a component.
137
+ * Used as the registry key to scope variables per component.
138
+ * @param {string} varName - The name of the variable to register within the component context.
139
+ * @param {*} defaultValue - The default value associated with the variable. Stored for
140
+ * initializing context state.
141
+ * @param {Object} virtualRegistry - A mutable in-memory registry object used to track
142
+ * component variables, context names, and defaults.
143
+ *
144
+ * @returns {string|undefined} The generated or existing context name associated with
145
+ * the component, or `undefined` if an error occurs.
146
+ *
147
+ * @important
148
+ * - The registry structure per component hash follows this shape:
149
+ * ```js
150
+ * {
151
+ * varNames: string[],
152
+ * ctxName: string | null,
153
+ * defaults: Record<string, any>
154
+ * }
155
+ * ```
156
+ * - `_CCTX_CMP_NAME_PREFIX` is assumed to be a predefined constant used to namespace
157
+ * generated context names.
158
+ * - Each component hash maps to exactly one context name.
159
+ * - Variables are registered idempotently: re-registering the same variable will not
160
+ * duplicate entries.
161
+ * - This function mutates `virtualRegistry` directly.
162
+ * - Errors are silently caught; consider logging in debug or development builds.
163
+ */
164
+ exports.getFilePathHASH = getFilePathHASH;
165
+ const registerVariable = (componentNameHash, varName, defaultValue, virtualRegistry) => {
166
+ try {
167
+ if (!virtualRegistry[componentNameHash]) {
168
+ virtualRegistry[componentNameHash] = {
169
+ varNames: [],
170
+ ctxName: null,
171
+ defaults: {}
172
+ };
173
+ }
174
+ if (virtualRegistry[componentNameHash] && virtualRegistry[componentNameHash].varNames.includes(varName)) {
175
+ return virtualRegistry[componentNameHash].ctxName;
176
+ }
177
+ const ctxName = `${_constants._CCTX_CMP_NAME_PREFIX}${componentNameHash}`;
178
+ virtualRegistry[componentNameHash].ctxName = ctxName;
179
+ virtualRegistry[componentNameHash].varNames = [...virtualRegistry[componentNameHash].varNames, varName];
180
+ const newDefaults = {
181
+ ...virtualRegistry[componentNameHash].defaults
182
+ };
183
+ newDefaults[varName] = defaultValue;
184
+ virtualRegistry[componentNameHash].defaults = newDefaults;
185
+ return ctxName;
186
+ } catch (e) {
187
+ log('error', '[::registerVariable::]', e.message);
188
+ }
189
+ };
190
+
191
+ /**
192
+ * Finds the component context identifier associated with a given variable name.
193
+ *
194
+ * This function searches through the virtual registry and returns the component
195
+ * hash (registry key) whose registered variable list contains the specified variable.
196
+ * It is typically used to resolve which component context manages a given variable.
197
+ *
198
+ * @param {string} varName - The variable name to search for in the registry.
199
+ * @param {Object} virtualRegistry - The in-memory registry that stores component
200
+ * context data, including variable mappings.
201
+ *
202
+ * @returns {string|null} The component hash (registry key) associated with the variable,
203
+ * or `null` if the variable is not registered in any context.
204
+ *
205
+ * @important
206
+ * - The registry is expected to follow this structure:
207
+ * ```js
208
+ * {
209
+ * [componentHash]: {
210
+ * varNames: string[],
211
+ * ctxName: string,
212
+ * defaults: Record<string, any>
213
+ * }
214
+ * }
215
+ * ```
216
+ * - The function performs a linear search through registry entries.
217
+ * - Returns the registry key (component hash), not the generated context name.
218
+ * - Does **not** mutate the registry.
219
+ * - Errors are silently caught; consider adding logging for debugging.
220
+ */
221
+ exports.registerVariable = registerVariable;
222
+ const findContextByVar = (varName, virtualRegistry) => {
223
+ try {
224
+ for (const [ctxName, ctxData] of Object.entries(virtualRegistry)) {
225
+ if (ctxData.varNames && ctxData.varNames.includes(varName)) {
226
+ return ctxName;
227
+ }
228
+ }
229
+ return null;
230
+ } catch (e) {
231
+ log('error', '[::findContextByVar::]', e.message);
232
+ }
233
+ };
234
+
235
+ /**
236
+ * Checks whether a context instance variable is already declared within a block scope.
237
+ *
238
+ * This utility scans the statements inside a given BlockStatement path and determines
239
+ * if a variable declaration with the specified name already exists. It is primarily
240
+ * used to prevent duplicate `useContext` or context-instance declarations from being
241
+ * injected multiple times during AST transformations.
242
+ *
243
+ * @param {NodePath} path - Babel NodePath pointing to a `BlockStatement`
244
+ * (typically a component or function body).
245
+ * @param {Object} t - Babel types helper (`@babel/types`) used for AST node checks.
246
+ * @param {string} varName - The variable name to look for in existing declarations.
247
+ *
248
+ * @returns {boolean} `true` if a variable declaration with the given name is found,
249
+ * otherwise `false`.
250
+ *
251
+ * @important
252
+ * - Only inspects **top-level statements** within the provided block body.
253
+ * - Detects `var`, `let`, and `const` declarations.
254
+ * - Does **not** traverse nested blocks or scopes.
255
+ * - Intended as a guard to ensure idempotent AST injection.
256
+ * - Errors are silently caught; add logging if visibility is required.
257
+ */
258
+ exports.findContextByVar = findContextByVar;
259
+ const isContextInstanceDeclare = (path, t, varName) => {
260
+ try {
261
+ return path.node.body.some(stmt => {
262
+ if (!t.isVariableDeclaration(stmt)) return false;
263
+ return stmt.declarations.some(decl => t.isIdentifier(decl.id, {
264
+ name: varName
265
+ }));
266
+ });
267
+ } catch (e) {
268
+ log('error', '[::isContextInstanceDeclare::]', e.message);
269
+ }
270
+ };
271
+
272
+ /**
273
+ * Determines the correct insertion index for injecting new statements
274
+ * into a function or program body.
275
+ *
276
+ * If the first statement is a `"use strict"` directive, the insertion
277
+ * point is moved **after** it to preserve directive semantics.
278
+ * Otherwise, insertion occurs at the start of the body.
279
+ *
280
+ * This utility is commonly used when inserting context hooks,
281
+ * variable declarations, or other setup code during Babel AST transforms.
282
+ *
283
+ * @param {Array} body - An array of AST statements (e.g. `BlockStatement.body`).
284
+ * @param {Object} t - Babel types helper (`@babel/types`) used for node checks.
285
+ *
286
+ * @returns {number} The index at which new statements should be inserted.
287
+ *
288
+ * @important
289
+ * - Only checks the **first statement** for a `"use strict"` directive.
290
+ * - Assumes `body` is a valid statement array.
291
+ * - Preserves directive ordering and avoids breaking strict mode.
292
+ * - Errors are silently swallowed; consider logging for debugging.
293
+ */
294
+ exports.isContextInstanceDeclare = isContextInstanceDeclare;
295
+ const getInsertionIndex = (body, t) => {
296
+ try {
297
+ let index = 0;
298
+ if (body[0] && t.isExpressionStatement(body[0]) && t.isStringLiteral(body[0].expression, {
299
+ value: _constants.USE_STRICT
300
+ })) {
301
+ index = 1;
302
+ }
303
+ return index;
304
+ } catch (e) {
305
+ log('error', '[::getInsertionIndex::]', e.message);
306
+ }
307
+ };
308
+
309
+ /**
310
+ * Appends debug information to the Casper debug log file.
311
+ *
312
+ * This helper writes a formatted log entry to a file located in the
313
+ * current working directory. Each argument is converted into a string:
314
+ * - String values are written as-is.
315
+ * - Non-string values are serialized using `JSON.stringify`.
316
+ *
317
+ * All arguments are concatenated with spaces and written as a single
318
+ * log line followed by a newline character.
319
+ *
320
+ * @param {...any} args - One or more values to log. Supports strings,
321
+ * objects, arrays, numbers, booleans, or any serializable value.
322
+ *
323
+ * @returns {void} This function does not return a value.
324
+ *
325
+ * @important
326
+ * - Logging is synchronous (`fs.appendFileSync`), which may impact performance
327
+ * if called frequently or inside hot execution paths.
328
+ * - Log file location is resolved relative to `process.cwd()`.
329
+ * - Non-serializable objects may throw during `JSON.stringify`.
330
+ * - Errors are silently swallowed to prevent logging from breaking execution.
331
+ * - Intended primarily for debugging, tracing, or development diagnostics.
332
+ */
333
+ exports.getInsertionIndex = getInsertionIndex;
334
+ function log(level, ...args) {
335
+ try {
336
+ if (!cachedConfig.debug) return;
337
+ if (!cachedConfig.debug_levels && cachedConfig.debug_levels.length === 0) level = _constants.CASPER_LOG_TYPE_ERROR;
338
+ if (cachedConfig.debug_levels && cachedConfig.debug_levels.length > 0 && !cachedConfig.debug_levels.includes(level)) return;
339
+ const {
340
+ raw
341
+ } = formatMessage(level.toUpperCase(), args);
342
+ _fs.default.appendFileSync(_path.default.join(process.cwd(), _constants.CASPER_DEBUG_LOG_FILE_NAME), raw + "\n");
343
+ writeToConsole(level, raw);
344
+ } catch (e) {}
345
+ }
346
+
347
+ /**
348
+ * Determines whether a file should be processed or excluded by the transformation.
349
+ *
350
+ * This utility filters files based on predefined exclusion rules and optional
351
+ * source directory constraints.
352
+ *
353
+ * Exclusion Rules:
354
+ * - Files inside `node_modules` are excluded.
355
+ * - Files inside build output folders (e.g., `dist`, `build`) are excluded.
356
+ * - The global context file is excluded.
357
+ *
358
+ * Inclusion Rule:
359
+ * - If `opts.sourceDir` is provided, only files inside that directory
360
+ * (relative to `opts.root` or `process.cwd()`) are included.
361
+ * - If `opts.sourceDir` is not provided, all non-excluded files are included.
362
+ *
363
+ * @param {string} file - Absolute or relative file path being evaluated.
364
+ * @param {Object} [opts] - Optional configuration object.
365
+ * @param {string} [opts.sourceDir] - Source directory to restrict processing.
366
+ * @param {string} [opts.root] - Project root directory. Defaults to `process.cwd()`.
367
+ *
368
+ * @returns {boolean}
369
+ * - `true` → File is allowed to be processed.
370
+ * - `false` → File should be excluded.
371
+ *
372
+ * @important
373
+ * - Path checks use simple string matching (`includes`, `startsWith`),
374
+ * so paths should be normalized before calling this function if
375
+ * cross-platform compatibility is required.
376
+ * - Ensure `file` is an absolute path when using `sourceDir` filtering
377
+ * for accurate matching.
378
+ */
379
+ function isExcludeFile(file, opts) {
380
+ try {
381
+ if (file.includes(_constants.NODE_MODULES)) return false;
382
+ if (file.includes(_constants.FOLDER_DIST) || file.includes(_constants.FOLDER_BUILD)) return false;
383
+ if (file.includes(_constants.GBL_CONTEXT_JS)) return false;
384
+ if (opts?.sourceDir) {
385
+ const root = opts.root || process.cwd();
386
+ const srcRoot = _path.default.join(root, opts.sourceDir);
387
+ return file.startsWith(srcRoot);
388
+ }
389
+ return true;
390
+ } catch (e) {
391
+ log('error', '[::isExcludeFile::]', e.message);
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Resolves the React identifier to be used when generating AST nodes.
397
+ *
398
+ * This function determines which React reference should be used based on
399
+ * how React is imported in the current file.
400
+ *
401
+ * Resolution priority:
402
+ * 1. `state.importState.reactId`
403
+ * - Covers:
404
+ * - `import React from 'react'`
405
+ * - `import * as React from 'react'`
406
+ * 2. Fallback to an injected global/core React identifier
407
+ * - Used when no explicit React import exists in the file
408
+ *
409
+ * @param {NodePath} path - Current Babel path (not directly used, but kept for symmetry/future use).
410
+ * @param {Object} t - Babel types helper.
411
+ * @param {Object} state - Babel plugin state.
412
+ * @param {Object} state.importState - Collected import metadata.
413
+ * @param {Identifier} [state.importState.reactId] - Resolved React identifier, if imported.
414
+ *
415
+ * @returns {Identifier}
416
+ * The identifier representing `React` to be used in generated expressions
417
+ * (e.g. `React.createElement`, `React.useContext`).
418
+ *
419
+ * @important
420
+ * - This function does NOT validate whether the fallback React identifier
421
+ * is actually in scope; callers must ensure it is injected if needed.
422
+ * - Hooks-only imports (`import { useState } from 'react'`) will NOT
423
+ * populate `reactId`, so fallback behavior applies.
424
+ */
425
+ function resolveReact(path, t, state) {
426
+ try {
427
+ const importState = state.importState;
428
+ let reactIdent;
429
+ // -------- React identifier --------
430
+ if (importState.reactId) {
431
+ // default OR namespace import
432
+ // import React from 'react'
433
+ // import * as React from 'react'
434
+ reactIdent = importState.reactId;
435
+ } else {
436
+ // fallback injected core react
437
+ reactIdent = t.identifier(_constants._CCTX_UNDUS_CORE_REACT);
438
+ }
439
+ return reactIdent;
440
+ } catch (e) {
441
+ log('error', '[::resolveReact::]', e.message);
442
+ }
443
+ }
444
+
445
+ /**
446
+ * Resets registered variable metadata for all virtual registry entries
447
+ * associated with a specific file hash.
448
+ *
449
+ * This function scans the provided `virtualRegistry` and clears the
450
+ * `varNames` and `defaults` collections for any registry entry whose key
451
+ * matches the given `fileHash` suffix. It is typically used when a file
452
+ * is reprocessed to ensure stale variable mappings do not persist.
453
+ *
454
+ * @param {Object<string, Object>} virtualRegistry - Central registry object that stores
455
+ * component/context mappings and variable metadata.
456
+ * @param {string} fileHash - Unique hash identifier representing the file.
457
+ * This hash is expected to be appended to registry keys using the format:
458
+ * `"<componentHash>_<fileHash>"`.
459
+ *
460
+ * @returns {void}
461
+ * This function mutates `virtualRegistry` in place and does not return a value.
462
+ *
463
+ * @important
464
+ * - Only registry entries whose keys end with `_<fileHash>` are affected.
465
+ * - The registry entry itself is NOT removed; only `varNames` and `defaults`
466
+ * are cleared.
467
+ * - Safe to call multiple times; repeated calls will simply reset the same entries.
468
+ * - Assumes registry keys consistently follow the expected naming convention.
469
+ */
470
+ function resetVarsForFile(virtualRegistry, fileHash) {
471
+ try {
472
+ for (const key of Object.keys(virtualRegistry)) {
473
+ if (key.endsWith(`_${fileHash}`)) {
474
+ virtualRegistry[key].varNames = [];
475
+ virtualRegistry[key].defaults = {};
476
+ }
477
+ }
478
+ } catch (e) {
479
+ log('error', '[::resetVarsForFile::]', e.message);
480
+ }
481
+ }
482
+
483
+ /**
484
+ * Reads, parses, and caches the Casper configuration file.
485
+ *
486
+ * This function loads user-defined configuration from the Casper config file
487
+ * located at `CASPER_CONFIG_PATH`. If the file exists and contains valid JSON,
488
+ * its values are merged with `DEFAULT_CONFIG`. The merged result is cached to
489
+ * avoid repeated filesystem reads and parsing during subsequent calls.
490
+ *
491
+ * If the config file does not exist or parsing fails, the function falls back
492
+ * to `DEFAULT_CONFIG`.
493
+ *
494
+ * @returns {Object}
495
+ * The resolved Casper configuration object, which is either:
496
+ * - A merged object of `DEFAULT_CONFIG` and user-provided config values, or
497
+ * - `DEFAULT_CONFIG` if no valid user configuration is found.
498
+ *
499
+ * @important
500
+ * - Configuration is cached after the first successful read to improve performance.
501
+ * - Subsequent calls return the cached configuration without re-reading the file.
502
+ * - If runtime config reloading is required, `cachedConfig` must be manually cleared.
503
+ * - User configuration values override matching keys in `DEFAULT_CONFIG`.
504
+ * - Invalid JSON or filesystem errors automatically trigger fallback to `DEFAULT_CONFIG`.
505
+ */
506
+ let cachedConfig = null;
507
+ function readCasperConfig() {
508
+ if (cachedConfig) return cachedConfig;
509
+ try {
510
+ if (_fs.default.existsSync(CASPER_CONFIG_PATH)) {
511
+ const file = _fs.default.readFileSync(CASPER_CONFIG_PATH, "utf8");
512
+ const userConfig = JSON.parse(file);
513
+ cachedConfig = {
514
+ ...DEFAULT_CONFIG,
515
+ ...userConfig
516
+ };
517
+ } else {
518
+ cachedConfig = DEFAULT_CONFIG;
519
+ }
520
+ } catch (e) {
521
+ cachedConfig = DEFAULT_CONFIG;
522
+ }
523
+ return cachedConfig;
524
+ }
525
+
526
+ /**
527
+ * Safely converts a value into a string representation.
528
+ *
529
+ * This utility ensures logging operations never fail due to
530
+ * circular references or non-serializable objects. If the
531
+ * value is already a string, it is returned as-is. Otherwise,
532
+ * the value is serialized using JSON.stringify().
533
+ *
534
+ * @param {any} value - The value to stringify.
535
+ *
536
+ * @returns {string} A safe string representation of the input value.
537
+ * Returns "[Unserializable Object]" if serialization fails.
538
+ *
539
+ * Behavior:
540
+ * - Prevents runtime crashes during logging.
541
+ * - Handles complex or circular objects gracefully.
542
+ * - Ensures consistent log output formatting.
543
+ */
544
+ function safeStringify(value) {
545
+ try {
546
+ return typeof value === _constants.CASPER_STRING_TYPE ? value : JSON.stringify(value);
547
+ } catch {
548
+ return "[Unserializable Object]";
549
+ }
550
+ }
551
+
552
+ /**
553
+ * Formats log data into a standardized Casper log structure.
554
+ *
555
+ * This function builds a consistent log message by attaching
556
+ * the context namespace, log level, timestamp, and safely
557
+ * stringified message content.
558
+ *
559
+ * @param {string} level - Log severity level (e.g., INFO, WARN, ERROR, DEBUG, TRACE).
560
+ * @param {Array<any>} args - List of values to include in the log message.
561
+ * Values are safely converted to strings using `safeStringify`.
562
+ *
563
+ * @returns {Object} Formatted log object containing:
564
+ * @returns {string} returns.raw - Fully formatted log string ready for console/file output.
565
+ * @returns {string} returns.timestamp - ISO timestamp when the log was created.
566
+ * @returns {string} returns.message - Combined stringified message content.
567
+ *
568
+ * Behavior:
569
+ * - Ensures consistent log formatting across Casper logging system.
570
+ * - Prevents runtime crashes by safely serializing non-string values.
571
+ * - Centralizes log formatting for console and file writers.
572
+ */
573
+ function formatMessage(level, args) {
574
+ try {
575
+ const timestamp = new Date().toISOString();
576
+ const message = args.map(safeStringify).join(" ");
577
+ return {
578
+ raw: `[${_constants.CONTEXT_NAMESPACE}][${level}][${timestamp}] ${message}`,
579
+ timestamp,
580
+ message
581
+ };
582
+ } catch (e) {}
583
+ }
584
+
585
+ /**
586
+ * Writes a formatted log message to the console with color styling.
587
+ *
588
+ * This function only outputs logs when Casper debug mode is enabled.
589
+ * It applies a color based on the log level to improve readability
590
+ * during development and debugging.
591
+ *
592
+ * @param {string} level - Log severity level (e.g., INFO, WARN, ERROR, DEBUG, TRACE).
593
+ * @param {string} text - Fully formatted log message to display.
594
+ *
595
+ * Behavior:
596
+ * - Skips console output if debug mode is disabled.
597
+ * - Falls back to INFO color if an unknown level is provided.
598
+ * - Resets console color after logging to prevent style bleeding.
599
+ */
600
+ function writeToConsole(level, text) {
601
+ try {
602
+ if (!cachedConfig.debug) return;
603
+ const color = _constants.COLORS[level.toLowerCase()] || _constants.COLORS.info;
604
+ console.log(`${color}${text}${_constants.COLORS.reset}`);
605
+ } catch (e) {}
606
+ }