odac 0.9.0 → 1.0.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 (208) hide show
  1. package/.github/workflows/auto-pr-description.yml +0 -2
  2. package/.github/workflows/codeql.yml +46 -0
  3. package/.github/workflows/release.yml +13 -6
  4. package/.github/workflows/test-coverage.yml +10 -9
  5. package/.releaserc.js +9 -6
  6. package/CHANGELOG.md +62 -150
  7. package/CODE_OF_CONDUCT.md +1 -1
  8. package/CONTRIBUTING.md +8 -8
  9. package/LICENSE +21 -661
  10. package/README.md +12 -12
  11. package/SECURITY.md +4 -4
  12. package/bin/odac.js +101 -0
  13. package/{framework/web/candy.js → client/odac.js} +310 -44
  14. package/docs/backend/01-overview/{01-whats-in-the-candy-box.md → 01-whats-in-the-odac-box.md} +4 -2
  15. package/docs/backend/01-overview/02-super-handy-helper-functions.md +29 -1
  16. package/docs/backend/01-overview/03-development-server.md +11 -11
  17. package/docs/backend/02-structure/01-typical-project-layout.md +4 -4
  18. package/docs/backend/03-config/00-configuration-overview.md +6 -6
  19. package/docs/backend/03-config/01-database-connection.md +1 -1
  20. package/docs/backend/03-config/02-static-route-mapping-optional.md +4 -4
  21. package/docs/backend/03-config/04-environment-variables.md +20 -20
  22. package/docs/backend/03-config/05-early-hints.md +4 -4
  23. package/docs/backend/04-routing/01-basic-page-routes.md +4 -4
  24. package/docs/backend/04-routing/02-controller-less-view-routes.md +5 -5
  25. package/docs/backend/04-routing/03-api-and-data-routes.md +3 -3
  26. package/docs/backend/04-routing/04-authentication-aware-routes.md +5 -5
  27. package/docs/backend/04-routing/05-advanced-routing.md +3 -3
  28. package/docs/backend/04-routing/06-error-pages.md +17 -17
  29. package/docs/backend/04-routing/07-cron-jobs.md +13 -13
  30. package/docs/backend/04-routing/08-middleware.md +214 -0
  31. package/docs/backend/04-routing/09-websocket-auth-middleware.md +292 -0
  32. package/docs/backend/04-routing/09-websocket-examples.md +381 -0
  33. package/docs/backend/04-routing/09-websocket-quick-reference.md +211 -0
  34. package/docs/backend/04-routing/09-websocket.md +298 -0
  35. package/docs/backend/05-controllers/01-how-to-build-a-controller.md +3 -3
  36. package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +41 -0
  37. package/docs/backend/05-controllers/03-controller-classes.md +19 -19
  38. package/docs/backend/05-forms/01-custom-forms.md +114 -114
  39. package/docs/backend/05-forms/02-automatic-database-insert.md +82 -82
  40. package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +26 -26
  41. package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +10 -10
  42. package/docs/backend/07-views/01-the-view-directory.md +1 -1
  43. package/docs/backend/07-views/02-rendering-a-view.md +22 -22
  44. package/docs/backend/07-views/03-template-syntax.md +52 -52
  45. package/docs/backend/07-views/03-variables.md +84 -84
  46. package/docs/backend/07-views/04-request-data.md +57 -57
  47. package/docs/backend/07-views/05-conditionals.md +78 -78
  48. package/docs/backend/07-views/06-loops.md +114 -114
  49. package/docs/backend/07-views/07-translations.md +66 -66
  50. package/docs/backend/07-views/08-backend-javascript.md +103 -103
  51. package/docs/backend/07-views/09-comments.md +71 -71
  52. package/docs/backend/08-database/01-database-connection.md +8 -8
  53. package/docs/backend/08-database/02-using-mysql.md +49 -49
  54. package/docs/backend/09-validation/01-the-validator-service.md +38 -38
  55. package/docs/backend/10-authentication/01-user-logins-with-authjs.md +15 -15
  56. package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +10 -10
  57. package/docs/backend/10-authentication/03-register.md +12 -12
  58. package/docs/backend/10-authentication/{04-candy-register-forms.md → 04-odac-register-forms.md} +141 -141
  59. package/docs/backend/10-authentication/05-session-management.md +10 -10
  60. package/docs/backend/10-authentication/{06-candy-login-forms.md → 06-odac-login-forms.md} +125 -125
  61. package/docs/backend/11-mail/01-the-mail-service.md +5 -5
  62. package/docs/backend/12-streaming/01-streaming-overview.md +96 -54
  63. package/docs/backend/13-utilities/{01-candy-var.md → 01-odac-var.md} +109 -109
  64. package/docs/frontend/01-overview/01-introduction.md +30 -30
  65. package/docs/frontend/02-ajax-navigation/01-quick-start.md +45 -45
  66. package/docs/frontend/02-ajax-navigation/02-configuration.md +14 -14
  67. package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +36 -36
  68. package/docs/frontend/03-forms/01-form-handling.md +32 -32
  69. package/docs/frontend/04-api-requests/01-get-post.md +33 -33
  70. package/docs/frontend/05-streaming/01-client-streaming.md +15 -15
  71. package/docs/frontend/06-websocket/00-overview.md +76 -0
  72. package/docs/frontend/06-websocket/01-websocket-client.md +139 -0
  73. package/docs/frontend/06-websocket/02-shared-websocket.md +149 -0
  74. package/docs/index.json +49 -11
  75. package/eslint.config.mjs +6 -6
  76. package/{framework/index.js → index.js} +1 -1
  77. package/package.json +14 -39
  78. package/{framework/src → src}/Auth.js +59 -59
  79. package/{framework/src → src}/Config.js +3 -3
  80. package/{framework/src → src}/Lang.js +7 -7
  81. package/{framework/src → src}/Mail.js +5 -5
  82. package/{framework/src → src}/Mysql.js +42 -42
  83. package/src/Odac.js +112 -0
  84. package/{framework/src → src}/Request.js +38 -36
  85. package/{framework/src → src}/Route/Internal.js +116 -116
  86. package/src/Route/Middleware.js +75 -0
  87. package/src/Route.js +621 -0
  88. package/src/Server.js +22 -0
  89. package/{framework/src → src}/Stream.js +11 -3
  90. package/{framework/src → src}/Validator.js +21 -21
  91. package/{framework/src → src}/Var.js +5 -5
  92. package/{framework/src → src}/View/EarlyHints.js +1 -1
  93. package/{framework/src → src}/View/Form.js +69 -69
  94. package/{framework/src → src}/View.js +78 -81
  95. package/src/WebSocket.js +403 -0
  96. package/template/config.json +5 -0
  97. package/{web → template}/controller/page/about.js +6 -6
  98. package/{web → template}/controller/page/index.js +9 -9
  99. package/{web → template}/package.json +4 -5
  100. package/{web → template}/public/assets/css/style.css +4 -4
  101. package/{web → template}/public/assets/js/app.js +6 -6
  102. package/{web → template}/route/www.js +6 -6
  103. package/{web → template}/skeleton/main.html +1 -1
  104. package/{web → template}/view/content/about.html +5 -5
  105. package/{web → template}/view/content/home.html +12 -12
  106. package/template/view/footer/main.html +11 -0
  107. package/{web → template}/view/head/main.html +1 -1
  108. package/{web → template}/view/header/main.html +2 -2
  109. package/test/core/Candy.test.js +58 -58
  110. package/test/core/Commands.test.js +7 -7
  111. package/test/core/Config.test.js +82 -85
  112. package/test/core/Lang.test.js +2 -2
  113. package/test/core/Process.test.js +6 -6
  114. package/test/framework/Route.test.js +56 -37
  115. package/test/framework/View/EarlyHints.test.js +2 -2
  116. package/test/framework/WebSocket.test.js +100 -0
  117. package/test/framework/middleware.test.js +85 -0
  118. package/test/server/Api.test.js +31 -31
  119. package/test/server/DNS.test.js +11 -11
  120. package/test/server/Hub.test.js +497 -0
  121. package/test/server/Mail.account.test_.js +3 -3
  122. package/test/server/Mail.init.test_.js +10 -10
  123. package/test/server/Mail.test_.js +20 -20
  124. package/test/server/SSL.test_.js +54 -54
  125. package/test/server/Server.test.js +39 -39
  126. package/test/server/Service.test_.js +7 -7
  127. package/test/server/Subdomain.test.js +7 -7
  128. package/test/server/Web/Firewall.test.js +87 -87
  129. package/test/server/Web/Proxy.test.js +397 -0
  130. package/test/server/{Web.test_.js → Web.test.js} +137 -205
  131. package/test/server/__mocks__/fs.js +2 -2
  132. package/test/server/__mocks__/{globalCandy.js → globalOdac.js} +5 -5
  133. package/test/server/__mocks__/index.js +6 -6
  134. package/test/server/__mocks__/testFactories.js +1 -1
  135. package/test/server/__mocks__/testHelpers.js +7 -7
  136. package/.husky/pre-commit +0 -2
  137. package/.kiro/steering/code-style.md +0 -56
  138. package/.kiro/steering/product.md +0 -20
  139. package/.kiro/steering/structure.md +0 -77
  140. package/.kiro/steering/tech.md +0 -87
  141. package/AGENTS.md +0 -84
  142. package/bin/candy +0 -10
  143. package/bin/candypack +0 -10
  144. package/cli/index.js +0 -3
  145. package/cli/src/Cli.js +0 -348
  146. package/cli/src/Connector.js +0 -93
  147. package/cli/src/Monitor.js +0 -416
  148. package/core/Candy.js +0 -87
  149. package/core/Commands.js +0 -239
  150. package/core/Config.js +0 -1094
  151. package/core/Lang.js +0 -52
  152. package/core/Log.js +0 -43
  153. package/core/Process.js +0 -26
  154. package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +0 -20
  155. package/docs/server/01-installation/01-quick-install.md +0 -19
  156. package/docs/server/01-installation/02-manual-installation-via-npm.md +0 -9
  157. package/docs/server/02-get-started/01-core-concepts.md +0 -7
  158. package/docs/server/02-get-started/02-basic-commands.md +0 -57
  159. package/docs/server/02-get-started/03-cli-reference.md +0 -276
  160. package/docs/server/02-get-started/04-cli-quick-reference.md +0 -102
  161. package/docs/server/03-service/01-start-a-new-service.md +0 -57
  162. package/docs/server/03-service/02-delete-a-service.md +0 -48
  163. package/docs/server/04-web/01-create-a-website.md +0 -36
  164. package/docs/server/04-web/02-list-websites.md +0 -9
  165. package/docs/server/04-web/03-delete-a-website.md +0 -29
  166. package/docs/server/05-subdomain/01-create-a-subdomain.md +0 -32
  167. package/docs/server/05-subdomain/02-list-subdomains.md +0 -33
  168. package/docs/server/05-subdomain/03-delete-a-subdomain.md +0 -41
  169. package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +0 -34
  170. package/docs/server/07-mail/01-create-a-mail-account.md +0 -23
  171. package/docs/server/07-mail/02-delete-a-mail-account.md +0 -20
  172. package/docs/server/07-mail/03-list-mail-accounts.md +0 -20
  173. package/docs/server/07-mail/04-change-account-password.md +0 -23
  174. package/framework/src/Candy.js +0 -81
  175. package/framework/src/Route.js +0 -455
  176. package/framework/src/Server.js +0 -15
  177. package/locale/de-DE.json +0 -80
  178. package/locale/en-US.json +0 -79
  179. package/locale/es-ES.json +0 -80
  180. package/locale/fr-FR.json +0 -80
  181. package/locale/pt-BR.json +0 -80
  182. package/locale/ru-RU.json +0 -80
  183. package/locale/tr-TR.json +0 -85
  184. package/locale/zh-CN.json +0 -80
  185. package/server/index.js +0 -5
  186. package/server/src/Api.js +0 -88
  187. package/server/src/DNS.js +0 -940
  188. package/server/src/Hub.js +0 -535
  189. package/server/src/Mail.js +0 -571
  190. package/server/src/SSL.js +0 -180
  191. package/server/src/Server.js +0 -27
  192. package/server/src/Service.js +0 -248
  193. package/server/src/Subdomain.js +0 -64
  194. package/server/src/Web/Firewall.js +0 -170
  195. package/server/src/Web/Proxy.js +0 -134
  196. package/server/src/Web.js +0 -451
  197. package/server/src/mail/imap.js +0 -1091
  198. package/server/src/mail/server.js +0 -32
  199. package/server/src/mail/smtp.js +0 -786
  200. package/test/server/Client.test.js +0 -338
  201. package/test/server/__mocks__/http-proxy.js +0 -105
  202. package/watchdog/index.js +0 -3
  203. package/watchdog/src/Watchdog.js +0 -156
  204. package/web/config.json +0 -5
  205. package/web/view/footer/main.html +0 -11
  206. /package/{framework/src → src}/Env.js +0 -0
  207. /package/{framework/src → src}/Route/Cron.js +0 -0
  208. /package/{framework/src → src}/Token.js +0 -0
