@adonisjs/assembler 6.1.3-4 → 6.1.3-5

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.
package/src/dev_server.ts CHANGED
@@ -7,17 +7,15 @@
7
7
  * file that was distributed with this source code.
8
8
  */
9
9
 
10
- import getPort from 'get-port'
11
10
  import picomatch from 'picomatch'
12
11
  import type tsStatic from 'typescript'
13
12
  import { type ExecaChildProcess } from 'execa'
14
- import type { Watcher } from '@poppinss/chokidar-ts'
15
13
  import { cliui, type Logger } from '@poppinss/cliui'
16
- import { EnvLoader, EnvParser } from '@adonisjs/env'
14
+ import type { Watcher } from '@poppinss/chokidar-ts'
17
15
 
18
- import { watch } from './watch.js'
19
- import { run, runNode } from './run.js'
20
16
  import type { DevServerOptions } from './types.js'
17
+ import { AssetsDevServer } from './assets_dev_server.js'
18
+ import { getPort, isDotEnvFile, isRcFile, runNode, watch } from './helpers.js'
21
19
 
22
20
  /**
23
21
  * Instance of CLIUI
@@ -41,14 +39,16 @@ export class DevServer {
41
39
  #options: DevServerOptions
42
40
  #isWatching: boolean = false
43
41
  #scriptFile: string = 'bin/server.js'
44
- #httpServerProcess?: ExecaChildProcess<string>
45
42
  #isMetaFileWithReloadsEnabled: picomatch.Matcher
46
43
  #isMetaFileWithReloadsDisabled: picomatch.Matcher
47
- #watcher?: ReturnType<Watcher['watch']>
48
- #assetsServerProcess?: ExecaChildProcess<string>
44
+
49
45
  #onError?: (error: any) => any
50
46
  #onClose?: (exitCode: number) => any
51
47
 
48
+ #httpServer?: ExecaChildProcess<string>
49
+ #watcher?: ReturnType<Watcher['watch']>
50
+ #assetsServer?: AssetsDevServer
51
+
52
52
  /**
53
53
  * Getting reference to colors library from logger
54
54
  */
@@ -73,24 +73,6 @@ export class DevServer {
73
73
  )
74
74
  }
75
75
 
76
- /**
77
- * Check if file is an .env file
78
- */
79
- #isDotEnvFile(filePath: string) {
80
- if (filePath === '.env') {
81
- return true
82
- }
83
-
84
- return filePath.includes('.env.')
85
- }
86
-
87
- /**
88
- * Check if file is .adonisrc.json file
89
- */
90
- #isRcFile(filePath: string) {
91
- return filePath === '.adonisrc.json'
92
- }
93
-
94
76
  /**
95
77
  * Inspect if child process message is from AdonisJS HTTP server
96
78
  */
@@ -115,97 +97,18 @@ export class DevServer {
115
97
  }
116
98
  }
117
99
 
118
- /**
119
- * Logs messages from vite dev server stdout and stderr
120
- */
121
- #logViteDevServerMessage(data: Buffer) {
122
- const dataString = data.toString()
123
- const lines = dataString.split('\n')
124
-
125
- /**
126
- * Logging VITE ready in message with proper
127
- * spaces and newlines
128
- */
129
- if (dataString.includes('ready in')) {
130
- console.log('')
131
- console.log(dataString.trim())
132
- return
133
- }
134
-
135
- /**
136
- * Put a wrapper around vite network address log
137
- */
138
- if (dataString.includes('Local') && dataString.includes('Network')) {
139
- const sticker = ui.sticker().useColors(this.#colors).useRenderer(this.#logger.getRenderer())
140
-
141
- lines.forEach((line: string) => {
142
- if (line.trim()) {
143
- sticker.add(line)
144
- }
145
- })
146
-
147
- sticker.render()
148
- return
149
- }
150
-
151
- /**
152
- * Log rest of the lines
153
- */
154
- lines.forEach((line: string) => {
155
- if (line.trim()) {
156
- console.log(line)
157
- }
158
- })
159
- }
160
-
161
- /**
162
- * Logs messages from assets dev server stdout and stderr
163
- */
164
- #logAssetsDevServerMessage(data: Buffer) {
165
- const dataString = data.toString()
166
- const lines = dataString.split('\n')
167
- lines.forEach((line: string) => {
168
- if (line.trim()) {
169
- console.log(line)
170
- }
171
- })
172
- }
173
-
174
- /**
175
- * Returns PORT for starting the HTTP server with option to use
176
- * a random PORT if main PORT is in use.
177
- */
178
- async #getPort(): Promise<number> {
179
- /**
180
- * Use existing port if exists
181
- */
182
- if (process.env.PORT) {
183
- return getPort({ port: Number(process.env.PORT) })
184
- }
185
-
186
- const files = await new EnvLoader(this.#cwd).load()
187
- for (let file of files) {
188
- const envVariables = new EnvParser(file.contents).parse()
189
- if (envVariables.PORT) {
190
- return getPort({ port: Number(envVariables.PORT) })
191
- }
192
- }
193
-
194
- return getPort({ port: 3333 })
195
- }
196
-
197
100
  /**
198
101
  * Starts the HTTP server
199
102
  */
