@plugjs/plug 0.0.16 → 0.0.17
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/dist/assert.cjs +18 -38
- package/dist/assert.cjs.map +1 -1
- package/dist/assert.d.ts +2 -16
- package/dist/assert.mjs +17 -32
- package/dist/assert.mjs.map +1 -1
- package/dist/async.cjs +2 -2
- package/dist/async.cjs.map +1 -1
- package/dist/async.d.ts +1 -1
- package/dist/async.mjs +2 -2
- package/dist/async.mjs.map +1 -1
- package/dist/build.cjs +20 -41
- package/dist/build.cjs.map +2 -2
- package/dist/build.d.ts +1 -1
- package/dist/build.mjs +22 -43
- package/dist/build.mjs.map +2 -2
- package/dist/failure.cjs +48 -0
- package/dist/failure.cjs.map +6 -0
- package/dist/failure.d.ts +13 -0
- package/dist/failure.mjs +22 -0
- package/dist/failure.mjs.map +6 -0
- package/dist/fork.cjs +21 -10
- package/dist/fork.cjs.map +1 -1
- package/dist/fork.d.ts +19 -1
- package/dist/fork.mjs +21 -11
- package/dist/fork.mjs.map +1 -1
- package/dist/helpers.cjs +2 -2
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.mjs +2 -2
- package/dist/helpers.mjs.map +1 -1
- package/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +3 -0
- package/dist/index.mjs.map +1 -1
- package/dist/log/logger.cjs +15 -12
- package/dist/log/logger.cjs.map +1 -1
- package/dist/log/logger.d.ts +2 -0
- package/dist/log/logger.mjs +15 -12
- package/dist/log/logger.mjs.map +1 -1
- package/dist/log/report.cjs +7 -4
- package/dist/log/report.cjs.map +1 -1
- package/dist/log/report.mjs +7 -4
- package/dist/log/report.mjs.map +1 -1
- package/dist/log.cjs +3 -0
- package/dist/log.cjs.map +1 -1
- package/dist/log.mjs +3 -0
- package/dist/log.mjs.map +1 -1
- package/dist/pipe.cjs +40 -34
- package/dist/pipe.cjs.map +1 -1
- package/dist/pipe.d.ts +10 -25
- package/dist/pipe.mjs +39 -33
- package/dist/pipe.mjs.map +1 -1
- package/dist/plugs/copy.mjs.map +1 -1
- package/dist/plugs/coverage.cjs +2 -3
- package/dist/plugs/coverage.cjs.map +1 -1
- package/dist/plugs/coverage.mjs +2 -3
- package/dist/plugs/coverage.mjs.map +1 -1
- package/dist/plugs/debug.mjs.map +1 -1
- package/dist/plugs/esbuild.mjs.map +1 -1
- package/dist/plugs/eslint/runner.cjs +2 -1
- package/dist/plugs/eslint/runner.cjs.map +1 -1
- package/dist/plugs/eslint/runner.d.ts +1 -1
- package/dist/plugs/eslint/runner.mjs +3 -2
- package/dist/plugs/eslint/runner.mjs.map +1 -1
- package/dist/plugs/eslint.cjs +2 -2
- package/dist/plugs/eslint.cjs.map +1 -1
- package/dist/plugs/eslint.mjs +1 -1
- package/dist/plugs/exec.cjs +1 -1
- package/dist/plugs/exec.cjs.map +1 -1
- package/dist/plugs/exec.mjs +1 -1
- package/dist/plugs/exec.mjs.map +1 -1
- package/dist/plugs/mocha/runner.cjs +2 -2
- package/dist/plugs/mocha/runner.cjs.map +1 -1
- package/dist/plugs/mocha/runner.d.ts +1 -1
- package/dist/plugs/mocha/runner.mjs +2 -2
- package/dist/plugs/mocha/runner.mjs.map +1 -1
- package/dist/plugs/mocha.cjs +2 -2
- package/dist/plugs/mocha.cjs.map +1 -1
- package/dist/plugs/mocha.mjs +1 -1
- package/dist/plugs/rmf.mjs.map +1 -1
- package/dist/plugs/tsc/report.cjs.map +1 -1
- package/dist/plugs/tsc/report.mjs.map +1 -1
- package/dist/plugs/tsc/runner.cjs +3 -6
- package/dist/plugs/tsc/runner.cjs.map +2 -2
- package/dist/plugs/tsc/runner.mjs +4 -7
- package/dist/plugs/tsc/runner.mjs.map +2 -2
- package/dist/plugs/tsc.cjs +2 -2
- package/dist/plugs/tsc.cjs.map +1 -1
- package/dist/plugs/tsc.mjs +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/utils/asyncfs.cjs.map +1 -1
- package/dist/utils/asyncfs.mjs.map +1 -1
- package/extra/cli.mjs +1 -1
- package/package.json +14 -12
- package/src/assert.ts +20 -34
- package/src/async.ts +3 -3
- package/src/failure.ts +31 -0
- package/src/fork.ts +43 -10
- package/src/helpers.ts +2 -2
- package/src/index.ts +3 -2
- package/src/log/logger.ts +22 -16
- package/src/log/report.ts +7 -3
- package/src/log.ts +4 -0
- package/src/pipe.ts +90 -59
- package/src/plugs/copy.ts +1 -1
- package/src/plugs/coverage/report.ts +1 -1
- package/src/plugs/coverage.ts +2 -3
- package/src/plugs/debug.ts +1 -1
- package/src/plugs/esbuild.ts +1 -1
- package/src/plugs/eslint/runner.ts +4 -3
- package/src/plugs/eslint.ts +1 -1
- package/src/plugs/exec.ts +1 -1
- package/src/plugs/mocha/runner.ts +3 -3
- package/src/plugs/mocha.ts +1 -1
- package/src/plugs/rmf.ts +1 -1
- package/src/plugs/tsc/runner.ts +5 -8
- package/src/plugs/tsc.ts +1 -1
- package/src/types.ts +3 -3
- package/src/utils/asyncfs.ts +1 -0
package/src/assert.ts
CHANGED
|
@@ -2,46 +2,32 @@
|
|
|
2
2
|
* BUILD FAILURES *
|
|
3
3
|
* ========================================================================== */
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
const buildFailure = Symbol.for('plugjs:buildFailure')
|
|
5
|
+
import { BuildFailure } from './failure'
|
|
7
6
|
|
|
8
|
-
/**
|
|
9
|
-
export function
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
/** Await and assert that all specified promises were fulfilled */
|
|
8
|
+
export async function assertPromises<T>(promises: (T | Promise<T>)[], message: string): Promise<T[]> {
|
|
9
|
+
// Await for the settlement of all the promises
|
|
10
|
+
const settlements = await Promise.allSettled(promises)
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
12
|
+
// Separate the good from the bad...
|
|
13
|
+
const results: T[] = []
|
|
14
|
+
const failures = new Set<any>()
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
16
|
+
settlements.forEach((settlement) => {
|
|
17
|
+
if (settlement.status === 'fulfilled') {
|
|
18
|
+
results.push(settlement.value)
|
|
19
|
+
} else {
|
|
20
|
+
failures.add(settlement.reason)
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
// Check for errors and report/fail if anything happened
|
|
25
|
+
if (failures.size) throw new BuildFailure(message, [ ...failures ])
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
export class BuildFailure extends Error {
|
|
28
|
-
constructor() {
|
|
29
|
-
super('Build Failure')
|
|
30
|
-
Object.defineProperty(this, buildFailure, { value: buildFailure })
|
|
31
|
-
}
|
|
27
|
+
return results
|
|
32
28
|
}
|
|
33
29
|
|
|
34
30
|
/** Asserts something as _truthy_ and fail the build if not */
|
|
35
31
|
export function assert(assertion: any, message: string): asserts assertion {
|
|
36
|
-
if (! assertion) throw new
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Throw a {@link BuildError} (an {@link Error} printed nicely) */
|
|
40
|
-
export function fail(message: string): never {
|
|
41
|
-
throw new BuildError(message)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Return a non-logged {@link BuildFailure} */
|
|
45
|
-
export function failure(): BuildFailure {
|
|
46
|
-
return new BuildFailure()
|
|
32
|
+
if (! assertion) throw new BuildFailure(message)
|
|
47
33
|
}
|
package/src/async.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import type { Context } from './pipe'
|
|
2
|
+
|
|
1
3
|
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
2
|
-
import { assert } from './assert'
|
|
3
|
-
import { Context } from './pipe'
|
|
4
4
|
|
|
5
5
|
/* ========================================================================== *
|
|
6
6
|
* EXPORTED *
|
|
@@ -39,7 +39,7 @@ export function currentContext(): Context | undefined {
|
|
|
39
39
|
*/
|
|
40
40
|
export function requireContext(): Context {
|
|
41
41
|
const context = storage.getStore()
|
|
42
|
-
|
|
42
|
+
if (! context) throw new Error('Unable to retrieve current context')
|
|
43
43
|
return context
|
|
44
44
|
}
|
|
45
45
|
|
package/src/failure.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** A symbol marking {@link BuildFailure} instances */
|
|
2
|
+
const buildFailure = Symbol.for('plugjs:buildFailure')
|
|
3
|
+
|
|
4
|
+
/** Check if the specified argument is a {@link BuildFailure} */
|
|
5
|
+
export function isBuildFailure(arg: any): arg is BuildFailure {
|
|
6
|
+
return arg && arg[buildFailure] === buildFailure
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** A {@link BuildFailure} represents an error _already logged_ in our build. */
|
|
10
|
+
export class BuildFailure extends Error {
|
|
11
|
+
readonly errors: readonly any[]
|
|
12
|
+
logged: boolean
|
|
13
|
+
|
|
14
|
+
/** Construct a {@link BuildFailure} that was already _logged_ (internal) */
|
|
15
|
+
constructor(options: { logged: true })
|
|
16
|
+
/** Construct a {@link BuildFailure} with a detail message */
|
|
17
|
+
constructor(message: string, errors?: any[])
|
|
18
|
+
// Constructor overload implementation
|
|
19
|
+
constructor(options: string | { logged: boolean } = 'Build Failure', errors: any[] = []) {
|
|
20
|
+
const { logged, message } =
|
|
21
|
+
typeof options === 'string' ?
|
|
22
|
+
{ message: options, logged: false } :
|
|
23
|
+
{ message: 'Build Failure', ...options }
|
|
24
|
+
|
|
25
|
+
super(message)
|
|
26
|
+
Error.captureStackTrace(this, BuildFailure)
|
|
27
|
+
Object.defineProperty(this, 'buildFailure', { value: buildFailure })
|
|
28
|
+
this.errors = errors.filter((e) => ! (isBuildFailure(e) && e.logged))
|
|
29
|
+
this.logged = logged
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/fork.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { fork } from 'node:child_process'
|
|
2
|
-
import { assert
|
|
2
|
+
import { assert } from './assert'
|
|
3
3
|
import { runAsync } from './async'
|
|
4
|
+
import { BuildFailure } from './failure'
|
|
4
5
|
import { Files } from './files'
|
|
5
6
|
import { $gry, $p, LogOptions, logOptions } from './log'
|
|
6
7
|
import { AbsolutePath, requireFilename, resolveFile } from './paths'
|
|
7
|
-
import { Plug,
|
|
8
|
+
import { Context, install, Plug, PlugName, PlugResult } from './pipe'
|
|
8
9
|
|
|
9
10
|
/** Fork data, from parent to child process */
|
|
10
11
|
export interface ForkData {
|
|
@@ -88,7 +89,7 @@ export abstract class ForkingPlug implements Plug<PlugResult> {
|
|
|
88
89
|
|
|
89
90
|
child.on('error', (error) => {
|
|
90
91
|
context.log.error('Child process error', error)
|
|
91
|
-
return done || reject(
|
|
92
|
+
return done || reject(new BuildFailure({ logged: true }))
|
|
92
93
|
})
|
|
93
94
|
|
|
94
95
|
child.on('message', (message: ForkResult) => {
|
|
@@ -99,16 +100,16 @@ export abstract class ForkingPlug implements Plug<PlugResult> {
|
|
|
99
100
|
child.on('exit', (code, signal) => {
|
|
100
101
|
if (signal) {
|
|
101
102
|
context.log.error(`Child process exited with signal ${signal}`, $gry(`(pid=${child.pid})`))
|
|
102
|
-
return done || reject(
|
|
103
|
+
return done || reject(new BuildFailure({ logged: true }))
|
|
103
104
|
} else if (code !== 0) {
|
|
104
105
|
context.log.error(`Child process exited with code ${code}`, $gry(`(pid=${child.pid})`))
|
|
105
|
-
return done || reject(
|
|
106
|
+
return done || reject(new BuildFailure({ logged: true }))
|
|
106
107
|
} else if (! result) {
|
|
107
108
|
context.log.error('Child process exited with no result', $gry(`(pid=${child.pid})`))
|
|
108
|
-
return done || reject(
|
|
109
|
+
return done || reject(new BuildFailure({ logged: true }))
|
|
109
110
|
} else if (result.failed) {
|
|
110
111
|
// definitely logged on the child side
|
|
111
|
-
return done || reject(
|
|
112
|
+
return done || reject(new BuildFailure({ logged: true }))
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
/* We definitely have a successful result! */
|
|
@@ -122,16 +123,16 @@ export abstract class ForkingPlug implements Plug<PlugResult> {
|
|
|
122
123
|
const result = child.send(message, (error) => {
|
|
123
124
|
if (error) {
|
|
124
125
|
context.log.error('Error sending message to child process (callback failure)', error)
|
|
125
|
-
reject(
|
|
126
|
+
reject(new BuildFailure({ logged: true }))
|
|
126
127
|
}
|
|
127
128
|
})
|
|
128
129
|
if (! result) {
|
|
129
130
|
context.log.error('Error sending message to child process (send returned false)')
|
|
130
|
-
reject(
|
|
131
|
+
reject(new BuildFailure({ logged: true }))
|
|
131
132
|
}
|
|
132
133
|
} catch (error) {
|
|
133
134
|
context.log.error('Error sending message to child process (exception caught)', error)
|
|
134
|
-
reject(
|
|
135
|
+
reject(new BuildFailure({ logged: true }))
|
|
135
136
|
}
|
|
136
137
|
}).finally(() => done = true)
|
|
137
138
|
}
|
|
@@ -223,3 +224,35 @@ if ((process.argv[1] === requireFilename(__fileurl)) && (process.send)) {
|
|
|
223
224
|
})
|
|
224
225
|
})
|
|
225
226
|
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Install a _forking_ {@link Plug} in the {@link Pipe}, in other words
|
|
230
|
+
* execute the plug in a separate process.
|
|
231
|
+
*
|
|
232
|
+
* As a contract, if the _last non-null_ parameter of the constructor is an
|
|
233
|
+
* object and contains the key `coverageDir`, the process will be forked with
|
|
234
|
+
* the approptiately resolved `NODE_V8_COVERAGE` environment variable.
|
|
235
|
+
*
|
|
236
|
+
* Also, forking plugs require some special attention:
|
|
237
|
+
*
|
|
238
|
+
* * plug functions are not supported, only classes implementing the
|
|
239
|
+
* {@link Plug} interface can be used with this.
|
|
240
|
+
*
|
|
241
|
+
* * the class itself _MUST_ be exported as the _default_ export for the
|
|
242
|
+
* `scriptFile` specified below. This is to simplify interoperability between
|
|
243
|
+
* CommonJS and ESM modules as we use dynamic `import(...)` statements.
|
|
244
|
+
*/
|
|
245
|
+
export function installForking<Name extends PlugName>(
|
|
246
|
+
plugName: Name,
|
|
247
|
+
scriptFile: AbsolutePath,
|
|
248
|
+
): void {
|
|
249
|
+
/** Extend out our ForkingPlug below */
|
|
250
|
+
const ctor = class extends ForkingPlug {
|
|
251
|
+
constructor(...args: any[]) {
|
|
252
|
+
super(scriptFile, args)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Install the plug in */
|
|
257
|
+
install(plugName, ctor as any)
|
|
258
|
+
}
|
package/src/helpers.ts
CHANGED
|
@@ -31,7 +31,7 @@ export function find(...args: ParseOptions<FindOptions>): Pipe {
|
|
|
31
31
|
const { params: globs, options } = parseOptions(args, {})
|
|
32
32
|
|
|
33
33
|
const context = requireContext()
|
|
34
|
-
return new Pipe(context, async ()
|
|
34
|
+
return new Pipe(context, Promise.resolve().then(async () => {
|
|
35
35
|
const directory = options.directory ?
|
|
36
36
|
context.resolve(options.directory) :
|
|
37
37
|
getCurrentWorkingDirectory()
|
|
@@ -42,7 +42,7 @@ export function find(...args: ParseOptions<FindOptions>): Pipe {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
return builder.build()
|
|
45
|
-
})
|
|
45
|
+
}))
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/**
|
package/src/index.ts
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
export type { AbsolutePath } from './paths'
|
|
4
4
|
export type { Files, FilesBuilder } from './files'
|
|
5
|
+
|
|
6
|
+
export { BuildFailure } from './failure'
|
|
5
7
|
export { Pipe } from './pipe'
|
|
6
8
|
|
|
7
9
|
// Our minimal exports
|
|
8
10
|
export * from './assert'
|
|
9
11
|
export * from './build'
|
|
12
|
+
export * from './fork'
|
|
10
13
|
export * from './helpers'
|
|
11
14
|
export * from './log'
|
|
12
|
-
|
|
13
|
-
// PlugJS types
|
|
14
15
|
export * from './plugs'
|
|
15
16
|
export * from './types'
|
package/src/log/logger.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BuildFailure, isBuildFailure } from '../failure'
|
|
2
2
|
import { emitColor, emitPlain, LogEmitter } from './emit'
|
|
3
3
|
import { DEBUG, ERROR, INFO, LogLevel, NOTICE, TRACE, WARN } from './levels'
|
|
4
4
|
import { logOptions } from './options'
|
|
@@ -34,6 +34,8 @@ export interface Log {
|
|
|
34
34
|
warn(...args: [ any, ...any ]): this
|
|
35
35
|
/** Log an `ERROR` message */
|
|
36
36
|
error(...args: [ any, ...any ]): this
|
|
37
|
+
/** Log an `ERROR` message and fail the build */
|
|
38
|
+
fail(...args: [ any, ...any ]): never
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
/** A {@link Logger} extends the basic {@link Log} adding some state. */
|
|
@@ -83,9 +85,18 @@ class LoggerImpl implements Logger {
|
|
|
83
85
|
private _emit(level: LogLevel, args: [ any, ...any ]): this {
|
|
84
86
|
if (this._level > level) return this
|
|
85
87
|
|
|
86
|
-
// Filter out build failures
|
|
87
|
-
const params = args.filter((arg) =>
|
|
88
|
-
|
|
88
|
+
// Filter out build failures that were already logged
|
|
89
|
+
const params = args.filter((arg) => {
|
|
90
|
+
if (isBuildFailure(arg)) {
|
|
91
|
+
if (arg.logged) return false // already logged? skip!
|
|
92
|
+
return arg.logged = true // set logged to true and log!
|
|
93
|
+
} else {
|
|
94
|
+
return true
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// If there's nothing left to log, then we're done
|
|
99
|
+
if (params.length === 0) return this
|
|
89
100
|
|
|
90
101
|
// Prepare our options for logging
|
|
91
102
|
const options = { level, taskName: this._task, indent: this._indent }
|
|
@@ -98,18 +109,8 @@ class LoggerImpl implements Logger {
|
|
|
98
109
|
this._stack.splice(0)
|
|
99
110
|
}
|
|
100
111
|
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
if (isBuildError(arg)) {
|
|
104
|
-
this._emitter({ ...options, level: ERROR }, [ arg.message ])
|
|
105
|
-
return false
|
|
106
|
-
} else {
|
|
107
|
-
return true
|
|
108
|
-
}
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
// If we have any leftovers, dump them out, too!
|
|
112
|
-
if (remaining) this._emitter(options, remaining)
|
|
112
|
+
// Emit our log lines and return
|
|
113
|
+
this._emitter(options, params)
|
|
113
114
|
return this
|
|
114
115
|
}
|
|
115
116
|
|
|
@@ -145,6 +146,11 @@ class LoggerImpl implements Logger {
|
|
|
145
146
|
return this._emit(ERROR, args)
|
|
146
147
|
}
|
|
147
148
|
|
|
149
|
+
fail(...args: [ any, ...any ]): never {
|
|
150
|
+
this._emit(ERROR, args)
|
|
151
|
+
throw new BuildFailure({ logged: true })
|
|
152
|
+
}
|
|
153
|
+
|
|
148
154
|
enter(): this
|
|
149
155
|
enter(level: LogLevel, message: string): this
|
|
150
156
|
enter(...args: [] | [ level: LogLevel, message: string ]): this {
|
package/src/log/report.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BuildFailure } from '../failure'
|
|
2
2
|
import { AbsolutePath } from '../paths'
|
|
3
3
|
import { readFile } from '../utils/asyncfs'
|
|
4
4
|
import { $blu, $cyn, $gry, $red, $und, $wht, $ylw } from './colors'
|
|
@@ -191,7 +191,11 @@ export class ReportImpl implements Report {
|
|
|
191
191
|
[ ...record.message.map((msg) => msg.split('\n')).flat(1) ] :
|
|
192
192
|
record.message.split('\n')
|
|
193
193
|
messages = messages.filter((message) => !! message)
|
|
194
|
-
if (! messages.length)
|
|
194
|
+
if (! messages.length) {
|
|
195
|
+
const options = { taskName: this._task, level: ERROR }
|
|
196
|
+
this._emitter(options, [ 'No message for report record' ])
|
|
197
|
+
throw new BuildFailure({ logged: true })
|
|
198
|
+
}
|
|
195
199
|
|
|
196
200
|
const level = record.level
|
|
197
201
|
const file = record.file
|
|
@@ -263,7 +267,7 @@ export class ReportImpl implements Report {
|
|
|
263
267
|
done(showSources?: boolean | undefined): void {
|
|
264
268
|
if (showSources == null) showSources = logOptions.showSources
|
|
265
269
|
if (! this.empty) this._emit(showSources)
|
|
266
|
-
if (this.errors) throw
|
|
270
|
+
if (this.errors) throw new BuildFailure({ logged: true })
|
|
267
271
|
}
|
|
268
272
|
|
|
269
273
|
private _emit(showSources: boolean): this {
|
package/src/log.ts
CHANGED
|
@@ -58,6 +58,10 @@ export const log: LogFunction = ((): LogFunction => {
|
|
|
58
58
|
logger().error(...args)
|
|
59
59
|
return wrapper
|
|
60
60
|
},
|
|
61
|
+
|
|
62
|
+
fail(...args: [ any, ...any ]): never {
|
|
63
|
+
throw logger().fail(...args) // fail() returns never but ?!?!?!?!?
|
|
64
|
+
},
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
/* Create a function that will default logging to "NOTICE" */
|
package/src/pipe.ts
CHANGED
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
} from './paths'
|
|
8
8
|
|
|
9
9
|
import { sep } from 'path'
|
|
10
|
-
import { assert } from './assert'
|
|
10
|
+
import { assert, assertPromises } from './assert'
|
|
11
11
|
import { requireContext } from './async'
|
|
12
12
|
import { Files } from './files'
|
|
13
|
-
import { ForkingPlug } from './fork'
|
|
14
13
|
import { getLogger, Logger } from './log'
|
|
14
|
+
import { Result } from './types'
|
|
15
15
|
|
|
16
16
|
/* ========================================================================== *
|
|
17
17
|
* PLUGS *
|
|
@@ -86,6 +86,25 @@ export class Context {
|
|
|
86
86
|
* PIPES *
|
|
87
87
|
* ========================================================================== */
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* In pipe chains, we want to keep track of the _leaf_ `Files` promises (that
|
|
91
|
+
* is, when a derived pipe is created calling `plug` we want to track only the
|
|
92
|
+
* new, derived, promise).
|
|
93
|
+
*
|
|
94
|
+
* We key these _leaf_ promises by _context_ (with a WeakMap), and those will
|
|
95
|
+
* be awaited at the end of the task.
|
|
96
|
+
*/
|
|
97
|
+
const contextPromises = new WeakMap<Context, Set<Promise<Result>>>()
|
|
98
|
+
|
|
99
|
+
export function getContextPromises(context: Context): Set<Promise<Result>> {
|
|
100
|
+
let promises = contextPromises.get(context)
|
|
101
|
+
if (! promises) {
|
|
102
|
+
promises = new Set<Promise<Files>>()
|
|
103
|
+
contextPromises.set(context, promises)
|
|
104
|
+
}
|
|
105
|
+
return promises
|
|
106
|
+
}
|
|
107
|
+
|
|
89
108
|
/**
|
|
90
109
|
* A class that will be extended by {@link Pipe} where {@link install} will
|
|
91
110
|
* add prototype properties from installed {@link Plug}s
|
|
@@ -98,14 +117,63 @@ abstract class PipeProto {
|
|
|
98
117
|
* The {@link Pipe} class defines processing pipeline where multiple
|
|
99
118
|
* {@link Plug}s can transform lists of {@link Files}.
|
|
100
119
|
*/
|
|
101
|
-
export class Pipe extends PipeProto {
|
|
120
|
+
export class Pipe extends PipeProto implements Promise<Files> {
|
|
121
|
+
readonly [Symbol.toStringTag] = 'Pipe'
|
|
122
|
+
|
|
102
123
|
constructor(
|
|
103
124
|
private readonly _context: Context,
|
|
104
|
-
private readonly
|
|
125
|
+
private readonly _promise: Promise<Result>,
|
|
105
126
|
) {
|
|
106
127
|
super()
|
|
128
|
+
|
|
129
|
+
// New "Pipe", remember the promise!
|
|
130
|
+
getContextPromises(_context).add(_promise)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* ------------------------------------------------------------------------ *
|
|
134
|
+
* Promise implementation *
|
|
135
|
+
* ------------------------------------------------------------------------ *
|
|
136
|
+
* From a _types_ point of view, the `Pipe` implements a `Promise<Files>` *
|
|
137
|
+
* (because only when plugging the correct `Plug` the correct value are *
|
|
138
|
+
* returned). *
|
|
139
|
+
* *
|
|
140
|
+
* Whether to return (as a type) another `Pipe` or a `Promise<undefined>` *
|
|
141
|
+
* is determined by the type of the `plug` parameter below. *
|
|
142
|
+
* *
|
|
143
|
+
* That said, in practice, a `Pipe` implements `Promise<Files | undefined>` *
|
|
144
|
+
* because the result of the plug is _eventually_ computed asynchronously *
|
|
145
|
+
* while `plug` returns immediately.
|
|
146
|
+
* *
|
|
147
|
+
* So, all those "as whatever" below are kind-of-legit... *
|
|
148
|
+
* ------------------------------------------------------------------------ */
|
|
149
|
+
|
|
150
|
+
then<R1 = Files, R2 = never>(
|
|
151
|
+
onfulfilled?: ((value: Files) => R1 | PromiseLike<R1>) | null | undefined,
|
|
152
|
+
onrejected?: ((reason: any) => R2 | PromiseLike<R2>) | null | undefined,
|
|
153
|
+
): Promise<R1 | R2> {
|
|
154
|
+
// We are delegating the handling of this promise to the caller
|
|
155
|
+
getContextPromises(this._context).delete(this._promise)
|
|
156
|
+
return this._promise.then(onfulfilled as (value: Result) => R1 | PromiseLike<R1>, onrejected)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
catch<R = never>(
|
|
160
|
+
onrejected?: ((reason: any) => R | PromiseLike<R>) | null | undefined,
|
|
161
|
+
): Promise<Files | R> {
|
|
162
|
+
// We are delegating the handling of this promise to the caller
|
|
163
|
+
getContextPromises(this._context).delete(this._promise)
|
|
164
|
+
return this._promise.catch(onrejected) as Promise<Files | R>
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
finally(onfinally?: (() => void) | null | undefined): Promise<Files> {
|
|
168
|
+
// We are delegating the handling of this promise to the caller
|
|
169
|
+
getContextPromises(this._context).delete(this._promise)
|
|
170
|
+
return this._promise.finally(onfinally) as Promise<Files>
|
|
107
171
|
}
|
|
108
172
|
|
|
173
|
+
/* ------------------------------------------------------------------------ *
|
|
174
|
+
* Pipe implementation *
|
|
175
|
+
* ------------------------------------------------------------------------ */
|
|
176
|
+
|
|
109
177
|
plug(plug: Plug<Files>): Pipe
|
|
110
178
|
plug(plug: PlugFunction<Files>): Pipe
|
|
111
179
|
plug(plug: Plug<void | undefined>): Promise<undefined>
|
|
@@ -113,18 +181,15 @@ export class Pipe extends PipeProto {
|
|
|
113
181
|
plug(arg: Plug<PlugResult> | PlugFunction<PlugResult>): Pipe | Promise<undefined> {
|
|
114
182
|
const plug = typeof arg === 'function' ? { pipe: arg } : arg
|
|
115
183
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return new Pipe(context, async (): Promise<Files> => {
|
|
119
|
-
const previous = await parent.run()
|
|
120
|
-
const current = await plug.pipe(previous, context)
|
|
121
|
-
assert(current, 'Unable to extend pipe')
|
|
122
|
-
return current
|
|
123
|
-
})
|
|
124
|
-
}
|
|
184
|
+
// We are creating a new "leaf" Pipe, we can forget our promise
|
|
185
|
+
getContextPromises(this._context).delete(this._promise)
|
|
125
186
|
|
|
126
|
-
|
|
127
|
-
return this.
|
|
187
|
+
// Create and return the new Pipe
|
|
188
|
+
return new Pipe(this._context, this._promise.then(async (result) => {
|
|
189
|
+
assert(result, 'Unable to extend pipe')
|
|
190
|
+
const result2 = await plug.pipe(result, this._context)
|
|
191
|
+
return result2 || undefined
|
|
192
|
+
}))
|
|
128
193
|
}
|
|
129
194
|
|
|
130
195
|
/**
|
|
@@ -137,8 +202,6 @@ export class Pipe extends PipeProto {
|
|
|
137
202
|
*
|
|
138
203
|
* ```
|
|
139
204
|
* const pipe: Pipe = merge([
|
|
140
|
-
* // other tasks return `Pipe & Promise<Files>` so we can
|
|
141
|
-
* // direcrly await their result without invoking `run()`
|
|
142
205
|
* await this.anotherTask1(),
|
|
143
206
|
* await this.anotherTask2(),
|
|
144
207
|
* ])
|
|
@@ -146,20 +209,20 @@ export class Pipe extends PipeProto {
|
|
|
146
209
|
*/
|
|
147
210
|
static merge(pipes: (Pipe | Files | Promise<Files>)[]): Pipe {
|
|
148
211
|
const context = requireContext()
|
|
149
|
-
return new Pipe(context, async ()
|
|
212
|
+
return new Pipe(context, Promise.resolve().then(async () => {
|
|
213
|
+
// No pipes? Just send off an empty pipe...
|
|
150
214
|
if (pipes.length === 0) return Files.builder(getCurrentWorkingDirectory()).build()
|
|
151
215
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}))
|
|
155
|
-
|
|
156
|
-
const firstDir = first.directory
|
|
157
|
-
const otherDirs = other.map((f) => f.directory)
|
|
216
|
+
// Await for all pipes / files / files promises
|
|
217
|
+
const results = await assertPromises<Files>(pipes, 'Error in pipe while merging')
|
|
158
218
|
|
|
219
|
+
// Find the common directory between all the Files instances
|
|
220
|
+
const [ firstDir, ...otherDirs ] = results.map((f) => f.directory)
|
|
159
221
|
const directory = commonPath(firstDir, ...otherDirs)
|
|
160
222
|
|
|
161
|
-
|
|
162
|
-
|
|
223
|
+
// Build our new files instance merging all the results
|
|
224
|
+
return Files.builder(directory).merge(...results).build()
|
|
225
|
+
}))
|
|
163
226
|
}
|
|
164
227
|
}
|
|
165
228
|
|
|
@@ -168,7 +231,7 @@ export class Pipe extends PipeProto {
|
|
|
168
231
|
* ========================================================================== */
|
|
169
232
|
|
|
170
233
|
/** The names which can be installed as direct plugs. */
|
|
171
|
-
export type PlugName = string & Exclude<keyof Pipe, 'plug' |
|
|
234
|
+
export type PlugName = string & Exclude<keyof Pipe, 'plug' | keyof Promise<any>>
|
|
172
235
|
|
|
173
236
|
/** The parameters of the plug extension with the given name */
|
|
174
237
|
export type PipeParameters<Name extends PlugName> = PipeOverloads<Name>['args']
|
|
@@ -282,35 +345,3 @@ export function install<
|
|
|
282
345
|
/* Inject the create function in the Pipe's prototype */
|
|
283
346
|
Object.defineProperty(PipeProto.prototype, name, { value: plug })
|
|
284
347
|
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Install a _forking_ {@link Plug} in the {@link Pipe}, in other words
|
|
288
|
-
* execute the plug in a separate process.
|
|
289
|
-
*
|
|
290
|
-
* As a contract, if the _last non-null_ parameter of the constructor is an
|
|
291
|
-
* object and contains the key `coverageDir`, the process will be forked with
|
|
292
|
-
* the approptiately resolved `NODE_V8_COVERAGE` environment variable.
|
|
293
|
-
*
|
|
294
|
-
* Also, forking plugs require some special attention:
|
|
295
|
-
*
|
|
296
|
-
* * plug functions are not supported, only classes implementing the
|
|
297
|
-
* {@link Plug} interface can be used with this.
|
|
298
|
-
*
|
|
299
|
-
* * the class itself _MUST_ be exported as the _default_ export for the
|
|
300
|
-
* `scriptFile` specified below. This is to simplify interoperability between
|
|
301
|
-
* CommonJS and ESM modules as we use dynamic `import(...)` statements.
|
|
302
|
-
*/
|
|
303
|
-
export function installForking<Name extends PlugName>(
|
|
304
|
-
plugName: Name,
|
|
305
|
-
scriptFile: AbsolutePath,
|
|
306
|
-
): void {
|
|
307
|
-
/** Extend out our ForkingPlug below */
|
|
308
|
-
const ctor = class extends ForkingPlug {
|
|
309
|
-
constructor(...args: any[]) {
|
|
310
|
-
super(scriptFile, args)
|
|
311
|
-
}
|
|
312
|
-
} as unknown as PlugConstructor<Name>
|
|
313
|
-
|
|
314
|
-
/** Install the plug in */
|
|
315
|
-
install(plugName, ctor)
|
|
316
|
-
}
|
package/src/plugs/copy.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { assert } from '../assert'
|
|
|
2
2
|
import { Files } from '../files'
|
|
3
3
|
import { $p } from '../log'
|
|
4
4
|
import { assertAbsolutePath, getAbsoluteParent, resolveAbsolutePath } from '../paths'
|
|
5
|
-
import { install, PipeParameters, Plug
|
|
5
|
+
import { Context, install, PipeParameters, Plug } from '../pipe'
|
|
6
6
|
import { chmod, copyFile, fsConstants, mkdir } from '../utils/asyncfs'
|
|
7
7
|
|
|
8
8
|
/** Options for copying files */
|
package/src/plugs/coverage.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { html, initFunction } from '@plugjs/cov8-html'
|
|
2
2
|
import { sep } from 'node:path'
|
|
3
|
-
import { fail } from '../assert'
|
|
4
3
|
import { Files } from '../files'
|
|
5
4
|
import { $gry, $ms, $p, $red, $ylw, ERROR, NOTICE, WARN } from '../log'
|
|
6
5
|
import { AbsolutePath, resolveAbsolutePath } from '../paths'
|
|
7
|
-
import { install, PipeParameters, Plug
|
|
6
|
+
import { Context, install, PipeParameters, Plug } from '../pipe'
|
|
8
7
|
import { walk } from '../utils/walk'
|
|
9
8
|
import { createAnalyser, SourceMapBias } from './coverage/analysis'
|
|
10
9
|
import { coverageReport, CoverageResult } from './coverage/report'
|
|
@@ -79,7 +78,7 @@ install('coverage', class Coverage implements Plug<Files | undefined> {
|
|
|
79
78
|
}
|
|
80
79
|
|
|
81
80
|
if (coverageFiles.length === 0) {
|
|
82
|
-
fail(`No coverage files found in ${$p(coverageDir)}`)
|
|
81
|
+
throw context.log.fail(`No coverage files found in ${$p(coverageDir)}`)
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
const sourceFiles = [ ...files.absolutePaths() ]
|
package/src/plugs/debug.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Files } from '../files'
|
|
2
2
|
import { $gry, $p, $und, $ylw } from '../log'
|
|
3
|
-
import {
|
|
3
|
+
import { Context, install, PipeParameters, Plug } from '../pipe'
|
|
4
4
|
|
|
5
5
|
declare module '../pipe' {
|
|
6
6
|
export interface Pipe {
|
package/src/plugs/esbuild.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { assert } from '../assert'
|
|
|
4
4
|
import { Files, FilesBuilder } from '../files'
|
|
5
5
|
import { $p, ERROR, Logger, ReportLevel, ReportRecord, WARN } from '../log'
|
|
6
6
|
import { AbsolutePath, getAbsoluteParent, resolveAbsolutePath } from '../paths'
|
|
7
|
-
import { install, PipeParameters, Plug
|
|
7
|
+
import { Context, install, PipeParameters, Plug } from '../pipe'
|
|
8
8
|
import { readFile } from '../utils/asyncfs'
|
|
9
9
|
|
|
10
10
|
export type ESBuildOptions = Omit<BuildOptions, 'absWorkingDir' | 'entryPoints' | 'watch'>
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { ESLint as RealESLint } from 'eslint'
|
|
2
|
-
import { assert
|
|
2
|
+
import { assert } from '../../assert'
|
|
3
|
+
import { BuildFailure } from '../../failure'
|
|
3
4
|
import { Files } from '../../files'
|
|
4
5
|
import { $p, ERROR, NOTICE, WARN } from '../../log'
|
|
5
6
|
import { getCurrentWorkingDirectory, resolveAbsolutePath, resolveDirectory, resolveFile } from '../../paths'
|
|
6
|
-
import { PipeParameters, Plug
|
|
7
|
+
import { Context, PipeParameters, Plug } from '../../pipe'
|
|
7
8
|
import { readFile } from '../../utils/asyncfs'
|
|
8
9
|
import { ESLintOptions } from '../eslint'
|
|
9
10
|
|
|
@@ -59,7 +60,7 @@ export default class ESLint implements Plug<void> {
|
|
|
59
60
|
|
|
60
61
|
/* In case of failures from promises, fail! */
|
|
61
62
|
const { results, failures } = summary
|
|
62
|
-
if (failures) throw
|
|
63
|
+
if (failures) throw new BuildFailure({ logged: true })
|
|
63
64
|
|
|
64
65
|
/* Create our report */
|
|
65
66
|
const report = context.log.report('ESLint Report')
|