@plugjs/plug 0.2.5 → 0.2.7
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/cli/plug.mjs +3 -15
- package/cli/ts-loader.mjs +1 -1
- package/cli/tsrun.mjs +1104 -36
- package/dist/asserts.cjs +4 -0
- package/dist/asserts.cjs.map +1 -1
- package/dist/asserts.mjs +4 -0
- package/dist/asserts.mjs.map +1 -1
- package/dist/fork.cjs +8 -5
- package/dist/fork.cjs.map +1 -1
- package/dist/fork.mjs +8 -5
- package/dist/fork.mjs.map +1 -1
- package/dist/helpers.cjs +20 -0
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.ts +6 -0
- package/dist/helpers.mjs +20 -1
- package/dist/helpers.mjs.map +1 -1
- package/dist/logging/emit.cjs +4 -7
- package/dist/logging/emit.cjs.map +1 -1
- package/dist/logging/emit.mjs +4 -7
- package/dist/logging/emit.mjs.map +1 -1
- package/dist/logging/github.cjs +63 -0
- package/dist/logging/github.cjs.map +6 -0
- package/dist/logging/github.d.ts +13 -0
- package/dist/logging/github.mjs +38 -0
- package/dist/logging/github.mjs.map +6 -0
- package/dist/logging/options.cjs +20 -3
- package/dist/logging/options.cjs.map +1 -1
- package/dist/logging/options.d.ts +9 -2
- package/dist/logging/options.mjs +20 -3
- package/dist/logging/options.mjs.map +1 -1
- package/dist/logging/report.cjs +25 -3
- package/dist/logging/report.cjs.map +1 -1
- package/dist/logging/report.mjs +26 -4
- package/dist/logging/report.mjs.map +1 -1
- package/dist/logging.cjs +1 -0
- package/dist/logging.cjs.map +1 -1
- package/dist/logging.d.ts +1 -0
- package/dist/logging.mjs +1 -0
- package/dist/logging.mjs.map +1 -1
- package/dist/utils/exec.cjs +20 -13
- package/dist/utils/exec.cjs.map +2 -2
- package/dist/utils/exec.mjs +21 -14
- package/dist/utils/exec.mjs.map +1 -1
- package/extra/plug.mts +6 -3
- package/extra/tsrun.mts +91 -28
- package/extra/utils.ts +0 -18
- package/package.json +1 -1
- package/src/asserts.ts +7 -0
- package/src/fork.ts +10 -5
- package/src/helpers.ts +24 -1
- package/src/logging/emit.ts +2 -7
- package/src/logging/github.ts +71 -0
- package/src/logging/options.ts +29 -4
- package/src/logging/report.ts +40 -6
- package/src/logging.ts +1 -0
- package/src/utils/exec.ts +29 -18
package/extra/tsrun.mts
CHANGED
|
@@ -2,8 +2,15 @@
|
|
|
2
2
|
/* eslint-disable no-console */
|
|
3
3
|
|
|
4
4
|
import _path from 'node:path'
|
|
5
|
+
import _repl from 'node:repl'
|
|
6
|
+
import _module from 'node:module'
|
|
5
7
|
|
|
6
|
-
import
|
|
8
|
+
import _yargs from 'yargs-parser'
|
|
9
|
+
|
|
10
|
+
import { $blu, $gry, $rst, $und, $wht, main } from './utils.js'
|
|
11
|
+
|
|
12
|
+
/** Version injected by esbuild */
|
|
13
|
+
declare const __version: string
|
|
7
14
|
|
|
8
15
|
/** Our minimalistic help */
|
|
9
16
|
function help(): never {
|
|
@@ -13,8 +20,10 @@ function help(): never {
|
|
|
13
20
|
|
|
14
21
|
${$blu}${$und}Options:${$rst}
|
|
15
22
|
|
|
16
|
-
${$wht}-h --help${$rst}
|
|
17
|
-
${$wht}-v --version${$rst}
|
|
23
|
+
${$wht}-h --help ${$rst} Help! You're reading it now!
|
|
24
|
+
${$wht}-v --version ${$rst} Version! This one: ${__version}!
|
|
25
|
+
${$wht}-e --eval ${$rst} Evaluate the script
|
|
26
|
+
${$wht}-p --print ${$rst} Evaluate the script and print the result
|
|
18
27
|
${$wht} --force-esm${$rst} Force transpilation of ".ts" files to EcmaScript modules
|
|
19
28
|
${$wht} --force-cjs${$rst} Force transpilation of ".ts" files to CommonJS modules
|
|
20
29
|
|
|
@@ -31,34 +40,88 @@ function help(): never {
|
|
|
31
40
|
|
|
32
41
|
/** Process the command line */
|
|
33
42
|
main((args: string[]): void => {
|
|
34
|
-
let
|
|
35
|
-
let
|
|
43
|
+
let _script: string | undefined
|
|
44
|
+
let _scriptArgs: string[] = []
|
|
45
|
+
let _print: boolean = false
|
|
46
|
+
let _eval: boolean = false
|
|
47
|
+
|
|
48
|
+
/* Yargs-parse our arguments */
|
|
49
|
+
const parsed = _yargs(args, {
|
|
50
|
+
configuration: {
|
|
51
|
+
'camel-case-expansion': false,
|
|
52
|
+
'strip-aliased': true,
|
|
53
|
+
'strip-dashed': true,
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
alias: {
|
|
57
|
+
'version': [ 'v' ],
|
|
58
|
+
'help': [ 'h' ],
|
|
59
|
+
'eval': [ 'e' ],
|
|
60
|
+
'print': [ 'p' ],
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
boolean: [ 'help', 'eval', 'print', 'force-esm', 'version', 'force-cjs' ],
|
|
64
|
+
})
|
|
36
65
|
|
|
37
66
|
// Parse options, leaving script and scriptArgs with our code to run
|
|
38
|
-
for (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
for (const [ key, value ] of Object.entries(parsed)) {
|
|
68
|
+
switch (key) {
|
|
69
|
+
case '_': // extra arguments
|
|
70
|
+
[ _script, ..._scriptArgs ] = value
|
|
71
|
+
break
|
|
72
|
+
case 'help': // help screen
|
|
73
|
+
help()
|
|
74
|
+
break
|
|
75
|
+
case 'version': // version dump
|
|
76
|
+
console.log(`v${__version}`)
|
|
77
|
+
process.exit(0)
|
|
78
|
+
break
|
|
79
|
+
case 'eval': // eval script
|
|
80
|
+
_eval = value
|
|
81
|
+
break
|
|
82
|
+
case 'print': // eval script and print return value
|
|
83
|
+
_print = true
|
|
84
|
+
_eval = true
|
|
85
|
+
break
|
|
50
86
|
}
|
|
51
|
-
|
|
52
|
-
([ script, ...scriptArgs ] = args.slice(i))
|
|
53
|
-
break
|
|
54
87
|
}
|
|
55
88
|
|
|
56
|
-
//
|
|
57
|
-
if (!
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
89
|
+
// Start the repl or run the script?
|
|
90
|
+
if (! _script) {
|
|
91
|
+
// No script? Then repl
|
|
92
|
+
console.log(`Welcome to Node.js ${process.version} (tsrun v${__version}).`)
|
|
93
|
+
console.log('Type ".help" for more information.')
|
|
94
|
+
_repl.start()
|
|
95
|
+
} else if (_eval) {
|
|
96
|
+
// If we are evaluating a script, we need to use some node internals to do
|
|
97
|
+
// all the tricks to run this... We a fake script running the code to
|
|
98
|
+
// evaluate, instrumenting "globalThis" with all required vars and modules
|
|
99
|
+
const script = `
|
|
100
|
+
globalThis.module = module;
|
|
101
|
+
globalThis.require = require;
|
|
102
|
+
globalThis.exports = exports;
|
|
103
|
+
globalThis.__dirname = __dirname;
|
|
104
|
+
globalThis.__filename = __filename;
|
|
105
|
+
|
|
106
|
+
for (const module of require('repl').builtinModules) {
|
|
107
|
+
if (module.indexOf('/') >= 0) continue;
|
|
108
|
+
if (Object.hasOwn(globalThis, module)) continue;
|
|
109
|
+
Object.defineProperty(globalThis, module, { get: () => require(module) });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return require('node:vm').runInThisContext(${JSON.stringify(_script)}, '[eval]')
|
|
113
|
+
`
|
|
114
|
+
|
|
115
|
+
// Use the Node internal "Module._compile" to compile and run our script
|
|
116
|
+
const result = (new _module('[eval]') as any)._compile(script, '[eval]')
|
|
117
|
+
|
|
118
|
+
// If we need to print, then let's do it!
|
|
119
|
+
if (_print) console.log(result)
|
|
120
|
+
} else {
|
|
121
|
+
// Resolve the _full_ path of the script, and tweak our process.argv
|
|
122
|
+
// arguments, them simply import the script and let Node do its thing...
|
|
123
|
+
_script = _path.resolve(process.cwd(), _script)
|
|
124
|
+
process.argv = [ process.argv0, _script, ..._scriptArgs ]
|
|
125
|
+
import(_script)
|
|
126
|
+
}
|
|
64
127
|
})
|
package/extra/utils.ts
CHANGED
|
@@ -17,24 +17,6 @@ export const $wht = process.stdout.isTTY ? '\u001b[1;38;5;255m' : '' // full-bri
|
|
|
17
17
|
export const $tsk = process.stdout.isTTY ? '\u001b[38;5;141m' : '' // the color for tasks (purple)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
/* ========================================================================== *
|
|
21
|
-
* PACKAGE VERSION *
|
|
22
|
-
* ========================================================================== */
|
|
23
|
-
|
|
24
|
-
export function version(): string {
|
|
25
|
-
const debug = _util.debuglog('plug:cli')
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const thisFile = _url.fileURLToPath(import.meta.url)
|
|
29
|
-
const packageFile = _path.resolve(thisFile, '..', '..', 'package.json')
|
|
30
|
-
const packageData = _fs.readFileSync(packageFile, 'utf-8')
|
|
31
|
-
return JSON.parse(packageData).version || '(unknown)'
|
|
32
|
-
} catch (error) {
|
|
33
|
-
debug('Error parsing version:', error)
|
|
34
|
-
return '(error)'
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
20
|
/* ========================================================================== *
|
|
39
21
|
* TS LOADER FORCE TYPE *
|
|
40
22
|
* ========================================================================== */
|
package/package.json
CHANGED
package/src/asserts.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* BUILD FAILURES *
|
|
3
3
|
* ========================================================================== */
|
|
4
4
|
|
|
5
|
+
import { githubAnnotation } from './logging/github'
|
|
6
|
+
|
|
5
7
|
/** A symbol marking {@link BuildFailure} instances */
|
|
6
8
|
const buildFailure = Symbol.for('plugjs:buildFailure')
|
|
7
9
|
|
|
@@ -18,6 +20,11 @@ export class BuildFailure extends Error {
|
|
|
18
20
|
constructor(message?: string | undefined, errors: any[] = []) {
|
|
19
21
|
super(message || '')
|
|
20
22
|
|
|
23
|
+
// Annotate this build failure in GitHub
|
|
24
|
+
if (message) {
|
|
25
|
+
githubAnnotation({ type: 'error', title: 'Build Failure' }, message)
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
/* Basic error setup: stack and errors */
|
|
22
29
|
Error.captureStackTrace(this, BuildFailure)
|
|
23
30
|
if (errors.length) this.errors = Object.freeze([ ...errors ])
|
package/src/fork.ts
CHANGED
|
@@ -62,10 +62,10 @@ export abstract class ForkingPlug implements Plug<PlugResult> {
|
|
|
62
62
|
|
|
63
63
|
/* Get _this_ filename to spawn */
|
|
64
64
|
const script = requireFilename(__fileurl)
|
|
65
|
-
context.log.debug('About to fork plug from', $p(
|
|
65
|
+
context.log.debug('About to fork plug from', $p(this._scriptFile))
|
|
66
66
|
|
|
67
67
|
/* Environment variables */
|
|
68
|
-
const env = { ...process.env, ...logOptions.forkEnv(context.taskName) }
|
|
68
|
+
const env = { ...process.env, ...logOptions.forkEnv(context.taskName, 4) }
|
|
69
69
|
|
|
70
70
|
/* Check our args (reversed) to see if the last specifies `coverageDir` */
|
|
71
71
|
for (let i = this._arguments.length - 1; i >= 0; i --) {
|
|
@@ -80,10 +80,15 @@ export abstract class ForkingPlug implements Plug<PlugResult> {
|
|
|
80
80
|
|
|
81
81
|
/* Run our script in a _separate_ process */
|
|
82
82
|
const child = fork(script, {
|
|
83
|
-
stdio: [ 'ignore', 'inherit', 'inherit', 'ipc' ],
|
|
83
|
+
stdio: [ 'ignore', 'inherit', 'inherit', 'ipc', 'pipe' ],
|
|
84
84
|
env,
|
|
85
85
|
})
|
|
86
86
|
|
|
87
|
+
/* Pipe child logs directly to the writer */
|
|
88
|
+
if (child.stdio[4]) {
|
|
89
|
+
child.stdio[4].on('data', (data) => logOptions.output.write(data))
|
|
90
|
+
}
|
|
91
|
+
|
|
87
92
|
context.log.info('Running', $p(script), $gry(`(pid=${child.pid})`))
|
|
88
93
|
|
|
89
94
|
/* Return a promise from the child process events */
|
|
@@ -97,7 +102,7 @@ export abstract class ForkingPlug implements Plug<PlugResult> {
|
|
|
97
102
|
})
|
|
98
103
|
|
|
99
104
|
child.on('message', (message: ForkResult) => {
|
|
100
|
-
context.log.debug('Message from child process', message)
|
|
105
|
+
context.log.debug('Message from child process with PID', child.pid, message)
|
|
101
106
|
response = message
|
|
102
107
|
})
|
|
103
108
|
|
|
@@ -176,7 +181,7 @@ if ((process.argv[1] === requireFilename(__fileurl)) && (process.send)) {
|
|
|
176
181
|
|
|
177
182
|
/* First of all, our plug context */
|
|
178
183
|
const context = new Context(buildFile, taskName)
|
|
179
|
-
context.log.debug('Message from parent process', message)
|
|
184
|
+
context.log.debug('Message from parent process for PID', process.pid, message)
|
|
180
185
|
|
|
181
186
|
/* Contextualize this run, and go! */
|
|
182
187
|
const result = runAsync(context, taskName, async () => {
|
package/src/helpers.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdtempSync } from 'node:fs'
|
|
1
|
+
import { mkdtempSync, readFileSync } from 'node:fs'
|
|
2
2
|
import { tmpdir } from 'node:os'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
4
|
|
|
@@ -207,3 +207,26 @@ export function exec(
|
|
|
207
207
|
const { params, options } = parseOptions(args)
|
|
208
208
|
return execChild(cmd, params, options, requireContext())
|
|
209
209
|
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Read and parse a JSON file, throwing an error if not found.
|
|
213
|
+
*
|
|
214
|
+
* @params file The JSON file to parse
|
|
215
|
+
*/
|
|
216
|
+
export function parseJson(file: string): any {
|
|
217
|
+
const jsonFile = requireContext().resolve(file)
|
|
218
|
+
let jsonText: string
|
|
219
|
+
try {
|
|
220
|
+
jsonText = readFileSync(jsonFile, 'utf-8')
|
|
221
|
+
} catch (error: any) {
|
|
222
|
+
if (error.code === 'ENOENT') log.fail(`File ${$p(jsonFile)} not found`)
|
|
223
|
+
if (error.code === 'EACCES') log.fail(`File ${$p(jsonFile)} can not be accessed`)
|
|
224
|
+
log.fail(`Error reading ${$p(jsonFile)}`, error)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
return JSON.parse(jsonText)
|
|
229
|
+
} catch (error) {
|
|
230
|
+
log.fail(`Error parsing ${$p(jsonFile)}`, error)
|
|
231
|
+
}
|
|
232
|
+
}
|
package/src/logging/emit.ts
CHANGED
|
@@ -77,10 +77,7 @@ export const emitColor: LogEmitter = (options: LogEmitterOptions, args: any[]):
|
|
|
77
77
|
|
|
78
78
|
/* Write each individual line out */
|
|
79
79
|
for (const line of message.split('\n')) {
|
|
80
|
-
_output.write(zapSpinner)
|
|
81
|
-
_output.write(linePrefix)
|
|
82
|
-
_output.write(line)
|
|
83
|
-
_output.write('\n')
|
|
80
|
+
_output.write(`${zapSpinner}${linePrefix}${line}\n`)
|
|
84
81
|
}
|
|
85
82
|
}
|
|
86
83
|
|
|
@@ -120,8 +117,6 @@ export const emitPlain: LogEmitter = (options: LogEmitterOptions, args: any[]):
|
|
|
120
117
|
|
|
121
118
|
/* Write each individual line out */
|
|
122
119
|
for (const line of message.split('\n')) {
|
|
123
|
-
_output.write(linePrefix)
|
|
124
|
-
_output.write(line)
|
|
125
|
-
_output.write('\n')
|
|
120
|
+
_output.write(`${linePrefix}${line}\n`)
|
|
126
121
|
}
|
|
127
122
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { EOL } from 'node:os'
|
|
2
|
+
import { formatWithOptions } from 'node:util'
|
|
3
|
+
|
|
4
|
+
import { logOptions } from './options'
|
|
5
|
+
|
|
6
|
+
import type { AbsolutePath } from '../paths'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
/* Initial values, and subscribe to changes */
|
|
10
|
+
let _colors = logOptions.colors
|
|
11
|
+
let _output = logOptions.output
|
|
12
|
+
let _inspectOptions = logOptions.inspectOptions
|
|
13
|
+
let _githubAnnotations = logOptions.githubAnnotations
|
|
14
|
+
logOptions.on('changed', (options) => {
|
|
15
|
+
_colors = options.colors
|
|
16
|
+
_output = options.output
|
|
17
|
+
_githubAnnotations = options.githubAnnotations
|
|
18
|
+
_inspectOptions = { ...options.inspectOptions, breakLength: Infinity }
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
function escapeData(data: string): string {
|
|
23
|
+
return data
|
|
24
|
+
.replace(/%/g, '%25')
|
|
25
|
+
.replace(/\r/g, '%0D')
|
|
26
|
+
.replace(/\n/g, '%0A')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function escapeProp(prop: string | number): string {
|
|
30
|
+
return `${prop}`
|
|
31
|
+
.replace(/%/g, '%25')
|
|
32
|
+
.replace(/\r/g, '%0D')
|
|
33
|
+
.replace(/\n/g, '%0A')
|
|
34
|
+
.replace(/:/g, '%3A')
|
|
35
|
+
.replace(/,/g, '%2C')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type GithubAnnotationType = 'warning' | 'error'
|
|
39
|
+
|
|
40
|
+
export interface GithubAnnotationOptions {
|
|
41
|
+
type: GithubAnnotationType
|
|
42
|
+
title?: string
|
|
43
|
+
file?: AbsolutePath
|
|
44
|
+
line?: number
|
|
45
|
+
endLine?: number
|
|
46
|
+
col?: number
|
|
47
|
+
endColumn?: number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function githubAnnotation(type: GithubAnnotationType, message: string, ...args: any[]): void
|
|
51
|
+
export function githubAnnotation(options: GithubAnnotationOptions, message: string, ...args: any[]): void
|
|
52
|
+
|
|
53
|
+
export function githubAnnotation(options: GithubAnnotationType | GithubAnnotationOptions, ...args: any[]): void {
|
|
54
|
+
if (_colors || (! _githubAnnotations)) return
|
|
55
|
+
|
|
56
|
+
if (typeof options === 'string') options = { type: options }
|
|
57
|
+
const { type, ...parameters } = options
|
|
58
|
+
|
|
59
|
+
const attributes = Object.entries(parameters)
|
|
60
|
+
.filter(([ key, value ]) => !!(key && value))
|
|
61
|
+
.map(([ key, value ]) => `${key}=${escapeProp(value)}`)
|
|
62
|
+
.join(',')
|
|
63
|
+
|
|
64
|
+
const msg = escapeData(formatWithOptions(_inspectOptions, ...args))
|
|
65
|
+
|
|
66
|
+
if (attributes) {
|
|
67
|
+
_output.write(`::${type} ${attributes}::${msg}${EOL}`)
|
|
68
|
+
} else {
|
|
69
|
+
_output.write(`::${type}::${msg}${EOL}`)
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/logging/options.ts
CHANGED
|
@@ -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
|
-
/**
|
|
45
|
-
|
|
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
|
-
|
|
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,
|
package/src/logging/report.ts
CHANGED
|
@@ -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 =
|
|
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):
|
|
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
|
|
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
|
-
/*
|
|
435
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|