odac 0.9.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 (213) hide show
  1. package/.editorconfig +21 -0
  2. package/.github/workflows/auto-pr-description.yml +49 -0
  3. package/.github/workflows/release.yml +32 -0
  4. package/.github/workflows/test-coverage.yml +58 -0
  5. package/.husky/pre-commit +2 -0
  6. package/.kiro/steering/code-style.md +56 -0
  7. package/.kiro/steering/product.md +20 -0
  8. package/.kiro/steering/structure.md +77 -0
  9. package/.kiro/steering/tech.md +87 -0
  10. package/.prettierrc +10 -0
  11. package/.releaserc.js +134 -0
  12. package/AGENTS.md +84 -0
  13. package/CHANGELOG.md +181 -0
  14. package/CODE_OF_CONDUCT.md +83 -0
  15. package/CONTRIBUTING.md +63 -0
  16. package/LICENSE +661 -0
  17. package/README.md +57 -0
  18. package/SECURITY.md +26 -0
  19. package/bin/candy +10 -0
  20. package/bin/candypack +10 -0
  21. package/cli/index.js +3 -0
  22. package/cli/src/Cli.js +348 -0
  23. package/cli/src/Connector.js +93 -0
  24. package/cli/src/Monitor.js +416 -0
  25. package/core/Candy.js +87 -0
  26. package/core/Commands.js +239 -0
  27. package/core/Config.js +1094 -0
  28. package/core/Lang.js +52 -0
  29. package/core/Log.js +43 -0
  30. package/core/Process.js +26 -0
  31. package/docs/backend/01-overview/01-whats-in-the-candy-box.md +9 -0
  32. package/docs/backend/01-overview/02-super-handy-helper-functions.md +9 -0
  33. package/docs/backend/01-overview/03-development-server.md +79 -0
  34. package/docs/backend/02-structure/01-typical-project-layout.md +39 -0
  35. package/docs/backend/03-config/00-configuration-overview.md +214 -0
  36. package/docs/backend/03-config/01-database-connection.md +60 -0
  37. package/docs/backend/03-config/02-static-route-mapping-optional.md +20 -0
  38. package/docs/backend/03-config/03-request-timeout.md +11 -0
  39. package/docs/backend/03-config/04-environment-variables.md +227 -0
  40. package/docs/backend/03-config/05-early-hints.md +352 -0
  41. package/docs/backend/04-routing/01-basic-page-routes.md +28 -0
  42. package/docs/backend/04-routing/02-controller-less-view-routes.md +43 -0
  43. package/docs/backend/04-routing/03-api-and-data-routes.md +20 -0
  44. package/docs/backend/04-routing/04-authentication-aware-routes.md +48 -0
  45. package/docs/backend/04-routing/05-advanced-routing.md +14 -0
  46. package/docs/backend/04-routing/06-error-pages.md +101 -0
  47. package/docs/backend/04-routing/07-cron-jobs.md +149 -0
  48. package/docs/backend/05-controllers/01-how-to-build-a-controller.md +17 -0
  49. package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +20 -0
  50. package/docs/backend/05-controllers/03-controller-classes.md +93 -0
  51. package/docs/backend/05-forms/01-custom-forms.md +395 -0
  52. package/docs/backend/05-forms/02-automatic-database-insert.md +297 -0
  53. package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +96 -0
  54. package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +40 -0
  55. package/docs/backend/07-views/01-the-view-directory.md +73 -0
  56. package/docs/backend/07-views/02-rendering-a-view.md +179 -0
  57. package/docs/backend/07-views/03-template-syntax.md +181 -0
  58. package/docs/backend/07-views/03-variables.md +328 -0
  59. package/docs/backend/07-views/04-request-data.md +231 -0
  60. package/docs/backend/07-views/05-conditionals.md +290 -0
  61. package/docs/backend/07-views/06-loops.md +353 -0
  62. package/docs/backend/07-views/07-translations.md +358 -0
  63. package/docs/backend/07-views/08-backend-javascript.md +398 -0
  64. package/docs/backend/07-views/09-comments.md +297 -0
  65. package/docs/backend/08-database/01-database-connection.md +99 -0
  66. package/docs/backend/08-database/02-using-mysql.md +322 -0
  67. package/docs/backend/09-validation/01-the-validator-service.md +424 -0
  68. package/docs/backend/10-authentication/01-user-logins-with-authjs.md +53 -0
  69. package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +55 -0
  70. package/docs/backend/10-authentication/03-register.md +134 -0
  71. package/docs/backend/10-authentication/04-candy-register-forms.md +676 -0
  72. package/docs/backend/10-authentication/05-session-management.md +159 -0
  73. package/docs/backend/10-authentication/06-candy-login-forms.md +596 -0
  74. package/docs/backend/11-mail/01-the-mail-service.md +42 -0
  75. package/docs/backend/12-streaming/01-streaming-overview.md +300 -0
  76. package/docs/backend/13-utilities/01-candy-var.md +504 -0
  77. package/docs/frontend/01-overview/01-introduction.md +146 -0
  78. package/docs/frontend/02-ajax-navigation/01-quick-start.md +608 -0
  79. package/docs/frontend/02-ajax-navigation/02-configuration.md +370 -0
  80. package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +519 -0
  81. package/docs/frontend/03-forms/01-form-handling.md +420 -0
  82. package/docs/frontend/04-api-requests/01-get-post.md +443 -0
  83. package/docs/frontend/05-streaming/01-client-streaming.md +163 -0
  84. package/docs/index.json +452 -0
  85. package/docs/server/01-installation/01-quick-install.md +19 -0
  86. package/docs/server/01-installation/02-manual-installation-via-npm.md +9 -0
  87. package/docs/server/02-get-started/01-core-concepts.md +7 -0
  88. package/docs/server/02-get-started/02-basic-commands.md +57 -0
  89. package/docs/server/02-get-started/03-cli-reference.md +276 -0
  90. package/docs/server/02-get-started/04-cli-quick-reference.md +102 -0
  91. package/docs/server/03-service/01-start-a-new-service.md +57 -0
  92. package/docs/server/03-service/02-delete-a-service.md +48 -0
  93. package/docs/server/04-web/01-create-a-website.md +36 -0
  94. package/docs/server/04-web/02-list-websites.md +9 -0
  95. package/docs/server/04-web/03-delete-a-website.md +29 -0
  96. package/docs/server/05-subdomain/01-create-a-subdomain.md +32 -0
  97. package/docs/server/05-subdomain/02-list-subdomains.md +33 -0
  98. package/docs/server/05-subdomain/03-delete-a-subdomain.md +41 -0
  99. package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +34 -0
  100. package/docs/server/07-mail/01-create-a-mail-account.md +23 -0
  101. package/docs/server/07-mail/02-delete-a-mail-account.md +20 -0
  102. package/docs/server/07-mail/03-list-mail-accounts.md +20 -0
  103. package/docs/server/07-mail/04-change-account-password.md +23 -0
  104. package/eslint.config.mjs +120 -0
  105. package/framework/index.js +4 -0
  106. package/framework/src/Auth.js +309 -0
  107. package/framework/src/Candy.js +81 -0
  108. package/framework/src/Config.js +79 -0
  109. package/framework/src/Env.js +60 -0
  110. package/framework/src/Lang.js +57 -0
  111. package/framework/src/Mail.js +83 -0
  112. package/framework/src/Mysql.js +575 -0
  113. package/framework/src/Request.js +301 -0
  114. package/framework/src/Route/Cron.js +128 -0
  115. package/framework/src/Route/Internal.js +439 -0
  116. package/framework/src/Route.js +455 -0
  117. package/framework/src/Server.js +15 -0
  118. package/framework/src/Stream.js +163 -0
  119. package/framework/src/Token.js +37 -0
  120. package/framework/src/Validator.js +271 -0
  121. package/framework/src/Var.js +211 -0
  122. package/framework/src/View/EarlyHints.js +190 -0
  123. package/framework/src/View/Form.js +600 -0
  124. package/framework/src/View.js +513 -0
  125. package/framework/web/candy.js +838 -0
  126. package/jest.config.js +22 -0
  127. package/locale/de-DE.json +80 -0
  128. package/locale/en-US.json +79 -0
  129. package/locale/es-ES.json +80 -0
  130. package/locale/fr-FR.json +80 -0
  131. package/locale/pt-BR.json +80 -0
  132. package/locale/ru-RU.json +80 -0
  133. package/locale/tr-TR.json +85 -0
  134. package/locale/zh-CN.json +80 -0
  135. package/package.json +86 -0
  136. package/server/index.js +5 -0
  137. package/server/src/Api.js +88 -0
  138. package/server/src/DNS.js +940 -0
  139. package/server/src/Hub.js +535 -0
  140. package/server/src/Mail.js +571 -0
  141. package/server/src/SSL.js +180 -0
  142. package/server/src/Server.js +27 -0
  143. package/server/src/Service.js +248 -0
  144. package/server/src/Subdomain.js +64 -0
  145. package/server/src/Web/Firewall.js +170 -0
  146. package/server/src/Web/Proxy.js +134 -0
  147. package/server/src/Web.js +451 -0
  148. package/server/src/mail/imap.js +1091 -0
  149. package/server/src/mail/server.js +32 -0
  150. package/server/src/mail/smtp.js +786 -0
  151. package/test/cli/Cli.test.js +36 -0
  152. package/test/core/Candy.test.js +234 -0
  153. package/test/core/Commands.test.js +538 -0
  154. package/test/core/Config.test.js +1435 -0
  155. package/test/core/Lang.test.js +250 -0
  156. package/test/core/Process.test.js +156 -0
  157. package/test/framework/Route.test.js +239 -0
  158. package/test/framework/View/EarlyHints.test.js +282 -0
  159. package/test/scripts/check-coverage.js +132 -0
  160. package/test/server/Api.test.js +647 -0
  161. package/test/server/Client.test.js +338 -0
  162. package/test/server/DNS.test.js +2050 -0
  163. package/test/server/DNS.test.js.bak +2084 -0
  164. package/test/server/Log.test.js +73 -0
  165. package/test/server/Mail.account.test_.js +460 -0
  166. package/test/server/Mail.init.test_.js +411 -0
  167. package/test/server/Mail.test_.js +1340 -0
  168. package/test/server/SSL.test_.js +1491 -0
  169. package/test/server/Server.test.js +765 -0
  170. package/test/server/Service.test_.js +1127 -0
  171. package/test/server/Subdomain.test.js +440 -0
  172. package/test/server/Web/Firewall.test.js +175 -0
  173. package/test/server/Web.test_.js +1562 -0
  174. package/test/server/__mocks__/acme-client.js +17 -0
  175. package/test/server/__mocks__/bcrypt.js +50 -0
  176. package/test/server/__mocks__/child_process.js +389 -0
  177. package/test/server/__mocks__/crypto.js +432 -0
  178. package/test/server/__mocks__/fs.js +450 -0
  179. package/test/server/__mocks__/globalCandy.js +227 -0
  180. package/test/server/__mocks__/http-proxy.js +105 -0
  181. package/test/server/__mocks__/http.js +575 -0
  182. package/test/server/__mocks__/https.js +272 -0
  183. package/test/server/__mocks__/index.js +249 -0
  184. package/test/server/__mocks__/mail/server.js +100 -0
  185. package/test/server/__mocks__/mail/smtp.js +31 -0
  186. package/test/server/__mocks__/mailparser.js +81 -0
  187. package/test/server/__mocks__/net.js +369 -0
  188. package/test/server/__mocks__/node-forge.js +328 -0
  189. package/test/server/__mocks__/os.js +320 -0
  190. package/test/server/__mocks__/path.js +291 -0
  191. package/test/server/__mocks__/selfsigned.js +8 -0
  192. package/test/server/__mocks__/server/src/mail/server.js +100 -0
  193. package/test/server/__mocks__/server/src/mail/smtp.js +31 -0
  194. package/test/server/__mocks__/smtp-server.js +106 -0
  195. package/test/server/__mocks__/sqlite3.js +394 -0
  196. package/test/server/__mocks__/testFactories.js +299 -0
  197. package/test/server/__mocks__/testHelpers.js +363 -0
  198. package/test/server/__mocks__/tls.js +229 -0
  199. package/watchdog/index.js +3 -0
  200. package/watchdog/src/Watchdog.js +156 -0
  201. package/web/config.json +5 -0
  202. package/web/controller/page/about.js +27 -0
  203. package/web/controller/page/index.js +34 -0
  204. package/web/package.json +18 -0
  205. package/web/public/assets/css/style.css +1835 -0
  206. package/web/public/assets/js/app.js +96 -0
  207. package/web/route/www.js +19 -0
  208. package/web/skeleton/main.html +22 -0
  209. package/web/view/content/about.html +65 -0
  210. package/web/view/content/home.html +205 -0
  211. package/web/view/footer/main.html +11 -0
  212. package/web/view/head/main.html +5 -0
  213. package/web/view/header/main.html +14 -0
