@voxgig/model 10.0.1 → 10.1.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/bin/voxgig-model +4 -4
- package/dist/model.js +29 -1
- package/dist/model.js.map +1 -1
- package/dist/producer/msg.d.ts +4 -0
- package/dist/producer/msg.js +170 -0
- package/dist/producer/msg.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -4
- package/src/build.ts +254 -0
- package/src/config.ts +49 -0
- package/src/init.ts +65 -0
- package/src/model.ts +330 -0
- package/src/producer/local.ts +116 -0
- package/src/producer/model.ts +122 -0
- package/src/producer/msg.ts +207 -0
- package/src/tsconfig.json +19 -0
- package/src/types.ts +162 -0
- package/src/watch.ts +335 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
|
|
2
|
+
import Path from 'path'
|
|
3
|
+
|
|
4
|
+
import type { Build, Producer, BuildContext, ProducerResult } from '../types'
|
|
5
|
+
|
|
6
|
+
const ORDERING_SPLIT_RE = /\s*,+\s*/
|
|
7
|
+
|
|
8
|
+
// Runs any producers local to the repo.
|
|
9
|
+
const local_producer: Producer = async (build: Build, ctx: BuildContext) => {
|
|
10
|
+
|
|
11
|
+
ctx.state.local = (ctx.state.local || {})
|
|
12
|
+
let actionDefs = ctx.state.local.actionDefs
|
|
13
|
+
|
|
14
|
+
if (null == actionDefs) {
|
|
15
|
+
actionDefs = ctx.state.local.actionDefs = []
|
|
16
|
+
|
|
17
|
+
// TODO: need to provide project root via build
|
|
18
|
+
let root = Path.resolve(build.path, '..', '..')
|
|
19
|
+
|
|
20
|
+
// TODO: build should do this
|
|
21
|
+
// Config is optional: with no .model-config build linked in, there are no
|
|
22
|
+
// declared actions and the model runs on its own.
|
|
23
|
+
let configBuildResult = build.use.config?.watch?.last
|
|
24
|
+
let configBuild = configBuildResult?.build()
|
|
25
|
+
let config = configBuild?.model || {}
|
|
26
|
+
|
|
27
|
+
let actions = config.sys?.model?.action ||
|
|
28
|
+
// NOTE: backwards compat
|
|
29
|
+
config.sys?.model?.builders ||
|
|
30
|
+
{}
|
|
31
|
+
|
|
32
|
+
let ordering = config.sys?.model?.order?.action
|
|
33
|
+
ordering = null == ordering ? Object.keys(actions) :
|
|
34
|
+
ordering.split(ORDERING_SPLIT_RE).filter((n: string) => null != n && '' != n)
|
|
35
|
+
|
|
36
|
+
// load actions
|
|
37
|
+
for (let name of ordering) {
|
|
38
|
+
let actiondef = actions[name]
|
|
39
|
+
|
|
40
|
+
if (null == actiondef) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
'Unknown model action "' + name +
|
|
43
|
+
'" referenced in sys.model.order.action')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (null == actiondef.load) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
'Model action "' + name + '" is missing a "load" path')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let actionpath = Path.join(root, actiondef.load)
|
|
52
|
+
|
|
53
|
+
let action = require(actionpath)
|
|
54
|
+
|
|
55
|
+
if (action instanceof Promise) {
|
|
56
|
+
action = await action
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const step = action.step || 'post'
|
|
60
|
+
|
|
61
|
+
actionDefs.push({ name, actiondef, action, step })
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const runActionDefs = actionDefs.filter((ad: any) => ctx.step === ad.step || 'all' === ad.step)
|
|
66
|
+
|
|
67
|
+
build.log.info({
|
|
68
|
+
point: ctx.step + '-actions', step: ctx.step, actions: runActionDefs,
|
|
69
|
+
note: runActionDefs.map((ad: any) => ad.name).join(';')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
let ok = true
|
|
73
|
+
let areslog = []
|
|
74
|
+
let reload = false
|
|
75
|
+
|
|
76
|
+
for (let actionDef of runActionDefs) {
|
|
77
|
+
try {
|
|
78
|
+
// TODO: this call signature needs to be well-defined as it is an external interface
|
|
79
|
+
let ares = await actionDef.action(build.model, build, ctx)
|
|
80
|
+
ok = ok && (null == ares || !!ares.ok)
|
|
81
|
+
reload = reload || ares?.reload
|
|
82
|
+
|
|
83
|
+
areslog.push(ares)
|
|
84
|
+
|
|
85
|
+
if (!ok) { break }
|
|
86
|
+
}
|
|
87
|
+
catch (err: any) {
|
|
88
|
+
if (!err.__logged__) {
|
|
89
|
+
build.log.error({
|
|
90
|
+
point: ctx.step + '-action', step: ctx.step, action: actionDef,
|
|
91
|
+
note: actionDef.name,
|
|
92
|
+
err
|
|
93
|
+
})
|
|
94
|
+
err.__logged__ = true
|
|
95
|
+
}
|
|
96
|
+
throw err
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let pr: ProducerResult = {
|
|
101
|
+
ok,
|
|
102
|
+
reload,
|
|
103
|
+
name: 'local',
|
|
104
|
+
step: ctx.step,
|
|
105
|
+
active: true,
|
|
106
|
+
errs: [],
|
|
107
|
+
runlog: []
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return pr
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
export {
|
|
115
|
+
local_producer
|
|
116
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
|
|
2
|
+
import Path from 'path'
|
|
3
|
+
|
|
4
|
+
import type { Build, Producer, BuildContext, ProducerResult } from '../types'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
// Serialize the model to two-space-indented JSON with object keys in strictly
|
|
8
|
+
// lexical (UTF-8 byte) order, byte-for-byte identical to the Go
|
|
9
|
+
// implementation's encoding/json. JSON.stringify cannot express this: JS
|
|
10
|
+
// objects iterate integer-like keys ("9", "10") in numeric order ahead of the
|
|
11
|
+
// other keys regardless of insertion order, so the order must be imposed
|
|
12
|
+
// during serialization, and the default JS string sort compares UTF-16 code
|
|
13
|
+
// units, which disagrees with Go's byte order for astral-plane keys. Arrays
|
|
14
|
+
// keep their order. Values only producer mutation can introduce mirror
|
|
15
|
+
// JSON.stringify: undefined, functions, and symbols are dropped from objects
|
|
16
|
+
// and become null in arrays (sparse holes too), and toJSON results are fed
|
|
17
|
+
// back through the canonical serializer.
|
|
18
|
+
function jsonify(value: any, indent: string): string {
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
if (0 === value.length) {
|
|
21
|
+
return '[]'
|
|
22
|
+
}
|
|
23
|
+
const inner = indent + ' '
|
|
24
|
+
// Array.from visits holes (as undefined); map/join would skip them.
|
|
25
|
+
return '[\n' +
|
|
26
|
+
Array.from(value, (item) => inner + jsonify(item, inner)).join(',\n') +
|
|
27
|
+
'\n' + indent + ']'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (null != value && 'object' === typeof value) {
|
|
31
|
+
if ('function' === typeof value.toJSON) {
|
|
32
|
+
return jsonify(value.toJSON(), indent)
|
|
33
|
+
}
|
|
34
|
+
const keys = Object.keys(value)
|
|
35
|
+
.filter((key) => {
|
|
36
|
+
const item = value[key]
|
|
37
|
+
return undefined !== item &&
|
|
38
|
+
'function' !== typeof item && 'symbol' !== typeof item
|
|
39
|
+
})
|
|
40
|
+
.sort((a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b)))
|
|
41
|
+
if (0 === keys.length) {
|
|
42
|
+
return '{}'
|
|
43
|
+
}
|
|
44
|
+
const inner = indent + ' '
|
|
45
|
+
return '{\n' +
|
|
46
|
+
keys.map((key) =>
|
|
47
|
+
inner + jstr(key) + ': ' + jsonify(value[key], inner))
|
|
48
|
+
.join(',\n') +
|
|
49
|
+
'\n' + indent + '}'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const scalar = jstr(value)
|
|
53
|
+
return undefined === scalar ? 'null' : scalar
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
// JSON.stringify, plus the U+2028/U+2029 escapes Go's encoding/json always
|
|
58
|
+
// applies even with HTML escaping off. JSON.stringify emits the separators
|
|
59
|
+
// literally (both forms are valid JSON), so escape them here for byte parity.
|
|
60
|
+
function jstr(value: any): string | undefined {
|
|
61
|
+
const out = JSON.stringify(value)
|
|
62
|
+
return undefined === out ? undefined :
|
|
63
|
+
out.replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
// Builds the main model file, after unification.
|
|
68
|
+
const model_producer: Producer = async (build: Build, ctx: BuildContext) => {
|
|
69
|
+
let pr: ProducerResult = {
|
|
70
|
+
ok: true,
|
|
71
|
+
name: 'model',
|
|
72
|
+
reload: false,
|
|
73
|
+
step: ctx.step,
|
|
74
|
+
active: true,
|
|
75
|
+
errs: [],
|
|
76
|
+
runlog: []
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if ('post' !== ctx.step) {
|
|
80
|
+
return pr
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let json = jsonify(build.model, '')
|
|
84
|
+
|
|
85
|
+
let filename = Path.basename(build.path)
|
|
86
|
+
let filenameparts = filename.match(/^(.*)\.[^.]+$/)
|
|
87
|
+
if (filenameparts) {
|
|
88
|
+
filename = filenameparts[1]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let file = build.opts.base + '/' + filename + '.json'
|
|
92
|
+
|
|
93
|
+
// Skip write when output is unchanged — avoids mtime churn that would
|
|
94
|
+
// invalidate caches (here and in downstream watchers).
|
|
95
|
+
let existing: string | undefined
|
|
96
|
+
try { existing = build.fs.readFileSync(file, 'utf8') } catch { }
|
|
97
|
+
|
|
98
|
+
if (existing === json) {
|
|
99
|
+
build.log.debug({
|
|
100
|
+
point: 'write-model-skip',
|
|
101
|
+
path: file,
|
|
102
|
+
note: file.replace(process.cwd(), '.') + ' (unchanged)'
|
|
103
|
+
})
|
|
104
|
+
return pr
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
build.log.info({
|
|
108
|
+
point: 'write-model',
|
|
109
|
+
path: file,
|
|
110
|
+
note: file.replace(process.cwd(), '.')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
build.fs.mkdirSync(Path.dirname(file), { recursive: true })
|
|
114
|
+
build.fs.writeFileSync(file, json)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
return pr
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export {
|
|
121
|
+
model_producer
|
|
122
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/* Copyright © 2026 Voxgig Ltd, MIT License. */
|
|
2
|
+
|
|
3
|
+
import type { Build, Producer, BuildContext, ProducerResult } from '../types'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
// Message declarations live in `main.msg` and come in two shapes.
|
|
7
|
+
//
|
|
8
|
+
// The legacy shape nests the pattern pairs, so the pattern is the path down
|
|
9
|
+
// to the definition, and the definition sits at whatever depth that reaches:
|
|
10
|
+
//
|
|
11
|
+
// aim: web: { on: todo: { save: item: { '$': { file: './web_save_item' } } } }
|
|
12
|
+
//
|
|
13
|
+
// The declared shape is flat - one entry per message, keyed by the message
|
|
14
|
+
// name, with the pattern as data and the definition at a known depth:
|
|
15
|
+
//
|
|
16
|
+
// save_item: { pat: [ {aim: web}, {save: item} ], doc: "Save a todo item" }
|
|
17
|
+
//
|
|
18
|
+
// The two are told apart by `pat`: a definition is a map holding a `pat`
|
|
19
|
+
// LIST, and a legacy chain node never holds one, because every value in a
|
|
20
|
+
// chain node is a map - either the next pattern level or the '$' leaf. So the
|
|
21
|
+
// discriminator holds even for a legacy pattern pair that happens to be
|
|
22
|
+
// spelled `pat:`. Anything without a `pat` list is left alone entirely, which
|
|
23
|
+
// is what lets the two shapes coexist while models migrate message by
|
|
24
|
+
// message.
|
|
25
|
+
//
|
|
26
|
+
// Only the declared shape is checked here. The checks are the two things the
|
|
27
|
+
// flat shape makes checkable and the nested one did not:
|
|
28
|
+
//
|
|
29
|
+
// 1. the entry key agrees with the last pattern pair - the key names the
|
|
30
|
+
// action file, a convention the legacy shape got implicitly from the
|
|
31
|
+
// chain's leaf, and which becomes a real consistency check once the key
|
|
32
|
+
// is written out by hand;
|
|
33
|
+
// 2. no two messages declare the same pattern - previously impossible to
|
|
34
|
+
// state twice, because the pattern WAS the path.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
// Report a problem against the message it belongs to.
|
|
38
|
+
function msgerr(name: string, why: string): string {
|
|
39
|
+
return 'model msg "' + name + '": ' + why
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
function isObj(val: any): boolean {
|
|
44
|
+
return null != val && 'object' === typeof val && !Array.isArray(val)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
// A message definition declares its pattern as a list; a chain node never does.
|
|
49
|
+
function isMsgDef(val: any): boolean {
|
|
50
|
+
return isObj(val) && Array.isArray(val.pat)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
// Sort in UTF-8 byte order, matching Go's sort.Strings, so both
|
|
55
|
+
// implementations report the same problems in the same order. The default JS
|
|
56
|
+
// string sort compares UTF-16 code units, which disagrees for astral-plane
|
|
57
|
+
// names (see the model producer's jsonify, which sorts keys the same way).
|
|
58
|
+
function sortNames(names: string[]): string[] {
|
|
59
|
+
return names.sort((a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b)))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
// Validate the declared-shape message entries in main.msg, returning one
|
|
64
|
+
// message per problem found (empty when the model is valid, which includes a
|
|
65
|
+
// model with no messages at all, or only legacy chains).
|
|
66
|
+
function checkMsg(model: any): string[] {
|
|
67
|
+
const problems: string[] = []
|
|
68
|
+
|
|
69
|
+
const msg = model?.main?.msg
|
|
70
|
+
if (!isObj(msg)) {
|
|
71
|
+
return problems
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Canonical pattern -> the message that claimed it first.
|
|
75
|
+
const seen: { [canon: string]: string } = {}
|
|
76
|
+
|
|
77
|
+
for (const name of sortNames(Object.keys(msg))) {
|
|
78
|
+
const def = msg[name]
|
|
79
|
+
|
|
80
|
+
// Legacy chain node (or not a map at all): not this check's business.
|
|
81
|
+
if (!isMsgDef(def)) {
|
|
82
|
+
continue
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const pat: any[] = def.pat
|
|
86
|
+
|
|
87
|
+
if (0 === pat.length) {
|
|
88
|
+
problems.push(msgerr(name, 'pat declares no pattern pairs'))
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Reduce the pattern to its pairs, stopping at the first malformed one -
|
|
93
|
+
// the rest of the checks read the pairs, so there is nothing further to
|
|
94
|
+
// say about this message until its pattern is well-formed.
|
|
95
|
+
//
|
|
96
|
+
// Two renderings: `pairs` reads well in a message, and `canon` identifies
|
|
97
|
+
// the pattern. They cannot be the same string, because `key:value` joined
|
|
98
|
+
// by commas is ambiguous once a key or value contains a delimiter -
|
|
99
|
+
// [{a: "b,c:d"}] and [{a: b}, {c: d}] would both render `a:b,c:d` and the
|
|
100
|
+
// second would be rejected as a duplicate of the first. Quoting each part
|
|
101
|
+
// removes the ambiguity: a delimiter inside a part is escaped, so only
|
|
102
|
+
// genuinely equal patterns produce equal keys.
|
|
103
|
+
const pairs: string[] = []
|
|
104
|
+
const canon: string[] = []
|
|
105
|
+
let last: string[] | undefined
|
|
106
|
+
|
|
107
|
+
for (let pI = 0; pI < pat.length; pI++) {
|
|
108
|
+
const pair = pat[pI]
|
|
109
|
+
const keys = isObj(pair) ? Object.keys(pair) : []
|
|
110
|
+
|
|
111
|
+
if (1 !== keys.length) {
|
|
112
|
+
problems.push(msgerr(name, 'pat pair ' + pI +
|
|
113
|
+
' is not a single key:value pair'))
|
|
114
|
+
last = undefined
|
|
115
|
+
break
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const key = keys[0]
|
|
119
|
+
const val = pair[key]
|
|
120
|
+
|
|
121
|
+
if ('string' !== typeof val) {
|
|
122
|
+
problems.push(msgerr(name, 'pat pair ' + pI + ' (' + key +
|
|
123
|
+
') value is not a string'))
|
|
124
|
+
last = undefined
|
|
125
|
+
break
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
pairs.push(key + ':' + val)
|
|
129
|
+
canon.push(JSON.stringify(key) + ':' + JSON.stringify(val))
|
|
130
|
+
last = [key, val]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (null == last) {
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// The entry key names the action file, so it must agree with the last
|
|
138
|
+
// pattern pair.
|
|
139
|
+
const expected = last[0] + '_' + last[1]
|
|
140
|
+
if (name !== expected) {
|
|
141
|
+
problems.push(msgerr(name, 'key does not match last pat pair ' +
|
|
142
|
+
last[0] + ':' + last[1] + ' (expected "' + expected + '")'))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const canonKey = canon.join(',')
|
|
146
|
+
if (null == seen[canonKey]) {
|
|
147
|
+
seen[canonKey] = name
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
problems.push(msgerr(name, 'pat [' + pairs.join(',') +
|
|
151
|
+
'] is already declared by "' + seen[canonKey] + '"'))
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return problems
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
// Checks the message declarations before anything is written.
|
|
160
|
+
//
|
|
161
|
+
// This runs in BOTH phases, and must: a `pre` action can rewrite model source
|
|
162
|
+
// and request a reload, and the build re-resolves the model AFTER the pre
|
|
163
|
+
// phase has finished (see BuildImpl.run). A pre-only check would then have
|
|
164
|
+
// validated a model that no longer exists, and the model producer would write
|
|
165
|
+
// the regenerated one unchecked. Checking again in post closes that window -
|
|
166
|
+
// this producer is first in the pipeline, so it still runs ahead of the model
|
|
167
|
+
// producer, and a build whose model went bad during a reload fails with
|
|
168
|
+
// nothing written.
|
|
169
|
+
const msg_producer: Producer = async (build: Build, ctx: BuildContext) => {
|
|
170
|
+
const pr: ProducerResult = {
|
|
171
|
+
ok: true,
|
|
172
|
+
name: 'msg',
|
|
173
|
+
reload: false,
|
|
174
|
+
step: ctx.step,
|
|
175
|
+
active: true,
|
|
176
|
+
errs: [],
|
|
177
|
+
runlog: []
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const problems = checkMsg(build.model)
|
|
181
|
+
|
|
182
|
+
if (0 < problems.length) {
|
|
183
|
+
pr.ok = false
|
|
184
|
+
pr.errs = problems.map((problem) => new Error(problem))
|
|
185
|
+
|
|
186
|
+
// Add them to the build too. BuildImpl.run collects the errors a producer
|
|
187
|
+
// THROWS, but not the ones it returns, so a returned error would
|
|
188
|
+
// otherwise be missing from the BuildResult. (The Go port needs no such
|
|
189
|
+
// push: its runProducer merges a failed producer's Errs itself. Both
|
|
190
|
+
// implementations end up with the same errors on the build.)
|
|
191
|
+
build.errs.push(...pr.errs)
|
|
192
|
+
|
|
193
|
+
build.log.error({
|
|
194
|
+
point: 'msg-invalid',
|
|
195
|
+
count: problems.length,
|
|
196
|
+
note: problems.join('; ')
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return pr
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
export {
|
|
205
|
+
msg_producer,
|
|
206
|
+
checkMsg,
|
|
207
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"composite": true,
|
|
4
|
+
"tsBuildInfoFile": "../dist/tsconfig.tsbuildinfo",
|
|
5
|
+
"esModuleInterop": true,
|
|
6
|
+
"module": "nodenext",
|
|
7
|
+
"noEmitOnError": true,
|
|
8
|
+
"outDir":"../dist",
|
|
9
|
+
"rootDir":".",
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"sourceMap": true,
|
|
13
|
+
"strict": true,
|
|
14
|
+
"target": "es2021",
|
|
15
|
+
"declaration": true,
|
|
16
|
+
"declarationDir": "../dist"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/* Copyright © 2021-2024 Voxgig Ltd, MIT License. */
|
|
2
|
+
|
|
3
|
+
import Fs from 'node:fs'
|
|
4
|
+
|
|
5
|
+
import Pino from 'pino'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
import type { Aontu } from 'aontu'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
type FST = typeof Fs
|
|
12
|
+
|
|
13
|
+
type Log = ReturnType<typeof Pino>
|
|
14
|
+
|
|
15
|
+
interface Build {
|
|
16
|
+
id: string
|
|
17
|
+
base: string
|
|
18
|
+
path: string
|
|
19
|
+
opts: { [key: string]: any }
|
|
20
|
+
pdef: ProducerDef[]
|
|
21
|
+
spec: BuildSpec
|
|
22
|
+
model: any
|
|
23
|
+
use: { [name: string]: any }
|
|
24
|
+
errs: any[]
|
|
25
|
+
ctx: BuildContext
|
|
26
|
+
deps: any
|
|
27
|
+
run: (rspec: RunSpec) => Promise<BuildResult>
|
|
28
|
+
log: Log
|
|
29
|
+
fs: FST
|
|
30
|
+
dryrun: boolean
|
|
31
|
+
args: any
|
|
32
|
+
aontu: Aontu
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
interface BuildResult {
|
|
38
|
+
ok: boolean
|
|
39
|
+
builder?: string
|
|
40
|
+
path?: string
|
|
41
|
+
producers?: ProducerResult[]
|
|
42
|
+
step?: string
|
|
43
|
+
errs: any[]
|
|
44
|
+
runlog: string[]
|
|
45
|
+
build?: () => Build
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
interface BuildContext {
|
|
50
|
+
step: 'pre' | 'post'
|
|
51
|
+
watch: boolean,
|
|
52
|
+
state: Record<string, any>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
interface BuildSpec {
|
|
57
|
+
path?: string
|
|
58
|
+
base?: string
|
|
59
|
+
res?: ProducerDef[]
|
|
60
|
+
require?: any
|
|
61
|
+
use?: { [name: string]: any }
|
|
62
|
+
log?: Log
|
|
63
|
+
idle?: number
|
|
64
|
+
name?: string
|
|
65
|
+
debug?: boolean | string
|
|
66
|
+
dryrun?: boolean,
|
|
67
|
+
buildargs?: any,
|
|
68
|
+
watch?: {
|
|
69
|
+
mod?: boolean // file modification
|
|
70
|
+
add?: boolean // file addition
|
|
71
|
+
rem?: boolean // file deletion
|
|
72
|
+
}
|
|
73
|
+
fs: FST
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
interface ProducerDef {
|
|
78
|
+
path: string
|
|
79
|
+
build: Producer
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
type Producer = (
|
|
83
|
+
build: Build,
|
|
84
|
+
ctx: BuildContext,
|
|
85
|
+
) => Promise<ProducerResult>
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
interface ProducerResult {
|
|
89
|
+
ok: boolean
|
|
90
|
+
name: string
|
|
91
|
+
active: boolean
|
|
92
|
+
errs: any[]
|
|
93
|
+
runlog: string[]
|
|
94
|
+
step: string
|
|
95
|
+
reload: boolean
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
type Run = {
|
|
100
|
+
canon: string
|
|
101
|
+
path: string
|
|
102
|
+
start: number
|
|
103
|
+
end: number
|
|
104
|
+
result?: BuildResult
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
type RunSpec = {
|
|
109
|
+
watch: boolean
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
type Canon = {
|
|
113
|
+
path: string
|
|
114
|
+
isFolder: boolean
|
|
115
|
+
when: number
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
type ChangeItem = {
|
|
120
|
+
path: string
|
|
121
|
+
when: number
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface ModelSpec {
|
|
125
|
+
path?: string
|
|
126
|
+
base?: string
|
|
127
|
+
require?: any
|
|
128
|
+
log?: Log
|
|
129
|
+
idle?: number
|
|
130
|
+
debug?: boolean | string
|
|
131
|
+
dryrun?: boolean
|
|
132
|
+
buildargs?: any
|
|
133
|
+
fs?: any
|
|
134
|
+
// Resolve a .model-config/model-config.aon (auto-created when missing)
|
|
135
|
+
// that declares the build actions and their order. Defaults to true; set
|
|
136
|
+
// false to skip the config entirely and run the model on its own.
|
|
137
|
+
config?: boolean
|
|
138
|
+
watch?: {
|
|
139
|
+
mod?: boolean // file modification
|
|
140
|
+
add?: boolean // file addition
|
|
141
|
+
rem?: boolean // file deletion
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
export type {
|
|
148
|
+
Build,
|
|
149
|
+
BuildResult,
|
|
150
|
+
ProducerDef,
|
|
151
|
+
Producer,
|
|
152
|
+
ProducerResult,
|
|
153
|
+
BuildContext,
|
|
154
|
+
BuildSpec,
|
|
155
|
+
Log,
|
|
156
|
+
Run,
|
|
157
|
+
Canon,
|
|
158
|
+
ChangeItem,
|
|
159
|
+
RunSpec,
|
|
160
|
+
ModelSpec,
|
|
161
|
+
FST,
|
|
162
|
+
}
|