200
103
  #startHTTPServer(port: string, mode: 'blocking' | 'nonblocking') {
201
- this.#httpServerProcess = runNode(this.#cwd, {
104
+ this.#httpServer = runNode(this.#cwd, {
202
105
  script: this.#scriptFile,
203
106
  env: { PORT: port, ...this.#options.env },
204
107
  nodeArgs: this.#options.nodeArgs,
205
108
  scriptArgs: this.#options.scriptArgs,
206
109
  })
207
110
 
208
- this.#httpServerProcess.on('message', (message) => {
111
+ this.#httpServer.on('message', (message) => {
209
112
  if (this.#isAdonisJSReadyMessage(message)) {
210
113
  ui.sticker()
211
114
  .useColors(this.#colors)
@@ -220,12 +123,13 @@ export class DevServer {
220
123
  }
221
124
  })
222
125
 
223
- this.#httpServerProcess
126
+ this.#httpServer
224
127
  .then((result) => {
225
128
  this.#logger.warning(`underlying HTTP server closed with status code "${result.exitCode}"`)
226
129
  if (mode === 'nonblocking') {
227
130
  this.#onClose?.(result.exitCode)
228
131
  this.#watcher?.close()
132
+ this.#assetsServer?.stop()
229
133
  }
230
134
  })
231
135
  .catch((error) => {
@@ -233,73 +137,61 @@ export class DevServer {
233
137
  this.#logger.fatal(error)
234
138
  this.#onError?.(error)
235
139
  this.#watcher?.close()
140
+ this.#assetsServer?.stop()
236
141
  })
237
142
  }
238
143
 
239
144
  /**
240
- * Starts the assets bundler server. The assets bundler server process is
241
- * considered as the secondary process and therefore we do not perform
242
- * any cleanup if it dies.
145
+ * Starts the assets server
243
146
  */
244
147
  #startAssetsServer() {
245
- const assetsBundler = this.#options.assets
246
- if (!assetsBundler?.serve) {
247
- return
248
- }
148
+ this.#assetsServer = new AssetsDevServer(this.#cwd, this.#options.assets)
149
+ this.#assetsServer.start()
150
+ }
249
151
 
250
- this.#logger.info(`starting "${assetsBundler.driver}" dev server...`)
251
- this.#assetsServerProcess = run(this.#cwd, {
252
- script: assetsBundler.cmd,
152
+ /**
153
+ * Restarts the HTTP server
154
+ */
155
+ #restartHTTPServer(port: string) {
156
+ if (this.#httpServer) {
157
+ this.#httpServer.removeAllListeners()
158
+ this.#httpServer.kill('SIGKILL')
159
+ }
253
160
 
254
- /**
255
- * We do not inherit the stdio for vite and encore, because they then
256
- * own the stdin and interrupts the `Ctrl + C`.
257
- */
258
- stdio: 'pipe',
259
- scriptArgs: this.#options.scriptArgs,
260
- })
161
+ this.#startHTTPServer(port, 'blocking')
162
+ }
261
163
 
262
- /**
263
- * Log child process messages
264
- */
265
- this.#assetsServerProcess.stdout?.on('data', (data) => {
266
- if (assetsBundler.driver === 'vite') {
267
- this.#logViteDevServerMessage(data)
268
- } else {
269
- this.#logAssetsDevServerMessage(data)
270
- }
271
- })
164
+ /**
165
+ * Handles a non TypeScript file change
166
+ */
167
+ #handleFileChange(action: string, port: string, relativePath: string) {
168
+ if (isDotEnvFile(relativePath) || isRcFile(relativePath)) {
169
+ this.#clearScreen()
170
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`)
171
+ this.#restartHTTPServer(port)
172
+ return
173
+ }
272
174
 
273
- this.#assetsServerProcess.stderr?.on('data', (data) => {
274
- if (assetsBundler.driver === 'vite') {
275
- this.#logViteDevServerMessage(data)
276
- } else {
277
- this.#logAssetsDevServerMessage(data)
278
- }
279
- })
175
+ if (this.#isMetaFileWithReloadsEnabled(relativePath)) {
176
+ this.#clearScreen()
177
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`)
178
+ this.#restartHTTPServer(port)
179
+ return
180
+ }
280
181
 
