@adonisjs/assembler 6.1.3-4 → 6.1.3-6

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,62 @@ 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.setLogger(this.#logger)
150
+ this.#assetsServer.start()
151
+ }
249
152
 
250
- this.#logger.info(`starting "${assetsBundler.driver}" dev server...`)
251
- this.#assetsServerProcess = run(this.#cwd, {
252
- script: assetsBundler.cmd,
153
+ /**
154
+ * Restarts the HTTP server
155
+ */
156
+ #restartHTTPServer(port: string) {
157
+ if (this.#httpServer) {
158
+ this.#httpServer.removeAllListeners()
159
+ this.#httpServer.kill('SIGKILL')
160
+ }
253
161
 
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
- })
162
+ this.#startHTTPServer(port, 'blocking')
163
+ }
261
164
 
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
- })
165
+ /**
166
+ * Handles a non TypeScript file change
167
+ */
168
+ #handleFileChange(action: string, port: string, relativePath: string) {
169
+ if (isDotEnvFile(relativePath) || isRcFile(relativePath)) {
170
+ this.#clearScreen()
171
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`)
172
+ this.#restartHTTPServer(port)
173
+ return
174
+ }
272
175
 
273
- this.#assetsServerProcess.stderr?.on('data', (data) => {
274
- if (assetsBundler.driver === 'vite') {
275
- this.#logViteDevServerMessage(data)
276
- } else {
277
- this.#logAssetsDevServerMessage(data)
278
- }
279
- })
176
+ if (this.#isMetaFileWithReloadsEnabled(relativePath)) {
177
+ this.#clearScreen()
178
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`)
179
+ this.#restartHTTPServer(port)
180
+ return
181
+ }
280
182
 
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
- })
183
+ if (this.#isMetaFileWithReloadsDisabled(relativePath)) {
184
+ this.#clearScreen()
185
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`)
186
+ }
291
187
  }
292
188
 
293
189
  /**
294
- * Restart the development server
190
+ * Handles TypeScript source file change
295
191
  */
296
- #restart(port: string) {
297
- if (this.#httpServerProcess) {
298
- this.#httpServerProcess.removeAllListeners()
299
- this.#httpServerProcess.kill('SIGKILL')
300
- }
301
-
302
- this.#startHTTPServer(port, 'blocking')
192
+ #handleSourceFileChange(action: string, port: string, relativePath: string) {
193
+ this.#clearScreen()
194
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`)
195
+ this.#restartHTTPServer(port)
303
196
  }
304
197
 
305
198
  /**
@@ -307,6 +200,7 @@ export class DevServer {
307
200
  */
308
201
  setLogger(logger: Logger) {
309
202
  this.#logger = logger
203
+ this.#assetsServer?.setLogger(logger)
310
204
  return this
311
205
  }
312
206
 
@@ -334,8 +228,7 @@ export class DevServer {
334
228
  async start() {
335
229
  this.#clearScreen()
336
230
  this.#logger.info('starting HTTP server...')
337
- this.#startHTTPServer(String(await this.#getPort()), 'nonblocking')
338
-
231
+ this.#startHTTPServer(String(await getPort(this.#cwd)), 'nonblocking')
339
232
  this.#startAssetsServer()
340
233
  }
341
234
 
@@ -343,13 +236,14 @@ export class DevServer {
343
236
  * Start the development server in watch mode
344
237
  */
345
238
  async startAndWatch(ts: typeof tsStatic, options?: { poll: boolean }) {
346
- const port = String(await this.#getPort())
239
+ const port = String(await getPort(this.#cwd))
347
240
  this.#isWatching = true
348
241
 
349
242
  this.#clearScreen()
350
- this.#logger.info('starting HTTP server...')
351
243
 
244
+ this.#logger.info('starting HTTP server...')
352
245
  this.#startHTTPServer(port, 'blocking')
246
+
353
247
  this.#startAssetsServer()
354
248
 
355
249
  /**
@@ -387,84 +281,27 @@ export class DevServer {
387
281
  /**
388
282
  * Changes in TypeScript source file
389
283
  */
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
- })
284
+ output.watcher.on('source:add', ({ relativePath }) =>
285
+ this.#handleSourceFileChange('add', port, relativePath)
286
+ )
287
+ output.watcher.on('source:change', ({ relativePath }) =>
288
+ this.#handleSourceFileChange('update', port, relativePath)
289
+ )
290
+ output.watcher.on('source:unlink', ({ relativePath }) =>
291
+ this.#handleSourceFileChange('delete', port, relativePath)
292
+ )
405
293
 
406
294
  /**
407
- * Changes in other files
295
+ * Changes in non-TypeScript source files
408
296
  */
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
- })
297
+ output.watcher.on('add', ({ relativePath }) =>
298
+ this.#handleFileChange('add', port, relativePath)
299
+ )
300
+ output.watcher.on('change', ({ relativePath }) =>
301
+ this.#handleFileChange('update', port, relativePath)
302
+ )
303
+ output.watcher.on('unlink', ({ relativePath }) =>
304
+ this.#handleFileChange('delete', port, relativePath)
305
+ )
469
306
  }
470
307
  }
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
+ }