@voxgig/sdkgen 4.5.3 → 4.7.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 (248) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/action/check.js +15 -0
  3. package/dist/action/check.js.map +1 -1
  4. package/dist/cmp/AgentGuide.js +1 -1
  5. package/dist/cmp/AgentGuide.js.map +1 -1
  6. package/dist/cmp/AgentGuideContent.d.ts +1 -1
  7. package/dist/cmp/AgentGuideContent.js +8 -2
  8. package/dist/cmp/AgentGuideContent.js.map +1 -1
  9. package/dist/cmp/Feature.js +22 -0
  10. package/dist/cmp/Feature.js.map +1 -1
  11. package/dist/cmp/FeatureDocs.d.ts +1 -1
  12. package/dist/cmp/FeatureDocs.js +6 -1
  13. package/dist/cmp/FeatureDocs.js.map +1 -1
  14. package/dist/cmp/ReadmeExplanation.js +3 -1
  15. package/dist/cmp/ReadmeExplanation.js.map +1 -1
  16. package/dist/cmp/ReadmeFeatures.js +1 -1
  17. package/dist/cmp/ReadmeFeatures.js.map +1 -1
  18. package/dist/cmp/ReadmeRefFeatures.js +1 -1
  19. package/dist/cmp/ReadmeRefFeatures.js.map +1 -1
  20. package/dist/cmp/TestControl.d.ts +4 -0
  21. package/dist/cmp/TestControl.js +51 -0
  22. package/dist/cmp/TestControl.js.map +1 -0
  23. package/dist/helpers/applicability.d.ts +6 -0
  24. package/dist/helpers/applicability.js +103 -0
  25. package/dist/helpers/applicability.js.map +1 -0
  26. package/dist/helpers/collectDeps.js +4 -2
  27. package/dist/helpers/collectDeps.js.map +1 -1
  28. package/dist/helpers/featureSource.d.ts +3 -1
  29. package/dist/helpers/featureSource.js +105 -0
  30. package/dist/helpers/featureSource.js.map +1 -1
  31. package/dist/helpers/serverVars.d.ts +2 -1
  32. package/dist/helpers/serverVars.js +17 -0
  33. package/dist/helpers/serverVars.js.map +1 -1
  34. package/dist/sdkgen.d.ts +5 -3
  35. package/dist/sdkgen.js +16 -4
  36. package/dist/sdkgen.js.map +1 -1
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/dist/utility.d.ts +2 -1
  39. package/dist/utility.js +59 -1
  40. package/dist/utility.js.map +1 -1
  41. package/model/sdkgen.aon +97 -0
  42. package/package.json +1 -1
  43. package/project/.sdk/model/feature/feature-index.aon +1 -0
  44. package/project/.sdk/model/feature/secrets.aon +200 -0
  45. package/project/.sdk/model/target/ts.aon +8 -1
  46. package/project/.sdk/src/cmp/c/Config_c.ts +5 -1
  47. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +5 -1
  48. package/project/.sdk/src/cmp/cpp/Main_cpp.ts +2 -1
  49. package/project/.sdk/src/cmp/cpp/Test_cpp.ts +5 -1
  50. package/project/.sdk/src/cmp/csharp/Config_csharp.ts +5 -1
  51. package/project/.sdk/src/cmp/csharp/Main_csharp.ts +2 -1
  52. package/project/.sdk/src/cmp/csharp/TestDirect_csharp.ts +30 -6
  53. package/project/.sdk/src/cmp/csharp/TestEntity_csharp.ts +29 -5
  54. package/project/.sdk/src/cmp/csharp/Test_csharp.ts +5 -1
  55. package/project/.sdk/src/cmp/dart/Config_dart.ts +5 -1
  56. package/project/.sdk/src/cmp/dart/Main_dart.ts +2 -1
  57. package/project/.sdk/src/cmp/dart/TestDirect_dart.ts +23 -6
  58. package/project/.sdk/src/cmp/dart/TestEntity_dart.ts +28 -7
  59. package/project/.sdk/src/cmp/dart/Test_dart.ts +5 -1
  60. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +5 -1
  61. package/project/.sdk/src/cmp/elixir/Main_elixir.ts +5 -1
  62. package/project/.sdk/src/cmp/go/Config_go.ts +5 -1
  63. package/project/.sdk/src/cmp/go/Main_go.ts +8 -3
  64. package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +16 -3
  65. package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +16 -3
  66. package/project/.sdk/src/cmp/go/TestDirect_go.ts +28 -4
  67. package/project/.sdk/src/cmp/go/TestEntity_go.ts +43 -10
  68. package/project/.sdk/src/cmp/go/Test_go.ts +5 -1
  69. package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +5 -5
  70. package/project/.sdk/src/cmp/java/Config_java.ts +22 -1
  71. package/project/.sdk/src/cmp/java/Main_java.ts +7 -2
  72. package/project/.sdk/src/cmp/java/ReadmeQuick_java.ts +17 -3
  73. package/project/.sdk/src/cmp/java/ReadmeTopQuick_java.ts +17 -3
  74. package/project/.sdk/src/cmp/java/TestDirect_java.ts +23 -4
  75. package/project/.sdk/src/cmp/java/TestEntity_java.ts +29 -4
  76. package/project/.sdk/src/cmp/java/Test_java.ts +5 -1
  77. package/project/.sdk/src/cmp/js/Config_js.ts +5 -1
  78. package/project/.sdk/src/cmp/js/Main_js.ts +7 -2
  79. package/project/.sdk/src/cmp/js/Package_js.ts +4 -1
  80. package/project/.sdk/src/cmp/js/TestDirect_js.ts +30 -5
  81. package/project/.sdk/src/cmp/js/TestEntity_js.ts +35 -5
  82. package/project/.sdk/src/cmp/js/Test_js.ts +5 -1
  83. package/project/.sdk/src/cmp/js/fragment/Direct.test.fragment.js +7 -1
  84. package/project/.sdk/src/cmp/js/fragment/Entity.test.fragment.js +7 -1
  85. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +5 -1
  86. package/project/.sdk/src/cmp/kotlin/Main_kotlin.ts +2 -1
  87. package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +5 -1
  88. package/project/.sdk/src/cmp/lean/Config_lean.ts +5 -1
  89. package/project/.sdk/src/cmp/lua/Config_lua.ts +5 -1
  90. package/project/.sdk/src/cmp/lua/Main_lua.ts +7 -2
  91. package/project/.sdk/src/cmp/lua/Package_lua.ts +4 -1
  92. package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +28 -4
  93. package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +24 -4
  94. package/project/.sdk/src/cmp/lua/Test_lua.ts +5 -1
  95. package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +5 -1
  96. package/project/.sdk/src/cmp/ocaml/Gitignore_ocaml.ts +1 -0
  97. package/project/.sdk/src/cmp/perl/Main_perl.ts +7 -2
  98. package/project/.sdk/src/cmp/perl/TestDirect_perl.ts +25 -4
  99. package/project/.sdk/src/cmp/perl/TestEntity_perl.ts +24 -4
  100. package/project/.sdk/src/cmp/perl/Test_perl.ts +5 -1
  101. package/project/.sdk/src/cmp/perl/utility_perl.ts +10 -0
  102. package/project/.sdk/src/cmp/php/Config_php.ts +5 -1
  103. package/project/.sdk/src/cmp/php/Main_php.ts +7 -2
  104. package/project/.sdk/src/cmp/php/TestDirect_php.ts +24 -5
  105. package/project/.sdk/src/cmp/php/TestEntity_php.ts +28 -4
  106. package/project/.sdk/src/cmp/php/Test_php.ts +5 -1
  107. package/project/.sdk/src/cmp/py/Config_py.ts +5 -1
  108. package/project/.sdk/src/cmp/py/Main_py.ts +7 -2
  109. package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +16 -3
  110. package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +16 -3
  111. package/project/.sdk/src/cmp/py/TestDirect_py.ts +24 -5
  112. package/project/.sdk/src/cmp/py/TestEntity_py.ts +23 -3
  113. package/project/.sdk/src/cmp/py/Test_py.ts +5 -1
  114. package/project/.sdk/src/cmp/rb/Config_rb.ts +5 -1
  115. package/project/.sdk/src/cmp/rb/Main_rb.ts +7 -2
  116. package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +24 -5
  117. package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +24 -4
  118. package/project/.sdk/src/cmp/rb/Test_rb.ts +5 -1
  119. package/project/.sdk/src/cmp/rb/utility_rb.ts +3 -1
  120. package/project/.sdk/src/cmp/rust/Config_rust.ts +5 -1
  121. package/project/.sdk/src/cmp/rust/Main_rust.ts +7 -2
  122. package/project/.sdk/src/cmp/rust/TestDirect_rust.ts +31 -4
  123. package/project/.sdk/src/cmp/rust/TestEntity_rust.ts +37 -4
  124. package/project/.sdk/src/cmp/rust/Test_rust.ts +5 -1
  125. package/project/.sdk/src/cmp/scala/Config_scala.ts +5 -1
  126. package/project/.sdk/src/cmp/scala/Main_scala.ts +7 -2
  127. package/project/.sdk/src/cmp/scala/Test_scala.ts +5 -1
  128. package/project/.sdk/src/cmp/swift/Config_swift.ts +5 -1
  129. package/project/.sdk/src/cmp/ts/Config_ts.ts +55 -9
  130. package/project/.sdk/src/cmp/ts/Main_ts.ts +67 -3
  131. package/project/.sdk/src/cmp/ts/Package_ts.ts +4 -1
  132. package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +15 -3
  133. package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +18 -5
  134. package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +19 -5
  135. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +75 -8
  136. package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +36 -5
  137. package/project/.sdk/src/cmp/ts/Test_ts.ts +5 -1
  138. package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +9 -2
  139. package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +9 -2
  140. package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +7 -0
  141. package/project/.sdk/src/cmp/zig/Config_zig.ts +5 -1
  142. package/project/.sdk/tm/c/utility/make_options.c +22 -0
  143. package/project/.sdk/tm/clojure/src/sdk/core.clj +18 -1
  144. package/project/.sdk/tm/cpp/utility/pipeline.hpp +23 -0
  145. package/project/.sdk/tm/csharp/test/CustomUtilityTest.cs +15 -0
  146. package/project/.sdk/tm/csharp/test/Runner.cs +45 -0
  147. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +23 -0
  148. package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +22 -0
  149. package/project/.sdk/tm/dart/test/utility.dart +39 -0
  150. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +19 -0
  151. package/project/.sdk/tm/go/core/context.go +1 -1
  152. package/project/.sdk/tm/go/feature/cost_feature.go +1 -1
  153. package/project/.sdk/tm/go/feature/paging_feature.go +5 -5
  154. package/project/.sdk/tm/go/feature/test_feature.go +12 -5
  155. package/project/.sdk/tm/go/test/custom_utility_test.go +15 -0
  156. package/project/.sdk/tm/go/test/feature_corpus_test.go +7 -0
  157. package/project/.sdk/tm/go/test/runner_test.go +46 -0
  158. package/project/.sdk/tm/go/test/struct_runner_test.go +7 -7
  159. package/project/.sdk/tm/go/test/struct_utility_test.go +63 -27
  160. package/project/.sdk/tm/go/utility/fetcher.go +2 -2
  161. package/project/.sdk/tm/go/utility/make_options.go +24 -5
  162. package/project/.sdk/tm/go/utility/make_point.go +1 -1
  163. package/project/.sdk/tm/go/utility/make_spec.go +17 -1
  164. package/project/.sdk/tm/go/utility/prepare_auth.go +1 -1
  165. package/project/.sdk/tm/go/utility/struct/voxgigstruct.go +370 -307
  166. package/project/.sdk/tm/go/utility/transform_request.go +29 -1
  167. package/project/.sdk/tm/go/utility/transform_response.go +20 -1
  168. package/project/.sdk/tm/java/test/CustomUtilityTest.java +10 -0
  169. package/project/.sdk/tm/java/test/FeatureCorpusTest.java +7 -0
  170. package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +6 -0
  171. package/project/.sdk/tm/java/test/RunnerSupport.java +43 -0
  172. package/project/.sdk/tm/java/utility/MakeOptions.java +84 -1
  173. package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +24 -0
  174. package/project/.sdk/tm/js/src/utility/StructUtility.js +2076 -1837
  175. package/project/.sdk/tm/js/test/feature/Corpus.test.js +9 -0
  176. package/project/.sdk/tm/js/test/sdk-test-control.json +19 -0
  177. package/project/.sdk/tm/js/test/utility.js +91 -1
  178. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +21 -0
  179. package/project/.sdk/tm/lean/src/SdkUtility.lean +12 -1
  180. package/project/.sdk/tm/lua/test/runner.lua +40 -0
  181. package/project/.sdk/tm/ocaml/sdk_runtime.ml +11 -0
  182. package/project/.sdk/tm/ocaml/test/harness.ml +14 -4
  183. package/project/.sdk/tm/perl/t/runner.pm +30 -0
  184. package/project/.sdk/tm/perl/utility/make_options.pm +18 -0
  185. package/project/.sdk/tm/php/test/FeatureCorpusTest.php +10 -1
  186. package/project/.sdk/tm/php/test/Runner.php +36 -0
  187. package/project/.sdk/tm/php/utility/MakeOptions.php +24 -0
  188. package/project/.sdk/tm/py/pkg/utility/make_options.py +20 -0
  189. package/project/.sdk/tm/py/test/runner.py +32 -0
  190. package/project/.sdk/tm/py/test/test_feature_corpus.py +10 -1
  191. package/project/.sdk/tm/rb/test/feature_corpus_test.rb +9 -1
  192. package/project/.sdk/tm/rb/test/runner.rb +25 -0
  193. package/project/.sdk/tm/rb/utility/make_options.rb +17 -0
  194. package/project/.sdk/tm/rust/tests/common/mod.rs +32 -0
  195. package/project/.sdk/tm/rust/utility/make_options.rs +26 -1
  196. package/project/.sdk/tm/scala/utility/Make.scala +17 -0
  197. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +21 -0
  198. package/project/.sdk/tm/ts/src/feature/secrets/SecretsFeature.ts +419 -0
  199. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +387 -0
  200. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +175 -0
  201. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +51 -0
  202. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/Registry.ts +90 -0
  203. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/addr.ts +48 -0
  204. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/aws.ts +219 -0
  205. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/azuresecrets.ts +144 -0
  206. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/boru.ts +113 -0
  207. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/doppler.ts +77 -0
  208. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/dotenv.ts +51 -0
  209. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/env.ts +31 -0
  210. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/file.ts +54 -0
  211. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/gcpsecrets.ts +118 -0
  212. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/hashicorp.ts +159 -0
  213. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/http.ts +66 -0
  214. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/infisical.ts +107 -0
  215. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/memory.ts +33 -0
  216. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/onepassword.ts +120 -0
  217. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/support.ts +176 -0
  218. package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +18 -0
  219. package/project/.sdk/tm/ts/src/utility/StructUtility.ts +1321 -710
  220. package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +9 -0
  221. package/project/.sdk/tm/ts/test/feature/secrets/Secrets.test.ts +764 -0
  222. package/project/.sdk/tm/ts/test/omni.test.ts +84 -0
  223. package/project/.sdk/tm/ts/test/omni.ts +86 -0
  224. package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +6 -2
  225. package/project/.sdk/tm/ts/test/utility/StructUtility.test.ts +2 -2
  226. package/project/.sdk/tm/ts/test/utility.ts +122 -0
  227. package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +694 -0
  228. package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +244 -0
  229. package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +188 -0
  230. package/project/.sdk/tm/ts/test/vendor/omni/index.ts +38 -0
  231. package/project/.sdk/tm/zig/core/utility.zig +24 -1
  232. package/project/sdkgen-package.json +2 -1
  233. package/src/action/check.ts +18 -0
  234. package/src/cmp/AgentGuide.ts +1 -1
  235. package/src/cmp/AgentGuideContent.ts +9 -2
  236. package/src/cmp/Feature.ts +24 -0
  237. package/src/cmp/FeatureDocs.ts +7 -1
  238. package/src/cmp/ReadmeExplanation.ts +4 -1
  239. package/src/cmp/ReadmeFeatures.ts +1 -1
  240. package/src/cmp/ReadmeRefFeatures.ts +1 -1
  241. package/src/cmp/TestControl.ts +64 -0
  242. package/src/helpers/applicability.ts +123 -0
  243. package/src/helpers/collectDeps.ts +5 -1
  244. package/src/helpers/featureSource.ts +130 -0
  245. package/src/helpers/serverVars.ts +19 -0
  246. package/src/sdkgen.ts +16 -1
  247. package/src/utility.ts +71 -1
  248. package/project/.sdk/tm/ts/test/runner.ts +0 -425
