@voxgig/sdkgen 4.6.0 → 4.8.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.
Files changed (144) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/Feature.js +5 -0
  3. package/dist/cmp/Feature.js.map +1 -1
  4. package/dist/cmp/TestControl.d.ts +4 -0
  5. package/dist/cmp/TestControl.js +51 -0
  6. package/dist/cmp/TestControl.js.map +1 -0
  7. package/dist/helpers/featureSource.d.ts +3 -1
  8. package/dist/helpers/featureSource.js +105 -0
  9. package/dist/helpers/featureSource.js.map +1 -1
  10. package/dist/helpers/opShape.js +8 -11
  11. package/dist/helpers/opShape.js.map +1 -1
  12. package/dist/helpers/pointPath.d.ts +10 -0
  13. package/dist/helpers/pointPath.js +54 -0
  14. package/dist/helpers/pointPath.js.map +1 -0
  15. package/dist/sdkgen.d.ts +6 -3
  16. package/dist/sdkgen.js +14 -4
  17. package/dist/sdkgen.js.map +1 -1
  18. package/dist/tsconfig.tsbuildinfo +1 -1
  19. package/dist/utility.d.ts +2 -1
  20. package/dist/utility.js +78 -1
  21. package/dist/utility.js.map +1 -1
  22. package/model/sdkgen.aon +58 -0
  23. package/package.json +2 -2
  24. package/project/.sdk/model/feature/secrets.aon +82 -0
  25. package/project/.sdk/src/cmp/c/Config_c.ts +7 -8
  26. package/project/.sdk/src/cmp/c/TestDirect_c.ts +4 -3
  27. package/project/.sdk/src/cmp/cpp/Main_cpp.ts +2 -1
  28. package/project/.sdk/src/cmp/cpp/TestDirect_cpp.ts +4 -3
  29. package/project/.sdk/src/cmp/cpp/Test_cpp.ts +5 -1
  30. package/project/.sdk/src/cmp/csharp/Main_csharp.ts +2 -1
  31. package/project/.sdk/src/cmp/csharp/TestDirect_csharp.ts +34 -9
  32. package/project/.sdk/src/cmp/csharp/TestEntity_csharp.ts +29 -5
  33. package/project/.sdk/src/cmp/csharp/Test_csharp.ts +5 -1
  34. package/project/.sdk/src/cmp/dart/Config_dart.ts +7 -8
  35. package/project/.sdk/src/cmp/dart/Main_dart.ts +2 -1
  36. package/project/.sdk/src/cmp/dart/TestDirect_dart.ts +28 -10
  37. package/project/.sdk/src/cmp/dart/TestEntity_dart.ts +28 -7
  38. package/project/.sdk/src/cmp/dart/Test_dart.ts +5 -1
  39. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +9 -8
  40. package/project/.sdk/src/cmp/go/Config_go.ts +1 -7
  41. package/project/.sdk/src/cmp/go/Main_go.ts +2 -1
  42. package/project/.sdk/src/cmp/go/TestDirect_go.ts +5 -4
  43. package/project/.sdk/src/cmp/go/Test_go.ts +5 -1
  44. package/project/.sdk/src/cmp/java/Config_java.ts +7 -7
  45. package/project/.sdk/src/cmp/java/Main_java.ts +2 -1
  46. package/project/.sdk/src/cmp/java/TestDirect_java.ts +5 -4
  47. package/project/.sdk/src/cmp/java/Test_java.ts +5 -1
  48. package/project/.sdk/src/cmp/js/Config_js.ts +7 -8
  49. package/project/.sdk/src/cmp/js/Main_js.ts +2 -1
  50. package/project/.sdk/src/cmp/js/TestDirect_js.ts +34 -8
  51. package/project/.sdk/src/cmp/js/TestEntity_js.ts +35 -5
  52. package/project/.sdk/src/cmp/js/Test_js.ts +5 -1
  53. package/project/.sdk/src/cmp/js/fragment/Direct.test.fragment.js +7 -1
  54. package/project/.sdk/src/cmp/js/fragment/Entity.test.fragment.js +7 -1
  55. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +7 -7
  56. package/project/.sdk/src/cmp/kotlin/Main_kotlin.ts +2 -1
  57. package/project/.sdk/src/cmp/kotlin/TestDirect_kotlin.ts +5 -4
  58. package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +5 -1
  59. package/project/.sdk/src/cmp/lean/Config_lean.ts +9 -8
  60. package/project/.sdk/src/cmp/lua/Config_lua.ts +1 -7
  61. package/project/.sdk/src/cmp/lua/Main_lua.ts +2 -1
  62. package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +32 -7
  63. package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +24 -4
  64. package/project/.sdk/src/cmp/lua/Test_lua.ts +5 -1
  65. package/project/.sdk/src/cmp/ocaml/Gitignore_ocaml.ts +1 -0
  66. package/project/.sdk/src/cmp/perl/Main_perl.ts +2 -1
  67. package/project/.sdk/src/cmp/perl/TestDirect_perl.ts +29 -7
  68. package/project/.sdk/src/cmp/perl/TestEntity_perl.ts +24 -4
  69. package/project/.sdk/src/cmp/perl/Test_perl.ts +5 -1
  70. package/project/.sdk/src/cmp/perl/utility_perl.ts +10 -0
  71. package/project/.sdk/src/cmp/php/Config_php.ts +1 -7
  72. package/project/.sdk/src/cmp/php/Main_php.ts +2 -1
  73. package/project/.sdk/src/cmp/php/TestDirect_php.ts +28 -8
  74. package/project/.sdk/src/cmp/php/TestEntity_php.ts +28 -4
  75. package/project/.sdk/src/cmp/php/Test_php.ts +5 -1
  76. package/project/.sdk/src/cmp/py/Config_py.ts +1 -7
  77. package/project/.sdk/src/cmp/py/Main_py.ts +2 -1
  78. package/project/.sdk/src/cmp/py/TestDirect_py.ts +5 -4
  79. package/project/.sdk/src/cmp/py/Test_py.ts +5 -1
  80. package/project/.sdk/src/cmp/rb/Config_rb.ts +1 -7
  81. package/project/.sdk/src/cmp/rb/Main_rb.ts +2 -1
  82. package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +28 -8
  83. package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +24 -4
  84. package/project/.sdk/src/cmp/rb/Test_rb.ts +5 -1
  85. package/project/.sdk/src/cmp/rb/utility_rb.ts +3 -1
  86. package/project/.sdk/src/cmp/rust/Main_rust.ts +2 -1
  87. package/project/.sdk/src/cmp/rust/TestDirect_rust.ts +35 -7
  88. package/project/.sdk/src/cmp/rust/TestEntity_rust.ts +37 -4
  89. package/project/.sdk/src/cmp/rust/Test_rust.ts +5 -1
  90. package/project/.sdk/src/cmp/scala/Config_scala.ts +9 -7
  91. package/project/.sdk/src/cmp/scala/Main_scala.ts +2 -1
  92. package/project/.sdk/src/cmp/scala/TestDirect_scala.ts +4 -3
  93. package/project/.sdk/src/cmp/scala/Test_scala.ts +5 -1
  94. package/project/.sdk/src/cmp/seneca-provider/Extras_seneca-provider.ts +3 -1
  95. package/project/.sdk/src/cmp/seneca-provider/Main_seneca-provider.ts +9 -5
  96. package/project/.sdk/src/cmp/ts/Config_ts.ts +57 -16
  97. package/project/.sdk/src/cmp/ts/Main_ts.ts +10 -2
  98. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +15 -7
  99. package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +11 -3
  100. package/project/.sdk/src/cmp/ts/Test_ts.ts +5 -1
  101. package/project/.sdk/tm/csharp/test/Runner.cs +45 -0
  102. package/project/.sdk/tm/dart/test/utility.dart +39 -0
  103. package/project/.sdk/tm/js/test/sdk-test-control.json +19 -0
  104. package/project/.sdk/tm/js/test/utility.js +91 -1
  105. package/project/.sdk/tm/lua/test/runner.lua +40 -0
  106. package/project/.sdk/tm/ocaml/test/harness.ml +14 -4
  107. package/project/.sdk/tm/perl/t/runner.pm +30 -0
  108. package/project/.sdk/tm/php/test/Runner.php +36 -0
  109. package/project/.sdk/tm/rb/test/runner.rb +25 -0
  110. package/project/.sdk/tm/rust/tests/common/mod.rs +32 -0
  111. package/project/.sdk/tm/rust/utility/make_options.rs +1 -1
  112. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +8 -2
  113. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +29 -5
  114. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +30 -21
  115. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/Registry.ts +90 -0
  116. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/addr.ts +48 -0
  117. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/aws.ts +219 -0
  118. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/azuresecrets.ts +144 -0
  119. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/boru.ts +113 -0
  120. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/doppler.ts +77 -0
  121. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/dotenv.ts +51 -0
  122. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/env.ts +31 -0
  123. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/file.ts +54 -0
  124. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/gcpsecrets.ts +118 -0
  125. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/hashicorp.ts +159 -0
  126. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/http.ts +66 -0
  127. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/infisical.ts +107 -0
  128. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/memory.ts +33 -0
  129. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/onepassword.ts +120 -0
  130. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/support.ts +176 -0
  131. package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +15 -13
  132. package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +25 -19
  133. package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +1 -1
  134. package/project/.sdk/tm/ts/test/vendor/omni/index.ts +1 -1
  135. package/project/.sdk/tm/zig/core/utility.zig +1 -1
  136. package/project/sdkgen-package.json +1 -1
  137. package/src/cmp/Feature.ts +5 -0
  138. package/src/cmp/TestControl.ts +64 -0
  139. package/src/helpers/featureSource.ts +130 -0
  140. package/src/helpers/opShape.ts +9 -14
  141. package/src/helpers/pointPath.ts +88 -0
  142. package/src/sdkgen.ts +18 -0
  143. package/src/utility.ts +92 -1
  144. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Providers.ts +0 -1221
