@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/build.ts CHANGED
@@ -1,78 +1,126 @@
1
1
  import { assert } from './asserts'
2
2
  import { runAsync } from './async'
3
- import { $ms, $p, $t, getLogger, logOptions } from './logging'
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
- state = { stack, cache, 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
+ })
55
103
 
56
104
  /* Create run context and build */
57
- const context = new Context(task.buildFile, taskName)
105
+ const context = new Context(this.buildFile, taskName)
58
106
 
59
107
  /* The build (the `this` value calling the definition) is a proxy */
60
108
  const build = new Proxy({}, {
61
- get(_: any, name: string): void | string | (() => Pipe) {
109
+ get: (_: any, name: string): void | string | (() => Pipe) => {
62
110
  // Tasks first, props might come also from environment
63
- if (name in tasks) {
111
+ if (name in state.tasks) {
64
112
  return (): Pipe => {
65
- const promise = tasks[name]!.invoke(state, name)
113
+ const promise = (state as any).tasks[name]!.invoke(state, name)
66
114
  return new PipeImpl(context, promise)
67
115
  }
68
- } else if (name in props) {
69
- return props[name]
116
+ } else if (name in state.props) {
117
+ return (state as any).props[name]
70
118
  }
71
119
  },
72
120
  })
73
121
 
74
122
  /* Run all tasks hooked _before_ this one */
75
- 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)
76
124
 
77
125
  /* Some logging */
78
126
  context.log.info('Running...')
@@ -80,54 +128,31 @@ function makeTask(
80
128
 
81
129
  /* Run asynchronously in an asynchronous context */
82
130
  const promise = runAsync(context, taskName, async () => {
83
- return await _def.call(build) || undefined
131
+ return await this._def.call(build) || undefined
84
132
  }).then(async (result) => {
85
133
  const level = taskName.startsWith('_') ? 'info' : 'notice'
86
134
  context.log[level](`Success ${$ms(Date.now() - now)}`)
87
135
  return result
88
136
  }).catch((error) => {
137
+ state.fails.add(this)
89
138
  throw context.log.fail(`Failure ${$ms(Date.now() - now)}`, error)
90
139
  }).finally(async () => {
91
140
  await ContextPromises.wait(context)
92
141
  }).then(async (result) => {
93
- 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)
94
143
  return result
95
144
  })
96
145
 
97
146
  /* Cache the resulting promise and return it */
98
- cache.set(task, promise)
99
- return promise
147
+ state.cache.set(this, promise)
148
+ return promise as Promise<R>
100
149
  }
101
-
102
- /* Create the new Task. The function will simply create an empty state */
103
- const task: Task = Object.assign((overrideProps: Props = {}) => {
104
- const state: State = {
105
- cache: new Map<Task, Promise<Result>>(),
106
- stack: [] as Task[],
107
- props: Object.assign({}, props, overrideProps),
108
- tasks: tasks,
109
- }
110
- return invoke(state, _name)
111
- }, { buildFile, tasks, props, invoke, before: [], after: [] })
112
-
113
- /* Assign the task's marker and name and return it */
114
- Object.defineProperty(task, taskMarker, { value: true })
115
- Object.defineProperty(task, 'name', { value: _name })
116
- return task
117
150
  }
118
151
 
119
152
  /* ========================================================================== *
120
153
  * BUILD COMPILER *
121
154
  * ========================================================================== */
122
155
 
123
- /**
124
- * Symbol indicating that an object is a {@link Build}.
125
- *
126
- * In a compiled {@link Build} this symbol will be associated with a function
127
- * taking an array of strings (task names) and record of props to override
128
- */
129
- const buildMarker = Symbol.for('plugjs:isBuild')
130
-
131
156
  /** Compile a {@link BuildDef | build definition} into a {@link Build} */
132
157
  export function build<
133
158
  D extends BuildDef, B extends ThisBuild<D>
@@ -139,13 +164,14 @@ export function build<
139
164
  /* Iterate through all definition extracting properties and tasks */
140
165
  for (const [ key, val ] of Object.entries(def)) {
141
166
  let len = 0
142
- if (isTask(val)) { // this goes first, tasks _are_ functions!
143
- tasks[key] = val
167
+ if (isTaskCall(val)) { // this goes first, tasks calls _are_ functions!
168
+ tasks[key] = val.task
144
169
  len = key.length
145
170
  } else if (typeof val === 'string') {
146
171
  props[key] = val
147
172
  } else if (typeof val === 'function') {
148
- 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)
149
175
  len = key.length
150
176
  }
151
177
 
@@ -154,83 +180,120 @@ export function build<
154
180
  if (len > logOptions.taskLength) logOptions.taskLength = len
155
181
  }
156
182
 
