@plugjs/plug 0.3.5 → 0.4.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 (119) hide show
  1. package/dist/asserts.cjs +10 -12
  2. package/dist/asserts.cjs.map +1 -1
  3. package/dist/asserts.d.ts +1 -2
  4. package/dist/asserts.mjs +9 -10
  5. package/dist/asserts.mjs.map +1 -1
  6. package/dist/async.cjs +5 -20
  7. package/dist/async.cjs.map +2 -2
  8. package/dist/async.mjs +5 -20
  9. package/dist/async.mjs.map +2 -2
  10. package/dist/build.cjs +113 -64
  11. package/dist/build.cjs.map +2 -2
  12. package/dist/build.d.ts +9 -7
  13. package/dist/build.mjs +110 -63
  14. package/dist/build.mjs.map +2 -2
  15. package/dist/cli.d.mts +12 -0
  16. package/dist/cli.mjs +266 -0
  17. package/dist/cli.mjs.map +6 -0
  18. package/dist/files.cjs +5 -3
  19. package/dist/files.cjs.map +1 -1
  20. package/dist/files.d.ts +2 -1
  21. package/dist/files.mjs +11 -4
  22. package/dist/files.mjs.map +1 -1
  23. package/dist/fork.cjs +30 -12
  24. package/dist/fork.cjs.map +1 -1
  25. package/dist/fork.d.ts +10 -0
  26. package/dist/fork.mjs +31 -13
  27. package/dist/fork.mjs.map +1 -1
  28. package/dist/helpers.cjs +32 -13
  29. package/dist/helpers.cjs.map +2 -2
  30. package/dist/helpers.d.ts +12 -0
  31. package/dist/helpers.mjs +37 -14
  32. package/dist/helpers.mjs.map +2 -2
  33. package/dist/index.cjs +5 -0
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.ts +2 -1
  36. package/dist/index.mjs +4 -1
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/logging/emit.cjs +4 -4
  39. package/dist/logging/emit.cjs.map +1 -1
  40. package/dist/logging/emit.mjs +4 -4
  41. package/dist/logging/emit.mjs.map +1 -1
  42. package/dist/logging/logger.cjs +43 -2
  43. package/dist/logging/logger.cjs.map +1 -1
  44. package/dist/logging/logger.d.ts +36 -3
  45. package/dist/logging/logger.mjs +43 -3
  46. package/dist/logging/logger.mjs.map +1 -1
  47. package/dist/logging/options.cjs +8 -12
  48. package/dist/logging/options.cjs.map +1 -1
  49. package/dist/logging/options.d.ts +44 -1
  50. package/dist/logging/options.mjs +8 -12
  51. package/dist/logging/options.mjs.map +1 -1
  52. package/dist/logging.cjs +14 -3
  53. package/dist/logging.cjs.map +1 -1
  54. package/dist/logging.d.ts +2 -0
  55. package/dist/logging.mjs +13 -3
  56. package/dist/logging.mjs.map +1 -1
  57. package/dist/plugs/build.cjs +63 -0
  58. package/dist/plugs/build.cjs.map +6 -0
  59. package/dist/plugs/build.d.ts +13 -0
  60. package/dist/plugs/build.mjs +37 -0
  61. package/dist/plugs/build.mjs.map +6 -0
  62. package/dist/plugs/debug.cjs +7 -9
  63. package/dist/plugs/debug.cjs.map +1 -1
  64. package/dist/plugs/debug.mjs +8 -10
  65. package/dist/plugs/debug.mjs.map +1 -1
  66. package/dist/types.cjs +12 -0
  67. package/dist/types.cjs.map +1 -1
  68. package/dist/types.d.ts +35 -9
  69. package/dist/types.mjs +5 -0
  70. package/dist/types.mjs.map +2 -2
  71. package/dist/utils/diff.cjs +1 -4
  72. package/dist/utils/diff.cjs.map +1 -1
  73. package/dist/utils/diff.mjs +1 -4
  74. package/dist/utils/diff.mjs.map +1 -1
  75. package/dist/utils/exec.cjs +5 -12
  76. package/dist/utils/exec.cjs.map +2 -2
  77. package/dist/utils/exec.d.ts +0 -2
  78. package/dist/utils/exec.mjs +6 -13
  79. package/dist/utils/exec.mjs.map +1 -1
  80. package/dist/utils/{types.cjs → singleton.cjs} +14 -13
  81. package/dist/utils/singleton.cjs.map +6 -0
  82. package/dist/utils/singleton.d.ts +12 -0
  83. package/dist/utils/singleton.mjs +13 -0
  84. package/dist/utils/singleton.mjs.map +6 -0
  85. package/dist/utils.cjs +2 -2
  86. package/dist/utils.cjs.map +1 -1
  87. package/dist/utils.d.ts +1 -1
  88. package/dist/utils.mjs +1 -1
  89. package/package.json +7 -9
  90. package/src/asserts.ts +10 -12
  91. package/src/async.ts +6 -29
  92. package/src/build.ts +169 -106
  93. package/{extra/plug.mts → src/cli.mts} +115 -141
  94. package/src/files.ts +14 -6
  95. package/src/fork.ts +42 -16
  96. package/src/helpers.ts +56 -5
  97. package/src/index.ts +2 -1
  98. package/src/logging/emit.ts +4 -4
  99. package/src/logging/logger.ts +60 -7
  100. package/src/logging/options.ts +9 -14
  101. package/src/logging.ts +20 -5
  102. package/src/plugs/build.ts +45 -0
  103. package/src/plugs/debug.ts +10 -9
  104. package/src/types.ts +54 -23
  105. package/src/utils/diff.ts +1 -6
  106. package/src/utils/exec.ts +6 -20
  107. package/src/utils/singleton.ts +19 -0
  108. package/src/utils.ts +1 -1
  109. package/cli/plug.mjs +0 -1385
  110. package/cli/ts-loader.mjs +0 -275
  111. package/cli/tsrun.mjs +0 -1204
  112. package/dist/utils/types.cjs.map +0 -6
  113. package/dist/utils/types.d.ts +0 -4
  114. package/dist/utils/types.mjs +0 -12
  115. package/dist/utils/types.mjs.map +0 -6
  116. package/extra/ts-loader.mts +0 -546
  117. package/extra/tsrun.mts +0 -127
  118. package/extra/utils.ts +0 -150
  119. package/src/utils/types.ts +0 -11
