@plugjs/plug 0.1.0 → 0.1.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 (53) 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/build.cjs +6 -8
  6. package/dist/build.cjs.map +1 -1
  7. package/dist/build.mjs +6 -8
  8. package/dist/build.mjs.map +1 -1
  9. package/dist/files.cjs +4 -8
  10. package/dist/files.cjs.map +1 -1
  11. package/dist/files.mjs +4 -8
  12. package/dist/files.mjs.map +1 -1
  13. package/dist/fs.cjs.map +1 -1
  14. package/dist/fs.mjs.map +1 -1
  15. package/dist/helpers.cjs +10 -0
  16. package/dist/helpers.cjs.map +1 -1
  17. package/dist/helpers.d.ts +6 -0
  18. package/dist/helpers.mjs +9 -0
  19. package/dist/helpers.mjs.map +1 -1
  20. package/dist/logging/emit.d.ts +1 -1
  21. package/dist/logging/levels.d.ts +6 -6
  22. package/dist/logging/report.cjs +1 -2
  23. package/dist/logging/report.cjs.map +1 -1
  24. package/dist/logging/report.d.ts +1 -1
  25. package/dist/logging/report.mjs +1 -2
  26. package/dist/logging/report.mjs.map +1 -1
  27. package/dist/logging.cjs +1 -4
  28. package/dist/logging.cjs.map +1 -1
  29. package/dist/logging.d.ts +1 -1
  30. package/dist/logging.mjs +1 -4
  31. package/dist/logging.mjs.map +1 -1
  32. package/dist/paths.cjs +22 -24
  33. package/dist/paths.cjs.map +1 -1
  34. package/dist/paths.d.ts +8 -8
  35. package/dist/paths.mjs +22 -24
  36. package/dist/paths.mjs.map +1 -1
  37. package/dist/pipe.d.ts +7 -7
  38. package/dist/plugs/esbuild/fix-extensions.cjs +3 -4
  39. package/dist/plugs/esbuild/fix-extensions.cjs.map +1 -1
  40. package/dist/plugs/esbuild/fix-extensions.mjs +3 -4
  41. package/dist/plugs/esbuild/fix-extensions.mjs.map +1 -1
  42. package/dist/plugs/esbuild.d.ts +1 -1
  43. package/dist/types.d.ts +7 -7
  44. package/dist/utils/match.d.ts +1 -1
  45. package/dist/utils/options.d.ts +3 -3
  46. package/extra/ts-loader.mjs +3 -3
  47. package/package.json +20 -14
  48. package/src/asserts.ts +1 -1
  49. package/src/build.ts +179 -0
  50. package/src/files.ts +4 -4
  51. package/src/fs.ts +6 -5
  52. package/src/helpers.ts +16 -0
  53. package/src/paths.ts +35 -40
