@plugjs/plug 0.0.1
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/LICENSE.md +211 -0
- package/NOTICE.md +13 -0
- package/README.md +7 -0
- package/dist/assert.cjs +72 -0
- package/dist/assert.cjs.map +6 -0
- package/dist/assert.mjs +41 -0
- package/dist/assert.mjs.map +6 -0
- package/dist/async.cjs +58 -0
- package/dist/async.cjs.map +6 -0
- package/dist/async.mjs +30 -0
- package/dist/async.mjs.map +6 -0
- package/dist/build.cjs +136 -0
- package/dist/build.cjs.map +6 -0
- package/dist/build.mjs +110 -0
- package/dist/build.mjs.map +6 -0
- package/dist/files.cjs +113 -0
- package/dist/files.cjs.map +6 -0
- package/dist/files.mjs +88 -0
- package/dist/files.mjs.map +6 -0
- package/dist/fork.cjs +177 -0
- package/dist/fork.cjs.map +6 -0
- package/dist/fork.mjs +151 -0
- package/dist/fork.mjs.map +6 -0
- package/dist/helpers.cjs +129 -0
- package/dist/helpers.cjs.map +6 -0
- package/dist/helpers.mjs +97 -0
- package/dist/helpers.mjs.map +6 -0
- package/dist/index.cjs +25 -0
- package/dist/index.cjs.map +6 -0
- package/dist/index.mjs +7 -0
- package/dist/index.mjs.map +6 -0
- package/dist/log/colors.cjs +129 -0
- package/dist/log/colors.cjs.map +6 -0
- package/dist/log/colors.mjs +93 -0
- package/dist/log/colors.mjs.map +6 -0
- package/dist/log/emit.cjs +109 -0
- package/dist/log/emit.cjs.map +6 -0
- package/dist/log/emit.mjs +83 -0
- package/dist/log/emit.mjs.map +6 -0
- package/dist/log/levels.cjs +75 -0
- package/dist/log/levels.cjs.map +6 -0
- package/dist/log/levels.mjs +41 -0
- package/dist/log/levels.mjs.map +6 -0
- package/dist/log/logger.cjs +129 -0
- package/dist/log/logger.cjs.map +6 -0
- package/dist/log/logger.mjs +104 -0
- package/dist/log/logger.mjs.map +6 -0
- package/dist/log/options.cjs +149 -0
- package/dist/log/options.cjs.map +6 -0
- package/dist/log/options.mjs +124 -0
- package/dist/log/options.mjs.map +6 -0
- package/dist/log/report.cjs +284 -0
- package/dist/log/report.cjs.map +6 -0
- package/dist/log/report.mjs +259 -0
- package/dist/log/report.mjs.map +6 -0
- package/dist/log/spinner.cjs +83 -0
- package/dist/log/spinner.cjs.map +6 -0
- package/dist/log/spinner.mjs +57 -0
- package/dist/log/spinner.mjs.map +6 -0
- package/dist/log.cjs +71 -0
- package/dist/log.cjs.map +6 -0
- package/dist/log.mjs +45 -0
- package/dist/log.mjs.map +6 -0
- package/dist/paths.cjs +158 -0
- package/dist/paths.cjs.map +6 -0
- package/dist/paths.mjs +122 -0
- package/dist/paths.mjs.map +6 -0
- package/dist/pipe.cjs +71 -0
- package/dist/pipe.cjs.map +6 -0
- package/dist/pipe.mjs +44 -0
- package/dist/pipe.mjs.map +6 -0
- package/dist/plugs/copy.cjs +95 -0
- package/dist/plugs/copy.cjs.map +6 -0
- package/dist/plugs/copy.mjs +64 -0
- package/dist/plugs/copy.mjs.map +6 -0
- package/dist/plugs/coverage/analysis.cjs +229 -0
- package/dist/plugs/coverage/analysis.cjs.map +6 -0
- package/dist/plugs/coverage/analysis.mjs +202 -0
- package/dist/plugs/coverage/analysis.mjs.map +6 -0
- package/dist/plugs/coverage/report.cjs +215 -0
- package/dist/plugs/coverage/report.cjs.map +6 -0
- package/dist/plugs/coverage/report.mjs +200 -0
- package/dist/plugs/coverage/report.mjs.map +6 -0
- package/dist/plugs/coverage.cjs +142 -0
- package/dist/plugs/coverage.cjs.map +6 -0
- package/dist/plugs/coverage.mjs +117 -0
- package/dist/plugs/coverage.mjs.map +6 -0
- package/dist/plugs/debug.cjs +50 -0
- package/dist/plugs/debug.cjs.map +6 -0
- package/dist/plugs/debug.mjs +25 -0
- package/dist/plugs/debug.mjs.map +6 -0
- package/dist/plugs/esbuild/bundle-locals.cjs +51 -0
- package/dist/plugs/esbuild/bundle-locals.cjs.map +6 -0
- package/dist/plugs/esbuild/bundle-locals.mjs +26 -0
- package/dist/plugs/esbuild/bundle-locals.mjs.map +6 -0
- package/dist/plugs/esbuild/check-dependencies.cjs +140 -0
- package/dist/plugs/esbuild/check-dependencies.cjs.map +6 -0
- package/dist/plugs/esbuild/check-dependencies.mjs +115 -0
- package/dist/plugs/esbuild/check-dependencies.mjs.map +6 -0
- package/dist/plugs/esbuild/fix-extensions.cjs +91 -0
- package/dist/plugs/esbuild/fix-extensions.cjs.map +6 -0
- package/dist/plugs/esbuild/fix-extensions.mjs +60 -0
- package/dist/plugs/esbuild/fix-extensions.mjs.map +6 -0
- package/dist/plugs/esbuild.cjs +109 -0
- package/dist/plugs/esbuild.cjs.map +6 -0
- package/dist/plugs/esbuild.mjs +83 -0
- package/dist/plugs/esbuild.mjs.map +6 -0
- package/dist/plugs/eslint/runner.cjs +91 -0
- package/dist/plugs/eslint/runner.cjs.map +6 -0
- package/dist/plugs/eslint/runner.mjs +68 -0
- package/dist/plugs/eslint/runner.mjs.map +6 -0
- package/dist/plugs/exec.cjs +128 -0
- package/dist/plugs/exec.cjs.map +6 -0
- package/dist/plugs/exec.mjs +96 -0
- package/dist/plugs/exec.mjs.map +6 -0
- package/dist/plugs/filter.cjs +59 -0
- package/dist/plugs/filter.cjs.map +6 -0
- package/dist/plugs/filter.mjs +34 -0
- package/dist/plugs/filter.mjs.map +6 -0
- package/dist/plugs/mocha/reporter.cjs +140 -0
- package/dist/plugs/mocha/reporter.cjs.map +6 -0
- package/dist/plugs/mocha/reporter.mjs +107 -0
- package/dist/plugs/mocha/reporter.mjs.map +6 -0
- package/dist/plugs/mocha/runner.cjs +73 -0
- package/dist/plugs/mocha/runner.cjs.map +6 -0
- package/dist/plugs/mocha/runner.mjs +44 -0
- package/dist/plugs/mocha/runner.mjs.map +6 -0
- package/dist/plugs/tsc/compiler.cjs +74 -0
- package/dist/plugs/tsc/compiler.cjs.map +6 -0
- package/dist/plugs/tsc/compiler.mjs +43 -0
- package/dist/plugs/tsc/compiler.mjs.map +6 -0
- package/dist/plugs/tsc/options.cjs +82 -0
- package/dist/plugs/tsc/options.cjs.map +6 -0
- package/dist/plugs/tsc/options.mjs +51 -0
- package/dist/plugs/tsc/options.mjs.map +6 -0
- package/dist/plugs/tsc/report.cjs +90 -0
- package/dist/plugs/tsc/report.cjs.map +6 -0
- package/dist/plugs/tsc/report.mjs +59 -0
- package/dist/plugs/tsc/report.mjs.map +6 -0
- package/dist/plugs/tsc/runner.cjs +101 -0
- package/dist/plugs/tsc/runner.cjs.map +6 -0
- package/dist/plugs/tsc/runner.mjs +72 -0
- package/dist/plugs/tsc/runner.mjs.map +6 -0
- package/dist/plugs.cjs +31 -0
- package/dist/plugs.cjs.map +6 -0
- package/dist/plugs.mjs +13 -0
- package/dist/plugs.mjs.map +6 -0
- package/dist/run.cjs +95 -0
- package/dist/run.cjs.map +6 -0
- package/dist/run.mjs +70 -0
- package/dist/run.mjs.map +6 -0
- package/dist/task.cjs +39 -0
- package/dist/task.cjs.map +6 -0
- package/dist/task.mjs +14 -0
- package/dist/task.mjs.map +6 -0
- package/dist/utils/asyncfs.cjs +143 -0
- package/dist/utils/asyncfs.cjs.map +6 -0
- package/dist/utils/asyncfs.mjs +83 -0
- package/dist/utils/asyncfs.mjs.map +6 -0
- package/dist/utils/caller.cjs +59 -0
- package/dist/utils/caller.cjs.map +6 -0
- package/dist/utils/caller.mjs +34 -0
- package/dist/utils/caller.mjs.map +6 -0
- package/dist/utils/match.cjs +69 -0
- package/dist/utils/match.cjs.map +6 -0
- package/dist/utils/match.mjs +38 -0
- package/dist/utils/match.mjs.map +6 -0
- package/dist/utils/options.cjs +41 -0
- package/dist/utils/options.cjs.map +6 -0
- package/dist/utils/options.mjs +16 -0
- package/dist/utils/options.mjs.map +6 -0
- package/dist/utils/walk.cjs +104 -0
- package/dist/utils/walk.cjs.map +6 -0
- package/dist/utils/walk.mjs +79 -0
- package/dist/utils/walk.mjs.map +6 -0
- package/extra/cli.mjs +212 -0
- package/extra/ts-loader.mjs +214 -0
- package/extra/webassembly.d.ts +11 -0
- package/package.json +57 -0
- package/src/assert.ts +47 -0
- package/src/async.ts +50 -0
- package/src/files.ts +129 -0
- package/src/fork.ts +263 -0
- package/src/helpers.ts +145 -0
- package/src/index.ts +20 -0
- package/src/log/colors.ts +119 -0
- package/src/log/emit.ts +125 -0
- package/src/log/levels.ts +65 -0
- package/src/log/logger.ts +171 -0
- package/src/log/options.ts +199 -0
- package/src/log/report.ts +433 -0
- package/src/log/spinner.ts +70 -0
- package/src/log.ts +68 -0
- package/src/paths.ts +213 -0
- package/src/pipe.ts +231 -0
- package/src/plugs/copy.ts +113 -0
- package/src/plugs/coverage/analysis.ts +395 -0
- package/src/plugs/coverage/report.ts +337 -0
- package/src/plugs/coverage.ts +194 -0
- package/src/plugs/debug.ts +35 -0
- package/src/plugs/esbuild/bundle-locals.ts +33 -0
- package/src/plugs/esbuild/check-dependencies.ts +158 -0
- package/src/plugs/esbuild/fix-extensions.ts +108 -0
- package/src/plugs/esbuild.ts +128 -0
- package/src/plugs/eslint/runner.ts +112 -0
- package/src/plugs/exec.ts +215 -0
- package/src/plugs/filter.ts +56 -0
- package/src/plugs/mocha/reporter.ts +152 -0
- package/src/plugs/mocha/runner.ts +77 -0
- package/src/plugs/tsc/compiler.ts +66 -0
- package/src/plugs/tsc/options.ts +97 -0
- package/src/plugs/tsc/report.ts +74 -0
- package/src/plugs/tsc/runner.ts +100 -0
- package/src/plugs.ts +33 -0
- package/src/run.ts +160 -0
- package/src/task.ts +26 -0
- package/src/utils/asyncfs.ts +82 -0
- package/src/utils/caller.ts +45 -0
- package/src/utils/match.ts +286 -0
- package/src/utils/options.ts +22 -0
- package/src/utils/walk.ts +136 -0
- package/types/assert.d.ts +18 -0
- package/types/async.d.ts +20 -0
- package/types/build.d.ts +56 -0
- package/types/files.d.ts +44 -0
- package/types/fork.d.ts +57 -0
- package/types/helpers.d.ts +49 -0
- package/types/index.d.ts +14 -0
- package/types/log/colors.d.ts +25 -0
- package/types/log/emit.d.ts +14 -0
- package/types/log/levels.d.ts +52 -0
- package/types/log/logger.d.ts +31 -0
- package/types/log/options.d.ts +40 -0
- package/types/log/report.d.ts +64 -0
- package/types/log/spinner.d.ts +2 -0
- package/types/log.d.ts +10 -0
- package/types/paths.d.ts +76 -0
- package/types/pipe.d.ts +152 -0
- package/types/plugs/copy.d.ts +27 -0
- package/types/plugs/coverage/analysis.d.ts +104 -0
- package/types/plugs/coverage/report.d.ts +53 -0
- package/types/plugs/coverage.d.ts +46 -0
- package/types/plugs/debug.d.ts +14 -0
- package/types/plugs/esbuild/bundle-locals.d.ts +6 -0
- package/types/plugs/esbuild/check-dependencies.d.ts +12 -0
- package/types/plugs/esbuild/fix-extensions.d.ts +29 -0
- package/types/plugs/esbuild.d.ts +24 -0
- package/types/plugs/eslint/runner.d.ts +22 -0
- package/types/plugs/exec.d.ts +90 -0
- package/types/plugs/filter.d.ts +23 -0
- package/types/plugs/mocha/reporter.d.ts +8 -0
- package/types/plugs/mocha/runner.d.ts +34 -0
- package/types/plugs/tsc/compiler.d.ts +24 -0
- package/types/plugs/tsc/options.d.ts +8 -0
- package/types/plugs/tsc/report.d.ts +5 -0
- package/types/plugs/tsc/runner.d.ts +13 -0
- package/types/plugs.d.ts +16 -0
- package/types/run.d.ts +89 -0
- package/types/task.d.ts +15 -0
- package/types/utils/asyncfs.d.ts +37 -0
- package/types/utils/caller.d.ts +7 -0
- package/types/utils/match.d.ts +216 -0
- package/types/utils/options.d.ts +15 -0
- package/types/utils/walk.d.ts +28 -0
package/src/async.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
2
|
+
import { Run } from './run.js'
|
|
3
|
+
|
|
4
|
+
/* ========================================================================== *
|
|
5
|
+
* EXPORTED *
|
|
6
|
+
* ========================================================================== */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Run the specified `callback` associating the specified {@link Run} and task
|
|
10
|
+
* name with the current asynchronous invocation context.
|
|
11
|
+
*/
|
|
12
|
+
export function runAsync<T>(run: Run, task: string, callback: () => Promise<T>): Promise<T> {
|
|
13
|
+
return storage.run({ run, task }, () => {
|
|
14
|
+
tasks.push(task)
|
|
15
|
+
return callback().finally(() => {
|
|
16
|
+
const index = tasks.lastIndexOf(task)
|
|
17
|
+
if (index >= 0) tasks.splice(index, 1)
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Returns the _task name_ associated with the current asynchronous invocation
|
|
24
|
+
* context or `undefined`.
|
|
25
|
+
*/
|
|
26
|
+
export function currentTask(): string | undefined {
|
|
27
|
+
return storage.getStore()?.task
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns the {@link Run} associated with the current asynchronous invocation
|
|
32
|
+
* context or `undefined`.
|
|
33
|
+
*/
|
|
34
|
+
export function currentRun(): Run | undefined {
|
|
35
|
+
return storage.getStore()?.run
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Return an array of all _task names_ currently running
|
|
40
|
+
*/
|
|
41
|
+
export function runningTasks(): string[] {
|
|
42
|
+
return [ ...tasks ].sort()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* ========================================================================== *
|
|
46
|
+
* INTERNALS *
|
|
47
|
+
* ========================================================================== */
|
|
48
|
+
|
|
49
|
+
const storage = new AsyncLocalStorage<{ run: Run, task: string }>()
|
|
50
|
+
const tasks: string[] = []
|
package/src/files.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { inspect } from 'node:util'
|
|
2
|
+
import { AbsolutePath, assertRelativeChildPath, getAbsoluteParent, resolveAbsolutePath } from './paths.js'
|
|
3
|
+
import { mkdir, writeFile } from './utils/asyncfs.js'
|
|
4
|
+
|
|
5
|
+
/** The {@link FilesBuilder} interface defines a builder for {@link Files}. */
|
|
6
|
+
export interface FilesBuilder {
|
|
7
|
+
/** The (resolved) directory the {@link Files} will be associated with */
|
|
8
|
+
readonly directory: AbsolutePath
|
|
9
|
+
/** Push files into the {@link Files} instance being built */
|
|
10
|
+
add(...files: string[]): this
|
|
11
|
+
/** Merge orther {@link Files} instance to the {@link Files} being built */
|
|
12
|
+
merge(...files: Files[]): this
|
|
13
|
+
/** Write a file and add it to the {@link Files} instance being built */
|
|
14
|
+
write(file: string, content: string | Buffer): Promise<AbsolutePath>
|
|
15
|
+
/** Build and return a {@link Files} instance */
|
|
16
|
+
build(): Files
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The {@link Files} class represents a collection of relative path names
|
|
21
|
+
* identifying some _files_ rooted in a given _directory_.
|
|
22
|
+
*/
|
|
23
|
+
export class Files {
|
|
24
|
+
readonly _directory: AbsolutePath
|
|
25
|
+
readonly _files: string[]
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create a new {@link Files} instance rooted in the specified `directory`
|
|
29
|
+
* relative to the specified {@link Run}'s directory.
|
|
30
|
+
*/
|
|
31
|
+
constructor(directory: AbsolutePath) {
|
|
32
|
+
this._directory = directory
|
|
33
|
+
this._files = []
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Return the _directory_ where this {@link Files} is rooted */
|
|
37
|
+
get directory(): AbsolutePath {
|
|
38
|
+
return this._directory
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Return the number of files tracked by this instance. */
|
|
42
|
+
get length(): number {
|
|
43
|
+
return this._files.length
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Return an iterator over all _relative_ files of this instance */
|
|
47
|
+
* [Symbol.iterator](): Generator<string> {
|
|
48
|
+
for (const file of this._files) yield file
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Return an iterator over all _absolute_ files of this instance */
|
|
52
|
+
* absolutePaths(): Generator<AbsolutePath> {
|
|
53
|
+
for (const file of this) yield resolveAbsolutePath(this._directory, file)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Return an iterator over all _relative_ to _absolute_ mappings */
|
|
57
|
+
* pathMappings(): Generator<[ relative: string, absolute: AbsolutePath ]> {
|
|
58
|
+
for (const file of this) yield [ file, resolveAbsolutePath(this._directory, file) ]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Nicety for logging */
|
|
62
|
+
[inspect.custom](): any {
|
|
63
|
+
const self = this
|
|
64
|
+
return new class Files {
|
|
65
|
+
directory = self._directory
|
|
66
|
+
files = [ ...self._files ]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Create a new {@link FilesBuilder} creating {@link Files} instances. */
|
|
71
|
+
static builder(files: Files): FilesBuilder
|
|
72
|
+
static builder(directory: AbsolutePath): FilesBuilder
|
|
73
|
+
static builder(arg: Files | AbsolutePath): FilesBuilder {
|
|
74
|
+
const directory = typeof arg === 'string' ? arg : arg.directory
|
|
75
|
+
const set = typeof arg === 'string' ? new Set<string>() : new Set(arg._files)
|
|
76
|
+
|
|
77
|
+
const instance = new Files(directory)
|
|
78
|
+
let built = false
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
directory: instance.directory,
|
|
82
|
+
|
|
83
|
+
add(...files: string[]): FilesBuilder {
|
|
84
|
+
if (built) throw new Error('FileBuilder "build()" already called')
|
|
85
|
+
|
|
86
|
+
if (typeof files === 'string') files = [ files ]
|
|
87
|
+
for (const file of files) {
|
|
88
|
+
const relative = assertRelativeChildPath(instance.directory, file)
|
|
89
|
+
set.add(relative)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return this
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
merge(...args: Files[]): FilesBuilder {
|
|
96
|
+
if (built) throw new Error('FileBuilder "build()" already called')
|
|
97
|
+
|
|
98
|
+
for (const files of args) {
|
|
99
|
+
for (const file of files.absolutePaths()) {
|
|
100
|
+
this.add(file)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return this
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
async write(file: string, content: string | Buffer): Promise<AbsolutePath> {
|
|
108
|
+
const relative = assertRelativeChildPath(instance.directory, file)
|
|
109
|
+
const absolute = resolveAbsolutePath(instance.directory, relative)
|
|
110
|
+
const directory = getAbsoluteParent(absolute)
|
|
111
|
+
|
|
112
|
+
await mkdir(directory, { recursive: true })
|
|
113
|
+
await writeFile(absolute, content)
|
|
114
|
+
this.add(absolute)
|
|
115
|
+
|
|
116
|
+
return absolute
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
build(): Files {
|
|
120
|
+
if (built) throw new Error('FileBuilder "build()" already called')
|
|
121
|
+
|
|
122
|
+
built = true
|
|
123
|
+
instance._files.push(...set)
|
|
124
|
+
instance._files.sort()
|
|
125
|
+
return instance
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
package/src/fork.ts
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { fork } from 'node:child_process'
|
|
2
|
+
import { assert, failure } from './assert.js'
|
|
3
|
+
import { runAsync } from './async.js'
|
|
4
|
+
import { Files } from './files.js'
|
|
5
|
+
import { $gry, $p, LogOptions, logOptions } from './log.js'
|
|
6
|
+
import { AbsolutePath, isFile, requireFilename } from './paths.js'
|
|
7
|
+
import { install, Plug, PlugName } from './pipe.js'
|
|
8
|
+
import { Run, RunImpl } from './run.js'
|
|
9
|
+
|
|
10
|
+
/** Fork data, from parent to child process */
|
|
11
|
+
export interface ForkData {
|
|
12
|
+
/** Script name for the Plug to execute */
|
|
13
|
+
scriptFile: AbsolutePath,
|
|
14
|
+
/** Plug constructor arguments */
|
|
15
|
+
constructorArgs: any[],
|
|
16
|
+
/** Task name (for logs) */
|
|
17
|
+
taskName: string,
|
|
18
|
+
/** Build file name */
|
|
19
|
+
buildFile: AbsolutePath,
|
|
20
|
+
/** Build directory */
|
|
21
|
+
buildDir: AbsolutePath,
|
|
22
|
+
/** Files directory */
|
|
23
|
+
filesDir: AbsolutePath,
|
|
24
|
+
/** All files to pipe */
|
|
25
|
+
filesList: AbsolutePath[],
|
|
26
|
+
/** Options for our logger in the child process */
|
|
27
|
+
logOpts: Partial<LogOptions>,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Fork result, from child to parent process */
|
|
31
|
+
export interface ForkResult {
|
|
32
|
+
/** If this is `true` we _might_ have `filesDir` and `filesList` */
|
|
33
|
+
failed: boolean,
|
|
34
|
+
/** Files directory of the result */
|
|
35
|
+
filesDir?: AbsolutePath | undefined,
|
|
36
|
+
/** All files returned by the plug */
|
|
37
|
+
filesList?: AbsolutePath[] | undefined,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* ========================================================================== *
|
|
41
|
+
* PIPE INSTALLATION *
|
|
42
|
+
* ========================================================================== */
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Install a _forking_ {@link Plug} in the {@link Pipe}, in other words
|
|
46
|
+
* execute the plug in a separate process.
|
|
47
|
+
*
|
|
48
|
+
* As a contract, if the _last non-null_ parameter of the constructor is an
|
|
49
|
+
* object and contains the key `coverageDir`, the process will be forked with
|
|
50
|
+
* the approptiately resolved `NODE_V8_COVERAGE` environment variable.
|
|
51
|
+
*
|
|
52
|
+
* Also, forking plugs require some special attention:
|
|
53
|
+
*
|
|
54
|
+
* * plug functions are not supported, only classes implementing the
|
|
55
|
+
* {@link Plug} interface can be used with this.
|
|
56
|
+
*
|
|
57
|
+
* * the class itself _MUST_ be exported as the _default_ export for the
|
|
58
|
+
* `scriptFile` specified below. This is to simplify interoperability between
|
|
59
|
+
* CommonJS and ESM modules as we use dynamic `import(...)` statements.
|
|
60
|
+
*/
|
|
61
|
+
export function installForking(
|
|
62
|
+
plugName: PlugName,
|
|
63
|
+
scriptFile: AbsolutePath,
|
|
64
|
+
): void {
|
|
65
|
+
/** Extend out our ForkingPlug below */
|
|
66
|
+
const ctor = class extends ForkingPlug {
|
|
67
|
+
constructor(...args: any[]) {
|
|
68
|
+
super(scriptFile, args)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Install the plug in */
|
|
73
|
+
install(plugName, ctor)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* ========================================================================== *
|
|
77
|
+
* PARENT PROCESS SIDE OF THE FORKING PLUG IMPLEMENTATION *
|
|
78
|
+
* ========================================================================== */
|
|
79
|
+
|
|
80
|
+
export abstract class ForkingPlug implements Plug<Files | undefined> {
|
|
81
|
+
constructor(
|
|
82
|
+
private readonly _scriptFile: AbsolutePath,
|
|
83
|
+
private readonly _arguments: any[],
|
|
84
|
+
) {}
|
|
85
|
+
|
|
86
|
+
pipe(files: Files, run: Run): Promise<Files | undefined> {
|
|
87
|
+
const message: ForkData = {
|
|
88
|
+
scriptFile: this._scriptFile,
|
|
89
|
+
constructorArgs: this._arguments,
|
|
90
|
+
taskName: run.taskName,
|
|
91
|
+
buildFile: run.buildFile,
|
|
92
|
+
buildDir: run.buildDir,
|
|
93
|
+
filesDir: files.directory,
|
|
94
|
+
filesList: [ ...files.absolutePaths() ],
|
|
95
|
+
logOpts: logOptions.fork(run.taskName),
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Get _this_ filename to spawn */
|
|
99
|
+
const script = requireFilename(__fileurl)
|
|
100
|
+
run.log.debug('About to fork plug from', $p(script))
|
|
101
|
+
|
|
102
|
+
/* Environment variables */
|
|
103
|
+
const env = { ...process.env }
|
|
104
|
+
|
|
105
|
+
/* Check our args (reversed) to see if the last specifies `coverageDir` */
|
|
106
|
+
for (let i = this._arguments.length - 1; i >= 0; i --) {
|
|
107
|
+
if (this._arguments[i] == null) continue // null or undefined... optionals
|
|
108
|
+
if (typeof this._arguments[i] === 'object') {
|
|
109
|
+
if (typeof this._arguments[i].coverageDir === 'string') {
|
|
110
|
+
const dir = env.NODE_V8_COVERAGE = run.resolve(this._arguments[i].coverageDir)
|
|
111
|
+
run.log.debug('Forked process will produce coverage in', $p(dir))
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Run our script in a _separate_ process */
|
|
117
|
+
const child = fork(script, {
|
|
118
|
+
stdio: [ 'ignore', 'inherit', 'inherit', 'ipc' ],
|
|
119
|
+
env,
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
run.log.info('Running', $p(script), $gry(`(pid=${child.pid})`))
|
|
123
|
+
|
|
124
|
+
/* Return a promise from the child process events */
|
|
125
|
+
let done = false // this will be fixed up in "finally" below
|
|
126
|
+
return new Promise<Files | undefined>((resolve, reject) => {
|
|
127
|
+
let result: ForkResult | undefined = undefined
|
|
128
|
+
|
|
129
|
+
child.on('error', (error) => {
|
|
130
|
+
run.log.error('Child process error', error)
|
|
131
|
+
return done ? reject(failure()) : void 0
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
child.on('message', (message: ForkResult) => {
|
|
135
|
+
run.log.debug('Message from child process', message)
|
|
136
|
+
result = message
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
child.on('exit', (code, signal) => {
|
|
140
|
+
if (signal) {
|
|
141
|
+
run.log.error(`Child process exited with signal ${signal}`, $gry(`(pid=${child.pid})`))
|
|
142
|
+
return done ? reject(failure()) : void 0
|
|
143
|
+
} else if (code !== 0) {
|
|
144
|
+
run.log.error(`Child process exited with code ${code}`, $gry(`(pid=${child.pid})`))
|
|
145
|
+
return done ? reject(failure()) : void 0
|
|
146
|
+
} else if (! result) {
|
|
147
|
+
run.log.error('Child process exited with no result', $gry(`(pid=${child.pid})`))
|
|
148
|
+
return done ? reject(failure()) : void 0
|
|
149
|
+
} else if (result.failed) {
|
|
150
|
+
// definitely logged on the child side
|
|
151
|
+
return done ? reject(failure()) : void 0
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* We definitely have a successful result! */
|
|
155
|
+
return resolve(message.filesDir && message.filesList ?
|
|
156
|
+
run.files(message.filesDir).add(...message.filesList).build() :
|
|
157
|
+
undefined)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
/* After the handlers have been setup, send the message */
|
|
161
|
+
try {
|
|
162
|
+
const result = child.send(message, (error) => {
|
|
163
|
+
if (error) {
|
|
164
|
+
run.log.error('Error sending message to child process (callback failure)', error)
|
|
165
|
+
reject(failure())
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
if (! result) {
|
|
169
|
+
run.log.error('Error sending message to child process (send returned false)')
|
|
170
|
+
reject(failure())
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
run.log.error('Error sending message to child process (exception caught)', error)
|
|
174
|
+
reject(failure())
|
|
175
|
+
}
|
|
176
|
+
}).finally(() => done = true)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
/* ========================================================================== *
|
|
182
|
+
* CHILD PROCESS SIDE OF THE FORKING PLUG IMPLEMENTATION *
|
|
183
|
+
* ========================================================================== */
|
|
184
|
+
|
|
185
|
+
/*
|
|
186
|
+
* If we were started as ourselves (fork.js) and we have an IPC channel to the
|
|
187
|
+
* parent process, we can safely assume we need to run our plug... So we wait
|
|
188
|
+
* for the message and respond once the plug returns _something_!
|
|
189
|
+
*/
|
|
190
|
+
if ((process.argv[1] === requireFilename(__fileurl)) && (process.send)) {
|
|
191
|
+
/* If we haven't processed our message in 5 seconds, fail _badly_ */
|
|
192
|
+
const timeout = setTimeout(() => {
|
|
193
|
+
// eslint-disable-next-line no-console
|
|
194
|
+
console.error('Mocha not initialized in 5 seconds')
|
|
195
|
+
process.exit(2)
|
|
196
|
+
}, 5000)
|
|
197
|
+
|
|
198
|
+
/* Await our message to initialize and run the plug */
|
|
199
|
+
process.on('message', (message: ForkData) => {
|
|
200
|
+
clearTimeout(timeout)
|
|
201
|
+
|
|
202
|
+
const {
|
|
203
|
+
scriptFile,
|
|
204
|
+
constructorArgs,
|
|
205
|
+
taskName,
|
|
206
|
+
buildFile,
|
|
207
|
+
buildDir,
|
|
208
|
+
filesDir,
|
|
209
|
+
filesList,
|
|
210
|
+
logOpts,
|
|
211
|
+
} = message
|
|
212
|
+
|
|
213
|
+
/* Restore logging options first */
|
|
214
|
+
Object.assign(logOptions, logOpts)
|
|
215
|
+
|
|
216
|
+
/* First of all, our Run */
|
|
217
|
+
const run = new RunImpl({ buildDir, buildFile, taskName })
|
|
218
|
+
run.log.debug('Message from parent process', message)
|
|
219
|
+
|
|
220
|
+
/* Contextualize this run, and go! */
|
|
221
|
+
const result = runAsync(run, taskName, async () => {
|
|
222
|
+
/* Check that we have a proper script file name */
|
|
223
|
+
assert(isFile(scriptFile), `Script file ${$p(scriptFile)} not found`)
|
|
224
|
+
const script = await import(scriptFile)
|
|
225
|
+
|
|
226
|
+
/* Check that we have a proper constructor */
|
|
227
|
+
assert(typeof script.default === 'function',
|
|
228
|
+
`Script ${$p(scriptFile)} does not export a default constructor`)
|
|
229
|
+
|
|
230
|
+
/* Create the Plug instance and our Files instance */
|
|
231
|
+
const Ctor = script.default as new (...args: any[]) => Plug<Files | undefined>
|
|
232
|
+
const plug = new Ctor(...constructorArgs)
|
|
233
|
+
const files = run.files(filesDir).add(...filesList).build()
|
|
234
|
+
|
|
235
|
+
/* Run and return the result */
|
|
236
|
+
return plug.pipe(files, run)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
/* The result promise generates a message back to the parent process */
|
|
240
|
+
const promise = result.then((result) => {
|
|
241
|
+
const message: ForkResult = result ?
|
|
242
|
+
{ failed: false, filesDir: result.directory, filesList: [ ...result.absolutePaths() ] } :
|
|
243
|
+
{ failed: false }
|
|
244
|
+
return new Promise<void>((resolve, reject) => {
|
|
245
|
+
process.send!(message, (err: Error) => err ? reject(err) : resolve())
|
|
246
|
+
})
|
|
247
|
+
}, (error) => {
|
|
248
|
+
run.log.error(error)
|
|
249
|
+
return new Promise<void>((resolve, reject) => {
|
|
250
|
+
process.send!({ failed: true }, (err: Error) => err ? reject(err) : resolve())
|
|
251
|
+
})
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
/* The promise generated by `process.send()` simply triggers process exit */
|
|
255
|
+
promise.then(() => {
|
|
256
|
+
run.log.debug('Forked plug exiting')
|
|
257
|
+
process.exit(0)
|
|
258
|
+
}, (error) => {
|
|
259
|
+
run.log.error('Error sending message back to parent process', error)
|
|
260
|
+
process.exit(1)
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
}
|
package/src/helpers.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { assert } from './assert.js'
|
|
2
|
+
import { currentRun } from './async.js'
|
|
3
|
+
import { Files, FilesBuilder } from './files.js'
|
|
4
|
+
import { $p, log, LogLevelString } from './log.js'
|
|
5
|
+
import { AbsolutePath, commonPath, getCurrentWorkingDirectory, isDirectory } from './paths.js'
|
|
6
|
+
import { Pipe } from './pipe.js'
|
|
7
|
+
import { FindOptions } from './run.js'
|
|
8
|
+
import { rm } from './utils/asyncfs.js'
|
|
9
|
+
import { ParseOptions } from './utils/options.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Recursively remove the specified directory _**(use with care)**_.
|
|
13
|
+
*/
|
|
14
|
+
export async function rmrf(directory: string): Promise<void> {
|
|
15
|
+
const run = currentRun()
|
|
16
|
+
assert(run, 'Unable to find files outside a running task')
|
|
17
|
+
const dir = run.resolve(directory)
|
|
18
|
+
|
|
19
|
+
assert(dir !== getCurrentWorkingDirectory(),
|
|
20
|
+
`Cowardly refusing to wipe current working directory ${$p(dir)}`)
|
|
21
|
+
|
|
22
|
+
assert(dir !== run.buildDir,
|
|
23
|
+
`Cowardly refusing to wipe build file directory ${$p(dir)}`)
|
|
24
|
+
|
|
25
|
+
if (! isDirectory(dir)) {
|
|
26
|
+
log.info('Directory', $p(dir), 'not found')
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
log.info('Removing', $p(dir))
|
|
31
|
+
await rm(dir, { recursive: true })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Set the current _log level_.
|
|
36
|
+
*
|
|
37
|
+
* The _level_ will be applied _only_ within the execution of the current task.
|
|
38
|
+
*/
|
|
39
|
+
export function setLogLevel(level: LogLevelString): void {
|
|
40
|
+
const run = currentRun()
|
|
41
|
+
assert(run, 'Unable to find files outside a running task')
|
|
42
|
+
return run.setLogLevel(level)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Resolve a path into an {@link AbsolutePath}.
|
|
47
|
+
*
|
|
48
|
+
* If the path starts with `@...` it is considered to be relative to the
|
|
49
|
+
* {@link process.cwd | current working directory}, otherwise it will be
|
|
50
|
+
* resolved against the build file where the task was originally defined in.
|
|
51
|
+
*/
|
|
52
|
+
export function resolve(...paths: string[]): AbsolutePath {
|
|
53
|
+
const run = currentRun()
|
|
54
|
+
assert(run, 'Unable to find files outside a running task')
|
|
55
|
+
return run.resolve(...paths)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create a new {@link Files} instance.
|
|
61
|
+
*/
|
|
62
|
+
export function files(files: Files): FilesBuilder
|
|
63
|
+
export function files(...paths: string[]): FilesBuilder
|
|
64
|
+
export function files(first: Files | string | undefined, ...paths: string[]): FilesBuilder {
|
|
65
|
+
const run = currentRun()
|
|
66
|
+
assert(run, 'Unable to create files builder outside a running task')
|
|
67
|
+
if (typeof first === 'string') {
|
|
68
|
+
return run.files(first, ...paths)
|
|
69
|
+
} else if (first) {
|
|
70
|
+
return run.files(first)
|
|
71
|
+
} else {
|
|
72
|
+
return run.files()
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Merge multiple {@link Files} instance.
|
|
78
|
+
*/
|
|
79
|
+
export function merge(args: (Files | Promise<Files>)[]): Promise<Files> & Pipe {
|
|
80
|
+
const run = currentRun()
|
|
81
|
+
assert(run, 'Unable to create files builder outside a running task')
|
|
82
|
+
|
|
83
|
+
const promise = Promise.resolve().then(async () => {
|
|
84
|
+
// No arguments, no files... Just convenience!
|
|
85
|
+
if (args.length === 0) return run.pipe(run.files().build())
|
|
86
|
+
|
|
87
|
+
// Resolve all the `Files` instances (might be from other tasks)
|
|
88
|
+
const instances = await Promise.all(args)
|
|
89
|
+
const [ first, ...others ] = instances
|
|
90
|
+
|
|
91
|
+
const firstDir = first.directory
|
|
92
|
+
const otherDirs = others.map((f) => f.directory)
|
|
93
|
+
|
|
94
|
+
const directory = commonPath(firstDir, ...otherDirs)
|
|
95
|
+
|
|
96
|
+
return run.files(directory).merge(first, ...others).build()
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
return run.pipe(promise)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Find files according to the globs and {@link FindOptions} specified.
|
|
104
|
+
*/
|
|
105
|
+
export function find(glob: string, ...args: ParseOptions<FindOptions>): Pipe & Promise<Files> {
|
|
106
|
+
const run = currentRun()
|
|
107
|
+
assert(run, 'Unable to find files outside a running task')
|
|
108
|
+
return run.find(glob, ...args)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Create a {@link Pipe} from a {@link Files} instance. */
|
|
112
|
+
export function pipe(files: Files | Promise<Files>): Pipe & Promise<Files> {
|
|
113
|
+
const run = currentRun()
|
|
114
|
+
assert(run, 'Unable to create pipes outside a running task')
|
|
115
|
+
return run.pipe(files)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Await for the settlement of all the promises, then return their results. */
|
|
119
|
+
export async function parallel<P extends readonly any[]>(promises: P): Promise<ParallelResult<P>> {
|
|
120
|
+
const settlements = await Promise.allSettled(promises)
|
|
121
|
+
const results: any[] = []
|
|
122
|
+
|
|
123
|
+
let errors = 0
|
|
124
|
+
for (const settlement of settlements) {
|
|
125
|
+
if (settlement.status === 'fulfilled') {
|
|
126
|
+
results.push(settlement.value)
|
|
127
|
+
continue
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
log.error(settlement.reason)
|
|
131
|
+
errors ++
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
assert(! errors, `Parallel execution failed for ${errors} tasks`)
|
|
135
|
+
return results as ParallelResult<P>
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
type ParallelResult<T extends readonly any[]> =
|
|
139
|
+
T extends readonly [ infer First, ...infer Rest ] ?
|
|
140
|
+
[ Awaited<First>, ...ParallelResult<Rest> ] :
|
|
141
|
+
T extends readonly [ infer Only ] ?
|
|
142
|
+
[ Awaited<Only> ] :
|
|
143
|
+
T extends readonly [] ?
|
|
144
|
+
[] :
|
|
145
|
+
never
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/// <reference path="../extra/webassembly.d.ts" />
|
|
2
|
+
|
|
3
|
+
// Our minimal exports
|
|
4
|
+
export * from './assert.js'
|
|
5
|
+
export * from './build.js'
|
|
6
|
+
export * from './plugs.js'
|
|
7
|
+
export * from './log.js'
|
|
8
|
+
export * from './helpers.js'
|
|
9
|
+
|
|
10
|
+
// Utility types
|
|
11
|
+
export type { MatchOptions, MatchResult } from './utils/match.js'
|
|
12
|
+
export type { ParseOptions } from './utils/options.js'
|
|
13
|
+
export type { WalkOptions } from './utils/walk.js'
|
|
14
|
+
|
|
15
|
+
// PlugJS types
|
|
16
|
+
export type { AbsolutePath } from './paths.js'
|
|
17
|
+
export type { Files, FilesBuilder } from './files.js'
|
|
18
|
+
export type { FindOptions, Run } from './run.js'
|
|
19
|
+
export type { Pipe, Plug, PlugFunction } from './pipe.js'
|
|
20
|
+
export type { Task } from './task.js'
|