@voxgig/sdkgen 1.3.1 → 1.3.4

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.
Files changed (98) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/action/action.d.ts +2 -1
  3. package/dist/action/action.js +13 -3
  4. package/dist/action/action.js.map +1 -1
  5. package/dist/action/feature.js +3 -5
  6. package/dist/action/feature.js.map +1 -1
  7. package/dist/action/target.js +9 -7
  8. package/dist/action/target.js.map +1 -1
  9. package/dist/cmp/Deploy.js +3 -3
  10. package/dist/cmp/Deploy.js.map +1 -1
  11. package/dist/cmp/ReadmeTop.js +24 -2
  12. package/dist/cmp/ReadmeTop.js.map +1 -1
  13. package/dist/helpers/opShape.js +38 -6
  14. package/dist/helpers/opShape.js.map +1 -1
  15. package/dist/sdkgen.d.ts +2 -2
  16. package/dist/sdkgen.js +5 -4
  17. package/dist/sdkgen.js.map +1 -1
  18. package/dist/utility.d.ts +2 -1
  19. package/dist/utility.js +20 -1
  20. package/dist/utility.js.map +1 -1
  21. package/model/sdkgen.aontu +1 -1
  22. package/package.json +1 -1
  23. package/project/.sdk/model/feature/feature-index.aontu +5 -0
  24. package/project/.sdk/src/cmp/go/Config_go.ts +3 -2
  25. package/project/.sdk/src/cmp/go/ReadmeEntity_go.ts +35 -9
  26. package/project/.sdk/src/cmp/go/ReadmeExamplesTest_go.ts +18 -14
  27. package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +6 -2
  28. package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +6 -2
  29. package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +51 -30
  30. package/project/.sdk/src/cmp/go/ReadmeRef_go.ts +66 -13
  31. package/project/.sdk/src/cmp/go/ReadmeTopHowto_go.ts +4 -0
  32. package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +51 -16
  33. package/project/.sdk/src/cmp/go/utility_go.ts +70 -0
  34. package/project/.sdk/src/cmp/js/Config_js.ts +3 -6
  35. package/project/.sdk/src/cmp/js/ReadmeEntity_js.ts +23 -6
  36. package/project/.sdk/src/cmp/js/ReadmeQuick_js.ts +31 -7
  37. package/project/.sdk/src/cmp/js/ReadmeRef_js.ts +33 -10
  38. package/project/.sdk/src/cmp/js/ReadmeTopQuick_js.ts +24 -14
  39. package/project/.sdk/src/cmp/js/ReadmeTopTest_js.ts +15 -3
  40. package/project/.sdk/src/cmp/lua/Config_lua.ts +3 -2
  41. package/project/.sdk/src/cmp/lua/ReadmeEntity_lua.ts +41 -6
  42. package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +94 -20
  43. package/project/.sdk/src/cmp/lua/ReadmeRef_lua.ts +51 -8
  44. package/project/.sdk/src/cmp/lua/ReadmeTopQuick_lua.ts +31 -2
  45. package/project/.sdk/src/cmp/lua/ReadmeTopTest_lua.ts +13 -2
  46. package/project/.sdk/src/cmp/php/Config_php.ts +3 -2
  47. package/project/.sdk/src/cmp/php/ReadmeEntity_php.ts +34 -6
  48. package/project/.sdk/src/cmp/php/ReadmeQuick_php.ts +95 -19
  49. package/project/.sdk/src/cmp/php/ReadmeRef_php.ts +44 -8
  50. package/project/.sdk/src/cmp/php/ReadmeTopQuick_php.ts +26 -2
  51. package/project/.sdk/src/cmp/php/ReadmeTopTest_php.ts +13 -2
  52. package/project/.sdk/src/cmp/py/Config_py.ts +3 -2
  53. package/project/.sdk/src/cmp/py/ReadmeEntity_py.ts +26 -8
  54. package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +95 -21
  55. package/project/.sdk/src/cmp/py/ReadmeRef_py.ts +37 -11
  56. package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +28 -2
  57. package/project/.sdk/src/cmp/py/ReadmeTopTest_py.ts +16 -3
  58. package/project/.sdk/src/cmp/rb/Config_rb.ts +3 -2
  59. package/project/.sdk/src/cmp/rb/ReadmeEntity_rb.ts +26 -8
  60. package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +96 -21
  61. package/project/.sdk/src/cmp/rb/ReadmeRef_rb.ts +36 -10
  62. package/project/.sdk/src/cmp/rb/ReadmeTopQuick_rb.ts +28 -2
  63. package/project/.sdk/src/cmp/rb/ReadmeTopTest_rb.ts +13 -2
  64. package/project/.sdk/src/cmp/ts/Config_ts.ts +3 -6
  65. package/project/.sdk/src/cmp/ts/ReadmeEntity_ts.ts +22 -6
  66. package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +56 -18
  67. package/project/.sdk/src/cmp/ts/ReadmeRef_ts.ts +33 -10
  68. package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +21 -14
  69. package/project/.sdk/src/cmp/ts/ReadmeTopTest_ts.ts +15 -3
  70. package/project/.sdk/tm/go/utility/prepare_auth.go +6 -1
  71. package/project/.sdk/tm/js/src/utility/PrepareAuthUtility.js +3 -1
  72. package/project/.sdk/tm/lua/utility/prepare_auth.lua +6 -1
  73. package/project/.sdk/tm/php/utility/PrepareAuth.php +3 -1
  74. package/project/.sdk/tm/py/test/test_struct_utility.py +1 -1
  75. package/project/.sdk/tm/py/utility/prepare_auth.py +4 -1
  76. package/project/.sdk/tm/rb/Makefile +3 -1
  77. package/project/.sdk/tm/rb/utility/prepare_auth.rb +3 -1
  78. package/project/.sdk/tm/ts/src/utility/PrepareAuthUtility.ts +5 -1
  79. package/src/action/action.ts +15 -3
  80. package/src/action/feature.ts +4 -8
  81. package/src/action/target.ts +11 -9
  82. package/src/cmp/Deploy.ts +3 -3
  83. package/src/cmp/ReadmeTop.ts +25 -2
  84. package/src/helpers/opShape.ts +45 -7
  85. package/src/sdkgen.ts +4 -3
  86. package/src/utility.ts +24 -1
  87. package/project/.sdk/model/feature/feature-index.jsonic +0 -5
  88. /package/project/.sdk/model/feature/{log.jsonic → log.aontu} +0 -0
  89. /package/project/.sdk/model/feature/{test.jsonic → test.aontu} +0 -0
  90. /package/project/.sdk/model/target/{go-cli.jsonic → go-cli.aontu} +0 -0
  91. /package/project/.sdk/model/target/{go-mcp.jsonic → go-mcp.aontu} +0 -0
  92. /package/project/.sdk/model/target/{go.jsonic → go.aontu} +0 -0
  93. /package/project/.sdk/model/target/{js.jsonic → js.aontu} +0 -0
  94. /package/project/.sdk/model/target/{lua.jsonic → lua.aontu} +0 -0
  95. /package/project/.sdk/model/target/{php.jsonic → php.aontu} +0 -0
  96. /package/project/.sdk/model/target/{py.jsonic → py.aontu} +0 -0
  97. /package/project/.sdk/model/target/{rb.jsonic → rb.aontu} +0 -0
  98. /package/project/.sdk/model/target/{ts.jsonic → ts.aontu} +0 -0
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, each, Content, canonToType, File, isAuthActive, entityIdField, safeVarName } from '@voxgig/sdkgen'
2
+ import { cmp, each, Content, canonToType, File, isAuthActive, entityIdField, opRequestShape, safeVarName } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -272,8 +272,20 @@ ${info.desc}
272
272
 