package/src/build.ts ADDED
@@ -0,0 +1,179 @@
1
+ import { assert } from './asserts'
2
+ import { runAsync } from './async'
3
+ import { $ms, $t, getLogger, log, logOptions } from './logging'
4
+ import { Context, ContextPromises, PipeImpl } from './pipe'
5
+ import { findCaller } from './utils/caller'
6
+ import { parseOptions } from './utils/options'
7
+
8
+ import type { Pipe } from './index'
9
+ import type { AbsolutePath } from './paths'
10
+ import type {
11
+ Build,
12
+ BuildDef,
13
+ Props,
14
+ Result,
15
+ State,
16
+ Task,
17
+ TaskDef,
18
+ Tasks,
19
+ ThisBuild,
20
+ } from './types'
21
+
22
+ /* ========================================================================== *
23
+ * TASK *
24
+ * ========================================================================== */
25
+
26
+ class TaskImpl implements Task {
27
+ constructor(
28
+ public readonly buildFile: AbsolutePath,
29
+ public readonly tasks: Tasks,
30
+ public readonly props: Props,
31
+ private readonly _def: TaskDef,
32
+ ) {}
33
+
34
+ invoke(state: State, taskName: string): Promise<Result> {
35
+ assert(! state.stack.includes(this), `Recursion detected calling ${$t(taskName)}`)
36
+
37
+ /* Check cache */
38
+ const cached = state.cache.get(this)
39
+ if (cached) return cached
40
+
41
+ /* Create new substate merging sibling tasks/props and adding this to the stack */
42
+ const props: Record<string, string> = Object.assign({}, this.props, state.props)
43
+ const tasks: Record<string, Task> = Object.assign({}, this.tasks, state.tasks)
44
+ const stack = [ ...state.stack, this ]
45
+ const cache = state.cache
46
+
47
+ /* Create run context and build */
48
+ const context = new Context(this.buildFile, taskName)
49
+
50
+ const build = new Proxy({}, {
51
+ get(_: any, name: string): void | string | (() => Pipe) {
52
+ // Tasks first, props might come also from environment
53
+ if (name in tasks) {
54
+ return (): Pipe => {
55
+ const state = { stack, cache, tasks, props }
56
+ const promise = tasks[name]!.invoke(state, name)
57
+ return new PipeImpl(context, promise)
58
+ }
59
+ } else if (name in props) {
60
+ return props[name]
61
+ }
62
+ },
63
+ })
64
+
65
+ /* Some logging */
66
+ context.log.info('Running...')
67
+ const now = Date.now()
68
+
69
+ /* Run asynchronously in an asynchronous context */
70
+ const promise = runAsync(context, taskName, async () => {
71
+ return await this._def.call(build) || undefined
72
+ }).then((result) => {
73
+ context.log.notice(`Success ${$ms(Date.now() - now)}`)
74
+ return result
75
+ }).catch((error) => {
76
+ throw context.log.fail(`Failure ${$ms(Date.now() - now)}`, error)
77
+ }).finally(() => ContextPromises.wait(context))
78
+
79
+ /* Cache the resulting promise and return it */
80
+ cache.set(this, promise)
81
+ return promise
82
+ }
83
+ }
84
+
85
+ /* ========================================================================== *
86
+ * BUILD COMPILER *
87
+ * ========================================================================== */
88
+
89
+ /** Symbol indicating that an object is a {@link Build} */
90
+ const buildMarker = Symbol.for('plugjs:isBuild')
91
+
92
+ /** Compile a {@link BuildDef | build definition} into a {@link Build} */
93
+ export function build<
94
+ D extends BuildDef, B extends ThisBuild<D>
95
+ >(def: D & ThisType<B>): Build<D> {
96
+ const buildFile = findCaller(build)
97
+ const tasks: Record<string, Task> = {}
98
+ const props: Record<string, string> = {}
99
+
100
+ /* Iterate through all definition extracting properties and tasks */
101
+ for (const [ key, val ] of Object.entries(def)) {
102
+ let len = 0
103
+ if (typeof val === 'string') {
104
+ props[key] = val
105
+ } else if (typeof val === 'function') {
106
+ tasks[key] = new TaskImpl(buildFile, tasks, props, val)
107
+ len = key.length
108
+ } else if (val instanceof TaskImpl) {
109
+ tasks[key] = val
110
+ len = key.length
111
+ }
112
+
113
+ /* Update the logger's own "taskLength" for nice printing */
114
+ /* coverage ignore if */
115
+ if (len > logOptions.taskLength) logOptions.taskLength = len
116
+ }
117
+
118
+ /* Create the "call" function for this build */
119
+ const invoke: InvokeBuild = async function invoke(
120
+ taskNames: string[],
121
+ overrideProps: Record<string, string | undefined> = {},
122
+ ): Promise<void> {
123
+ /* Our "root" logger and initial (empty) state */
124
+ const logger = getLogger()
125
+ const state = {
126
+ cache: new Map<Task, Promise<Result>>(),
127
+ stack: [] as Task[],
128
+ props: Object.assign({}, props, overrideProps),
129
+ tasks: tasks,
130
+ }
131
+
132
+ /* Let's go down to business */
133
+ logger.notice('Starting...')
134
+ const now = Date.now()
135
+
136
+ try {
137
+ /* Run tasks _serially_ */
138
+ for (const name of taskNames) {
139
+ const task = tasks[name]
140
+ assert(task, `Task ${$t(name)} not found in build yoooo`)
141
+ await task.invoke(state, name)
142
+ }
143
+ logger.notice(`Build successful ${$ms(Date.now() - now)}`)
144
+ } catch (error) {
145
+ throw logger.fail(`Build failed ${$ms(Date.now() - now)}`, error)
146
+ }
147
+ }
148
+
149
+ /* Create our build, the collection of all props and tasks */
150
+ const compiled = Object.assign({}, props, tasks) as Build<D>
151
+
152
+ /* Sneak our "call" function in the build, for the CLI and "call" below */
153
+ Object.defineProperty(compiled, buildMarker, { value: invoke })
154
+
155
+ /* All done! */
156
+ return compiled
157
+ }
158
+
159
+ /** Internal type describing the build invocation function */
160
+ type InvokeBuild = (tasks: string[], props?: Record<string, string | undefined>) => Promise<void>
161
+
162
+ /** Serially invoke tasks in a {@link Build} optionally overriding properties */
163
+ export async function invoke(
164
+ build: Build,
165
+ ...args:
166
+ | [ ...taskNames: [ string, ...string[] ] ]
167
+ | [ ...taskNames: [ string, ...string[] ], options: Record<string, string | undefined> ]
168
+ ): Promise<void> {
169
+ const { params: tasks, options: props } = parseOptions(args, {})
170
+
171
+ /* Get the calling function from the sneaked-in property in build */
172
+ const invoke: InvokeBuild = (build as any)[buildMarker]
173
+
174
+ /* Triple check that we actually _have_ a function (no asserts here, log!) */
175
+ if (typeof invoke !== 'function') log.fail('Unknown build type')
176
+
177
+ /* Call everyhin that needs to be called */
178
+ return await invoke(tasks, props)
179
+ }
package/src/files.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { assert } from './asserts'
1
2
  import { mkdir, writeFile } from './fs'