@@ -3,7 +3,8 @@ import type {
3
3
  ModelEntity
4
4
  } from '@voxgig/apidef'
5
5
 
6
- import { cmp, each, Folder, entityCollection } from '@voxgig/sdkgen'
6
+ import { cmp, each, Folder, entityCollection,
7
+ TestControl } from '@voxgig/sdkgen'
7
8
 
8
9
 
9
10
  import { TestEntity } from './TestEntity_csharp'
@@ -21,6 +22,9 @@ const Test = cmp(function Test(props: any) {
21
22
  // tests are generated here.
22
23
  Folder({ name: 'test' }, () => {
23
24
 
25
+ // Write-once: a project's edited control file survives regeneration.
26
+ TestControl({ target, dir: 'test' })
27
+
24
28
  const entity = each(entityCollection(model))
25
29
  .filter((e: any) => false !== e.active)
26
30
 
@@ -26,7 +26,6 @@ import {
26
26
 
27
27
 
28
28
  import {
29
- clean,
30
29
  dartStringLiteral,
31
30
  dartValue,
32
31
  } from './utility_dart'
@@ -159,13 +158,13 @@ const Config = cmp(async function Config(props: any) {
159
158
  Line(` '${entity.name}': <String, dynamic>{},`)
160
159
  }),
161
160
 
162
- "'ENTITYMAP'": dartValue(Object.values(entity)
163
- .reduce((a: any, n: any) => (a[n.name] = clean({
164
- fields: n.fields,
165
- name: n.name,
166
- op: n.op,
167
- relations: n.relations,
168
- }, true), a), {}), 1),
161
+ // configDefinition's `def.entity` verbatim, NOT rebuilt here. This
162
+ // reduce was a second copy of that function's entityDefs loop, and
163
+ // when configDefinition started reconstructing a point's `parts`
164
+ // from apidef's segment vector (its ADR-003), only the data
165
+ // representation got it — the literal one emitted empty paths. The
166
+ // config-repr equivalence test caught it, which is what it is for.
167
+ "'ENTITYMAP'": dartValue(configDef.entity, 1),
169
168
  }
170
169
  })
171
170
  })
