@plugjs/plug 0.2.6 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/cli/plug.mjs +3 -15
  2. package/cli/ts-loader.mjs +1 -1
  3. package/cli/tsrun.mjs +1104 -36
  4. package/dist/asserts.cjs +4 -0
  5. package/dist/asserts.cjs.map +1 -1
  6. package/dist/asserts.mjs +4 -0
  7. package/dist/asserts.mjs.map +1 -1
  8. package/dist/fork.cjs +8 -5
  9. package/dist/fork.cjs.map +1 -1
  10. package/dist/fork.mjs +8 -5
  11. package/dist/fork.mjs.map +1 -1
  12. package/dist/fs.cjs +1 -1
  13. package/dist/helpers.cjs +20 -0
  14. package/dist/helpers.cjs.map +1 -1
  15. package/dist/helpers.d.ts +6 -0
  16. package/dist/helpers.mjs +20 -1
  17. package/dist/helpers.mjs.map +1 -1
  18. package/dist/index.cjs +8 -8
  19. package/dist/logging/emit.cjs +4 -7
  20. package/dist/logging/emit.cjs.map +1 -1
  21. package/dist/logging/emit.mjs +4 -7
  22. package/dist/logging/emit.mjs.map +1 -1
  23. package/dist/logging/github.cjs +63 -0
  24. package/dist/logging/github.cjs.map +6 -0
  25. package/dist/logging/github.d.ts +13 -0
  26. package/dist/logging/github.mjs +38 -0
  27. package/dist/logging/github.mjs.map +6 -0
  28. package/dist/logging/options.cjs +20 -3
  29. package/dist/logging/options.cjs.map +1 -1
  30. package/dist/logging/options.d.ts +9 -2
  31. package/dist/logging/options.mjs +20 -3
  32. package/dist/logging/options.mjs.map +1 -1
  33. package/dist/logging/report.cjs +25 -3
  34. package/dist/logging/report.cjs.map +1 -1
  35. package/dist/logging/report.mjs +26 -4
  36. package/dist/logging/report.mjs.map +1 -1
  37. package/dist/logging.cjs +1 -0
  38. package/dist/logging.cjs.map +1 -1
  39. package/dist/logging.d.ts +1 -0
  40. package/dist/logging.mjs +1 -0
  41. package/dist/logging.mjs.map +1 -1
  42. package/dist/plugs/debug.mjs +18 -28
  43. package/dist/plugs/debug.mjs.map +1 -1
  44. package/dist/plugs/edit.mjs +13 -23
  45. package/dist/plugs/edit.mjs.map +1 -1
  46. package/dist/plugs/esbuild/fix-extensions.cjs +1 -1
  47. package/dist/plugs/rmf.mjs +14 -24
  48. package/dist/plugs/rmf.mjs.map +1 -1
  49. package/dist/utils/exec.cjs +22 -15
  50. package/dist/utils/exec.cjs.map +2 -2
  51. package/dist/utils/exec.mjs +21 -14
  52. package/dist/utils/exec.mjs.map +1 -1
  53. package/dist/utils/match.cjs +1 -1
  54. package/extra/plug.mts +6 -3
  55. package/extra/tsrun.mts +91 -28
  56. package/extra/utils.ts +0 -18
  57. package/package.json +4 -5
  58. package/src/asserts.ts +7 -0
  59. package/src/fork.ts +10 -5
  60. package/src/helpers.ts +24 -1
  61. package/src/logging/emit.ts +2 -7
  62. package/src/logging/github.ts +71 -0
  63. package/src/logging/options.ts +29 -4
  64. package/src/logging/report.ts +40 -6
  65. package/src/logging.ts +1 -0
  66. package/src/utils/exec.ts +29 -18
@@ -1,4 +1,5 @@
1
1
  import { EventEmitter } from 'node:events'
2
+ import { Socket } from 'node:net'
2
3
 
3
4
  import { getLevelNumber, NOTICE } from './levels'
4
5
 
