@plugjs/plug 0.3.5 → 0.4.0

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 (77) hide show
  1. package/dist/asserts.cjs +9 -11
  2. package/dist/asserts.cjs.map +1 -1
  3. package/dist/asserts.d.ts +1 -2
  4. package/dist/asserts.mjs +8 -9
  5. package/dist/asserts.mjs.map +1 -1
  6. package/dist/build.cjs +29 -5
  7. package/dist/build.cjs.map +1 -1
  8. package/dist/build.d.ts +4 -0
  9. package/dist/build.mjs +27 -5
  10. package/dist/build.mjs.map +1 -1
  11. package/dist/cli.d.mts +12 -0
  12. package/dist/cli.mjs +266 -0
  13. package/dist/cli.mjs.map +6 -0
  14. package/dist/fork.cjs +30 -12
  15. package/dist/fork.cjs.map +1 -1
  16. package/dist/fork.d.ts +10 -0
  17. package/dist/fork.mjs +31 -13
  18. package/dist/fork.mjs.map +1 -1
  19. package/dist/helpers.cjs +30 -10
  20. package/dist/helpers.cjs.map +2 -2
  21. package/dist/helpers.d.ts +12 -0
  22. package/dist/helpers.mjs +35 -11
  23. package/dist/helpers.mjs.map +2 -2
  24. package/dist/index.cjs +5 -0
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.ts +2 -1
  27. package/dist/index.mjs +4 -1
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/logging/emit.cjs +4 -4
  30. package/dist/logging/emit.cjs.map +1 -1
  31. package/dist/logging/emit.mjs +4 -4
  32. package/dist/logging/emit.mjs.map +1 -1
  33. package/dist/logging/logger.cjs +43 -2
  34. package/dist/logging/logger.cjs.map +1 -1
  35. package/dist/logging/logger.d.ts +36 -3
  36. package/dist/logging/logger.mjs +43 -3
  37. package/dist/logging/logger.mjs.map +1 -1
  38. package/dist/logging/options.cjs +5 -2
  39. package/dist/logging/options.cjs.map +1 -1
  40. package/dist/logging/options.mjs +5 -2
  41. package/dist/logging/options.mjs.map +1 -1
  42. package/dist/logging.cjs +14 -3
  43. package/dist/logging.cjs.map +1 -1
  44. package/dist/logging.d.ts +2 -0
  45. package/dist/logging.mjs +13 -3
  46. package/dist/logging.mjs.map +1 -1
  47. package/dist/plugs/build.cjs +66 -0
  48. package/dist/plugs/build.cjs.map +6 -0
  49. package/dist/plugs/build.d.ts +13 -0
  50. package/dist/plugs/build.mjs +40 -0
  51. package/dist/plugs/build.mjs.map +6 -0
  52. package/dist/types.d.ts +2 -0
  53. package/dist/utils/exec.cjs +5 -12
  54. package/dist/utils/exec.cjs.map +2 -2
  55. package/dist/utils/exec.d.ts +0 -2
  56. package/dist/utils/exec.mjs +6 -13
  57. package/dist/utils/exec.mjs.map +1 -1
  58. package/package.json +7 -9
  59. package/src/asserts.ts +9 -11
  60. package/src/build.ts +33 -4
  61. package/{extra/plug.mts → src/cli.mts} +115 -141
  62. package/src/fork.ts +42 -16
  63. package/src/helpers.ts +53 -1
  64. package/src/index.ts +2 -1
  65. package/src/logging/emit.ts +4 -4
  66. package/src/logging/logger.ts +60 -7
  67. package/src/logging/options.ts +5 -1
  68. package/src/logging.ts +20 -5
  69. package/src/plugs/build.ts +58 -0
  70. package/src/types.ts +2 -0
  71. package/src/utils/exec.ts +6 -20
  72. package/cli/plug.mjs +0 -1385
  73. package/cli/ts-loader.mjs +0 -275
  74. package/cli/tsrun.mjs +0 -1204
  75. package/extra/ts-loader.mts +0 -546
  76. package/extra/tsrun.mts +0 -127
  77. package/extra/utils.ts +0 -150
@@ -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
  }
@@ -96,7 +96,11 @@ class LogOptionsImpl extends EventEmitter implements LogOptions {
96
96
  * and consumed by the `Exec` plug (which has no other way of communicating)
97
97
  */
98
98
  const { fd, ...options } = JSON.parse(process.env.__LOG_OPTIONS || '{}')
99
- if (fd) this._output = new Socket({ fd, readable: false, writable: true }).unref()
99
+ if (fd) {
100
+ const output = new Socket({ fd, readable: false, writable: true }).unref()
101
+ process.on('beforeExit', () => this._output.end())
102
+ this._output = output
103
+ }
100
104
  Object.assign(this, options)
101
105
  }
102
106
 
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,58 @@
1
+ import { $p } from '../logging/colors'
2
+ import { ForkingPlug, type ForkOptions } from '../fork'
3
+ import { requireFilename } from '../paths'
4
+
5
+ import type { Files } from '../files'
6
+ import type { Context, Plug } from '../pipe'
7
+ import type { Build } from '../types'
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 maybeBuild[buildMarker](tasks, this._props)
32
+ }
33
+ }
34
+ }
35
+ }
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
+ export class RunBuild extends ForkingPlug {
51
+ constructor(
52
+ tasks: readonly string[],
53
+ props: Readonly<Record<string, string>>,
54
+ options: ForkOptions,
55
+ ) {
56
+ super(requireFilename(__fileurl), [ tasks, props, options ], RunBuildInternal.name)
57
+ }
58
+ }
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
  /* ========================================================================== *
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()