@gesslar/toolkit 0.2.8 → 0.3.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.
- package/package.json +4 -2
- package/src/index.js +1 -0
- package/src/lib/ActionBuilder.js +144 -0
- package/src/lib/ActionRunner.js +109 -0
- package/src/lib/BaseActionManager.js +246 -0
- package/src/lib/BaseHookManager.js +206 -0
- package/src/lib/Glog.js +324 -86
- package/src/lib/Logger.js +182 -0
- package/src/lib/Piper.js +181 -0
- package/src/lib/Sass.js +2 -4
- package/src/lib/Tantrum.js +69 -0
- package/src/types/Tantrum.d.ts +81 -0
- package/src/types/index.d.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gesslar/toolkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Get in, bitches, we're going toolkitting.",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"submit": "npm publish --access public",
|
|
26
26
|
"update": "npx npm-check-updates -u && npm install",
|
|
27
27
|
"test": "node --test tests/unit/*.test.js",
|
|
28
|
-
"test:unit": "node --test tests/unit/*.test.js"
|
|
28
|
+
"test:unit": "node --test tests/unit/*.test.js",
|
|
29
|
+
"pr": "gt submit --cli --publish --restack --ai --merge-when-ready"
|
|
29
30
|
},
|
|
30
31
|
"repository": {
|
|
31
32
|
"type": "git",
|
|
@@ -49,6 +50,7 @@
|
|
|
49
50
|
"license": "Unlicense",
|
|
50
51
|
"homepage": "https://github.com/gesslar/toolkit#readme",
|
|
51
52
|
"dependencies": {
|
|
53
|
+
"@gesslar/colours": "^0.0.1",
|
|
52
54
|
"globby": "^15.0.0",
|
|
53
55
|
"json5": "^2.2.3",
|
|
54
56
|
"yaml": "^2.8.1"
|
package/src/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export {default as Collection} from "./lib/Collection.js"
|
|
|
9
9
|
export {default as Data} from "./lib/Data.js"
|
|
10
10
|
export {default as Glog} from "./lib/Glog.js"
|
|
11
11
|
export {default as Sass} from "./lib/Sass.js"
|
|
12
|
+
export {default as Tantrum} from "./lib/Tantrum.js"
|
|
12
13
|
export {default as Term} from "./lib/Term.js"
|
|
13
14
|
export {default as Type} from "./lib/TypeSpec.js"
|
|
14
15
|
export {default as Util} from "./lib/Util.js"
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import Valid from "./Valid.js"
|
|
2
|
+
|
|
3
|
+
/** @typedef {import("./ActionRunner.js").default} ActionRunner */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Activity bit flags recognised by {@link ActionBuilder#act}. The flag decides
|
|
7
|
+
* how results are accumulated for each activity.
|
|
8
|
+
*
|
|
9
|
+
* @readonly
|
|
10
|
+
* @enum {number}
|
|
11
|
+
*/
|
|
12
|
+
export const ACTIVITY = Object.freeze({
|
|
13
|
+
ONCE: 1<<1,
|
|
14
|
+
MANY: 1<<2,
|
|
15
|
+
PARALLEL: 1<<3,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fluent builder for describing how an action should process the context that
|
|
20
|
+
* flows through the {@link ActionRunner}. Consumers register named activities,
|
|
21
|
+
* optional hook pairs, and nested parallel pipelines before handing the
|
|
22
|
+
* builder back to the runner for execution.
|
|
23
|
+
*
|
|
24
|
+
* Typical usage:
|
|
25
|
+
*
|
|
26
|
+
* ```js
|
|
27
|
+
* const pipeline = new ActionBuilder(myAction)
|
|
28
|
+
* .act("prepare", ACTIVITY.ONCE, ctx => ctx.initialise())
|
|
29
|
+
* .parallel(parallel => parallel
|
|
30
|
+
* .act("step", ACTIVITY.MANY, ctx => ctx.consume())
|
|
31
|
+
* )
|
|
32
|
+
* .act("finalise", ACTIVITY.ONCE, ctx => ctx.complete())
|
|
33
|
+
* .build()
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @class ActionBuilder
|
|
37
|
+
*/
|
|
38
|
+
export default class ActionBuilder {
|
|
39
|
+
#action = null
|
|
40
|
+
#activities = new Map([])
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Creates a new ActionBuilder instance with the provided action callback.
|
|
44
|
+
*
|
|
45
|
+
* @param {(ctx: object) => void} action Base action invoked by the runner when a block
|
|
46
|
+
* satisfies the configured structure.
|
|
47
|
+
*/
|
|
48
|
+
constructor(action) {
|
|
49
|
+
this.#action = action
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Returns the underlying action that will receive the extracted context.
|
|
54
|
+
*
|
|
55
|
+
* @returns {(ctx: object) => void} The action callback function that processes the extracted context.
|
|
56
|
+
*/
|
|
57
|
+
get action() {
|
|
58
|
+
return this.#action
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns the registered activities keyed by their name.
|
|
63
|
+
*
|
|
64
|
+
* @returns {Map<string | symbol, {op: (context: object) => unknown, kind: number, hooks: {before: ((context: object) => void)|null, after: ((context: object) => void)|null}}>} Map of registered activities and their metadata.
|
|
65
|
+
*/
|
|
66
|
+
get activities() {
|
|
67
|
+
return this.#activities
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Registers a named activity that will run for each matching block.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} name Unique activity identifier.
|
|
74
|
+
* @param {number} kind Activity accumulation strategy (see {@link ACTIVITY}).
|
|
75
|
+
* @param {(context: object) => unknown} op Work function executed with the runner context.
|
|
76
|
+
* @param {{before?: (context: object) => void, after?: (context: object) => void}} [hooks] Optional hooks to run before or after the activity operation.
|
|
77
|
+
* @returns {ActionBuilder} Builder instance for chaining.
|
|
78
|
+
*/
|
|
79
|
+
act(name, kind, op, hooks={}) {
|
|
80
|
+
this.#validActivityKind(kind)
|
|
81
|
+
this.#dupeActivityCheck(name)
|
|
82
|
+
|
|
83
|
+
hooks = this.#normalizeHooks(hooks)
|
|
84
|
+
|
|
85
|
+
this.#activities.set(name, {op, kind, hooks})
|
|
86
|
+
|
|
87
|
+
return this
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#normalizeHooks({before=null, after=null}) {
|
|
91
|
+
return {before, after}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Defines a nested pipeline that will run with the {@link ACTIVITY} flag PARALLEL.
|
|
96
|
+
*
|
|
97
|
+
* The callback receives a fresh {@link ActionBuilder} scoped to the current
|
|
98
|
+
* action. The callback must return the configured builder so the runner can
|
|
99
|
+
* execute the nested pipeline.
|
|
100
|
+
*
|
|
101
|
+
* @param {(builder: ActionBuilder) => ActionBuilder} func Callback configuring a nested builder.
|
|
102
|
+
* @returns {ActionBuilder} Builder instance for chaining.
|
|
103
|
+
*/
|
|
104
|
+
parallel(func) {
|
|
105
|
+
const activityName = Symbol(performance.now())
|
|
106
|
+
|
|
107
|
+
this.#activities.set(activityName, {
|
|
108
|
+
op: func.call(this.action, new ActionBuilder(this.action)),
|
|
109
|
+
kind: ACTIVITY.PARALLEL
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
return this
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#validActivityKind(kind) {
|
|
116
|
+
Valid.assert(
|
|
117
|
+
Object.values(ACTIVITY).includes(kind),
|
|
118
|
+
"Invalid activity kind."
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Validates that an activity name has not been reused.
|
|
124
|
+
*
|
|
125
|
+
* @private
|
|
126
|
+
* @param {string|symbol} name Activity identifier.
|
|
127
|
+
*/
|
|
128
|
+
#dupeActivityCheck(name) {
|
|
129
|
+
Valid.assert(
|
|
130
|
+
!this.#activities.has(name),
|
|
131
|
+
`Activity '${String(name)}' has already been registered.`
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Finalises the builder and returns a payload that can be consumed by the
|
|
137
|
+
* runner.
|
|
138
|
+
*
|
|
139
|
+
* @returns {{action: (context: object) => unknown, build: ActionBuilder}} Payload consumed by the {@link ActionRunner} constructor.
|
|
140
|
+
*/
|
|
141
|
+
build() {
|
|
142
|
+
return {action: this.#action, build: this}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import ActionBuilder, {ACTIVITY} from "./ActionBuilder.js"
|
|
2
|
+
import Data from "./Data.js"
|
|
3
|
+
import Piper from "./Piper.js"
|
|
4
|
+
import Sass from "./Sass.js"
|
|
5
|
+
import Glog from "./Glog.js"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Orchestrates execution of {@link ActionBuilder}-produced pipelines.
|
|
9
|
+
*
|
|
10
|
+
* Activities run in insertion order, with support for once-off work, repeated
|
|
11
|
+
* loops, and nested parallel pipelines. Each activity receives a mutable
|
|
12
|
+
* context object under `result.value` that can be replaced or enriched.
|
|
13
|
+
*/
|
|
14
|
+
export default class ActionRunner {
|
|
15
|
+
#action = null
|
|
16
|
+
#build = null
|
|
17
|
+
#logger = null
|
|
18
|
+
|
|
19
|
+
constructor({action, build, logger}) {
|
|
20
|
+
this.#action = action
|
|
21
|
+
this.#build = build
|
|
22
|
+
this.#logger = logger ?? {newDebug: () => () => {}}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Executes the configured action pipeline.
|
|
27
|
+
*
|
|
28
|
+
* @param {unknown} content Seed value passed to the first activity.
|
|
29
|
+
* @returns {Promise<unknown>} Final value produced by the pipeline, or null when a parallel stage reports failures.
|
|
30
|
+
* @throws {Sass} When no activities are registered or required parallel builders are missing.
|
|
31
|
+
*/
|
|
32
|
+
async run(content) {
|
|
33
|
+
const AR = ActionRunner
|
|
34
|
+
const result = {value: content}
|
|
35
|
+
const action = this.#action
|
|
36
|
+
const activities = this.#build.activities
|
|
37
|
+
|
|
38
|
+
if(!activities.size)
|
|
39
|
+
throw Sass.new("No activities defined in action.")
|
|
40
|
+
|
|
41
|
+
for(const [_,activity] of activities) {
|
|
42
|
+
const {op} = activity
|
|
43
|
+
|
|
44
|
+
if(activity.kind === ACTIVITY.ONCE) {
|
|
45
|
+
|
|
46
|
+
if(Data.typeOf(activity.hooks?.before) === "Function")
|
|
47
|
+
await activity.hooks.before.call(action, result)
|
|
48
|
+
|
|
49
|
+
const activityResult = await op.call(action, result)
|
|
50
|
+
|
|
51
|
+
if(!activityResult)
|
|
52
|
+
break
|
|
53
|
+
|
|
54
|
+
if(Data.typeOf(activity.hooks?.after) === "Function")
|
|
55
|
+
await activity.hooks.after.call(action, result)
|
|
56
|
+
|
|
57
|
+
} else if(activity.kind == ACTIVITY.MANY) {
|
|
58
|
+
for(;;) {
|
|
59
|
+
|
|
60
|
+
if(Data.typeOf(activity.hooks?.before) === "Function")
|
|
61
|
+
await activity.hooks.before.call(action, result)
|
|
62
|
+
|
|
63
|
+
const activityResult = await op.call(action, result)
|
|
64
|
+
|
|
65
|
+
if(!activityResult)
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
if(Data.typeOf(activity.hooks?.after) === "Function")
|
|
69
|
+
await activity.hooks.after.call(action, result)
|
|
70
|
+
}
|
|
71
|
+
} else if(activity.kind === ACTIVITY.PARALLEL) {
|
|
72
|
+
if(op === undefined)
|
|
73
|
+
throw Sass.new("Missing action builder. Did you return the builder?")
|
|
74
|
+
|
|
75
|
+
if(!op)
|
|
76
|
+
throw Sass.new("Okay, cheeky monkey, you need to return the builder for this to work.")
|
|
77
|
+
|
|
78
|
+
const piper = new Piper({logger: this.#logger})
|
|
79
|
+
.addStep(c => new AR(op.build()).run(c))
|
|
80
|
+
|
|
81
|
+
result.value = await piper.pipe()
|
|
82
|
+
Glog(result)
|
|
83
|
+
throw Sass.new("Nope")
|
|
84
|
+
|
|
85
|
+
// // wheeeeeeeeeeeeee! ZOOMZOOM!
|
|
86
|
+
// const settled = await Util.settleAll(
|
|
87
|
+
// result.value.map()
|
|
88
|
+
// )
|
|
89
|
+
|
|
90
|
+
// const rejected = settled
|
|
91
|
+
// .filter(r => r.status === "rejected")
|
|
92
|
+
// .map(r => {
|
|
93
|
+
// return r.reason instanceof Sass
|
|
94
|
+
// ? r.reason
|
|
95
|
+
// : Sass.new("Running structured parsing.", r.reason)
|
|
96
|
+
// })
|
|
97
|
+
// .map(r => r.report(true))
|
|
98
|
+
|
|
99
|
+
// if(rejected.length)
|
|
100
|
+
// return null
|
|
101
|
+
|
|
102
|
+
// result.value = settled.map(s => s.value)
|
|
103
|
+
// .sort((a,b) => a.index-b.index)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return result.value
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import Data from "./Data.js"
|
|
2
|
+
import Sass from "./Sass.js"
|
|
3
|
+
import ActionBuilder from "./ActionBuilder.js"
|
|
4
|
+
import ActionRunner from "./ActionRunner.js"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generic base class for managing actions with lifecycle hooks.
|
|
8
|
+
* Provides common functionality for action setup, execution, and cleanup.
|
|
9
|
+
* Designed to be extended by specific implementations.
|
|
10
|
+
*/
|
|
11
|
+
export default class BaseActionManager {
|
|
12
|
+
#action = null
|
|
13
|
+
#hookManager = null
|
|
14
|
+
#contract = null
|
|
15
|
+
#log = null
|
|
16
|
+
#debug = null
|
|
17
|
+
#file = null
|
|
18
|
+
#variables = null
|
|
19
|
+
#runner = null
|
|
20
|
+
#id = null
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {object} config - Configuration object
|
|
24
|
+
* @param {object} config.actionDefinition - Action definition with action, file, and contract
|
|
25
|
+
* @param {object} config.logger - Logger instance
|
|
26
|
+
* @param {object} [config.variables] - Variables to pass to action
|
|
27
|
+
*/
|
|
28
|
+
constructor({actionDefinition, logger, variables}) {
|
|
29
|
+
this.#id = Symbol(performance.now())
|
|
30
|
+
this.#log = logger
|
|
31
|
+
this.#debug = this.#log.newDebug()
|
|
32
|
+
this.#variables = variables || {}
|
|
33
|
+
|
|
34
|
+
this.#initialize(actionDefinition)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get id() {
|
|
38
|
+
return this.#id
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get action() {
|
|
42
|
+
return this.#action
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get hookManager() {
|
|
46
|
+
return this.#hookManager
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
set hookManager(hookManager) {
|
|
50
|
+
if (this.hookManager)
|
|
51
|
+
throw new Error("Hook manager already set")
|
|
52
|
+
|
|
53
|
+
this.#hookManager = hookManager
|
|
54
|
+
this.#attachHooksToAction(hookManager)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get contract() {
|
|
58
|
+
return this.#contract
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get meta() {
|
|
62
|
+
return this.#action?.meta
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get log() {
|
|
66
|
+
return this.#log
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get variables() {
|
|
70
|
+
return this.#variables
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
get runner() {
|
|
74
|
+
return this.#runner
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
get file() {
|
|
78
|
+
return this.#file
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Initialize the action manager with the provided definition.
|
|
83
|
+
* Override in subclasses to add specific validation or setup.
|
|
84
|
+
*
|
|
85
|
+
* @param {object} actionDefinition - Action definition
|
|
86
|
+
* @protected
|
|
87
|
+
*/
|
|
88
|
+
#initialize(actionDefinition) {
|
|
89
|
+
const debug = this.#debug
|
|
90
|
+
|
|
91
|
+
debug("Setting up action", 2)
|
|
92
|
+
|
|
93
|
+
const {action, file, contract} = actionDefinition
|
|
94
|
+
|
|
95
|
+
if (!action)
|
|
96
|
+
throw new Error("Action is required")
|
|
97
|
+
|
|
98
|
+
if (!contract)
|
|
99
|
+
throw new Error("Contract is required")
|
|
100
|
+
|
|
101
|
+
this.#action = action
|
|
102
|
+
this.#contract = contract
|
|
103
|
+
this.#file = file
|
|
104
|
+
|
|
105
|
+
debug("Action initialization complete", 2)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Attach hooks to the action instance.
|
|
110
|
+
* Override in subclasses to customize hook attachment.
|
|
111
|
+
*
|
|
112
|
+
* @param {object} hookManager - Hook manager instance
|
|
113
|
+
* @protected
|
|
114
|
+
*/
|
|
115
|
+
#attachHooksToAction(hookManager) {
|
|
116
|
+
// Basic hook attachment - can be overridden by subclasses
|
|
117
|
+
this.action.hook = hookManager.on?.bind(hookManager)
|
|
118
|
+
this.action.hooks = hookManager.hooks
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Setup the action by creating and configuring the runner.
|
|
123
|
+
* Override setupActionInstance() in subclasses for custom setup logic.
|
|
124
|
+
*
|
|
125
|
+
* @returns {Promise<void>}
|
|
126
|
+
*/
|
|
127
|
+
async setupAction() {
|
|
128
|
+
this.#debug("Setting up action for %s on %s", 2, this.action.meta?.kind, this.id)
|
|
129
|
+
|
|
130
|
+
await this.#setupHooks()
|
|
131
|
+
await this.#setupActionInstance()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Setup the action instance and create the runner.
|
|
136
|
+
* Override in subclasses to customize action setup.
|
|
137
|
+
*
|
|
138
|
+
* @protected
|
|
139
|
+
*/
|
|
140
|
+
async #setupActionInstance() {
|
|
141
|
+
const actionInstance = new this.action()
|
|
142
|
+
const setup = actionInstance?.setup
|
|
143
|
+
|
|
144
|
+
// Setup is required for actions.
|
|
145
|
+
if (Data.typeOf(setup) === "Function") {
|
|
146
|
+
const builder = new ActionBuilder(actionInstance)
|
|
147
|
+
const configuredBuilder = setup(builder)
|
|
148
|
+
const buildResult = configuredBuilder.build()
|
|
149
|
+
const runner = new ActionRunner({
|
|
150
|
+
action: buildResult.action,
|
|
151
|
+
build: buildResult.build,
|
|
152
|
+
logger: this.#log
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
this.#runner = runner
|
|
156
|
+
} else {
|
|
157
|
+
throw Sass.new("Action setup must be a function.")
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Run the action with the provided input.
|
|
163
|
+
*
|
|
164
|
+
* @param {unknown} result - Input to pass to the action
|
|
165
|
+
* @returns {Promise<unknown>} Action result
|
|
166
|
+
*/
|
|
167
|
+
async runAction(result) {
|
|
168
|
+
if (!this.#runner)
|
|
169
|
+
throw new Error("Action not set up. Call setupAction() first.")
|
|
170
|
+
|
|
171
|
+
return await this.#runner.run(result)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Cleanup the action and hooks.
|
|
176
|
+
*
|
|
177
|
+
* @returns {Promise<void>}
|
|
178
|
+
*/
|
|
179
|
+
async cleanupAction() {
|
|
180
|
+
this.#debug("Cleaning up action for %s on %s", 2, this.action.meta?.kind, this.id)
|
|
181
|
+
|
|
182
|
+
await this.#cleanupHooks()
|
|
183
|
+
await this.#cleanupActionInstance()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Setup hooks if hook manager is present.
|
|
188
|
+
* Override in subclasses to customize hook setup.
|
|
189
|
+
*
|
|
190
|
+
* @protected
|
|
191
|
+
*/
|
|
192
|
+
async #setupHooks() {
|
|
193
|
+
const setup = this.#hookManager?.setup
|
|
194
|
+
|
|
195
|
+
const type = Data.typeOf(setup)
|
|
196
|
+
|
|
197
|
+
// No hooks attached.
|
|
198
|
+
if (type === "Null" || type === "Undefined")
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
if (type !== "Function")
|
|
202
|
+
throw Sass.new("Hook setup must be a function.")
|
|
203
|
+
|
|
204
|
+
await setup.call(
|
|
205
|
+
this.hookManager.hooks, {
|
|
206
|
+
action: this.action,
|
|
207
|
+
variables: this.#variables,
|
|
208
|
+
log: this.#log
|
|
209
|
+
}
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Cleanup hooks if hook manager is present.
|
|
215
|
+
* Override in subclasses to customize hook cleanup.
|
|
216
|
+
*
|
|
217
|
+
* @protected
|
|
218
|
+
*/
|
|
219
|
+
async #cleanupHooks() {
|
|
220
|
+
const cleanup = this.hookManager?.cleanup
|
|
221
|
+
|
|
222
|
+
if (!cleanup)
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
await cleanup.call(this.hookManager.hooks)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Cleanup the action instance.
|
|
230
|
+
* Override in subclasses to add custom cleanup logic.
|
|
231
|
+
*
|
|
232
|
+
* @protected
|
|
233
|
+
*/
|
|
234
|
+
async #cleanupActionInstance() {
|
|
235
|
+
const cleanup = this.action?.cleanup
|
|
236
|
+
|
|
237
|
+
if (!cleanup)
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
await cleanup.call(this.action)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
toString() {
|
|
244
|
+
return `${this.#file?.module || "UNDEFINED"} (${this.meta?.action || "UNDEFINED"})`
|
|
245
|
+
}
|
|
246
|
+
}
|