package/src/helpers.ts CHANGED
@@ -7,14 +7,23 @@ import { requireContext } from './async'
7
7
  import { Files } from './files'
8
8
  import { rm } from './fs'
9
9
  import { $p, log } from './logging'
10
- import { commonPath, getCurrentWorkingDirectory, resolveDirectory, resolveFile } from './paths'
10
+ import {
11
+ commonPath,
12
+ getAbsoluteParent,
13
+ getCurrentWorkingDirectory,
14
+ resolveDirectory,
15
+ resolveFile,
16
+ } from './paths'
11
17
  import { PipeImpl } from './pipe'
18
+ import { RunBuild } from './plugs/build'
12
19
  import { execChild } from './utils/exec'
13
20
  import { parseOptions } from './utils/options'
14
21
  import { walk } from './utils/walk'
15
22
 
23
+ import type { ForkOptions } from './fork'
16
24
  import type { Pipe } from './index'
17
25
  import type { AbsolutePath } from './paths'
26
+ import type { Context } from './pipe'
18
27
  import type { ExecChildOptions } from './utils/exec'
19
28
  import type { ParseOptions } from './utils/options'
20
29
  import type { WalkOptions } from './utils/walk'
@@ -29,6 +38,11 @@ export interface FindOptions extends WalkOptions {
29
38
  directory?: string
30
39
  }
31
40
 
41
+ /** Return the current execution {@link Context} */
42
+ export function context(): Context {
43
+ return requireContext()
44
+ }
45
+
32
46
  /** Find files in the current directory using the specified _glob_. */
33
47
  export function find(glob: string): Pipe
34
48
  /** Find files in the current directory using the specified _globs_. */
@@ -56,6 +70,44 @@ export function find(...args: ParseOptions<FindOptions>): Pipe {
56
70
  }))
57
71
  }
58
72
 
73
+ export type InvokeBuildOptions = ForkOptions & Record<string, string>
74
+ export type InvokeBuildTasks = string | [ string, ...string[] ]
75
+
76
+ export function invokeBuild(buildFile: string): Promise<void>
77
+ export function invokeBuild(buildFile: string, task: string): Promise<void>
78
+ export function invokeBuild(buildFile: string, task: string, options: InvokeBuildOptions): Promise<void>
79
+ export function invokeBuild(buildFile: string, tasks: [ string, ...string[] ]): Promise<void>
80
+ export function invokeBuild(buildFile: string, tasks: [ string, ...string[] ], options: InvokeBuildOptions): Promise<void>
81
+ export function invokeBuild(buildFile: string, options: InvokeBuildOptions): Promise<void>
82
+ export async function invokeBuild(
83
+ buildFile: string,
84
+ tasksOrOptions?: string | [ string, ...string[] ] | InvokeBuildOptions,
85
+ maybeOptions?: InvokeBuildOptions,
86
+ ): Promise<void> {
87
+ const [ tasks, options = {} ] =
88
+ typeof tasksOrOptions === 'string' ?
89
+ [ [ tasksOrOptions ], maybeOptions ] :
90
+ Array.isArray(tasksOrOptions) ?
91
+ [ tasksOrOptions, maybeOptions ] :
92
+ typeof tasksOrOptions === 'object' ?
93
+ [ [ 'default' ], tasksOrOptions ] :
94
+ [ [ 'default' ], {} ]
95
+
96
+ if (tasks.length === 0) tasks.push('default')
97
+
98
+ const { coverageDir, forceModule, ...props } = options
99
+ const forkOptions = { coverageDir, forceModule }
100
+
101
+ const context = requireContext()
102
+ const file = context.resolve(buildFile)
103
+ const dir = getAbsoluteParent(file)
104
+ const files = Files.builder(dir).add(file).build()
105
+
106
+ return new RunBuild(tasks, props, forkOptions)
107
+ .pipe(files, context)
108
+ .then(() => void 0)
109
+ }
110
+
59
111
  /**
60
112
  * Recursively remove the specified directory _**(use with care)**_.
61
113
  */
