@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/paths.ts ADDED
@@ -0,0 +1,213 @@
1
+ import { statSync } from 'node:fs'
2
+ import { createRequire } from 'node:module'
3
+ import { dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path'
4
+ import { fileURLToPath, pathToFileURL } from 'node:url'
5
+ import { assert } from './assert.js'
6
+
7
+ /** A _branded_ `string` representing an _absolute_ path name */
8
+ export type AbsolutePath = string & { __brand_absolute_path: never }
9
+
10
+ /* ========================================================================== *
11
+ * PATH FUNCTIONS *
12
+ * ========================================================================== */
13
+
14
+ /** Resolve a path into an {@link AbsolutePath} */
15
+ export function resolveAbsolutePath(directory: AbsolutePath, ...paths: string[]): AbsolutePath {
16
+ const resolved = resolve(directory, ...paths) as AbsolutePath
17
+ assert(isAbsolute(resolved), `Path "${join(...paths)}" resolved in "${directory}" is not absolute`)
18
+ return resolved
19
+ }
20
+
21
+ /**
22
+ * Resolve a path as a relative path to the directory specified, returning the
23
+ * relative child path or `undefined` if the specified path was not a child.
24
+ *
25
+ * The `path` specified here _could_ also be another {@link AbsolutePath}
26
+ * therefore something like this will work:
27
+ *
28
+ * ```
29
+ * resolveRelativeChildPath('/foo', '/foo/bar')
30
+ * // will yield `bar`
31
+ * ```
32
+ */
33
+ export function resolveRelativeChildPath(directory: AbsolutePath, ...paths: string[]): string | undefined {
34
+ assertAbsolutePath(directory)
35
+
36
+ const abs = resolveAbsolutePath(directory, ...paths)
37
+ const rel = relative(directory, abs)
38
+ return (isAbsolute(rel) || (rel === '..') || rel.startsWith(`..${sep}`)) ? undefined : rel
39
+ }
40
+
41
+ /**
42
+ * Asserts that a path is a relative path to the directory specified, failing
43
+ * the build if it's not (see also {@link resolveRelativeChildPath}).
44
+ */
45
+
46
+ export function assertRelativeChildPath(directory: AbsolutePath, ...paths: string[]): string {
47
+ const relative = resolveRelativeChildPath(directory, ...paths)
48
+ assert(relative, `Path "${join(...paths)}" not relative to "${directory}"`)
49
+ return relative
50
+ }
51
+
52
+ /** Checks that the specified path is an {@link AbsolutePath} */
53
+ export function isAbsolutePath(path: string): path is AbsolutePath {
54
+ return isAbsolute(path)
55
+ }
56
+
57
+ /** Asserts that the specified path is an {@link AbsolutePath} */
58
+ export function assertAbsolutePath(p: string): asserts p is AbsolutePath {
59
+ assert(isAbsolute(p), `Path "${p}" not absolute`)
60
+ }
61
+
62
+ /** Return the {@link AbsolutePath} parent of another */
63
+ export function getAbsoluteParent(path: AbsolutePath): AbsolutePath {
64
+ assertAbsolutePath(path)
65
+ return dirname(path) as AbsolutePath
66
+ }
67
+
68
+ /**
69
+ * Return the {@link process.cwd() | current working directory} as an
70
+ * {@link AbsolutePath}.
71
+ */
72
+ export function getCurrentWorkingDirectory(): AbsolutePath {
73
+ const cwd = process.cwd()
74
+ assertAbsolutePath(cwd)
75
+ return cwd
76
+ }
77
+
78
+ /* ========================================================================== *
79
+ * MODULE RESOLUTION FUNCTIONS *
80
+ * ========================================================================== */
81
+
82
+ /**
83
+ * Return the absolute path of a file relative to the given `__fileurl`, where
84
+ * `__fileurl` is either CommonJS's own `__filename` variable, or EcmaScript's
85
+ * `import.meta.url` (so either an absolute path name, or a `file:///...` url).
86
+ *
87
+ * If further `paths` are specified, those will be resolved as relative paths
88
+ * to the original `__fileurl` so we can easily write something like this:
89
+ *
90
+ * ```
91
+ * const dataFile = resolveFilename(__fileurl, 'data.json')
92
+ * // if we write this in "/foo/bar/baz.(ts|js|cjs|mjs)"
93
+ * // `dataFile` will now be "/foo/bar/data.json"
94
+ * ```
95
+ */
96
+ export function requireFilename(__fileurl: string, ...paths: string[]): AbsolutePath {
97
+ /* Convert any "file:..." URL into a path name */
98
+ const file = __fileurl.startsWith('file:') ? fileURLToPath(__fileurl) : __fileurl
99
+
100
+ /* We should really have a proper absolute file name now */
101
+ assertAbsolutePath(file)
102
+
103
+ /* No paths? Return the file! */
104
+ if (! paths.length) return file
105
+
106
+ /* Resolve any paths, relative to the file */
107
+ const directory = getAbsoluteParent(file)
108
+ return resolveAbsolutePath(directory, ...paths)
109
+ }
110
+
111
+ /**
112
+ * Return the absolute path of a file which can be _required_ or _imported_
113
+ * by Node (or forked to via `child_process.fork`).
114
+ *
115
+ * This leverages {@link requireFilename} to figure out the starting point where
116
+ * to look for files, and will _try_ to match the same extension of `__fileurl`
117
+ * (so, `.ts` for `ts-node`, `.mjs` for ESM modules, ...).
118
+ */
119
+ export function requireResolve(__fileurl: string, module: string): AbsolutePath {
120
+ const file = requireFilename(__fileurl)
121
+
122
+ // We do our custom resolution _only_ for local (./foo.bar) files...
123
+ if (module.match(/^\.\.?\//)) {
124
+ // If we import "../foo.ext" from "/a/b/c/bar.ts" we need to check:
125
+ // * /a/b/foo.ext
126
+ // * /a/b/foo.ext.ts
127
+ // * /a/b/foo.ext/index.ts
128
+ // ... then delegate to the standard "require.resolve(...)"
129
+ const url = pathToFileURL(file)
130
+ const ext = extname(file)
131
+ const checks = ext ? [ `${module}`, `${module}${ext}`, `${module}/index${ext}` ] : [ module ]
132
+
133
+ for (const check of checks) {
134
+ const resolved = fileURLToPath(new URL(check, url)) as AbsolutePath
135
+ if (isFile(resolved)) {
136
+ module = check
137
+ break
138
+ }
139
+ }
140
+ }
141
+
142
+ const require = createRequire(file)
143
+ const required = require.resolve(module)
144
+ assertAbsolutePath(required)
145
+ return required
146
+ }
147
+
148
+ /**
149
+ * Return the _common_ path amongst all specified paths.
150
+ *
151
+ * While the first `path` _must_ be an {@link AbsolutePath}, all other `paths`
152
+ * can be _relative_ and will be resolved against the first `path`.
153
+ */
154
+ export function commonPath(path: AbsolutePath, ...paths: string[]): AbsolutePath {
155
+ assertAbsolutePath(path)
156
+
157
+ // Here the first path will be split into its components
158
+ // on win => [ 'C:', 'Windows', 'System32' ]
159
+ // on unx => [ '', 'usr'
160
+ const components = path.split(sep)
161
+
162
+ let length = components.length
163
+ for (const current of paths) {
164
+ const absolute = resolveAbsolutePath(path, current)
165
+ const parts = absolute.split(sep)
166
+ for (let i = 0; i < length; i++) {
167
+ if (components[i] !== parts[i]) {
168
+ length = i
169
+ break
170
+ }
171
+ }
172
+
173
+ assert(length, 'No common ancestors amongst paths')
174
+ }
175
+
176
+ const common = components.slice(0, length).join(sep)
177
+ assertAbsolutePath(common)
178
+ return common
179
+ }
180
+
181
+ /* ========================================================================== *
182
+ * FILE CHECKING FUNCTIONS *
183
+ * ========================================================================== */
184
+
185
+ /**
186
+ * Resolves the specified path as an {@link AbsolutePath} and checks it is a
187
+ * _file_, returning `undefined` if it is not.
188
+ */
189
+ export function isFile(path: AbsolutePath, ...paths: string[]): AbsolutePath | undefined {
190
+ const file = resolveAbsolutePath(path, ...paths)
191
+ try {
192
+ const stat = statSync(file)
193
+ if (stat.isFile()) return file
194
+ } catch (error: any) {
195
+ if (error.code !== 'ENOENT') throw error
196
+ }
197
+ return undefined
198
+ }
199
+
200
+ /**
201
+ * Resolves the specified path as an {@link AbsolutePath} and checks it is a
202
+ * _directory_, returning `undefined` if it is not.
203
+ */
204
+ export function isDirectory(path: AbsolutePath, ...paths: string[]): AbsolutePath | undefined {
205
+ const directory = resolveAbsolutePath(path, ...paths)
206
+ try {
207
+ const stat = statSync(directory)
208
+ if (stat.isDirectory()) return directory
209
+ } catch (error: any) {
210
+ if (error.code !== 'ENOENT') throw error
211
+ }
212
+ return undefined
213
+ }
package/src/pipe.ts ADDED
@@ -0,0 +1,231 @@
1
+ import { assert } from './assert.js'
2
+ import { Files } from './files.js'
3
+ import { Run } from './run.js'
4
+
5
+ /**
6
+ * The {@link Plug} interface describes an extension mechanism for our build.
7
+ */
8
+ export interface Plug<T extends Files | undefined> {
9
+ pipe(files: Files, run: Run): T | Promise<T>
10
+ }
11
+
12
+ /**
13
+ * A type identifying a {@link Plug} as a `function`
14
+ */
15
+ export type PlugFunction<T extends Files | undefined> = Plug<T>['pipe']
16
+
17
+ /**
18
+ * A {@link Pipe} represents a sequence of operations performed by
19
+ * a series of {@link Plug | Plugs}.
20
+ */
21
+ export interface Pipe {
22
+ /* Left empty, for definitions of installed plugs as extensions */
23
+ }
24
+
25
+ /**
26
+ * The {@link Pipe} abstract class exposes the prototype upon which all
27
+ * extension plugs will be installed on.
28
+ */
29
+ export abstract class Pipe implements Pipe {
30
+ abstract plug(plug: Plug<Files> | PlugFunction<Files>): Pipe & Promise<Files>
31
+ abstract plug(plug: Plug<undefined> | PlugFunction<undefined>): Promise<undefined>
32
+ }
33
+
34
+
35
+ /** Implementation of our {@link Pipe}. */
36
+ export class PipeImpl<T extends Files | undefined> extends Pipe implements Promise<T> {
37
+ readonly #promise: Promise<T>
38
+ readonly #run: Run
39
+
40
+ constructor(start: T | Promise<T>, run: Run) {
41
+ super()
42
+ this.#promise = Promise.resolve(start)
43
+ this.#run = run
44
+ }
45
+
46
+ plug<T extends Files | undefined>(arg: Plug<T> | PlugFunction<T>): Pipe & Promise<T> {
47
+ const plug = typeof arg === 'function' ? { pipe: arg } : arg
48
+ const promise = this.#promise.then((files) => {
49
+ assert(files, 'Pipe can not be further extended')
50
+ return plug.pipe(files, this.#run)
51
+ })
52
+ return new PipeImpl(promise, this.#run)
53
+ }
54
+
55
+ then<T1 = T, T2 = never>(
56
+ onfulfilled?: ((value: T) => T1 | PromiseLike<T1>) | null | undefined,
57
+ onrejected?: ((reason: any) => T2 | PromiseLike<T2>) | null | undefined,
58
+ ): Promise<T1 | T2> {
59
+ return this.#promise.then(onfulfilled, onrejected)
60
+ }
61
+
62
+ catch<T0 = never>(
63
+ onrejected?: ((reason: any) => T0 | PromiseLike<T0>) | null | undefined,
64
+ ): Promise<T0 | T> {
65
+ return this.#promise.catch(onrejected)
66
+ }
67
+
68
+ finally(
69
+ onfinally?: (() => void) | null | undefined,
70
+ ): Promise<T> {
71
+ return this.#promise.finally(onfinally)
72
+ }
73
+
74
+ [Symbol.toStringTag] = 'Pipe'
75
+ }
76
+
77
+ /* ========================================================================== *
78
+ * PLUG INSTALLATION (INTERNAL) *
79
+ * ========================================================================== */
80
+
81
+ /** The names which can be installed as direct plugs. */
82
+ export type PlugName = string & Exclude<keyof Pipe, 'plug' | keyof Promise<Files>>
83
+
84
+ /** A convenience type identifying a {@link Plug} constructor. */
85
+ export type PlugConstructor = new (...args: any) => Plug<Files | undefined>
86
+
87
+ /** Convert the resulting type of a {@link Plug} for use in a {@link Pipe} */
88
+ type PlugReturnForPipe<T> =
89
+ T extends Plug<infer R> ?
90
+ R extends Files ?
91
+ Promise<Files> & Pipe :
92
+ R extends undefined ?
93
+ Promise<undefined> :
94
+ never :
95
+ never
96
+
97
+ /**
98
+ * Map constructors into an array of all known overloads.
99
+ *
100
+ * This is a _royal_ pain in the ass, as we need to distinguish between
101
+ * all possible number of overloads of a constructor... Limit to 5 of them!
102
+ *
103
+ * Also, the empty constructor (when specified in the overloads) will simply
104
+ * match the first case (most overloads) and generate functions somewhat like
105
+ *
106
+ * (...args: unknown[]) => never
107
+ * (...args: unknown[]) => never
108
+ * (...args: unknown[]) => never
109
+ * () => PlugReturnForPipe<R3>
110
+ * (arg: Options) => PlugReturnForPipe<R4>
111
+ *
112
+ * Somehow inferring the result to `Function` or the right type and ANDing all
113
+ * those together here doesn't work, so we create this array and we'll AND
114
+ * all its members in the PipeExtension<...> type.
115
+ */
116
+ type PlugConstructorOverloads<T extends PlugConstructor> =
117
+ T extends {
118
+ new (...args: infer A0): infer R0
119
+ new (...args: infer A1): infer R1
120
+ new (...args: infer A2): infer R2
121
+ new (...args: infer A3): infer R3
122
+ new (...args: infer A4): infer R4
123
+ } ? [
124
+ R0 extends Plug<Files | undefined> ? ((...args: A0) => PlugReturnForPipe<R0>) : Function,
125
+ R1 extends Plug<Files | undefined> ? ((...args: A1) => PlugReturnForPipe<R1>) : Function,
126
+ R2 extends Plug<Files | undefined> ? ((...args: A2) => PlugReturnForPipe<R2>) : Function,
127
+ R3 extends Plug<Files | undefined> ? ((...args: A3) => PlugReturnForPipe<R3>) : Function,
128
+ R4 extends Plug<Files | undefined> ? ((...args: A4) => PlugReturnForPipe<R4>) : Function,
129
+ ] :
130
+ T extends {
131
+ new (...args: infer A0): infer R0
132
+ new (...args: infer A1): infer R1
133
+ new (...args: infer A2): infer R2
134
+ new (...args: infer A3): infer R3
135
+ } ? [
136
+ R0 extends Plug<Files | undefined> ? (...args: A0) => PlugReturnForPipe<R0> : Function,
137
+ R1 extends Plug<Files | undefined> ? (...args: A1) => PlugReturnForPipe<R1> : Function,
138
+ R2 extends Plug<Files | undefined> ? (...args: A2) => PlugReturnForPipe<R2> : Function,
139
+ R3 extends Plug<Files | undefined> ? (...args: A3) => PlugReturnForPipe<R3> : Function,
140
+ ] :
141
+ T extends {
142
+ new (...args: infer A0): infer R0
143
+ new (...args: infer A1): infer R1
144
+ new (...args: infer A2): infer R2
145
+ } ? [
146
+ R0 extends Plug<Files | undefined> ? (...args: A0) => PlugReturnForPipe<R0> : Function,
147
+ R1 extends Plug<Files | undefined> ? (...args: A1) => PlugReturnForPipe<R1> : Function,
148
+ R2 extends Plug<Files | undefined> ? (...args: A2) => PlugReturnForPipe<R2> : Function,
149
+ ] :
150
+ T extends {
151
+ new (...args: infer A0): infer R0
152
+ new (...args: infer A1): infer R1
153
+ } ? [
154
+ R0 extends Plug<Files | undefined> ? (...args: A0) => PlugReturnForPipe<R0> : Function,
155
+ R1 extends Plug<Files | undefined> ? (...args: A1) => PlugReturnForPipe<R1> : Function,
156
+ ] :
157
+ T extends {
158
+ new (...args: infer A0): infer R0
159
+ } ? [
160
+ R0 extends Plug<Files | undefined> ? (...args: A0) => PlugReturnForPipe<R0> : Function,
161
+ ] :
162
+ never
163
+
164
+ /**
165
+ * A convenience type to easily annotate installed {@link Plug Plugs}.
166
+ *
167
+ * See also {@link install}.
168
+ *
169
+ * ```
170
+ * export class Write implements Plug {
171
+ * // ... the plug implementation lives here
172
+ * }
173
+ *
174
+ * install('write', Write)
175
+ *
176
+ * declare module '../pipe' {
177
+ * export interface Pipe {
178
+ * write: PipeExtension<typeof Write>
179
+ * }
180
+ * }
181
+ * ```
182
+ */
183
+ export type PipeExtension<T extends PlugConstructor, A = PlugConstructorOverloads<T>> =
184
+ A extends readonly [ infer First, ...infer Rest ] ?
185
+ First & PipeExtension<T, Rest> :
186
+ A extends readonly [ infer Only ] ?
187
+ Only :
188
+ Function
189
+
190
+ /**
191
+ * Install a {@link Plug} into our {@link Pipe} prototype, and return a static
192
+ * creator function for the {@link Plug} itself.
193
+ *
194
+ * This allows our shorthand syntax for well-defined plugs such as:
195
+ *
196
+ * ```
197
+ * find('./src', '*.ts').write('./target')
198
+ * // Nicer and easier than...
199
+ * find('./src', '*.ts').plug(new Write('./target'))
200
+ * ```
201
+ *
202
+ * Use this alongside interface merging like:
203
+ *
204
+ * ```
205
+ * export class Write implements Plug {
206
+ * // ... the plug implementation lives here
207
+ * }
208
+ *
209
+ * install('write', Write)
210
+ *
211
+ * declare module '../pipe' {
212
+ * export interface Pipe {
213
+ * write: PipeExtension<typeof Write>
214
+ * }
215
+ * }
216
+ * ```
217
+ */
218
+ export function install<C extends PlugConstructor>(name: PlugName, ctor: C): void {
219
+ /* This is quite hairy when it comes to types, so, just give up! :-P */
220
+
221
+ function create(this: Pipe, ...args: any): Pipe & Promise<Files | undefined> {
222
+ // eslint-disable-next-line new-cap
223
+ return this.plug(new ctor(...args) as any)
224
+ }
225
+
226
+ /* Setup name so that stack traces look better */
227
+ Object.defineProperty(create, 'name', { value: name })
228
+
229
+ /* Inject the create function in the Pipe's prototype */
230
+ Object.defineProperty(Pipe.prototype, name, { value: create })
231
+ }
@@ -0,0 +1,113 @@
1
+ import fs from '../utils/asyncfs.js'
2
+
3
+ import { assert } from '../assert.js'
4
+ import { Files } from '../files.js'
5
+ import { $p } from '../log.js'
6
+ import { assertAbsolutePath, getAbsoluteParent, resolveAbsolutePath } from '../paths.js'
7
+ import { install, Plug } from '../pipe.js'
8
+ import { Run } from '../run.js'
9
+
10
+ /** Options for copying files */
11
+ export interface CopyOptions {
12
+ /** Whether to allow overwriting or not (default `false`). */
13
+ overwrite?: boolean,
14
+ /** If specified, use this `mode` (octal string) when creating files. */
15
+ mode?: string | number,
16
+ /** If specified, use this `mode` (octal string) when creating directories. */
17
+ dirMode?: string | number,
18
+ /** If specified, this function will be invoked to rename files. */
19
+ rename?: (relative: string) => string
20
+ }
21
+
22
+ /** Copy the curent {@link Files} to a different directory */
23
+ export class Copy implements Plug<Files> {
24
+ constructor(directory: string, options?: CopyOptions)
25
+ constructor(
26
+ private readonly _directory: string,
27
+ private readonly _options: CopyOptions = {},
28
+ ) {}
29
+
30
+ async pipe(files: Files, run: Run): Promise<Files> {
31
+ /* Destructure our options with some defaults and compute write flags */
32
+ const { mode, dirMode, overwrite, rename = (s): string => s } = this._options
33
+ const flags = overwrite ? fs.constants.COPYFILE_EXCL : 0
34
+ const dmode = parseMode(dirMode)
35
+ const fmode = parseMode(mode)
36
+
37
+ /* Our files builder for all written files */
38
+ const builder = run.files(this._directory)
39
+
40
+ /* Iterate through all the mappings of the source files */
41
+ for (const [ relative, absolute ] of files.pathMappings()) {
42
+ /* The target absolute is the (possibly) renamed relative source file
43
+ * relocated to the the target directory */
44
+ const target = resolveAbsolutePath(builder.directory, rename(relative))
45
+
46
+ /* We never copy a file onto itself, but not fail either */
47
+ if (target === absolute) {
48
+ run.log.warn('Cowardly refusing to copy same file', $p(absolute))
49
+ continue
50
+ }
51
+
52
+ /* Create the parent directory, recursively */
53
+ const directory = getAbsoluteParent(target)
54
+ const firstParent = await fs.mkdir(directory, { recursive: true })
55
+
56
+ /* Set the mode for all created directories */
57
+ if (firstParent && (dmode !== undefined)) {
58
+ assertAbsolutePath(firstParent)
59
+ for (let dir = directory; ; dir = getAbsoluteParent(dir)) {
60
+ run.log.trace(`Setting mode ${stringifyMode(dmode)} for directory`, $p(dir))
61
+ await fs.chmod(dir, dmode)
62
+ if (dir === firstParent) break
63
+ }
64
+ }
65
+
66
+ /* Actually _copy_ the file */
67
+ run.log.trace(`Copying "${$p(absolute)}" to "${$p(target)}"`)
68
+ await fs.copyFile(absolute, target, flags)
69
+
70
+ /* Set the mode, if we need to */
71
+ if (fmode !== undefined) {
72
+ run.log.trace(`Setting mode ${stringifyMode(fmode)} for file`, $p(target))
73
+ await fs.chmod(target, fmode)
74
+ }
75
+
76
+ /* Record this file */
77
+ builder.add(relative)
78
+ }
79
+
80
+ const result = builder.build()
81
+ run.log.info('Copied', result.length, 'files to', $p(builder.directory))
82
+ return result
83
+ }
84
+ }
85
+
86
+ /* ========================================================================== *
87
+ * INSTALLATION *
88
+ * ========================================================================== */
89
+
90
+ install('copy', Copy)
91
+
92
+ declare module '../pipe.js' {
93
+ export interface Pipe {
94
+ /** Copy the curent {@link Files} to a different directory */
95
+ copy: PipeExtension<typeof Copy>
96
+ }
97
+ }
98
+
99
+ /* ========================================================================== *
100
+ * INTERNALS *
101
+ * ========================================================================== */
102
+
103
+ function parseMode(mode: string | number | undefined): number | undefined {
104
+ if (mode === undefined) return undefined
105
+ if (typeof mode === 'number') return mode
106
+ const parsed = parseInt(mode, 8)
107
+ assert(! isNaN(parsed), `Invalid mode "${mode}"`)
108
+ return parsed
109
+ }
110
+
111
+ function stringifyMode(mode: number): string {
112
+ return mode.toString(8).padStart(4, '0')
113
+ }