@plugjs/plug 0.4.0 → 0.4.2

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 (72) hide show
  1. package/dist/asserts.cjs +1 -1
  2. package/dist/asserts.cjs.map +1 -1
  3. package/dist/asserts.mjs +1 -1
  4. package/dist/asserts.mjs.map +1 -1
  5. package/dist/async.cjs +5 -20
  6. package/dist/async.cjs.map +2 -2
  7. package/dist/async.mjs +5 -20
  8. package/dist/async.mjs.map +2 -2
  9. package/dist/build.cjs +95 -70
  10. package/dist/build.cjs.map +2 -2
  11. package/dist/build.d.ts +6 -8
  12. package/dist/build.mjs +92 -67
  13. package/dist/build.mjs.map +2 -2
  14. package/dist/cli.mjs +1 -1
  15. package/dist/files.cjs +5 -3
  16. package/dist/files.cjs.map +1 -1
  17. package/dist/files.d.ts +2 -1
  18. package/dist/files.mjs +11 -4
  19. package/dist/files.mjs.map +1 -1
  20. package/dist/helpers.cjs +3 -4
  21. package/dist/helpers.cjs.map +1 -1
  22. package/dist/helpers.mjs +3 -4
  23. package/dist/helpers.mjs.map +1 -1
  24. package/dist/logging/options.cjs +3 -10
  25. package/dist/logging/options.cjs.map +1 -1
  26. package/dist/logging/options.d.ts +44 -1
  27. package/dist/logging/options.mjs +3 -10
  28. package/dist/logging/options.mjs.map +1 -1
  29. package/dist/plugs/build.cjs +4 -7
  30. package/dist/plugs/build.cjs.map +1 -1
  31. package/dist/plugs/build.mjs +2 -5
  32. package/dist/plugs/build.mjs.map +1 -1
  33. package/dist/plugs/debug.cjs +7 -9
  34. package/dist/plugs/debug.cjs.map +1 -1
  35. package/dist/plugs/debug.mjs +8 -10
  36. package/dist/plugs/debug.mjs.map +1 -1
  37. package/dist/types.cjs +12 -0
  38. package/dist/types.cjs.map +1 -1
  39. package/dist/types.d.ts +33 -9
  40. package/dist/types.mjs +5 -0
  41. package/dist/types.mjs.map +2 -2
  42. package/dist/utils/diff.cjs +1 -4
  43. package/dist/utils/diff.cjs.map +1 -1
  44. package/dist/utils/diff.mjs +1 -4
  45. package/dist/utils/diff.mjs.map +1 -1
  46. package/dist/utils/{types.cjs → singleton.cjs} +14 -13
  47. package/dist/utils/singleton.cjs.map +6 -0
  48. package/dist/utils/singleton.d.ts +12 -0
  49. package/dist/utils/singleton.mjs +13 -0
  50. package/dist/utils/singleton.mjs.map +6 -0
  51. package/dist/utils.cjs +2 -2
  52. package/dist/utils.cjs.map +1 -1
  53. package/dist/utils.d.ts +1 -1
  54. package/dist/utils.mjs +1 -1
  55. package/package.json +1 -1
  56. package/src/asserts.ts +1 -1
  57. package/src/async.ts +6 -29
  58. package/src/build.ts +151 -117
  59. package/src/files.ts +14 -6
  60. package/src/helpers.ts +3 -4
  61. package/src/logging/options.ts +4 -13
  62. package/src/plugs/build.ts +2 -15
  63. package/src/plugs/debug.ts +10 -9
  64. package/src/types.ts +52 -23
  65. package/src/utils/diff.ts +1 -6
  66. package/src/utils/singleton.ts +19 -0
  67. package/src/utils.ts +1 -1
  68. package/dist/utils/types.cjs.map +0 -6
  69. package/dist/utils/types.d.ts +0 -4
  70. package/dist/utils/types.mjs +0 -12
  71. package/dist/utils/types.mjs.map +0 -6
  72. package/src/utils/types.ts +0 -11
