@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.
- package/dist/asserts.cjs +1 -1
- package/dist/asserts.cjs.map +1 -1
- package/dist/asserts.mjs +1 -1
- package/dist/asserts.mjs.map +1 -1
- package/dist/build.cjs +6 -8
- package/dist/build.cjs.map +1 -1
- package/dist/build.mjs +6 -8
- package/dist/build.mjs.map +1 -1
- package/dist/files.cjs +4 -8
- package/dist/files.cjs.map +1 -1
- package/dist/files.mjs +4 -8
- package/dist/files.mjs.map +1 -1
- package/dist/fs.cjs.map +1 -1
- package/dist/fs.mjs.map +1 -1
- package/dist/helpers.cjs +10 -0
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.ts +6 -0
- package/dist/helpers.mjs +9 -0
- package/dist/helpers.mjs.map +1 -1
- package/dist/logging/emit.d.ts +1 -1
- package/dist/logging/levels.d.ts +6 -6
- package/dist/logging/report.cjs +1 -2
- package/dist/logging/report.cjs.map +1 -1
- package/dist/logging/report.d.ts +1 -1
- package/dist/logging/report.mjs +1 -2
- package/dist/logging/report.mjs.map +1 -1
- package/dist/logging.cjs +1 -4
- package/dist/logging.cjs.map +1 -1
- package/dist/logging.d.ts +1 -1
- package/dist/logging.mjs +1 -4
- package/dist/logging.mjs.map +1 -1
- package/dist/paths.cjs +22 -24
- package/dist/paths.cjs.map +1 -1
- package/dist/paths.d.ts +8 -8
- package/dist/paths.mjs +22 -24
- package/dist/paths.mjs.map +1 -1
- package/dist/pipe.d.ts +7 -7
- package/dist/plugs/esbuild/fix-extensions.cjs +3 -4
- package/dist/plugs/esbuild/fix-extensions.cjs.map +1 -1
- package/dist/plugs/esbuild/fix-extensions.mjs +3 -4
- package/dist/plugs/esbuild/fix-extensions.mjs.map +1 -1
- package/dist/plugs/esbuild.d.ts +1 -1
- package/dist/types.d.ts +7 -7
- package/dist/utils/match.d.ts +1 -1
- package/dist/utils/options.d.ts +3 -3
- package/extra/ts-loader.mjs +3 -3
- package/package.json +20 -14
- package/src/asserts.ts +1 -1
- package/src/build.ts +179 -0
- package/src/files.ts +4 -4
- package/src/fs.ts +6 -5
- package/src/helpers.ts +16 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
18
|
-
|
|
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 =
|
|
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
|
* ========================================================================== */
|