@@ -28,6 +29,8 @@ export interface LogOptions {
28
29
  showSources: boolean,
29
30
  /** The task name to be used by default if a task is not contextualized. */
30
31
  defaultTaskName: string,
32
+ /** Whether GitHub annotations are enabled or not. */
33
+ githubAnnotations: boolean,
31
34
  /** The options used by NodeJS for object inspection. */
32
35
  readonly inspectOptions: InspectOptions,
33
36
 
@@ -41,8 +44,13 @@ export interface LogOptions {
41
44
  /** Remove an event listener for the specified event. */
42
45
  off(eventName: 'changed', listener: (logOptions: this) => void): this;
43
46
 
44
- /** Return a record of environment variables for forking. */
45
- forkEnv(taskName?: string): Record<string, string>
47
+ /**
48
+ * Return a record of environment variables for forking.
49
+ *
50
+ * @param taskName The default task name of the forked process
51
+ * @param logFd A file descriptor where logs should be sinked to
52
+ */
53
+ forkEnv(taskName?: string, logFd?: number): Record<string, string>
46
54
  }
47
55
 
48
56
  /* ========================================================================== *
@@ -58,6 +66,7 @@ class LogOptionsImpl extends EventEmitter implements LogOptions {
58
66
  private _lineLength = (<NodeJS.WriteStream> this._output).columns || 80
59
67
  private _lineLengthSet = false // has line length been set manually?
60
68
  private _showSources = true // by default, always show source snippets
69
+ private _githubAnnotations = false // ultimately set by the constructor
61
70
  private _inspectOptions: InspectOptions = {}
62
71
  private _defaultTaskName = ''
63
72
  private _taskLength = 0
@@ -78,27 +87,34 @@ class LogOptionsImpl extends EventEmitter implements LogOptions {
78
87
  // Other values don't change the value of `options.colors`
79
88
  }
80
89
 
90
+ /* If the `GITHUB_ACTIONS` is `true` then enable annotations */
91
+ this._githubAnnotations = process.env.GITHUB_ACTIONS === 'true'
92
+
81
93
  /*
82
94
  * The `__LOG_OPTIONS` variable is a JSON-serialized `LogOptions` object
83
95
  * and it's processed _last_ as it's normally only created by fork below
84
96
  * and consumed by the `Exec` plug (which has no other way of communicating)
85
97
  */
86
- Object.assign(this, JSON.parse(process.env.__LOG_OPTIONS || '{}'))
98
+ const { fd, ...options } = JSON.parse(process.env.__LOG_OPTIONS || '{}')
99
+ if (fd) this.output = new Socket({ fd }).unref()
100
+ Object.assign(this, options)
87
101
  }
88
102
 
89
103
  private _notifyListeners(): void {
90
104
  super.emit('changed', this)
91
105
  }
92
106
 
