@voxgig/apidef 8.13.0 → 8.15.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-apidef +1 -174
- package/dist/apidef.d.ts +2 -2
- package/dist/apidef.js +2 -2
- package/dist/apidef.js.map +1 -1
- package/dist/builder/entity/entity.d.ts +5 -1
- package/dist/builder/entity/entity.js +17 -1
- package/dist/builder/entity/entity.js.map +1 -1
- package/dist/builder/flow/flowHeuristic01.js +14 -14
- package/dist/builder/flow/flowHeuristic01.js.map +1 -1
- package/dist/cli.d.ts +38 -0
- package/dist/cli.js +279 -0
- package/dist/cli.js.map +1 -0
- package/dist/guide/guide.js +8 -5
- package/dist/guide/guide.js.map +1 -1
- package/dist/model.d.ts +35 -33
- package/dist/resolved.d.ts +10 -6
- package/dist/resolved.js +65 -15
- package/dist/resolved.js.map +1 -1
- package/dist/transform/args.js +19 -19
- package/dist/transform/args.js.map +1 -1
- package/dist/transform/clean.js +4 -0
- package/dist/transform/clean.js.map +1 -1
- package/dist/transform/contract.d.ts +0 -2
- package/dist/transform/contract.js +6 -75
- package/dist/transform/contract.js.map +1 -1
- package/dist/transform/entity.d.ts +2 -1
- package/dist/transform/entity.js +13 -1
- package/dist/transform/entity.js.map +1 -1
- package/dist/transform/field.js +64 -64
- package/dist/transform/field.js.map +1 -1
- package/dist/transform/flowstep.js +42 -42
- package/dist/transform/flowstep.js.map +1 -1
- package/dist/transform/graphql.js +8 -8
- package/dist/transform/graphql.js.map +1 -1
- package/dist/transform/operation.js +9 -9
- package/dist/transform/operation.js.map +1 -1
- package/dist/transform/select.js +13 -13
- package/dist/transform/select.js.map +1 -1
- package/dist/transform/top.js +1 -1
- package/dist/transform/top.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utility.d.ts +2 -1
- package/dist/utility.js +6 -1
- package/dist/utility.js.map +1 -1
- package/model/apidef.aon +128 -157
- package/model/guide.aon +3 -2
- package/package.json +5 -3
- package/src/apidef.ts +4 -3
- package/src/builder/entity/entity.ts +18 -1
- package/src/builder/flow/flowHeuristic01.ts +14 -14
- package/src/cli.ts +341 -0
- package/src/guide/guide.ts +8 -4
- package/src/model.ts +36 -37
- package/src/resolved.ts +62 -17
- package/src/transform/args.ts +20 -20
- package/src/transform/clean.ts +5 -0
- package/src/transform/contract.ts +6 -71
- package/src/transform/entity.ts +13 -1
- package/src/transform/field.ts +66 -69
- package/src/transform/flowstep.ts +42 -42
- package/src/transform/graphql.ts +8 -8
- package/src/transform/operation.ts +9 -9
- package/src/transform/select.ts +13 -13
- package/src/transform/top.ts +1 -1
- package/src/types.ts +1 -1
- package/src/utility.ts +8 -1
|
@@ -35,7 +35,8 @@ function resolveEntity(
|
|
|
35
35
|
each(kit.entity, ((entity: any, entityName: string) => {
|
|
36
36
|
const entityFile = (null == opts.outprefix ? '' : opts.outprefix) + entityName + '.aon'
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
const { model, relations } = entityAncestorSource(entity)
|
|
39
|
+
let entityJSONIC = formatJSONIC(model).trim()
|
|
39
40
|
entityJSONIC = entityJSONIC.substring(1, entityJSONIC.length - 1)
|
|
40
41
|
|
|
41
42
|
const fieldAliasesSrc = fieldAliases(entity)
|
|
@@ -45,6 +46,7 @@ function resolveEntity(
|
|
|
45
46
|
`main: ${KIT}: entity: ${entity.name}: {\n\n` +
|
|
46
47
|
` alias: field: ${fieldAliasesSrc}\n` +
|
|
47
48
|
entityJSONIC +
|
|
49
|
+
relations +
|
|
48
50
|
'\n\n}\n'
|
|
49
51
|
|
|
50
52
|
entityFiles.push({ name: entityFile, src: entitySrc })
|
|
@@ -65,6 +67,20 @@ function resolveEntity(
|
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
|
|
70
|
+
function entityAncestorSource(entity: any): { model: any, relations: string } {
|
|
71
|
+
const model = { ...entity }
|
|
72
|
+
const ancestors: string[][] = entity.relations?.ancestors ?? []
|
|
73
|
+
if (null != entity.relations) {
|
|
74
|
+
model.relations = { ...entity.relations }
|
|
75
|
+
delete model.relations.ancestors
|
|
76
|
+
if (0 === Object.keys(model.relations).length) delete model.relations
|
|
77
|
+
}
|
|
78
|
+
const chains = ancestors.map(chain => ' [' + chain.map(name =>
|
|
79
|
+
'path(' + JSON.stringify('$.main.kit.entity.' + name) + ')').join(' ') + ']')
|
|
80
|
+
return { model, relations: 0 === chains.length ? '' :
|
|
81
|
+
'\n relations: ancestors: [\n' + chains.join('\n') + '\n ]' }
|
|
82
|
+
}
|
|
83
|
+
|
|
68
84
|
|
|
69
85
|
function gcEntityFiles(
|
|
70
86
|
fs: any,
|
|
@@ -126,4 +142,5 @@ function fieldAliases(_entity: any): string {
|
|
|
126
142
|
export {
|
|
127
143
|
resolveEntity,
|
|
128
144
|
gcEntityFiles,
|
|
145
|
+
entityAncestorSource,
|
|
129
146
|
}
|
|
@@ -81,11 +81,11 @@ function resolveBasicEntityFlow(ctx: any, entity: any) {
|
|
|
81
81
|
|
|
82
82
|
let num = (i * size(apiEntity.fields) * 10)
|
|
83
83
|
each(apiEntity.fields, (field) => {
|
|
84
|
-
ent[field.
|
|
85
|
-
'number' === field.
|
|
86
|
-
'boolean' === field.
|
|
87
|
-
'object' === field.
|
|
88
|
-
'array' === field.
|
|
84
|
+
ent[field.n] =
|
|
85
|
+
'number' === field.t ? num :
|
|
86
|
+
'boolean' === field.t ? 0 === num % 2 :
|
|
87
|
+
'object' === field.t ? {} :
|
|
88
|
+
'array' === field.t ? [] :
|
|
89
89
|
's' + (num.toString(16))
|
|
90
90
|
num++
|
|
91
91
|
})
|
|
@@ -108,9 +108,9 @@ function resolveBasicEntityFlow(ctx: any, entity: any) {
|
|
|
108
108
|
const point = findMainLoadPoint(entop.load)
|
|
109
109
|
|
|
110
110
|
// Get additional required match properties
|
|
111
|
-
each(point?.
|
|
112
|
-
if (param.
|
|
113
|
-
let ancestorName = param.
|
|
111
|
+
each(point?.g.params, (param: any) => {
|
|
112
|
+
if (param.r) {
|
|
113
|
+
let ancestorName = param.n
|
|
114
114
|
let ancestorEntity = apimodel.main.api.entity[ancestorName]
|
|
115
115
|
|
|
116
116
|
if (null == ancestorEntity) {
|
|
@@ -122,10 +122,10 @@ function resolveBasicEntityFlow(ctx: any, entity: any) {
|
|
|
122
122
|
flow.model.param[`${model.NAME}_TEST_${ancestorEntity.NAME}_ENTID`] = {
|
|
123
123
|
[ancestorEntity.name + '01']: ancestorEntity.NAME + '01'
|
|
124
124
|
}
|
|
125
|
-
am[param.
|
|
125
|
+
am[param.n] =
|
|
126
126
|
`\`dm$=p.${model.NAME}_TEST_${ancestorEntity.NAME}_ENTID.${ancestorEntity.name}01\``
|
|
127
127
|
|
|
128
|
-
data[`${nom(apiEntity, 'NAME')}01`][param.
|
|
128
|
+
data[`${nom(apiEntity, 'NAME')}01`][param.n] = ancestorEntity.NAME + '01'
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
})
|
|
@@ -197,7 +197,7 @@ function resolveBasicEntityFlow(ctx: any, entity: any) {
|
|
|
197
197
|
|
|
198
198
|
|
|
199
199
|
function findMainLoadPoint(op: ModelOp): ModelPoint | undefined {
|
|
200
|
-
let cands = op.points.filter(a => 'id' === getelem(a.
|
|
200
|
+
let cands = op.points.filter(a => 'id' === getelem(a.s, -1)?.var)
|
|
201
201
|
return cands[0]
|
|
202
202
|
}
|
|
203
203
|
|
|
@@ -207,12 +207,12 @@ function makeUpdateData(name: string, apiEntity: any, flow: any, id: string) {
|
|
|
207
207
|
const data = flow.model.test.entity[apiEntity.name]
|
|
208
208
|
|
|
209
209
|
const dataFields =
|
|
210
|
-
each(apiEntity.
|
|
211
|
-
const stringFields = each(dataFields).filter(f => 'string' === f.
|
|
210
|
+
each(apiEntity.fields).filter(f => 'id' !== f.n && !f.n.includes('_id'))
|
|
211
|
+
const stringFields = each(dataFields).filter(f => 'string' === f.t)
|
|
212
212
|
|
|
213
213
|
if (0 < size(stringFields)) {
|
|
214
214
|
const f = stringFields[0]
|
|
215
|
-
ud[f.
|
|
215
|
+
ud[f.n] = data[id][f.n] + '-`$WHEN`'
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
return ud
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/* Copyright (c) 2024-2026 Voxgig, MIT License */
|
|
2
|
+
|
|
3
|
+
// The command-line tool. `bin/voxgig-apidef` and the standalone executable
|
|
4
|
+
// entry points are shims over `main`, so the option handling and the project
|
|
5
|
+
// layout live in one place and under test.
|
|
6
|
+
|
|
7
|
+
import * as Fs from 'node:fs'
|
|
8
|
+
import Path from 'node:path'
|
|
9
|
+
import { parseArgs } from 'node:util'
|
|
10
|
+
|
|
11
|
+
import { Shape, Fault } from 'shape'
|
|
12
|
+
|
|
13
|
+
import { ApiDef } from './apidef'
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
const Pkg = require('../package.json')
|
|
17
|
+
|
|
18
|
+
const GUIDE_FILE = 'guide.aon'
|
|
19
|
+
const LEGACY_GUIDE_FILE = 'guide.aontu'
|
|
20
|
+
const BASE_GUIDE_FILE = 'base-guide.aon'
|
|
21
|
+
|
|
22
|
+
const WATCH_INTERVAL_MS = 500
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
type CliOptions = {
|
|
26
|
+
name: string
|
|
27
|
+
folder: string
|
|
28
|
+
def: string
|
|
29
|
+
prefix?: string
|
|
30
|
+
watch: boolean
|
|
31
|
+
debug?: string
|
|
32
|
+
help: boolean
|
|
33
|
+
version: boolean
|
|
34
|
+
extra?: string[]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
type CliProject = {
|
|
39
|
+
root: string
|
|
40
|
+
folder: string
|
|
41
|
+
outprefix: string
|
|
42
|
+
def: string
|
|
43
|
+
model: { name: string, def: string }
|
|
44
|
+
guide: string
|
|
45
|
+
legacyguide: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
type CliIO = {
|
|
50
|
+
log: (...args: any[]) => void
|
|
51
|
+
error: (...args: any[]) => void
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
const CONSOLE_IO: CliIO = {
|
|
56
|
+
log: (...args: any[]) => console.log(...args),
|
|
57
|
+
error: (...args: any[]) => console.error(...args),
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
function usage(): string {
|
|
62
|
+
return [
|
|
63
|
+
'Usage: voxgig-apidef <name> [options]',
|
|
64
|
+
'',
|
|
65
|
+
'Build the API model for project <name> from an OpenAPI, Swagger or',
|
|
66
|
+
'GraphQL definition file.',
|
|
67
|
+
'',
|
|
68
|
+
'Options:',
|
|
69
|
+
' -f, --folder <dir> project folder (default: <name>)',
|
|
70
|
+
' -d, --def <file> the API definition file (required)',
|
|
71
|
+
' -p, --prefix <text> prefix for generated file names (default: <name>-)',
|
|
72
|
+
' -w, --watch rebuild when the definition file changes',
|
|
73
|
+
' -g, --debug <level> log level (debug, info, warn, error); also writes',
|
|
74
|
+
' the resolved definition as <def>.full.json',
|
|
75
|
+
' -h, --help print this help and exit',
|
|
76
|
+
' -v, --version print the package version and exit',
|
|
77
|
+
'',
|
|
78
|
+
'The model is written to <folder>/model, which must already hold the guide',
|
|
79
|
+
'entry file <folder>/model/guide/<prefix>' + GUIDE_FILE + ':',
|
|
80
|
+
'',
|
|
81
|
+
' @"@voxgig/apidef/model/' + GUIDE_FILE + '"',
|
|
82
|
+
' @"./<prefix>' + BASE_GUIDE_FILE + '"',
|
|
83
|
+
].join('\n')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
function resolveOptions(argv: string[]): CliOptions {
|
|
88
|
+
const args = parseArgs({
|
|
89
|
+
args: argv,
|
|
90
|
+
allowPositionals: true,
|
|
91
|
+
options: {
|
|
92
|
+
folder: { type: 'string', short: 'f', default: '' },
|
|
93
|
+
def: { type: 'string', short: 'd', default: '' },
|
|
94
|
+
prefix: { type: 'string', short: 'p' },
|
|
95
|
+
watch: { type: 'boolean', short: 'w' },
|
|
96
|
+
debug: { type: 'string', short: 'g' },
|
|
97
|
+
help: { type: 'boolean', short: 'h' },
|
|
98
|
+
version: { type: 'boolean', short: 'v' },
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const [name, ...extra] = args.positionals
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
name,
|
|
106
|
+
folder: '' === args.values.folder ? name : args.values.folder,
|
|
107
|
+
def: args.values.def,
|
|
108
|
+
prefix: args.values.prefix,
|
|
109
|
+
watch: !!args.values.watch,
|
|
110
|
+
debug: args.values.debug,
|
|
111
|
+
help: !!args.values.help,
|
|
112
|
+
version: !!args.values.version,
|
|
113
|
+
extra,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
function validateOptions(rawOptions: CliOptions): CliOptions {
|
|
119
|
+
// An absent prefix defaults to <name>- later, an empty one is a valid
|
|
120
|
+
// choice, an absent debug leaves the library its own default, and `extra`
|
|
121
|
+
// is not an option at all; the shape rejects all four, so they are taken
|
|
122
|
+
// out and checked here. A positional after the name is a typo rather than
|
|
123
|
+
// a spare, and was being dropped without a word.
|
|
124
|
+
const { prefix, debug, extra, ...shaped } = rawOptions
|
|
125
|
+
|
|
126
|
+
if (null != extra && 0 < extra.length) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
'Unexpected extra arguments: ' + extra.join(' ') + '\n\n' + usage())
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const optShape = Shape({
|
|
132
|
+
name: Fault('The first argument should be the project name.', String),
|
|
133
|
+
folder: String,
|
|
134
|
+
def: Fault('A definition file is required: --def <file>.', String),
|
|
135
|
+
watch: Boolean,
|
|
136
|
+
help: Boolean,
|
|
137
|
+
version: Boolean,
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
if (null != prefix && 'string' !== typeof prefix) {
|
|
141
|
+
throw new Error('The prefix should be a string.')
|
|
142
|
+
}
|
|
143
|
+
if (null != debug && 'string' !== typeof debug) {
|
|
144
|
+
throw new Error('The debug level should be a string.')
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const err: any[] = []
|
|
148
|
+
const options: CliOptions = optShape(shaped, { err })
|
|
149
|
+
|
|
150
|
+
if (err[0]) {
|
|
151
|
+
throw new Error(err[0].text)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
options.prefix = prefix
|
|
155
|
+
options.debug = debug
|
|
156
|
+
|
|
157
|
+
options.def = Path.resolve(options.def)
|
|
158
|
+
const stat = Fs.statSync(options.def, { throwIfNoEntry: false })
|
|
159
|
+
if (null == stat) {
|
|
160
|
+
throw new Error('Definition file not found: ' + options.def)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return options
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
// A name still absolute after Path.relative is on another drive, which the
|
|
168
|
+
// pipeline's <base>/../def join cannot reach.
|
|
169
|
+
function defName(deffolder: string, def: string, path: typeof Path = Path): string {
|
|
170
|
+
const name = path.relative(deffolder, def)
|
|
171
|
+
|
|
172
|
+
if (path.isAbsolute(name)) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
'Definition file must be on the same drive as the project folder: ' + def)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return name
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
// The pipeline reads the definition at <base>/../def/<model.def> and writes
|
|
182
|
+
// under the output folder; both are <root>/model here, so a definition kept
|
|
183
|
+
// anywhere is named relative to <root>/def.
|
|
184
|
+
function resolveProject(options: CliOptions): CliProject {
|
|
185
|
+
const root = Path.resolve(options.folder)
|
|
186
|
+
const folder = Path.join(root, 'model')
|
|
187
|
+
const outprefix = null == options.prefix ? options.name + '-' : options.prefix
|
|
188
|
+
const def = Path.resolve(options.def)
|
|
189
|
+
const guidefolder = Path.join(folder, 'guide')
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
root,
|
|
193
|
+
folder,
|
|
194
|
+
outprefix,
|
|
195
|
+
def,
|
|
196
|
+
model: {
|
|
197
|
+
name: options.name,
|
|
198
|
+
def: defName(Path.join(folder, '..', 'def'), def),
|
|
199
|
+
},
|
|
200
|
+
guide: Path.join(guidefolder, outprefix + GUIDE_FILE),
|
|
201
|
+
legacyguide: Path.join(guidefolder, outprefix + LEGACY_GUIDE_FILE),
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
// A legacy `.aontu` guide is accepted here because the guide stage migrates
|
|
207
|
+
// it to `.aon` before reading it.
|
|
208
|
+
function checkProject(project: CliProject): void {
|
|
209
|
+
if (Fs.existsSync(project.guide) || Fs.existsSync(project.legacyguide)) {
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
throw new Error(
|
|
214
|
+
'Guide entry file not found: ' + project.guide + '\n' +
|
|
215
|
+
'Create it with these two lines:\n' +
|
|
216
|
+
' @"@voxgig/apidef/model/' + GUIDE_FILE + '"\n' +
|
|
217
|
+
' @"./' + project.outprefix + BASE_GUIDE_FILE + '"')
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
// The closure makeBuild returns memoises the ApiDef instance and its logger,
|
|
222
|
+
// so a watch that reuses it rebuilds the model without rebuilding those.
|
|
223
|
+
async function makeRunBuild(
|
|
224
|
+
project: CliProject, options: CliOptions): Promise<() => Promise<any>> {
|
|
225
|
+
const build = await ApiDef.makeBuild({
|
|
226
|
+
folder: project.folder,
|
|
227
|
+
outprefix: project.outprefix,
|
|
228
|
+
debug: options.debug,
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
return () => build(project.model, { spec: { base: project.folder } }, {})
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
function report(result: any, project: CliProject, io: CliIO): void {
|
|
236
|
+
if (result.ok) {
|
|
237
|
+
const entities = Object.keys(result.apimodel?.main?.kit?.entity || {})
|
|
238
|
+
io.log('voxgig-apidef: ok' +
|
|
239
|
+
' model: ' + project.folder +
|
|
240
|
+
' entities: ' + (0 < entities.length ? entities.join(' ') : 'none'))
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
const last = result.steps?.[result.steps.length - 1] || 'start'
|
|
244
|
+
io.error('voxgig-apidef: failed after step ' + last + ': ' +
|
|
245
|
+
(result.err?.message || 'unknown error'))
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
function watchDef(project: CliProject, rebuild: () => Promise<void>, io: CliIO): Promise<never> {
|
|
251
|
+
return new Promise(() => {
|
|
252
|
+
let running = false
|
|
253
|
+
let pending = false
|
|
254
|
+
|
|
255
|
+
const run = async () => {
|
|
256
|
+
if (running) {
|
|
257
|
+
pending = true
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
running = true
|
|
261
|
+
try {
|
|
262
|
+
await rebuild()
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
running = false
|
|
266
|
+
if (pending) {
|
|
267
|
+
pending = false
|
|
268
|
+
await run()
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
Fs.watchFile(project.def, { interval: WATCH_INTERVAL_MS }, () => { run() })
|
|
274
|
+
io.log('voxgig-apidef: watching ' + project.def)
|
|
275
|
+
})
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
async function runCli(argv: string[], io: CliIO = CONSOLE_IO): Promise<number> {
|
|
280
|
+
try {
|
|
281
|
+
let options = resolveOptions(argv)
|
|
282
|
+
|
|
283
|
+
if (options.version) {
|
|
284
|
+
io.log(Pkg.version)
|
|
285
|
+
return 0
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (options.help) {
|
|
289
|
+
io.log(usage())
|
|
290
|
+
return 0
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
options = validateOptions(options)
|
|
294
|
+
|
|
295
|
+
const project = resolveProject(options)
|
|
296
|
+
checkProject(project)
|
|
297
|
+
|
|
298
|
+
const runBuild = await makeRunBuild(project, options)
|
|
299
|
+
|
|
300
|
+
const result = await runBuild()
|
|
301
|
+
report(result, project, io)
|
|
302
|
+
|
|
303
|
+
if (options.watch) {
|
|
304
|
+
await watchDef(project, async () => {
|
|
305
|
+
report(await runBuild(), project, io)
|
|
306
|
+
}, io)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return result.ok ? 0 : 1
|
|
310
|
+
}
|
|
311
|
+
catch (err: any) {
|
|
312
|
+
io.error('Voxgig API Definition Error:')
|
|
313
|
+
io.error(err?.message || err)
|
|
314
|
+
return 1
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
function main(): void {
|
|
320
|
+
runCli(process.argv.slice(2)).then((code) => {
|
|
321
|
+
process.exitCode = code
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
export {
|
|
327
|
+
main,
|
|
328
|
+
runCli,
|
|
329
|
+
resolveOptions,
|
|
330
|
+
validateOptions,
|
|
331
|
+
defName,
|
|
332
|
+
resolveProject,
|
|
333
|
+
checkProject,
|
|
334
|
+
usage,
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export type {
|
|
338
|
+
CliOptions,
|
|
339
|
+
CliProject,
|
|
340
|
+
CliIO,
|
|
341
|
+
}
|
package/src/guide/guide.ts
CHANGED
|
@@ -54,12 +54,16 @@ function migrateLegacyGuide(fs: any, folder: string, guideprefix: string): boole
|
|
|
54
54
|
return false
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
const migrated = legacysrc
|
|
57
|
+
let migrated = String(fs.readFileSync(legacyguide, 'utf8'))
|
|
59
58
|
.replace(/@"@voxgig\/apidef\/model\/guide\.aontu"/g,
|
|
60
59
|
'@"@voxgig/apidef/model/guide.aon"')
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
|
|
61
|
+
// The sibling include is written bare or with `./`; both name this file.
|
|
62
|
+
for (const dir of ['', './']) {
|
|
63
|
+
migrated = migrated
|
|
64
|
+
.split('@"' + dir + guideprefix + 'base-guide.aontu"')
|
|
65
|
+
.join('@"' + dir + guideprefix + 'base-guide.aon"')
|
|
66
|
+
}
|
|
63
67
|
|
|
64
68
|
fs.writeFileSync(guidepath, migrated)
|
|
65
69
|
try { fs.unlinkSync(legacyguide) } catch (_err: any) { }
|
package/src/model.ts
CHANGED
|
@@ -61,17 +61,18 @@ type ModelFieldOp = {
|
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
type ModelField = {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
n: string
|
|
65
|
+
h: string
|
|
66
|
+
t: any
|
|
67
|
+
r: boolean
|
|
67
68
|
op: Partial<Record<OpName, ModelFieldOp>>
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
sh?: string
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
ro?: boolean
|
|
73
|
+
wo?: boolean
|
|
74
|
+
de?: boolean
|
|
75
|
+
fo?: string
|
|
75
76
|
union?: {
|
|
76
77
|
count: number
|
|
77
78
|
branches: number
|
|
@@ -80,18 +81,14 @@ type ModelField = {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
|
|
83
|
-
//
|
|
84
|
-
// `example` captures a value the spec advertises (parameter `example`,
|
|
85
|
-
// the first entry of `examples`, or `schema.example`/`schema.default`).
|
|
86
|
-
// Test generators use this for required params in live test setup so the
|
|
87
|
-
// generated request actually satisfies the API contract.
|
|
84
|
+
// `ex` holds an advertised parameter example or schema default.
|
|
88
85
|
type ModelArg = {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
86
|
+
n: string
|
|
87
|
+
or?: string
|
|
88
|
+
t: any
|
|
89
|
+
k: ArgKind
|
|
90
|
+
r: boolean
|
|
91
|
+
ex?: any
|
|
95
92
|
}
|
|
96
93
|
|
|
97
94
|
|
|
@@ -133,29 +130,30 @@ type ModelPathSegment = {
|
|
|
133
130
|
|
|
134
131
|
|
|
135
132
|
type ModelPoint = {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
133
|
+
co?: { version: 2, id: string, source: string }
|
|
134
|
+
li?: boolean | Record<string, any>
|
|
135
|
+
o: string
|
|
136
|
+
k?: PointKind
|
|
137
|
+
gq?: ModelGraphql
|
|
138
|
+
m: MethodName
|
|
139
|
+
s: ModelPathSegment[]
|
|
140
|
+
r: Partial<{
|
|
143
141
|
param: Record<string, string>
|
|
144
142
|
query: Record<string, string>
|
|
145
143
|
header: Record<string, string>
|
|
146
144
|
cookie: Record<string, string>
|
|
147
145
|
}>
|
|
148
|
-
|
|
146
|
+
g: Partial<{
|
|
149
147
|
params: ModelArg[]
|
|
150
148
|
query: ModelArg[]
|
|
151
149
|
header: ModelArg[]
|
|
152
150
|
cookie: ModelArg[]
|
|
153
151
|
}>
|
|
154
|
-
|
|
152
|
+
t: {
|
|
155
153
|
req?: any
|
|
156
154
|
res?: any
|
|
157
155
|
}
|
|
158
|
-
|
|
156
|
+
q: {
|
|
159
157
|
exist: string[]
|
|
160
158
|
$action?: string
|
|
161
159
|
}
|
|
@@ -173,7 +171,7 @@ type ModelEntity = {
|
|
|
173
171
|
Name?: string
|
|
174
172
|
NAME?: string
|
|
175
173
|
op: ModelOpMap
|
|
176
|
-
fields: ModelField
|
|
174
|
+
fields: Record<string, ModelField>
|
|
177
175
|
id?: {
|
|
178
176
|
name: string
|
|
179
177
|
field: string
|
|
@@ -197,7 +195,7 @@ type ModelEntityFlow = {
|
|
|
197
195
|
// Per-step input cluster. Test-generators name the variables they emit by
|
|
198
196
|
// reading these slots, falling back to derived defaults. All fields are
|
|
199
197
|
// optional — `newFlowStep` in transform/flowstep.ts guarantees the input
|
|
200
|
-
// object itself exists, so consumers don't need to null-check `step.
|
|
198
|
+
// object itself exists, so consumers don't need to null-check `step.i`.
|
|
201
199
|
type ModelEntityFlowStepInput = {
|
|
202
200
|
ref?: string
|
|
203
201
|
entvar?: string
|
|
@@ -226,12 +224,13 @@ type ModelEntityFlowStepSpec = {
|
|
|
226
224
|
|
|
227
225
|
|
|
228
226
|
type ModelEntityFlowStep = {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
227
|
+
a?: boolean
|
|
228
|
+
o: OpName
|
|
229
|
+
i: ModelEntityFlowStepInput
|
|
230
|
+
m: Record<string, any>
|
|
231
|
+
d: Record<string, any>
|
|
232
|
+
s: ModelEntityFlowStepSpec[]
|
|
233
|
+
v: ModelEntityFlowStepValidator[]
|
|
235
234
|
}
|
|
236
235
|
|
|
237
236
|
|