@joystick.js/cli-canary 0.0.0-canary.9 → 0.0.0-canary.90

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 (45) hide show
  1. package/dist/cli.js +7 -0
  2. package/dist/functions/index.js +21 -0
  3. package/dist/functions/start/index.js +3 -489
  4. package/dist/functions/start/onWarn.js +1 -1
  5. package/dist/functions/start/setComponentId.js +1 -1
  6. package/dist/functions/test/index.js +7 -26
  7. package/dist/lib/build/buildFiles.js +63 -7
  8. package/dist/lib/build/buildPlugins.js +18 -15
  9. package/dist/lib/build/getCodeFrame.js +3 -4
  10. package/dist/lib/build/onWarn.js +1 -1
  11. package/dist/lib/dev/cleanup.js +15 -27
  12. package/dist/lib/dev/databases/mongodb/index.js +1 -1
  13. package/dist/lib/dev/databases/postgresql/index.js +2 -2
  14. package/dist/lib/dev/databases/providerMap.js +1 -1
  15. package/dist/lib/dev/index.js +104 -28
  16. package/dist/lib/dev/isWindows.js +5 -0
  17. package/dist/lib/dev/loadSettings.js +1 -3
  18. package/dist/lib/dev/startApp.js +43 -3
  19. package/dist/lib/dev/startDatabases.js +12 -5
  20. package/dist/lib/dev/startHMR.js +30 -5
  21. package/dist/lib/getProcessIdFromPort.js +60 -0
  22. package/dist/lib/types.js +6 -0
  23. package/package.json +1 -1
  24. package/src/cli.js +9 -0
  25. package/src/functions/index.js +21 -0
  26. package/src/functions/start/index.js +2 -691
  27. package/src/functions/start/onWarn.js +1 -1
  28. package/src/functions/start/setComponentId.js +1 -1
  29. package/src/functions/test/index.js +7 -32
  30. package/src/lib/build/buildFiles.js +74 -8
  31. package/src/lib/build/buildPlugins.js +29 -22
  32. package/src/lib/build/getCodeFrame.js +3 -4
  33. package/src/lib/build/onWarn.js +1 -1
  34. package/src/lib/dev/cleanup.js +20 -28
  35. package/src/lib/dev/databases/mongodb/index.js +1 -1
  36. package/src/lib/dev/databases/postgresql/index.js +2 -2
  37. package/src/lib/dev/databases/providerMap.js +1 -1
  38. package/src/lib/dev/index.js +124 -41
  39. package/src/lib/dev/isWindows.js +3 -0
  40. package/src/lib/dev/loadSettings.js +1 -3
  41. package/src/lib/dev/startApp.js +52 -6
  42. package/src/lib/dev/startDatabases.js +12 -6
  43. package/src/lib/dev/startHMR.js +36 -7
  44. package/src/lib/getProcessIdFromPort.js +92 -0
  45. package/src/lib/types.js +3 -0
@@ -3,7 +3,7 @@ import generateId from "./generateId.js";
3
3
 