157
- /* Create the "call" function for this build */
158
- const invoke = async function invoke(
159
- taskNames: string[],
183
+ /* A function _starting_ a build */
184
+ const start = async function start<R>(
185
+ callback: (state: State) => Promise<R>,
160
186
  overrideProps: Record<string, string | undefined> = {},
161
- ): Promise<void> {
162
- /* Our "root" logger and initial (empty) state */
163
- const logger = getLogger()
164
- const state = {
165
- cache: new Map<Task, Promise<Result>>(),
166
- stack: [] as Task[],
167
- props: Object.assign({}, props, overrideProps),
168
- tasks: tasks,
169
- }
170
-
187
+ ): Promise<R> {
171
188
  /* Let's go down to business */
189
+ const state = makeState({ tasks, props: merge(props, overrideProps) })
190
+ const logger = getLogger()
172
191
  logger.notice('Starting...')
173
192
  const now = Date.now()
174
193
 
175
194
  try {
176
- /* Run tasks _serially_ */
195
+ const result = await callback(state)
196
+ logger.notice(`Build successful ${$ms(Date.now() - now)}`)
197
+ return result
198
+ } catch (error) {
199
+ if (state.fails.size) {
200
+ logger.error('')
201
+ logger.error(state.fails.size, state.fails.size === 1 ? 'task' : 'tasks', 'failed:')
202
+ state.fails.forEach((task) => logger.error($gry('*'), $t(task.name)))
203
+ logger.error('')
204
+ }
205
+ throw logger.fail(`Build failed ${$ms(Date.now() - now)}`, error)
206
+ }
207
+ }
208
+
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> => {
177
215
  for (const name of taskNames) {
178
216
  const task = tasks[name]
179
217
  assert(task, `Task ${$t(name)} not found in build ${$p(buildFile)}`)
180
218
  await task.invoke(state, name)
181
219
  }
182
- logger.notice(`Build successful ${$ms(Date.now() - now)}`)
183
- } catch (error) {
184
- throw logger.fail(`Build failed ${$ms(Date.now() - now)}`, error)
185
- }
220
+ }, overrideProps)
186
221
  }
187
222
 
188
- /* Create our build, the collection of all props and tasks */
189
- const compiled = Object.assign({}, props, tasks) as Build<D>
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
+ }
190
238
 
191
- /* 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)
192
241
  Object.defineProperty(compiled, buildMarker, { value: invoke })
242
+ return compiled as Build<D>
243
+ }
193
244
 
194
- /* All done! */
195
- return compiled
245
+ /** Check if the specified build is actually a {@link Build} */
246
+ export function isBuild(build: any): build is Build<Record<string, any>> {
247
+ return build && typeof build[buildMarker] === 'function'
248
+ }
249
+
250
+ /** Invoke a number of tasks in a {@link Build} */
251
+ export function invokeTasks<B extends Build>(
252
+ build: B,
253
+ tasks: readonly BuildTasks<B>[],
254
+ props?: BuildProps<B>,
255
+ ): Promise<void> {
256
+ if (isBuild(build)) {
257
+ return build[buildMarker](tasks, props)
258
+ } else {
259
+ throw new TypeError('Invalid build instance')
260
+ }
196
261
  }
197
262
 
198
263
  /* ========================================================================== *
199
264
  * HOOKS *
200
265
  * ========================================================================== */
201
266
 
202
- type TaskNames<B extends Build> = keyof {
203
- [ name in keyof B as B[name] extends Task ? name : never ] : any
204
- }
205
-
267
+ /** Make sure that the specified hooks run _before_ the given tasks */
206
268
  export function hookBefore<B extends Build, T extends keyof B>(
207
269
  build: B,
208
- taskName: string & T & TaskNames<B>,
209
- hooks: (string & Exclude<TaskNames<B>, T>)[],
270
+ taskName: string & T & BuildTasks<B>,
271
+ hooks: readonly (string & Exclude<BuildTasks<B>, T>)[],
210
272
  ): void {
211
- const task = build[taskName]
212
- 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`)
213
275
 
214
276
  for (const hook of hooks) {
215
277
  const beforeHook = build[hook]
216
- assert(isTask(beforeHook), `Task "${$t(hook)}" to hook before "${$t(taskName)}" not found in build`)
217
- if (task.before.includes(beforeHook)) continue
218
- 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)
219
281
  }
220
282
  }
221
283
 
284
+ /** Make sure that the specified hooks run _after_ the given tasks */
222
285
  export function hookAfter<B extends Build, T extends keyof B>(
223
286
  build: B,
224
- taskName: string & T & TaskNames<B>,
225
- hooks: (string & Exclude<TaskNames<B>, T>)[],
287
+ taskName: string & T & BuildTasks<B>,
288
+ hooks: readonly (string & Exclude<BuildTasks<B>, T>)[],
226
289
  ): void {
227
- const task = build[taskName]
228
- 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`)
229
292
 
230
293
  for (const hook of hooks) {
231
294
  const afterHook = build[hook]
232
- assert(isTask(afterHook), `Task "${$t(hook)}" to hook after "${$t(taskName)}" not found in build`)
233
- if (task.after.includes(afterHook)) continue
234
- 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)
235
298
  }
236
299
  }