273
273
  // Show example
274
274
  if ('load' === opname || 'remove' === opname) {
275
+ // The id key plus every REQUIRED match key (parent path params
276
+ // like page_id) — the same shape that generates <Name><Op>Match,
277
+ // so the example always type-checks.
278
+ const matchItems = opRequestShape(ent, opname).items
279
+ .filter((it: any) => !it.optional || it.name === idF)
280
+ .sort((a: any, b: any) =>
281
+ (a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
282
+ const arg = 0 < matchItems.length
283
+ ? `{ ${matchItems.map((it: any) =>
284
+ `${it.name}: ${exampleValue(ent, ent.op && ent.op[opname], it.name,
285
+ it.name === idF ? ent.name + '_id' : it.name)}`).join(', ')} }`
286
+ : ''
275
287
  Content(`\`\`\`ts
276
- const result = await client.${ent.Name}().${opname}(${idF ? `{ ${idF}: ${exampleValue(ent, ent.op && ent.op[opname], idF, ent.name + '_id')} }` : ''})
288
+ const result = await client.${ent.Name}().${opname}(${arg})
277
289
  \`\`\`
278
290
 
279
291
  `)
@@ -286,14 +298,19 @@ const results = await client.${ent.Name}().${opname}()
286
298
  `)
287
299
  }
288
300
  else if ('create' === opname) {
301
+ // Members come from the SAME shape that generates
302
+ // <Name>CreateData: every required member must appear (the
303
+ // /* type */ placeholders also mark the block as an illustration
304
+ // for the doc gates); an all-optional create renders an empty —
305
+ // still assignable — literal.
306
+ const createItems = opRequestShape(ent, 'create').items
307
+ .filter((it: any) => !it.optional)
289
308
  Content(`\`\`\`ts
290
309
  const result = await client.${ent.Name}().create({
291
310
  `)
292
- each(fields, (field: any) => {
293
- if ('id' !== field.name && field.req) {
294
- Content(` ${field.name}: /* ${canonToType(field.type, target.name)} */,
311
+ createItems.map((it: any) => {
312
+ Content(` ${it.name}: /* ${canonToType(it.type, target.name)} */,
295
313
  `)
296
- }
297
314
  })
298
315
  Content(`})
299
316
  \`\`\`
@@ -301,12 +318,18 @@ const result = await client.${ent.Name}().create({
301
318
  `)
302
319
  }
303
320
  else if ('update' === opname) {
304
- const updateIdLine = idF
305
- ? ` ${idF}: ${exampleValue(ent, ent.op && ent.op.update, idF, ent.name + '_id')},\n`
306
- : ''
321
+ // The id key plus every REQUIRED data member — the same shape
322
+ // that generates <Name>UpdateData then the patch-fields note.
323
+ const updateItems = opRequestShape(ent, 'update').items
324
+ .filter((it: any) => !it.optional || it.name === idF)
325
+ .sort((a: any, b: any) =>
326
+ (a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
327
+ const updateLines = updateItems.map((it: any) =>
328
+ ` ${it.name}: ${exampleValue(ent, ent.op && ent.op.update, it.name,
329
+ it.name === idF ? ent.name + '_id' : it.name)},\n`).join('')
307
330
  Content(`\`\`\`ts
308
331
  const result = await client.${ent.Name}().update({
309
- ${updateIdLine} // Fields to update
332
+ ${updateLines} // Fields to update
310
333
  })
311
334
  \`\`\`
312
335
 
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, Content, isAuthActive, packageName, envName, entityIdField, safeVarName } from '@voxgig/sdkgen'
2
+ import { cmp, Content, isAuthActive, packageName, envName, entityIdField, entityOps, opRequestShape, safeVarName } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -31,7 +31,7 @@ const client = ${ctor}
31
31
  if (exampleEntity) {
32
32
  const eName = nom(exampleEntity, 'Name')
33
33
  const eVar = safeVarName(eName.toLowerCase(), 'ts')
34
- const opnames = Object.keys(exampleEntity.op || {})
34
+ const opnames = entityOps(exampleEntity)
35
35
 
36
36
  let hasCall = false
37
37
 
@@ -45,26 +45,33 @@ for (const ${eVar} of ${eVar}s) {
45
45
  hasCall = true
46
46
  }
47
47
 
48
- // Find a nested entity for a more interesting example
48
+ // Find a nested entity for a more interesting example: one with a parent
49
+ // chain, an active load op of its OWN, and a required non-id load param
50
+ // to demonstrate (the parent key, e.g. page_id).
49
51
  const nestedEntity = Object.values(entity).find((e: any) =>
50
- e.active !== false && e.ancestors && e.ancestors.length > 0
52
+ e.active !== false &&
53
+ e.relations && e.relations.ancestors && 0 < e.relations.ancestors.length &&
54
+ entityOps(e).includes('load') &&
55
+ opRequestShape(e, 'load').items.some((it: any) =>
56
+ !it.optional && it.name !== entityIdField(e))
51
57
  ) as any
52
58
 
53
- if (nestedEntity && opnames.includes('load')) {
59
+ if (nestedEntity) {
54
60
  const neName = nom(nestedEntity, 'Name')
55
61
  const neVar = safeVarName(neName.toLowerCase(), 'ts')
56
- const parentFields = (nestedEntity.fields || [])
57
- .filter((f: any) => f.name !== 'id' && f.name.endsWith('_id'))
58
- const parentParam = parentFields.length > 0 ? parentFields[0].name : 'parent_id'
59
62
  const loadOp = nestedEntity.op && nestedEntity.op.load
60
63
 
61
- // Model-driven id key: only emit an id match line if the nested entity
62
- // has an id-like key field.
64
+ // Every REQUIRED load-match key (parent keys first, own id last) the
65
+ // same shape that generates <Name>LoadMatch, so the example
66
+ // type-checks.
63
67
  const neIdF = entityIdField(nestedEntity)
64
- const neMatchLines = [` ${parentParam}: ${exampleValue(nestedEntity, loadOp, parentParam, 'example')},`]
65
- if (neIdF) {
66
- neMatchLines.push(` ${neIdF}: ${exampleValue(nestedEntity, loadOp, neIdF, 'example_id')},`)
67
- }
68
+ const neMatchLines = opRequestShape(nestedEntity, 'load').items
69
+ .filter((it: any) => !it.optional || it.name === neIdF)
70
+ .sort((a: any, b: any) =>
71
+ (a.name === neIdF ? 1 : 0) - (b.name === neIdF ? 1 : 0))
72
+ .map((it: any) =>
73
+ ` ${it.name}: ${exampleValue(nestedEntity, loadOp, it.name,
74
+ it.name === neIdF ? 'example_id' : 'example_' + it.name)},`)
68
75
 
69
76
  Content(`
70
77
  // Load a specific ${neName.toLowerCase()} (returns a ${neName})
@@ -22,15 +22,27 @@ const client = ${model.const.Name}SDK.test()
22
22
 
23
23
  if (exampleEntity) {
24
24
  const eName = nom(exampleEntity, 'Name')
25
- const eVar = safeVarName(eName.toLowerCase(), 'ts')
26
25
  // Drive the test-mode example off the entity's PRIMARY op (never a
27
26
  // hardcoded `load` a create-only entity lacks).
28
27
  const primaryOp = entityPrimaryOp(exampleEntity) || 'load'
28
+ // A list() result is an array — name the variable accordingly.
29
+ const eVar = safeVarName(eName.toLowerCase(), 'ts') +
30
+ ('list' === primaryOp ? 's' : '')
29
31
  const primaryOpDef = exampleEntity.op && exampleEntity.op[primaryOp]
30
32
  const idF = entityIdField(exampleEntity)
31
33
  let arg = ''
32
34
  if ('load' === primaryOp || 'remove' === primaryOp) {
33
- arg = idF ? `{ ${idF}: ${exampleValue(exampleEntity, primaryOpDef, idF, 'test01')} }` : ''
35
+ // Every REQUIRED match key (id first) the same shape that generates
36
+ // the op's Match type, so the block type-checks.
37
+ const items = opRequestShape(exampleEntity, primaryOp).items
38
+ .filter((it: any) => !it.optional || it.name === idF)
39
+ .sort((a: any, b: any) =>
40
+ (a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
41
+ arg = 0 < items.length
42
+ ? `{ ${items.map((it: any) =>
43
+ `${it.name}: ${exampleValue(exampleEntity, primaryOpDef, it.name,
44
+ it.name === idF ? 'test01' : 'example_' + it.name)}`).join(', ')} }`
45
+ : ''
34
46
  } else if ('create' === primaryOp || 'update' === primaryOp) {
35
47
  const items = opRequestShape(exampleEntity, primaryOp).items
36
48
  .filter((it: any) => it.name !== idF && it.name !== 'id')
@@ -40,7 +52,7 @@ const client = ${model.const.Name}SDK.test()
40
52
  `${it.name}: ${exampleValue(exampleEntity, primaryOpDef, it.name, 'example_' + it.name)}`).join(', ')} }`
41
53
  }
42
54
  Content(`const ${eVar} = await client.${eName}().${primaryOp}(${arg})
43
- // ${eVar} is a bare ${eName} populated with mock data
55
+ // ${eVar} is ${'list' === primaryOp ? `an array of bare ${eName} records` : `a bare ${eName}`} populated with mock data
44
56
  console.log(${eVar})
45
57
  `)
46
58
  }
@@ -47,7 +47,12 @@ func prepareAuthUtil(ctx *core.Context) (*core.Spec, error) {
47
47
  if av, ok := apikey.(string); ok {
48
48
  apikeyVal = av
49
49
  }
50
- headers[headerAuth] = authPrefix + " " + apikeyVal
50
+ // Empty prefix (raw apiKey credential) must not add a leading space.
51
+ if authPrefix == "" {
52
+ headers[headerAuth] = apikeyVal
53
+ } else {
54
+ headers[headerAuth] = authPrefix + " " + apikeyVal
55
+ }
51
56
  }
52
57
 
53
58
  return spec, nil
@@ -36,7 +36,9 @@ function prepareAuth(ctx) {
36
36
  delprop(headers, HEADER_auth)
37
37
  }
38
38
  else {
39
- setprop(headers, HEADER_auth, options.auth.prefix + ' ' + apikey)
39
+ // Empty prefix (raw apiKey credential) must not add a leading space.
40
+ setprop(headers, HEADER_auth,
41
+ options.auth.prefix ? options.auth.prefix + ' ' + apikey : apikey)
40
42
  }
41
43
 
42
44
  return spec
@@ -38,7 +38,12 @@ local function prepare_auth_util(ctx)
38
38
  if type(apikey) == "string" then
39
39
  apikey_val = apikey
40
40
  end
41
- headers[HEADER_AUTH] = auth_prefix .. " " .. apikey_val
41
+ -- Empty prefix (raw apiKey credential) must not add a leading space.
42
+ if auth_prefix == "" then
43
+ headers[HEADER_AUTH] = apikey_val
44
+ else
45
+ headers[HEADER_AUTH] = auth_prefix .. " " .. apikey_val
46
+ end
42
47
  end
43
48
 
44
49
  return spec, nil
@@ -35,7 +35,9 @@ class ProjectNamePrepareAuth
35
35
  } else {
36
36
  $auth_prefix = \Voxgig\Struct\Struct::getpath($options, 'auth.prefix') ?? '';
37
37
  $apikey_val = is_string($apikey) ? $apikey : '';
38
- $headers[self::HEADER_AUTH] = "{$auth_prefix} {$apikey_val}";
38
+ // Empty prefix (raw apiKey credential) must not add a leading space.
39
+ $headers[self::HEADER_AUTH] = $auth_prefix === ''
40
+ ? $apikey_val : "{$auth_prefix} {$apikey_val}";
39
41
  }
40
42
 
41
43
  return [$spec, null];
@@ -516,7 +516,7 @@ class TestStructUtility(unittest.TestCase):
516
516
 
517
517
  # -------------------------------------------------
518
518
  # transform tests
519
- # Inputs and expected outputs: build/test/transform.jsonic
519
+ # Inputs and expected outputs: build/test/transform.aontu
520
520
  # -------------------------------------------------
521
521
 
522
522
  def test_transform_basic(self):
@@ -38,6 +38,9 @@ def prepare_auth_util(ctx):
38
38
  apikey_val = ""
39
39
  if isinstance(apikey, str):
40
40
  apikey_val = apikey
41
- headers[HEADER_AUTH] = auth_prefix + " " + apikey_val
41
+ # Empty prefix (raw apiKey credential) must not add a leading space.
42
+ headers[HEADER_AUTH] = (
43
+ auth_prefix + " " + apikey_val if auth_prefix else apikey_val
44
+ )
42
45
 
43
46
  return spec, None
@@ -1,7 +1,9 @@
1
1
  .PHONY: test build clean publish
2
2
 
3
+ # Run the WHOLE suite (entity, direct, utility, and readme-example tests) —
4
+ # a single-file invocation here leaves the rest of test/ silently unrun.
3
5
  test:
4
- ruby -Ilib test/exists_test.rb
6
+ ruby -Ilib -Itest -e 'Dir.glob("./test/*_test.rb").each { |f| require f }'
5
7
 
6
8
  build:
7
9
  @echo "No build step for Ruby"
@@ -25,7 +25,9 @@ module ProjectNameUtilities
25
25
  else
26
26
  auth_prefix = VoxgigStruct.getpath(options, "auth.prefix") || ""
27
27
  apikey_val = apikey.is_a?(String) ? apikey : ""
28
- headers[HEADER_AUTH] = "#{auth_prefix} #{apikey_val}"
28
+ # Empty prefix (raw apiKey credential) must not add a leading space.
29
+ headers[HEADER_AUTH] =
30
+ auth_prefix.empty? ? apikey_val : "#{auth_prefix} #{apikey_val}"
29
31
  end
30
32
 
31
33
  return spec, nil
@@ -36,13 +36,17 @@ function prepareAuth(ctx: Context): Spec | Error {
36
36
  return spec
37
37
  }
38
38
 
39
+ const prefix = options.auth.prefix
40
+
39
41
  const apikey = getprop(options, OPTION_apikey, NOTFOUND)
40
42
 
41
43
  if (NOTFOUND === apikey || null == apikey || '' === apikey) {
42
44
  delprop(headers, HEADER_auth)
43
45
  }
44
46
  else {
45
- setprop(headers, HEADER_auth, options.auth.prefix + ' ' + apikey)
47
+ // A raw credential (empty prefix, e.g. an apiKey scheme) must go in
48
+ // as-is; only a non-empty prefix (Bearer/Basic/OAuth) is space-joined.
49
+ setprop(headers, HEADER_auth, prefix ? prefix + ' ' + apikey : apikey)
46
50
  }
47
51
 
48
52
  return spec
@@ -12,14 +12,14 @@ import type {
12
12
  } from '../types'
13
13
 
14
14
 
15
- // Append `@"<name>.jsonic"` import lines for each name not already present in
15
+ // Append `@"<name>.aontu"` import lines for each name not already present in
16
16
  // the index content. Checking against the accumulating result (not the
17
17
  // original) means duplicate names in the same call are added at most once.
18
18
  function appendIndexEntries(content: string, names: string[]): string {
19
19
  let out = content
20
20
 
21
21
  for (const n of names) {
22
- const entry = `@"${n}.jsonic"`
22
+ const entry = `@"${n}.aontu"`
23
23
  if (!out.includes(entry)) {
24
24
  out += '\n' + entry
25
25
  }
@@ -34,6 +34,17 @@ const UpdateIndex = cmp(function UpdateIndex(props: any) {
34
34
  })
35
35
 
36
36
 
37
+ // Names given to an `add` action: every positional after the subcommand is
38
+ // a name, each possibly comma-separated — `target add ts,py,go` and
39
+ // `target add ts py go` are equivalent (space-separated extras used to be
40
+ // silently dropped).
41
+ function parseAddNames(args: any[]): string[] {
42
+ return args.slice(2)
43
+ .flatMap((a: any) => 'string' === typeof a ? a.split(',') : a)
44
+ .filter((n: any) => null != n && '' !== n)
45
+ }
46
+
47
+
37
48
  function loadContent(actx: ActionContext, which: string | string[]) {
38
49
  which = Array.isArray(which) ? which : [which]
39
50
 
@@ -43,7 +54,7 @@ function loadContent(actx: ActionContext, which: string | string[]) {
43
54
  const modelfolder = Path.dirname(actx.url)
44
55
 
45
56
  which.map((w: string) => {
46
- const indexfile = Path.join(modelfolder, w, w + '-index.jsonic')
57
+ const indexfile = Path.join(modelfolder, w, w + '-index.aontu')
47
58
  const indexcontent = fs.readFileSync(indexfile, 'utf8')
48
59
  content[`${w}_index`] = indexcontent
49
60
  })
@@ -56,5 +67,6 @@ function loadContent(actx: ActionContext, which: string | string[]) {
56
67
  export {
57
68
  UpdateIndex,
58
69
  appendIndexEntries,
70
+ parseAddNames,
59
71
  loadContent
60
72
  }
@@ -26,6 +26,7 @@ import { SdkGenError } from '../utility'
26
26
 
27
27
  import {
28
28
  UpdateIndex,
29
+ parseAddNames,
29
30
  loadContent,
30
31
  } from './action'
31
32
 
@@ -52,12 +53,7 @@ async function action_feature(args: string[], actx: ActionContext): Promise<Acti
52
53
 
53
54
 
54
55
  async function cmd_feature_add(args: string[], actx: ActionContext): Promise<ActionResult> {
55
-
56
- const features_arg = args[2]
57
- const features: string[] =
58
- 'string' === typeof features_arg ? features_arg.split(',') : features_arg
59
-
60
- return feature_add(features, actx)
56
+ return feature_add(parseAddNames(args), actx)
61
57
  }
62
58
 
63
59
 
@@ -122,10 +118,10 @@ const FeatureRoot = cmp(function FeatureRoot(props: any) {
122
118
  Folder({ name: 'model/feature' }, () => {
123
119
  Copy({
124
120
  // TODO: these paths needs to be parameterised
125
- from: BASE + '/project/.sdk/model/feature/' + fname + '.jsonic',
121
+ from: BASE + '/project/.sdk/model/feature/' + fname + '.aontu',
126
122
  exclude: true
127
123
  })
128
- File({ name: 'feature-index.jsonic' }, () => UpdateIndex({
124
+ File({ name: 'feature-index.aontu' }, () => UpdateIndex({
129
125
  content: ctx$.meta.content.feature_index,
130
126
  names: features,
131
127
  }))
@@ -31,6 +31,7 @@ import {
31
31
 
32
32
  import {
33
33
  UpdateIndex,
34
+ parseAddNames,
34
35
  loadContent,
35
36
  } from './action'
36
37
 
@@ -55,11 +56,7 @@ async function action_target(args: string[], actx: ActionContext): Promise<Actio
55
56
 
56
57
 
57
58
  async function cmd_target_add(args: string[], actx: ActionContext): Promise<ActionResult> {
58
- const targets_arg = args[2]
59
- const targets: string[] =
60
- 'string' === typeof targets_arg ? targets_arg.split(',') : targets_arg
61
-
62
- return target_add(targets, actx)
59
+ return target_add(parseAddNames(args), actx)
63
60
  }
64
61
 
65
62
 
@@ -124,6 +121,11 @@ const TargetRoot = cmp(function TargetRoot(props: any) {
124
121
  // const tfolder = 'node_modules/@voxgig/sdkgen/project/.sdk'
125
122
 
126
123
  Project({}, () => {
124
+ // Resolved names of every target in this run. The index File is
125
+ // re-rendered per target and the last render wins, so each render must
126
+ // carry all names seen so far, not just its own.
127
+ const tnames: string[] = []
128
+
127
129
  each(targets, (n) => {
128
130
  const tref = n.val$
129
131
 
@@ -134,6 +136,7 @@ const TargetRoot = cmp(function TargetRoot(props: any) {
134
136
  })
135
137
 
136
138
  const { tname, tfolder, torigname, base } = resolveTarget(tref, ctx$)
139
+ tnames.push(tname)
137
140
  const targetNote = tname + (tname != tref ? ' ref:' + tref : '')
138
141
 
139
142
  log.info({
@@ -145,16 +148,15 @@ const TargetRoot = cmp(function TargetRoot(props: any) {
145
148
 
146
149
  Folder({ name: 'model/target' }, () => {
147
150
  Copy({
148
- from: tfolder + '/model/target/' + torigname + '.jsonic',
151
+ from: tfolder + '/model/target/' + torigname + '.aontu',
149
152
  // exclude: true
150
153
  replace: {
151
154
  "'BASE'": "'" + base + "'"
152
155
  }
153
156
  })
154
- File({ name: 'target-index.jsonic' }, () => UpdateIndex({
157
+ File({ name: 'target-index.aontu' }, () => UpdateIndex({
155
158
  content: ctx$.meta.content.target_index,
156
- // names: targets,
157
- names: [tname]
159
+ names: tnames,
158
160
  }))
159
161
  })
160
162
 
package/src/cmp/Deploy.ts CHANGED
@@ -7,7 +7,7 @@ import {
7
7
 
8
8
 
9
9
  // Generate the SDK root deployment Makefile from the model's per-target
10
- // `publish` sections (see model/sdkgen.jsonic):
10
+ // `publish` sections (see model/sdkgen.aontu):
11
11
  //
12
12
  // publish.tag — git release tag `<prefix>/vX.Y.Z`; every port has
13
13
  // one, pushed with a token from the aql key vault
@@ -179,7 +179,7 @@ deploy-dry-${t.name}:
179
179
 
180
180
  return `# ${model.Name} SDK deployment. GENERATED by @voxgig/sdkgen — regenerated
181
181
  # on every \`npm run generate\` in .sdk/; publication details live in the
182
- # model (.sdk/model/target/<target>.jsonic, \`publish\` section).
182
+ # model (.sdk/model/target/<target>.aontu, \`publish\` section).
183
183
  #
184
184
  # Every port gets a git release tag <target>/vX.Y.Z. Ports with an ACTIVE
185
185
  # registry also publish a package there. Credentials are injected at exec
@@ -218,7 +218,7 @@ TARGETS := ${targets.map((t: any) => t.name).join(' ')}
218
218
  deploy:
219
219
  \t@echo "Deployment is per-target — pick one (each upload is irreversible):"
220
220
  \t@echo " make deploy-<target> targets: $(TARGETS)"
221
- \t@echo "Registry state is set in the model (.sdk/model/target/<t>.jsonic):"
221
+ \t@echo "Registry state is set in the model (.sdk/model/target/<t>.aontu):"
222
222
  ${targets.map((t: any) => {
223
223
  const reg = registryOf(t)
224
224
  const state = reg ? `${reg.name} ${regIsActive(reg) ? 'ACTIVE' : 'publish pending (deploy = git tag only)'}` : 'tag-only'
@@ -86,6 +86,17 @@ const ReadmeTop = cmp(function ReadmeTop(props: any) {
86
86
  const docsUrl = info.docs_url || ''
87
87
  const entityDesc = info.entity_desc || {}
88
88
 
89
+ // Spec-derived (apidef): a short "what this API is" blurb and a link back
90
+ // to the API's own website. Both surface right under the title, before
91
+ // the unofficial-SDK disclosure, so a reader immediately sees what the
92
+ // underlying API is and where it comes from.
93
+ const apiSummary = (info.summary || '').trim()
94
+ const apiWebsite = (info.website || '').trim()
95
+ const websiteLine = apiWebsite
96
+ ? `Learn more about ${productName} at ` +
97
+ `[${apiWebsite.replace(/^https?:\/\//, '').replace(/\/$/, '')}](${apiWebsite}).`
98
+ : ''
99
+
89
100
  const entity = getModelPath(model, `main.${KIT}.entity`)
90
101
  const target = getModelPath(model, `main.${KIT}.target`)
91
102
  const feature = getModelPath(model, `main.${KIT}.feature`)
@@ -123,12 +134,24 @@ const ReadmeTop = cmp(function ReadmeTop(props: any) {
123
134
 
124
135
  File({ name: 'README.md' }, () => {
125
136
 
126
- // 1. H1 + one-line value prop + unofficial / non-affiliation disclosure
137
+ // 1. H1 + one-line value prop, then the API's own purpose + website
138
+ // (spec-derived), then the unofficial / non-affiliation disclosure.
127
139
  Content(`# ${model.Name} SDK
128
140
 
129
141
  ${tagline}
130
142
 
131
- ${nonAffiliation(model)}
143
+ `)
144
+ if (apiSummary) {
145
+ Content(`${apiSummary}
146
+
147
+ `)
148
+ }
149
+ if (websiteLine) {
150
+ Content(`${websiteLine}
151
+
152
+ `)
153
+ }
154
+ Content(`${nonAffiliation(model)}
132
155
 
133
156
  `)
134
157
 
@@ -60,20 +60,58 @@ type OpShapeItem = {
60
60
  }
61
61
 
62
62
 
63
- // Collect an op's params, deduped by name across all of its points.
63
+ // Collect an op's params, deduped by name across its points.
64
+ //
65
+ // Points tagged with `select.$action` are sub-resource/action routes folded
66
+ // into the op (e.g. POST .../components/{component_id}/page_access_groups on
67
+ // the create op); they do not describe the op's canonical request payload,
68
+ // so they are excluded — unless the op consists only of action points.
69
+ //
70
+ // Requiredness merges by INTERSECTION: with several alternative canonical
71
+ // points, a param the caller must always supply is one required by every
72
+ // point; a param only some routes use is optional in the merged shape.
73
+ // (Union-required makes the common route untypeable — e.g. a plain
74
+ // list-by-parent-id call failing because sibling routes' ids are demanded —
75
+ // and steers callers into passing keys that flip runtime point dispatch to
76
+ // the wrong route.)
64
77
  function opParams(op: any): any[] {
65
- const points = op && op.points ? each(op.points) : []
66
- const seen: Record<string, boolean> = {}
78
+ let points: any[] = op && op.points ? each(op.points) : []
79
+
80
+ const canonical = points.filter((pt: any) =>
81
+ null == (pt && pt.select && pt.select['$action']))
82
+ if (0 < canonical.length) {
83
+ points = canonical
84
+ }
85
+
86
+ const seen: Record<string, any> = {}
87
+ const requiredOnAll: Record<string, boolean> = {}
67
88
  const out: any[] = []
68
- points.forEach((pt: any) => {
89
+
90
+ points.forEach((pt: any, pointIndex: number) => {
69
91
  const params = pt && pt.args && pt.args.params ? each(pt.args.params) : []
92
+ const requiredHere: Record<string, boolean> = {}
70
93
  params.forEach((p: any) => {
71
- if (p && null != p.name && !seen[p.name]) {
72
- seen[p.name] = true
73
- out.push(p)
94
+ if (p && null != p.name) {
95
+ requiredHere[p.name] = false !== p.reqd
96
+ if (!seen[p.name]) {
97
+ seen[p.name] = { ...p }
98
+ // A param first seen on a later point was absent earlier: optional.
99
+ requiredOnAll[p.name] = 0 === pointIndex
100
+ out.push(seen[p.name])
101
+ }
102
+ }
103
+ })
104
+ Object.keys(requiredOnAll).forEach((name) => {
105
+ if (true !== requiredHere[name]) {
106
+ requiredOnAll[name] = false
74
107
  }
75
108
  })
76
109
  })
110
+
111
+ out.forEach((p: any) => {
112
+ p.reqd = true === requiredOnAll[p.name]
113
+ })
114
+
77
115
  return out
78
116
  }
79
117
 
package/src/sdkgen.ts CHANGED
@@ -19,7 +19,7 @@ import type {
19
19
  ActionResult,
20
20
  } from './types'
21
21
 
22
- import { SdkGenError, requirePath, isAuthActive } from './utility'
22
+ import { SdkGenError, requirePath, isAuthActive, resolveAuthPrefix } from './utility'
23
23
 
24
24
  import { Main } from './cmp/Main'
25
25
  import { Deploy } from './cmp/Deploy'
@@ -226,7 +226,7 @@ function SdkGen(opts: SdkGenOptions) {
226
226
 
227
227
 
228
228
  function resolveModel() {
229
- const path = './model/sdk.jsonic'
229
+ const path = './model/sdk.aontu'
230
230
  const errs: any[] = []
231
231
 
232
232
  if (null == aontu) {
@@ -299,7 +299,7 @@ SdkGen.makeBuild = async function(opts: SdkGenOptions) {
299
299
  root: opts.root,
300
300
  def: opts.def || 'no-def',
301
301
  kind: 'openapi-3',
302
- model: opts.model ? (opts.model.folder + '/api.jsonic') : 'no-model',
302
+ model: opts.model ? (opts.model.folder + '/api.aontu') : 'no-model',
303
303
  meta: opts.meta || {},
304
304
  }
305
305
 
@@ -432,6 +432,7 @@ export {
432
432
 
433
433
  requirePath,
434
434
  isAuthActive,
435
+ resolveAuthPrefix,
435
436
 
436
437
  buildIdNames,
437
438
  getMatchEntries,