kist 0.0.0 → 0.1.31

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 (237) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +298 -3
  3. package/js/actions/CoreActions.d.ts +6 -0
  4. package/js/actions/CoreActions.js +47 -0
  5. package/js/actions/DirectoryCleanAction/DirectoryCleanAction.d.ts +36 -0
  6. package/js/actions/DirectoryCleanAction/DirectoryCleanAction.js +123 -0
  7. package/js/actions/DirectoryCleanAction/index.d.ts +2 -0
  8. package/js/actions/DirectoryCleanAction/index.js +8 -0
  9. package/js/actions/DirectoryCopyAction/DirectoryCopyAction.d.ts +42 -0
  10. package/js/actions/DirectoryCopyAction/DirectoryCopyAction.js +118 -0
  11. package/js/actions/DirectoryCopyAction/index.d.ts +2 -0
  12. package/js/actions/DirectoryCopyAction/index.js +8 -0
  13. package/js/actions/DirectoryCreateAction/DirectoryCreateAction.d.ts +30 -0
  14. package/js/actions/DirectoryCreateAction/DirectoryCreateAction.js +85 -0
  15. package/js/actions/DirectoryCreateAction/index.d.ts +2 -0
  16. package/js/actions/DirectoryCreateAction/index.js +8 -0
  17. package/js/actions/DocumentationAction/DocumentationAction.d.ts +23 -0
  18. package/js/actions/DocumentationAction/DocumentationAction.js +88 -0
  19. package/js/actions/DocumentationAction/index.d.ts +2 -0
  20. package/js/actions/DocumentationAction/index.js +8 -0
  21. package/js/actions/FileCopyAction/FileCopyAction.d.ts +42 -0
  22. package/js/actions/FileCopyAction/FileCopyAction.js +127 -0
  23. package/js/actions/FileCopyAction/index.d.ts +2 -0
  24. package/js/actions/FileCopyAction/index.js +8 -0
  25. package/js/actions/FileRenameAction/FileRenameAction.d.ts +30 -0
  26. package/js/actions/FileRenameAction/FileRenameAction.js +84 -0
  27. package/js/actions/FileRenameAction/index.d.ts +2 -0
  28. package/js/actions/FileRenameAction/index.js +8 -0
  29. package/js/actions/JavaScriptMinifyAction/JavaScriptMinifyAction.d.ts +31 -0
  30. package/js/actions/JavaScriptMinifyAction/JavaScriptMinifyAction.js +98 -0
  31. package/js/actions/JavaScriptMinifyAction/index.d.ts +2 -0
  32. package/js/actions/JavaScriptMinifyAction/index.js +8 -0
  33. package/js/actions/JavaScriptMinifyAction/terser.config.d.ts +27 -0
  34. package/js/actions/JavaScriptMinifyAction/terser.config.js +119 -0
  35. package/js/actions/LintAction/LintAction.d.ts +17 -0
  36. package/js/actions/LintAction/LintAction.js +63 -0
  37. package/js/actions/LintAction/index.d.ts +2 -0
  38. package/js/actions/LintAction/index.js +8 -0
  39. package/js/actions/PackageManagerAction/PackageManagerAction.d.ts +57 -0
  40. package/js/actions/PackageManagerAction/PackageManagerAction.js +161 -0
  41. package/js/actions/PackageManagerAction/index.d.ts +2 -0
  42. package/js/actions/PackageManagerAction/index.js +8 -0
  43. package/js/actions/PackageManagerAction/package.config.d.ts +16 -0
  44. package/js/actions/PackageManagerAction/package.config.js +91 -0
  45. package/js/actions/StyleProcessingAction/StyleProcessingAction.d.ts +34 -0
  46. package/js/actions/StyleProcessingAction/StyleProcessingAction.js +164 -0
  47. package/js/actions/StyleProcessingAction/index.d.ts +2 -0
  48. package/js/actions/StyleProcessingAction/index.js +8 -0
  49. package/js/actions/StyleProcessingAction/postcss.config.compressed.d.ts +10 -0
  50. package/js/actions/StyleProcessingAction/postcss.config.compressed.js +31 -0
  51. package/js/actions/StyleProcessingAction/postcss.config.expanded.d.ts +16 -0
  52. package/js/actions/StyleProcessingAction/postcss.config.expanded.js +45 -0
  53. package/js/actions/SvgPackagerAction/SvgPackagerAction.d.ts +68 -0
  54. package/js/actions/SvgPackagerAction/SvgPackagerAction.js +186 -0
  55. package/js/actions/SvgPackagerAction/index.d.ts +2 -0
  56. package/js/actions/SvgPackagerAction/index.js +8 -0
  57. package/js/actions/SvgReaderAction/SvgReaderAction.d.ts +32 -0
  58. package/js/actions/SvgReaderAction/SvgReaderAction.js +87 -0
  59. package/js/actions/SvgReaderAction/index.d.ts +2 -0
  60. package/js/actions/SvgReaderAction/index.js +8 -0
  61. package/js/actions/SvgSpriteAction/SvgSpriteAction.d.ts +37 -0
  62. package/js/actions/SvgSpriteAction/SvgSpriteAction.js +114 -0
  63. package/js/actions/SvgSpriteAction/index.d.ts +2 -0
  64. package/js/actions/SvgSpriteAction/index.js +8 -0
  65. package/js/actions/SvgSpriteAction/svgsprite.config.d.ts +3 -0
  66. package/js/actions/SvgSpriteAction/svgsprite.config.js +117 -0
  67. package/js/actions/SvgToPngAction/SvgToPngAction.d.ts +28 -0
  68. package/js/actions/SvgToPngAction/SvgToPngAction.js +108 -0
  69. package/js/actions/SvgToPngAction/index.d.ts +2 -0
  70. package/js/actions/SvgToPngAction/index.js +8 -0
  71. package/js/actions/TypeScriptCompilerAction/TypeScriptCompilerAction.d.ts +28 -0
  72. package/js/actions/TypeScriptCompilerAction/TypeScriptCompilerAction.js +96 -0
  73. package/js/actions/TypeScriptCompilerAction/index.d.ts +2 -0
  74. package/js/actions/TypeScriptCompilerAction/index.js +8 -0
  75. package/js/actions/VersionWriteAction/VersionWriteAction.d.ts +45 -0
  76. package/js/actions/VersionWriteAction/VersionWriteAction.js +147 -0
  77. package/js/actions/VersionWriteAction/index.d.ts +2 -0
  78. package/js/actions/VersionWriteAction/index.js +8 -0
  79. package/js/cli/ArgumentParser.d.ts +62 -0
  80. package/js/cli/ArgumentParser.js +118 -0
  81. package/js/cli.d.ts +6 -0
  82. package/js/cli.js +58 -0
  83. package/js/core/abstract/AbstractProcess.d.ts +62 -0
  84. package/js/core/abstract/AbstractProcess.js +96 -0
  85. package/js/core/abstract/AbstractValidator.d.ts +72 -0
  86. package/js/core/abstract/AbstractValidator.js +128 -0
  87. package/js/core/config/ConfigLoader.d.ts +47 -0
  88. package/js/core/config/ConfigLoader.js +130 -0
  89. package/js/core/config/ConfigStore.d.ts +53 -0
  90. package/js/core/config/ConfigStore.js +136 -0
  91. package/js/core/config/defaultConfig.d.ts +5 -0
  92. package/js/core/config/defaultConfig.js +131 -0
  93. package/js/core/pipeline/Action.d.ts +60 -0
  94. package/js/core/pipeline/Action.js +77 -0
  95. package/js/core/pipeline/ActionRegistry.d.ts +80 -0
  96. package/js/core/pipeline/ActionRegistry.js +180 -0
  97. package/js/core/pipeline/Pipeline.d.ts +42 -0
  98. package/js/core/pipeline/Pipeline.js +107 -0
  99. package/js/core/pipeline/PipelineManager.d.ts +55 -0
  100. package/js/core/pipeline/PipelineManager.js +164 -0
  101. package/js/core/pipeline/Stage.d.ts +45 -0
  102. package/js/core/pipeline/Stage.js +110 -0
  103. package/js/core/pipeline/Step.d.ts +26 -0
  104. package/js/core/pipeline/Step.js +85 -0
  105. package/js/core/validation/OptionsValidator.d.ts +43 -0
  106. package/js/core/validation/OptionsValidator.js +123 -0
  107. package/js/index.d.ts +3 -0
  108. package/js/index.js +36 -0
  109. package/js/interface/ActionInterface.d.ts +57 -0
  110. package/js/interface/ActionInterface.js +5 -0
  111. package/js/interface/ActionPlugin.d.ts +4 -0
  112. package/js/interface/ActionPlugin.js +5 -0
  113. package/js/interface/ConfigInterface.d.ts +43 -0
  114. package/js/interface/ConfigInterface.js +5 -0
  115. package/js/interface/LiveOptionsInterface.d.ts +42 -0
  116. package/js/interface/LiveOptionsInterface.js +2 -0
  117. package/js/interface/MetadataInterface.d.ts +95 -0
  118. package/js/interface/MetadataInterface.js +2 -0
  119. package/js/interface/OptionsInterface.d.ts +45 -0
  120. package/js/interface/OptionsInterface.js +5 -0
  121. package/js/interface/PipelineOptionsInterface.d.ts +66 -0
  122. package/js/interface/PipelineOptionsInterface.js +5 -0
  123. package/js/interface/StageInterface.d.ts +79 -0
  124. package/js/interface/StageInterface.js +5 -0
  125. package/js/interface/StepInterface.d.ts +66 -0
  126. package/js/interface/StepInterface.js +5 -0
  127. package/js/interface/StepOptionsInterface.d.ts +38 -0
  128. package/js/interface/StepOptionsInterface.js +21 -0
  129. package/js/interface/index.d.ts +7 -0
  130. package/js/interface/index.js +3 -0
  131. package/js/kist.d.ts +58 -0
  132. package/js/kist.js +145 -0
  133. package/js/live/LiveServer.d.ts +95 -0
  134. package/js/live/LiveServer.js +233 -0
  135. package/js/live/LiveWatcher.d.ts +45 -0
  136. package/js/live/LiveWatcher.js +140 -0
  137. package/js/logger/Logger.d.ts +94 -0
  138. package/js/logger/Logger.js +151 -0
  139. package/js/logger/LoggerStyles.d.ts +23 -0
  140. package/js/logger/LoggerStyles.js +30 -0
  141. package/js/types/ActionOptionsType.d.ts +8 -0
  142. package/js/types/ActionOptionsType.js +2 -0
  143. package/js/types/index.d.ts +1 -0
  144. package/js/types/index.js +3 -0
  145. package/package.json +93 -7
  146. package/ts/actions/CoreActions.ts +64 -0
  147. package/ts/actions/DirectoryCleanAction/DirectoryCleanAction.ts +121 -0
  148. package/ts/actions/DirectoryCleanAction/index.ts +11 -0
  149. package/ts/actions/DirectoryCopyAction/DirectoryCopyAction.ts +118 -0
  150. package/ts/actions/DirectoryCopyAction/index.ts +11 -0
  151. package/ts/actions/DirectoryCreateAction/DirectoryCreateAction.ts +81 -0
  152. package/ts/actions/DirectoryCreateAction/index.ts +11 -0
  153. package/ts/actions/DocumentationAction/DocumentationAction.ts +100 -0
  154. package/ts/actions/DocumentationAction/index.ts +11 -0
  155. package/ts/actions/FileCopyAction/FileCopyAction.ts +125 -0
  156. package/ts/actions/FileCopyAction/index.ts +11 -0
  157. package/ts/actions/FileRenameAction/FileRenameAction.ts +82 -0
  158. package/ts/actions/FileRenameAction/index.ts +11 -0
  159. package/ts/actions/JavaScriptMinifyAction/JavaScriptMinifyAction.ts +109 -0
  160. package/ts/actions/JavaScriptMinifyAction/index.ts +11 -0
  161. package/ts/actions/JavaScriptMinifyAction/terser.config.ts +177 -0
  162. package/ts/actions/LintAction/LintAction.ts +67 -0
  163. package/ts/actions/LintAction/index.ts +11 -0
  164. package/ts/actions/PackageManagerAction/PackageManagerAction.ts +176 -0
  165. package/ts/actions/PackageManagerAction/index.ts +11 -0
  166. package/ts/actions/PackageManagerAction/package.config.ts +94 -0
  167. package/ts/actions/SassDocAction/SassDocAction.ts +66 -0
  168. package/ts/actions/SassDocAction/index.ts +11 -0
  169. package/ts/actions/StyleProcessingAction/StyleProcessingAction.ts +142 -0
  170. package/ts/actions/StyleProcessingAction/index.ts +11 -0
  171. package/ts/actions/StyleProcessingAction/postcss.config.compressed.ts +31 -0
  172. package/ts/actions/StyleProcessingAction/postcss.config.expanded.ts +47 -0
  173. package/ts/actions/SvgPackagerAction/SvgPackagerAction.ts +187 -0
  174. package/ts/actions/SvgPackagerAction/index.ts +11 -0
  175. package/ts/actions/SvgReaderAction/SvgReaderAction.ts +77 -0
  176. package/ts/actions/SvgReaderAction/index.ts +11 -0
  177. package/ts/actions/SvgSpriteAction/SvgSpriteAction.ts +127 -0
  178. package/ts/actions/SvgSpriteAction/index.ts +11 -0
  179. package/ts/actions/SvgSpriteAction/svgsprite.config.ts +123 -0
  180. package/ts/actions/SvgToPngAction/SvgToPngAction.ts +113 -0
  181. package/ts/actions/SvgToPngAction/index.ts +11 -0
  182. package/ts/actions/TypeScriptCompilerAction/TypeScriptCompilerAction.ts +117 -0
  183. package/ts/actions/TypeScriptCompilerAction/index.ts +11 -0
  184. package/ts/actions/VersionWriteAction/VersionWriteAction.ts +174 -0
  185. package/ts/actions/VersionWriteAction/index.ts +11 -0
  186. package/ts/actions/index.ts +0 -0
  187. package/ts/cli/ArgumentParser.ts +150 -0
  188. package/ts/cli/index.ts +1 -0
  189. package/ts/cli.ts +56 -0
  190. package/ts/core/abstract/AbstractProcess.ts +109 -0
  191. package/ts/core/abstract/AbstractSingleton.ts +46 -0
  192. package/ts/core/abstract/AbstractValidator.ts +167 -0
  193. package/ts/core/abstract/index.ts +0 -0
  194. package/ts/core/config/ConfigLoader.ts +141 -0
  195. package/ts/core/config/ConfigStore copy.ts +201 -0
  196. package/ts/core/config/ConfigStore.ts +157 -0
  197. package/ts/core/config/defaultConfig.ts +154 -0
  198. package/ts/core/config/index.ts +0 -0
  199. package/ts/core/index.ts +34 -0
  200. package/ts/core/pipeline/Action.ts +101 -0
  201. package/ts/core/pipeline/ActionRegistry.ts +216 -0
  202. package/ts/core/pipeline/Pipeline.ts +121 -0
  203. package/ts/core/pipeline/PipelineManager.ts +170 -0
  204. package/ts/core/pipeline/Stage.ts +131 -0
  205. package/ts/core/pipeline/Step.ts +96 -0
  206. package/ts/core/pipeline/index.ts +0 -0
  207. package/ts/core/validation/ActionValidator.ts +97 -0
  208. package/ts/core/validation/ConfigValidator.ts +103 -0
  209. package/ts/core/validation/OptionsValidator.ts +179 -0
  210. package/ts/core/validation/StageValidator.ts +175 -0
  211. package/ts/core/validation/StepValidator.ts +203 -0
  212. package/ts/core/validation/index.ts +0 -0
  213. package/ts/index.ts +26 -0
  214. package/ts/interface/ActionInterface.ts +70 -0
  215. package/ts/interface/ActionPlugin.ts +14 -0
  216. package/ts/interface/ConfigInterface.ts +55 -0
  217. package/ts/interface/File.ts +24 -0
  218. package/ts/interface/LiveOptionsInterface.ts +46 -0
  219. package/ts/interface/MetadataInterface.ts +105 -0
  220. package/ts/interface/OptionsInterface.ts +58 -0
  221. package/ts/interface/PackageJson.ts +171 -0
  222. package/ts/interface/PipelineOptionsInterface.ts +74 -0
  223. package/ts/interface/SVG.ts +84 -0
  224. package/ts/interface/StageInterface.ts +96 -0
  225. package/ts/interface/StepInterface.ts +83 -0
  226. package/ts/interface/StepOptionsInterface.ts +57 -0
  227. package/ts/interface/index.ts +9 -0
  228. package/ts/kist.ts +161 -0
  229. package/ts/live/LiveServer.ts +311 -0
  230. package/ts/live/LiveWatcher.ts +150 -0
  231. package/ts/live/index.ts +11 -0
  232. package/ts/logger/Logger.ts +187 -0
  233. package/ts/logger/LoggerStyles.ts +28 -0
  234. package/ts/logger/index.ts +0 -0
  235. package/ts/types/ActionOptionsType.ts +10 -0
  236. package/ts/types/index.ts +3 -0
  237. package/index.js +0 -3