281
- this.#assetsServerProcess
282
- .then((result) => {
283
- this.#logger.warning(
284
- `"${assetsBundler.driver}" dev server closed with status code "${result.exitCode}"`
285
- )
286
- })
287
- .catch((error) => {
288
- this.#logger.warning(`unable to connect to "${assetsBundler.driver}" dev server`)
289
- this.#logger.fatal(error)
290
- })
182
+ if (this.#isMetaFileWithReloadsDisabled(relativePath)) {
183
+ this.#clearScreen()
184
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`)
185
+ }
291
186
  }
292
187
 
293
188
  /**
294
- * Restart the development server
189
+ * Handles TypeScript source file change
295
190
  */
296
- #restart(port: string) {
297
- if (this.#httpServerProcess) {
298
- this.#httpServerProcess.removeAllListeners()
299
- this.#httpServerProcess.kill('SIGKILL')
300
- }
301
-
302
- this.#startHTTPServer(port, 'blocking')
191
+ #handleSourceFileChange(action: string, port: string, relativePath: string) {
192
+ this.#clearScreen()
193
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`)
194
+ this.#restartHTTPServer(port)
303
195
  }
304
196
 
305
197
  /**
@@ -307,6 +199,7 @@ export class DevServer {
307
199
  */
308
200
  setLogger(logger: Logger) {
309
201
  this.#logger = logger
202
+ this.#assetsServer?.setLogger(logger)
310
203
  return this
311
204
  }
312
205
 