@@ -5,6 +5,7 @@ import {
5
5
  cmp, each,
6
6
  List, File, Copy, Folder, Fragment, Line,
7
7
  entityClassName, entityCollection,
8
+ TEST_CONTROL_EXCLUDE
8
9
  } from '@voxgig/sdkgen'
9
10
 
10
11
 
@@ -45,7 +46,7 @@ const Main = cmp(async function Main(props: any) {
45
46
  // lib/feature/), so they are excluded from the generated package.
46
47
  Copy({
47
48
  from: 'tm/' + target.name,
48
- exclude: [/src\//],
49
+ exclude: [/src\//, TEST_CONTROL_EXCLUDE],
49
50
  replace: {
50
51
  ...props.ctx$.stdrep,
51
52
  }
@@ -16,14 +16,16 @@ import {
16
16
  Slot,
17
17
  cmp,
18
18
  snakify,
19
- isAuthActive, envName, envToken
19
+ isAuthActive, envName, envToken,
20
+ serverVarEnv,
21
+ serverVariables,
22
+ pointParts,
20
23
  } from '@voxgig/sdkgen'
21
24
 
22
25
 
23
26
  import {
24
27
  projectPath,
25
- dartValue,
26
- } from './utility_dart'
28
+ dartValue, dartStringLiteral} from './utility_dart'
27
29
 
28
30
 
29
31
  const TestDirect = cmp(function TestDirect(props: any) {
@@ -40,13 +42,26 @@ const TestDirect = cmp(function TestDirect(props: any) {
40
42
 
41
43
  const authActive = isAuthActive(model)
42
44
  const apikeyEnvEntry = authActive
43
- ? `\n '${PROJECTNAME}_APIKEY': 'NONE',`
45
+ ? `\n '${PROJECTNAME}_APIKEY': '',`
44
46
  : ''
45
47
  const apikeyLiveField = authActive
46
48
  ? `
47
49
  'apikey': env['${PROJECTNAME}_APIKEY'],`
48
50
  : ''
49
51
 
52
+ // A templated server URL (OpenAPI server variables) makes a LIVE client
53
+ // impossible to construct without values: makeOptions raises rather than
54
+ // request a URL with a literal `{account_id}` in it. So the live suite
55
+ // takes them from the environment the same way it takes the apikey.
56
+ const svars = serverVariables(model)
57
+ const serverEnvEntry = svars
58
+ .map((v: any) => `\n '${serverVarEnv(PROJECTNAME, v.name)}': ${dartStringLiteral(v.dflt)},`).join('')
59
+ const serverLiveField = 0 === svars.length ? '' : `
60
+ 'server': <String, dynamic>{${svars
61
+ .map((v: any) => `
62
+ '${v.name}': env['${serverVarEnv(PROJECTNAME, v.name)}'],`).join('')}
63
+ },`
64
+
50
65
  const opnames = Object.keys(entity.op || {})
51
66
  const hasLoad = opnames.includes('load')
52
67
  const hasList = opnames.includes('list')
@@ -78,13 +93,16 @@ Map<String, dynamic> directSetup([dynamic mockres]) {
78
93
 
79
94
  final env = envOverride({
80
95
  '${entidEnvVar}': <String, dynamic>{},
81
- '${PROJECTNAME}_TEST_LIVE': 'FALSE',${apikeyEnvEntry}
96
+ '${PROJECTNAME}_TEST_LIVE': 'FALSE',${apikeyEnvEntry}${serverEnvEntry}
82
97
  });
83
98
 
84
99
  final live = 'TRUE' == env['${PROJECTNAME}_TEST_LIVE'];
85
100
 
86
101
  if (live) {
87
- final client = ${nom(model.const, 'Name')}SDK({${apikeyLiveField}
102
+ // Spread FIRST, so the generated fields below win: sdk-test-control.json's
103
+ // test.client.options adds to the live client, it does not redirect it.
104
+ final client = ${nom(model.const, 'Name')}SDK(<String, dynamic>{
105
+ ...liveClientOptions(),${apikeyLiveField}${serverLiveField}
88
106
  });
89
107
 
90
108
  dynamic idmap = env['${entidEnvVar}'];
@@ -165,12 +183,12 @@ function generateDirectLoad(model: Model, entity: ModelEntity) {
165
183
  }
166
184
 
167
185
  const allLoadParams = loadPoint.args?.params || []
168
- const loadPath = normalizePathParams(loadPoint.parts || [], allLoadParams, loadPoint.rename?.param)
186
+ const loadPath = normalizePathParams(pointParts(loadPoint), allLoadParams, loadPoint.rename?.param)
169
187
 
170
188
  // Only path params that actually appear in the URL template drive the
171
189
  // direct-test path-param setup and URL-substitution asserts.
172
190
  const pathPlaceholders = new Set<string>()
173
- for (const part of (loadPoint.parts || [])) {
191
+ for (const part of pointParts(loadPoint)) {
174
192
  if (typeof part === 'string' && part.startsWith('{') && part.endsWith('}')) {
175
193
  pathPlaceholders.add(part.slice(1, -1))
176
194
  }
@@ -200,7 +218,7 @@ function generateDirectLoad(model: Model, entity: ModelEntity) {
200
218
  const listOp = entity.op?.list
201
219
  const listPoint = listOp?.points?.[0]
202
220
  const listParams = listPoint?.args?.params || []
203
- const listPath = listPoint ? normalizePathParams(listPoint.parts || [], listParams, listPoint.rename?.param) : ''
221
+ const listPath = listPoint ? normalizePathParams(pointParts(listPoint), listParams, listPoint.rename?.param) : ''
204
222
  const hasList = null != listPoint
205
223
 
206
224
  // Ancestor params (not 'id') for live mode
@@ -358,7 +376,7 @@ function generateDirectList(model: Model, entity: ModelEntity) {
358
376
  }
359
377
 
360
378
  const listParams = listPoint.args?.params || []
361
- const listPath = normalizePathParams(listPoint.parts || [], listParams, listPoint.rename?.param)
379
+ const listPath = normalizePathParams(pointParts(listPoint), listParams, listPoint.rename?.param)
362
380
 
363
381
  const listQuery = listPoint.args?.query || []
364
382
  const liveQueryLines = listQuery
@@ -23,13 +23,14 @@ import {
23
23
  Slot,
24
24
  cmp,
25
25
  each,
26
- isAuthActive, envName, envToken
26
+ isAuthActive, envName, envToken,
27
+ serverVarEnv,
28
+ serverVariables
27
29
  } from '@voxgig/sdkgen'
28
30
 
29
31
 
30
32
  import {
31
- projectPath
32
- } from './utility_dart'
33
+ projectPath, dartStringLiteral} from './utility_dart'
33
34
 
34
35
 
35
36
  // GenCtx is the per-language generation context passed to every OpGen.
@@ -54,13 +55,26 @@ const TestEntity = cmp(function TestEntity(props: any) {
54
55
  const ENTENVNAME = envToken(entity.name)
55
56
  const authActive = isAuthActive(model)
56
57
  const apikeyEnvEntry = authActive
57
- ? `\n '${PROJENVNAME}_APIKEY': 'NONE',`
58
+ ? `\n '${PROJENVNAME}_APIKEY': '',`
58
59
  : ''
59
60
  const apikeyLiveField = authActive
60
61
  ? `
61
62
  'apikey': env['${PROJENVNAME}_APIKEY'],`
62
63
  : ''
63
64
 
65
+ // A templated server URL (OpenAPI server variables) makes a LIVE client
66
+ // impossible to construct without values: makeOptions raises rather than
67
+ // request a URL with a literal `{account_id}` in it. So the live suite
68
+ // takes them from the environment the same way it takes the apikey.
69
+ const svars = serverVariables(model)
70
+ const serverEnvEntry = svars
71
+ .map((v: any) => `\n '${serverVarEnv(PROJENVNAME, v.name)}': ${dartStringLiteral(v.dflt)},`).join('')
72
+ const serverLiveField = 0 === svars.length ? '' : `
73
+ 'server': <String, dynamic>{${svars
74
+ .map((v: any) => `
75
+ '${v.name}': env['${serverVarEnv(PROJENVNAME, v.name)}'],`).join('')}
76
+ },`
77
+
64
78
  const ff = projectPath('src/cmp/dart/fragment/')
65
79
 
66
80
  Folder({ name: entity.name }, () => {
@@ -191,7 +205,7 @@ Map<String, dynamic> basicSetup([dynamic extra]) {
191
205
  final env = envOverride({
192
206
  '${PROJENVNAME}_TEST_${ENTENVNAME}_ENTID': idmap,
193
207
  '${PROJENVNAME}_TEST_LIVE': 'FALSE',
194
- '${PROJENVNAME}_TEST_EXPLAIN': 'FALSE',${apikeyEnvEntry}
208
+ '${PROJENVNAME}_TEST_EXPLAIN': 'FALSE',${apikeyEnvEntry}${serverEnvEntry}
195
209
  });
196
210
 
197
211
  idmap = env['${PROJENVNAME}_TEST_${ENTENVNAME}_ENTID'];
@@ -200,9 +214,16 @@ Map<String, dynamic> basicSetup([dynamic extra]) {
200
214
 
201
215
  if (live) {
202
216
  client = ${model.Name}SDK(merge([
203
- <String, dynamic>{${apikeyLiveField}
217
+ // FIRST, so the generated fields below win: sdk-test-control.json's
218
+ // test.client.options adds to the live client, it does not redirect it.
219
+ liveClientOptions(),
220
+ <String, dynamic>{${apikeyLiveField}${serverLiveField}
204
221
  },
205
- extra
222
+ // 'extra ?? {}', not a bare 'extra': merge returns null when the last
223
+ // entry is null, and basicSetup is normally called with no argument at
224
+ // all - so a bare 'extra' silently discarded the apikey and server
225
+ // values above and handed the SDK null.
226
+ extra ?? <String, dynamic>{}
206
227
  ]));
207
228
  }
208
229
 
@@ -7,7 +7,8 @@ import type {
7
7
  ModelEntity
8
8
  } from '@voxgig/apidef'
9
9
 
10
- import { cmp, each, snakify, Folder, File, Content, entityCollection } from '@voxgig/sdkgen'
10
+ import { cmp, each, snakify, Folder, File, Content, entityCollection,
11
+ TestControl } from '@voxgig/sdkgen'
11
12
 
12
13
 
13
14
  import { TestDirect } from './TestDirect_dart'
@@ -24,6 +25,9 @@ const Test = cmp(function Test(props: any) {
24
25
 
25
26
  Folder({ name: 'test' }, () => {
26
27
 
28
+ // Write-once: a project's edited control file survives regeneration.
29
+ TestControl({ target, dir: 'test' })
30
+
27
31
  // Suite entry: registers every static suite plus the generated
28
32
  // per-entity suites, then runs them (Makefile: dart run test/main.dart).
29
33
  File({ name: 'main.' + target.ext }, () => {
@@ -22,7 +22,6 @@ import {
22
22
 
23
23
 
24
24
  import {
25
- clean,
26
25
  formatElixir,
27
26
  elixirString,
28
27
  } from './utility_elixir'
@@ -53,13 +52,6 @@ const Config = cmp(async function Config(props: any) {
53
52
  ? ` "auth" => %{"prefix" => ${elixirString(authPrefix)}},\n`
54
53
  : ''
55
54
 
56
- const entityClean = Object.values(entity).reduce((a: any, n: any) => (a[n.name] = clean({
57
- fields: n.fields,
58
- name: n.name,
59
- op: n.op,
60
- relations: n.relations,
61
- }, true), a), {})
62
-
63
55
  Folder({ name: 'lib' }, () => {
64
56
  // The same config as an OBJECT, built by the shared helper so this
65
57
  // target's literal and the data that replaces it above the threshold are
@@ -72,6 +64,15 @@ const Config = cmp(async function Config(props: any) {
72
64
  const { def: configDef, json: configJson } = configDefinition(model, target.name)
73
65
  const asData = isConfigData(configJson, configReprSetting(model))
74
66
 
67
+ // configDefinition's `def.entity` verbatim, NOT rebuilt here. The reduce
68
+ // this replaces was one of fourteen copies of that function's entityDefs
69
+ // loop, and when configDefinition started reconstructing a point's
70
+ // `parts` from apidef's segment vector (its ADR-003), only the copies
71
+ // that read `configDef` got it — this target's literal config emitted
72
+ // paths with no parts at all while its data config had them. One rule,
73
+ // one place.
74
+ const entityClean = configDef.entity
75
+
75
76
  File({ name: 'config.ex' }, () => {
76
77
 
77
78
  // ABOVE THE THRESHOLD: emit the model as DATA.
@@ -9,7 +9,6 @@ import {
9
9
  Line,
10
10
  cmp,
11
11
  each,
12
- clean,
13
12
  configDefinition,
14
13
  configReprSetting,
15
14
  isAuthActive,
@@ -200,12 +199,7 @@ ${serverBlock}${authBlock} "headers": ${formatGoMap(headers, 3)},
200
199
  Content(` },
201
200
  },
202
201
  "entity": ${formatGoMap(
203
- Object.values(entity).reduce((a: any, n: any) => (a[n.name] = clean({
204
- fields: n.fields,
205
- name: n.name,
206
- op: n.op,
207
- relations: n.relations,
208
- }, true), a), {}), 2)},
202
+ configDef.entity, 2)},
209
203
  }
210
204
  }
211
205
  `)
@@ -6,6 +6,7 @@ import {
6
6
  List, File, Content, Copy, Folder, Fragment, Line, FeatureHook,
7
7
  entityClassName, entityCollection, goModule, goPackageIdent,
8
8
  targetFeatures,
9
+ TEST_CONTROL_EXCLUDE
9
10
  } from '@voxgig/sdkgen'
10
11
 
11
12
 
@@ -59,7 +60,7 @@ const Main = cmp(async function Main(props: any) {
59
60
  // downstream consumers.
60
61
  Copy({
61
62
  from: 'tm/' + target.name,
62
- exclude: [/src\//, /utility\/struct\/go\.mod$/],
63
+ exclude: [/src\//, /utility\/struct\/go\.mod$/, TEST_CONTROL_EXCLUDE],
63
64
  replace: {
64
65
  ...props.ctx$.stdrep,
65
66
  GOMODULE: gomodule,
@@ -13,7 +13,8 @@ import {
13
13
  snakify,
14
14
  isAuthActive,
15
15
  serverVarEnv,
16
- serverVariables, envName, envToken, liveStrict
16
+ serverVariables, envName, envToken, liveStrict,
17
+ pointParts,
17
18
  } from '@voxgig/sdkgen'
18
19
 
19
20
 
@@ -109,7 +110,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
109
110
  // Get load point info
110
111
  const loadPoint = loadOp?.points?.[0]
111
112
  const loadIsGraphql = 'graphql' === (loadPoint as any)?.kind
112
- const loadPath = loadPoint ? normalizePathParams(loadPoint.parts || [], loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
113
+ const loadPath = loadPoint ? normalizePathParams(pointParts(loadPoint), loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
113
114
  const allLoadParams = loadPoint?.args?.params || []
114
115
  // Some upstream OpenAPI specs declare a parameter as `in: path` even when
115
116
  // that path has no `{name}` placeholder for it. Only path params that
@@ -117,7 +118,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
117
118
  // setup and URL-substitution asserts; otherwise the SDK silently drops
118
119
  // them and the URL-includes assert fails.
119
120
  const _pathPlaceholders = new Set<string>()
120
- for (const part of (loadPoint?.parts || [])) {
121
+ for (const part of pointParts(loadPoint)) {
121
122
  if (typeof part === 'string' && part.startsWith('{') && part.endsWith('}')) {
122
123
  _pathPlaceholders.add(part.slice(1, -1))
123
124
  }
@@ -136,7 +137,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
136
137
  // Get list point info
137
138
  const listPoint = listOp?.points?.[0]
138
139
  const listIsGraphql = 'graphql' === (listPoint as any)?.kind
139
- const listPath = listPoint ? normalizePathParams(listPoint.parts || [], listPoint?.args?.params || [], listPoint?.rename?.param) : ''
140
+ const listPath = listPoint ? normalizePathParams(pointParts(listPoint), listPoint?.args?.params || [], listPoint?.rename?.param) : ''
140
141
  const listParams = listPoint?.args?.params || []
141
142
 
142
143
  // Required query params with spec-provided examples — needed in live mode
@@ -3,7 +3,8 @@ import type {
3
3
  ModelEntity
4
4
  } from '@voxgig/apidef'
5
5
 
6
- import { cmp, each, Folder, File, Content, goModule, entityCollection } from '@voxgig/sdkgen'
6
+ import { cmp, each, Folder, File, Content, goModule, entityCollection,
7
+ TestControl } from '@voxgig/sdkgen'
7
8
 
8
9
 
9
10
  import { TestEntity } from './TestEntity_go'
@@ -21,6 +22,9 @@ const Test = cmp(function Test(props: any) {
21
22
 
22
23
  Folder({ name: 'test' }, () => {
23
24
 
25
+ // Write-once: a project's edited control file survives regeneration.
26
+ TestControl({ target, dir: 'test' })
27
+
24
28
  // Generate exists_test.go programmatically to avoid duplication
25
29
  // with any scaffolded test file (e.g. projectname_sdk_test.go).
26
30
  File({ name: 'exists_test.' + target.ext }, () => {
@@ -98,13 +98,13 @@ const Config = cmp(async function Config(props: any) {
98
98
  options.headers = headers
99
99
  options.entity = optionsEntity
100
100
 
101
- const entityConfig = Object.values(entity).reduce((a: any, n: any) => (
102
- a[n.name] = cleanModel({
103
- fields: n.fields,
104
- name: n.name,
105
- op: n.op,
106
- relations: n.relations,
107
- }, true), a), {})
101
+ // configDefinition's `def.entity` verbatim, NOT rebuilt here. This reduce
102
+ // was one of fourteen copies of that function's entityDefs loop, and when
103
+ // configDefinition started reconstructing a point's `parts` from apidef's
104
+ // segment vector (its ADR-003), only the copies that read `configDef` got
105
+ // it — this target's literal config emitted paths with no parts at all
106
+ // while its data config had them. One rule, one place.
107
+ const entityConfig = configDef.entity
108
108
 
109
109
  const config = {
110
110
  main: configDef.main,
@@ -5,6 +5,7 @@ import {
5
5
  cmp, each,
6
6
  File, Content, Copy, Folder, Fragment,
7
7
  targetFeatures,
8
+ TEST_CONTROL_EXCLUDE
8
9
  } from '@voxgig/sdkgen'
9
10
 
10
11
 
@@ -52,7 +53,7 @@ const Main = cmp(async function Main(props: any) {
52
53
  // token used throughout the templates (package/import statements).
53
54
  Copy({
54
55
  from: 'tm/' + target.name,
55
- exclude: [/src\//],
56
+ exclude: [/src\//, TEST_CONTROL_EXCLUDE],
56
57
  replace: {
57
58
  ...props.ctx$.stdrep,
58
59
  JAVAPACKAGE: javapackage,
@@ -13,7 +13,8 @@ import {
13
13
  snakify,
14
14
  isAuthActive,
15
15
  serverVarEnv,
16
- serverVariables, envName, envToken
16
+ serverVariables, envName, envToken,
17
+ pointParts,
17
18
  } from '@voxgig/sdkgen'
18
19
 
19
20
 
@@ -116,12 +117,12 @@ const TestDirect = cmp(function TestDirect(props: any) {
116
117
 
117
118
  // Get load point info
118
119
  const loadPoint = loadOp?.points?.[0]
119
- const loadPath = loadPoint ? normalizePathParams(loadPoint.parts || [], loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
120
+ const loadPath = loadPoint ? normalizePathParams(pointParts(loadPoint), loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
120
121
  const allLoadParams = loadPoint?.args?.params || []
121
122
  // Only path params that actually appear in the URL template drive
122
123
  // direct-test path-param setup and URL-substitution asserts.
123
124
  const _pathPlaceholders = new Set<string>()
124
- for (const part of (loadPoint?.parts || [])) {
125
+ for (const part of pointParts(loadPoint)) {
125
126
  if (typeof part === 'string' && part.startsWith('{') && part.endsWith('}')) {
126
127
  _pathPlaceholders.add(part.slice(1, -1))
127
128
  }
@@ -139,7 +140,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
139
140
 
140
141
  // Get list point info
141
142
  const listPoint = listOp?.points?.[0]
142
- const listPath = listPoint ? normalizePathParams(listPoint.parts || [], listPoint?.args?.params || [], listPoint?.rename?.param) : ''
143
+ const listPath = listPoint ? normalizePathParams(pointParts(listPoint), listPoint?.args?.params || [], listPoint?.rename?.param) : ''
143
144
  const listParams = listPoint?.args?.params || []
144
145
 
145
146
  // Required query params with spec-provided examples — needed in live mode.
@@ -3,7 +3,8 @@ import type {
3
3
  ModelEntity
4
4
  } from '@voxgig/apidef'
5
5
 
6
- import { cmp, each, Folder, entityCollection } from '@voxgig/sdkgen'
6
+ import { cmp, each, Folder, entityCollection,
7
+ TestControl } from '@voxgig/sdkgen'
7
8
 
8
9
 
9
10
  import { TestEntity } from './TestEntity_java'
@@ -20,6 +21,9 @@ const Test = cmp(function Test(props: any) {
20
21
 
21
22
  Folder({ name: 'test' }, () => {
22
23
 
24
+ // Write-once: a project's edited control file survives regeneration.
25
+ TestControl({ target, dir: 'test' })
26
+
23
27
  // The shared test infrastructure (RunnerSupport, FeatureHarness,
24
28
  // PipelineTest, FeatureTest, NetsimTest, PrimaryUtilityTest,
25
29
  // CustomUtilityTest, StructRunner/StructCorpusTest, ExistsTest and
@@ -8,7 +8,6 @@ import {
8
8
  Fragment,
9
9
  Line,
10
10
  cmp,
11
- clean,
12
11
  configDefinition,
13
12
  configReprSetting,
14
13
  each,
@@ -161,13 +160,13 @@ const Config = cmp(async function Config(props: any) {
161
160
  `)
162
161
  }),
163
162
 
164
- "'ENTITYMAP'": formatJson(Object.values(entity)
165
- .reduce((a: any, n: any) => (a[n.name] = clean({
166
- fields: n.fields,
167
- name: n.name,
168
- op: n.op,
169
- relations: n.relations,
170
- }, true), a), {}), { margin: 2 }).trim(),
163
+ // configDefinition's `def.entity` verbatim, NOT rebuilt here. This
164
+ // reduce was a second copy of that function's entityDefs loop, and
165
+ // when configDefinition started reconstructing a point's `parts`
166
+ // from apidef's segment vector (its ADR-003), only the data
167
+ // representation got it — the literal one emitted empty paths. The
168
+ // config-repr equivalence test caught it, which is what it is for.
169
+ "'ENTITYMAP'": formatJson(configDef.entity, { margin: 2 }).trim(),
171
170
  }
172
171
  })
173
172
  })
@@ -6,6 +6,7 @@ import {
6
6
  List, File, Content, Copy, Folder, Fragment, Line, FeatureHook,
7
7
  entityClassName, entityCollection, srcFeatureExcludes, stationLibrary,
8
8
  targetFeatures,
9
+ TEST_CONTROL_EXCLUDE
9
10
  } from '@voxgig/sdkgen'
10
11
 
11
12
 
@@ -49,7 +50,7 @@ const Main = cmp(async function Main(props: any) {
49
50
  from: 'tm/' + target.name,
50
51
  // Root copies src/feature/<name>/ per ACTIVE feature; keep this blanket
51
52
  // copy from restoring one that was switched off after `target add`.
52
- exclude: srcFeatureExcludes(model),
53
+ exclude: [...srcFeatureExcludes(model), TEST_CONTROL_EXCLUDE],
53
54
  replace: {
54
55
  ...props.ctx$.stdrep,
55
56
  }
@@ -17,7 +17,11 @@ import {
17
17
  cmp,
18
18
  snakify,
19
19
  isAuthActive,
20
- jsProp, envName, envToken
20
+ serverVarEnv,
21
+ serverVariables,
22
+ jsProp, envName, envToken,
23
+ jsKey,
24
+ pointParts,
21
25
  } from '@voxgig/sdkgen'
22
26
 
23
27
 
@@ -41,13 +45,32 @@ const TestDirect = cmp(function TestDirect(props: any) {
41
45
 
42
46
  const authActive = isAuthActive(model)
43
47
  const apikeyEnvEntry = authActive
44
- ? `\n '${PROJECTNAME}_APIKEY': 'NONE',`
48
+ ? `\n '${PROJECTNAME}_APIKEY': '',`
45
49
  : ''
46
50
  const apikeyLiveField = authActive
47
51
  ? `
48
52
  apikey: env.${PROJECTNAME}_APIKEY,`
49
53
  : ''
50
54
 
55
+ // A templated server URL (OpenAPI server variables) makes a LIVE client
56
+ // impossible to construct without values: makeOptions raises rather than
57
+ // request a URL with a literal `{account_id}` in it. Taken from the
58
+ // environment, the same way the apikey is.
59
+ //
60
+ // Keys are quoted and the env read is bracketed via jsKey/jsProp: a server
61
+ // variable name is spec-derived and need not be a JS identifier — the URL
62
+ // grammar admits a leading digit ({2fa}), and a declared-but-unreferenced
63
+ // variable ({edge-zone}) is not constrained at all. Bare `name:` and
64
+ // `env.PROJ_SERVER_EDGE-ZONE` are both syntax errors.
65
+ const svars = serverVariables(model)
66
+ const serverEnvEntry = svars
67
+ .map((v: any) => `\n '${serverVarEnv(PROJECTNAME, v.name)}': ${JSON.stringify(v.dflt)},`).join('')
68
+ const serverLiveField = 0 === svars.length ? '' : `
69
+ server: {${svars
70
+ .map((v: any) => `
71
+ ${jsKey(v.name)}: ${jsProp('env', serverVarEnv(PROJECTNAME, v.name))},`).join('')}
72
+ },`
73
+
51
74
  const opnames = Object.keys(entity.op || {})
52
75
  const hasLoad = opnames.includes('load')
53
76
  const hasList = opnames.includes('list')
@@ -78,14 +101,17 @@ function directSetup(mockres) {
78
101
 
79
102
  const env = envOverride({
80
103
  '${entidEnvVar}': {},
81
- '${PROJECTNAME}_TEST_LIVE': 'FALSE',${apikeyEnvEntry}
104
+ '${PROJECTNAME}_TEST_LIVE': 'FALSE',${apikeyEnvEntry}${serverEnvEntry}
82
105
  })
83
106
 
84
107
  const live = 'TRUE' === env.${PROJECTNAME}_TEST_LIVE
85
108
 
86
109
  if (live) {
87
- const client = new ${nom(model.const, 'Name')}SDK({${apikeyLiveField}
88
- })
110
+ // Merged so the generated fields win: sdk-test-control.json's
111
+ // test.client.options adds to the live client, it does not redirect it.
112
+ const client = new ${nom(model.const, 'Name')}SDK(
113
+ Object.assign({}, liveClientOptions(), {${apikeyLiveField}${serverLiveField}
114
+ }))
89
115
 
90
116
  let idmap = env['${entidEnvVar}']
91
117
  if ('string' === typeof idmap && idmap.startsWith('{')) {
@@ -147,13 +173,13 @@ function generateDirectLoad(model: Model, entity: ModelEntity) {
147
173
  }
148
174
 
149
175
  const loadParams = loadPoint.args?.params || []
150
- const loadPath = normalizePathParams(loadPoint.parts || [], loadParams, loadPoint.rename?.param)
176
+ const loadPath = normalizePathParams(pointParts(loadPoint), loadParams, loadPoint.rename?.param)
151
177
 
152
178
  // Get list info for live mode bootstrapping
153
179
  const listOp = entity.op?.list
154
180
  const listPoint = listOp?.points?.[0]
155
181
  const listParams = listPoint?.args?.params || []
156
- const listPath = listPoint ? normalizePathParams(listPoint.parts || [], listParams, listPoint.rename?.param) : ''
182
+ const listPath = listPoint ? normalizePathParams(pointParts(listPoint), listParams, listPoint.rename?.param) : ''
157
183
  const hasList = null != listPoint
158
184
 
159
185
  // Ancestor params (not 'id') for live mode
@@ -249,7 +275,7 @@ function generateDirectList(model: Model, entity: ModelEntity) {
249
275
  }
250
276
 
251
277
  const listParams = listPoint.args?.params || []
252
- const listPath = normalizePathParams(listPoint.parts || [], listParams, listPoint.rename?.param)
278
+ const listPath = normalizePathParams(pointParts(listPoint), listParams, listPoint.rename?.param)
253
279
 
254
280
  // Build live params
255
281
  const liveParams = listParams.map((p: any) => {