@@ -0,0 +1,17 @@
1
+ const mockAcme = {
2
+ directory: {
3
+ letsencrypt: {
4
+ production: 'https://acme-v02.api.letsencrypt.org/directory',
5
+ staging: 'https://acme-staging-v02.api.letsencrypt.org/directory'
6
+ }
7
+ },
8
+ forge: {
9
+ createPrivateKey: jest.fn().mockResolvedValue('mock-private-key'),
10
+ createCsr: jest.fn().mockResolvedValue(['mock-key', 'mock-csr'])
11
+ },
12
+ Client: jest.fn().mockImplementation(() => ({
13
+ auto: jest.fn().mockResolvedValue('mock-certificate')
14
+ }))
15
+ }
16
+
17
+ module.exports = mockAcme
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Mock implementation of bcrypt for server tests
3
+ */
4
+
5
+ const bcrypt = {
6
+ hash: jest.fn((password, rounds, callback) => {
7
+ const mockHash = `$2b$${rounds}$mocksalt${password.substring(0, 10).padEnd(10, '0')}`
8
+
9
+ if (callback) {
10
+ setTimeout(() => callback(null, mockHash), 0)
11
+ } else {
12
+ return Promise.resolve(mockHash)
13
+ }
14
+ }),
15
+
16
+ hashSync: jest.fn((password, rounds) => {
17
+ return `$2b$${rounds}$mocksalt${password.substring(0, 10).padEnd(10, '0')}`
18
+ }),
19
+
20
+ compare: jest.fn((password, hash, callback) => {
21
+ // Simple mock comparison - check if password is in the hash
22
+ const isValid = hash.includes(password.substring(0, 10).padEnd(10, '0'))
23
+
24
+ if (callback) {
25
+ setTimeout(() => callback(null, isValid), 0)
26
+ } else {
27
+ return Promise.resolve(isValid)
28
+ }
29
+ }),
30
+
31
+ compareSync: jest.fn((password, hash) => {
32
+ return hash.includes(password.substring(0, 10).padEnd(10, '0'))
33
+ }),
34
+
35
+ genSalt: jest.fn((rounds, callback) => {
36
+ const mockSalt = `$2b$${rounds}$mocksalt1234567890123456`
37
+
38
+ if (callback) {
39
+ setTimeout(() => callback(null, mockSalt), 0)
40
+ } else {
41
+ return Promise.resolve(mockSalt)
42
+ }
43
+ }),
44
+
45
+ genSaltSync: jest.fn(rounds => {
46
+ return `$2b$${rounds}$mocksalt1234567890123456`
47
+ })
48
+ }
49
+
50
+ module.exports = bcrypt
@@ -0,0 +1,389 @@
1
+ /**
2
+ * Mock implementation of the child_process module for server tests
3
+ * Provides comprehensive mocking of process spawning and management
4
+ */
5
+
6
+ const {createMockEventEmitter} = require('./testHelpers')
7
+ const {createMockChildProcess} = require('./testFactories')
8
+
9
+ // Track active child processes
10
+ const activeProcesses = new Map()
11
+ let nextPid = 1000
12
+
13
+ const createMockChildProcessInstance = (command, args = [], options = {}) => {
14
+ const pid = nextPid++
15
+ const child = createMockEventEmitter()
16
+
17
+ Object.assign(child, {
18
+ // Process properties
19
+ pid,
20
+ killed: false,
21
+ connected: options.stdio !== 'ignore',
22
+ exitCode: null,
23
+ signalCode: null,
24
+ spawnfile: command,
25
+ spawnargs: [command, ...args],
26
+
27
+ // Stdio streams
28
+ stdin: options.stdio === 'ignore' ? null : createMockWritableStream(),
29
+ stdout: options.stdio === 'ignore' ? null : createMockReadableStream(),
30
+ stderr: options.stdio === 'ignore' ? null : createMockReadableStream(),
31
+
32
+ // Methods
33
+ kill: jest.fn((signal = 'SIGTERM') => {
34
+ if (child.killed) return false
35
+
36
+ child.killed = true
37
+ child.signalCode = signal
38
+
39
+ // Simulate process termination
40
+ setTimeout(() => {
41
+ if (signal === 'SIGKILL') {
42
+ child.exitCode = null
43
+ child.signalCode = 'SIGKILL'
44
+ } else {
45
+ child.exitCode = signal === 'SIGTERM' ? 0 : 1
46
+ }
47
+
48
+ child.emit('exit', child.exitCode, child.signalCode)
49
+ child.emit('close', child.exitCode, child.signalCode)
50
+
51
+ activeProcesses.delete(pid)
52
+ }, 0)
53
+
54
+ return true
55
+ }),
56
+
57
+ send: jest.fn((message, sendHandle, options, callback) => {
58
+ if (!child.connected) {
59
+ const error = new Error('Channel closed')
60
+ if (callback) callback(error)
61
+ return false
62
+ }
63
+
64
+ if (typeof options === 'function') {
65
+ callback = options
66
+ options = {}
67
+ }
68
+ if (typeof sendHandle === 'function') {
69
+ callback = sendHandle
70
+ sendHandle = undefined
71
+ options = {}
72
+ }
73
+
74
+ // Simulate message sending
75
+ setTimeout(() => {
76
+ if (callback) callback(null)
77
+ }, 0)
78
+
79
+ return true
80
+ }),
81
+
82
+ disconnect: jest.fn(() => {
83
+ if (child.connected) {
84
+ child.connected = false
85
+ child.emit('disconnect')
86
+ }
87
+ }),
88
+
89
+ ref: jest.fn(() => child),
90
+ unref: jest.fn(() => child),
91
+
92
+ // Test helpers
93
+ __simulateExit: (code = 0, signal = null) => {
94
+ child.exitCode = code
95
+ child.signalCode = signal
96
+ child.killed = !!signal
97
+
98
+ setTimeout(() => {
99
+ child.emit('exit', code, signal)
100
+ child.emit('close', code, signal)
101
+ activeProcesses.delete(pid)
102
+ }, 0)
103
+ },
104
+
105
+ __simulateError: error => {
106
+ setTimeout(() => {
107
+ child.emit('error', error)
108
+ }, 0)
109
+ },
110
+
111
+ __simulateStdout: data => {
112
+ if (child.stdout) {
113
+ child.stdout.__simulateData(data)
114
+ }
115
+ },
116
+
117
+ __simulateStderr: data => {
118
+ if (child.stderr) {
119
+ child.stderr.__simulateData(data)
120
+ }
121
+ },
122
+
123
+ __simulateMessage: message => {
124
+ if (child.connected) {
125
+ setTimeout(() => {
126
+ child.emit('message', message)
127
+ }, 0)
128
+ }
129
+ }
130
+ })
131
+
132
+ activeProcesses.set(pid, child)
133
+ return child
134
+ }
135
+
136
+ const createMockReadableStream = () => {
137
+ const stream = createMockEventEmitter()
138
+
139
+ Object.assign(stream, {
140
+ readable: true,
141
+ destroyed: false,
142
+
143
+ read: jest.fn(),
144
+ setEncoding: jest.fn(encoding => {
145
+ stream._encoding = encoding
146
+ return stream
147
+ }),
148
+ pause: jest.fn(() => {
149
+ stream._paused = true
150
+ return stream
151
+ }),
152
+ resume: jest.fn(() => {
153
+ stream._paused = false
154
+ return stream
155
+ }),
156
+ pipe: jest.fn(destination => {
157
+ return destination
158
+ }),
159
+ unpipe: jest.fn(destination => {
160
+ return stream
161
+ }),
162
+ destroy: jest.fn(error => {
163
+ stream.destroyed = true
164
+ if (error) {
165
+ stream.emit('error', error)
166
+ }
167
+ stream.emit('close')
168
+ return stream
169
+ }),
170
+
171
+ // Test helpers
172
+ __simulateData: data => {
173
+ if (!stream.destroyed && stream.readable) {
174
+ stream.emit('data', Buffer.isBuffer(data) ? data : Buffer.from(data))
175
+ }
176
+ },
177
+
178
+ __simulateEnd: () => {
179
+ if (!stream.destroyed) {
180
+ stream.emit('end')
181
+ }
182
+ },
183
+
184
+ __simulateError: error => {
185
+ stream.emit('error', error)
186
+ }
187
+ })
188
+
189
+ return stream
190
+ }
191
+
192
+ const createMockWritableStream = () => {
193
+ const stream = createMockEventEmitter()
194
+
195
+ Object.assign(stream, {
196
+ writable: true,
197
+ destroyed: false,
198
+
199
+ write: jest.fn((chunk, encoding, callback) => {
200
+ if (stream.destroyed || !stream.writable) {
201
+ const error = new Error('Cannot write to destroyed stream')
202
+ if (callback) callback(error)
203
+ return false
204
+ }
205
+
206
+ if (typeof encoding === 'function') {
207
+ callback = encoding
208
+ }
209
+
210
+ if (callback) {
211
+ setTimeout(callback, 0)
212
+ }
213
+
214
+ return true
215
+ }),
216
+
217
+ end: jest.fn((chunk, encoding, callback) => {
218
+ if (chunk) {
219
+ stream.write(chunk, encoding)
220
+ }
221
+
222
+ if (typeof encoding === 'function') {
223
+ callback = encoding
224
+ }
225
+ if (typeof chunk === 'function') {
226
+ callback = chunk
227
+ }
228
+
229
+ stream.writable = false
230
+
231
+ setTimeout(() => {
232
+ stream.emit('finish')
233
+ if (callback) callback()
234
+ }, 0)
235
+
236
+ return stream
237
+ }),
238
+
239
+ destroy: jest.fn(error => {
240
+ stream.destroyed = true
241
+ stream.writable = false
242
+
243
+ if (error) {
244
+ stream.emit('error', error)
245
+ }
246
+ stream.emit('close')
247
+ return stream
248
+ }),
249
+
250
+ // Test helpers
251
+ __simulateError: error => {
252
+ stream.emit('error', error)
253
+ }
254
+ })
255
+
256
+ return stream
257
+ }
258
+
259
+ const child_process = {
260
+ // Process spawning
261
+ spawn: jest.fn((command, args = [], options = {}) => {
262
+ const child = createMockChildProcessInstance(command, args, options)
263
+
264
+ // Simulate spawn delay
265
+ setTimeout(() => {
266
+ if (options.detached) {
267
+ child.unref()
268
+ }
269
+
270
+ child.emit('spawn')
271
+ }, 0)
272
+
273
+ return child
274
+ }),
275
+
276
+ exec: jest.fn((command, options, callback) => {
277
+ if (typeof options === 'function') {
278
+ callback = options
279
+ options = {}
280
+ }
281
+
282
+ const child = createMockChildProcessInstance('/bin/sh', ['-c', command], options)
283
+
284
+ // Simulate execution
285
+ setTimeout(() => {
286
+ const stdout = 'Mock stdout output'
287
+ const stderr = ''
288
+ const error = null
289
+
290
+ if (callback) {
291
+ callback(error, stdout, stderr)
292
+ }
293
+
294
+ child.__simulateExit(0)
295
+ }, 0)
296
+
297
+ return child
298
+ }),
299
+
300
+ execFile: jest.fn((file, args = [], options, callback) => {
301
+ if (typeof args === 'function') {
302
+ callback = args
303
+ args = []
304
+ options = {}
305
+ } else if (typeof options === 'function') {
306
+ callback = options
307
+ options = {}
308
+ }
309
+
310
+ const child = createMockChildProcessInstance(file, args, options)
311
+
312
+ // Simulate execution
313
+ setTimeout(() => {
314
+ const stdout = 'Mock execFile output'
315
+ const stderr = ''
316
+ const error = null
317
+
318
+ if (callback) {
319
+ callback(error, stdout, stderr)
320
+ }
321
+
322
+ child.__simulateExit(0)
323
+ }, 0)
324
+
325
+ return child
326
+ }),
327
+
328
+ fork: jest.fn((modulePath, args = [], options = {}) => {
329
+ const child = createMockChildProcessInstance(process.execPath, [modulePath, ...args], {
330
+ ...options,
331
+ stdio: ['pipe', 'pipe', 'pipe', 'ipc']
332
+ })
333
+
334
+ child.connected = true
335
+
336
+ // Simulate fork delay
337
+ setTimeout(() => {
338
+ child.emit('spawn')
339
+ }, 0)
340
+
341
+ return child
342
+ }),
343
+
344
+ execSync: jest.fn((command, options = {}) => {
345
+ // Simulate synchronous execution
346
+ if (options.encoding === 'buffer' || !options.encoding) {
347
+ return Buffer.from('Mock execSync output')
348
+ }
349
+ return 'Mock execSync output'
350
+ }),
351
+
352
+ execFileSync: jest.fn((file, args = [], options = {}) => {
353
+ // Simulate synchronous file execution
354
+ if (options.encoding === 'buffer' || !options.encoding) {
355
+ return Buffer.from('Mock execFileSync output')
356
+ }
357
+ return 'Mock execFileSync output'
358
+ }),
359
+
360
+ spawnSync: jest.fn((command, args = [], options = {}) => {
361
+ return {
362
+ pid: nextPid++,
363
+ output: [null, Buffer.from('Mock stdout'), Buffer.from('Mock stderr')],
364
+ stdout: Buffer.from('Mock stdout'),
365
+ stderr: Buffer.from('Mock stderr'),
366
+ status: 0,
367
+ signal: null,
368
+ error: null
369
+ }
370
+ }),
371
+
372
+ // Classes
373
+ ChildProcess: jest.fn(function () {
374
+ return createMockChildProcessInstance('mock-process')
375
+ }),
376
+
377
+ // Test helpers
378
+ __getActiveProcesses: () => new Map(activeProcesses),
379
+ __clearAll: () => {
380
+ activeProcesses.clear()
381
+ nextPid = 1000
382
+ },
383
+
384
+ __setNextPid: pid => {
385
+ nextPid = pid
386
+ }
387
+ }
388
+
389
+ module.exports = child_process