@voxgig/apidef 8.3.0 → 8.5.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/dist/guide/guide.js +65 -0
- package/dist/guide/guide.js.map +1 -1
- package/dist/model.d.ts +3 -0
- package/dist/transform/field.js +563 -4
- package/dist/transform/field.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.js.map +1 -1
- package/model/apidef.aon +27 -0
- package/model/guide.aon +42 -0
- package/package.json +1 -1
- package/src/guide/guide.ts +74 -0
- package/src/model.ts +23 -0
- package/src/transform/field.ts +659 -4
- package/src/types.ts +19 -0
package/model/guide.aon
CHANGED
|
@@ -3,6 +3,48 @@ guide: entity: &: {
|
|
|
3
3
|
active?: boolean
|
|
4
4
|
name: key()
|
|
5
5
|
|
|
6
|
+
# COMPOSITE IDENTITY OVERRIDE.
|
|
7
|
+
#
|
|
8
|
+
# Some APIs address one record by several path parameters at once, with no
|
|
9
|
+
# single parameter that is the id: github needs {owner} and {repo} together
|
|
10
|
+
# to name a repository, and cloudsmith needs {owner}/{repo}/{identifier}
|
|
11
|
+
# for an entitlement. The model carries those as `id.parts`, joined by
|
|
12
|
+
# `id.sep` into the one id an SDK entity holds.
|
|
13
|
+
#
|
|
14
|
+
# apidef infers this from ADJACENT variable path segments, which is right
|
|
15
|
+
# far more often than not but cannot always be right: a trailing variable
|
|
16
|
+
# is sometimes a MODIFIER rather than part of the key. github's
|
|
17
|
+
# `/…/artifacts/{artifact_id}/{archive_format}` is the clear case —
|
|
18
|
+
# `archive_format` selects zip or tar, it does not identify the artifact —
|
|
19
|
+
# and no reading of the path alone distinguishes that from a genuine
|
|
20
|
+
# compound key.
|
|
21
|
+
#
|
|
22
|
+
# So state it here when the inference is wrong:
|
|
23
|
+
#
|
|
24
|
+
# id: parts: [ 'owner', 'repo' ] # this IS the compound key
|
|
25
|
+
# id: composite: false # it is not; the last param is the id
|
|
26
|
+
#
|
|
27
|
+
# An explicit `parts` wins over the inference. `composite: false` turns the
|
|
28
|
+
# inference off, which is what `archive_format` above wants.
|
|
29
|
+
#
|
|
30
|
+
# THE FLAG IS A BOOLEAN, NOT AN EMPTY `parts`. Writing `parts: []` reads as
|
|
31
|
+
# "no parts" but cannot work: aontu resolves an empty list to nothing, so
|
|
32
|
+
# the key arrives absent and is indistinguishable from never having been
|
|
33
|
+
# set — the inference then runs anyway. That was tried first.
|
|
34
|
+
#
|
|
35
|
+
# `sep` defaults to '/' and rarely wants changing: a slash cannot occur
|
|
36
|
+
# inside one path segment, so the join is unambiguous.
|
|
37
|
+
id?: {
|
|
38
|
+
parts?: [ &: string ]
|
|
39
|
+
sep?: string
|
|
40
|
+
composite?: boolean
|
|
41
|
+
|
|
42
|
+
# Corrects WHERE A PART LIVES in a response, per part, so a spec can fix
|
|
43
|
+
# one mapping without restating the others.
|
|
44
|
+
from?: &: string
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
6
48
|
path: &: {
|
|
7
49
|
active?: boolean
|
|
8
50
|
|
package/package.json
CHANGED
package/src/guide/guide.ts
CHANGED
|
@@ -96,6 +96,26 @@ function migrateLegacyGuide(fs: any, folder: string, guideprefix: string): boole
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
|
|
99
|
+
// The first unresolved merge-conflict marker in a source, or null.
|
|
100
|
+
//
|
|
101
|
+
// Anchored at line start and requiring exactly the conventional seven
|
|
102
|
+
// characters: a guide legitimately contains `>>>>>>> GENERATED` inside the
|
|
103
|
+
// jostraca provenance comments it writes about itself, and `====` shows up in
|
|
104
|
+
// prose. Only a real marker at column zero counts.
|
|
105
|
+
function findConflict(src: string): { line: number, text: string } | null {
|
|
106
|
+
const lines = String(src || '').split('\n')
|
|
107
|
+
|
|
108
|
+
for (let i = 0; i < lines.length; i++) {
|
|
109
|
+
const line = lines[i]
|
|
110
|
+
if (/^(<{7}|>{7})(?!<|>)/.test(line) || /^={7}(?!=)\s*$/.test(line)) {
|
|
111
|
+
return { line: i + 1, text: line.slice(0, 80) }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return null
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
99
119
|
async function buildGuide(ctx: ApiDefContext): Promise<any> {
|
|
100
120
|
const log = ctx.log
|
|
101
121
|
const errs: any[] = []
|
|
@@ -135,6 +155,60 @@ async function buildGuide(ctx: ApiDefContext): Promise<any> {
|
|
|
135
155
|
|
|
136
156
|
handleErrors(ctx, errs)
|
|
137
157
|
|
|
158
|
+
// A MERGE CONFLICT IN A GUIDE IS SAID OUT LOUD, HERE.
|
|
159
|
+
//
|
|
160
|
+
// The guide is 3-way merged: apidef regenerates the base guide from the
|
|
161
|
+
// spec and merges it over what the project already had. Change the spec
|
|
162
|
+
// enough — swap a 5-path definition for the API's whole 722-path one — and
|
|
163
|
+
// an edit the project made can no longer be reconciled, so the merge
|
|
164
|
+
// writes ordinary `<<<<<<<` / `=======` / `>>>>>>>` markers into the file.
|
|
165
|
+
//
|
|
166
|
+
// Nothing then read the file until aontu did, and aontu reports what it
|
|
167
|
+
// sees: `unexpected character(s): <<<<<<<`, thousands of lines into a
|
|
168
|
+
// generated file, with no hint that this is a merge conflict or which edit
|
|
169
|
+
// caused it. That cost a long detour — the failure was read as apidef
|
|
170
|
+
// hanging, and the real cause (one conflicted rename) sat two lines away
|
|
171
|
+
// from a marker nobody had looked for.
|
|
172
|
+
//
|
|
173
|
+
// The guide is a file apidef itself writes, so apidef is the right place to
|
|
174
|
+
// recognise its own merge output before handing it on.
|
|
175
|
+
// BOTH FILES, and the base guide is the one that usually has it: `guide.aon`
|
|
176
|
+
// is two @-includes a user rarely edits, while `base-guide.aon` is what
|
|
177
|
+
// apidef regenerates and merges. Checking only the top-level file found
|
|
178
|
+
// nothing and left aontu to report the marker.
|
|
179
|
+
const basepath = Path.join(folder, 'guide', guideprefix + 'base-guide.aon')
|
|
180
|
+
for (const checkpath of [guidepath, basepath]) {
|
|
181
|
+
let checksrc = ''
|
|
182
|
+
try {
|
|
183
|
+
checksrc = checkpath === guidepath ? src : String(ctx.fs.readFileSync(checkpath, 'utf8'))
|
|
184
|
+
}
|
|
185
|
+
catch (_err: any) {
|
|
186
|
+
continue
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const conflict = findConflict(checksrc)
|
|
190
|
+
if (null != conflict) {
|
|
191
|
+
errs.push(new Error(
|
|
192
|
+
`@voxgig/apidef: guide: unresolved merge conflict at ${
|
|
193
|
+
relativizePath(checkpath)}:${conflict.line}\n` +
|
|
194
|
+
` ${conflict.text}\n` +
|
|
195
|
+
`A guide is merged, not overwritten, so an edit the regenerated base\n` +
|
|
196
|
+
`guide contradicts is left for a human to settle. Resolve the marked\n` +
|
|
197
|
+
`block` +
|
|
198
|
+
// DELETING ONLY HELPS FOR THE BASE GUIDE. Regeneration rewrites that
|
|
199
|
+
// file, while the top-level entry guide is the user's own and is read
|
|
200
|
+
// back unchanged — so advising its deletion would send a reader in a
|
|
201
|
+
// circle, failing this same check on the next build.
|
|
202
|
+
(checkpath === basepath ?
|
|
203
|
+
`, or delete ${guideprefix}base-guide.aon to regenerate it from the\n` +
|
|
204
|
+
`specification and re-apply the edit afterwards.` :
|
|
205
|
+
` in ${relativizePath(checkpath)}.`)))
|
|
206
|
+
break
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
handleErrors(ctx, errs)
|
|
211
|
+
|
|
138
212
|
|
|
139
213
|
|
|
140
214
|
|
package/src/model.ts
CHANGED
|
@@ -232,6 +232,29 @@ type ModelEntity = {
|
|
|
232
232
|
id?: {
|
|
233
233
|
name: string
|
|
234
234
|
field: string
|
|
235
|
+
// COMPOSITE IDENTITY. Present only when the API addresses one record by
|
|
236
|
+
// MORE THAN ONE path parameter, so no single parameter is the id.
|
|
237
|
+
// github's repo is the case: GET /repos/{owner}/{repo} needs both, and
|
|
238
|
+
// neither alone names a repository.
|
|
239
|
+
//
|
|
240
|
+
// `parts` are those parameters in path order; `sep` joins them into the
|
|
241
|
+
// one `id` an SDK entity carries. Absent means the ordinary single-key
|
|
242
|
+
// entity, so downstream can branch on presence alone.
|
|
243
|
+
parts?: string[]
|
|
244
|
+
sep?: string
|
|
245
|
+
// WHERE EACH PART'S VALUE LIVES IN A RESPONSE, as a dotted path.
|
|
246
|
+
//
|
|
247
|
+
// The parts are PATH PARAMETER names and a response names its fields
|
|
248
|
+
// whatever it likes: github addresses a repo by `{owner}/{repo}` and
|
|
249
|
+
// returns the owner as an OBJECT, so the value is at `owner.login`, and
|
|
250
|
+
// the repository under `name`. Without this a consumer can address a
|
|
251
|
+
// record it was given the id of, but cannot put an id on a record the
|
|
252
|
+
// API returned.
|
|
253
|
+
//
|
|
254
|
+
// A part no rule resolves is left OUT, so an incomplete map says the id
|
|
255
|
+
// cannot be rebuilt for that entity — better than a confidently wrong id
|
|
256
|
+
// on a real record. guide.aon can state it instead.
|
|
257
|
+
from?: Record<string, string>
|
|
235
258
|
}
|
|
236
259
|
relations: ModelEntityRelations
|
|
237
260
|
}
|