@@ -98,14 +150,14 @@ export function merge(pipes: (Pipe | Files | Promise<Files>)[]): Pipe {
98
150
  const context = requireContext()
99
151
  return new PipeImpl(context, Promise.resolve().then(async () => {
100
152
  // No pipes? Just send off an empty pipe...
101
- if (pipes.length === 0) return Files.builder(getCurrentWorkingDirectory()).build()
153
+ if (pipes.length === 0) return new Files()
102
154
 
103
155
  // Await for all pipes / files / files promises
104
156
  const awaited = await assertPromises<Files>(pipes)
105
157
  const results = awaited.filter((result) => result.length)
106
158
 
107
159
  // No files in anything to be merged? Again send off an empty pipe...
108
- if (results.length === 0) return Files.builder(getCurrentWorkingDirectory()).build()
160
+ if (results.length === 0) return new Files()
109
161
 
110
162
  // Find the common directory between all the Files instances
111
163
  const [ firstDir, ...otherDirs ] = results.map((f) => f.directory)
@@ -132,8 +184,7 @@ export function merge(pipes: (Pipe | Files | Promise<Files>)[]): Pipe {
132
184
  */
133
185
  export function noop(): Pipe {
134
186
  const context = requireContext()
135
- const files = new Files(getCurrentWorkingDirectory())
136
- return new PipeImpl(context, Promise.resolve(files))
187
+ return new PipeImpl(context, Promise.resolve(new Files()))
137
188
  }
138
189
 
139
190
  /**
package/src/index.ts CHANGED
@@ -28,6 +28,7 @@ export interface Pipe extends Promise<Files> {
28
28
 
29
29
  // Submodule exports (our package.json exports)
30
30
  export * as asserts from './asserts'
31
+ export * as async from './async'
31
32
  export * as files from './files'
32
33
  export * as fork from './fork'
33
34
  export * as fs from './fs'
@@ -37,7 +38,7 @@ export * as pipe from './pipe'
37
38
  export * as utils from './utils'
38
39
 
39
40
  // Individual utilities
40
- export { log, $ms, $p, $t, $blu, $cyn, $grn, $gry, $mgt, $red, $und, $wht, $ylw } from './logging'
41
+ export { banner, log, $ms, $p, $t, $blu, $cyn, $grn, $gry, $mgt, $red, $und, $wht, $ylw } from './logging'
41
42
  export { assert, fail, BuildFailure } from './asserts'
42
43
 
43
44
  // Our minimal exports
@@ -42,8 +42,8 @@ export type LogEmitter = (options: LogEmitterOptions, args: any[]) => void
42
42
 
43
43
  /** Emit in full colors! */
44
44
  export const emitColor: LogEmitter = (options: LogEmitterOptions, args: any[]): void => {
45
- const { taskName, level, prefix, indent } = options
46
- const logPrefix = prefix ? prefix : indent ? ''.padStart(indent * _indentSize) : ''
45
+ const { taskName, level, prefix = '', indent = 0 } = options
46
+ const logPrefix = ''.padStart(indent * _indentSize) + prefix
47
47
 
48
48
  /* Prefixes, to prepend at the beginning of each line */
49
49
  const prefixes: string[] = []
@@ -85,8 +85,8 @@ export const emitColor: LogEmitter = (options: LogEmitterOptions, args: any[]):
85
85
 
86
86
  /** Emit in plain text! (no colors) */
87
87
  export const emitPlain: LogEmitter = (options: LogEmitterOptions, args: any[]): void => {
88
- const { taskName, level, prefix, indent } = options
89
- const logPrefix = prefix ? prefix : indent ? ''.padStart(indent * _indentSize) : ''
88
+ const { taskName, level, prefix = '', indent = 0 } = options
89
+ const logPrefix = ''.padStart(indent * _indentSize) + prefix
90
90
 
91
91
  const prefixes: string[] = []
92
92
 
@@ -1,13 +1,17 @@
1
- import { BuildFailure, isBuildFailure } from '../asserts'
1
+ import { formatWithOptions } from 'node:util'
2
+
3
+ import { BuildFailure } from '../asserts'
2
4
  import { emitColor, emitPlain } from './emit'
3
5
  import { DEBUG, ERROR, INFO, NOTICE, TRACE, WARN } from './levels'
4
6
  import { logOptions } from './options'
5
7
  import { ReportImpl } from './report'
8
+ import { $gry } from './colors'
6
9
 
7
- import type { LogEmitter } from './emit'
10
+ import type { LogEmitter, LogEmitterOptions } from './emit'
8
11
  import type { LogLevel } from './levels'
9
12
  import type { Report } from './report'
10
13
 
14
+
11
15
  /* ========================================================================== */
12
16
 
13
17
  /* Initial value of log colors, and subscribe to changes */
@@ -26,8 +30,6 @@ logOptions.on('changed', ({ defaultTaskName, colors, level }) => {
26
30
 
27
31
  /** The basic interface giving access to log facilities. */
28
32
  export interface Log {
29
- /** The current {@link Logger} */
30
- readonly logger: Logger
31
33
  /** Log a `TRACE` message */
32
34
  trace(...args: [ any, ...any ]): void
33
35
  /** Log a `DEBUG` message */
@@ -45,7 +47,7 @@ export interface Log {
45
47
  }
46
48
 
47
49
  /** A {@link Logger} extends the basic {@link Log} adding some state. */
48
- export interface Logger extends Omit<Log, 'logger'> {
50
+ export interface Logger extends Log {
49
51
  /** The current level for logging. */
50
52
  level: LogLevel,
51
53
 
@@ -95,7 +97,7 @@ class LoggerImpl implements Logger {
95
97
 
96
98
  // The `BuildFailure` is a bit special case
97
99
  const params = args.filter((arg) => {
98
- if (isBuildFailure(arg)) {
100
+ if (arg instanceof BuildFailure) {
99
101
  // Filter out any previously logged build failure and mark
100
102
  if (_loggedFailures.has(arg)) return false
101
103
  _loggedFailures.add(arg)
@@ -198,6 +200,57 @@ class LoggerImpl implements Logger {
198
200
  }
199
201
 
200
202
  report(title: string): Report {
201
- return new ReportImpl(title, this._task, this._emitter)
203
+ const emitter: LogEmitter = (options: LogEmitterOptions, args: any) => {
204
+ if (this._stack.length) {
205
+ for (const { message, ...extras } of this._stack) {
206
+ this._emitter({ ...options, ...extras }, [ message ])
207
+ }
208
+ this._stack.splice(0)
209
+ }
210
+
211
+ let { indent = 0, prefix = '' } = options
212
+ prefix = this._indent ? $gry('| ') + prefix : prefix
213
+ indent += this._indent
214
+ this._emitter({ ...options, indent, prefix }, args)
215
+ }
216
+ return new ReportImpl(title, this._task, emitter)
217
+ }
218
+ }
219
+
220
+ /* ========================================================================== */
221
+
222
+ /** Pattern to match ANSI expressions */
223
+ const ansiPattern = '[\\u001b\\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]'
224
+ /** Regular expression matching ANSI */
225
+ const ansiRegExp = new RegExp(ansiPattern, 'g')
226
+
227
+ /** A test logger, writing to a buffer always _without_ colors */
228
+ export class TestLogger extends LoggerImpl {
229
+ private _lines: string[] = []
230
+
231
+ constructor() {
232
+ super('', (options: LogEmitterOptions, args: any[]): void => {
233
+ const { prefix = '', indent = 0 } = options
234
+ const linePrefix = ''.padStart(indent * 2) + prefix
235
+
236
+ /* Now for the normal logging of all our parameters */
237
+ formatWithOptions({ colors: false, breakLength: 120 }, ...args)
238
+ .split('\n').forEach((line) => {
239
+ const stripped = line.replaceAll(ansiRegExp, '')
240
+ this._lines.push(`${linePrefix}${stripped}`)
241
+ })
242
+ })
243
+ }
244
+
245
+ /** Return the _current_ buffer for this instance */
246
+ get buffer(): string {
247
+ return this._lines.join('\n')
248
+ }
249
+
250
+ /** Reset the buffer and return any previously buffered text */
251
+ reset(): string {
252
+ const buffer = this.buffer
253
+ this._lines = []
254
+ return buffer
202
255
  }
203
256
  }
@@ -2,6 +2,7 @@ import { EventEmitter } from 'node:events'
2
2
  import { Socket } from 'node:net'
3
3
 
4
4
  import { getLevelNumber, NOTICE } from './levels'
5
+ import { getSingleton } from '../utils/singleton'
5
6
 
6
7
  import type { Writable } from 'node:stream'
7
8
  import type { InspectOptions } from 'node:util'
@@ -96,7 +97,11 @@ class LogOptionsImpl extends EventEmitter implements LogOptions {
96
97
  * and consumed by the `Exec` plug (which has no other way of communicating)
97
98
  */
98
99
  const { fd, ...options } = JSON.parse(process.env.__LOG_OPTIONS || '{}')
99
- if (fd) this._output = new Socket({ fd, readable: false, writable: true }).unref()
100
+ if (fd) {
101
+ const output = new Socket({ fd, readable: false, writable: true }).unref()
102
+ process.on('beforeExit', () => this._output.end())
103
+ this._output = output
104
+ }
100
105
  Object.assign(this, options)
101
106
  }
102
107
 
@@ -228,18 +233,8 @@ class LogOptionsImpl extends EventEmitter implements LogOptions {
228
233
  }
229
234
  }
230
235
 
231
- /* Unique symbol to share `LogOptions` per process */
232
- const optionsKey = Symbol.for('plugjs.plug.logging.logOptions')
233
-
234
- /** Get the shared _per process_ instance of our {@link LogOptions}. */
235
- function getLogOptions(): LogOptions {
236
- let options: LogOptions = (<any> globalThis)[optionsKey]
237
- if (! options) {
238
- options = new LogOptionsImpl()
239
- ;(<any> globalThis)[optionsKey] = options
240
- }
241
- return options
242
- }
236
+ /** Singleton key for {@link LogOptions} instance. */
237
+ const optionsKey = Symbol.for('plugjs:plug:types:LogOptions')
243
238
 
244
239
  /** Shared instance of our {@link LogOptions}. */
245
- export const logOptions = getLogOptions()
240
+ export const logOptions = getSingleton(optionsKey, () => new LogOptionsImpl())
package/src/logging.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { currentContext } from './async'
2
+ import { $gry, $wht } from './logging/colors'
2
3
  import { getLogger } from './logging/logger'
3
4
  import { setupSpinner } from './logging/spinner'
4
5
 
5
- import type { Logger, Log } from './logging/logger'
6
+ import type { Log, Logger } from './logging/logger'
6
7
 
7
8
  export * from './logging/colors'
8
9
  export * from './logging/github'
@@ -32,10 +33,6 @@ export const log: LogFunction = ((): LogFunction => {
32
33
 
33
34
  /* Create a Logger wrapping the current logger */
34
35
  const wrapper: Log = {
35
- get logger() {
36
- return logger()
37
- },
38
-
39
36
  trace(...args: [ any, ...any ]): void {
40
37
  logger().trace(...args)
41
38
  },
@@ -71,3 +68,21 @@ export const log: LogFunction = ((): LogFunction => {
71
68
  /* Return our function, with added Logger implementation */
72
69
  return Object.assign(log, wrapper)
73
70
  })()
71
+
72
+ /* ========================================================================== *
73
+ * BANNER *
74
+ * ========================================================================== */
75
+
76
+ /** Print a nice _banner_ message on the log */
77
+ export function banner(message: string): void {
78
+ const padMessage = message.length > 60 ? message.length : 60
79
+ const padLines = padMessage + 2
80
+
81
+ log.notice([
82
+ '',
83
+ $gry(`\u2554${''.padStart(padLines, '\u2550')}\u2557`),
84
+ `${$gry('\u2551')} ${$wht(message.padEnd(padMessage, ' '))} ${$gry('\u2551')}`,
85
+ $gry(`\u255A${''.padStart(padLines, '\u2550')}\u255D`),
86
+ '',
87
+ ].join('\n'))
88
+ }
@@ -0,0 +1,45 @@
1
+ import { $p } from '../logging/colors'
2
+ import { ForkingPlug, type ForkOptions } from '../fork'
3
+ import { requireFilename } from '../paths'
4
+ import { invokeTasks, isBuild } from '../build'
5
+
6
+ import type { Files } from '../files'
7
+ import type { Context, Plug } from '../pipe'
8
+
9
+ /** Writes some info about the current {@link Files} being passed around. */
10
+ export class RunBuildInternal implements Plug<void> {
11
+ constructor(
12
+ private readonly _tasks: readonly string[],
13
+ private readonly _props: Readonly<Record<string, string>>,
14
+ ) {}
15
+
16
+ async pipe(files: Files, context: Context): Promise<void> {
17
+ const tasks = this._tasks.length === 0 ? [ 'default' ] : this._tasks
18
+
19
+ for (const file of files.absolutePaths()) {
20
+ // Import and check build file
21
+ let maybeBuild = await import(file)
22
+ while (maybeBuild) {
23
+ if (isBuild(maybeBuild)) break
24
+ maybeBuild = maybeBuild.default
25
+ }
26
+
27
+ // We _need_ a build
28
+ if (! isBuild(maybeBuild)) {
29
+ context.log.fail(`File ${$p(file)} did not export a proper build`)
30
+ } else {
31
+ await invokeTasks(maybeBuild, tasks, this._props)
32
+ }
33
+ }
34
+ }
35
+ }
36
+
37
+ export class RunBuild extends ForkingPlug {
38
+ constructor(
39
+ tasks: readonly string[],
40
+ props: Readonly<Record<string, string>>,
41
+ options: ForkOptions,
42
+ ) {
43
+ super(requireFilename(__fileurl), [ tasks, props, options ], RunBuildInternal.name)
44
+ }
45
+ }
@@ -1,4 +1,4 @@
1
- import { $gry, $p, $und, $ylw } from '../logging'
1
+ import { $gry, $und, $ylw } from '../logging'
2
2
  import { install } from '../pipe'
3
3
 
4
4
  import type { Files } from '../files'
@@ -26,14 +26,15 @@ install('debug', class Debug implements Plug<Files> {
26
26
 
27
27
  async pipe(files: Files, context: Context): Promise<Files> {
28
28
  context.log.notice(this._title, `${$gry('(')}${$ylw(files.length)} ${$gry('files)')}`)
29
- context.log.notice('- base dir:', $p(context.resolve('@')))
30
- context.log.notice('- build file dir:', $p(context.resolve('.')))
31
- context.log.notice('- files dir:', $p(files.directory))
32
- if (files.length) {
33
- const [ path, ...paths ] = files
34
- context.log.notice('- relative paths:', $und($gry(path)))
35
- for (const p of paths) context.log.notice('- :', $und($gry(p)))
36
- }
29
+ context.log.notice('- build file dir:', $gry($und(context.resolve('@'))))
30
+ context.log.notice('- current dir:', $gry($und(context.resolve('.'))))
31
+ context.log.notice('- files dir:', $gry($und(files.directory)))
32
+
33
+ const paths = [ ...files ]
34
+ const path = paths.shift()
35
+ context.log.notice('- relative paths:', $und($gry(path)))
36
+ paths.forEach((p) => context.log.notice('- :', $und($gry(p))))
37
+
37
38
  return files
38
39
  }
39
40
  })
package/src/types.ts CHANGED
@@ -26,6 +26,8 @@ export interface State {
26
26
  readonly tasks: Tasks
27
27
  /** All _properties_ available in this {@link State} */
28
28
  readonly props: Props
29
+ /** All _tasks_ that have failed in this {@link State} */
30
+ readonly fails: Set<Task>
29
31
  }
30
32
 
31
33
  /* ========================================================================== *
@@ -36,21 +38,23 @@ export interface State {
36
38
  * The {@link Task} interface normalizes a task definition, associating it with
37
39
  * its build file, its sibling {@link Task}s and available _properties_.
38
40
  */
39
- export interface Task<T extends Result = Result, P extends Props = Record<string, string>> {
40
- /** Simply invoke this task stand alone */
41
- (props?: Partial<P>): Promise<T>
41
+ export interface Task<T extends Result = Result> {
42
+ /** The unique ID of this {@link Task} */
43
+ readonly id: number,
44
+ /** The _original_ name of this task */
45
+ readonly name: string
42
46
  /** All _properties_ siblings to this {@link Task} */
43
47
  readonly props: Props
44
48
  /** All {@link Tasks} sibling to this {@link Task} */
45
49
  readonly tasks: Tasks
46
50
  /** The absolute file name where this {@link Task} was defined */
47
51
  readonly buildFile: AbsolutePath,
48
- /** Invoke a task from in the context of a {@link Build} */
49
- invoke(state: State, taskName: string): Promise<T>
50
52
  /** Other {@link Task}s hooked _before_ this one */
51
53
  readonly before: Task[]
52
54
  /** Other {@link Task}s hooked _after_ this one */
53
55
  readonly after: Task[]
56
+ /** Invoke a task from in the context of a {@link Build} */
57
+ invoke(state: State, taskName: string): Promise<T>
54
58
  }
55
59
 
56
60
  /**
@@ -62,6 +66,12 @@ export type TaskResult = Pipe | Files | void | undefined
62
66
  /** The {@link TaskDef} type identifies the _definition_ of a task. */
63
67
  export type TaskDef<R extends TaskResult = TaskResult> = () => R | Promise<R>
64
68
 
69
+ /** A callable, compiled {@link Task} from a {@link TaskDef} */
70
+ export type TaskCall<D extends BuildDef = BuildDef, R extends Result = Result> = {
71
+ (props?: Partial<Props<D>>): Promise<R>
72
+ task: Task<R>
73
+ }
74
+
65
75
  /* ========================================================================== *
66
76
  * TASKS AND PROPERTIES *
67
77
  * ========================================================================== */
@@ -73,12 +83,12 @@ export type Props<D extends BuildDef = BuildDef> = {
73
83
 
74
84
  /** A type identifying all _tasks_ in a {@link Build} */
75
85
  export type Tasks<D extends BuildDef = BuildDef> = {
76
- readonly [ k in string & keyof D as D[k] extends TaskDef | Task ? k : never ] :
86
+ readonly [ k in string & keyof D as D[k] extends TaskDef | TaskCall ? k : never ] :
77
87
  D[k] extends TaskDef<infer R> ?
78
- R extends void | undefined ? Task<undefined, Props<D>> :
79
- R extends Pipe | Files ? Task<Files, Props<D>> :
88
+ R extends void | undefined ? TaskCall<D, undefined> :
89
+ R extends Pipe | Files ? TaskCall<D, Files> :
80
90
  never :
81
- D[k] extends Task ? D[k] :
91
+ D[k] extends TaskCall ? D[k] :
82
92
  never
83
93
  }
84
94
 
@@ -91,7 +101,7 @@ export type Tasks<D extends BuildDef = BuildDef> = {
91
101
  * all its properties and tasks.
92
102
  */
93
103
  export interface BuildDef {
94
- [ k : string ] : string | TaskDef | Task
104
+ [ k : string ] : string | TaskDef | TaskCall
95
105
  }
96
106
 
97
107
  /**
@@ -99,24 +109,45 @@ export interface BuildDef {
99
109
  * {@link TaskDef | task definitions }.
100
110
  */
101
111
  export type ThisBuild<D extends BuildDef> = {
102
- readonly [ k in keyof D ] :
103
- k extends string ?
104
- D[k] extends TaskDef<infer R> ?
105
- R extends Promise<undefined> | void | undefined ? () => Promise<undefined> :
106
- R extends Pipe | Files ? () => Pipe :
107
- never :
108
- D[k] extends Task<infer R> ?
109
- R extends undefined ? () => Promise<undefined> :
110
- R extends Files ? () => Pipe :
111
- never :
112
- D[k] extends string ?
113
- string :
112
+ readonly [ k in keyof D as k extends string ? k : never ] :
113
+ D[k] extends TaskDef<infer R> ?
114
+ R extends Promise<undefined> | void | undefined ? () => Promise<undefined> :
115
+ R extends Pipe | Files ? () => Pipe :
116
+ never :
117
+ D[k] extends TaskCall<any, infer R> ?
118
+ R extends undefined ? () => Promise<undefined> :
119
+ R extends Files ? () => Pipe :
114
120
  never :
121
+ D[k] extends string ?
122
+ string :
115
123
  never
116
124
  }
117
125
 
126
+ /**
127
+ * Symbol indicating that an object is a {@link Build}.
128
+ *
129
+ * In a compiled {@link Build} this symbol will be associated with a function
130
+ * taking an array of strings (task names) and record of props to override
131
+ */
132
+ export const buildMarker = Symbol.for('plugjs:plug:types:Build')
133
+
118
134
  /**
119
135
  * The {@link Build} type represents the collection of {@link Task}s
120
136
  * and _properties_ compiled from a {@link BuildDef | build definition}.
121
137
  */
122
- export type Build<D extends BuildDef = BuildDef> = Tasks<D> & Props<D>
138
+ export type Build<D extends BuildDef = BuildDef> = Props<D> & Tasks<D> & {
139
+ readonly [buildMarker]: (
140
+ tasks: readonly string[],
141
+ props?: Record<string, string | undefined>,
142
+ ) => Promise<void>
143
+ }
144
+
145
+ /** A type identifying all _task names_ in a {@link Build}. */
146
+ export type BuildTasks<B extends Build> = string & keyof {
147
+ [ name in keyof B as B[name] extends Function ? name : never ] : any
148
+ }
149
+
150
+ /** A type identifying a subset of _properties_ for a {@link Build}. */
151
+ export type BuildProps<B extends Build> = {
152
+ [ name in keyof B as B[name] extends string ? name : never ]? : string
153
+ }
package/src/utils/diff.ts CHANGED
@@ -3,7 +3,6 @@ import { inspect, isDeepStrictEqual } from 'node:util'
3
3
 
4
4
  import { assert } from '../asserts'
5
5
  import { $grn, $red, logOptions } from '../logging'
6
- import { getTypeOf } from './types'
7
6
 
8
7
  import type { InspectOptions } from 'node:util'
9
8
 
@@ -326,13 +325,9 @@ export function textDiff(
326
325
  let lhsLines: string[]
327
326
  let rhsLines: string[]
328
327
 
329
- // Get the _real_ types of both arguments
330
- const lhsType = getTypeOf(lhs)
331
- const rhsType = getTypeOf(rhs)
332
-
333
328
  // If _both_ arguments are strings, then just split and compare, otherwise
334
329
  // we nuse NodeJS' inspect to prep their string version
335
- if ((lhsType === 'string') && (rhsType === 'string')) {
330
+ if ((typeof lhs === 'string') && (typeof rhs === 'string')) {
336
331
  lhsLines = lhs.split('\n')
337
332
  rhsLines = rhs.split('\n')
338
333
  } else {
package/src/utils/exec.ts CHANGED
@@ -1,9 +1,9 @@
1
+ import { spawn } from 'node:child_process'
1
2
  import path from 'node:path'
2
3
  import readline from 'node:readline'
3
- import { fork as forkProcess, spawn as spawnProcess } from 'node:child_process'
4
4
 
5
5
  import { assert, BuildFailure } from '../asserts'
6
- import { $p, logOptions } from '../logging'
6
+ import { $p } from '../logging'
7
7
  import { getCurrentWorkingDirectory, resolveDirectory } from '../paths'
8
8
 
9
9
  import type { SpawnOptions } from 'node:child_process'
@@ -16,8 +16,6 @@ export interface ExecChildOptions {
16
16
  coverageDir?: string,
17
17
  /** Extra environment variables, or overrides for existing ones */
18
18
  env?: Record<string, any>,
19
- /** Whether to _fork_ the process (argument is a javascript file) or not */
20
- fork?: boolean,
21
19
  /** Whether to run the command in a shell (optionally name the shell) */
22
20
  shell?: string | boolean,
23
21
  /** The current working directory of the process to execute. */
@@ -32,7 +30,6 @@ export async function execChild(
32
30
  ): Promise<void> {
33
31
  const {
34
32
  env = {}, // default empty environment
35
- fork = false, // by default do not fork
36
33
  shell = false, // by default do not use a shell
37
34
  cwd = undefined, // by default use "process.cwd()"
38
35
  coverageDir, // default "undefined" (pass throug from env)
@@ -42,9 +39,6 @@ export async function execChild(
42
39
  const childCwd = cwd ? context.resolve(cwd) : getCurrentWorkingDirectory()
43
40
  assert(resolveDirectory(childCwd), `Current working directory ${$p(childCwd)} does not exist`)
44
41
 
45
- // Check for wrong fork/shell combination
46
- assert(!(fork && shell), 'Options "fork" and "shell" can not coexist')
47
-
48
42
  // Figure out the PATH environment variable
49
43
  const childPaths: AbsolutePath[] = []
50
44
 
@@ -62,8 +56,7 @@ export async function execChild(
62
56
 
63
57
  // Build our environment variables record
64
58
  const PATH = childPaths.join(path.delimiter)
65
- const logForkEnv = logOptions.forkEnv(context.taskName, 4)
66
- const childEnv: Record<string, string> = { ...process.env, ...env, ...logForkEnv, PATH }
59
+ const childEnv: Record<string, string> = { ...process.env, ...env, PATH }
67
60
 
68
61
  // Instrument coverage directory if needed
69
62
  if (coverageDir) childEnv.NODE_V8_COVERAGE = context.resolve(coverageDir)
@@ -71,19 +64,17 @@ export async function execChild(
71
64
  // Prepare the options for calling `spawn`
72
65
  const childOptions: SpawnOptions = {
73
66
  ...extraOptions,
74
- stdio: [ 'ignore', 'pipe', 'pipe', 'ipc', 'pipe' ],
67
+ stdio: [ 'ignore', 'pipe', 'pipe' ],
75
68
  cwd: childCwd,
76
69
  env: childEnv,
77
70
  shell,
78
71
  }
79
72
 
80
73
  // Spawn our subprocess and monitor its stdout/stderr
81
- context.log.info(fork ? 'Forking' : 'Executing', [ cmd, ...args ])
74
+ context.log.info('Executing', [ cmd, ...args ])
82
75
  context.log.debug('Child process options', childOptions)
83
76
 
84
- const child = fork ?
85
- forkProcess(cmd, args, childOptions) :
86
- spawnProcess(cmd, args, childOptions)
77
+ const child = spawn(cmd, args, childOptions)
87
78
 
88
79
  try {
89
80
  context.log.info('Child process PID', child.pid)
@@ -99,11 +90,6 @@ export async function execChild(
99
90
  const err = readline.createInterface(child.stderr)
100
91
  err.on('line', (line) => context.log.warn(line ||'\u00a0'))
101
92
  }
102
-
103
- // Log output bypass
104
- if (child.stdio[4]) {
105
- child.stdio[4].on('data', (data) => logOptions.output.write(data))
106
- }
107
93
  } catch (error) {
108
94
  // If something happens before returning our promise, kill the child...
109
95
  child.kill()