package/src/build.ts CHANGED
@@ -3,77 +3,124 @@ import { runAsync } from './async'
3
3
  import { $gry, $ms, $p, $t, getLogger, logOptions } from './logging'
4
4
  import { Context, ContextPromises, PipeImpl } from './pipe'
5
5
  import { findCaller } from './utils/caller'
6
+ import { getSingleton } from './utils/singleton'
7
+ import { buildMarker } from './types'
6
8
 
7
9
  import type { Pipe } from './index'
8
10
  import type { AbsolutePath } from './paths'
9
11
  import type {
10
12
  Build,
13
+ BuildProps,
11
14
  BuildDef,
12
- Props,
13
15
  Result,
14
16
  State,
15
17
  Task,
16
18
  TaskDef,
17
- Tasks,
18
19
  ThisBuild,
20
+ Props,
21
+ TaskCall,
22
+ BuildTasks,
23
+ Tasks,
19
24
  } from './types'
20
25
 
21
26
  /* ========================================================================== *
22
- * TASK *
27
+ * INTERNAL UTILITIES *
23
28
  * ========================================================================== */
24
29
 
25
- /** Symbol indicating that an object is a {@link Task} */
26
- const taskMarker = Symbol.for('plugjs:isTask')
30
+ /** Symbol indicating that an object is a {@link TaskCall} */
31
+ const taskCallMarker = Symbol.for('plugjs:plug:types:TaskCall')
32
+
33
+ /** Type guard for {@link TaskCall}s */
34
+ function isTaskCall(something: any): something is TaskCall {
35
+ return something[taskCallMarker] === taskCallMarker
36
+ }
37
+
38
+ /** Shallow merge two records */
39
+ function merge<A, B>(a: A, b: B): A & B {
40
+ return Object.assign(Object.create(null), a, b)
41
+ }
27
42
 