package/ts/kist.ts ADDED
@@ -0,0 +1,161 @@
1
+ // ============================================================================
2
+ // Import
3
+ // ============================================================================
4
+
5
+ import { AbstractProcess } from "./core/abstract/AbstractProcess";
6
+ import { ConfigStore } from "./core/config/ConfigStore";
7
+ import { ActionRegistry } from "./core/pipeline/ActionRegistry";
8
+ import { PipelineManager } from "./core/pipeline/PipelineManager";
9
+ import { LiveServer } from "./live/LiveServer";
10
+ import { LiveWatcher } from "./live/LiveWatcher";
11
+
12
+ // ============================================================================
13
+ // Class
14
+ // ============================================================================
15
+
16
+ /**
17
+ * The Kist class encapsulates the kist CLI functionality.
18
+ * It manages the pipeline execution, configuration loading, and live reload.
19
+ */
20
+ export class Kist extends AbstractProcess {
21
+ // Constructor
22
+ // ========================================================================
23
+
24
+ /**
25
+ * Constructs the Kist class instance and initializes necessary components.
26
+ */
27
+ constructor() {
28
+ super();
29
+ this.logDebug("Kist initialized.");
30
+ }
31
+
32
+ // Methods
33
+ // ========================================================================
34
+
35
+ /**
36
+ * Executes the Kist workflow.
37
+ *
38
+ * This method orchestrates the execution of the Kist pipeline, starting
39
+ * from initializing the ActionRegistry, loading configuration settings,
40
+ * running the pipeline stages through the `PipelineManager`, and
41
+ * optionally enabling live reload for real-time updates.
42
+ *
43
+ * @returns {Promise<void>} Resolves when the workflow completes successfully.
44
+ * @example
45
+ * const Kist = new Kist();
46
+ * Kist.run().then(() => console.log("Pipeline execution complete."));
47
+ */
48
+ public async run(): Promise<void> {
49
+ this.logInfo("Starting Kist workflow...");
50
+
51
+ try {
52
+ // Initialize the ActionRegistry with available actions
53
+ this.initializeActionRegistry();
54
+
55
+ // Create and run the PipelineManager
56
+ const liveReloadEnabled = ConfigStore.getInstance().get<boolean>(
57
+ "options.live.enabled",
58
+ );
59
+ const liveReloadServer = liveReloadEnabled
60
+ ? new LiveServer()
61
+ : null;
62
+
63
+ const pipelineManager = new PipelineManager(liveReloadServer!);
64
+ await pipelineManager.runPipeline();
65
+
66
+ // Setup live reload if enabled
67
+ if (liveReloadEnabled) {
68
+ this.setupLiveReload(pipelineManager, liveReloadServer!);
69
+ }
70
+ } catch (error) {
71
+ this.handleError(error);
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Initializes the ActionRegistry with available actions.
77
+ * Automatically registers core actions and discovers external plugins.
78
+ */
79
+ private initializeActionRegistry(): void {
80
+ this.logInfo("Initializing ActionRegistry...");
81
+ ActionRegistry.initialize();
82
+ this.logInfo("ActionRegistry initialized successfully.");
83
+ }
84
+
85
+ /**
86
+ * Sets up live reload functionality.
87
+ * Monitors file changes and restarts the pipeline when updates are detected.
88
+ *
89
+ * @param pipelineManager - The manager responsible for the pipeline process.
90
+ * @param liveReloadServer - The server for live reload connections.
91
+ */
92
+ private setupLiveReload(
93
+ pipelineManager: PipelineManager,
94
+ liveReloadServer: LiveServer,
95
+ ): void {
96
+ this.logInfo("Enabling live reload functionality...");
97
+
98
+ new LiveWatcher((filePath) => {
99
+ this.logInfo(
100
+ `Detected change in: ${filePath}. Restarting pipeline...`,
101
+ );
102
+ pipelineManager.restartPipelineWithDelay(500);
103
+ });
104
+
105
+ pipelineManager.restartPipeline();
106
+ this.registerShutdownHandlers(pipelineManager, liveReloadServer);
107
+ }
108
+
109
+ /**
110
+ * Registers handlers for graceful shutdown signals.
111
+ *
112
+ * @param pipelineManager - The manager responsible for the pipeline process.
113
+ * @param liveReloadServer - The server for live reload connections.
114
+ */
115
+ private registerShutdownHandlers(
116
+ pipelineManager: PipelineManager,
117
+ liveReloadServer: LiveServer,
118
+ ): void {
119
+ process.on("SIGINT", () =>
120
+ this.handleShutdown(pipelineManager, liveReloadServer),
121
+ );
122
+ process.on("SIGTERM", () =>
123
+ this.handleShutdown(pipelineManager, liveReloadServer),
124
+ );
125
+ }
126
+
127
+ /**
128
+ * Handles graceful shutdown of the pipeline and live reload server.
129
+ *
130
+ * @param pipelineManager - The manager responsible for the pipeline process.
131
+ * @param liveReloadServer - The server for live reload connections.
132
+ */
133
+ private async handleShutdown(
134
+ pipelineManager: PipelineManager,
135
+ liveReloadServer: LiveServer,
136
+ ): Promise<void> {
137
+ this.logInfo("Shutdown signal received. Shutting down...");
138
+
139
+ try {
140
+ await pipelineManager.stopPipeline();
141
+ await liveReloadServer.shutdown();
142
+ this.logInfo("Shutdown completed successfully.");
143
+ } catch (error) {
144
+ this.logError("Error during shutdown.", error);
145
+ } finally {
146
+ process.exit(0);
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Handles errors occurring during the execution of the Kist workflow.
152
+ *
153
+ * @param error - The error object to log and handle.
154
+ */
155
+ private handleError(error: unknown): void {
156
+ const errorMessage =
157
+ error instanceof Error ? error.message : String(error);
158
+ this.logError(`An error occurred: ${errorMessage}`, error);
159
+ process.exit(1);
160
+ }
161
+ }
@@ -0,0 +1,311 @@
1
+ // ============================================================================
2
+ // Import
3
+ // ============================================================================
4
+
5
+ import express, { NextFunction, Request, Response } from "express";
6
+ import rateLimit from "express-rate-limit";
7
+ import { Server } from "http";
8
+ import path from "path";
9
+ import { WebSocket, WebSocketServer } from "ws";
10
+ import { AbstractProcess } from "../core/abstract/AbstractProcess";
11
+ import { ConfigStore } from "../core/config/ConfigStore";
12
+ import { LiveOptionsInterface } from "../interface";
13
+ import { OptionsInterface } from "../interface/OptionsInterface";
14
+
15
+ // ============================================================================
16
+ // Class
17
+ // ============================================================================
18
+
19
+ /**
20
+ * LiveServer class provides functionality to serve static files,
21
+ * inject live reload scripts into HTML responses, and manage WebSocket
22
+ * connections to enable live reload capabilities.
23
+ */
24
+ export class LiveServer extends AbstractProcess {
25
+ // Parameters
26
+ // ========================================================================
27
+
28
+ /**
29
+ * Express application
30
+ */
31
+ private app = express();
32
+
33
+ /**
34
+ * The underlying HTTP server used by the LiveServer.
35
+ * Handles incoming HTTP requests and serves static files.
36
+ */
37
+ private server: Server;
38
+
39
+ /**
40
+ * The WebSocket server responsible for managing WebSocket connections.
41
+ * Enables real-time communication with connected clients.
42
+ */
43
+ private wss: WebSocketServer;
44
+
45
+ /**
46
+ * A set of WebSocket clients currently connected to the server.
47
+ * Each client represents an active WebSocket connection.
48
+ */
49
+ private clients: Set<WebSocket> = new Set();
50
+
51
+ /**
52
+ * The port number on which the server is running.
53
+ * Defaults to 3000 if not specified in the configuration.
54
+ */
55
+ private port: number;
56
+
57
+ /**
58
+ * The root directory from which static files are served.
59
+ * Defaults to the "public" folder in the current working directory.
60
+ */
61
+ private root: string;
62
+
63
+ /**
64
+ * An array of paths to watch for changes.
65
+ * When a file within these paths changes, the server triggers live reload.
66
+ */
67
+ private watchPaths: string[];
68
+
69
+ /**
70
+ * An array of paths or patterns to ignore during file watching.
71
+ * Prevents unnecessary reloads caused by changes in these paths.
72
+ * Defaults to ignoring the "node_modules" directory.
73
+ */
74
+ private ignoredPaths: string[];
75
+
76
+ // Constructor
77
+ // ========================================================================
78
+
79
+ /**
80
+ * Initializes the LiveServer.
81
+ // * @param port - The port on which the server will listen.
82
+ */
83
+ constructor() {
84
+ super();
85
+
86
+ const configStore = ConfigStore.getInstance();
87
+
88
+ const liveReloadOptions: LiveOptionsInterface =
89
+ configStore.get<OptionsInterface["live"]>("options.live") || {};
90
+
91
+ // Extract and apply live reload options with defaults
92
+ this.port = liveReloadOptions.port || 3000;
93
+ this.root = path.resolve(
94
+ process.cwd(),
95
+ liveReloadOptions.root || "public",
96
+ );
97
+ this.watchPaths = (
98
+ liveReloadOptions.watchPaths || [
99
+ "src/**/*",
100
+ "config/**/*",
101
+ "pack.yaml",
102
+ ]
103
+ ).map((p: string) => path.resolve(process.cwd(), p));
104
+ this.ignoredPaths = (
105
+ liveReloadOptions.ignoredPaths || ["node_modules"]
106
+ ).map((p: string) => path.resolve(process.cwd(), p));
107
+
108
+ // Log initialization details
109
+ this.logInitializationDetails();
110
+
111
+ // Initialize server
112
+ // this.initializeServer();
113
+
114
+ // Start the HTTP server
115
+ this.server = this.app.listen(this.port, () => {
116
+ this.logInfo(
117
+ `Live Server running at http://localhost:${this.port}`,
118
+ );
119
+ });
120
+
121
+ // Initialize WebSocket server
122
+ this.wss = new WebSocketServer({ server: this.server });
123
+
124
+ // Set up rate limiting
125
+ this.setupRateLimiter();
126
+
127
+ // Set up WebSocket handlers
128
+ this.setupWebSocketHandlers();
129
+
130
+ // Set up middleware
131
+ this.setupMiddleware();
132
+ }
133
+
134
+ // Methods
135
+ // ========================================================================
136
+
137
+ /**
138
+ * Initializes the HTTP server and WebSocket server.
139
+ */
140
+ private initializeServer(): void {
141
+ // Start the HTTP server
142
+ this.server = this.app.listen(this.port, () => {
143
+ this.logInfo(
144
+ `Live Server running at http://localhost:${this.port}`,
145
+ );
146
+ });
147
+
148
+ // Initialize WebSocket server
149
+ this.wss = new WebSocketServer({ server: this.server });
150
+ }
151
+
152
+ /** Logs initialization details for the LiveServer. */
153
+ private logInitializationDetails(): void {
154
+ this.logInfo(`LiveServer initialized with port: ${this.port}`);
155
+ this.logInfo(`Serving static files from: ${this.root}`);
156
+ this.logInfo(`Watching paths: ${JSON.stringify(this.watchPaths)}`);
157
+ this.logInfo(`Ignoring paths: ${JSON.stringify(this.ignoredPaths)}`);
158
+ }
159
+
160
+ /**
161
+ * Sets up rate limiting middleware to prevent abuse of HTTP requests.
162
+ */
163
+ private setupRateLimiter(): void {
164
+ const limiter = rateLimit({
165
+ windowMs: 15 * 60 * 1000, // 15 minutes
166
+ max: 100, // Limit each IP to 100 requests per windowMs
167
+ message: "Too many requests from this IP, please try again later.",
168
+ });
169
+ this.app.use(limiter);
170
+ }
171
+
172
+ /**
173
+ * Sets up WebSocket handlers to manage client connections.
174
+ */
175
+ private setupWebSocketHandlers(): void {
176
+ this.wss.on("connection", (ws: WebSocket) => {
177
+ this.logInfo("New WebSocket connection established.");
178
+ this.clients.add(ws);
179
+
180
+ ws.on("message", (message) => {
181
+ this.logInfo(
182
+ `WebSocket message received: ${message.toString()}`,
183
+ );
184
+ });
185
+ ws.on("close", () => {
186
+ this.logInfo("WebSocket connection closed.");
187
+ this.clients.delete(ws);
188
+ });
189
+ ws.on("error", (error) => {
190
+ console.error("WebSocket encountered an error:", error);
191
+ this.clients.delete(ws);
192
+ });
193
+ });
194
+ }
195
+
196
+ /**
197
+ * Sets up middleware for serving static files and injecting the live
198
+ * reload script into HTML files.
199
+ */
200
+ private setupMiddleware(): void {
201
+ // Securely serve static files from the "public" directory
202
+ // const publicPath = path.resolve(
203
+ // __dirname,
204
+ // "public"
205
+ // );
206
+ this.logInfo(`Resolved public directory: ${this.root}`);
207
+ this.logInfo(`Serving static files from: ${this.root}`);
208
+ this.app.use(express.static(this.root));
209
+ // Middleware to inject the live reload script into HTML files
210
+ this.app.use(this.injectLiveReloadScript.bind(this));
211
+ }
212
+
213
+ /**
214
+ * Middleware function to inject the live reload script into HTML
215
+ * responses. Prevents directory traversal attacks by sanitizing the
216
+ * requested file path.
217
+ * @param req - The HTTP request object.
218
+ * @param res - The HTTP response object.
219
+ * @param next - The next middleware function.
220
+ */
221
+ private injectLiveReloadScript(
222
+ req: Request,
223
+ res: Response,
224
+ next: NextFunction,
225
+ ): void {
226
+ if (req.url.endsWith(".html")) {
227
+ const sanitizedPath = path.join(
228
+ path.resolve(__dirname, "public"),
229
+ // Prevent directory traversal
230
+ path.normalize(req.url).replace(/^(\.\.(\/|\\|$))+/g, ""),
231
+ );
232
+
233
+ res.sendFile(sanitizedPath, (err) => {
234
+ if (err) {
235
+ console.error("Error sending HTML file:", err);
236
+ next(err);
237
+ } else {
238
+ res.write(
239
+ `<script>
240
+ const ws = new WebSocket("ws://localhost:${this.port}");
241
+ ws.onmessage = (event) => {
242
+ if (event.data === "reload") {
243
+ this.logInfo("Reloading page...");
244
+ window.location.reload();
245
+ }
246
+ };
247
+ </script>`,
248
+ );
249
+ res.end();
250
+ }
251
+ });
252
+ } else {
253
+ next();
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Sends a reload signal to all connected WebSocket clients.
259
+ */
260
+ public reloadClients(): void {
261
+ this.logInfo("Reloading all connected clients...");
262
+
263
+ this.clients.forEach((client) => {
264
+ if (client.readyState === WebSocket.OPEN) {
265
+ client.send("reload");
266
+ }
267
+ });
268
+ }
269
+
270
+ /**
271
+ * Gracefully shuts down the server and all WebSocket connections.
272
+ */
273
+ public async shutdown(): Promise<void> {
274
+ this.logInfo("Shutting down Live Reload Server...");
275
+
276
+ this.clients.forEach((client) => client.close());
277
+ this.wss.close();
278
+
279
+ await new Promise<void>((resolve, reject) => {
280
+ this.server.close((err) => {
281
+ if (err) {
282
+ if (
283
+ this.isErrnoException(err) &&
284
+ err.code === "ERR_SERVER_NOT_RUNNING"
285
+ ) {
286
+ this.logWarn(
287
+ "Server is not running, skipping shutdown.",
288
+ );
289
+ resolve();
290
+ } else {
291
+ this.logError("Error shutting down server:", err);
292
+ reject(err);
293
+ }
294
+ } else {
295
+ resolve();
296
+ }
297
+ });
298
+ });
299
+
300
+ this.logInfo("Live Reload Server has been shut down.");
301
+ }
302
+
303
+ /**
304
+ * Type guard to check if an error is an instance of NodeJS.ErrnoException.
305
+ * @param error - The error to check.
306
+ * @returns True if the error has a `code` property.
307
+ */
308
+ private isErrnoException(error: unknown): error is NodeJS.ErrnoException {
309
+ return typeof error === "object" && error !== null && "code" in error;
310
+ }
311
+ }
@@ -0,0 +1,150 @@
1
+ // ============================================================================
2
+ // Import
3
+ // ============================================================================
4
+
5
+ import chokidar, { FSWatcher } from "chokidar";
6
+ import { AbstractProcess } from "../core/abstract/AbstractProcess";
7
+ import { ConfigStore } from "../core/config/ConfigStore";
8
+ import { LiveOptionsInterface } from "../interface";
9
+ import { OptionsInterface } from "../interface/OptionsInterface";
10
+
11
+ // ============================================================================
12
+ // Class
13
+ // ============================================================================
14
+
15
+ /**
16
+ * LiveWatcher is a utility class for monitoring file and directory changes.
17
+ * It leverages the `chokidar` library to efficiently detect file changes and
18
+ * trigger appropriate callbacks.
19
+ */
20
+ export class LiveWatcher extends AbstractProcess {
21
+ // Parameters
22
+ // ========================================================================
23
+
24
+ /**
25
+ * The chokidar file watcher instance.
26
+ */
27
+ private watcher: FSWatcher | null = null;
28
+
29
+ private pathsToWatch: string[];
30
+ private ignoredPaths: string[];
31
+ private onChange: (filePath: string) => void;
32
+
33
+ // Constructor
34
+ // ========================================================================
35
+
36
+ /**
37
+ * Creates an instance of LiveWatcher.
38
+ * @param pathsToWatch - An array of paths to monitor for changes.
39
+ * @param ignoredPaths - A regular expression to specify paths or patterns
40
+ * to exclude from watching.
41
+ * @param onChange - A callback function that is executed when a file
42
+ * change is detected.
43
+ */
44
+ constructor(
45
+ // private pathsToWatch: string[],
46
+ // private ignoredPaths: RegExp,
47
+ onChange: (filePath: string) => void,
48
+ ) {
49
+ super();
50
+
51
+ // Retrieve live reload configuration from ConfigStore
52
+ const liveReloadOptions: LiveOptionsInterface =
53
+ ConfigStore.getInstance().get<OptionsInterface["live"]>(
54
+ "options.live",
55
+ ) || {};
56
+
57
+ this.pathsToWatch = liveReloadOptions.watchPaths ?? [
58
+ "src/**/*",
59
+ "config/**/*",
60
+ "pack.yaml",
61
+ ];
62
+ this.ignoredPaths = liveReloadOptions.ignoredPaths ?? ["node_modules"];
63
+
64
+ this.onChange = onChange;
65
+
66
+ this.startWatching();
67
+ }
68
+
69
+ // Methods
70
+ // ========================================================================
71
+
72
+ /**
73
+ * Initializes and configures the chokidar watcher to monitor files and
74
+ * directories.
75
+ */
76
+ private setupWatchers() {
77
+ if (!this.watcher) return;
78
+
79
+ this.watcher
80
+ .on("ready", () => {
81
+ this.logInfo(
82
+ "File watching is active. Waiting for changes...",
83
+ );
84
+ })
85
+ .on("change", (filePath) => {
86
+ this.logInfo(`File changed: ${filePath}`);
87
+ try {
88
+ this.onChange(filePath);
89
+ } catch (error) {
90
+ this.logError(
91
+ `Error handling file change for ${filePath}:`,
92
+ error,
93
+ );
94
+ }
95
+ })
96
+ .on("error", (error) => {
97
+ this.logError("Watcher encountered an error:", error);
98
+ });
99
+ }
100
+
101
+ /**
102
+ * Starts the file watcher if it is not already running. If the watcher
103
+ * was stopped previously, it re-initializes the watcher.
104
+ */
105
+ public startWatching() {
106
+ if (this.watcher) {
107
+ this.logInfo("Watcher is already running.");
108
+ return;
109
+ }
110
+
111
+ this.logInfo("Starting file watcher...");
112
+ this.watcher = chokidar.watch(this.pathsToWatch, {
113
+ ignored: this.ignoredPaths,
114
+ persistent: true,
115
+ // Prevents initial "add" events on startup
116
+ ignoreInitial: true,
117
+ awaitWriteFinish: {
118
+ // Polling interval to check for file stability
119
+ pollInterval: 100,
120
+ // Waits for file to finish writing
121
+ stabilityThreshold: 100,
122
+ },
123
+ });
124
+
125
+ this.setupWatchers();
126
+ }
127
+
128
+ /**
129
+ * Stops the file watcher and releases its resources. This is useful when
130
+ * you need to clean up or reinitialize the watcher.
131
+ */
132
+ public async stopWatching() {
133
+ if (this.watcher) {
134
+ await this.watcher.close();
135
+ this.logInfo("File watching has been stopped.");
136
+ this.watcher = null;
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Restarts the file watcher by first stopping the existing watcher (if
142
+ * any) and then starting a new one. This can be useful in scenarios where
143
+ * watcher configurations or paths have changed.
144
+ */
145
+ public async restartWatcher() {
146
+ this.logInfo("Restarting file watcher...");
147
+ await this.stopWatching();
148
+ this.startWatching();
149
+ }
150
+ }
@@ -0,0 +1,11 @@
1
+ // ============================================================================
2
+ // Live Reload Core - Entry Point
3
+ // ============================================================================
4
+
5
+ // Export core classes for live reload functionality
6
+
7
+ // Manages server operations and live reload communication
8
+ export { LiveServer } from "./LiveServer";
9
+
10
+ // Watches for file changes and triggers reload actions
11
+ export { LiveWatcher } from "./LiveWatcher";