93
- forkEnv(taskName?: string): Record<string, string> {
107
+ forkEnv(taskName?: string, fd?: number): Record<string, string> {
94
108
  return {
95
109
  __LOG_OPTIONS: JSON.stringify({
96
110
  level: this._level,
97
111
  colors: this._colors,
98
112
  lineLength: this._lineLength,
99
113
  taskLength: this._taskLength,
114
+ githubAnnotations: this.githubAnnotations,
100
115
  defaultTaskName: taskName || this._defaultTaskName,
101
116
  spinner: false, // forked spinner is always false
117
+ fd, // file descriptor for logs
102
118
  }),
103
119
  }
104
120
  }
@@ -189,6 +205,15 @@ class LogOptionsImpl extends EventEmitter implements LogOptions {
189
205
  this._notifyListeners()
190
206
  }
191
207
 
208
+ get githubAnnotations(): boolean {
209
+ return this._githubAnnotations
210
+ }
211
+
212
+ set githubAnnotations(githubAnnotations: boolean) {
213
+ this._githubAnnotations = githubAnnotations
214
+ this._notifyListeners()
215
+ }
216
+
192
217
  get inspectOptions(): InspectOptions {
193
218
  return {
194
219
  colors: this._colors,
@@ -1,13 +1,22 @@
1
1
  import { BuildFailure } from '../asserts'
2
2
  import { readFile } from '../fs'
3
3
  import { $blu, $cyn, $gry, $red, $und, $wht, $ylw } from './colors'
4
- import { ERROR, NOTICE, WARN } from './levels'
4
+ import { ERROR, logLevels, NOTICE, WARN } from './levels'
5
5
  import { logOptions } from './options'
6
+ import { githubAnnotation } from './github'
6
7
 
7
8
  import type { AbsolutePath } from '../paths'
8
9
  import type { LogEmitter } from './emit'
9
10
  import type { LogLevels } from './levels'
10
11
 
12
+ let _showSources = logOptions.showSources
13
+ let _githubAnnotations = logOptions.githubAnnotations
14
+ logOptions.on('changed', (options) => {
15
+ _showSources = options.showSources
16
+ _githubAnnotations = options.githubAnnotations
17
+ })
18
+
19
+
11
20
  /* ========================================================================== */
12
21
 
13
22
  /** Levels used in a {@link Report} */
@@ -267,12 +276,12 @@ export class ReportImpl implements Report {
267
276
 
268
277
 
269
278
  done(showSources?: boolean | undefined): void {
270
- if (showSources == null) showSources = logOptions.showSources
279
+ if (showSources == null) showSources = _showSources
271
280
  if (! this.empty) this._emit(showSources)
272
281
  if (this.errors) throw BuildFailure.fail()
273
282
  }
274
283
 
275
- private _emit(showSources: boolean): this {
284
+ private _emit(showSources: boolean): void {
276
285
  /* Counters for all we need to print nicely */
277
286
  let fPad = 0
278
287
  let aPad = 0
@@ -281,7 +290,7 @@ export class ReportImpl implements Report {
281
290
  let cPad = 0
282
291
 
283
292
  /* Skip report all together if empty! */
284
- if ((this._annotations.size === 0) && (this._records.size === 0)) return this
293
+ if ((this._annotations.size === 0) && (this._records.size === 0)) return
285
294
 
286
295
  /* This is GIANT: sort and convert our data for easy reporting */
287
296
  const entries = [ ...this._annotations.keys(), ...this._records.keys() ]
@@ -431,7 +440,32 @@ export class ReportImpl implements Report {
431
440
  this._emitter(options, [ '' ])
432
441
  }
433
442
 
434
- /* Done! */
435
- return this
443
+ /* Annotate in GitHub */
444
+ if (_githubAnnotations) {
445
+ for (const entry of entries) {
446
+ const file = entry.file === nul ? undefined : entry.file
447
+
448
+ for (const report of entry.records) {
449
+ const type: 'error' | 'warning' | null =
450
+ report.level === logLevels.ERROR ? 'error' :
451
+ report.level === logLevels.WARN ? 'warning' :
452
+ null
453
+
454
+ if (! type) continue
455
+
456
+ const title = `${this._title} (task "${this._task}")`
457
+ const col = report.column || undefined
458
+ const line = report.line || undefined
459
+ const endColumn =
460
+ report.column ?
461
+ report.length >= Number.MAX_SAFE_INTEGER ? undefined :
462
+ ((report.column + report.length) || undefined) :
463
+ undefined
464
+ const message = report.messages.join('\n')
465
+
466
+ githubAnnotation({ type, title, file, col, line, endColumn }, message)
467
+ }
468
+ }
469
+ }
436
470
  }
437
471
  }
package/src/logging.ts CHANGED
@@ -3,6 +3,7 @@ import { getLogger, type Log } from './logging/logger'
3
3
  import { setupSpinner } from './logging/spinner'
4
4
 
5
5
  export * from './logging/colors'
6
+ export * from './logging/github'
6
7
  export * from './logging/levels'
7
8
  export * from './logging/logger'
8
9
  export * from './logging/options'
package/src/utils/exec.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path'
2
- import reaadline from 'node:readline'
2
+ import readline from 'node:readline'
3
3
  import { fork as forkProcess, spawn as spawnProcess } from 'node:child_process'
4
4
 
5
5
  import { assert, BuildFailure } from '../asserts'
@@ -62,7 +62,7 @@ export async function execChild(
62
62
 
63
63
  // Build our environment variables record
64
64
  const PATH = childPaths.join(path.delimiter)
65
- const logForkEnv = logOptions.forkEnv(context.taskName)
65
+ const logForkEnv = logOptions.forkEnv(context.taskName, 4)
66
66
  const childEnv: Record<string, string> = { ...process.env, ...env, ...logForkEnv, PATH }
67
67
 
68
68
  // Instrument coverage directory if needed
@@ -71,32 +71,43 @@ export async function execChild(
71
71
  // Prepare the options for calling `spawn`
72
72
  const childOptions: SpawnOptions = {
73
73
  ...extraOptions,
74
- stdio: [ 'ignore', 'pipe', 'pipe' ],
74
+ stdio: [ 'ignore', 'pipe', 'pipe', 'ipc', 'pipe' ],
75
75
  cwd: childCwd,
76
76
  env: childEnv,
77
77
  shell,
78
78
  }
79
79
 
80
- // Add the 'ipc' channel to stdio options if forking
81
- if (fork) childOptions.stdio = [ 'ignore', 'pipe', 'pipe', 'ipc' ]
82
-
83
80
  // Spawn our subprocess and monitor its stdout/stderr
84
- context.log.info('Executing', [ cmd, ...args ])
85
- context.log.info('Execution options', childOptions)
81
+ context.log.info(fork ? 'Forking' : 'Executing', [ cmd, ...args ])
82
+ context.log.debug('Child process options', childOptions)
83
+
86
84
  const child = fork ?
87
85
  forkProcess(cmd, args, childOptions) :
88
86
  spawnProcess(cmd, args, childOptions)
89
87
 
90
- // Standard output to "notice"
91
- if (child.stdout) {
92
- const out = reaadline.createInterface(child.stdout)
93
- out.on('line', (line) => line ? context.log.notice(line) : context.log.notice('\u00a0'))
94
- }
95
-
96
- // Standard error to "warning"
97
- if (child.stderr) {
98
- const err = reaadline.createInterface(child.stderr)
99
- err.on('line', (line) => line ? context.log.warn(line) : context.log.warn('\u00a0'))
88
+ try {
89
+ context.log.info('Child process PID', child.pid)
90
+
91
+ // Standard output to "notice"
92
+ if (child.stdout) {
93
+ const out = readline.createInterface(child.stdout)
94
+ out.on('line', (line) => context.log.notice(line || '\u00a0'))
95
+ }
96
+
97
+ // Standard error to "warning"
98
+ if (child.stderr) {
99
+ const err = readline.createInterface(child.stderr)
100
+ err.on('line', (line) => context.log.warn(line ||'\u00a0'))
101
+ }
102
+
103
+ // Log output bypass
104
+ if (child.stdio[4]) {
105
+ child.stdio[4].on('data', (data) => logOptions.output.write(data))
106
+ }
107
+ } catch (error) {
108
+ // If something happens before returning our promise, kill the child...
109
+ child.kill()
110
+ throw error
100
111
  }
101
112
 
102
113
  // Return our promise from the spawn events