2
3
  import { assertRelativeChildPath, getAbsoluteParent, resolveAbsolutePath } from './paths'
3
4
 
@@ -88,9 +89,8 @@ export class Files {
88
89
  directory: instance.directory,
89
90
 
90
91
  add(...files: string[]): FilesBuilder {
91
- if (built) throw new Error('FileBuilder "build()" already called')
92
+ assert(! built, 'FileBuilder "build()" already called')
92
93
 
93
- if (typeof files === 'string') files = [ files ]
94
94
  for (const file of files) {
95
95
  const relative = assertRelativeChildPath(instance.directory, file)
96
96
  set.add(relative)
@@ -100,7 +100,7 @@ export class Files {
100
100
  },
101
101
 
102
102
  merge(...args: Files[]): FilesBuilder {
103
- if (built) throw new Error('FileBuilder "build()" already called')
103
+ assert(! built, 'FileBuilder "build()" already called')
104
104
 
105
105
  for (const files of args) {
106
106
  for (const file of files.absolutePaths()) {
@@ -124,7 +124,7 @@ export class Files {
124
124
  },
125
125
 
126
126
  build(): Files {
127
- if (built) throw new Error('FileBuilder "build()" already called')
127
+ assert(! built, 'FileBuilder "build()" already called')
128
128
 
129
129
  built = true
130
130
  instance._files.push(...set)
package/src/fs.ts CHANGED
@@ -30,11 +30,12 @@ const fs = Object.entries(fsp as any).reduce((fs, [ key, val ]) => {
30
30
  /* If the value is a function, wrap it! */
31
31
  const f = function(...args: any[]): any {
32
32
  /* Call the function, and _catch_ any error */
33
- return val.apply(fsp, args).catch((error: any) => {
34
- /* For any error caught, we fill in the stack trace */
35
- Error.captureStackTrace(error)
36
- throw error
37
- })
33
+ return val.apply(fsp, args)
34
+ .catch(/* coverage ignore next*/ (error: any) => {
35
+ /* For any error caught, we fill in the stack trace */
36
+ Error.captureStackTrace(error)
37
+ throw error
38
+ })
38
39
  }
39
40
 
40
41
  /* Make sure that the functions are called correctly */
package/src/helpers.ts CHANGED
@@ -1,3 +1,7 @@
1
+ import { tmpdir } from 'node:os'
2
+ import { join } from 'node:path'
3
+ import { mkdtempSync } from 'node:fs'
4
+
1
5
  import { assert, assertPromises } from './asserts'
2
6
  import { requireContext } from './async'
3
7
  import { Files } from './files'
@@ -65,6 +69,7 @@ export async function rmrf(directory: string): Promise<void> {
65
69
  assert(dir !== context.resolve('@'),
66
70
  `Cowardly refusing to wipe build file directory ${$p(dir)}`)
67
71
 
72
+ /* coverage ignore if */
68
73
  if (! resolveDirectory(dir)) {
69
74
  log.info('Directory', $p(dir), 'not found')
70
75
  return
@@ -159,6 +164,17 @@ export function isDirectory(...paths: [ string, ...string[] ]): AbsolutePath | u
159
164
  return resolveDirectory(path)
160
165
  }
161
166
 
167
+ /**
168
+ * Create a temporary directory and return its {@link AbsolutePath}.
169
+ *
170
+ * The directory will be rooted in `/tmp` or wherever `os.tmpdir()` decides.
171
+ */
172
+ export function mkdtemp(): AbsolutePath {
173
+ const prefix = join(tmpdir(), 'plugjs-')
174
+ const path = mkdtempSync(prefix)
175
+ return resolve(path)
176
+ }
177
+
162
178
  /**
163
179
  * Execute a command and await for its result from within a task.
164
180
  *
package/src/paths.ts CHANGED
@@ -14,9 +14,8 @@ export type AbsolutePath = string & { __brand_absolute_path: never }
14
14
 
15
15
  /** Resolve a path into an {@link AbsolutePath} */
16
16
  export function resolveAbsolutePath(directory: AbsolutePath, ...paths: string[]): AbsolutePath {
17
- const resolved = resolve(directory, ...paths) as AbsolutePath
18
- assert(isAbsolute(resolved), `Path "${join(...paths)}" resolved in "${directory}" is not absolute`)
19
- return resolved
17
+ assertAbsolutePath(directory)
18
+ return resolve(directory, ...paths) as AbsolutePath
20
19
  }
21
20
 
22
21
  /**
@@ -32,8 +31,6 @@ export function resolveAbsolutePath(directory: AbsolutePath, ...paths: string[])
32
31
  * ```
33
32
  */
34
33
  export function resolveRelativeChildPath(directory: AbsolutePath, ...paths: string[]): string | undefined {
35
- assertAbsolutePath(directory)
36
-
37
34
  const abs = resolveAbsolutePath(directory, ...paths)
38
35
  const rel = relative(directory, abs)
39
36
  return (isAbsolute(rel) || (rel === '..') || rel.startsWith(`..${sep}`)) ? undefined : rel
@@ -43,7 +40,6 @@ export function resolveRelativeChildPath(directory: AbsolutePath, ...paths: stri
43
40
  * Asserts that a path is a relative path to the directory specified, failing
44
41
  * the build if it's not (see also {@link resolveRelativeChildPath}).
45
42
  */
46
-
47
43
  export function assertRelativeChildPath(directory: AbsolutePath, ...paths: string[]): string {
48
44
  const relative = resolveRelativeChildPath(directory, ...paths)
49
45
  assert(relative, `Path "${join(...paths)}" not relative to "${directory}"`)
@@ -76,6 +72,38 @@ export function getCurrentWorkingDirectory(): AbsolutePath {
76
72
  return cwd
77
73
  }
78
74
 
75
+ /**
76
+ * Return the _common_ path amongst all specified paths.
77
+ *
78
+ * While the first `path` _must_ be an {@link AbsolutePath}, all other `paths`
79
+ * can be _relative_ and will be resolved against the first `path`.
80
+ */
81
+ export function commonPath(path: AbsolutePath, ...paths: string[]): AbsolutePath {
82
+ assertAbsolutePath(path)
83
+
84
+ // Here the first path will be split into its components
85
+ // on win => [ 'C:', 'Windows', 'System32' ]
86
+ // on unx => [ '', 'usr'
87
+ const components = normalize(path).split(sep)
88
+
89
+ let length = components.length
90
+ for (const current of paths) {
91
+ const absolute = resolveAbsolutePath(path, current)
92
+ const parts = absolute.split(sep)
93
+ for (let i = 0; i < length; i++) {
94
+ if (components[i] !== parts[i]) {
95
+ length = i
96
+ break
97
+ }
98
+ }
99
+
100
+ assert(length, 'No common ancestors amongst paths')
101
+ }
102
+
103
+ const common = components.slice(0, length).join(sep)
104
+ assertAbsolutePath(common)
105
+ return common
106
+ }
79
107
 
80
108
  /* ========================================================================== *
81
109
  * MODULE RESOLUTION FUNCTIONS *
@@ -130,7 +158,7 @@ export function requireResolve(__fileurl: string, module: string): AbsolutePath
130
158
  // ... then delegate to the standard "require.resolve(...)"
131
159
  const url = pathToFileURL(file)
132
160
  const ext = extname(file)
133
- const checks = ext ? [ `${module}`, `${module}${ext}`, `${module}/index${ext}` ] : [ module ]
161
+ const checks = [ `${module}`, `${module}${ext}`, `${module}/index${ext}` ]
134
162
 
135
163
  for (const check of checks) {
136
164
  const resolved = fileURLToPath(new URL(check, url)) as AbsolutePath
@@ -147,39 +175,6 @@ export function requireResolve(__fileurl: string, module: string): AbsolutePath
147
175
  return required
148
176
  }
149
177
 
150
- /**
151
- * Return the _common_ path amongst all specified paths.
152
- *
153
- * While the first `path` _must_ be an {@link AbsolutePath}, all other `paths`
154
- * can be _relative_ and will be resolved against the first `path`.
155
- */
156
- export function commonPath(path: AbsolutePath, ...paths: string[]): AbsolutePath {
157
- assertAbsolutePath(path)
158
-
159
- // Here the first path will be split into its components
160
- // on win => [ 'C:', 'Windows', 'System32' ]
161
- // on unx => [ '', 'usr'
162
- const components = normalize(path).split(sep)
163
-
164
- let length = components.length
165
- for (const current of paths) {
166
- const absolute = resolveAbsolutePath(path, current)
167
- const parts = absolute.split(sep)
168
- for (let i = 0; i < length; i++) {
169
- if (components[i] !== parts[i]) {
170
- length = i
171
- break
172
- }
173
- }
174
-
175
- assert(length, 'No common ancestors amongst paths')
176
- }
177
-
178
- const common = components.slice(0, length).join(sep)
179
- assertAbsolutePath(common)
180
- return common
181
- }
182
-
183
178
  /* ========================================================================== *
184
179
  * FILE CHECKING FUNCTIONS *
185
180
  * ========================================================================== */