28
- /** Type guard for {@link Tasks} */
29
- function isTask(something: any): something is Task {
30
- return something[taskMarker] === true
43
+ /** Create a {@link State} from its components */
44
+ function makeState(state: {
45
+ cache?: Map<Task, Promise<Result>>
46
+ stack?: Task[],
47
+ tasks?: Record<string, Task>
48
+ props?: Record<string, string>
49
+ fails?: Set<Task>
50
+ }): State {
51
+ const {
52
+ cache = new Map(),
53
+ fails = new Set(),
54
+ stack = [],
55
+ tasks = {},
56
+ props = {},
57
+ } = state
58
+
59
+ return { cache, fails, stack, tasks, props } as State
31
60
  }
32
61
 
33
- /** Create a new {@link Task} instance */
34
- function makeTask(
35
- buildFile: AbsolutePath,
36
- tasks: Tasks,
37
- props: Props,
38
- _def: TaskDef,
39
- _name: string,
40
- ): Task {
41
- /* Invoke the task, checking call stack, caches, and merging builds */
42
- async function invoke(state: State, taskName: string): Promise<Result> {
43
- assert(! state.stack.includes(task), `Recursion detected calling ${$t(taskName)}`)
62
+ /* ========================================================================== *
63
+ * TASK IMPLEMENTATION *
64
+ * ========================================================================== */
65
+
66
+ const lastIdKey = Symbol.for('plugjs:plug:singleton:taskId')
67
+ const taskId = getSingleton(lastIdKey, () => ({ id: 0 }))
68
+
69
+ class TaskImpl<R extends Result> implements Task<R> {
70
+ public readonly before: Task<Result>[] = []
71
+ public readonly after: Task<Result>[] = []
72
+ public readonly id: number = ++ taskId.id
73
+
74
+ props: Props<BuildDef>
75
+ tasks: Tasks<BuildDef>
76
+
77
+ constructor(
78
+ public readonly name: string,
79
+ public readonly buildFile: AbsolutePath,
80
+ private readonly _def: TaskDef,
81
+ _tasks: Record<string, Task>,
82
+ _props: Record<string, string>,
83
+ ) {
84
+ this.tasks = _tasks as Tasks
85
+ this.props = _props as Props
86
+ }
87
+
88
+ async invoke(state: State, taskName: string): Promise<R> {
89
+ assert(! state.stack.includes(this), `Recursion detected calling ${$t(taskName)}`)
44
90
 
45
91
  /* Check cache */
46
- const cached = state.cache.get(task)
47
- if (cached) return cached
92
+ const cached = state.cache.get(this)
93
+ if (cached) return cached as Promise<R>
48
94
 
49
95
  /* Create new substate merging sibling tasks/props and adding this to the stack */
50
- const props: Record<string, string> = Object.assign({}, task.props, state.props)
51
- const tasks: Record<string, Task> = Object.assign({}, task.tasks, state.tasks)
52
- const stack = [ ...state.stack, task ]
53
- const cache = state.cache
54
- const fails = state.fails
55
- state = { stack, cache, fails, tasks, props }
96
+ state = makeState({
97
+ props: merge(this.props, state.props),
98
+ tasks: merge(this.tasks, state.tasks),
99
+ stack: [ ...state.stack, this ],
100
+ cache: state.cache,
101
+ fails: state.fails,
102
+ })
56
103
 
57
104
  /* Create run context and build */
58
- const context = new Context(task.buildFile, taskName)
105
+ const context = new Context(this.buildFile, taskName)
59
106
 
60
107
  /* The build (the `this` value calling the definition) is a proxy */
61
108
  const build = new Proxy({}, {
62
- get(_: any, name: string): void | string | (() => Pipe) {
109
+ get: (_: any, name: string): void | string | (() => Pipe) => {
63
110
  // Tasks first, props might come also from environment
64
- if (name in tasks) {
111
+ if (name in state.tasks) {
65
112
  return (): Pipe => {
66
- const promise = tasks[name]!.invoke(state, name)
113
+ const promise = (state as any).tasks[name]!.invoke(state, name)
67
114
  return new PipeImpl(context, promise)
68
115
  }
69
- } else if (name in props) {
70
- return props[name]
116
+ } else if (name in state.props) {
117
+ return (state as any).props[name]
71
118
  }
72
119
  },
73
120
  })
74
121
 
75
122
  /* Run all tasks hooked _before_ this one */
76
- for (const before of task.before) await before.invoke(state, before.name)
123
+ for (const before of this.before) await before.invoke(state, before.name)
77
124
 
78
125
  /* Some logging */
79
126
  context.log.info('Running...')
@@ -81,56 +128,31 @@ function makeTask(
81
128
 
82
129
  /* Run asynchronously in an asynchronous context */
83
130
  const promise = runAsync(context, taskName, async () => {
84
- return await _def.call(build) || undefined
131
+ return await this._def.call(build) || undefined
85
132
  }).then(async (result) => {
86
133
  const level = taskName.startsWith('_') ? 'info' : 'notice'
87
134
  context.log[level](`Success ${$ms(Date.now() - now)}`)
88
135
  return result
89
136
  }).catch((error) => {
90
- fails.add(task)
137
+ state.fails.add(this)
91
138
  throw context.log.fail(`Failure ${$ms(Date.now() - now)}`, error)
92
139
  }).finally(async () => {
93
140
  await ContextPromises.wait(context)
94
141
  }).then(async (result) => {
95
- for (const after of task.after) await after.invoke(state, after.name)
142
+ for (const after of this.after) await after.invoke(state, after.name)
96
143
  return result
97
144
  })
98
145
 
99
146
  /* Cache the resulting promise and return it */
100
- cache.set(task, promise)
101
- return promise
147
+ state.cache.set(this, promise)
148
+ return promise as Promise<R>
102
149
  }
103
-
104
- /* Create the new Task. The function will simply create an empty state */
105
- const task: Task = Object.assign((overrideProps: Props = {}) => {
106
- const state: State = {
107
- cache: new Map<Task, Promise<Result>>(),
108
- stack: [] as Task[],
109
- props: Object.assign({}, props, overrideProps),
110
- fails: new Set<Task>,
111
- tasks: tasks,
112
- }
113
- return invoke(state, _name)
114
- }, { buildFile, tasks, props, invoke, before: [], after: [] })
115
-
116
- /* Assign the task's marker and name and return it */
117
- Object.defineProperty(task, taskMarker, { value: true })
118
- Object.defineProperty(task, 'name', { value: _name })
119
- return task
120
150
  }
121
151
 
122
152
  /* ========================================================================== *
123
153
  * BUILD COMPILER *
124
154
  * ========================================================================== */
125
155
 
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
- const buildMarker = Symbol.for('plugjs:isBuild')
133
-
134
156
  /** Compile a {@link BuildDef | build definition} into a {@link Build} */
135
157
  export function build<
136
158
  D extends BuildDef, B extends ThisBuild<D>
@@ -142,13 +164,14 @@ export function build<
142
164
  /* Iterate through all definition extracting properties and tasks */
143
165
  for (const [ key, val ] of Object.entries(def)) {
144
166
  let len = 0
145
- if (isTask(val)) { // this goes first, tasks _are_ functions!
146
- tasks[key] = val
167
+ if (isTaskCall(val)) { // this goes first, tasks calls _are_ functions!
168
+ tasks[key] = val.task
147
169
  len = key.length
148
170
  } else if (typeof val === 'string') {
149
171
  props[key] = val
150
172
  } else if (typeof val === 'function') {
151
- tasks[key] = makeTask(buildFile, tasks, props, val, key)
173
+ tasks[key] = new TaskImpl(key, buildFile, val, tasks, props)
174
+ // tasks[key] = makeTask(buildFile, tasks, props, val, key)
152
175
  len = key.length
153
176
  }
154
177
 
@@ -157,33 +180,21 @@ export function build<
157
180
  if (len > logOptions.taskLength) logOptions.taskLength = len
158
181
  }
159
182
 
160
- /* Create the "call" function for this build */
161
- const invoke = async function invoke(
162
- taskNames: string[],
183
+ /* A function _starting_ a build */
184
+ const start = async function start<R>(
185
+ callback: (state: State) => Promise<R>,
163
186
  overrideProps: Record<string, string | undefined> = {},
164
- ): Promise<void> {
165
- /* Our "root" logger and initial (empty) state */
166
- const logger = getLogger()
167
- const state = {
168
- cache: new Map<Task, Promise<Result>>(),
169
- props: Object.assign({}, props, overrideProps),
170
- fails: new Set<Task>(),
171
- stack: [] as Task[],
172
- tasks: tasks,
173
- }
174
-
187
+ ): Promise<R> {
175
188
  /* Let's go down to business */
189
+ const state = makeState({ tasks, props: merge(props, overrideProps) })
190
+ const logger = getLogger()
176
191
  logger.notice('Starting...')
177
192
  const now = Date.now()
178
193
 
179
194
  try {
180
- /* Run tasks _serially_ */
181
- for (const name of taskNames) {
182
- const task = tasks[name]
183
- assert(task, `Task ${$t(name)} not found in build ${$p(buildFile)}`)
184
- await task.invoke(state, name)
185
- }
195
+ const result = await callback(state)
186
196
  logger.notice(`Build successful ${$ms(Date.now() - now)}`)
197
+ return result
187
198
  } catch (error) {
188
199
  if (state.fails.size) {
189
200
  logger.error('')
@@ -195,14 +206,40 @@ export function build<
195
206
  }
196
207
  }
197
208
 
198
- /* Create our build, the collection of all props and tasks */
199
- const compiled = Object.assign(Object.create(null), props, tasks) as Build<D>
209
+ /* Create the "invoke" function for this build */
210
+ const invoke = async function invoke(
211
+ taskNames: readonly string[],
212
+ overrideProps: Record<string, string | undefined> = {},
213
+ ): Promise<void> {
214
+ await start(async (state: State): Promise<void> => {
215
+ for (const name of taskNames) {
216
+ const task = tasks[name]
217
+ assert(task, `Task ${$t(name)} not found in build ${$p(buildFile)}`)
218
+ await task.invoke(state, name)
219
+ }
220
+ }, overrideProps)
221
+ }
222
+
223
+ /* Convert our Tasks into TaskCalls */
224
+ const callables: Record<string, TaskCall> = {}
225
+ for (const [ name, task ] of Object.entries(tasks)) {
226
+ /** The callable function, using "start" */
227
+ const callable = async (overrideProps?: Record<string, string>): Promise<Result> =>
228
+ start(async (state: State): Promise<Result> =>
229
+ task.invoke(state, name), overrideProps)
230
+
231
+ /* Extra properties for our callable: marker, task and name */
232
+ callables[name] = Object.defineProperties(callable, {
233
+ [taskCallMarker]: { value: taskCallMarker },
234
+ 'task': { value: task },
235
+ 'name': { value: name },
236
+ }) as TaskCall
237
+ }
200
238
 
201
- /* Sneak our "call" function in the build, for the CLI and "call" below */
239
+ /* Create and return our build */
240
+ const compiled = merge(props, callables)
202
241
  Object.defineProperty(compiled, buildMarker, { value: invoke })
203
-
204
- /* All done! */
205
- return compiled
242
+ return compiled as Build<D>
206
243
  }
207
244
 
208
245
  /** Check if the specified build is actually a {@link Build} */
@@ -211,13 +248,12 @@ export function isBuild(build: any): build is Build<Record<string, any>> {
211
248
  }
212
249
 
213
250
  /** Invoke a number of tasks in a {@link Build} */
214
- export function invokeTasks(
215
- build: Build,
216
- tasks: string[],
217
- props?: Record<string, string>,
251
+ export function invokeTasks<B extends Build>(
252
+ build: B,
253
+ tasks: readonly BuildTasks<B>[],
254
+ props?: BuildProps<B>,
218
255
  ): Promise<void> {
219
- if (build && (typeof build === 'object') &&
220
- (buildMarker in build) && (typeof build[buildMarker] === 'function')) {
256
+ if (isBuild(build)) {
221
257
  return build[buildMarker](tasks, props)
222
258
  } else {
223
259
  throw new TypeError('Invalid build instance')
@@ -228,38 +264,36 @@ export function invokeTasks(
228
264
  * HOOKS *
229
265
  * ========================================================================== */
230
266
 
231
- type TaskNames<B extends Build> = keyof {
232
- [ name in keyof B as B[name] extends Task ? name : never ] : any
233
- }
234
-
267
+ /** Make sure that the specified hooks run _before_ the given tasks */
235
268
  export function hookBefore<B extends Build, T extends keyof B>(
236
269
  build: B,
237
- taskName: string & T & TaskNames<B>,
238
- hooks: (string & Exclude<TaskNames<B>, T>)[],
270
+ taskName: string & T & BuildTasks<B>,
271
+ hooks: readonly (string & Exclude<BuildTasks<B>, T>)[],
239
272
  ): void {
240
- const task = build[taskName]
241
- assert(isTask(task), `Task "${$t(taskName)}" not found in build`)
273
+ const taskCall = build[taskName]
274
+ assert(isTaskCall(taskCall), `Task "${$t(taskName)}" not found in build`)
242
275
 
243
276
  for (const hook of hooks) {
244
277
  const beforeHook = build[hook]
245
- assert(isTask(beforeHook), `Task "${$t(hook)}" to hook before "${$t(taskName)}" not found in build`)
246
- if (task.before.includes(beforeHook)) continue
247
- task.before.push(beforeHook)
278
+ assert(isTaskCall(beforeHook), `Task "${$t(hook)}" to hook before "${$t(taskName)}" not found in build`)
279
+ if (taskCall.task.before.includes(beforeHook.task)) continue
280
+ taskCall.task.before.push(beforeHook.task)
248
281
  }
249
282
  }
250
283
 
284
+ /** Make sure that the specified hooks run _after_ the given tasks */
251
285
  export function hookAfter<B extends Build, T extends keyof B>(
252
286
  build: B,
253
- taskName: string & T & TaskNames<B>,
254
- hooks: (string & Exclude<TaskNames<B>, T>)[],
287
+ taskName: string & T & BuildTasks<B>,
288
+ hooks: readonly (string & Exclude<BuildTasks<B>, T>)[],
255
289
  ): void {
256
- const task = build[taskName]
257
- assert(isTask(task), `Task "${$t(taskName)}" not found in build`)
290
+ const taskCall = build[taskName]
291
+ assert(isTaskCall(taskCall), `Task "${$t(taskName)}" not found in build`)
258
292
 
259
293
  for (const hook of hooks) {
260
294
  const afterHook = build[hook]
261
- assert(isTask(afterHook), `Task "${$t(hook)}" to hook after "${$t(taskName)}" not found in build`)
262
- if (task.after.includes(afterHook)) continue
263
- task.after.push(afterHook)
295
+ assert(isTaskCall(afterHook), `Task "${$t(hook)}" to hook after "${$t(taskName)}" not found in build`)
296
+ if (taskCall.task.after.includes(afterHook.task)) continue
297
+ taskCall.task.after.push(afterHook.task)
264
298
  }
265
299
  }
package/src/files.ts CHANGED
@@ -1,6 +1,13 @@
1
+ import { inspect } from 'node:util'
2
+
1
3
  import { assert } from './asserts'
2
4
  import { mkdir, writeFile } from './fs'
3
- import { assertRelativeChildPath, getAbsoluteParent, resolveAbsolutePath } from './paths'
5
+ import {
6
+ assertRelativeChildPath,
7
+ getAbsoluteParent,
8
+ getCurrentWorkingDirectory,
9
+ resolveAbsolutePath,
10
+ } from './paths'
4
11
 
5
12
  import type { AbsolutePath } from './paths'
6
13
 
@@ -38,13 +45,12 @@ export class Files {
38
45
  * Create a new {@link Files} instance rooted in the specified `directory`
39
46
  * relative to the specified {@link Run}'s directory.
40
47
  */
41
- constructor(directory: AbsolutePath) {
42
- this._directory = directory
48
+ constructor(directory?: AbsolutePath) {
49
+ this._directory = directory || getCurrentWorkingDirectory()
43
50
  this._files = []
44
51
 
45
52
  // Nicety for "console.log" / "util.inspect"...
46
- const inspect = Symbol.for('nodejs.util.inspect.custom')
47
- Object.defineProperty(this, inspect, { value: () => ({
53
+ Object.defineProperty(this, inspect.custom, { value: () => ({
48
54
  directory: this._directory,
49
55
  files: [ ...this._files ],
50
56
  }) })
@@ -76,9 +82,11 @@ export class Files {
76
82
  }
77
83
 
78
84
  /** Create a new {@link FilesBuilder} creating {@link Files} instances. */
85
+ static builder(): FilesBuilder
79
86
  static builder(files: Files): FilesBuilder
80
87
  static builder(directory: AbsolutePath): FilesBuilder
81
- static builder(arg: Files | AbsolutePath): FilesBuilder {
88
+ static builder(arg?: Files | AbsolutePath): FilesBuilder {
89
+ if (! arg) arg = getCurrentWorkingDirectory()
82
90
  const directory = typeof arg === 'string' ? arg : arg.directory
83
91
  const set = typeof arg === 'string' ? new Set<string>() : new Set(arg._files)
84
92
 
package/src/helpers.ts CHANGED
@@ -150,14 +150,14 @@ export function merge(pipes: (Pipe | Files | Promise<Files>)[]): Pipe {
150
150
  const context = requireContext()
151
151
  return new PipeImpl(context, Promise.resolve().then(async () => {
152
152
  // No pipes? Just send off an empty pipe...
153
- if (pipes.length === 0) return Files.builder(getCurrentWorkingDirectory()).build()
153
+ if (pipes.length === 0) return new Files()
154
154
 
155
155
  // Await for all pipes / files / files promises
156
156
  const awaited = await assertPromises<Files>(pipes)
157
157
  const results = awaited.filter((result) => result.length)
158
158
 
159
159
  // No files in anything to be merged? Again send off an empty pipe...
160
- if (results.length === 0) return Files.builder(getCurrentWorkingDirectory()).build()
160
+ if (results.length === 0) return new Files()
161
161
 
162
162
  // Find the common directory between all the Files instances
163
163
  const [ firstDir, ...otherDirs ] = results.map((f) => f.directory)
@@ -184,8 +184,7 @@ export function merge(pipes: (Pipe | Files | Promise<Files>)[]): Pipe {
184
184
  */
185
185
  export function noop(): Pipe {
186
186
  const context = requireContext()
187
- const files = new Files(getCurrentWorkingDirectory())
188
- return new PipeImpl(context, Promise.resolve(files))
187
+ return new PipeImpl(context, Promise.resolve(new Files()))
189
188
  }
190
189
 
191
190
  /**
@@ -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'
@@ -232,18 +233,8 @@ class LogOptionsImpl extends EventEmitter implements LogOptions {
232
233
  }
233
234
  }
234
235
 
235
- /* Unique symbol to share `LogOptions` per process */
236
- const optionsKey = Symbol.for('plugjs.plug.logging.logOptions')
237
-
238
- /** Get the shared _per process_ instance of our {@link LogOptions}. */
239
- function getLogOptions(): LogOptions {
240
- let options: LogOptions = (<any> globalThis)[optionsKey]
241
- if (! options) {
242
- options = new LogOptionsImpl()
243
- ;(<any> globalThis)[optionsKey] = options
244
- }
245
- return options
246
- }
236
+ /** Singleton key for {@link LogOptions} instance. */
237
+ const optionsKey = Symbol.for('plugjs:plug:types:LogOptions')
247
238
 
248
239
  /** Shared instance of our {@link LogOptions}. */
249
- export const logOptions = getLogOptions()
240
+ export const logOptions = getSingleton(optionsKey, () => new LogOptionsImpl())
@@ -1,10 +1,10 @@
1
1
  import { $p } from '../logging/colors'
2
2
  import { ForkingPlug, type ForkOptions } from '../fork'
3
3
  import { requireFilename } from '../paths'
4
+ import { invokeTasks, isBuild } from '../build'
4
5
 
5
6
  import type { Files } from '../files'
6
7
  import type { Context, Plug } from '../pipe'
7
- import type { Build } from '../types'
8
8
 
9
9
  /** Writes some info about the current {@link Files} being passed around. */
10
10
  export class RunBuildInternal implements Plug<void> {
@@ -28,25 +28,12 @@ export class RunBuildInternal implements Plug<void> {
28
28
  if (! isBuild(maybeBuild)) {
29
29
  context.log.fail(`File ${$p(file)} did not export a proper build`)
30
30
  } else {
31
- await maybeBuild[buildMarker](tasks, this._props)
31
+ await invokeTasks(maybeBuild, tasks, this._props)
32
32
  }
33
33
  }
34
34
  }
35
35
  }
36
36
 
37
- /** Symbol indicating that an object is a {@link Build}. */
38
- const buildMarker = Symbol.for('plugjs:isBuild')
39
-
40
- /** Check if the specified build is actually a {@link Build} */
41
- function isBuild(build: any): build is Build<Record<string, any>> & {
42
- [buildMarker]: (
43
- tasks: readonly string[],
44
- props?: Record<string, string | undefined>,
45
- ) => Promise<void>
46
- } {
47
- return build && typeof build[buildMarker] === 'function'
48
- }
49
-
50
37
  export class RunBuild extends ForkingPlug {
51
38
  constructor(
52
39
  tasks: readonly string[],
@@ -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
  })