@@ -1,338 +0,0 @@
1
- // Create mock log function first
2
- const mockLog = jest.fn()
3
-
4
- // Mock global Candy first
5
- const {mockCandy} = require('./__mocks__/globalCandy')
6
-
7
- // Set up the Log mock before setting global.Candy
8
- mockCandy.setMock('core', 'Log', {
9
- init: jest.fn().mockReturnValue({
10
- log: mockLog,
11
- error: jest.fn()
12
- })
13
- })
14
-
15
- global.Candy = mockCandy
16
-
17
- // Mock axios
18
- jest.mock('axios')
19
- const axios = require('axios')
20
-
21
- const Client = require('../../server/src/Client')
22
-
23
- describe('Client', () => {
24
- beforeEach(() => {
25
- jest.clearAllMocks()
26
-
27
- // Reset config
28
- mockCandy.setMock('core', 'Config', {
29
- config: {}
30
- })
31
- })
32
-
33
- describe('authentication', () => {
34
- it('should authenticate successfully with valid code', async () => {
35
- // Arrange
36
- const mockCode = 'valid-auth-code'
37
- const mockResponse = {
38
- data: {
39
- result: {success: true},
40
- data: {
41
- token: 'test-token-123',
42
- secret: 'test-secret-456'
43
- }
44
- }
45
- }
46
-
47
- const mockConfig = {config: {}}
48
- mockCandy.setMock('core', 'Config', mockConfig)
49
- axios.post.mockResolvedValue(mockResponse)
50
-
51
- // Act
52
- Client.auth(mockCode)
53
-
54
- // Wait for promise to resolve
55
- await new Promise(resolve => setTimeout(resolve, 0))
56
-
57
- // Assert
58
- expect(mockLog).toHaveBeenCalledWith('CandyPack authenticating...')
59
- expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/auth', {code: mockCode})
60
- expect(mockConfig.config.auth).toEqual({
61
- token: 'test-token-123',
62
- secret: 'test-secret-456'
63
- })
64
- expect(mockLog).toHaveBeenCalledWith('CandyPack authenticated!')
65
- })
66
-
67
- it('should handle authentication failure with error message', async () => {
68
- // Arrange
69
- const mockCode = 'invalid-code'
70
- const mockError = 'Invalid authentication code'
71
-
72
- axios.post.mockRejectedValue({
73
- response: {data: mockError}
74
- })
75
-
76
- // Act
77
- Client.auth(mockCode)
78
-
79
- // Wait for promise to reject
80
- await new Promise(resolve => setTimeout(resolve, 0))
81
-
82
- // Assert
83
- expect(mockLog).toHaveBeenCalledWith('CandyPack authenticating...')
84
- expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/auth', {code: mockCode})
85
- expect(mockLog).toHaveBeenCalledWith(mockError)
86
- })
87
-
88
- it('should handle authentication failure without error message', async () => {
89
- // Arrange
90
- const mockCode = 'invalid-code'
91
-
92
- axios.post.mockRejectedValue({
93
- response: {data: null}
94
- })
95
-
96
- // Act
97
- Client.auth(mockCode)
98
-
99
- // Wait for promise to reject
100
- await new Promise(resolve => setTimeout(resolve, 0))
101
-
102
- // Assert
103
- expect(mockLog).toHaveBeenCalledWith('CandyPack authenticating...')
104
- expect(mockLog).toHaveBeenCalledWith('CandyPack authentication failed!')
105
- })
106
-
107
- it('should handle API response with success false', async () => {
108
- // Arrange
109
- const mockCode = 'valid-code'
110
- const mockResponse = {
111
- data: {
112
- result: {
113
- success: false,
114
- message: 'Authentication failed on server'
115
- }
116
- }
117
- }
118
-
119
- axios.post.mockResolvedValue(mockResponse)
120
-
121
- // Act
122
- Client.auth(mockCode)
123
-
124
- // Wait for promise to reject
125
- await new Promise(resolve => setTimeout(resolve, 0))
126
-
127
- // Assert
128
- expect(mockLog).toHaveBeenCalledWith('CandyPack authenticating...')
129
- expect(mockLog).toHaveBeenCalledWith('Authentication failed on server')
130
- })
131
-
132
- it('should store authentication credentials in config', async () => {
133
- // Arrange
134
- const mockCode = 'test-code'
135
- const mockToken = 'stored-token'
136
- const mockSecret = 'stored-secret'
137
- const mockResponse = {
138
- data: {
139
- result: {success: true},
140
- data: {
141
- token: mockToken,
142
- secret: mockSecret
143
- }
144
- }
145
- }
146
-
147
- const mockConfig = {config: {existingProp: 'value'}}
148
- mockCandy.setMock('core', 'Config', mockConfig)
149
- axios.post.mockResolvedValue(mockResponse)
150
-
151
- // Act
152
- Client.auth(mockCode)
153
-
154
- // Wait for promise to resolve
155
- await new Promise(resolve => setTimeout(resolve, 0))
156
-
157
- // Assert
158
- expect(mockConfig.config.auth).toEqual({
159
- token: mockToken,
160
- secret: mockSecret
161
- })
162
- // Verify existing config is preserved
163
- expect(mockConfig.config.existingProp).toBe('value')
164
- })
165
- })
166
-
167
- describe('API communication', () => {
168
- it('should make successful API call and return data', async () => {
169
- // Arrange
170
- const mockAction = 'test-action'
171
- const mockData = {param1: 'value1', param2: 'value2'}
172
- const mockResponseData = {result: 'success', info: 'test data'}
173
- const mockResponse = {
174
- data: {
175
- result: {success: true},
176
- data: mockResponseData
177
- }
178
- }
179
-
180
- axios.post.mockResolvedValue(mockResponse)
181
-
182
- // Act
183
- const result = await Client.call(mockAction, mockData)
184
-
185
- // Assert
186
- expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/test-action', mockData)
187
- expect(result).toEqual(mockResponseData)
188
- })
189
-
190
- it('should handle API call with different endpoints', async () => {
191
- // Arrange
192
- const testCases = [
193
- {action: 'users', data: {id: 1}},
194
- {action: 'domains', data: {domain: 'example.com'}},
195
- {action: 'ssl/renew', data: {cert: 'test.crt'}}
196
- ]
197
-
198
- const mockResponseData = {status: 'ok'}
199
- const mockResponse = {
200
- data: {
201
- result: {success: true},
202
- data: mockResponseData
203
- }
204
- }
205
-
206
- axios.post.mockResolvedValue(mockResponse)
207
-
208
- // Act & Assert
209
- for (const testCase of testCases) {
210
- const result = await Client.call(testCase.action, testCase.data)
211
- expect(axios.post).toHaveBeenCalledWith(`https://api.candypack.dev/${testCase.action}`, testCase.data)
212
- expect(result).toEqual(mockResponseData)
213
- }
214
- })
215
-
216
- it('should reject when API response indicates failure', async () => {
217
- // Arrange
218
- const mockAction = 'failing-action'
219
- const mockData = {test: 'data'}
220
- const mockErrorMessage = 'API operation failed'
221
- const mockResponse = {
222
- data: {
223
- result: {
224
- success: false,
225
- message: mockErrorMessage
226
- }
227
- }
228
- }
229
-
230
- axios.post.mockResolvedValue(mockResponse)
231
-
232
- // Act & Assert
233
- await expect(Client.call(mockAction, mockData)).rejects.toBe(mockErrorMessage)
234
- expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/failing-action', mockData)
235
- })
236
-
237
- it('should handle network errors and log response data', async () => {
238
- // Arrange
239
- const mockAction = 'network-error'
240
- const mockData = {test: 'data'}
241
- const mockErrorData = {error: 'Network timeout', code: 500}
242
- const mockError = {
243
- response: {data: mockErrorData}
244
- }
245
-
246
- axios.post.mockRejectedValue(mockError)
247
-
248
- // Act & Assert
249
- await expect(Client.call(mockAction, mockData)).rejects.toBe(mockErrorData)
250
- expect(mockLog).toHaveBeenCalledWith(mockErrorData)
251
- expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/network-error', mockData)
252
- })
253
-
254
- it('should handle axios errors without response data', async () => {
255
- // Arrange
256
- const mockAction = 'connection-error'
257
- const mockData = {test: 'data'}
258
- const mockError = {
259
- response: {
260
- data: 'Connection refused'
261
- }
262
- }
263
-
264
- axios.post.mockRejectedValue(mockError)
265
-
266
- // Act & Assert
267
- await expect(Client.call(mockAction, mockData)).rejects.toBe('Connection refused')
268
- expect(mockLog).toHaveBeenCalledWith('Connection refused')
269
- })
270
-
271
- it('should format API requests correctly', async () => {
272
- // Arrange
273
- const mockAction = 'format-test'
274
- const mockData = {
275
- string: 'test',
276
- number: 123,
277
- boolean: true,
278
- object: {nested: 'value'},
279
- array: [1, 2, 3]
280
- }
281
- const mockResponse = {
282
- data: {
283
- result: {success: true},
284
- data: {received: 'ok'}
285
- }
286
- }
287
-
288
- axios.post.mockResolvedValue(mockResponse)
289
-
290
- // Act
291
- await Client.call(mockAction, mockData)
292
-
293
- // Assert
294
- expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/format-test', mockData)
295
- expect(axios.post).toHaveBeenCalledTimes(1)
296
- })
297
-
298
- it('should handle empty response data', async () => {
299
- // Arrange
300
- const mockAction = 'empty-response'
301
- const mockData = {test: 'data'}
302
- const mockResponse = {
303
- data: {
304
- result: {success: true},
305
- data: null
306
- }
307
- }
308
-
309
- axios.post.mockResolvedValue(mockResponse)
310
-
311
- // Act
312
- const result = await Client.call(mockAction, mockData)
313
-
314
- // Assert
315
- expect(result).toBeNull()
316
- })
317
-
318
- it('should handle API calls with no data parameter', async () => {
319
- // Arrange
320
- const mockAction = 'no-data'
321
- const mockResponse = {
322
- data: {
323
- result: {success: true},
324
- data: {message: 'success'}
325
- }
326
- }
327
-
328
- axios.post.mockResolvedValue(mockResponse)
329
-
330
- // Act
331
- const result = await Client.call(mockAction)
332
-
333
- // Assert
334
- expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/no-data', undefined)
335
- expect(result).toEqual({message: 'success'})
336
- })
337
- })
338
- })
@@ -1,105 +0,0 @@
1
- /**
2
- * Mock implementation of http-proxy module for server tests
3
- */
4
-
5
- const {createMockEventEmitter} = require('./testHelpers')
6
-
7
- const createMockProxyServer = (options = {}) => {
8
- const proxy = Object.assign(createMockEventEmitter(), {
9
- options: options,
10
-
11
- // Main proxy methods
12
- web: jest.fn((req, res, options, callback) => {
13
- // Simulate successful proxying
14
- setTimeout(() => {
15
- if (callback) callback()
16
- }, 0)
17
- }),
18
-
19
- ws: jest.fn((req, socket, head, options, callback) => {
20
- // Simulate WebSocket proxying
21
- setTimeout(() => {
22
- if (callback) callback()
23
- }, 0)
24
- }),
25
-
26
- listen: jest.fn((port, hostname, callback) => {
27
- if (typeof hostname === 'function') {
28
- callback = hostname
29
- hostname = undefined
30
- }
31
-
32
- setTimeout(() => {
33
- if (callback) callback()
34
- }, 0)
35
-
36
- return proxy
37
- }),
38
-
39
- close: jest.fn(callback => {
40
- setTimeout(() => {
41
- if (callback) callback()
42
- }, 0)
43
- }),
44
-
45
- // Test helper methods
46
- simulateProxyReq: (proxyReq, req, res, options) => {
47
- proxy.emit('proxyReq', proxyReq, req, res, options)
48
- },
49
-
50
- simulateProxyRes: (proxyRes, req, res) => {
51
- proxy.emit('proxyRes', proxyRes, req, res)
52
- },
53
-
54
- simulateError: (error, req, res, target) => {
55
- proxy.emit('error', error, req, res, target)
56
- },
57
-
58
- simulateOpen: proxySocket => {
59
- proxy.emit('open', proxySocket)
60
- },
61
-
62
- simulateClose: (res, socket, head) => {
63
- proxy.emit('close', res, socket, head)
64
- }
65
- })
66
-
67
- return proxy
68
- }
69
-
70
- const httpProxy = {
71
- createProxyServer: jest.fn((options = {}) => {
72
- return createMockProxyServer(options)
73
- }),
74
-
75
- createServer: jest.fn((options = {}, requestListener) => {
76
- if (typeof options === 'function') {
77
- requestListener = options
78
- options = {}
79
- }
80
-
81
- const proxy = createMockProxyServer(options)
82
-
83
- // Add server-like methods
84
- proxy.listen = jest.fn((port, hostname, callback) => {
85
- if (typeof hostname === 'function') {
86
- callback = hostname
87
- hostname = undefined
88
- }
89
-
90
- setTimeout(() => {
91
- proxy.emit('listening')
92
- if (callback) callback()
93
- }, 0)
94
-
95
- return proxy
96
- })
97
-
98
- return proxy
99
- }),
100
-
101
- // Test helpers
102
- __createMockProxyServer: createMockProxyServer
103
- }
104
-
105
- module.exports = httpProxy
package/watchdog/index.js DELETED
@@ -1,3 +0,0 @@
1
- require('../core/Candy.js')
2
-
3
- Candy.watchdog('Watchdog')
@@ -1,156 +0,0 @@
1
- const {spawn} = require('child_process')
2
- const fs = require('fs').promises
3
- const os = require('os')
4
- const path = require('path')
5
-
6
- // --- Constants ---
7
- const CANDYPACK_HOME = path.join(os.homedir(), '.candypack')
8
- const LOG_DIR = path.join(CANDYPACK_HOME, 'logs')
9
- const SERVER_SCRIPT_PATH = path.join(__dirname, '..', '..', 'server', 'index.js')
10
-
11
- const MAX_RESTARTS_IN_WINDOW = 100
12
- const RESTART_WINDOW_MS = 1000 * 60 * 5 // 5 minutes
13
- const SAVE_INTERVAL_MS = 1000 // 1 second
14
-
15
- class Watchdog {
16
- #logBuffer = ''
17
- #errorBuffer = ''
18
- #restartCount = 0
19
- #lastRestartTimestamp = 0
20
- #isSaving = false
21
-
22
- init() {
23
- // Set up periodic log saving. This is done only once.
24
- setInterval(() => this.#saveLogs(), SAVE_INTERVAL_MS)
25
-
26
- // Start the server for the first time.
27
- this.#startServer()
28
- }
29
-
30
- /**
31
- * Saves the buffered logs to their respective files.
32
- * This function is designed to be called periodically.
33
- * Keeps only the last 1000 lines in each log file.
34
- */
35
- async #saveLogs() {
36
- if (this.#isSaving) return
37
- this.#isSaving = true
38
-
39
- try {
40
- // Ensure log directory exists before attempting to write files
41
- await fs.mkdir(LOG_DIR, {recursive: true})
42
- const logFile = path.join(LOG_DIR, '.candypack.log')
43
- const errFile = path.join(LOG_DIR, '.candypack_err.log')
44
-
45
- // Limit log buffer to last 1000 lines
46
- const logLines = this.#logBuffer.split('\n')
47
- if (logLines.length > 1000) {
48
- this.#logBuffer = logLines.slice(-1000).join('\n')
49
- }
50
-
51
- // Limit error buffer to last 1000 lines
52
- const errLines = this.#errorBuffer.split('\n')
53
- if (errLines.length > 1000) {
54
- this.#errorBuffer = errLines.slice(-1000).join('\n')
55
- }
56
-
57
- await fs.writeFile(logFile, this.#logBuffer, 'utf8')
58
- await fs.writeFile(errFile, this.#errorBuffer, 'utf8')
59
- } catch (error) {
60
- console.error('Failed to save logs:', error)
61
- } finally {
62
- this.#isSaving = false
63
- }
64
- }
65
-
66
- /**
67
- * Performs startup checks to ensure a clean environment.
68
- * It kills any old watchdog or server processes that might still be running.
69
- * It also creates the necessary configuration files and directories if they don't exist.
70
- * @returns {Promise<boolean>} A promise that resolves to true if the checks pass.
71
- */
72
- async #performStartupChecks() {
73
- try {
74
- // Kill previous watchdog process if it exists and is different from the current one
75
- if (Candy.core('Config').config.server.watchdog && Candy.core('Config').config.server.watchdog !== process.pid)
76
- await Candy.core('Process').stop(Candy.core('Config').config.server.watchdog)
77
-
78
- // Kill previous server process if it exists
79
- if (Candy.core('Config').config.server.pid) await Candy.core('Process').stop(Candy.core('Config').config.server.pid)
80
-
81
- for (const domain of Object.keys(Candy.core('Config').config?.websites ?? []))
82
- if (Candy.core('Config').config.websites[domain].pid)
83
- await Candy.core('Process').stop(Candy.core('Config').config.websites[domain].pid)
84
-
85
- for (const service of Candy.core('Config').config.services ?? []) if (service.pid) await Candy.core('Process').stop(service.pid)
86
-
87
- // Update config with current watchdog's info
88
- Candy.core('Config').config.server.watchdog = process.pid
89
- Candy.core('Config').config.server.started = Date.now()
90
- Candy.core('Config').force()
91
-
92
- return new Promise(resolve => setTimeout(() => resolve(true), 1000))
93
- } catch (error) {
94
- console.error('Error during startup checks:', error)
95
- return false
96
- }
97
- }
98
-
99
- /**
100
- * Starts the server process and sets up monitoring.
101
- */
102
- async #startServer() {
103
- const checksPassed = await this.#performStartupChecks()
104
- if (!checksPassed) {
105
- console.error('Startup checks failed. Aborting.')
106
- process.exit(1)
107
- }
108
-
109
- // Ensure log directory exists before starting
110
- await fs.mkdir(LOG_DIR, {recursive: true})
111
-
112
- const child = spawn('node', [SERVER_SCRIPT_PATH])
113
-
114
- process.on('exit', () => child.kill())
115
-
116
- Candy.core('Config').config.server.pid = child.pid
117
-
118
- console.log(`Watchdog process started with PID: ${process.pid}`)
119
- console.log(`Server process started with PID: ${child.pid}`)
120
-
121
- child.stdout.on('data', data => {
122
- this.#logBuffer += `[LOG][${new Date().toISOString()}] ${data.toString()}`
123
- })
124
-
125
- child.stderr.on('data', data => {
126
- this.#logBuffer += `[ERR][${new Date().toISOString()}] ${data.toString()}`
127
- this.#errorBuffer += `[ERR][${new Date().toISOString()}] ${data.toString()}`
128
- })
129
-
130
- child.on('close', code => {
131
- Candy.core('Config').reload()
132
- this.#errorBuffer += `[ERR][${new Date().toISOString()}] Process closed with code ${code}\n`
133
-
134
- // Reset restart count if the last restart was a while ago
135
- if (Date.now() - this.#lastRestartTimestamp > RESTART_WINDOW_MS) {
136
- this.#restartCount = 0
137
- }
138
-
139
- this.#restartCount++
140
- this.#lastRestartTimestamp = Date.now()
141
-
142
- // If restart limit is not exceeded, restart the server
143
- if (this.#restartCount < MAX_RESTARTS_IN_WINDOW) {
144
- console.log('Server process closed. Restarting...')
145
- // Relaunch the server process without setting up new intervals
146
- this.#startServer()
147
- } else {
148
- console.error('Server has crashed too many times. Not restarting.')
149
- // Final attempt to save logs before exiting
150
- this.#saveLogs().then(() => process.exit(1))
151
- }
152
- })
153
- }
154
- }
155
-
156
- module.exports = new Watchdog()
package/web/config.json DELETED
@@ -1,5 +0,0 @@
1
- {
2
- "route": {
3
- "/assets/js/candy.js": "${candy}/framework/web/candy.js"
4
- }
5
- }
@@ -1,11 +0,0 @@
1
- <!-- Footer -->
2
- <div class="footer">
3
- <div class="container">
4
- <p>Powered by <strong>CandyPack</strong> - A next-generation server and framework toolkit</p>
5
- <p class="footer-links">
6
- <a href="https://github.com/candypack" target="_blank" data-navigate="false">GitHub</a> •
7
- <a href="https://docs.candypack.dev" target="_blank" data-navigate="false">Documentation</a> •
8
- <a href="https://candypack.dev" target="_blank" data-navigate="false">Official Site</a>
9
- </p>
10
- </div>
11
- </div>
File without changes
File without changes
File without changes