@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.
Files changed (264) hide show
  1. package/LICENSE.md +211 -0
  2. package/NOTICE.md +13 -0
  3. package/README.md +7 -0
  4. package/dist/assert.cjs +72 -0
  5. package/dist/assert.cjs.map +6 -0
  6. package/dist/assert.mjs +41 -0
  7. package/dist/assert.mjs.map +6 -0
  8. package/dist/async.cjs +58 -0
  9. package/dist/async.cjs.map +6 -0
  10. package/dist/async.mjs +30 -0
  11. package/dist/async.mjs.map +6 -0
  12. package/dist/build.cjs +136 -0
  13. package/dist/build.cjs.map +6 -0
  14. package/dist/build.mjs +110 -0
  15. package/dist/build.mjs.map +6 -0
  16. package/dist/files.cjs +113 -0
  17. package/dist/files.cjs.map +6 -0
  18. package/dist/files.mjs +88 -0
  19. package/dist/files.mjs.map +6 -0
  20. package/dist/fork.cjs +177 -0
  21. package/dist/fork.cjs.map +6 -0
  22. package/dist/fork.mjs +151 -0
  23. package/dist/fork.mjs.map +6 -0
  24. package/dist/helpers.cjs +129 -0
  25. package/dist/helpers.cjs.map +6 -0
  26. package/dist/helpers.mjs +97 -0
  27. package/dist/helpers.mjs.map +6 -0
  28. package/dist/index.cjs +25 -0
  29. package/dist/index.cjs.map +6 -0
  30. package/dist/index.mjs +7 -0
  31. package/dist/index.mjs.map +6 -0
  32. package/dist/log/colors.cjs +129 -0
  33. package/dist/log/colors.cjs.map +6 -0
  34. package/dist/log/colors.mjs +93 -0
  35. package/dist/log/colors.mjs.map +6 -0
  36. package/dist/log/emit.cjs +109 -0
  37. package/dist/log/emit.cjs.map +6 -0
  38. package/dist/log/emit.mjs +83 -0
  39. package/dist/log/emit.mjs.map +6 -0
  40. package/dist/log/levels.cjs +75 -0
  41. package/dist/log/levels.cjs.map +6 -0
  42. package/dist/log/levels.mjs +41 -0
  43. package/dist/log/levels.mjs.map +6 -0
  44. package/dist/log/logger.cjs +129 -0
  45. package/dist/log/logger.cjs.map +6 -0
  46. package/dist/log/logger.mjs +104 -0
  47. package/dist/log/logger.mjs.map +6 -0
  48. package/dist/log/options.cjs +149 -0
  49. package/dist/log/options.cjs.map +6 -0
  50. package/dist/log/options.mjs +124 -0
  51. package/dist/log/options.mjs.map +6 -0
  52. package/dist/log/report.cjs +284 -0
  53. package/dist/log/report.cjs.map +6 -0
  54. package/dist/log/report.mjs +259 -0
  55. package/dist/log/report.mjs.map +6 -0
  56. package/dist/log/spinner.cjs +83 -0
  57. package/dist/log/spinner.cjs.map +6 -0
  58. package/dist/log/spinner.mjs +57 -0
  59. package/dist/log/spinner.mjs.map +6 -0
  60. package/dist/log.cjs +71 -0
  61. package/dist/log.cjs.map +6 -0
  62. package/dist/log.mjs +45 -0
  63. package/dist/log.mjs.map +6 -0
  64. package/dist/paths.cjs +158 -0
  65. package/dist/paths.cjs.map +6 -0
  66. package/dist/paths.mjs +122 -0
  67. package/dist/paths.mjs.map +6 -0
  68. package/dist/pipe.cjs +71 -0
  69. package/dist/pipe.cjs.map +6 -0
  70. package/dist/pipe.mjs +44 -0
  71. package/dist/pipe.mjs.map +6 -0
  72. package/dist/plugs/copy.cjs +95 -0
  73. package/dist/plugs/copy.cjs.map +6 -0
  74. package/dist/plugs/copy.mjs +64 -0
  75. package/dist/plugs/copy.mjs.map +6 -0
  76. package/dist/plugs/coverage/analysis.cjs +229 -0
  77. package/dist/plugs/coverage/analysis.cjs.map +6 -0
  78. package/dist/plugs/coverage/analysis.mjs +202 -0
  79. package/dist/plugs/coverage/analysis.mjs.map +6 -0
  80. package/dist/plugs/coverage/report.cjs +215 -0
  81. package/dist/plugs/coverage/report.cjs.map +6 -0
  82. package/dist/plugs/coverage/report.mjs +200 -0
  83. package/dist/plugs/coverage/report.mjs.map +6 -0
  84. package/dist/plugs/coverage.cjs +142 -0
  85. package/dist/plugs/coverage.cjs.map +6 -0
  86. package/dist/plugs/coverage.mjs +117 -0
  87. package/dist/plugs/coverage.mjs.map +6 -0
  88. package/dist/plugs/debug.cjs +50 -0
  89. package/dist/plugs/debug.cjs.map +6 -0
  90. package/dist/plugs/debug.mjs +25 -0
  91. package/dist/plugs/debug.mjs.map +6 -0
  92. package/dist/plugs/esbuild/bundle-locals.cjs +51 -0
  93. package/dist/plugs/esbuild/bundle-locals.cjs.map +6 -0
  94. package/dist/plugs/esbuild/bundle-locals.mjs +26 -0
  95. package/dist/plugs/esbuild/bundle-locals.mjs.map +6 -0
  96. package/dist/plugs/esbuild/check-dependencies.cjs +140 -0
  97. package/dist/plugs/esbuild/check-dependencies.cjs.map +6 -0
  98. package/dist/plugs/esbuild/check-dependencies.mjs +115 -0
  99. package/dist/plugs/esbuild/check-dependencies.mjs.map +6 -0
  100. package/dist/plugs/esbuild/fix-extensions.cjs +91 -0
  101. package/dist/plugs/esbuild/fix-extensions.cjs.map +6 -0
  102. package/dist/plugs/esbuild/fix-extensions.mjs +60 -0
  103. package/dist/plugs/esbuild/fix-extensions.mjs.map +6 -0
  104. package/dist/plugs/esbuild.cjs +109 -0
  105. package/dist/plugs/esbuild.cjs.map +6 -0
  106. package/dist/plugs/esbuild.mjs +83 -0
  107. package/dist/plugs/esbuild.mjs.map +6 -0
  108. package/dist/plugs/eslint/runner.cjs +91 -0
  109. package/dist/plugs/eslint/runner.cjs.map +6 -0
  110. package/dist/plugs/eslint/runner.mjs +68 -0
  111. package/dist/plugs/eslint/runner.mjs.map +6 -0
  112. package/dist/plugs/exec.cjs +128 -0
  113. package/dist/plugs/exec.cjs.map +6 -0
  114. package/dist/plugs/exec.mjs +96 -0
  115. package/dist/plugs/exec.mjs.map +6 -0
  116. package/dist/plugs/filter.cjs +59 -0
  117. package/dist/plugs/filter.cjs.map +6 -0
  118. package/dist/plugs/filter.mjs +34 -0
  119. package/dist/plugs/filter.mjs.map +6 -0
  120. package/dist/plugs/mocha/reporter.cjs +140 -0
  121. package/dist/plugs/mocha/reporter.cjs.map +6 -0
  122. package/dist/plugs/mocha/reporter.mjs +107 -0
  123. package/dist/plugs/mocha/reporter.mjs.map +6 -0
  124. package/dist/plugs/mocha/runner.cjs +73 -0
  125. package/dist/plugs/mocha/runner.cjs.map +6 -0
  126. package/dist/plugs/mocha/runner.mjs +44 -0
  127. package/dist/plugs/mocha/runner.mjs.map +6 -0
  128. package/dist/plugs/tsc/compiler.cjs +74 -0
  129. package/dist/plugs/tsc/compiler.cjs.map +6 -0
  130. package/dist/plugs/tsc/compiler.mjs +43 -0
  131. package/dist/plugs/tsc/compiler.mjs.map +6 -0
  132. package/dist/plugs/tsc/options.cjs +82 -0
  133. package/dist/plugs/tsc/options.cjs.map +6 -0
  134. package/dist/plugs/tsc/options.mjs +51 -0
  135. package/dist/plugs/tsc/options.mjs.map +6 -0
  136. package/dist/plugs/tsc/report.cjs +90 -0
  137. package/dist/plugs/tsc/report.cjs.map +6 -0
  138. package/dist/plugs/tsc/report.mjs +59 -0
  139. package/dist/plugs/tsc/report.mjs.map +6 -0
  140. package/dist/plugs/tsc/runner.cjs +101 -0
  141. package/dist/plugs/tsc/runner.cjs.map +6 -0
  142. package/dist/plugs/tsc/runner.mjs +72 -0
  143. package/dist/plugs/tsc/runner.mjs.map +6 -0
  144. package/dist/plugs.cjs +31 -0
  145. package/dist/plugs.cjs.map +6 -0
  146. package/dist/plugs.mjs +13 -0
  147. package/dist/plugs.mjs.map +6 -0
  148. package/dist/run.cjs +95 -0
  149. package/dist/run.cjs.map +6 -0
  150. package/dist/run.mjs +70 -0
  151. package/dist/run.mjs.map +6 -0
  152. package/dist/task.cjs +39 -0
  153. package/dist/task.cjs.map +6 -0
  154. package/dist/task.mjs +14 -0
  155. package/dist/task.mjs.map +6 -0
  156. package/dist/utils/asyncfs.cjs +143 -0
  157. package/dist/utils/asyncfs.cjs.map +6 -0
  158. package/dist/utils/asyncfs.mjs +83 -0
  159. package/dist/utils/asyncfs.mjs.map +6 -0
  160. package/dist/utils/caller.cjs +59 -0
  161. package/dist/utils/caller.cjs.map +6 -0
  162. package/dist/utils/caller.mjs +34 -0
  163. package/dist/utils/caller.mjs.map +6 -0
  164. package/dist/utils/match.cjs +69 -0
  165. package/dist/utils/match.cjs.map +6 -0
  166. package/dist/utils/match.mjs +38 -0
  167. package/dist/utils/match.mjs.map +6 -0
  168. package/dist/utils/options.cjs +41 -0
  169. package/dist/utils/options.cjs.map +6 -0
  170. package/dist/utils/options.mjs +16 -0
  171. package/dist/utils/options.mjs.map +6 -0
  172. package/dist/utils/walk.cjs +104 -0
  173. package/dist/utils/walk.cjs.map +6 -0
  174. package/dist/utils/walk.mjs +79 -0
  175. package/dist/utils/walk.mjs.map +6 -0
  176. package/extra/cli.mjs +212 -0
  177. package/extra/ts-loader.mjs +214 -0
  178. package/extra/webassembly.d.ts +11 -0
  179. package/package.json +57 -0
  180. package/src/assert.ts +47 -0
  181. package/src/async.ts +50 -0
  182. package/src/files.ts +129 -0
  183. package/src/fork.ts +263 -0
  184. package/src/helpers.ts +145 -0
  185. package/src/index.ts +20 -0
  186. package/src/log/colors.ts +119 -0
  187. package/src/log/emit.ts +125 -0
  188. package/src/log/levels.ts +65 -0
  189. package/src/log/logger.ts +171 -0
  190. package/src/log/options.ts +199 -0
  191. package/src/log/report.ts +433 -0
  192. package/src/log/spinner.ts +70 -0
  193. package/src/log.ts +68 -0
  194. package/src/paths.ts +213 -0
  195. package/src/pipe.ts +231 -0
  196. package/src/plugs/copy.ts +113 -0
  197. package/src/plugs/coverage/analysis.ts +395 -0
  198. package/src/plugs/coverage/report.ts +337 -0
  199. package/src/plugs/coverage.ts +194 -0
  200. package/src/plugs/debug.ts +35 -0
  201. package/src/plugs/esbuild/bundle-locals.ts +33 -0
  202. package/src/plugs/esbuild/check-dependencies.ts +158 -0
  203. package/src/plugs/esbuild/fix-extensions.ts +108 -0
  204. package/src/plugs/esbuild.ts +128 -0
  205. package/src/plugs/eslint/runner.ts +112 -0
  206. package/src/plugs/exec.ts +215 -0
  207. package/src/plugs/filter.ts +56 -0
  208. package/src/plugs/mocha/reporter.ts +152 -0
  209. package/src/plugs/mocha/runner.ts +77 -0
  210. package/src/plugs/tsc/compiler.ts +66 -0
  211. package/src/plugs/tsc/options.ts +97 -0
  212. package/src/plugs/tsc/report.ts +74 -0
  213. package/src/plugs/tsc/runner.ts +100 -0
  214. package/src/plugs.ts +33 -0
  215. package/src/run.ts +160 -0
  216. package/src/task.ts +26 -0
  217. package/src/utils/asyncfs.ts +82 -0
  218. package/src/utils/caller.ts +45 -0
  219. package/src/utils/match.ts +286 -0
  220. package/src/utils/options.ts +22 -0
  221. package/src/utils/walk.ts +136 -0
  222. package/types/assert.d.ts +18 -0
  223. package/types/async.d.ts +20 -0
  224. package/types/build.d.ts +56 -0
  225. package/types/files.d.ts +44 -0
  226. package/types/fork.d.ts +57 -0
  227. package/types/helpers.d.ts +49 -0
  228. package/types/index.d.ts +14 -0
  229. package/types/log/colors.d.ts +25 -0
  230. package/types/log/emit.d.ts +14 -0
  231. package/types/log/levels.d.ts +52 -0
  232. package/types/log/logger.d.ts +31 -0
  233. package/types/log/options.d.ts +40 -0
  234. package/types/log/report.d.ts +64 -0
  235. package/types/log/spinner.d.ts +2 -0
  236. package/types/log.d.ts +10 -0
  237. package/types/paths.d.ts +76 -0
  238. package/types/pipe.d.ts +152 -0
  239. package/types/plugs/copy.d.ts +27 -0
  240. package/types/plugs/coverage/analysis.d.ts +104 -0
  241. package/types/plugs/coverage/report.d.ts +53 -0
  242. package/types/plugs/coverage.d.ts +46 -0
  243. package/types/plugs/debug.d.ts +14 -0
  244. package/types/plugs/esbuild/bundle-locals.d.ts +6 -0
  245. package/types/plugs/esbuild/check-dependencies.d.ts +12 -0
  246. package/types/plugs/esbuild/fix-extensions.d.ts +29 -0
  247. package/types/plugs/esbuild.d.ts +24 -0
  248. package/types/plugs/eslint/runner.d.ts +22 -0
  249. package/types/plugs/exec.d.ts +90 -0
  250. package/types/plugs/filter.d.ts +23 -0
  251. package/types/plugs/mocha/reporter.d.ts +8 -0
  252. package/types/plugs/mocha/runner.d.ts +34 -0
  253. package/types/plugs/tsc/compiler.d.ts +24 -0
  254. package/types/plugs/tsc/options.d.ts +8 -0
  255. package/types/plugs/tsc/report.d.ts +5 -0
  256. package/types/plugs/tsc/runner.d.ts +13 -0
  257. package/types/plugs.d.ts +16 -0
  258. package/types/run.d.ts +89 -0
  259. package/types/task.d.ts +15 -0
  260. package/types/utils/asyncfs.d.ts +37 -0
  261. package/types/utils/caller.d.ts +7 -0
  262. package/types/utils/match.d.ts +216 -0
  263. package/types/utils/options.d.ts +15 -0
  264. 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'