4
4
  export default (file = "") => {
5
5
  const componentMapPath =
6
- process.env.NODE_ENV === "development"
6
+ ['development', 'test'].includes(process.env.NODE_ENV)
7
7
  ? `./.joystick/build/componentMap.json`
8
8
  : `./.build/componentMap.json`;
9
9
  const componentMapExists = fs.existsSync(componentMapPath);
@@ -1,34 +1,9 @@
1
- /* eslint-disable consistent-return */
1
+ import dev from "../../lib/dev/index.js";
2
2
 
3
- const actionMethod = () => {
4
- try {
5
- // Perform a single step in your action here.
6
- } catch (exception) {
7
- throw new Error(`[test.actionMethod] ${exception.message}`);
8
- }
9
- };
10
-
11
- const validateOptions = (options) => {
12
- try {
13
- if (!options) throw new Error('options object is required.');
14
- if (!options.someOption) throw new Error('options.someOption is required.');
15
- } catch (exception) {
16
- throw new Error(`[test.validateOptions] ${exception.message}`);
17
- }
18
- };
19
-
20
- const test = (options, { resolve, reject }) => {
21
- try {
22
- validateOptions(options);
23
- // Call action methods in sequence here.
24
- resolve();
25
- } catch (exception) {
26
- reject(`[test] ${exception.message}`);
27
- }
28
- };
29
-
30
- export default (options) =>
31
- new Promise((resolve, reject) => {
32
- test(options, { resolve, reject });
3
+ export default async (args = {}, options = {}) => {
4
+ await dev({
5
+ environment: 'test',
6
+ process,
7
+ port: 1977,
33
8
  });
34
-
9
+ };
@@ -8,6 +8,43 @@ import browserPathExclusions from "./browserPathExclusions.js";
8
8
  import nodePaths from "./nodePaths.js";
9
9
  import nodePathExclusions from "./nodePathExclusions.js";
10
10
  import buildPlugins from "./buildPlugins.js";
11
+ import getCodeFrame from "./getCodeFrame.js";
12
+ import onWarn from "./onWarn.js";
13
+
14
+ const handleBuildException = (exception = {}, file = '') => {
15
+ try {
16
+ const error = exception?.errors && exception?.errors[0];
17
+ const snippet = fs.existsSync(file)
18
+ ? getCodeFrame(file, {
19
+ line: error?.location?.line,
20
+ column: error?.location?.column,
21
+ })
22
+ : null;
23
+
24
+ onWarn({
25
+ file,
26
+ stack: exception?.stack,
27
+ line: error?.location?.line,
28
+ column: error?.location?.column,
29
+ snippet,
30
+ lineWithError: error?.location?.lineText?.trim(),
31
+ message: error?.text,
32
+ });
33
+
34
+ return snippet;
35
+ } catch (exception) {
36
+ throw new Error(`[actionName.handleBuildException] ${exception.message}`);
37
+ }
38
+ };
39
+
40
+ const handleParseFilePathFromException = (exception = {}) => {
41
+ try {
42
+ const rawErrorMessage = exception?.message?.split(':');
43
+ return rawErrorMessage[1] && rawErrorMessage[1]?.replace('\n', '') || '';
44
+ } catch (exception) {
45
+ throw new Error(`[actionName.handleParseFilePathFromException] ${exception.message}`);
46
+ }
47
+ };
11
48
 
12
49
  const handleBuildForNode = (nodePaths = [], options = {}) => {
13
50
  try {
@@ -63,8 +100,10 @@ const handleCopyFiles = (files = [], outputPath = '') => {
63
100
  for (let i = 0; i < files?.length; i += 1) {
64
101
  const file = files[i];
65
102
  const fileContents = fs.readFileSync(file.path);
66
- // TODO: I was using fs-extra here but I don't think I need it? Was using fs.outputFileSync()...
67
- fs.writeFileSync(
103
+
104
+ // NOTE: Using fs.outputFileSync() from fs-extra to ensure parent path is created
105
+ // if it doesn't exist (avoids need for separate fs.mkdirSync()).
106
+ fs.outputFileSync(
68
107
  `${outputPath || "./.joystick/build"}/${file.path}`,
69
108
  fileContents
70
109
  );
@@ -76,7 +115,7 @@ const handleCopyFiles = (files = [], outputPath = '') => {
76
115
 
77
116
  const isNodePath = (path = '') => {
78
117
  try {
79
- return nodePaths.some((nodePath) => {
118
+ return !isNotJavaScript(path) && nodePaths.some((nodePath) => {
80
119
  return path.includes(nodePath);
81
120
  }) &&
82
121
  !nodePathExclusions.some((nodeExclusionPath) => {
@@ -89,7 +128,7 @@ const isNodePath = (path = '') => {
89
128
 
90
129
  const isBrowserPath = (path = '') => {
91
130
  try {
92
- return browserPaths.some((browserPath) => {
131
+ return !isNotJavaScript(path) && browserPaths.some((browserPath) => {
93
132
  return path.includes(browserPath);
94
133
  }) &&
95
134
  !browserPathExclusions.some((browserExclusionPath) => {
@@ -181,9 +220,36 @@ const buildFiles = async (options, { resolve, reject }) => {
181
220
 
182
221
  handleCopyFiles(copyFiles, options?.outputPath);
183
222
 
184
- await Promise.all([
185
- handleBuildForBrowser(browserFiles, options),
186
- handleBuildForNode(nodeFiles, options),
223
+ const fileResults = await Promise.all([
224
+ handleBuildForBrowser(browserFiles, options).then(() => {
225
+ return { success: true };
226
+ }).catch((exception) => {
227
+ const file = handleParseFilePathFromException(exception);
228
+ console.log(file, exception);
229
+ const snippet = handleBuildException(exception, file);
230
+
231
+ return {
232
+ success: false,
233
+ path: file,
234
+ error: {
235
+ stack: exception?.stack,
236
+ snippet,
237
+ },
238
+ };
239
+ }),
240
+ handleBuildForNode(nodeFiles, options).catch((exception) => {
241
+ const file = handleParseFilePathFromException(exception);
242
+ const snippet = handleBuildException(exception, file);
243
+
244
+ return {
245
+ success: false,
246
+ path: file,
247
+ error: {
248
+ stack: exception?.stack,
249
+ snippet,
250
+ },
251
+ };
252
+ }),
187
253
  ]);
188
254
 
189
255
  if (options?.environment !== 'development') {
@@ -192,7 +258,7 @@ const buildFiles = async (options, { resolve, reject }) => {
192
258
  }));
193
259
  }
194
260
 
195
- resolve();
261
+ resolve(fileResults);
196
262
  } catch (exception) {
197
263
  reject(`[buildFiles] ${exception.message}`);
198
264
  }
@@ -25,22 +25,25 @@ export default {
25
25
  (bootstrapTarget) => {
26
26
  return args.path.includes(bootstrapTarget);
27
27
  }
28
- );
28
+ );
29
+
29
30
  const isLayoutComponent = [getPlatformSafePath("ui/layouts")].some(
30
31
  (bootstrapTarget) => {
31
32
  return args.path.includes(bootstrapTarget);
32
33
  }
33
- );
34
+ );
35
+
34
36
  const isPageComponent = [getPlatformSafePath("ui/pages")].some(
35
37
  (bootstrapTarget) => {
36
38
  return args.path.includes(bootstrapTarget);
37
39
  }
38
- );
40
+ );
41
+
39
42
  const isEmailComponent = [getPlatformSafePath("email/")].some(
40
43
  (bootstrapTarget) => {
41
44
  return args.path.includes(bootstrapTarget);
42
45
  }
43
- );
46
+ );
44
47
 
45
48
  if (
46
49
  shouldSetSSRId ||
@@ -51,7 +54,7 @@ export default {
51
54
  const code = fs.readFileSync(
52
55
  getPlatformSafePath(args.path),
53
56
  "utf-8"
54
- );
57
+ );
55
58
 
56
59
  // NOTE: Check to see if we have a valid component file.
57
60
  const joystickUIMatches = code.match(JOYSTICK_UI_REGEX) || [];
@@ -65,8 +68,8 @@ export default {
65
68
  console.warn(
66
69
  chalk.yellowBright(
67
70
  `All Joystick components in the ui directory must have an export default statement (e.g., export default MyComponent, export default MyLayout, or export default MyPage). Please check the file at ${args.path}.`
68
- )
69
- );
71
+ )
72
+ );
70
73
  console.log(" ");
71
74
  return;
72
75
  }
@@ -110,7 +113,7 @@ export default {
110
113
 
111
114
  export default ${componentName};
112
115
  `
113
- );
116
+ );
114
117
  }
115
118
 
116
119
  if (componentName && isPageComponent) {
@@ -128,7 +131,7 @@ export default {
128
131
 
129
132
  export default ${componentName};
130
133
  `
131
- );
134
+ );
132
135
  }
133
136
 
134
137
  return {
@@ -143,25 +146,29 @@ export default {
143
146
 
144
147
  build.onEnd(() => {
145
148
  return new Promise((resolve) => {
146
- const shouldSetComponentId = [
147
- getPlatformSafePath("ui/"),
148
- getPlatformSafePath("email/"),
149
+ for (let i = 0; i < build?.initialOptions?.entryPoints?.length; i += 1) {
150
+ const entryPoint = build?.initialOptions?.entryPoints[i];
151
+ const shouldSetComponentId = [
152
+ getPlatformSafePath("ui/"),
153
+ getPlatformSafePath("email/"),
149
154
  ].some((bootstrapTarget) => {
150
- return build.initialOptions.outfile.includes(bootstrapTarget);
155
+ return entryPoint.includes(bootstrapTarget);
151
156
  });
152
157
 
153
- if (shouldSetComponentId) {
154
- const file = fs.readFileSync(build.initialOptions.outfile, "utf-8");
155
- const joystickUIMatches =
158
+ if (shouldSetComponentId) {
159
+ const file = fs.readFileSync(`${build?.initialOptions?.outdir}/${entryPoint}`, "utf-8");
160
+ const joystickUIMatches =
156
161
  file?.match(JOYSTICK_COMPONENT_REGEX) || [];
157
162
 
158
- if (joystickUIMatches?.length > 0) {
159
- let contents = setComponentId(file)?.replace(
160
- /\.component\(\/\*\*\//g,
161
- ".component("
163
+ if (joystickUIMatches?.length > 0) {
164
+ let contents = setComponentId(file)?.replace(
165
+ /\.component\(\/\*\*\//g,
166
+ ".component("
162
167
  );
163
- fs.writeFileSync(build.initialOptions.outfile, contents);
164
- }
168
+
169
+ fs.writeFileSync(`${build?.initialOptions?.outdir}/${entryPoint}`, contents);
170
+ }
171
+ }
165
172
  }
166
173
 
167
174
  resolve();
@@ -1,8 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import { codeFrameColumns } from "@babel/code-frame";
3
3
 
4
- export default (id = "", location = {}) => {
5
- const file = fs.readFileSync(id, "utf-8");
6
- const frame = codeFrameColumns(file, { start: location });
7
- return frame;
4
+ export default (path = "", location = {}) => {
5
+ const file = fs.readFileSync(path, "utf-8");
6
+ return codeFrameColumns(file, { start: location });
8
7
  };
@@ -1,7 +1,7 @@
1
1
  import chalk from "chalk";
2
2
  import { OBJECT_REGEX } from "../regexes.js";
3
3
  import rainbowRoad from '../rainbowRoad.js';
4
- import getCodeFrame from "../getCodeFrame.js";
4
+ import getCodeFrame from "./getCodeFrame.js";
5
5
 
6
6
  const removeLocationDataFromStackTrace = (stackTrace = "") => {
7
7
  return stackTrace.replace(OBJECT_REGEX, "");
@@ -1,34 +1,26 @@
1
- /* eslint-disable consistent-return */
1
+ // NOTE: This module is intended to be run as a child_process as part of
2
+ // a SIGINT or SIGTERM event.
2
3
 
3
- const actionMethod = () => {
4
- try {
5
- // Perform a single step in your action here.
6
- } catch (exception) {
7
- throw new Error(`[cleanup.actionMethod] ${exception.message}`);
8
- }
9
- };
4
+ import ps from "ps-node";
10
5
 
11
- const validateOptions = (options) => {
12
- try {
13
- if (!options) throw new Error('options object is required.');
14
- if (!options.someOption) throw new Error('options.someOption is required.');
15
- } catch (exception) {
16
- throw new Error(`[cleanup.validateOptions] ${exception.message}`);
17
- }
18
- };
6
+ process.title = "joystick_cleanup";
19
7
 
20
- const cleanup = (options, { resolve, reject }) => {
21
- try {
22
- validateOptions(options);
23
- // Call action methods in sequence here.
24
- resolve();
25
- } catch (exception) {
26
- reject(`[cleanup] ${exception.message}`);
27
- }
8
+ const killProcess = (pid = 0) => {
9
+ return new Promise((resolve) => {
10
+ ps.kill(pid, () => {
11
+ resolve();
12
+ });
13
+ });
28
14
  };
29
15
 
30
- export default (options) =>
31
- new Promise((resolve, reject) => {
32
- cleanup(options, { resolve, reject });
33
- });
16
+ process.on('message', async (message) => {
17
+ const parsedMessage = JSON.parse(message);
18
+ const processIds = parsedMessage?.processIds;
19
+
20
+ for (let i = 0; i < processIds?.length; i += 1) {
21
+ const processId = processIds[i];
22
+ await killProcess(processId);
23
+ }
34
24
 
25
+ process.exit();
26
+ });
@@ -4,7 +4,7 @@ import child_process from "child_process";
4
4
  import { kill as killPortProcess } from 'cross-port-killer';
5
5
  import isWindows from "../../isWindows.js";
6
6
  import CLILog from "../../../../lib/CLILog.js";
7
- import getProcessIdFromPort from "../../getProcessIdFromPort.js";
7
+ import getProcessIdFromPort from "../../../getProcessIdFromPort.js";
8
8
 
9
9
  const getMongoProcessId = async (port = 2601) => {
10
10
  const pids = await getProcessIdFromPort(port);
@@ -4,8 +4,8 @@ import util from "util";
4
4
  import commandExists from "command-exists";
5
5
  import child_process from "child_process";
6
6
  import { kill as killPortProcess } from 'cross-port-killer';
7
- import getProcessIdFromPort from "../../getProcessIdFromPort.js";
8
- import CLILog from "../../../../lib/CLILog.js";
7
+ import getProcessIdFromPort from "../../../getProcessIdFromPort.js";
8
+ import CLILog from "../../../CLILog.js";
9
9
 
10
10
  const exec = util.promisify(child_process.exec);
11
11
 
@@ -1,4 +1,4 @@
1
- import connectMongoDB from './mongodb/connect.js.js';
1
+ import connectMongoDB from './mongodb/connect.js';
2
2
  import connectPostgreSQL from './postgresql/connect.js';
3
3
 
4
4
  export default {
@@ -10,7 +10,6 @@ import loadSettings from "./loadSettings.js";
10
10
  import Loader from "../loader.js";
11
11
  import startDatabases from "./startDatabases.js";
12
12
  import runTests from "./runTests.js";
13
- import cleanup from "./cleanup.js";
14
13
  import startHMR from "./startHMR.js";
15
14
  import startApp from "./startApp.js";
16
15
  import requiredFiles from "./requiredFiles.js";
@@ -21,14 +20,7 @@ import filesToCopy from "../filesToCopy.js";
21
20
  import { SETTINGS_FILE_NAME_REGEX } from "../regexes.js";
22
21
  import getCodependenciesForFile from "./getCodependenciesForFile.js";
23
22
  import removeDeletedDependenciesFromMap from "../build/removeDeletedDependenciesFromMap.js";
24
-
25
- const nodeMajorVersion = parseInt(
26
- process?.version?.split(".")[0]?.replace("v", ""),
27
- 10
28
- );
29
-
30
- const __filename = fileURLToPath(import.meta.url);
31
- const __dirname = dirname(__filename);
23
+ import chalk from "chalk";
32
24
 
33
25
  const getDatabaseProcessIds = () => {
34
26
  try {
@@ -61,7 +53,7 @@ const getDatabaseProcessIds = () => {
61
53
  }
62
54
  };
63
55
 
64
- const handleSignalEvents = (processIds = []) => {
56
+ const handleSignalEvents = (processIds = [], nodeMajorVersion = 0, __dirname = '') => {
65
57
  try {
66
58
  const execArgv = ["--no-warnings"];
67
59
 
@@ -70,7 +62,7 @@ const handleSignalEvents = (processIds = []) => {
70
62
  }
71
63
 
72
64
  const cleanupProcess = child_process.fork(
73
- path.resolve(`${__dirname}/cleanup/index.js`),
65
+ path.resolve(`${__dirname}/cleanup.js`),
74
66
  [],
75
67
  {
76
68
  // NOTE: Run in detached mode so when parent process dies, the child still runs
@@ -99,6 +91,53 @@ const handleSignalEvents = (processIds = []) => {
99
91
  }
100
92
  };
101
93
 
94
+ const handleServerProcessMessages = () => {
95
+ try {
96
+ process.serverProcess.on("message", (message) => {
97
+ const processMessages = ["SERVER_CLOSED"];
98
+
99
+ if (!processMessages.includes(message)) {
100
+ process.loader.stable(message);
101
+ }
102
+ });
103
+ } catch (exception) {
104
+ throw new Error(`[dev.handleServerProcessMessages] ${exception.message}`);
105
+ }
106
+ };
107
+
108
+ const handleServerProcessSTDIO = () => {
109
+ try {
110
+ if (process.serverProcess) {
111
+ process.serverProcess.on("error", (error) => {
112
+ console.log(error);
113
+ });
114
+
115
+ process.serverProcess.stdout.on("data", (data) => {
116
+ const message = data.toString();
117
+
118
+ if (message && message.includes("App running at:")) {
119
+ process.loader.stable(message);
120
+ } else {
121
+ if (message && !message.includes("BUILD_ERROR")) {
122
+ console.log(message);
123
+ }
124
+ }
125
+ });
126
+
127
+ process.serverProcess.stderr.on("data", (data) => {
128
+ process.loader.stop();
129
+
130
+ CLILog(data.toString(), {
131
+ level: "danger",
132
+ docs: "https://cheatcode.co/docs/joystick",
133
+ });
134
+ });
135
+ }
136
+ } catch (exception) {
137
+ throw new Error(`[dev.handleServerProcessSTDIO] ${exception.message}`);
138
+ }
139
+ };
140
+
102
141
  const handleAddOrChangeFile = async (context = {}, path = '') => {
103
142
  try {
104
143
  if (context.isAddingOrChangingFile) {
@@ -228,7 +267,7 @@ const getWatchChangeContext = (event = '', path = '') => {
228
267
  try {
229
268
  const isHTMLUpdate = path === "index.html";
230
269
  const isUIPath = path?.includes("ui/") || path === 'index.css' || isHTMLUpdate;
231
- const isUIUpdate = (process.hmrProcess.hasConnections && isUIPath) || false;
270
+ const isUIUpdate = (process.hmrProcess && process.hmrProcess.hasConnections && isUIPath) || false;
232
271
  const isSettingsUpdate = path?.match(SETTINGS_FILE_NAME_REGEX)?.length > 0;
233
272
  const isDirectory = fs.statSync(path).isDirectory();
234
273
  const isFile = fs.statSync(path).isFile();
@@ -292,21 +331,26 @@ const startFileWatcher = (options = {}) => {
292
331
 
293
332
  const runInitialBuild = async (buildSettings = {}) => {
294
333
  try {
334
+ process.loader.text('Building app...');
335
+
295
336
  const filesToBuild = getFilesToBuild(buildSettings?.excludedPaths, "start");
296
- const fileResults = await buildFiles(
297
- filesToBuild,
298
- null,
299
- process.env.NODE_ENV
300
- );
337
+ const fileResults = await buildFiles({
338
+ files: filesToBuild,
339
+ environment: process.env.NODE_ENV
340
+ });
301
341
 
302
342
  const hasErrors = [...fileResults]
303
343
  .filter((result) => !!result)
304
344
  .map(({ success }) => success)
305
345
  .includes(false);
306
346
 
307
- // TODO: Prevent startup if this build fails (hasError > 0)
308
-
347
+ // NOTE: If the initial build fails, exit the startup process.
348
+ if (hasErrors) {
349
+ console.log(chalk.redBright('Failed to start app. Correct the errors above and run joystick start again.\n'));
350
+ process.exit(1);
351
+ }
309
352
  } catch (exception) {
353
+ console.warn(exception);
310
354
  throw new Error(`[dev.runInitialBuild] ${exception.message}`);
311
355
  }
312
356
  };
@@ -352,18 +396,22 @@ const checkForRequiredFiles = () => {
352
396
 
353
397
  let error = `The following paths are missing and required in a Joystick project:\n\n`;
354
398
 
355
- error += `Files:\n\n`;
399
+ if (files?.length > 0) {
400
+ error += ` > Required Files:\n\n`;
356
401
 
357
- for (let i = 0; i < files?.length; i += 1) {
358
- const file = files[i];
359
- error += `${file.path}\n\n`;
402
+ for (let i = 0; i < files?.length; i += 1) {
403
+ const file = files[i];
404
+ error += ` /${file.path}\n`;
405
+ }
360
406
  }
361
407
 
362
- error += `Directories:\n\n`;
408
+ if (directories?.length > 0) {
409
+ error += ` > Required Directories:\n\n`;
363
410
 
364
- for (let i = 0; i < directories?.length; i += 1) {
365
- const file = directories[i];
366
- error += `${file.path}`;
411
+ for (let i = 0; i < directories?.length; i += 1) {
412
+ const file = directories[i];
413
+ error += ` /${file.path}\n`;
414
+ }
367
415
  }
368
416
 
369
417
  CLILog(error, {
@@ -395,7 +443,7 @@ const warnInvalidJoystickEnvironment = () => {
395
443
  process.exit(0);
396
444
  }
397
445
 
398
- if (process.env.NODE_ENV !== 'test' && (!hasJoystickFolder || !hasTestsFolder)) {
446
+ if (process.env.NODE_ENV !== 'test' && !hasJoystickFolder) {
399
447
  CLILog(
400
448
  "joystick start must be run in a directory with a .joystick folder.",
401
449
  {
@@ -425,29 +473,61 @@ const dev = async (options, { resolve, reject }) => {
425
473
  validateOptions(options);
426
474
  initProcess(options);
427
475
 
476
+ const nodeMajorVersion = parseInt(
477
+ process?.version?.split(".")[0]?.replace("v", ""),
478
+ 10
479
+ );
480
+
481
+ const __filename = fileURLToPath(import.meta.url);
482
+ const __dirname = dirname(__filename);
483
+
428
484
  warnInvalidJoystickEnvironment();
429
485
  checkForRequiredFiles();
430
486
 
431
487
  const settings = await loadSettings({
432
488
  environment: options.environment,
433
- process: options.process,
434
489
  });
435
490
 
436
- await startDatabases(settings.parsed);
491
+ await startDatabases({
492
+ environment: options.environment,
493
+ port: options.port,
494
+ settings: settings.parsed
495
+ });
437
496
 
438
497
  await runInitialBuild(settings?.parsed?.config?.build);
439
498
  await startFileWatcher(options);
440
499
 
441
- handleSignalEvents();
500
+ const serverProcess = await startApp({
501
+ nodeMajorVersion,
502
+ port: options?.port,
503
+ });
504
+
505
+ if (serverProcess) {
506
+ process.serverProcess = serverProcess;
507
+ handleServerProcessSTDIO();
508
+ handleServerProcessMessages();
509
+ }
510
+
511
+ // NOTE: Scope this out here so we can reference the processId below.
512
+ let hmrProcess;
442
513
 
443
- // if (options?.environment !== 'test') {
444
- // await startHMR();
445
- // }
446
- //
447
- // if (options?.environment === 'test') {
448
- // await runTests(options);
449
- // await cleanup();
450
- // }
514
+ if (options?.environment !== 'test') {
515
+ hmrProcess = await startHMR({
516
+ nodeMajorVersion,
517
+ __dirname,
518
+ });
519
+ }
520
+
521
+ handleSignalEvents(
522
+ [serverProcess.pid, hmrProcess.pid],
523
+ nodeMajorVersion,
524
+ __dirname
525
+ );
526
+
527
+ if (options?.environment === 'test') {
528
+ process.loader.text('Running tests...');
529
+ // await runTests(options);
530
+ }
451
531
 
452
532
  /*
453
533
  TODO:
@@ -456,14 +536,17 @@ const dev = async (options, { resolve, reject }) => {
456
536
  - [x] If environment === 'test', check that both a .joystick fo lder exists AND a /tests folder
457
537
  exists. If no /tests, log out docs on how to scaffold your tests.
458
538
  - [x] Load settings.<environment>.json.
459
- - [ ] Start databases relative to options.environment (development|test) from settings.<environment>.json.
460
- - [ ] Start the app as normal from the build directory.
539
+ - [x] Start databases relative to options.environment (development|test) from settings.<environment>.json.
540
+ - [x] Run the initial build
541
+ - [x] Start the file watcher
542
+ - [x] Start the app as normal from the build directory.
461
543
  - [ ] If environment === 'test', run the tests.
462
544
  - [ ] If environment === 'test', after tests, run process.exit(0).
463
545
  */
464
546
 
465
547
  resolve();
466
548
  } catch (exception) {
549
+ console.warn(exception);
467
550
  reject(`[dev] ${exception.message}`);
468
551
  }
469
552
  };
@@ -0,0 +1,3 @@
1
+ import os from "os";
2
+
3
+ export default os.platform() === "win32";
@@ -73,9 +73,7 @@ const loadSettings = (options, { resolve, reject }) => {
73
73
  const settings = getSettings(settingsPath);
74
74
  warnIfInvalidJSONInSettings(settings);
75
75
 
76
- if (options?.process) {
77
- options.process.env.JOYSTICK_SETTINGS = settings;
78
- }
76
+ process.env.JOYSTICK_SETTINGS = settings;
79
77
 
80
78
  resolve({
81
79
  parsed: JSON.parse(settings),