@plugjs/plug 0.1.4 → 0.2.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/README.md +2 -2
- package/cli/plug.mjs +1397 -0
- package/cli/ts-loader.mjs +273 -0
- package/cli/tsrun.mjs +136 -0
- package/dist/asserts.cjs +1 -0
- package/dist/asserts.cjs.map +1 -1
- package/dist/asserts.mjs +1 -0
- package/dist/asserts.mjs.map +1 -1
- package/dist/build.cjs +51 -24
- package/dist/build.cjs.map +2 -2
- package/dist/build.d.ts +7 -3
- package/dist/build.mjs +50 -24
- package/dist/build.mjs.map +2 -2
- package/dist/files.cjs +9 -0
- package/dist/files.cjs.map +1 -1
- package/dist/files.mjs +9 -0
- package/dist/files.mjs.map +1 -1
- package/dist/fork.cjs +2 -0
- package/dist/fork.cjs.map +1 -1
- package/dist/fork.mjs +2 -0
- package/dist/fork.mjs.map +1 -1
- package/dist/fs.cjs +11 -4
- package/dist/fs.cjs.map +1 -1
- package/dist/fs.mjs +7 -4
- package/dist/fs.mjs.map +1 -1
- package/dist/helpers.cjs +1 -1
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.mjs +1 -1
- package/dist/helpers.mjs.map +1 -1
- package/dist/index.cjs +7 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -1
- package/dist/logging/levels.cjs.map +1 -1
- package/dist/logging/levels.mjs.map +1 -1
- package/dist/logging/logger.cjs.map +1 -1
- package/dist/logging/logger.mjs.map +1 -1
- package/dist/logging/options.cjs +5 -0
- package/dist/logging/options.cjs.map +1 -1
- package/dist/logging/options.mjs +5 -0
- package/dist/logging/options.mjs.map +1 -1
- package/dist/logging/report.cjs.map +1 -1
- package/dist/logging/report.mjs.map +1 -1
- package/dist/logging/spinner.cjs +12 -0
- package/dist/logging/spinner.cjs.map +1 -1
- package/dist/logging/spinner.mjs +12 -0
- package/dist/logging/spinner.mjs.map +1 -1
- package/dist/paths.cjs.map +1 -1
- package/dist/paths.mjs.map +1 -1
- package/dist/pipe.cjs +34 -0
- package/dist/pipe.cjs.map +1 -1
- package/dist/pipe.mjs +34 -0
- package/dist/pipe.mjs.map +1 -1
- package/dist/plugs/debug.cjs.map +1 -1
- package/dist/plugs/debug.mjs.map +1 -1
- package/dist/plugs/edit.cjs +1 -1
- package/dist/plugs/edit.cjs.map +1 -1
- package/dist/plugs/edit.d.ts +2 -2
- package/dist/plugs/edit.mjs +1 -1
- package/dist/plugs/edit.mjs.map +1 -1
- package/dist/plugs/esbuild/fix-extensions.cjs +4 -0
- package/dist/plugs/esbuild/fix-extensions.cjs.map +1 -1
- package/dist/plugs/esbuild/fix-extensions.mjs.map +1 -1
- package/dist/plugs/esbuild.cjs +6 -2
- package/dist/plugs/esbuild.cjs.map +1 -1
- package/dist/plugs/esbuild.mjs +6 -2
- package/dist/plugs/esbuild.mjs.map +1 -1
- package/dist/plugs/exec.cjs.map +1 -1
- package/dist/plugs/exec.mjs.map +1 -1
- package/dist/plugs/exports.cjs +122 -0
- package/dist/plugs/exports.cjs.map +6 -0
- package/dist/plugs/exports.d.ts +17 -0
- package/dist/plugs/exports.mjs +105 -0
- package/dist/plugs/exports.mjs.map +6 -0
- package/dist/plugs/filter.cjs.map +1 -1
- package/dist/plugs/filter.mjs.map +1 -1
- package/dist/plugs.cjs +1 -0
- package/dist/plugs.cjs.map +1 -1
- package/dist/plugs.d.ts +1 -0
- package/dist/plugs.mjs +1 -0
- package/dist/plugs.mjs.map +1 -1
- package/dist/types.d.ts +5 -1
- package/dist/utils/exec.cjs +9 -0
- package/dist/utils/exec.cjs.map +1 -1
- package/dist/utils/exec.mjs +5 -0
- package/dist/utils/exec.mjs.map +1 -1
- package/dist/utils/match.cjs +5 -0
- package/dist/utils/match.cjs.map +1 -1
- package/dist/utils/match.mjs +1 -0
- package/dist/utils/match.mjs.map +1 -1
- package/dist/utils/options.cjs.map +1 -1
- package/dist/utils/options.mjs.map +1 -1
- package/dist/utils/walk.cjs.map +1 -1
- package/dist/utils/walk.mjs.map +1 -1
- package/extra/plug.mts +373 -0
- package/extra/ts-loader.mts +545 -0
- package/extra/tsrun.mts +64 -0
- package/extra/utils.ts +168 -0
- package/package.json +9 -19
- package/src/build.ts +66 -33
- package/src/fork.ts +1 -0
- package/src/helpers.ts +1 -1
- package/src/index.ts +7 -0
- package/src/logging/spinner.ts +2 -0
- package/src/plugs/edit.ts +4 -3
- package/src/plugs/esbuild.ts +0 -1
- package/src/plugs/exports.ts +175 -0
- package/src/plugs.ts +1 -0
- package/src/types.ts +5 -1
- package/types/plugjs.d.ts +0 -2
- package/LICENSE.md +0 -211
- package/NOTICE.md +0 -13
- package/extra/cli.mjs +0 -1356
- package/extra/ts-loader.mjs +0 -223
package/extra/utils.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import _childProcess from 'node:child_process'
|
|
3
|
+
import _fs from 'node:fs'
|
|
4
|
+
import _path from 'node:path'
|
|
5
|
+
import _url from 'node:url'
|
|
6
|
+
import _util from 'node:util'
|
|
7
|
+
|
|
8
|
+
/* ========================================================================== *
|
|
9
|
+
* PRETTY COLORS *
|
|
10
|
+
* ========================================================================== */
|
|
11
|
+
|
|
12
|
+
export const $rst = process.stdout.isTTY ? '\u001b[0m' : '' // reset all colors to default
|
|
13
|
+
export const $und = process.stdout.isTTY ? '\u001b[4m' : '' // underline on
|
|
14
|
+
export const $gry = process.stdout.isTTY ? '\u001b[38;5;240m' : '' // somewhat gray
|
|
15
|
+
export const $blu = process.stdout.isTTY ? '\u001b[38;5;69m' : '' // brighter blue
|
|
16
|
+
export const $wht = process.stdout.isTTY ? '\u001b[1;38;5;255m' : '' // full-bright white
|
|
17
|
+
export const $tsk = process.stdout.isTTY ? '\u001b[38;5;141m' : '' // the color for tasks (purple)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
/* ========================================================================== *
|
|
21
|
+
* PACKAGE VERSION *
|
|
22
|
+
* ========================================================================== */
|
|
23
|
+
|
|
24
|
+
export function version(): string {
|
|
25
|
+
const debug = _util.debuglog('plug:cli')
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const thisFile = _url.fileURLToPath(import.meta.url)
|
|
29
|
+
const packageFile = _path.resolve(thisFile, '..', '..', 'package.json')
|
|
30
|
+
const packageData = _fs.readFileSync(packageFile, 'utf-8')
|
|
31
|
+
return JSON.parse(packageData).version || '(unknown)'
|
|
32
|
+
} catch (error) {
|
|
33
|
+
debug('Error parsing version:', error)
|
|
34
|
+
return '(error)'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* ========================================================================== *
|
|
39
|
+
* TS LOADER FORCE TYPE *
|
|
40
|
+
* ========================================================================== */
|
|
41
|
+
|
|
42
|
+
function forceType(type: 'commonjs' | 'module'): void {
|
|
43
|
+
const debug = _util.debuglog('plug:cli')
|
|
44
|
+
|
|
45
|
+
const tsLoaderMarker = Symbol.for('plugjs:tsLoader')
|
|
46
|
+
|
|
47
|
+
if (!(tsLoaderMarker in globalThis)) {
|
|
48
|
+
throw new Error('TypeScript Loader not available')
|
|
49
|
+
}
|
|
50
|
+
debug(`Forcing type to "${type}"`)
|
|
51
|
+
;(globalThis as any)[tsLoaderMarker] = type
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
/* ========================================================================== *
|
|
56
|
+
* FILES UTILITIES *
|
|
57
|
+
* ========================================================================== */
|
|
58
|
+
|
|
59
|
+
/* Returns a boolean indicating whether the specified file exists or not */
|
|
60
|
+
export function isFile(path: string): boolean {
|
|
61
|
+
try {
|
|
62
|
+
return _fs.statSync(path).isFile()
|
|
63
|
+
} catch (error) {
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Returns a boolean indicating whether the specified directory exists or not */
|
|
69
|
+
export function isDirectory(path: string): boolean {
|
|
70
|
+
try {
|
|
71
|
+
return _fs.statSync(path).isDirectory()
|
|
72
|
+
} catch (error) {
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
/* ========================================================================== *
|
|
79
|
+
* MAIN ENTRY POINT *
|
|
80
|
+
* ========================================================================== */
|
|
81
|
+
export function main(callback: (args: string[]) => void | Promise<void>): void {
|
|
82
|
+
const debug = _util.debuglog('plug:cli')
|
|
83
|
+
|
|
84
|
+
/* Check for source maps and typescript support */
|
|
85
|
+
const sourceMapsEnabled = process.execArgv.indexOf('--enable-source-maps') >= 0
|
|
86
|
+
|
|
87
|
+
/* Check if our `ts-loader` loader is enabled */
|
|
88
|
+
const tsLoaderMarker = Symbol.for('plugjs:tsLoader')
|
|
89
|
+
const typeScriptEnabled = (globalThis as any)[tsLoaderMarker]
|
|
90
|
+
|
|
91
|
+
/* Some debugging if needed */
|
|
92
|
+
debug('SourceMaps enabled =', sourceMapsEnabled)
|
|
93
|
+
debug('TypeScript enabled =', typeScriptEnabled || false)
|
|
94
|
+
|
|
95
|
+
/* If both source maps and typescript are on, run! */
|
|
96
|
+
if (sourceMapsEnabled && typeScriptEnabled) {
|
|
97
|
+
const args = process.argv.slice(2).filter((arg: string): string | void => {
|
|
98
|
+
if (arg === '--force-esm') {
|
|
99
|
+
return forceType('module')
|
|
100
|
+
} else if (arg === '--force-cjs') {
|
|
101
|
+
return forceType('commonjs')
|
|
102
|
+
} else {
|
|
103
|
+
return arg
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
Promise.resolve().then(() => callback(args)).catch(console.error)
|
|
109
|
+
} else {
|
|
110
|
+
const script = _url.fileURLToPath(import.meta.url)
|
|
111
|
+
|
|
112
|
+
/* Fork out ourselves with new options */
|
|
113
|
+
const execArgv = [ ...process.execArgv ]
|
|
114
|
+
|
|
115
|
+
/* Enable source maps if not done already */
|
|
116
|
+
if (! sourceMapsEnabled) execArgv.push('--enable-source-maps')
|
|
117
|
+
|
|
118
|
+
/* Enable our ESM TypeScript loader if not done already */
|
|
119
|
+
if (! typeScriptEnabled) {
|
|
120
|
+
const directory = _path.dirname(script)
|
|
121
|
+
const extension = _path.extname(script) // .mts or .mjs
|
|
122
|
+
const loader = _path.resolve(directory, `ts-loader${extension}`)
|
|
123
|
+
execArgv.push(`--experimental-loader=${loader}`, '--no-warnings')
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/*
|
|
127
|
+
* It seems that setting "type" as "module" in "package.json" creates some
|
|
128
|
+
* problems when the module is being imported from a "commonjs" one.
|
|
129
|
+
*
|
|
130
|
+
* TypeScript _incorrectly_ says (regardless of how we set up our conditional
|
|
131
|
+
* exports) that we must use dynamic imports:
|
|
132
|
+
*
|
|
133
|
+
* Module '@plugjs/plug' cannot be imported using this construct. The
|
|
134
|
+
* specifier only resolves to an ES module, which cannot be imported
|
|
135
|
+
* synchronously. Use dynamic import instead.
|
|
136
|
+
* TS(1471)
|
|
137
|
+
*
|
|
138
|
+
* So for now our only option is to leave "type" as "commonjs", and for those
|
|
139
|
+
* brave souls willing to force ESM irregardless of what's in "package.json",
|
|
140
|
+
* we allow the "--force-esm" option, and instruct `ts-loader` that the
|
|
141
|
+
* current directory (and subdirs) will transpile as ESM always.
|
|
142
|
+
*/
|
|
143
|
+
|
|
144
|
+
/* Fork ourselves! */
|
|
145
|
+
const child = _childProcess.fork(script, [ ...process.argv.slice(2) ], {
|
|
146
|
+
stdio: [ 'inherit', 'inherit', 'inherit', 'ipc' ],
|
|
147
|
+
execArgv,
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
/* Monitor child process... */
|
|
151
|
+
child.on('error', (error) => {
|
|
152
|
+
console.log('Error respawning CLI', error)
|
|
153
|
+
process.exit(1)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
child.on('exit', (code, signal) => {
|
|
157
|
+
if (signal) {
|
|
158
|
+
console.log(`CLI process exited with signal ${signal}`)
|
|
159
|
+
process.exit(1)
|
|
160
|
+
} else if (typeof code !== 'number') {
|
|
161
|
+
console.log('CLI process failed for an unknown reason')
|
|
162
|
+
process.exit(1)
|
|
163
|
+
} else {
|
|
164
|
+
process.exit(code)
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plugjs/plug",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -98,37 +98,27 @@
|
|
|
98
98
|
}
|
|
99
99
|
},
|
|
100
100
|
"bin": {
|
|
101
|
-
"plug": "./
|
|
101
|
+
"plug": "./cli/plug.mjs",
|
|
102
|
+
"tsrun": "./cli/tsrun.mjs"
|
|
102
103
|
},
|
|
103
|
-
"
|
|
104
|
-
|
|
105
|
-
"build": "./bootstrap.sh && ./extra/cli.mjs",
|
|
106
|
-
"dev": "./extra/cli.mjs -w ./src -w ./test"
|
|
107
|
-
},
|
|
108
|
-
"author": "",
|
|
109
|
-
"license": "ISC",
|
|
104
|
+
"author": "Juit Developers <developers@juit.com>",
|
|
105
|
+
"license": "Apache-2.0",
|
|
110
106
|
"dependencies": {
|
|
111
107
|
"@types/node": "<19",
|
|
112
|
-
"esbuild": "^0.
|
|
108
|
+
"esbuild": "^0.17.10",
|
|
113
109
|
"picomatch": "^2.3.1",
|
|
114
|
-
"typescript": "^4.9.
|
|
110
|
+
"typescript": "^4.9.5"
|
|
115
111
|
},
|
|
116
112
|
"devDependencies": {
|
|
117
|
-
"@plugjs/cov8": "^0.1.2",
|
|
118
|
-
"@plugjs/eslint": "^0.1.2",
|
|
119
|
-
"@plugjs/eslint-plugin": "^0.1.1",
|
|
120
|
-
"@plugjs/jasmine": "^0.1.2",
|
|
121
|
-
"@plugjs/typescript": "^0.1.2",
|
|
122
113
|
"@types/picomatch": "^2.3.0",
|
|
123
114
|
"@types/yargs-parser": "^21.0.0",
|
|
124
|
-
"chai": "^4.3.7",
|
|
125
|
-
"chai-as-promised": "^7.1.1",
|
|
126
115
|
"yargs-parser": "^21.1.1"
|
|
127
116
|
},
|
|
128
117
|
"files": [
|
|
129
118
|
"*.md",
|
|
119
|
+
"cli/",
|
|
130
120
|
"dist/",
|
|
131
|
-
"extra
|
|
121
|
+
"extra/",
|
|
132
122
|
"src/",
|
|
133
123
|
"types/"
|
|
134
124
|
]
|
package/src/build.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { assert } from './asserts'
|
|
2
2
|
import { runAsync } from './async'
|
|
3
|
-
import { $ms, $p, $t, getLogger,
|
|
3
|
+
import { $ms, $p, $t, getLogger, logOptions } from './logging'
|
|
4
4
|
import { Context, ContextPromises, PipeImpl } from './pipe'
|
|
5
5
|
import { findCaller } from './utils/caller'
|
|
6
|
-
import { parseOptions } from './utils/options'
|
|
7
6
|
|
|
8
7
|
import type { Pipe } from './index'
|
|
9
8
|
import type { AbsolutePath } from './paths'
|
|
@@ -40,7 +39,7 @@ function makeTask(
|
|
|
40
39
|
_name: string,
|
|
41
40
|
): Task {
|
|
42
41
|
/* Invoke the task, checking call stack, caches, and merging builds */
|
|
43
|
-
function invoke(state: State, taskName: string): Promise<Result> {
|
|
42
|
+
async function invoke(state: State, taskName: string): Promise<Result> {
|
|
44
43
|
assert(! state.stack.includes(task), `Recursion detected calling ${$t(taskName)}`)
|
|
45
44
|
|
|
46
45
|
/* Check cache */
|
|
@@ -52,6 +51,7 @@ function makeTask(
|
|
|
52
51
|
const tasks: Record<string, Task> = Object.assign({}, task.tasks, state.tasks)
|
|
53
52
|
const stack = [ ...state.stack, task ]
|
|
54
53
|
const cache = state.cache
|
|
54
|
+
state = { stack, cache, tasks, props }
|
|
55
55
|
|
|
56
56
|
/* Create run context and build */
|
|
57
57
|
const context = new Context(task.buildFile, taskName)
|
|
@@ -62,7 +62,6 @@ function makeTask(
|
|
|
62
62
|
// Tasks first, props might come also from environment
|
|
63
63
|
if (name in tasks) {
|
|
64
64
|
return (): Pipe => {
|
|
65
|
-
const state = { stack, cache, tasks, props }
|
|
66
65
|
const promise = tasks[name]!.invoke(state, name)
|
|
67
66
|
return new PipeImpl(context, promise)
|
|
68
67
|
}
|
|
@@ -72,6 +71,9 @@ function makeTask(
|
|
|
72
71
|
},
|
|
73
72
|
})
|
|
74
73
|
|
|
74
|
+
/* Run all tasks hooked _before_ this one */
|
|
75
|
+
for (const before of task.before) await before.invoke(state, before.name)
|
|
76
|
+
|
|
75
77
|
/* Some logging */
|
|
76
78
|
context.log.info('Running...')
|
|
77
79
|
const now = Date.now()
|
|
@@ -79,12 +81,18 @@ function makeTask(
|
|
|
79
81
|
/* Run asynchronously in an asynchronous context */
|
|
80
82
|
const promise = runAsync(context, taskName, async () => {
|
|
81
83
|
return await _def.call(build) || undefined
|
|
82
|
-
}).then((result) => {
|
|
83
|
-
|
|
84
|
+
}).then(async (result) => {
|
|
85
|
+
const level = taskName.startsWith('_') ? 'info' : 'notice'
|
|
86
|
+
context.log[level](`Success ${$ms(Date.now() - now)}`)
|
|
84
87
|
return result
|
|
85
88
|
}).catch((error) => {
|
|
86
89
|
throw context.log.fail(`Failure ${$ms(Date.now() - now)}`, error)
|
|
87
|
-
}).finally(() =>
|
|
90
|
+
}).finally(async () => {
|
|
91
|
+
await ContextPromises.wait(context)
|
|
92
|
+
}).then(async (result) => {
|
|
93
|
+
for (const after of task.after) await after.invoke(state, after.name)
|
|
94
|
+
return result
|
|
95
|
+
})
|
|
88
96
|
|
|
89
97
|
/* Cache the resulting promise and return it */
|
|
90
98
|
cache.set(task, promise)
|
|
@@ -100,17 +108,24 @@ function makeTask(
|
|
|
100
108
|
tasks: tasks,
|
|
101
109
|
}
|
|
102
110
|
return invoke(state, _name)
|
|
103
|
-
}, { buildFile, tasks, props, invoke })
|
|
111
|
+
}, { buildFile, tasks, props, invoke, before: [], after: [] })
|
|
104
112
|
|
|
105
|
-
/* Assign the task name
|
|
106
|
-
|
|
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
|
|
107
117
|
}
|
|
108
118
|
|
|
109
119
|
/* ========================================================================== *
|
|
110
120
|
* BUILD COMPILER *
|
|
111
121
|
* ========================================================================== */
|
|
112
122
|
|
|
113
|
-
/**
|
|
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
|
+
*/
|
|
114
129
|
const buildMarker = Symbol.for('plugjs:isBuild')
|
|
115
130
|
|
|
116
131
|
/** Compile a {@link BuildDef | build definition} into a {@link Build} */
|
|
@@ -124,14 +139,14 @@ export function build<
|
|
|
124
139
|
/* Iterate through all definition extracting properties and tasks */
|
|
125
140
|
for (const [ key, val ] of Object.entries(def)) {
|
|
126
141
|
let len = 0
|
|
127
|
-
if (
|
|
142
|
+
if (isTask(val)) { // this goes first, tasks _are_ functions!
|
|
143
|
+
tasks[key] = val
|
|
144
|
+
len = key.length
|
|
145
|
+
} else if (typeof val === 'string') {
|
|
128
146
|
props[key] = val
|
|
129
147
|
} else if (typeof val === 'function') {
|
|
130
148
|
tasks[key] = makeTask(buildFile, tasks, props, val, key)
|
|
131
149
|
len = key.length
|
|
132
|
-
} else if (isTask(val)) {
|
|
133
|
-
tasks[key] = val
|
|
134
|
-
len = key.length
|
|
135
150
|
}
|
|
136
151
|
|
|
137
152
|
/* Update the logger's own "taskLength" for nice printing */
|
|
@@ -140,7 +155,7 @@ export function build<
|
|
|
140
155
|
}
|
|
141
156
|
|
|
142
157
|
/* Create the "call" function for this build */
|
|
143
|
-
const invoke
|
|
158
|
+
const invoke = async function invoke(
|
|
144
159
|
taskNames: string[],
|
|
145
160
|
overrideProps: Record<string, string | undefined> = {},
|
|
146
161
|
): Promise<void> {
|
|
@@ -180,24 +195,42 @@ export function build<
|
|
|
180
195
|
return compiled
|
|
181
196
|
}
|
|
182
197
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
/** Serially invoke tasks in a {@link Build} optionally overriding properties */
|
|
187
|
-
export async function invoke(
|
|
188
|
-
build: Build,
|
|
189
|
-
...args:
|
|
190
|
-
| [ ...taskNames: [ string, ...string[] ] ]
|
|
191
|
-
| [ ...taskNames: [ string, ...string[] ], options: Record<string, string | undefined> ]
|
|
192
|
-
): Promise<void> {
|
|
193
|
-
const { params: tasks, options: props } = parseOptions(args, {})
|
|
198
|
+
/* ========================================================================== *
|
|
199
|
+
* HOOKS *
|
|
200
|
+
* ========================================================================== */
|
|
194
201
|
|
|
195
|
-
|
|
196
|
-
|
|
202
|
+
type TaskNames<B extends Build> = keyof {
|
|
203
|
+
[ name in keyof B as B[name] extends Task ? name : never ] : any
|
|
204
|
+
}
|
|
197
205
|
|
|
198
|
-
|
|
199
|
-
|
|
206
|
+
export function hookBefore<B extends Build, T extends keyof B>(
|
|
207
|
+
build: B,
|
|
208
|
+
taskName: string & T & TaskNames<B>,
|
|
209
|
+
hooks: (string & Exclude<TaskNames<B>, T>)[],
|
|
210
|
+
): void {
|
|
211
|
+
const task = build[taskName]
|
|
212
|
+
assert(isTask(task), `Task "${$t(taskName)}" not found in build`)
|
|
213
|
+
|
|
214
|
+
for (const hook of hooks) {
|
|
215
|
+
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)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
200
221
|
|
|
201
|
-
|
|
202
|
-
|
|
222
|
+
export function hookAfter<B extends Build, T extends keyof B>(
|
|
223
|
+
build: B,
|
|
224
|
+
taskName: string & T & TaskNames<B>,
|
|
225
|
+
hooks: (string & Exclude<TaskNames<B>, T>)[],
|
|
226
|
+
): void {
|
|
227
|
+
const task = build[taskName]
|
|
228
|
+
assert(isTask(task), `Task "${$t(taskName)}" not found in build`)
|
|
229
|
+
|
|
230
|
+
for (const hook of hooks) {
|
|
231
|
+
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)
|
|
235
|
+
}
|
|
203
236
|
}
|
package/src/fork.ts
CHANGED
|
@@ -200,6 +200,7 @@ if ((process.argv[1] === requireFilename(__fileurl)) && (process.send)) {
|
|
|
200
200
|
assert(typeof Ctor === 'function', `Script ${$p(scriptFile)} does not export a default constructor`)
|
|
201
201
|
} else {
|
|
202
202
|
Ctor = script[exportName]
|
|
203
|
+
if ((! Ctor) && (script.default)) Ctor = script.default[exportName]
|
|
203
204
|
assert(typeof Ctor === 'function', `Script ${$p(scriptFile)} does not export "${exportName}"`)
|
|
204
205
|
}
|
|
205
206
|
|
package/src/helpers.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -6,9 +6,16 @@
|
|
|
6
6
|
import type { Files } from './files'
|
|
7
7
|
import type { Plug, PlugFunction } from './pipe'
|
|
8
8
|
|
|
9
|
+
export { Files } from './files'
|
|
10
|
+
export type { AbsolutePath } from './paths'
|
|
11
|
+
export type { Plug, PlugFunction } from './pipe'
|
|
12
|
+
|
|
9
13
|
/**
|
|
10
14
|
* The {@link Pipe} interface defines a processing pipeline where multiple
|
|
11
15
|
* {@link Plug}s can transform lists of {@link Files}.
|
|
16
|
+
*
|
|
17
|
+
* This is exported _here_, in the main module export file so that plugs can
|
|
18
|
+
* add definitions by simply referring the module.
|
|
12
19
|
*/
|
|
13
20
|
export interface Pipe extends Promise<Files> {
|
|
14
21
|
plug(plug: Plug<Files>): Pipe
|
package/src/logging/spinner.ts
CHANGED
package/src/plugs/edit.ts
CHANGED
|
@@ -2,12 +2,13 @@ import { readFile, writeFile } from '../fs'
|
|
|
2
2
|
import { install } from '../pipe'
|
|
3
3
|
|
|
4
4
|
import type { Files } from '../files'
|
|
5
|
+
import type { AbsolutePath } from '../paths'
|
|
5
6
|
import type { PipeParameters, Plug } from '../pipe'
|
|
6
7
|
|
|
7
8
|
declare module '../index' {
|
|
8
9
|
export interface Pipe {
|
|
9
10
|
/** Edits the content of all files in a pipeline. */
|
|
10
|
-
edit(callback: (content: string) => string | void | Promise<string | void>): Pipe
|
|
11
|
+
edit(callback: (content: string, fileName: AbsolutePath) => string | void | Promise<string | void>): Pipe
|
|
11
12
|
}
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -17,7 +18,7 @@ declare module '../index' {
|
|
|
17
18
|
|
|
18
19
|
/** Edits the content of all files in a pipeline. */
|
|
19
20
|
install('edit', class Edit implements Plug<Files> {
|
|
20
|
-
private readonly _callback: (content: string) => string | void | Promise<string | void>
|
|
21
|
+
private readonly _callback: (content: string, fileName: AbsolutePath) => string | void | Promise<string | void>
|
|
21
22
|
|
|
22
23
|
constructor(...args: PipeParameters<'edit'>) {
|
|
23
24
|
this._callback = args[0]
|
|
@@ -26,7 +27,7 @@ install('edit', class Edit implements Plug<Files> {
|
|
|
26
27
|
async pipe(files: Files): Promise<Files> {
|
|
27
28
|
for (const file of files.absolutePaths()) {
|
|
28
29
|
const data = await readFile(file, 'utf-8')
|
|
29
|
-
const edited = await this._callback(data)
|
|
30
|
+
const edited = await this._callback(data, file)
|
|
30
31
|
if (edited !== undefined) await writeFile(file, edited, 'utf-8')
|
|
31
32
|
}
|
|
32
33
|
return files
|
package/src/plugs/esbuild.ts
CHANGED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { EOL } from 'node:os'
|
|
2
|
+
import { sep } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { Files } from '../files'
|
|
5
|
+
import { readFile, writeFile } from '../fs'
|
|
6
|
+
import { $p } from '../logging'
|
|
7
|
+
import { assertRelativeChildPath, getAbsoluteParent } from '../paths'
|
|
8
|
+
import { install } from '../pipe'
|
|
9
|
+
|
|
10
|
+
import type { Context, PipeParameters, Plug } from '../pipe'
|
|
11
|
+
|
|
12
|
+
/** Options for our `exports` plug. */
|
|
13
|
+
export interface ExportsOptions {
|
|
14
|
+
/** The `package.json` file used as the input for processing */
|
|
15
|
+
packageJson?: string
|
|
16
|
+
/** The `package.json` file to be written including the matching exports */
|
|
17
|
+
outputPackageJson?: string
|
|
18
|
+
/** The extension for CommonJS modules (default: `.cjs` or `.js`) */
|
|
19
|
+
cjsExtension?: string
|
|
20
|
+
/** The extension for EcmaScript modules (default: `.mjs` or `.js`) */
|
|
21
|
+
esmExtension?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
declare module '../index' {
|
|
25
|
+
export interface Pipe {
|
|
26
|
+
/** Include the files piped into this task as `exports` in `package.json` */
|
|
27
|
+
exports(options?: ExportsOptions): Pipe
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* ========================================================================== *
|
|
32
|
+
* INSTALLATION / IMPLEMENTATION *
|
|
33
|
+
* ========================================================================== */
|
|
34
|
+
|
|
35
|
+
type ExportsDeclaration = {
|
|
36
|
+
[ name in string ]? : {
|
|
37
|
+
[ type in 'require' | 'import' ]? : {
|
|
38
|
+
[ kind in 'types' | 'default' ]? : string
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
install('exports', class Exports implements Plug<Files> {
|
|
44
|
+
private readonly _packageJson: string
|
|
45
|
+
private readonly _outputPackageJson: string
|
|
46
|
+
private readonly _cjsExtension?: string
|
|
47
|
+
private readonly _esmExtension?: string
|
|
48
|
+
|
|
49
|
+
constructor(...args: PipeParameters<'exports'>) {
|
|
50
|
+
const options = args[0] || {}
|
|
51
|
+
const {
|
|
52
|
+
packageJson = 'package.json',
|
|
53
|
+
outputPackageJson = packageJson,
|
|
54
|
+
cjsExtension,
|
|
55
|
+
esmExtension,
|
|
56
|
+
} = options
|
|
57
|
+
this._packageJson = packageJson,
|
|
58
|
+
this._outputPackageJson = outputPackageJson
|
|
59
|
+
this._cjsExtension = cjsExtension
|
|
60
|
+
this._esmExtension = esmExtension
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async pipe(files: Files, context: Context): Promise<Files> {
|
|
64
|
+
// read up our package.json, we need it to figure out the default `type`
|
|
65
|
+
const incomingFile = context.resolve(this._packageJson)
|
|
66
|
+
const incomingData = await readFile(incomingFile, 'utf8')
|
|
67
|
+
const packageData = JSON.parse(incomingData)
|
|
68
|
+
|
|
69
|
+
// exports must be relative to the _output_ package.json
|
|
70
|
+
const outgoingFile = context.resolve(this._outputPackageJson)
|
|
71
|
+
const outgoingDirectory = getAbsoluteParent(outgoingFile)
|
|
72
|
+
|
|
73
|
+
// type here determines the extension of commonjs or ecmascript modules
|
|
74
|
+
const type =
|
|
75
|
+
packageData.type === 'module' ? 'module' :
|
|
76
|
+
packageData.type === 'commonjs' ? 'commonjs' :
|
|
77
|
+
packageData.type == null ? 'commonjs' :
|
|
78
|
+
undefined
|
|
79
|
+
if (! type) context.log.fail(`Unknown module type "${packageData.type}" in ${$p(incomingFile)}`)
|
|
80
|
+
|
|
81
|
+
context.log.debug(`Package file ${$p(incomingFile)} declares module type "${type}"`)
|
|
82
|
+
|
|
83
|
+
const cjsExtension = this._cjsExtension || (type === 'commonjs' ? '.js' : '.cjs')
|
|
84
|
+
const esmExtension = this._esmExtension || (type === 'module' ? '.js' : '.mjs')
|
|
85
|
+
|
|
86
|
+
// reject when commonjs and ecmascript modules have the same extension
|
|
87
|
+
if (cjsExtension === esmExtension) {
|
|
88
|
+
context.log.fail(`CommonJS and EcmaScript modules both resolve to same extension "${cjsExtension}"`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const exports: ExportsDeclaration = {}
|
|
92
|
+
function addExport(
|
|
93
|
+
name: string,
|
|
94
|
+
type: 'require' | 'import',
|
|
95
|
+
kind: 'types' | 'default',
|
|
96
|
+
file: string,
|
|
97
|
+
): void {
|
|
98
|
+
if (! exports[name]) exports[name] = {}
|
|
99
|
+
if (! exports[name]![type]) exports[name]![type] = {}
|
|
100
|
+
exports[name]![type]![kind] = file
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// all extensions to match in the incoming files
|
|
104
|
+
const exts = [ '.d.mts', '.d.cts', '.d.ts', cjsExtension, esmExtension ]
|
|
105
|
+
|
|
106
|
+
// look up all the files we were piped in
|
|
107
|
+
for (const [ name, absolute ] of files.pathMappings()) {
|
|
108
|
+
const relative = assertRelativeChildPath(outgoingDirectory, absolute)
|
|
109
|
+
|
|
110
|
+
for (const ext of exts) {
|
|
111
|
+
if (! relative.endsWith(ext)) continue
|
|
112
|
+
|
|
113
|
+
const base = `.${sep}${name.slice(0, -ext.length)}`
|
|
114
|
+
const exp = base.endsWith(`${sep}index`) ? base.slice(0, -6) : base
|
|
115
|
+
|
|
116
|
+
switch (ext) {
|
|
117
|
+
case cjsExtension:
|
|
118
|
+
addExport(exp, 'require', 'default', `.${sep}${relative}`)
|
|
119
|
+
break
|
|
120
|
+
case esmExtension:
|
|
121
|
+
addExport(exp, 'import', 'default', `.${sep}${relative}`)
|
|
122
|
+
break
|
|
123
|
+
case '.d.cts':
|
|
124
|
+
addExport(exp, 'require', 'types', `.${sep}${relative}`)
|
|
125
|
+
break
|
|
126
|
+
case '.d.mts':
|
|
127
|
+
addExport(exp, 'import', 'types', `.${sep}${relative}`)
|
|
128
|
+
break
|
|
129
|
+
case '.d.ts':
|
|
130
|
+
addExport(exp, 'require', 'types', `.${sep}${relative}`)
|
|
131
|
+
addExport(exp, 'import', 'types', `.${sep}${relative}`)
|
|
132
|
+
break
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// if we have a "." export, inject the "main", "module" and "types" fields
|
|
138
|
+
if ('.' in exports) {
|
|
139
|
+
const rootExport = exports['.']
|
|
140
|
+
packageData['main'] = rootExport?.require?.default
|
|
141
|
+
packageData['module'] = rootExport?.import?.default
|
|
142
|
+
packageData['types'] = packageData['type'] === 'module' ?
|
|
143
|
+
rootExport?.import?.types : rootExport?.require?.types
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// correctly order the exports record (e.g. types comes before default)
|
|
147
|
+
packageData['exports'] = Object.keys(exports).sort().reduce((obj, name) => {
|
|
148
|
+
const current = exports[name]
|
|
149
|
+
if (! current) return obj
|
|
150
|
+
|
|
151
|
+
// json serialization will scrub all undefined... here we export the types
|
|
152
|
+
// only if the "default" export is available, or we scrub the whole thing!
|
|
153
|
+
obj[name] = current.require?.default || current.import?.default ? {
|
|
154
|
+
require: current.require?.default ? {
|
|
155
|
+
types: current.require.types || undefined,
|
|
156
|
+
default: current.require.default || undefined,
|
|
157
|
+
} : undefined,
|
|
158
|
+
import: current.import?.default ? {
|
|
159
|
+
types: current.import.types || undefined,
|
|
160
|
+
default: current.import.default || undefined,
|
|
161
|
+
} : undefined,
|
|
162
|
+
} : undefined
|
|
163
|
+
|
|
164
|
+
return obj
|
|
165
|
+
}, {} as ExportsDeclaration)
|
|
166
|
+
|
|
167
|
+
// convert back our package data into a json and write it
|
|
168
|
+
const outgoingData = JSON.stringify(packageData, null, 2)
|
|
169
|
+
context.log.info(`Writing new ${$p(outgoingFile)}`, outgoingData)
|
|
170
|
+
await writeFile(outgoingFile, outgoingData + EOL, 'utf8')
|
|
171
|
+
|
|
172
|
+
// return a `Files` instance with our `package.json` in there
|
|
173
|
+
return Files.builder(getAbsoluteParent(outgoingFile)).add(outgoingFile).build()
|
|
174
|
+
}
|
|
175
|
+
})
|