@joystick.js/cli-canary 0.0.0-canary.93 → 0.0.0-canary.94

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.
@@ -1,4 +1,45 @@
1
1
  import dev from "../../lib/dev/index.js";
2
+ process.hmrProcess.on("message", (message) => {
3
+ const processMessages = [
4
+ "SERVER_CLOSED",
5
+ "HAS_HMR_CONNECTIONS",
6
+ "HAS_NO_HMR_CONNECTIONS",
7
+ "HMR_UPDATE_COMPLETED"
8
+ ];
9
+ if (!processMessages.includes(message?.type)) {
10
+ process.loader.stable(message);
11
+ }
12
+ if (message?.type === "HAS_HMR_CONNECTIONS") {
13
+ process.hmrProcess.hasConnections = true;
14
+ }
15
+ if (message?.type === "HAS_NO_HMR_CONNECTIONS") {
16
+ process.hmrProcess.hasConnections = false;
17
+ }
18
+ if (message?.type === "HMR_UPDATE_COMPLETED") {
19
+ setTimeout(() => {
20
+ restartApplicationProcess(message?.sessions);
21
+ }, 500);
22
+ }
23
+ });
24
+ const startHMRProcess = () => {
25
+ const execArgv = ["--no-warnings"];
26
+ if (majorVersion < 19) {
27
+ execArgv.push("--experimental-specifier-resolution=node");
28
+ }
29
+ const hmrProcess = child_process.fork(
30
+ path.resolve(`${__dirname}/hmrServer.js`),
31
+ [],
32
+ {
33
+ execArgv,
34
+ // NOTE: Pipe stdin, stdout, and stderr. IPC establishes a message channel so we
35
+ // communicate with the child_process.
36
+ silent: true
37
+ }
38
+ );
39
+ process.hmrProcess = hmrProcess;
40
+ handleHMRProcessSTDIO();
41
+ handleHMRProcessMessages();
42
+ };
2
43
  var start_default = async (args = {}, options = {}) => {
3
44
  await dev({
4
45
  environment: args?.environment || "development",
@@ -76,6 +76,61 @@ const handleSignalEvents = (processIds = [], nodeMajorVersion = 0, __dirname = "
76
76
  throw new Error(`[dev.handleSignalEvents] ${exception.message}`);
77
77
  }
78
78
  };
79
+ const handleHMRProcessMessages = (options = {}) => {
80
+ try {
81
+ process.hmrProcess.on("message", (message) => {
82
+ const processMessages = [
83
+ "SERVER_CLOSED",
84
+ "HAS_HMR_CONNECTIONS",
85
+ "HAS_NO_HMR_CONNECTIONS",
86
+ "HMR_UPDATE_COMPLETED"
87
+ ];
88
+ if (!processMessages.includes(message?.type)) {
89
+ process.loader.stable(message);
90
+ }
91
+ if (message?.type === "HAS_HMR_CONNECTIONS") {
92
+ process.hmrProcess.hasConnections = true;
93
+ }
94
+ if (message?.type === "HAS_NO_HMR_CONNECTIONS") {
95
+ process.hmrProcess.hasConnections = false;
96
+ }
97
+ if (message?.type === "HMR_UPDATE_COMPLETED") {
98
+ setTimeout(() => {
99
+ handleRestartApplicationProcess({
100
+ ...options,
101
+ sessionsBeforeHMRUpdate: message?.sessions
102
+ });
103
+ }, 500);
104
+ }
105
+ });
106
+ } catch (exception) {
107
+ throw new Error(`[dev.handleHMRProcessMessages] ${exception.message}`);
108
+ }
109
+ };
110
+ const handleHMRProcessSTDIO = () => {
111
+ try {
112
+ if (process.hmrProcess) {
113
+ process.hmrProcess.on("error", (error) => {
114
+ CLILog(error.toString(), {
115
+ level: "danger",
116
+ docs: "https://github.com/cheatcode/joystick"
117
+ });
118
+ });
119
+ process.hmrProcess.stdout.on("data", (data) => {
120
+ console.log(data.toString());
121
+ });
122
+ process.hmrProcess.stderr.on("data", (data) => {
123
+ process.loader.stop();
124
+ CLILog(data.toString(), {
125
+ level: "danger",
126
+ docs: "https://github.com/cheatcode/joystick"
127
+ });
128
+ });
129
+ }
130
+ } catch (exception) {
131
+ throw new Error(`[dev.handleHMRProcessSTDIO] ${exception.message}`);
132
+ }
133
+ };
79
134
  const handleServerProcessMessages = () => {
80
135
  try {
81
136
  process.serverProcess.on("message", (message) => {
@@ -116,7 +171,7 @@ const handleServerProcessSTDIO = () => {
116
171
  throw new Error(`[dev.handleServerProcessSTDIO] ${exception.message}`);
117
172
  }
118
173
  };
119
- const handleAddOrChangeFile = async (context = {}, path2 = "") => {
174
+ const handleAddOrChangeFile = async (context = {}, path2 = "", options = {}) => {
120
175
  try {
121
176
  if (context.isAddingOrChangingFile) {
122
177
  const codependencies = await getCodependenciesForFile(path2);
@@ -144,59 +199,73 @@ const handleAddOrChangeFile = async (context = {}, path2 = "") => {
144
199
  if (context.isUIUpdate) {
145
200
  handleNotifyHMRClients(context.isHTMLUpdate);
146
201
  } else {
147
- handleRestartApplicationProcess();
202
+ handleRestartApplicationProcess(options);
148
203
  }
149
- return;
150
204
  }
151
205
  }
152
206
  } catch (exception) {
153
207
  throw new Error(`[dev.handleAddOrChangeFile] ${exception.message}`);
154
208
  }
155
209
  };
156
- const handleAddDirectory = (context = {}, path2 = "") => {
210
+ const handleAddDirectory = (context = {}, path2 = "", options = {}) => {
157
211
  try {
158
212
  if (context.isAddDirectory) {
159
213
  fs.mkdirSync(`./.joystick/build/${path2}`);
160
214
  if (context.isUIUpdate) {
161
215
  handleNotifyHMRClients(context.isHTMLUpdate);
162
216
  } else {
163
- handleRestartApplicationProcess();
217
+ handleRestartApplicationProcess(options);
164
218
  }
165
219
  }
166
220
  } catch (exception) {
167
221
  throw new Error(`[dev.handleAddDirectory] ${exception.message}`);
168
222
  }
169
223
  };
170
- const handleCopyFile = (context = {}, path2 = "") => {
224
+ const handleCopyFile = (context = {}, path2 = "", options = {}) => {
171
225
  try {
172
226
  if (context.isFileToCopy && !context.isDirectory) {
173
227
  fs.writeFileSync(`./.joystick/build/${path2}`, fs.readFileSync(path2));
174
228
  if (context.isUIUpdate) {
175
229
  handleNotifyHMRClients(context.isHTMLUpdate);
176
230
  } else {
177
- handleRestartApplicationProcess();
231
+ handleRestartApplicationProcess(options);
178
232
  }
179
233
  }
180
234
  } catch (exception) {
181
235
  throw new Error(`[dev.handleCopyFile] ${exception.message}`);
182
236
  }
183
237
  };
184
- const handleCopyDirectory = (context = {}, path2 = "") => {
238
+ const handleCopyDirectory = (context = {}, path2 = "", options = {}) => {
185
239
  try {
186
240
  if (context.isFileToCopy && context.isDirectory && !context.isExistingDirectoryInBuild) {
187
241
  fs.mkdirSync(`./.joystick/build/${path2}`);
188
242
  if (context.isUIUpdate) {
189
243
  handleNotifyHMRClients(context.isHTMLUpdate);
190
244
  } else {
191
- handleRestartApplicationProcess();
245
+ handleRestartApplicationProcess(options);
192
246
  }
193
247
  }
194
248
  } catch (exception) {
195
249
  throw new Error(`[dev.handleCopyDirectory] ${exception.message}`);
196
250
  }
197
251
  };
198
- const handleRestartApplicationProcess = () => {
252
+ const handleRestartApplicationProcess = async (options = {}) => {
199
253
  try {
254
+ if (process.serverProcess && process.serverProcess.pid) {
255
+ process.loader.text("Restarting app...");
256
+ process.serverProcess.kill();
257
+ await startApp({
258
+ nodeMajorVersion: options?.nodeMajorVersion,
259
+ port: options?.port,
260
+ sessionsBeforeHMRUpdate: options?.sessionsBeforeHMRUpdate
261
+ });
262
+ return Promise.resolve();
263
+ }
264
+ process.loader.text("Starting app...");
265
+ startApplicationProcess();
266
+ if (!process.hmrProcess) {
267
+ startHMR();
268
+ }
200
269
  } catch (exception) {
201
270
  throw new Error(`[dev.handleRestartApplicationProcess] ${exception.message}`);
202
271
  }
@@ -259,10 +328,10 @@ const startFileWatcher = (options = {}) => {
259
328
  checkForRequiredFiles();
260
329
  process.loader.text("Rebuilding app...");
261
330
  const watchChangeContext = getWatchChangeContext(event, path2);
262
- handleCopyDirectory(watchChangeContext, path2);
263
- handleCopyFile(watchChangeContext, path2);
264
- handleAddDirectory(watchChangeContext, path2);
265
- await handleAddOrChangeFile(watchChangeContext, path2);
331
+ handleCopyDirectory(watchChangeContext, path2, options);
332
+ handleCopyFile(watchChangeContext, path2, options);
333
+ handleAddDirectory(watchChangeContext, path2, options);
334
+ await handleAddOrChangeFile(watchChangeContext, path2, options);
266
335
  if (watchChangeContext?.isSettingsUpdate) {
267
336
  await loadSettings({
268
337
  environment: options.environment,
@@ -433,6 +502,9 @@ const dev = async (options, { resolve, reject }) => {
433
502
  __dirname
434
503
  });
435
504
  processIds.push(hmrProcess.pid);
505
+ process.hmrProcess = hmrProcess;
506
+ handleHMRProcessSTDIO();
507
+ handleHMRProcessMessages(options);
436
508
  }
437
509
  handleSignalEvents(
438
510
  processIds,
@@ -56,7 +56,7 @@ const startApp = (options, { resolve, reject }) => {
56
56
  try {
57
57
  validateOptions(options);
58
58
  const execArgv = getExecArgs(options?.nodeMajorVersion);
59
- const serverProcess = handleStartServerProcess(execArgv);
59
+ const serverProcess = handleStartServerProcess(execArgv, options?.sessionsBeforeHMRUpdate);
60
60
  return resolve(serverProcess);
61
61
  } catch (exception) {
62
62
  reject(`[startApp] ${exception.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joystick.js/cli-canary",
3
- "version": "0.0.0-canary.93",
3
+ "version": "0.0.0-canary.94",
4
4
  "type": "module",
5
5
  "description": "CLI for the Joystick JavaScript framework.",
6
6
  "main": "development.js",
@@ -1,5 +1,661 @@
1
+ /* eslint-disable consistent-return */
2
+
3
+ //import child_process from "child_process";
4
+ //import path, { dirname } from "path";
5
+ //import { fileURLToPath } from "url";
6
+ //import ps from "ps-node";
7
+ //import { killPortProcess } from "kill-port-process";
8
+ //import fs from "fs";
9
+ //import chokidar from "chokidar";
10
+ //import Loader from "../../lib/loader.js";
11
+ //import getFilesToBuild from "./getFilesToBuild.js";
12
+ //import buildFiles from "./buildFiles.js";
13
+ //import filesToCopy from "./filesToCopy.js";
14
+ //import checkIfPortAvailable from "./checkIfPortAvailable.js";
15
+ //import getCodependenciesForFile from "./getCodependenciesForFile.js";
16
+ //import isValidJSONString from "../../lib/isValidJSONString.js";
17
+ //import startDatabaseProvider from "./databases/startProvider.js";
18
+ //import CLILog from "../../lib/CLILog.js";
19
+ //import removeDeletedDependenciesFromMap from "./removeDeletedDependenciesFromMap.js";
20
+ //import validateDatabasesFromSettings from "../../lib/validateDatabasesFromSettings.js";
21
+ //import wait from "../../lib/wait.js";
1
22
  import dev from "../../lib/dev/index.js";
2
23
 
24
+ //const majorVersion = parseInt(
25
+ // process?.version?.split(".")[0]?.replace("v", ""),
26
+ // 10
27
+ //);
28
+ //
29
+ //const __filename = fileURLToPath(import.meta.url);
30
+ //const __dirname = dirname(__filename);
31
+ //
32
+ //const killProcess = (pid = 0) => {
33
+ // return new Promise((resolve) => {
34
+ // ps.kill(pid, () => {
35
+ // resolve();
36
+ // });
37
+ // });
38
+ //};
39
+ //
40
+ //const watchlist = [
41
+ // { path: "ui" },
42
+ // { path: "lib" },
43
+ // { path: "i18n" },
44
+ // { path: "api" },
45
+ // { path: "email" },
46
+ // { path: "fixtures" },
47
+ // { path: "routes" },
48
+ // { path: "index.client.js" },
49
+ // { path: "index.server.js" },
50
+ // ...filesToCopy,
51
+ //];
52
+ //
53
+ //const requiredFileCheck = () => {
54
+ // return new Promise((resolve) => {
55
+ // const requiredFiles = [
56
+ // { path: "index.server.js", type: "file" },
57
+ // { path: "index.html", type: "file" },
58
+ // { path: "index.client.js", type: "file" },
59
+ // { path: "api", type: "directory" },
60
+ // { path: "i18n", type: "directory" },
61
+ // { path: "lib", type: "directory" },
62
+ // { path: "public", type: "directory" },
63
+ // { path: "ui", type: "directory" },
64
+ // { path: "ui/components", type: "directory" },
65
+ // { path: "ui/layouts", type: "directory" },
66
+ // { path: "ui/pages", type: "directory" },
67
+ // ];
68
+ //
69
+ // requiredFiles.forEach((requiredFile) => {
70
+ // const exists = fs.existsSync(`${process.cwd()}/${requiredFile.path}`);
71
+ // const stats =
72
+ // exists && fs.statSync(`${process.cwd()}/${requiredFile.path}`);
73
+ // const isFile = stats && stats.isFile();
74
+ // const isDirectory = stats && stats.isDirectory();
75
+ //
76
+ // if (requiredFile && requiredFile.type === "file") {
77
+ // if (!exists || (exists && !isFile)) {
78
+ // CLILog(
79
+ // `The path ${requiredFile.path} must exist in your project and must be a file (not a directory).`,
80
+ // {
81
+ // level: "danger",
82
+ // docs: "https://github.com/cheatcode/joystick#folder-and-file-structure",
83
+ // }
84
+ // );
85
+ // process.exit(0);
86
+ // }
87
+ // }
88
+ //
89
+ // if (requiredFile && requiredFile.type === "directory") {
90
+ // if (!exists || (exists && !isDirectory)) {
91
+ // CLILog(
92
+ // `The path ${requiredFile.path} must exist in your project and must be a directory (not a file).`,
93
+ // {
94
+ // level: "danger",
95
+ // docs: "https://github.com/cheatcode/joystick#folder-and-file-structure",
96
+ // }
97
+ // );
98
+ // process.exit(0);
99
+ // }
100
+ // }
101
+ // });
102
+ //
103
+ // resolve();
104
+ // });
105
+ //};
106
+ //
107
+ //const handleCleanup = async (
108
+ // processIds = [process?.serverProcess?.pid, process?.hmrProcess?.pid]
109
+ //) => {
110
+ // for (let i = 0; i < processIds?.length; i += 1) {
111
+ // const processId = processIds[i];
112
+ //
113
+ // if (processId) {
114
+ // await killProcess(processId);
115
+ // }
116
+ // }
117
+ //
118
+ // const databases = Object.entries(process._databases || {});
119
+ //
120
+ // for (let i = 0; i < databases?.length; i += 1) {
121
+ // const [provider, providerConnection] = databases[i];
122
+ //
123
+ // if (providerConnection?.pid) {
124
+ // await killProcess(providerConnection.pid);
125
+ // }
126
+ //
127
+ // if (!providerConnection?.pid) {
128
+ // const providerConnections = Object.entries(providerConnection);
129
+ //
130
+ // for (let pc = 0; pc < providerConnections?.length; pc += 1) {
131
+ // const [_connectionName, connection] = providerConnections[pc];
132
+ //
133
+ // if (connection?.pid) {
134
+ // await killProcess(connection?.pid);
135
+ // }
136
+ // }
137
+ // }
138
+ // }
139
+ //};
140
+ //
141
+ //const getDatabaseProcessIds = () => {
142
+ // const databaseProcessIds = [];
143
+ // const databases = Object.entries(process._databases || {});
144
+ //
145
+ // for (let i = 0; i < databases?.length; i += 1) {
146
+ // const [_provider, providerConnection] = databases[i];
147
+ //
148
+ // if (providerConnection?.pid) {
149
+ // databaseProcessIds.push(providerConnection.pid);
150
+ // }
151
+ //
152
+ // if (!providerConnection?.pid) {
153
+ // const providerConnections = Object.entries(providerConnection);
154
+ //
155
+ // for (let pc = 0; pc < providerConnections?.length; pc += 1) {
156
+ // const [_connectionName, connection] = providerConnections[pc];
157
+ //
158
+ // if (connection?.pid) {
159
+ // databaseProcessIds.push(connection.pid);
160
+ // }
161
+ // }
162
+ // }
163
+ // }
164
+ //
165
+ // return databaseProcessIds;
166
+ //};
167
+ //
168
+ //const handleSignalEvents = (processIds = []) => {
169
+ // const execArgv = ["--no-warnings"];
170
+ //
171
+ // if (majorVersion < 19) {
172
+ // execArgv.push("--experimental-specifier-resolution=node");
173
+ // }
174
+ //
175
+ // const cleanupProcess = child_process.fork(
176
+ // path.resolve(`${__dirname}/cleanup/index.js`),
177
+ // [],
178
+ // {
179
+ // // NOTE: Run in detached mode so when parent process dies, the child still runs
180
+ // // and cleanup completes.
181
+ // detached: true,
182
+ // execArgv,
183
+ // // NOTE: Pipe stdin, stdout, and stderr. IPC establishes a message channel so we
184
+ // // communicate with the child_process.
185
+ // silent: true,
186
+ // }
187
+ // );
188
+ //
189
+ // process.on("SIGINT", async () => {
190
+ // const databaseProcessIds = getDatabaseProcessIds();
191
+ // cleanupProcess.send(JSON.stringify(({ processIds: [...processIds, ...databaseProcessIds] })));
192
+ // process.exit();
193
+ // });
194
+ //
195
+ // process.on("SIGTERM", async () => {
196
+ // const databaseProcessIds = getDatabaseProcessIds();
197
+ // cleanupProcess.send(JSON.stringify(({ processIds: [...processIds, ...databaseProcessIds] })));
198
+ // process.exit();
199
+ // });
200
+ //};
201
+ //
202
+ //const handleHMRProcessMessages = () => {
203
+ process.hmrProcess.on("message", (message) => {
204
+ const processMessages = [
205
+ "SERVER_CLOSED",
206
+ "HAS_HMR_CONNECTIONS",
207
+ "HAS_NO_HMR_CONNECTIONS",
208
+ "HMR_UPDATE_COMPLETED",
209
+ ];
210
+
211
+ if (!processMessages.includes(message?.type)) {
212
+ process.loader.stable(message);
213
+ }
214
+
215
+ if (message?.type === "HAS_HMR_CONNECTIONS") {
216
+ process.hmrProcess.hasConnections = true;
217
+ }
218
+
219
+ if (message?.type === "HAS_NO_HMR_CONNECTIONS") {
220
+ process.hmrProcess.hasConnections = false;
221
+ }
222
+
223
+ if (message?.type === "HMR_UPDATE_COMPLETED") {
224
+ // NOTE: Do a setTimeout to ensure that server is still available while the HMR update completes.
225
+ // Necessary because some updates are instance, but others might mount a UI that needs a server
226
+ // available (e.g., runs API requests).
227
+ setTimeout(() => {
228
+ restartApplicationProcess(message?.sessions);
229
+ }, 500);
230
+ }
231
+ });
232
+ //};
233
+ //
234
+ //const handleHMRProcessSTDIO = () => {
235
+ // try {
236
+ // if (process.hmrProcess) {
237
+ // process.hmrProcess.on("error", (error) => {
238
+ // CLILog(error.toString(), {
239
+ // level: "danger",
240
+ // docs: "https://github.com/cheatcode/joystick",
241
+ // });
242
+ // });
243
+ //
244
+ // process.hmrProcess.stdout.on("data", (data) => {
245
+ // console.log(data.toString());
246
+ // });
247
+ //
248
+ // process.hmrProcess.stderr.on("data", (data) => {
249
+ // process.loader.stop();
250
+ // CLILog(data.toString(), {
251
+ // level: "danger",
252
+ // docs: "https://github.com/cheatcode/joystick",
253
+ // });
254
+ // });
255
+ // }
256
+ // } catch (exception) {
257
+ // throw new Error(`[dev.handleHMRProcessSTDIO] ${exception.message}`);
258
+ // }
259
+ //};
260
+ //
261
+ const startHMRProcess = () => {
262
+ const execArgv = ["--no-warnings"];
263
+
264
+ if (majorVersion < 19) {
265
+ execArgv.push("--experimental-specifier-resolution=node");
266
+ }
267
+
268
+ const hmrProcess = child_process.fork(
269
+ path.resolve(`${__dirname}/hmrServer.js`),
270
+ [],
271
+ {
272
+ execArgv,
273
+ // NOTE: Pipe stdin, stdout, and stderr. IPC establishes a message channel so we
274
+ // communicate with the child_process.
275
+ silent: true,
276
+ }
277
+ );
278
+
279
+ process.hmrProcess = hmrProcess;
280
+
281
+ handleHMRProcessSTDIO();
282
+ handleHMRProcessMessages();
283
+ };
284
+ //
285
+ //const notifyHMRClients = (indexHTMLChanged = false) => {
286
+ // const settings = loadSettings(process.env.NODE_ENV);
287
+ // process.hmrProcess.send(
288
+ // JSON.stringify({
289
+ // type: "RESTART_SERVER",
290
+ // settings,
291
+ // indexHTMLChanged,
292
+ // })
293
+ // );
294
+ //};
295
+ //
296
+ //const handleServerProcessMessages = () => {
297
+ // process.serverProcess.on("message", (message) => {
298
+ // const processMessages = ["SERVER_CLOSED"];
299
+ //
300
+ // if (!processMessages.includes(message)) {
301
+ // process.loader.stable(message);
302
+ // }
303
+ // });
304
+ //};
305
+ //
306
+ //const handleServerProcessSTDIO = () => {
307
+ // try {
308
+ // if (process.serverProcess) {
309
+ // process.serverProcess.on("error", (error) => {
310
+ // console.log(error);
311
+ // });
312
+ //
313
+ // process.serverProcess.stdout.on("data", (data) => {
314
+ // const message = data.toString();
315
+ //
316
+ // if (message && message.includes("App running at:")) {
317
+ // process.loader.stable(message);
318
+ // } else {
319
+ // if (message && !message.includes("BUILD_ERROR")) {
320
+ // console.log(message);
321
+ // }
322
+ // }
323
+ // });
324
+ //
325
+ // process.serverProcess.stderr.on("data", (data) => {
326
+ // process.loader.stop();
327
+ // CLILog(data.toString(), {
328
+ // level: "danger",
329
+ // docs: "https://github.com/cheatcode/joystick",
330
+ // });
331
+ // });
332
+ // }
333
+ // } catch (exception) {
334
+ // throw new Error(`[dev.handleServerProcessSTDIO] ${exception.message}`);
335
+ // }
336
+ //};
337
+ //
338
+ //const startApplicationProcess = (sessionsBeforeHMRUpdate = null) => {
339
+ // const execArgv = ["--no-warnings"];
340
+ //
341
+ // if (majorVersion < 19) {
342
+ // execArgv.push("--experimental-specifier-resolution=node");
343
+ // }
344
+ //
345
+ // if (
346
+ // process.env.NODE_ENV === "development" &&
347
+ // process.env.IS_DEBUG_MODE === "true"
348
+ // ) {
349
+ // execArgv.push("--inspect");
350
+ // }
351
+ //
352
+ // const serverProcess = child_process.fork(
353
+ // path.resolve(".joystick/build/index.server.js"),
354
+ // [],
355
+ // {
356
+ // execArgv,
357
+ // // NOTE: Pipe stdin, stdout, and stderr. IPC establishes a message channel so we
358
+ // // communicate with the child_process.
359
+ // silent: true,
360
+ // env: {
361
+ // FORCE_COLOR: "1",
362
+ // LOGS_PATH: process.env.LOGS_PATH,
363
+ // NODE_ENV: process.env.NODE_ENV,
364
+ // ROOT_URL: process.env.ROOT_URL,
365
+ // PORT: process.env.PORT,
366
+ // JOYSTICK_SETTINGS: process.env.JOYSTICK_SETTINGS,
367
+ // HMR_SESSIONS: sessionsBeforeHMRUpdate,
368
+ // },
369
+ // }
370
+ // );
371
+ //
372
+ // process.serverProcess = serverProcess;
373
+ //
374
+ // handleServerProcessSTDIO();
375
+ // handleServerProcessMessages();
376
+ //
377
+ // return serverProcess;
378
+ //};
379
+ //
380
+ //const restartApplicationProcess = async (sessionsBeforeHMRUpdate = null) => {
381
+ // if (process.serverProcess && process.serverProcess.pid) {
382
+ // process.loader.text("Restarting app...");
383
+ // process.serverProcess.kill();
384
+ // startApplicationProcess(sessionsBeforeHMRUpdate);
385
+ // return Promise.resolve();
386
+ // }
387
+ //
388
+ // // NOTE: Original process was never initialized due to an error.
389
+ // process.loader.text("Starting app...");
390
+ // startApplicationProcess();
391
+ //
392
+ // if (!process.hmrProcess) {
393
+ // startHMRProcess();
394
+ // }
395
+ //};
396
+ //
397
+ //const initialBuild = async (buildSettings = {}) => {
398
+ // const buildPath = `.joystick/build`;
399
+ // const fileMapPath = `.joystick/build/fileMap.json`;
400
+ //
401
+ // if (!fs.existsSync(buildPath)) {
402
+ // fs.mkdirSync(".joystick/build");
403
+ // }
404
+ //
405
+ // if (fs.existsSync(fileMapPath)) {
406
+ // fs.unlinkSync(fileMapPath);
407
+ // }
408
+ //
409
+ // process.loader.text("Building app...");
410
+ //
411
+ // await requiredFileCheck();
412
+ //
413
+ // const filesToBuild = getFilesToBuild(buildSettings?.excludedPaths, "start");
414
+ // const fileResults = await buildFiles(
415
+ // filesToBuild,
416
+ // null,
417
+ // process.env.NODE_ENV
418
+ // );
419
+ //
420
+ // const hasErrors = [...fileResults]
421
+ // .filter((result) => !!result)
422
+ // .map(({ success }) => success)
423
+ // .includes(false);
424
+ //
425
+ // if (!hasErrors) {
426
+ // startApplicationProcess();
427
+ // startHMRProcess();
428
+ // // If the file has errors on startup, no way to trigger a restart w/o a watcher.
429
+ // }
430
+ //};
431
+ //
432
+ //const startWatcher = async (buildSettings = {}) => {
433
+ // await initialBuild(buildSettings);
434
+ //
435
+ // const watcher = chokidar.watch(
436
+ // watchlist.map(({ path }) => path),
437
+ // {
438
+ // ignoreInitial: true,
439
+ // }
440
+ // );
441
+ //
442
+ // watcher.on("all", async (event, path) => {
443
+ // await requiredFileCheck();
444
+ // process.loader.text("Rebuilding app...");
445
+ // const isHTMLUpdate = path === "index.html";
446
+ // const isUIPath = path?.includes("ui/") || path === 'index.css' || isHTMLUpdate;
447
+ // const isUIUpdate =
448
+ // (process.hmrProcess.hasConnections && isUIPath) || false;
449
+ //
450
+ // if (
451
+ // ["addDir"].includes(event) &&
452
+ // fs.existsSync(path) &&
453
+ // fs.lstatSync(path).isDirectory() &&
454
+ // !fs.existsSync(`./.joystick/build/${path}`)
455
+ // ) {
456
+ // fs.mkdirSync(`./.joystick/build/${path}`);
457
+ //
458
+ // if (isUIUpdate) {
459
+ // notifyHMRClients(isHTMLUpdate);
460
+ // } else {
461
+ // restartApplicationProcess();
462
+ // }
463
+ //
464
+ // return;
465
+ // }
466
+ //
467
+ // // If path is file to copy...
468
+ // if (!!filesToCopy.find((fileToCopy) => fileToCopy.path === path)) {
469
+ // const isDirectory = fs.statSync(path).isDirectory();
470
+ //
471
+ // // If path to copy is a directory and doesn't exist in the build...
472
+ // if (isDirectory && !fs.existsSync(`./.joystick/build/${path}`)) {
473
+ // fs.mkdirSync(`./.joystick/build/${path}`);
474
+ // }
475
+ //
476
+ // // If path to copy is not a directory...
477
+ // if (!isDirectory) {
478
+ // fs.writeFileSync(`./.joystick/build/${path}`, fs.readFileSync(path));
479
+ // }
480
+ //
481
+ // // Load settings in case we copied new values over...
482
+ // loadSettings(process.env.NODE_ENV);
483
+ //
484
+ // if (isUIUpdate) {
485
+ // notifyHMRClients(isHTMLUpdate);
486
+ // } else {
487
+ // restartApplicationProcess();
488
+ // }
489
+ //
490
+ // return;
491
+ // }
492
+ //
493
+ // if (["add", "change"].includes(event) && fs.existsSync(path)) {
494
+ // const codependencies = getCodependenciesForFile(path);
495
+ // const fileResults = await buildFiles(
496
+ // [path, ...(codependencies?.existing || [])],
497
+ // null,
498
+ // process.env.NODE_ENV
499
+ // );
500
+ // const fileResultsHaveErrors = fileResults
501
+ // .filter((result) => !!result)
502
+ // .map(({ success }) => success)
503
+ // .includes(false);
504
+ //
505
+ // removeDeletedDependenciesFromMap(codependencies.deleted);
506
+ //
507
+ // const hasErrors = fileResultsHaveErrors;
508
+ //
509
+ // if (process.serverProcess && hasErrors) {
510
+ // process.serverProcess.send(
511
+ // JSON.stringify({
512
+ // error: "BUILD_ERROR",
513
+ // paths: fileResults
514
+ // .filter(({ success }) => !success)
515
+ // .map(({ path: pathWithError, error }) => ({
516
+ // path: pathWithError,
517
+ // error,
518
+ // })),
519
+ // })
520
+ // );
521
+ //
522
+ // return;
523
+ // }
524
+ //
525
+ // if (!hasErrors) {
526
+ // process.initialBuildComplete = true;
527
+ //
528
+ // if (isUIUpdate) {
529
+ // notifyHMRClients(isHTMLUpdate);
530
+ // } else {
531
+ // restartApplicationProcess();
532
+ // }
533
+ //
534
+ // return;
535
+ // }
536
+ // }
537
+ //
538
+ // if (
539
+ // ["unlink", "unlinkDir"].includes(event) &&
540
+ // !fs.existsSync(`./.joystick/build/${path}`)
541
+ // ) {
542
+ // if (isUIUpdate) {
543
+ // notifyHMRClients(isHTMLUpdate);
544
+ // } else {
545
+ // restartApplicationProcess();
546
+ // }
547
+ //
548
+ // return;
549
+ // }
550
+ //
551
+ // if (
552
+ // ["unlink", "unlinkDir"].includes(event) &&
553
+ // fs.existsSync(`./.joystick/build/${path}`)
554
+ // ) {
555
+ // const pathToUnlink = `./.joystick/build/${path}`;
556
+ // const stats = fs.lstatSync(pathToUnlink);
557
+ //
558
+ // if (stats.isDirectory()) {
559
+ // fs.rmdirSync(pathToUnlink, { recursive: true });
560
+ // }
561
+ //
562
+ // if (stats.isFile()) {
563
+ // fs.unlinkSync(pathToUnlink);
564
+ // }
565
+ //
566
+ // if (isUIUpdate) {
567
+ // notifyHMRClients(isHTMLUpdate);
568
+ // } else {
569
+ // restartApplicationProcess();
570
+ // }
571
+ //
572
+ // return;
573
+ // }
574
+ // });
575
+ //};
576
+ //
577
+ //const startDatabase = async (database = {}, databasePort = 2610, hasMultipleOfProvider = false) => {
578
+ // process._databases = {
579
+ // ...(process._databases || {}),
580
+ // [database.provider]: !hasMultipleOfProvider ? await startDatabaseProvider(database, databasePort) : {
581
+ // ...((process._databases && process._databases[database.provider]) || {}),
582
+ // [database?.name || `${database.provider}_${databasePort}`]: await startDatabaseProvider(database, databasePort)
583
+ // },
584
+ // };
585
+ //
586
+ // return Promise.resolve(process._databases);
587
+ //};
588
+ //
589
+ //const startDatabases = async (databasePortStart = 2610) => {
590
+ // try {
591
+ // const hasSettings = !!process.env.JOYSTICK_SETTINGS;
592
+ // const settings = hasSettings && JSON.parse(process.env.JOYSTICK_SETTINGS);
593
+ // const databases = settings?.config?.databases || [];
594
+ //
595
+ // if (databases && Array.isArray(databases) && databases.length > 0) {
596
+ // validateDatabasesFromSettings(databases);
597
+ //
598
+ // for (let i = 0; i < databases?.length; i += 1) {
599
+ // const database = databases[i];
600
+ // const hasMultipleOfProvider = (databases?.filter((database) => database?.provider === database?.provider))?.length > 1;
601
+ //
602
+ // // NOTE: Increment each database port using index in the databases array from settings.
603
+ // await startDatabase(database, databasePortStart + i, hasMultipleOfProvider);
604
+ // }
605
+ //
606
+ // return Promise.resolve();
607
+ // }
608
+ //
609
+ // return Promise.resolve();
610
+ // } catch (exception) {
611
+ // console.warn(exception);
612
+ // }
613
+ //};
614
+ //
615
+ //const loadSettings = () => {
616
+ // const environment = process.env.NODE_ENV;
617
+ // const settingsFilePath = `${process.cwd()}/settings.${environment}.json`;
618
+ // const hasSettingsFile = fs.existsSync(settingsFilePath);
619
+ //
620
+ // if (!hasSettingsFile) {
621
+ // CLILog(
622
+ // `A settings file could not be found for this environment (${environment}). Create a settings.${environment}.json file at the root of your project and restart Joystick.`,
623
+ // {
624
+ // level: "danger",
625
+ // docs: "https://github.com/cheatcode/joystick#settings",
626
+ // }
627
+ // );
628
+ // process.exit(0);
629
+ // }
630
+ //
631
+ // const rawSettingsFile = fs.readFileSync(settingsFilePath, "utf-8");
632
+ // const isValidJSON = isValidJSONString(rawSettingsFile);
633
+ //
634
+ // if (!isValidJSON) {
635
+ // CLILog(
636
+ // `Failed to parse settings file. Double-check the syntax in your settings.${environment}.json file at the root of your project and restart Joystick.`,
637
+ // {
638
+ // level: "danger",
639
+ // docs: "https://github.com/cheatcode/joystick#settings",
640
+ // tools: [{ title: "JSON Linter", url: "https://jsonlint.com/" }],
641
+ // }
642
+ // );
643
+ //
644
+ // process.exit(0);
645
+ // }
646
+ //
647
+ // const settingsFile = isValidJSON ? rawSettingsFile : "{}";
648
+ //
649
+ // // NOTE: Child process will inherit this env var from this parent process.
650
+ // process.env.JOYSTICK_SETTINGS = settingsFile;
651
+ //
652
+ // return JSON.parse(settingsFile);
653
+ //};
654
+ //
655
+ //const checkIfJoystickProject = () => {
656
+ // return fs.existsSync(`${process.cwd()}/.joystick`);
657
+ //};
658
+
3
659
  export default async (args = {}, options = {}) => {
4
660
  await dev({
5
661
  environment: args?.environment || 'development',
@@ -91,6 +91,72 @@ const handleSignalEvents = (processIds = [], nodeMajorVersion = 0, __dirname = '
91
91
  }
92
92
  };
93
93
 
94
+ const handleHMRProcessMessages = (options = {}) => {
95
+ try {
96
+ process.hmrProcess.on("message", (message) => {
97
+ const processMessages = [
98
+ "SERVER_CLOSED",
99
+ "HAS_HMR_CONNECTIONS",
100
+ "HAS_NO_HMR_CONNECTIONS",
101
+ "HMR_UPDATE_COMPLETED",
102
+ ];
103
+
104
+ if (!processMessages.includes(message?.type)) {
105
+ process.loader.stable(message);
106
+ }
107
+
108
+ if (message?.type === "HAS_HMR_CONNECTIONS") {
109
+ process.hmrProcess.hasConnections = true;
110
+ }
111
+
112
+ if (message?.type === "HAS_NO_HMR_CONNECTIONS") {
113
+ process.hmrProcess.hasConnections = false;
114
+ }
115
+
116
+ if (message?.type === "HMR_UPDATE_COMPLETED") {
117
+ // NOTE: Do a setTimeout to ensure that server is still available while the HMR update completes.
118
+ // Necessary because some updates are instance, but others might mount a UI that needs a server
119
+ // available (e.g., runs API requests).
120
+ setTimeout(() => {
121
+ handleRestartApplicationProcess({
122
+ ...options,
123
+ sessionsBeforeHMRUpdate: message?.sessions,
124
+ });
125
+ }, 500);
126
+ }
127
+ });
128
+ } catch (exception) {
129
+ throw new Error(`[dev.handleHMRProcessMessages] ${exception.message}`);
130
+ }
131
+ };
132
+
133
+ const handleHMRProcessSTDIO = () => {
134
+ try {
135
+ if (process.hmrProcess) {
136
+ process.hmrProcess.on("error", (error) => {
137
+ CLILog(error.toString(), {
138
+ level: "danger",
139
+ docs: "https://github.com/cheatcode/joystick",
140
+ });
141
+ });
142
+
143
+ process.hmrProcess.stdout.on("data", (data) => {
144
+ console.log(data.toString());
145
+ });
146
+
147
+ process.hmrProcess.stderr.on("data", (data) => {
148
+ process.loader.stop();
149
+ CLILog(data.toString(), {
150
+ level: "danger",
151
+ docs: "https://github.com/cheatcode/joystick",
152
+ });
153
+ });
154
+ }
155
+ } catch (exception) {
156
+ throw new Error(`[dev.handleHMRProcessSTDIO] ${exception.message}`);
157
+ }
158
+ };
159
+
94
160
  const handleServerProcessMessages = () => {
95
161
  try {
96
162
  process.serverProcess.on("message", (message) => {
@@ -138,7 +204,7 @@ const handleServerProcessSTDIO = () => {
138
204
  }
139
205
  };
140
206
 
141
- const handleAddOrChangeFile = async (context = {}, path = '') => {
207
+ const handleAddOrChangeFile = async (context = {}, path = '', options = {}) => {
142
208
  try {
143
209
  if (context.isAddingOrChangingFile) {
144
210
  const codependencies = await getCodependenciesForFile(path);
@@ -179,10 +245,8 @@ const handleAddOrChangeFile = async (context = {}, path = '') => {
179
245
  if (context.isUIUpdate) {
180
246
  handleNotifyHMRClients(context.isHTMLUpdate);
181
247
  } else {
182
- handleRestartApplicationProcess();
248
+ handleRestartApplicationProcess(options);
183
249
  }
184
-
185
- return;
186
250
  }
187
251
  }
188
252
  } catch (exception) {
@@ -190,7 +254,7 @@ const handleAddOrChangeFile = async (context = {}, path = '') => {
190
254
  }
191
255
  };
192
256
 
193
- const handleAddDirectory = (context = {}, path = '') => {
257
+ const handleAddDirectory = (context = {}, path = '', options = {}) => {
194
258
  try {
195
259
  if (context.isAddDirectory) {
196
260
  fs.mkdirSync(`./.joystick/build/${path}`);
@@ -198,7 +262,7 @@ const handleAddDirectory = (context = {}, path = '') => {
198
262
  if (context.isUIUpdate) {
199
263
  handleNotifyHMRClients(context.isHTMLUpdate);
200
264
  } else {
201
- handleRestartApplicationProcess();
265
+ handleRestartApplicationProcess(options);
202
266
  }
203
267
  }
204
268
  } catch (exception) {
@@ -206,7 +270,7 @@ const handleAddDirectory = (context = {}, path = '') => {
206
270
  }
207
271
  };
208
272
 
209
- const handleCopyFile = (context = {}, path = '') => {
273
+ const handleCopyFile = (context = {}, path = '', options = {}) => {
210
274
  try {
211
275
  if (context.isFileToCopy && !context.isDirectory) {
212
276
  fs.writeFileSync(`./.joystick/build/${path}`, fs.readFileSync(path));
@@ -214,7 +278,7 @@ const handleCopyFile = (context = {}, path = '') => {
214
278
  if (context.isUIUpdate) {
215
279
  handleNotifyHMRClients(context.isHTMLUpdate);
216
280
  } else {
217
- handleRestartApplicationProcess();
281
+ handleRestartApplicationProcess(options);
218
282
  }
219
283
  }
220
284
  } catch (exception) {
@@ -222,7 +286,7 @@ const handleCopyFile = (context = {}, path = '') => {
222
286
  }
223
287
  };
224
288
 
225
- const handleCopyDirectory = (context = {}, path = '') => {
289
+ const handleCopyDirectory = (context = {}, path = '', options = {}) => {
226
290
  try {
227
291
  if (context.isFileToCopy && context.isDirectory && !context.isExistingDirectoryInBuild) {
228
292
  fs.mkdirSync(`./.joystick/build/${path}`);
@@ -230,7 +294,7 @@ const handleCopyDirectory = (context = {}, path = '') => {
230
294
  if (context.isUIUpdate) {
231
295
  handleNotifyHMRClients(context.isHTMLUpdate);
232
296
  } else {
233
- handleRestartApplicationProcess();
297
+ handleRestartApplicationProcess(options);
234
298
  }
235
299
  }
236
300
  } catch (exception) {
@@ -238,9 +302,28 @@ const handleCopyDirectory = (context = {}, path = '') => {
238
302
  }
239
303
  };
240
304
 
241
- const handleRestartApplicationProcess = () => {
305
+ const handleRestartApplicationProcess = async (options = {}) => {
242
306
  try {
243
- // Perform a single step in your action here.
307
+ if (process.serverProcess && process.serverProcess.pid) {
308
+ process.loader.text("Restarting app...");
309
+ process.serverProcess.kill();
310
+
311
+ await startApp({
312
+ nodeMajorVersion: options?.nodeMajorVersion,
313
+ port: options?.port,
314
+ sessionsBeforeHMRUpdate: options?.sessionsBeforeHMRUpdate,
315
+ });
316
+
317
+ return Promise.resolve();
318
+ }
319
+
320
+ // NOTE: Original process was never initialized due to an error.
321
+ process.loader.text("Starting app...");
322
+ startApplicationProcess();
323
+
324
+ if (!process.hmrProcess) {
325
+ startHMR();
326
+ }
244
327
  } catch (exception) {
245
328
  throw new Error(`[dev.handleRestartApplicationProcess] ${exception.message}`);
246
329
  }
@@ -312,10 +395,10 @@ const startFileWatcher = (options = {}) => {
312
395
 
313
396
  const watchChangeContext = getWatchChangeContext(event, path);
314
397
 
315
- handleCopyDirectory(watchChangeContext, path);
316
- handleCopyFile(watchChangeContext, path);
317
- handleAddDirectory(watchChangeContext, path);
318
- await handleAddOrChangeFile(watchChangeContext, path);
398
+ handleCopyDirectory(watchChangeContext, path, options);
399
+ handleCopyFile(watchChangeContext, path, options);
400
+ handleAddDirectory(watchChangeContext, path, options);
401
+ await handleAddOrChangeFile(watchChangeContext, path, options);
319
402
 
320
403
  if (watchChangeContext?.isSettingsUpdate) {
321
404
  await loadSettings({
@@ -518,6 +601,10 @@ const dev = async (options, { resolve, reject }) => {
518
601
  });
519
602
 
520
603
  processIds.push(hmrProcess.pid);
604
+ process.hmrProcess = hmrProcess;
605
+
606
+ handleHMRProcessSTDIO();
607
+ handleHMRProcessMessages(options);
521
608
  }
522
609
 
523
610
 
@@ -65,7 +65,7 @@ const startApp = (options, { resolve, reject }) => {
65
65
  validateOptions(options);
66
66
 
67
67
  const execArgv = getExecArgs(options?.nodeMajorVersion);
68
- const serverProcess = handleStartServerProcess(execArgv);
68
+ const serverProcess = handleStartServerProcess(execArgv, options?.sessionsBeforeHMRUpdate);
69
69
 
70
70
  return resolve(serverProcess);
71
71
  } catch (exception) {