@@ -334,8 +227,7 @@ export class DevServer {
334
227
  async start() {
335
228
  this.#clearScreen()
336
229
  this.#logger.info('starting HTTP server...')
337
- this.#startHTTPServer(String(await this.#getPort()), 'nonblocking')
338
-
230
+ this.#startHTTPServer(String(await getPort(this.#cwd)), 'nonblocking')
339
231
  this.#startAssetsServer()
340
232
  }
341
233
 
@@ -343,13 +235,14 @@ export class DevServer {
343
235
  * Start the development server in watch mode
344
236
  */
345
237
  async startAndWatch(ts: typeof tsStatic, options?: { poll: boolean }) {
346
- const port = String(await this.#getPort())
238
+ const port = String(await getPort(this.#cwd))
347
239
  this.#isWatching = true
348
240
 
349
241
  this.#clearScreen()
350
- this.#logger.info('starting HTTP server...')
351
242
 
243
+ this.#logger.info('starting HTTP server...')
352
244
  this.#startHTTPServer(port, 'blocking')
245
+
353
246
  this.#startAssetsServer()
354
247
 
355
248
  /**
@@ -387,84 +280,27 @@ export class DevServer {
387
280
  /**
388
281
  * Changes in TypeScript source file
389
282
  */
390
- output.watcher.on('source:add', ({ relativePath }) => {
391
- this.#clearScreen()
392
- this.#logger.log(`${this.#colors.green('add')} ${relativePath}`)
393
- this.#restart(port)
394
- })
395
- output.watcher.on('source:change', ({ relativePath }) => {
396
- this.#clearScreen()
397
- this.#logger.log(`${this.#colors.green('update')} ${relativePath}`)
398
- this.#restart(port)
399
- })
400
- output.watcher.on('source:unlink', ({ relativePath }) => {
401
- this.#clearScreen()
402
- this.#logger.log(`${this.#colors.green('delete')} ${relativePath}`)
403
- this.#restart(port)
404
- })
283
+ output.watcher.on('source:add', ({ relativePath }) =>
284
+ this.#handleSourceFileChange('add', port, relativePath)
285
+ )
286
+ output.watcher.on('source:change', ({ relativePath }) =>
287
+ this.#handleSourceFileChange('update', port, relativePath)
288
+ )
289
+ output.watcher.on('source:unlink', ({ relativePath }) =>
290
+ this.#handleSourceFileChange('delete', port, relativePath)
291
+ )
405
292
 
406
293
  /**
407
- * Changes in other files
294
+ * Changes in non-TypeScript source files
408
295
  */
409
- output.watcher.on('add', ({ relativePath }) => {
410
- if (this.#isDotEnvFile(relativePath) || this.#isRcFile(relativePath)) {
411
- this.#clearScreen()
412
- this.#logger.log(`${this.#colors.green('add')} ${relativePath}`)
413
- this.#restart(port)
414
- return
415
- }
416
-
417
- if (this.#isMetaFileWithReloadsEnabled(relativePath)) {
418
- this.#clearScreen()
419
- this.#logger.log(`${this.#colors.green('add')} ${relativePath}`)
420
- this.#restart(port)
421
- return
422
- }
423
-
424
- if (this.#isMetaFileWithReloadsDisabled(relativePath)) {
425
- this.#clearScreen()
426
- this.#logger.log(`${this.#colors.green('add')} ${relativePath}`)
427
- }
428
- })
429
- output.watcher.on('change', ({ relativePath }) => {
430
- if (this.#isDotEnvFile(relativePath) || this.#isRcFile(relativePath)) {
431
- this.#clearScreen()
432
- this.#logger.log(`${this.#colors.green('update')} ${relativePath}`)
433
- this.#restart(port)
434
- return
435
- }
436
-
437
- if (this.#isMetaFileWithReloadsEnabled(relativePath)) {
438
- this.#clearScreen()
439
- this.#logger.log(`${this.#colors.green('update')} ${relativePath}`)
440
- this.#restart(port)
441
- return
442
- }
443
-
444
- if (this.#isMetaFileWithReloadsDisabled(relativePath)) {
445
- this.#clearScreen()
446
- this.#logger.log(`${this.#colors.green('update')} ${relativePath}`)
447
- }
448
- })
449
- output.watcher.on('unlink', ({ relativePath }) => {
450
- if (this.#isDotEnvFile(relativePath) || this.#isRcFile(relativePath)) {
451
- this.#clearScreen()
452
- this.#logger.log(`${this.#colors.green('delete')} ${relativePath}`)
453
- this.#restart(port)
454
- return
455
- }
456
-
457
- if (this.#isMetaFileWithReloadsEnabled(relativePath)) {
458
- this.#clearScreen()
459
- this.#logger.log(`${this.#colors.green('delete')} ${relativePath}`)
460
- this.#restart(port)
461
- return
462
- }
463
-
464
- if (this.#isMetaFileWithReloadsDisabled(relativePath)) {
465
- this.#clearScreen()
466
- this.#logger.log(`${this.#colors.green('delete')} ${relativePath}`)
467
- }
468
- })
296
+ output.watcher.on('add', ({ relativePath }) =>
297
+ this.#handleFileChange('add', port, relativePath)
298
+ )
299
+ output.watcher.on('change', ({ relativePath }) =>
300
+ this.#handleFileChange('update', port, relativePath)
301
+ )
302
+ output.watcher.on('unlink', ({ relativePath }) =>
303
+ this.#handleFileChange('delete', port, relativePath)
304
+ )
469
305
  }
470
306
  }
package/src/helpers.ts ADDED
@@ -0,0 +1,162 @@
1
+ /*
2
+ * @adonisjs/assembler
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+
10
+ import getRandomPort from 'get-port'
11
+ import type tsStatic from 'typescript'
12
+ import { fileURLToPath } from 'node:url'
13
+ import { execaNode, execa } from 'execa'
14
+ import { EnvLoader, EnvParser } from '@adonisjs/env'
15
+ import { ConfigParser, Watcher } from '@poppinss/chokidar-ts'
16
+
17
+ import type { RunOptions, WatchOptions } from './types.js'
18
+
19
+ /**
20
+ * Default set of args to pass in order to run TypeScript
21
+ * source. Used by "run" and "runNode" scripts
22
+ */
23
+ const DEFAULT_NODE_ARGS = [
24
+ // Use ts-node/esm loader. The project must install it
25
+ '--loader=ts-node/esm',
26
+ // Disable annonying warnings
27
+ '--no-warnings',
28
+ // Enable expiremental meta resolve for cases where someone uses magic import string
29
+ '--experimental-import-meta-resolve',
30
+ ]
31
+
32
+ /**
33
+ * Parses tsconfig.json and prints errors using typescript compiler
34
+ * host
35
+ */
36
+ export function parseConfig(cwd: string | URL, ts: typeof tsStatic) {
37
+ const { config, error } = new ConfigParser(cwd, 'tsconfig.json', ts).parse()
38
+ if (error) {
39
+ const compilerHost = ts.createCompilerHost({})
40
+ console.log(ts.formatDiagnosticsWithColorAndContext([error], compilerHost))
41
+ return
42
+ }
43
+
44
+ if (config!.errors.length) {
45
+ const compilerHost = ts.createCompilerHost({})
46
+ console.log(ts.formatDiagnosticsWithColorAndContext(config!.errors, compilerHost))
47
+ return
48
+ }
49
+
50
+ return config
51
+ }
52
+
53
+ /**
54
+ * Runs a Node.js script as a child process and inherits the stdio streams
55
+ */
56
+ export function runNode(cwd: string | URL, options: RunOptions) {
57
+ const childProcess = execaNode(options.script, options.scriptArgs, {
58
+ nodeOptions: DEFAULT_NODE_ARGS.concat(options.nodeArgs),
59
+ preferLocal: true,
60
+ windowsHide: false,
61
+ localDir: cwd,
62
+ cwd,
63
+ buffer: false,
64
+ stdio: options.stdio || 'inherit',
65
+ env: {
66
+ ...(options.stdio === 'pipe' ? { FORCE_COLOR: 'true' } : {}),
67
+ ...options.env,
68
+ },
69
+ })
70
+
71
+ return childProcess
72
+ }
73
+
74
+ /**
75
+ * Runs a script as a child process and inherits the stdio streams
76
+ */
77
+ export function run(cwd: string | URL, options: Omit<RunOptions, 'nodeArgs'>) {
78
+ const childProcess = execa(options.script, options.scriptArgs, {
79
+ preferLocal: true,
80
+ windowsHide: false,
81
+ localDir: cwd,
82
+ cwd,
83
+ buffer: false,
84
+ stdio: options.stdio || 'inherit',
85
+ env: {
86
+ ...(options.stdio === 'pipe' ? { FORCE_COLOR: 'true' } : {}),
87
+ ...options.env,
88
+ },
89
+ })
90
+
91
+ return childProcess
92
+ }
93
+
94
+ /**
95
+ * Watches the file system using tsconfig file
96
+ */
97
+ export function watch(cwd: string | URL, ts: typeof tsStatic, options: WatchOptions) {
98
+ const config = parseConfig(cwd, ts)
99
+ if (!config) {
100
+ return
101
+ }
102
+
103
+ const watcher = new Watcher(typeof cwd === 'string' ? cwd : fileURLToPath(cwd), config!)
104
+ const chokidar = watcher.watch(['.'], { usePolling: options.poll })
105
+ return { watcher, chokidar }
106
+ }
107
+
108
+ /**
109
+ * Check if file is an .env file
110
+ */
111
+ export function isDotEnvFile(filePath: string) {
112
+ if (filePath === '.env') {
113
+ return true
114
+ }
115
+
116
+ return filePath.includes('.env.')
117
+ }
118
+
119
+ /**
120
+ * Check if file is .adonisrc.json file
121
+ */
122
+ export function isRcFile(filePath: string) {
123
+ return filePath === '.adonisrc.json'
124
+ }
125
+
126
+ /**
127
+ * Returns the port to use after inspect the dot-env files inside
128
+ * a given directory.
129
+ *
130
+ * A random port is used when the specified port is in use. Following
131
+ * is the logic for finding a specified port.
132
+ *
133
+ * - The "process.env.PORT" value is used if exists.
134
+ * - The dot-env files are loaded using the "EnvLoader" and the PORT
135
+ * value is by iterating over all the loaded files. The iteration
136
+ * stops after first find.
137
+ */
138
+ export async function getPort(cwd: URL): Promise<number> {
139
+ /**
140
+ * Use existing port if exists
141
+ */
142
+ if (process.env.PORT) {
143
+ return getRandomPort({ port: Number(process.env.PORT) })
144
+ }
145
+
146
+ /**
147
+ * Loop over files and use the port from their contents. Stops
148
+ * after first match
149
+ */
150
+ const files = await new EnvLoader(cwd).load()
151
+ for (let file of files) {
152
+ const envVariables = new EnvParser(file.contents).parse()
153
+ if (envVariables.PORT) {
154
+ return getRandomPort({ port: Number(envVariables.PORT) })
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Use 3333 as the port
160
+ */
161
+ return getRandomPort({ port: 3333 })
162
+ }