@voxgig/sdkgen 4.30.3 → 4.31.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/bin/voxgig-sdkgen +1 -1
- package/dist/action/package.d.ts +2 -1
- package/dist/action/package.js +8 -1
- package/dist/action/package.js.map +1 -1
- package/dist/cmp/ReadmeTop.js +6 -5
- package/dist/cmp/ReadmeTop.js.map +1 -1
- package/dist/helpers/definitionPlan.d.ts +27 -0
- package/dist/helpers/definitionPlan.js +219 -0
- package/dist/helpers/definitionPlan.js.map +1 -0
- package/dist/sdkgen.d.ts +2 -1
- package/dist/sdkgen.js +5 -3
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/project/.sdk/src/cmp/c/PrepareAuth_c.ts +5 -3
- package/project/.sdk/src/cmp/clojure/PrepareAuth_clojure.ts +7 -5
- package/project/.sdk/src/cmp/cpp/PrepareAuth_cpp.ts +5 -3
- package/project/.sdk/src/cmp/csharp/PrepareAuth_csharp.ts +6 -4
- package/project/.sdk/src/cmp/elixir/PrepareAuth_elixir.ts +8 -6
- package/project/.sdk/src/cmp/go/PrepareAuth_go.ts +7 -9
- package/project/.sdk/src/cmp/java/PrepareAuth_java.ts +5 -3
- package/project/.sdk/src/cmp/js/PrepareAuth_js.ts +6 -4
- package/project/.sdk/src/cmp/js/ReadmeInstall_js.ts +22 -5
- package/project/.sdk/src/cmp/js/TestDefinition_js.ts +40 -0
- package/project/.sdk/src/cmp/js/Test_js.ts +2 -0
- package/project/.sdk/src/cmp/kotlin/PrepareAuth_kotlin.ts +5 -3
- package/project/.sdk/src/cmp/lua/PrepareAuth_lua.ts +7 -3
- package/project/.sdk/src/cmp/ocaml/PrepareAuth_ocaml.ts +7 -5
- package/project/.sdk/src/cmp/perl/PrepareAuth_perl.ts +6 -3
- package/project/.sdk/src/cmp/php/PrepareAuth_php.ts +5 -3
- package/project/.sdk/src/cmp/php/ReadmeInstall_php.ts +11 -5
- package/project/.sdk/src/cmp/py/PrepareAuth_py.ts +5 -3
- package/project/.sdk/src/cmp/rb/PrepareAuth_rb.ts +5 -3
- package/project/.sdk/src/cmp/rb/ReadmeInstall_rb.ts +14 -4
- package/project/.sdk/src/cmp/rust/PrepareAuth_rust.ts +5 -3
- package/project/.sdk/src/cmp/scala/PrepareAuth_scala.ts +5 -3
- package/project/.sdk/src/cmp/swift/PrepareAuth_swift.ts +5 -3
- package/project/.sdk/src/cmp/ts/PrepareAuth_ts.ts +6 -4
- package/project/.sdk/src/cmp/ts/ReadmeInstall_ts.ts +10 -4
- package/project/.sdk/src/cmp/ts/TestDefinition_ts.ts +40 -0
- package/project/.sdk/src/cmp/ts/Test_ts.ts +2 -0
- package/project/.sdk/src/cmp/zig/PrepareAuth_zig.ts +5 -3
- package/project/.sdk/tm/c/utility/prepare_query.c +34 -2
- package/project/.sdk/tm/clojure/src/sdk/core.clj +20 -2
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +31 -2
- package/project/.sdk/tm/csharp/utility/PrepareQuery.cs +32 -4
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +35 -1
- package/project/.sdk/tm/go/utility/prepare_query.go +31 -1
- package/project/.sdk/tm/java/utility/PrepareQuery.java +29 -5
- package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +16 -2
- package/project/.sdk/tm/js/test/definition-runner.js +161 -0
- package/project/.sdk/tm/js/test/pipeline.test.js +43 -3
- package/project/.sdk/tm/js/test/sdk-test-control.json +4 -1
- package/project/.sdk/tm/kotlin/utility/Prepare.kt +29 -5
- package/project/.sdk/tm/lua/utility/prepare_query.lua +31 -2
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +22 -1
- package/project/.sdk/tm/perl/utility/prepare_query.pm +25 -2
- package/project/.sdk/tm/php/utility/PrepareQuery.php +28 -1
- package/project/.sdk/tm/py/pkg/utility/prepare_query.py +23 -2
- package/project/.sdk/tm/rb/utility/prepare_query.rb +24 -2
- package/project/.sdk/tm/rust/utility/prepare_query.rs +34 -2
- package/project/.sdk/tm/scala/utility/Prepare.scala +31 -4
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +23 -5
- package/project/.sdk/tm/ts/src/utility/PrepareQueryUtility.ts +16 -2
- package/project/.sdk/tm/ts/test/definition-runner.ts +179 -0
- package/project/.sdk/tm/ts/test/pipeline.test.ts +43 -3
- package/project/.sdk/tm/ts/test/sdk-test-control.json +4 -1
- package/project/.sdk/tm/ts/test/utility.ts +3 -2
- package/project/.sdk/tm/zig/core/utility.zig +29 -1
- package/project/sdkgen-package.json +1 -1
- package/src/action/package.ts +10 -1
- package/src/cmp/ReadmeTop.ts +7 -6
- package/src/helpers/definitionPlan.ts +265 -0
- package/src/sdkgen.ts +2 -0
|
@@ -64,22 +64,40 @@ func prepareParamsUtil(_ ctx: Context) -> VMap {
|
|
|
64
64
|
func prepareQueryUtil(_ ctx: Context) -> VMap {
|
|
65
65
|
let reqmatch = ctx.reqmatch
|
|
66
66
|
|
|
67
|
-
var paramnames:
|
|
68
|
-
if let pl = gp(ctx.point, "params").asList { paramnames
|
|
67
|
+
var paramnames: [Value] = []
|
|
68
|
+
if let pl = gp(ctx.point, "params").asList { paramnames.append(contentsOf: pl.items) }
|
|
69
|
+
// A path parameter travels in the path. The generated config lists them as
|
|
70
|
+
// args.params, which prepareParams reads; params is the older list of names.
|
|
71
|
+
if let apl = gpath(ctx.point, "args", "params").asList {
|
|
72
|
+
for pd in apl.items {
|
|
73
|
+
paramnames.append(gp(pd, "name"))
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// A query parameter travels under the name the definition gives it, its
|
|
78
|
+
// orig, which the model may have renamed for the caller.
|
|
79
|
+
var wire: [String: String] = [:]
|
|
80
|
+
if let aql = gpath(ctx.point, "args", "query").asList {
|
|
81
|
+
for qd in aql.items {
|
|
82
|
+
if let name = gp(qd, "name").asString, let orig = gp(qd, "orig").asString, !orig.isEmpty {
|
|
83
|
+
wire[name] = orig
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
69
87
|
|
|
70
88
|
let query = VMap()
|
|
71
89
|
for item in items(.map(reqmatch)) {
|
|
72
90
|
let key = item[0].asString ?? ""
|
|
73
91
|
let val = item[1]
|
|
74
92
|
if !isNil(val) && "$action" != key && !containsStr(paramnames, key) {
|
|
75
|
-
query.entries[key] = val
|
|
93
|
+
query.entries[wire[key] ?? key] = val
|
|
76
94
|
}
|
|
77
95
|
}
|
|
78
96
|
return query
|
|
79
97
|
}
|
|
80
98
|
|
|
81
|
-
private func containsStr(_ list:
|
|
82
|
-
return list.
|
|
99
|
+
private func containsStr(_ list: [Value], _ s: String) -> Bool {
|
|
100
|
+
return list.contains { $0.asString == s }
|
|
83
101
|
}
|
|
84
102
|
|
|
85
103
|
func prepareBodyUtil(_ ctx: Context) -> Value {
|
|
@@ -14,10 +14,24 @@ function prepareQuery(ctx: Context) {
|
|
|
14
14
|
params = params || []
|
|
15
15
|
reqmatch = reqmatch || {}
|
|
16
16
|
|
|
17
|
+
// A path parameter travels in the path. The generated config lists them as
|
|
18
|
+
// args.params, which prepareParams reads; params is the older list of names.
|
|
19
|
+
const inpath: string[] = params.concat(
|
|
20
|
+
(point.args?.params || []).map((p: any) => p?.name))
|
|
21
|
+
|
|
22
|
+
// A query parameter travels under the name the definition gives it, its
|
|
23
|
+
// orig, which the model may have renamed for the caller.
|
|
24
|
+
const wire: Record<string, string> = Object.create(null)
|
|
25
|
+
for (const q of (point.args?.query || [])) {
|
|
26
|
+
if ('string' === typeof q?.name && 'string' === typeof q?.orig && '' !== q.orig) {
|
|
27
|
+
wire[q.name] = q.orig
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
17
31
|
const out: any = {}
|
|
18
32
|
for (let [key, val] of items(reqmatch)) {
|
|
19
|
-
if (null != val && '$action' !== key && !
|
|
20
|
-
out[key] = val
|
|
33
|
+
if (null != val && '$action' !== key && !inpath.includes(key)) {
|
|
34
|
+
out[wire[key] ?? key] = val
|
|
21
35
|
}
|
|
22
36
|
}
|
|
23
37
|
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// Checks one operation against the API definition rather than the model the
|
|
2
|
+
// SDK was generated from. A mock transport answers with the definition's own
|
|
3
|
+
// response example, so nothing here depends on how the model reads it.
|
|
4
|
+
|
|
5
|
+
import assert from 'node:assert/strict'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
type Credential = { in: string, name: string, scheme?: string }
|
|
9
|
+
|
|
10
|
+
type DefinitionPoint = {
|
|
11
|
+
entity: string
|
|
12
|
+
accessor: string
|
|
13
|
+
op: string
|
|
14
|
+
method: string
|
|
15
|
+
path: string
|
|
16
|
+
action?: string
|
|
17
|
+
args: { name: string, wire: string, value: any }[]
|
|
18
|
+
select: Record<string, any>
|
|
19
|
+
query: string[]
|
|
20
|
+
auth: Credential[][] | null
|
|
21
|
+
status: number
|
|
22
|
+
sample: any
|
|
23
|
+
idField: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
const KEY = 'definition-test-key'
|
|
28
|
+
const BASE = 'http://definition.test'
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async function runDefinitionPoint(SDK: any, point: DefinitionPoint): Promise<void> {
|
|
32
|
+
const sent: { url: URL, init: any }[] = []
|
|
33
|
+
const body = null == point.sample ? null : JSON.stringify(point.sample)
|
|
34
|
+
|
|
35
|
+
const client = new SDK({
|
|
36
|
+
apikey: KEY,
|
|
37
|
+
base: BASE,
|
|
38
|
+
feature: { test: { active: false } },
|
|
39
|
+
system: {
|
|
40
|
+
fetch: async (url: any, init: any) => {
|
|
41
|
+
sent.push({ url: new URL(String(url)), init: init || {} })
|
|
42
|
+
return new Response(204 === point.status ? null : body, {
|
|
43
|
+
status: point.status,
|
|
44
|
+
headers: { 'content-type': 'application/json' },
|
|
45
|
+
})
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const input: any = { ...point.select }
|
|
51
|
+
for (const arg of point.args) input[arg.name] = arg.value
|
|
52
|
+
if (null != point.action) input.$action = point.action
|
|
53
|
+
|
|
54
|
+
let result: any
|
|
55
|
+
let error: any
|
|
56
|
+
try {
|
|
57
|
+
result = await client[point.accessor]()[point.op](input)
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
error = err
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
assert.equal(sent.length, 1, 'expected one request, sent ' + sent.length +
|
|
64
|
+
(error ? ': ' + (error as any).message : ''))
|
|
65
|
+
const { url, init } = sent[0]
|
|
66
|
+
|
|
67
|
+
assert.equal(String(init.method || 'GET').toUpperCase(), point.method, 'method')
|
|
68
|
+
|
|
69
|
+
const route = point.path.replace(/\{([^}]+)\}/g, (_: string, name: string) => {
|
|
70
|
+
const arg = point.args.find((a) => a.wire === name)
|
|
71
|
+
return null == arg ? '{' + name + '}' : encodeURIComponent(String(arg.value))
|
|
72
|
+
})
|
|
73
|
+
assert.equal(url.pathname, route, 'route')
|
|
74
|
+
|
|
75
|
+
// Only what the definition declares, so never a path parameter again.
|
|
76
|
+
const credentialQuery = (point.auth || []).flat()
|
|
77
|
+
.filter((c) => 'query' === c.in).map((c) => c.name)
|
|
78
|
+
for (const key of url.searchParams.keys()) {
|
|
79
|
+
assert(point.query.includes(key) || credentialQuery.includes(key),
|
|
80
|
+
'query parameter not in the definition: ' + key)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// From the apikey alone, placed as the definition's security scheme says.
|
|
84
|
+
if (null != point.auth && 0 < point.auth.length) {
|
|
85
|
+
const headers = new Headers(init.headers)
|
|
86
|
+
assert(point.auth.some((set) => set.every((c) => placed(c, headers, url))),
|
|
87
|
+
'credential not sent as the definition declares it: ' + JSON.stringify(point.auth))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (null != error) {
|
|
91
|
+
throw error
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (null == point.sample) {
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if ('list' === point.op) {
|
|
99
|
+
const records = recordsOf(point.sample)
|
|
100
|
+
if (null != records) {
|
|
101
|
+
assert(Array.isArray(result), 'list did not return a list')
|
|
102
|
+
assert.equal(result.length, records.length,
|
|
103
|
+
'list read ' + result.length + ' records where the definition example holds ' +
|
|
104
|
+
records.length)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else if ('load' === point.op || 'create' === point.op || 'update' === point.op) {
|
|
108
|
+
const record = recordOf(point.sample, point.idField)
|
|
109
|
+
if (null != record) {
|
|
110
|
+
assert.equal(result?.data?.()?.[point.idField], record[point.idField],
|
|
111
|
+
'the entity does not hold the record the definition example returns')
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
function placed(cred: Credential, headers: Headers, url: URL): boolean {
|
|
118
|
+
if ('query' === cred.in) {
|
|
119
|
+
return KEY === url.searchParams.get(cred.name)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if ('cookie' === cred.in) {
|
|
123
|
+
return String(headers.get('cookie') || '').split(';')
|
|
124
|
+
.some((part) => cred.name + '=' + KEY === part.trim())
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const value = headers.get(cred.name)
|
|
128
|
+
if (null == value) {
|
|
129
|
+
return false
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const [kind, token] = value.split(' ')
|
|
133
|
+
|
|
134
|
+
// The client carries no secret, so the password must be empty.
|
|
135
|
+
if ('basic' === cred.scheme) {
|
|
136
|
+
return 'basic' === String(kind).toLowerCase() &&
|
|
137
|
+
Buffer.from(String(token), 'base64').toString('utf8') === KEY + ':'
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if ('bearer' === cred.scheme) {
|
|
141
|
+
return 'bearer' === String(kind).toLowerCase() && KEY === token
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return value.includes(KEY)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
// The records a list response holds: the body itself, or its one property
|
|
149
|
+
// that is a non-empty list of objects. Anything else proves nothing.
|
|
150
|
+
function recordsOf(sample: any): any[] | null {
|
|
151
|
+
if (Array.isArray(sample)) {
|
|
152
|
+
return sample
|
|
153
|
+
}
|
|
154
|
+
if (null == sample || 'object' !== typeof sample) {
|
|
155
|
+
return null
|
|
156
|
+
}
|
|
157
|
+
const lists = Object.values(sample).filter((v: any) => Array.isArray(v) && 0 < v.length &&
|
|
158
|
+
v.every((x: any) => null != x && 'object' === typeof x && !Array.isArray(x)))
|
|
159
|
+
return 1 === lists.length ? lists[0] as any[] : null
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
// The record a single-item response returns: the body, or its one property
|
|
164
|
+
// holding an object with the identity field.
|
|
165
|
+
function recordOf(sample: any, idField: string): any {
|
|
166
|
+
if (null == sample || 'object' !== typeof sample || Array.isArray(sample)) {
|
|
167
|
+
return null
|
|
168
|
+
}
|
|
169
|
+
if (null != sample[idField]) {
|
|
170
|
+
return sample
|
|
171
|
+
}
|
|
172
|
+
const inner = Object.values(sample).filter((v: any) => null != v && 'object' === typeof v &&
|
|
173
|
+
!Array.isArray(v) && null != v[idField])
|
|
174
|
+
return 1 === inner.length ? inner[0] : null
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
export { runDefinitionPoint, recordsOf, recordOf }
|
|
179
|
+
export type { DefinitionPoint }
|
|
@@ -333,9 +333,9 @@ describe('pipeline:prepareAuth', () => {
|
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
// `basic: false` is explicit: an HTTP Basic API's generated config carries
|
|
336
|
-
// `auth.basic: true`, and a client that merges it in takes
|
|
337
|
-
//
|
|
338
|
-
// the
|
|
336
|
+
// `auth.basic: true`, and a client that merges it in takes the Basic
|
|
337
|
+
// branch, which base64-joins the key with a password rather than placing
|
|
338
|
+
// the key as it is.
|
|
339
339
|
function auth(prefix: string): any {
|
|
340
340
|
return { prefix, basic: false }
|
|
341
341
|
}
|
|
@@ -423,6 +423,46 @@ describe('pipeline:prepareAuth', () => {
|
|
|
423
423
|
test('a missing apikey option drops the credential', () => {
|
|
424
424
|
strictEqual(placed({ auth: auth('Bearer') }, 'stale'), undefined)
|
|
425
425
|
})
|
|
426
|
+
|
|
427
|
+
// HTTP Basic joins the key with a password, which may be empty (RFC 7617):
|
|
428
|
+
// Lob documents its key as the user with a blank password. An SDK for an
|
|
429
|
+
// API that is not HTTP Basic has no such branch, and the first placement
|
|
430
|
+
// below tells the two apart.
|
|
431
|
+
test('HTTP Basic sends the key with a blank password', () => {
|
|
432
|
+
if (null == CRED || 'headers' !== CRED.where || '' !== CRED.pair) return
|
|
433
|
+
const basic = { prefix: 'Basic', basic: true }
|
|
434
|
+
const b64 = (s: string) => Buffer.from(s).toString('base64')
|
|
435
|
+
if (placed({ apikey: 'K', secret: 'S', auth: basic }) !== 'Basic ' + b64('K:S')) return
|
|
436
|
+
strictEqual(placed({ apikey: 'K', auth: basic }), 'Basic ' + b64('K:'))
|
|
437
|
+
strictEqual(placed({ apikey: 'K', secret: '', auth: basic }), 'Basic ' + b64('K:'))
|
|
438
|
+
strictEqual(placed({ apikey: '', secret: 'S', auth: basic }, 'stale'), undefined)
|
|
439
|
+
})
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
describe('pipeline:prepareQuery', () => {
|
|
444
|
+
|
|
445
|
+
// The generated config lists path parameters as args.params. A match field
|
|
446
|
+
// that fills the path must not be sent again as a query parameter, which a
|
|
447
|
+
// strict server rejects.
|
|
448
|
+
test('a path parameter stays out of the query', () => {
|
|
449
|
+
const point = { args: { params: [{ name: 'id', kind: 'param' }] } }
|
|
450
|
+
const ctx = base({ point, reqmatch: { id: 'i1', q: 'x' } })
|
|
451
|
+
deepStrictEqual((stdutil as any).prepareQuery(ctx), { q: 'x' })
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
test('an action is never a query parameter', () => {
|
|
455
|
+
const ctx = base({ point: { args: { params: [] } }, reqmatch: { $action: 'go', q: 'x' } })
|
|
456
|
+
deepStrictEqual((stdutil as any).prepareQuery(ctx), { q: 'x' })
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
// A query argument's orig is the name the API definition gives it, which
|
|
460
|
+
// the model may have renamed for the caller.
|
|
461
|
+
test('a query argument goes out under its orig', () => {
|
|
462
|
+
const point = { args: { query: [{ name: 'item_id', orig: 'itemIds', kind: 'query' }] } }
|
|
463
|
+
const ctx = base({ point, reqmatch: { item_id: ['i1'], q: 'x' } })
|
|
464
|
+
deepStrictEqual((stdutil as any).prepareQuery(ctx), { itemIds: ['i1'], q: 'x' })
|
|
465
|
+
})
|
|
426
466
|
})
|
|
427
467
|
|
|
428
468
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"_doc": "Per-SDK test control. Lists tests/operations to skip in unit and
|
|
3
|
+
"_doc": "Per-SDK test control. Lists tests/operations to skip in unit, live and definition modes; tunes per-test pacing. Edit by hand. Loaded by ts/test/utility.ts.",
|
|
4
4
|
"test": {
|
|
5
5
|
"skip": {
|
|
6
6
|
"live": {
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
"unit": {
|
|
11
11
|
"direct": [],
|
|
12
12
|
"entityOp": []
|
|
13
|
+
},
|
|
14
|
+
"definition": {
|
|
15
|
+
"entityOp": []
|
|
13
16
|
}
|
|
14
17
|
},
|
|
15
18
|
"live": {
|
|
@@ -77,6 +77,7 @@ type TestControl = {
|
|
|
77
77
|
skip?: {
|
|
78
78
|
live?: { direct?: any[], entityOp?: any[] }
|
|
79
79
|
unit?: { direct?: any[], entityOp?: any[] }
|
|
80
|
+
definition?: { direct?: any[], entityOp?: any[] }
|
|
80
81
|
}
|
|
81
82
|
live?: { delayMs?: number }
|
|
82
83
|
client?: { options?: Record<string, any> }
|
|
@@ -105,11 +106,11 @@ function loadTestControl(): TestControl {
|
|
|
105
106
|
|
|
106
107
|
// Returns the skip decision for a given test name from sdk-test-control.json.
|
|
107
108
|
// `kind` is 'direct' (matches by `test` field) or 'entityOp' (matches by
|
|
108
|
-
// `entity` + `op`). `mode` is 'live' or '
|
|
109
|
+
// `entity` + `op`). `mode` is 'live', 'unit' or 'definition'.
|
|
109
110
|
function isControlSkipped(
|
|
110
111
|
kind: 'direct' | 'entityOp',
|
|
111
112
|
name: string,
|
|
112
|
-
mode: 'live' | 'unit'
|
|
113
|
+
mode: 'live' | 'unit' | 'definition'
|
|
113
114
|
): { skip: boolean, reason?: string } {
|
|
114
115
|
const ctrl = loadTestControl()
|
|
115
116
|
const list = ctrl?.test?.skip?.[mode]?.[kind] ?? []
|
|
@@ -1104,6 +1104,12 @@ pub fn prepare_query_util(ctx: *Context) Value {
|
|
|
1104
1104
|
.array => h.getp(point, "params"),
|
|
1105
1105
|
else => h.olist(),
|
|
1106
1106
|
};
|
|
1107
|
+
// A path parameter travels in the path. The generated config lists them
|
|
1108
|
+
// as args.params, which prepare_params reads; params is the older list.
|
|
1109
|
+
const aparams: Value = h.getpath(&.{ "args", "params" }, point);
|
|
1110
|
+
// A query parameter travels under the name the definition gives it, its
|
|
1111
|
+
// orig, which the model may have renamed for the caller.
|
|
1112
|
+
const aquery: Value = h.getpath(&.{ "args", "query" }, point);
|
|
1107
1113
|
|
|
1108
1114
|
const out = h.omap();
|
|
1109
1115
|
if (reqmatch == .object) {
|
|
@@ -1120,7 +1126,29 @@ pub fn prepare_query_util(ctx: *Context) Value {
|
|
|
1120
1126
|
}
|
|
1121
1127
|
}
|
|
1122
1128
|
}
|
|
1123
|
-
if (!
|
|
1129
|
+
if (!contained and aparams == .array) {
|
|
1130
|
+
for (aparams.array.data.items) |pd| {
|
|
1131
|
+
const name = h.getp(pd, "name");
|
|
1132
|
+
if (name == .string and std.mem.eql(u8, name.string, key)) {
|
|
1133
|
+
contained = true;
|
|
1134
|
+
break;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
var wire: []const u8 = key;
|
|
1139
|
+
if (aquery == .array) {
|
|
1140
|
+
for (aquery.array.data.items) |qd| {
|
|
1141
|
+
const name = h.getp(qd, "name");
|
|
1142
|
+
const orig = h.getp(qd, "orig");
|
|
1143
|
+
if (name == .string and orig == .string and orig.string.len != 0 and
|
|
1144
|
+
std.mem.eql(u8, name.string, key))
|
|
1145
|
+
{
|
|
1146
|
+
wire = orig.string;
|
|
1147
|
+
break;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
if (!h.is_noval(val) and !std.mem.eql(u8, key, "$action") and !contained) h.setp(out, wire, val);
|
|
1124
1152
|
}
|
|
1125
1153
|
}
|
|
1126
1154
|
return out;
|
package/src/action/package.ts
CHANGED
|
@@ -737,7 +737,7 @@ async function npmFetch(pkgname: string, actx: ActionContext) {
|
|
|
737
737
|
try {
|
|
738
738
|
const out = await run(
|
|
739
739
|
'win32' === process.platform ? 'npm.cmd' : 'npm',
|
|
740
|
-
|
|
740
|
+
npmFetchArgs(pkgname),
|
|
741
741
|
{ cwd, maxBuffer: 64 * 1024 * 1024 })
|
|
742
742
|
|
|
743
743
|
actx.log.debug({
|
|
@@ -755,6 +755,14 @@ async function npmFetch(pkgname: string, actx: ActionContext) {
|
|
|
755
755
|
}
|
|
756
756
|
|
|
757
757
|
|
|
758
|
+
// The saved range is a tilde, as the scaffold writes it: a caret would let
|
|
759
|
+
// the next minor float in, and a scaffold contract can change in a minor.
|
|
760
|
+
// An .npmrc `save-exact` outranks the prefix, so it is switched off here.
|
|
761
|
+
function npmFetchArgs(pkgname: string): string[] {
|
|
762
|
+
return ['install', '--save-dev', '--save-exact=false', '--save-prefix=~', pkgname + '@latest']
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
|
|
758
766
|
function validateFetched(
|
|
759
767
|
pkgname: string, installed: Installed[], actx: ActionContext,
|
|
760
768
|
) {
|
|
@@ -938,6 +946,7 @@ export {
|
|
|
938
946
|
package_add,
|
|
939
947
|
package_update,
|
|
940
948
|
installedFrom,
|
|
949
|
+
npmFetchArgs,
|
|
941
950
|
resolvePackage,
|
|
942
951
|
selectItems,
|
|
943
952
|
parseAliases,
|
package/src/cmp/ReadmeTop.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
|
|
9
9
|
import { requirePath } from '../utility'
|
|
10
10
|
import { featureDocs } from './FeatureDocs'
|
|
11
|
+
import type { FeatureDoc } from './FeatureDocs'
|
|
11
12
|
|
|
12
13
|
import {
|
|
13
14
|
entityPrimaryOp, entityIdField, opRequestShape, entityPath, entityActions,
|
|
@@ -23,7 +24,6 @@ import {
|
|
|
23
24
|
vendorCommand,
|
|
24
25
|
apiName,
|
|
25
26
|
nonAffiliation,
|
|
26
|
-
repoInfo,
|
|
27
27
|
docsSiteUrl,
|
|
28
28
|
SECURITY_EMAIL,
|
|
29
29
|
} from '../helpers/packageMeta'
|
|
@@ -192,7 +192,7 @@ Learn more about Voxgig SDKs at [voxgig.com/sdk](${VOXGIG_SDK}).
|
|
|
192
192
|
// Named here, in one line, with the detail left to each target's README.
|
|
193
193
|
const features = featureDocs(model)
|
|
194
194
|
if (0 < features.length) {
|
|
195
|
-
Content(`> **Features:** ${features.map((f:
|
|
195
|
+
Content(`> **Features:** ${features.map((f: FeatureDoc) => '`' + f.name + '`').join(', ')} — opt-in,
|
|
196
196
|
> inactive until switched on, and configured per client. See the Features
|
|
197
197
|
> section of any SDK README below for what each one does.
|
|
198
198
|
|
|
@@ -308,10 +308,9 @@ network, and no credentials:
|
|
|
308
308
|
// 3. Packages — real published package name + install command per
|
|
309
309
|
// ecosystem. A package that is NOT yet live on its registry (the fleet
|
|
310
310
|
// default: 'pending') must NOT advertise a `npm install ...` that 404s —
|
|
311
|
-
// its Install cell links to the
|
|
312
|
-
// family resolves from the
|
|
311
|
+
// its Install cell links to the target README's install section instead.
|
|
312
|
+
// The go family resolves from the default branch (`go get <mod>@latest`).
|
|
313
313
|
if (pkgTargets.length > 0) {
|
|
314
|
-
const { tagsUrl } = repoInfo(model)
|
|
315
314
|
Content(`## Packages
|
|
316
315
|
|
|
317
316
|
| Language | Package | Install |
|
|
@@ -327,7 +326,9 @@ network, and no credentials:
|
|
|
327
326
|
} else if ('tag' === state) {
|
|
328
327
|
cell = '`' + vendorCommand(model, tgt.name) + '`'
|
|
329
328
|
} else {
|
|
330
|
-
|
|
329
|
+
// The target README always gives a route that works without a
|
|
330
|
+
// tag, which a repository may not have.
|
|
331
|
+
cell = `publish pending — [install from source](${tgt.name}/README.md#install)`
|
|
331
332
|
}
|
|
332
333
|
Content(`| ${tgt.title} | \`${packageName(model, tgt.name)}\` | ${cell} |
|
|
333
334
|
`)
|