@@ -30,7 +30,11 @@ import {
30
30
  cmp,
31
31
  each,
32
32
  isAuthActive,
33
- entityDataIdField, envName, envToken
33
+ serverVarEnv,
34
+ serverVariables,
35
+ entityDataIdField, envName, envToken,
36
+ jsKey,
37
+ jsProp
34
38
  } from '@voxgig/sdkgen'
35
39
 
36
40
 
@@ -63,13 +67,32 @@ const TestEntity = cmp(function TestEntity(props: any) {
63
67
  const ENTENVNAME = envToken(entity.name)
64
68
  const authActive = isAuthActive(model)
65
69
  const apikeyEnvEntry = authActive
66
- ? `\n '${PROJENVNAME}_APIKEY': 'NONE',`
70
+ ? `\n '${PROJENVNAME}_APIKEY': '',`
67
71
  : ''
68
72
  const apikeyLiveField = authActive
69
73
  ? `
70
74
  apikey: env.${PROJENVNAME}_APIKEY,`
71
75
  : ''
72
76
 
77
+ // A templated server URL (OpenAPI server variables) makes a LIVE client
78
+ // impossible to construct without values: makeOptions raises rather than
79
+ // request a URL with a literal `{account_id}` in it. So the live suite
80
+ // takes them from the environment the same way it takes the apikey.
81
+ //
82
+ // Keys are quoted and the env read is bracketed via jsKey/jsProp: a server
83
+ // variable name is spec-derived and need not be a JS identifier — the URL
84
+ // grammar admits a leading digit ({2fa}), and a declared-but-unreferenced
85
+ // variable ({edge-zone}) is not constrained at all. Bare `name:` and
86
+ // `env.PROJ_SERVER_EDGE-ZONE` are both syntax errors.
87
+ const svars = serverVariables(model)
88
+ const serverEnvEntry = svars
89
+ .map((v: any) => `\n '${serverVarEnv(PROJENVNAME, v.name)}': ${JSON.stringify(v.dflt)},`).join('')
90
+ const serverLiveField = 0 === svars.length ? '' : `
91
+ server: {${svars
92
+ .map((v: any) => `
93
+ ${jsKey(v.name)}: ${jsProp('env', serverVarEnv(PROJENVNAME, v.name))},`).join('')}
94
+ },`
95
+
73
96
  // TODO: should be a utility function
74
97
  const ff = projectPath('src/cmp/js/fragment/')
75
98
 
@@ -143,16 +166,23 @@ function basicSetup(extra) {
143
166
  const env = envOverride({
144
167
  '${PROJENVNAME}_TEST_${ENTENVNAME}_ENTID': idmap,
145
168
  '${PROJENVNAME}_TEST_LIVE': 'FALSE',
146
- '${PROJENVNAME}_TEST_EXPLAIN': 'FALSE',${apikeyEnvEntry}
169
+ '${PROJENVNAME}_TEST_EXPLAIN': 'FALSE',${apikeyEnvEntry}${serverEnvEntry}
147
170
  })
148
171
 
149
172
  idmap = env['${PROJENVNAME}_TEST_${ENTENVNAME}_ENTID']
150
173
 
151
174
  if ('TRUE' === env.${PROJENVNAME}_TEST_LIVE) {
152
175
  client = new ${model.Name}SDK(merge([
153
- {${apikeyLiveField}
176
+ // FIRST, so the generated fields below win: sdk-test-control.json's
177
+ // test.client.options adds to the live client, it does not redirect it.
178
+ liveClientOptions(),
179
+ {${apikeyLiveField}${serverLiveField}
154
180
  },
155
- extra
181
+ // 'extra || {}', not a bare 'extra': struct.merge returns UNDEFINED when
182
+ // the last entry is undefined, and basicSetup is normally called with no
183
+ // argument at all - so a bare 'extra' silently discarded the apikey and
184
+ // server values above and handed the SDK undefined.
185
+ extra || {}
156
186
  ]))
157
187
  }
158
188
 
@@ -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 { TestDirect } from './TestDirect_js'
@@ -16,6 +17,9 @@ const Test = cmp(function Test(props: any) {
16
17
 
17
18
  Folder({ name: 'test' }, () => {
18
19
 
20
+ // Write-once: a project's edited control file survives regeneration.
21
+ TestControl({ target, dir: 'test' })
22
+
19
23
  Folder({ name: 'entity' }, () => {
20
24
  const entity = each(entityCollection(model))
21
25
  .filter((e: any) => false !== e.active)
@@ -2,7 +2,7 @@
2
2
  const envlocal = __dirname + '/../../../.env.local'
3
3
  require('dotenv').config({ quiet: true, path: [envlocal] })
4
4
 
5
- const { test, describe } = require('node:test')
5
+ const { test, describe, afterEach } = require('node:test')
6
6
  const assert = require('node:assert')
7
7
 
8
8
 
@@ -10,11 +10,17 @@ const { ProjectNameSDK } = require('../../..')
10
10
 
11
11
  const {
12
12
  envOverride,
13
+ liveClientOptions,
14
+ liveDelay,
13
15
  } = require('../../utility')
14
16
 
15
17
 
16
18
  describe('EntityNameDirect', async () => {
17
19
 
20
+ // Per-test live pacing. Delay is read from sdk-test-control.json's
21
+ // `test.live.delayMs`; only sleeps when PROJECTENV_TEST_LIVE=TRUE.
22
+ afterEach(liveDelay('PROJECTENV_TEST_LIVE'))
23
+
18
24
  test('direct-exists', async () => {
19
25
  const sdk = new ProjectNameSDK({
20
26
  // Concrete base: a live construction must satisfy any server
@@ -5,7 +5,7 @@ require('dotenv').config({ quiet: true, path: [envlocal] })
5
5
  const Path = require('node:path')
6
6
  const Fs = require('node:fs')
7
7
 
8
- const { test, describe } = require('node:test')
8
+ const { test, describe, afterEach } = require('node:test')
9
9
  const assert = require('node:assert')
10
10
 
11
11
 
@@ -13,6 +13,8 @@ const { ProjectNameSDK, BaseFeature, stdutil, config } = require('../../..')
13
13
 
14
14
  const {
15
15
  envOverride,
16
+ liveClientOptions,
17
+ liveDelay,
16
18
  makeCtrl,
17
19
  makeMatch,
18
20
  makeReqdata,
@@ -23,6 +25,10 @@ const {
23
25
 
24
26
  describe('EntityNameEntity', async () => {
25
27
 
28
+ // Per-test live pacing. Delay is read from sdk-test-control.json's
29
+ // `test.live.delayMs`; only sleeps when PROJECTENV_TEST_LIVE=TRUE.
30
+ afterEach(liveDelay('PROJECTENV_TEST_LIVE'))
31
+
26
32
  test('instance', async () => {
27
33
  const testsdk = ProjectNameSDK.test()
28
34
  const ent = testsdk.EntityName()
@@ -7,6 +7,7 @@ import {
7
7
  each,
8
8
  isAuthActive,
9
9
  resolveAuthPrefix,
10
+ targetFeatures,
10
11
  } from '@voxgig/sdkgen'
11
12
 
12
13
 
@@ -36,7 +37,10 @@ const Config = cmp(async function Config(props: any) {
36
37
  const kotlinpackage = kotlinPackage(model)
37
38
 
38
39
  const entity = getModelPath(model, `main.${KIT}.entity`)
39
- const feature = getModelPath(model, `main.${KIT}.feature`)
40
+ // Gated by the applicability tags, so this target never imports or
41
+ // registers a feature it has no source for. One rule, one place:
42
+ // helpers/applicability.
43
+ const feature = targetFeatures(model, target)
40
44
 
41
45
  const headers = getModelPath(model, `main.${KIT}.config.headers`) || {}
42
46
 
@@ -4,6 +4,7 @@ import * as Path from 'node:path'
4
4
  import {
5
5
  cmp, each,
6
6
  File, Copy, Folder, Fragment,
7
+ TEST_CONTROL_EXCLUDE
7
8
  } from '@voxgig/sdkgen'
8
9
 
9
10
 
@@ -47,7 +48,7 @@ const Main = cmp(async function Main(props: any) {
47
48
  // token used throughout the templates (package/import statements).
48
49
  Copy({
49
50
  from: 'tm/' + target.name,
50
- exclude: [/src\//],
51
+ exclude: [/src\//, TEST_CONTROL_EXCLUDE],
51
52
  replace: {
52
53
  ...props.ctx$.stdrep,
53
54
  KOTLINPACKAGE: kotlinpackage,
@@ -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_kotlin'
@@ -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
@@ -5,6 +5,7 @@ import {
5
5
  each,
6
6
  isAuthActive,
7
7
  resolveAuthPrefix,
8
+ targetFeatures,
8
9
  } from '@voxgig/sdkgen'
9
10
 
10
11
  import {
@@ -29,7 +30,10 @@ const Config = cmp(async function Config(props: any) {
29
30
  const model: Model = ctx$.model
30
31
 
31
32
  const entity = getModelPath(model, `main.${KIT}.entity`)
32
- const feature = getModelPath(model, `main.${KIT}.feature`)
33
+ // Gated by the applicability tags, so this target never imports or
34
+ // registers a feature it has no source for. One rule, one place:
35
+ // helpers/applicability.
36
+ const feature = targetFeatures(model, target)
33
37
  const headers = getModelPath(model, `main.${KIT}.config.headers`) || {}
34
38
 
35
39
  const authActive = isAuthActive(model)
@@ -15,6 +15,7 @@ import {
15
15
  isConfigData,
16
16
  resolveAuthPrefix,
17
17
  serverVariables,
18
+ targetFeatures,
18
19
  } from '@voxgig/sdkgen'
19
20
 
20
21
 
@@ -40,7 +41,10 @@ const Config = cmp(async function Config(props: any) {
40
41
  const model: Model = ctx$.model
41
42
 
42
43
  const entity = getModelPath(model, `main.${KIT}.entity`)
43
- const feature = getModelPath(model, `main.${KIT}.feature`)
44
+ // Gated by the applicability tags, so this target never imports or
45
+ // registers a feature it has no source for. One rule, one place:
46
+ // helpers/applicability.
47
+ const feature = targetFeatures(model, target)
44
48
 
45
49
  const headers = getModelPath(model, `main.${KIT}.config.headers`) || {}
46
50
 
@@ -4,6 +4,8 @@ import * as Path from 'node:path'
4
4
  import {
5
5
  cmp, each, names, cmap,
6
6
  List, File, Content, Copy, Folder, Fragment, Line, FeatureHook,
7
+ targetFeatures,
8
+ TEST_CONTROL_EXCLUDE
7
9
  } from '@voxgig/sdkgen'
8
10
 
9
11
 
@@ -31,7 +33,10 @@ const Main = cmp(async function Main(props: any) {
31
33
  const { model } = props.ctx$
32
34
 
33
35
  const entity: ModelEntity = getModelPath(model, `main.${KIT}.entity`)
34
- const feature = getModelPath(model, `main.${KIT}.feature`)
36
+ // Gated by the applicability tags, so this target never imports or
37
+ // registers a feature it has no source for. One rule, one place:
38
+ // helpers/applicability.
39
+ const feature = targetFeatures(model, target)
35
40
 
36
41
  Package({ target })
37
42
 
@@ -40,7 +45,7 @@ const Main = cmp(async function Main(props: any) {
40
45
  // Copy tm/lua files with replacements
41
46
  Copy({
42
47
  from: 'tm/' + target.name,
43
- exclude: [/src\//],
48
+ exclude: [/src\//, TEST_CONTROL_EXCLUDE],
44
49
  replace: {
45
50
  ...props.ctx$.stdrep,
46
51
  }
@@ -9,6 +9,7 @@ import {
9
9
  keywords,
10
10
  repoInfo, packageName,
11
11
  packageVersion,
12
+ targetFeatures,
12
13
  } from '@voxgig/sdkgen'
13
14
 
14
15
 
@@ -78,7 +79,9 @@ dependencies = {
78
79
  // factory falls back to. The station feature additionally carries the
79
80
  // VENDORED voxgig_station library beside its adapter (no voxgig-station
80
81
  // rock exists to depend on - station design 9.2's registry-less tier).
81
- const feature = getModelPath(model, `main.${KIT}.feature`)
82
+ // Gated: a feature that does not apply to lua must not be listed
83
+ // as a rockspec module — the require would not resolve.
84
+ const feature = targetFeatures(model, target)
82
85
  let featureModules = ` ["feature.base_feature"] = "feature/base_feature.lua",\n`
83
86
  each(feature, (f: any) => {
84
87
  featureModules +=
@@ -11,9 +11,13 @@ import {
11
11
  File,
12
12
  cmp,
13
13
  snakify,
14
- isAuthActive, envName, envToken
14
+ isAuthActive, envName, envToken,
15
+ serverVarEnv,
16
+ serverVariables
15
17
  } from '@voxgig/sdkgen'
16
18
 
19
+ import { formatLuaValue } from './utility_lua'
20
+
17
21
 
18
22
  function normalizePathParams(
19
23
  parts: string[],
@@ -64,12 +68,25 @@ const TestDirect = cmp(function TestDirect(props: any) {
64
68
 
65
69
  const authActive = isAuthActive(model)
66
70
  const apikeyEnvEntry = authActive
67
- ? `\n ["${PROJECTNAME}_APIKEY"] = "NONE",`
71
+ ? `\n ["${PROJECTNAME}_APIKEY"] = "",`
68
72
  : ''
69
73
  const apikeyLiveField = authActive
70
74
  ? `\n apikey = env["${PROJECTNAME}_APIKEY"],`
71
75
  : ''
72
76
 
77
+ // A templated server URL (OpenAPI server variables) makes a LIVE client
78
+ // impossible to construct without values: makeOptions raises rather than
79
+ // request a URL with a literal `{account_id}` in it. So the live suite
80
+ // takes them from the environment the same way it takes the apikey.
81
+ const svars = serverVariables(model)
82
+ const serverEnvEntry = svars
83
+ .map((v: any) => `\n ["${serverVarEnv(PROJECTNAME, v.name)}"] = ${formatLuaValue(v.dflt)},`).join('')
84
+ const serverLiveField = 0 === svars.length ? '' : `
85
+ server = {${svars
86
+ .map((v: any) => `
87
+ ["${v.name}"] = env["${serverVarEnv(PROJECTNAME, v.name)}"],`).join('')}
88
+ },`
89
+
73
90
  const opnames = Object.keys(entity.op || {})
74
91
  const hasLoad = opnames.includes('load')
75
92
  const hasList = opnames.includes('list')
@@ -350,14 +367,21 @@ function ${entity.name}_direct_setup(mockres)
350
367
 
351
368
  local env = runner.env_override({
352
369
  ["${entidEnvVar}"] = {},
353
- ["${PROJECTNAME}_TEST_LIVE"] = "FALSE",${apikeyEnvEntry}
370
+ ["${PROJECTNAME}_TEST_LIVE"] = "FALSE",${apikeyEnvEntry}${serverEnvEntry}
354
371
  })
355
372
 
356
373
  local live = env["${PROJECTNAME}_TEST_LIVE"] == "TRUE"
357
374
 
358
375
  if live then
359
- local merged_opts = {${apikeyLiveField}
376
+ local merged_opts = {${apikeyLiveField}${serverLiveField}
360
377
  }
378
+ -- sdk-test-control.json's test.client.options goes UNDER the generated
379
+ -- fields: it adds to the live client, it does not redirect it.
380
+ for _k, _v in pairs(runner.live_client_options()) do
381
+ if merged_opts[_k] == nil then
382
+ merged_opts[_k] = _v
383
+ end
384
+ end
361
385
  local client = sdk.new(merged_opts)
362
386
  return {
363
387
  client = client,
@@ -24,9 +24,13 @@ import {
24
24
  buildIdNames,
25
25
  getMatchEntries,
26
26
  isAuthActive,
27
- entityDataIdField, envName, envToken
27
+ entityDataIdField, envName, envToken,
28
+ serverVarEnv,
29
+ serverVariables
28
30
  } from '@voxgig/sdkgen'
29
31
 
32
+ import { formatLuaValue } from './utility_lua'
33
+
30
34
 
31
35
  // See TestEntity_ts.ts for the GenCtx/OpGen contract.
32
36
  type GenCtx = {
@@ -62,12 +66,25 @@ const TestEntity = cmp(function TestEntity(props: any) {
62
66
 
63
67
  const authActive = isAuthActive(model)
64
68
  const apikeyEnvEntry = authActive
65
- ? `\n ["${PROJUPPER}_APIKEY"] = "NONE",`
69
+ ? `\n ["${PROJUPPER}_APIKEY"] = "",`
66
70
  : ''
67
71
  const apikeyLiveField = authActive
68
72
  ? `\n apikey = env["${PROJUPPER}_APIKEY"],`
69
73
  : ''
70
74
 
75
+ // A templated server URL (OpenAPI server variables) makes a LIVE client
76
+ // impossible to construct without values: makeOptions raises rather than
77
+ // request a URL with a literal `{account_id}` in it. So the live suite
78
+ // takes them from the environment the same way it takes the apikey.
79
+ const svars = serverVariables(model)
80
+ const serverEnvEntry = svars
81
+ .map((v: any) => `\n ["${serverVarEnv(PROJUPPER, v.name)}"] = ${formatLuaValue(v.dflt)},`).join('')
82
+ const serverLiveField = 0 === svars.length ? '' : `
83
+ server = {${svars
84
+ .map((v: any) => `
85
+ ["${v.name}"] = env["${serverVarEnv(PROJUPPER, v.name)}"],`).join('')}
86
+ },`
87
+
71
88
  const idnames = buildIdNames(entity, basicflow)
72
89
  const idnamesStr = idnames.map(n => `"${n}"`).join(', ')
73
90
 
@@ -241,7 +258,7 @@ end)
241
258
  local env = runner.env_override({
242
259
  ["${PROJUPPER}_TEST_${envToken(entity.name)}_ENTID"] = idmap,
243
260
  ["${PROJUPPER}_TEST_LIVE"] = "FALSE",
244
- ["${PROJUPPER}_TEST_EXPLAIN"] = "FALSE",${apikeyEnvEntry}
261
+ ["${PROJUPPER}_TEST_EXPLAIN"] = "FALSE",${apikeyEnvEntry}${serverEnvEntry}
245
262
  })
246
263
 
247
264
  local idmap_resolved = helpers.to_map(
@@ -262,7 +279,10 @@ end)
262
279
  Content(`
263
280
  if env["${PROJUPPER}_TEST_LIVE"] == "TRUE" then
264
281
  local merged_opts = vs.merge({
265
- {${apikeyLiveField}
282
+ -- FIRST, so the generated fields below win: sdk-test-control.json's
283
+ -- test.client.options adds to the live client, it does not redirect it.
284
+ runner.live_client_options(),
285
+ {${apikeyLiveField}${serverLiveField}
266
286
  },
267
287
  extra or {},
268
288
  })
@@ -3,7 +3,8 @@ import type {
3
3
  ModelEntity
4
4
  } from '@voxgig/apidef'
5
5
 
6
- import { cmp, each, Folder, File, Content, entityCollection } from '@voxgig/sdkgen'
6
+ import { cmp, each, Folder, File, Content, entityCollection,
7
+ TestControl } from '@voxgig/sdkgen'
7
8
 
8
9
 
9
10
  import { TestEntity } from './TestEntity_lua'
@@ -17,6 +18,9 @@ const Test = cmp(function Test(props: any) {
17
18
 
18
19
  Folder({ name: 'test' }, () => {
19
20
 
21
+ // Write-once: a project's edited control file survives regeneration.
22
+ TestControl({ target, dir: 'test' })
23
+
20
24
  // Generate exists test
21
25
  File({ name: 'exists_test.' + target.ext }, () => {
22
26
  Content(`-- ${model.const.Name} SDK exists test
@@ -9,6 +9,7 @@ import {
9
9
  isAuthActive,
10
10
  isConfigData,
11
11
  resolveAuthPrefix,
12
+ targetFeatures,
12
13
  } from '@voxgig/sdkgen'
13
14
 
14
15
 
@@ -34,7 +35,10 @@ const Config = cmp(async function Config(props: any) {
34
35
 
35
36
  const model: Model = ctx$.model
36
37
 
37
- const feature = getModelPath(model, `main.${KIT}.feature`)
38
+ // Gated by the applicability tags, so this target never imports or
39
+ // registers a feature it has no source for. One rule, one place:
40
+ // helpers/applicability.
41
+ const feature = targetFeatures(model, target)
38
42
 
39
43
  // The canonical config OBJECT and its JSON, from the shared helper. Both
40
44
  // representations render from the same `def`, so they cannot describe
@@ -19,6 +19,7 @@ const Gitignore = cmp(async function Gitignore(_props: any) {
19
19
  a.out
20
20
  run_sdk_test
21
21
  run_struct_corpus
22
+ run_primary_corpus
22
23
 
23
24
  # dune (unused; the build is stock ocamlc via the Makefile)
24
25
  _build/
@@ -4,6 +4,8 @@ import * as Path from 'node:path'
4
4
  import {
5
5
  cmp, each, names,
6
6
  File, Content, Copy, Folder, Fragment,
7
+ targetFeatures,
8
+ TEST_CONTROL_EXCLUDE
7
9
  } from '@voxgig/sdkgen'
8
10
 
9
11
 
@@ -36,7 +38,10 @@ const Main = cmp(async function Main(props: any) {
36
38
  const { model } = props.ctx$
37
39
 
38
40
  const entity: ModelEntity = getModelPath(model, `main.${KIT}.entity`)
39
- const feature = getModelPath(model, `main.${KIT}.feature`)
41
+ // Gated by the applicability tags, so this target never imports or
42
+ // registers a feature it has no source for. One rule, one place:
43
+ // helpers/applicability.
44
+ const feature = targetFeatures(model, target)
40
45
 
41
46
  Package({ target })
42
47
 
@@ -45,7 +50,7 @@ const Main = cmp(async function Main(props: any) {
45
50
  // Copy tm/perl files with replacements
46
51
  Copy({
47
52
  from: 'tm/' + target.name,
48
- exclude: [/src\//],
53
+ exclude: [/src\//, TEST_CONTROL_EXCLUDE],
49
54
  replace: {
50
55
  ...props.ctx$.stdrep,
51
56
  }
@@ -11,9 +11,13 @@ import {
11
11
  File,
12
12
  cmp,
13
13
  snakify,
14
- isAuthActive, envName, envToken
14
+ isAuthActive, envName, envToken,
15
+ serverVarEnv,
16
+ serverVariables
15
17
  } from '@voxgig/sdkgen'
16
18
 
19
+ import { perlStringLiteral } from './utility_perl'
20
+
17
21
 
18
22
  function normalizePathParams(
19
23
  parts: string[],
@@ -74,12 +78,25 @@ const TestDirect = cmp(function TestDirect(props: any) {
74
78
 
75
79
  const authActive = isAuthActive(model)
76
80
  const apikeyEnvEntry = authActive
77
- ? `\n '${PROJECTNAME}_APIKEY' => 'NONE',`
81
+ ? `\n '${PROJECTNAME}_APIKEY' => '',`
78
82
  : ''
79
83
  const apikeyLiveField = authActive
80
84
  ? `\n 'apikey' => $env->{'${PROJECTNAME}_APIKEY'},`
81
85
  : ''
82
86
 
87
+ // A templated server URL (OpenAPI server variables) makes a LIVE client
88
+ // impossible to construct without values: makeOptions raises rather than
89
+ // request a URL with a literal `{account_id}` in it. So the live suite
90
+ // takes them from the environment the same way it takes the apikey.
91
+ const svars = serverVariables(model)
92
+ const serverEnvEntry = svars
93
+ .map((v: any) => `\n '${serverVarEnv(PROJECTNAME, v.name)}' => ${perlStringLiteral(v.dflt)},`).join('')
94
+ const serverLiveField = 0 === svars.length ? '' : `
95
+ 'server' => {${svars
96
+ .map((v: any) => `
97
+ '${v.name}' => $env->{'${serverVarEnv(PROJECTNAME, v.name)}'},`).join('')}
98
+ },`
99
+
83
100
  const opnames = Object.keys(entity.op || {})
84
101
  const hasLoad = opnames.includes('load')
85
102
  const hasList = opnames.includes('list')
@@ -377,13 +394,17 @@ sub ${entity.name}_direct_setup {
377
394
 
378
395
  my $env = ${N}TestRunner::env_override({
379
396
  '${entidEnvVar}' => {},
380
- '${PROJECTNAME}_TEST_LIVE' => 'FALSE',${apikeyEnvEntry}
397
+ '${PROJECTNAME}_TEST_LIVE' => 'FALSE',${apikeyEnvEntry}${serverEnvEntry}
381
398
  });
382
399
 
383
400
  my $live = ((($env->{'${PROJECTNAME}_TEST_LIVE'}) || '') eq 'TRUE') ? 1 : 0;
384
401
 
385
402
  if ($live) {
386
- my $client = ${N}SDK->new({${apikeyLiveField}
403
+ # live_client_options() FIRST so the generated fields below win:
404
+ # sdk-test-control.json's test.client.options adds to the live client,
405
+ # it does not redirect it (a later key wins in a Perl hash literal).
406
+ my $client = ${N}SDK->new({
407
+ %{ ${N}TestRunner::live_client_options() },${apikeyLiveField}${serverLiveField}
387
408
  });
388
409
  return {
389
410
  'client' => $client,
@@ -17,9 +17,13 @@ import {
17
17
  each,
18
18
  buildIdNames,
19
19
  getMatchEntries,
20
- isAuthActive, envName, envToken
20
+ isAuthActive, envName, envToken,
21
+ serverVarEnv,
22
+ serverVariables
21
23
  } from '@voxgig/sdkgen'
22
24
 
25
+ import { perlStringLiteral } from './utility_perl'
26
+
23
27
 
24
28
  // See TestEntity_ts.ts for the GenCtx/OpGen contract. Flow-step variables
25
29
  // are held in a single declared perl hash (%V) so generated steps never
@@ -54,12 +58,25 @@ const TestEntity = cmp(function TestEntity(props: any) {
54
58
 
55
59
  const authActive = isAuthActive(model)
56
60
  const apikeyEnvEntry = authActive
57
- ? `\n '${PROJUPPER}_APIKEY' => 'NONE',`
61
+ ? `\n '${PROJUPPER}_APIKEY' => '',`
58
62
  : ''
59
63
  const apikeyLiveField = authActive
60
64
  ? `\n 'apikey' => $env->{'${PROJUPPER}_APIKEY'},`
61
65
  : ''
62
66
 
67
+ // A templated server URL (OpenAPI server variables) makes a LIVE client
68
+ // impossible to construct without values: makeOptions raises rather than
69
+ // request a URL with a literal `{account_id}` in it. So the live suite
70
+ // takes them from the environment the same way it takes the apikey.
71
+ const svars = serverVariables(model)
72
+ const serverEnvEntry = svars
73
+ .map((v: any) => `\n '${serverVarEnv(PROJUPPER, v.name)}' => ${perlStringLiteral(v.dflt)},`).join('')
74
+ const serverLiveField = 0 === svars.length ? '' : `
75
+ 'server' => {${svars
76
+ .map((v: any) => `
77
+ '${v.name}' => $env->{'${serverVarEnv(PROJUPPER, v.name)}'},`).join('')}
78
+ },`
79
+
63
80
  const idnames = buildIdNames(entity, basicflow)
64
81
  const idnamesStr = idnames.map(n => `'${n}'`).join(', ')
65
82
 
@@ -193,7 +210,7 @@ BASIC_FLOW: {
193
210
  my $env = ${N}TestRunner::env_override({
194
211
  '${ENTIDVAR}' => $idmap,
195
212
  '${PROJUPPER}_TEST_LIVE' => 'FALSE',
196
- '${PROJUPPER}_TEST_EXPLAIN' => 'FALSE',${apikeyEnvEntry}
213
+ '${PROJUPPER}_TEST_EXPLAIN' => 'FALSE',${apikeyEnvEntry}${serverEnvEntry}
197
214
  });
198
215
 
199
216
  my $idmap_resolved = ${N}Helpers::to_map($env->{'${ENTIDVAR}'});
@@ -213,7 +230,10 @@ BASIC_FLOW: {
213
230
  Content(`
214
231
  if ((($env->{'${PROJUPPER}_TEST_LIVE'}) || '') eq 'TRUE') {
215
232
  my $merged_opts = Voxgig::Struct::merge([
216
- {${apikeyLiveField}
233
+ # FIRST, so the generated fields below win: sdk-test-control.json's
234
+ # test.client.options adds to the live client, it does not redirect it.
235
+ ${N}TestRunner::live_client_options(),
236
+ {${apikeyLiveField}${serverLiveField}
217
237
  },
218
238
  (Voxgig::Struct::ismap($extra) ? $extra : {}),
219
239
  ]);
@@ -3,7 +3,8 @@ import type {
3
3
  ModelEntity
4
4
  } from '@voxgig/apidef'
5
5
 
6
- import { cmp, each, Folder, File, Content, entityCollection } from '@voxgig/sdkgen'
6
+ import { cmp, each, Folder, File, Content, entityCollection,
7
+ TestControl } from '@voxgig/sdkgen'
7
8
 
8
9
 
9
10
  import { TestEntity } from './TestEntity_perl'
@@ -17,6 +18,9 @@ const Test = cmp(function Test(props: any) {
17
18
 
18
19
  Folder({ name: 't' }, () => {
19
20
 
21
+ // Write-once: a project's edited control file survives regeneration.
22
+ TestControl({ target, dir: 't' })
23
+
20
24
  // Generate exists test
21
25
  File({ name: 'exists.t' }, () => {
22
26
  Content(`#!perl
@@ -75,7 +75,17 @@ function clean(o: any, dropDefaults?: boolean): any {
75
75
  }
76
76
 
77
77
 
78
+ // A Perl SINGLE-quoted string literal. Single quotes do not interpolate, so a
79
+ // spec-derived value containing $foo or @foo survives verbatim; only the
80
+ // backslash and the quote itself need escaping. A double-quoted literal (what
81
+ // JSON.stringify emits) would interpolate them.
82
+ function perlStringLiteral(val: string): string {
83
+ return "'" + String(val).replace(/\\/g, '\\\\').replace(/'/g, "\\'") + "'"
84
+ }
85
+
86
+
78
87
  export {
88
+ perlStringLiteral,
79
89
  clean,
80
90
  projectPath,
81
91
  }