@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
@@ -16,6 +16,7 @@ import {
16
16
  rawStringLiteral,
17
17
  resolveAuthPrefix,
18
18
  serverVariables,
19
+ targetFeatures,
19
20
  } from '@voxgig/sdkgen'
20
21
 
21
22
 
@@ -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/php 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
  }
@@ -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 { formatPhpValue } from './utility_php'
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)}" => ${formatPhpValue(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')
@@ -356,14 +373,16 @@ function ${entity.name}_direct_setup($mockres)
356
373
 
357
374
  $env = Runner::env_override([
358
375
  "${entidEnvVar}" => [],
359
- "${PROJECTNAME}_TEST_LIVE" => "FALSE",${apikeyEnvEntry}
376
+ "${PROJECTNAME}_TEST_LIVE" => "FALSE",${apikeyEnvEntry}${serverEnvEntry}
360
377
  ]);
361
378
 
362
379
  $live = $env["${PROJECTNAME}_TEST_LIVE"] === "TRUE";
363
380
 
364
381
  if ($live) {
365
- $merged_opts = [${apikeyLiveField}
366
- ];
382
+ // Merged so the generated fields win: sdk-test-control.json's
383
+ // test.client.options adds to the live client, it does not redirect it.
384
+ $merged_opts = array_merge(Runner::live_client_options(), [${apikeyLiveField}${serverLiveField}
385
+ ]);
367
386
  $client = new ${model.const.Name}SDK($merged_opts);
368
387
  return [
369
388
  "client" => $client,
@@ -24,10 +24,14 @@ import {
24
24
  buildIdNames,
25
25
  getMatchEntries,
26
26
  isAuthActive,
27
+ serverVarEnv,
28
+ serverVariables,
27
29
  entityDataIdField, envName, envToken,
28
30
  phpEntityAccessor,
29
31
  } from '@voxgig/sdkgen'
30
32
 
33
+ import { formatPhpValue } from './utility_php'
34
+
31
35
 
32
36
  // PHP's GenCtx mirrors the shared shape (see TestEntity_ts.ts) plus an
33
37
  // `accessor` slot used to mangle the entity factory name when it collides
@@ -75,12 +79,25 @@ const TestEntity = cmp(function TestEntity(props: any) {
75
79
 
76
80
  const authActive = isAuthActive(model)
77
81
  const apikeyEnvEntry = authActive
78
- ? `\n "${PROJUPPER}_APIKEY" => "NONE",`
82
+ ? `\n "${PROJUPPER}_APIKEY" => "",`
79
83
  : ''
80
84
  const apikeyLiveField = authActive
81
85
  ? `\n "apikey" => $env["${PROJUPPER}_APIKEY"],`
82
86
  : ''
83
87
 
88
+ // A templated server URL (OpenAPI server variables) makes a LIVE client
89
+ // impossible to construct without values: makeOptions raises rather than
90
+ // request a URL with a literal `{account_id}` in it. So the live suite
91
+ // takes them from the environment the same way it takes the apikey.
92
+ const svars = serverVariables(model)
93
+ const serverEnvEntry = svars
94
+ .map((v: any) => `\n "${serverVarEnv(PROJUPPER, v.name)}" => ${formatPhpValue(v.dflt)},`).join('')
95
+ const serverLiveField = 0 === svars.length ? '' : `
96
+ "server" => [${svars
97
+ .map((v: any) => `
98
+ "${v.name}" => $env["${serverVarEnv(PROJUPPER, v.name)}"],`).join('')}
99
+ ],`
100
+
84
101
  const idnames = buildIdNames(entity, basicflow)
85
102
  const idnamesStr = idnames.map(n => `"${n}"`).join(', ')
86
103
 
@@ -246,7 +263,7 @@ ${hasList ? `
246
263
  $env = Runner::env_override([
247
264
  "${PROJUPPER}_TEST_${envToken(entity.name)}_ENTID" => $idmap,
248
265
  "${PROJUPPER}_TEST_LIVE" => "FALSE",
249
- "${PROJUPPER}_TEST_EXPLAIN" => "FALSE",${apikeyEnvEntry}
266
+ "${PROJUPPER}_TEST_EXPLAIN" => "FALSE",${apikeyEnvEntry}${serverEnvEntry}
250
267
  ]);
251
268
 
252
269
  $idmap_resolved = Helpers::to_map(
@@ -267,9 +284,16 @@ ${hasList ? `
267
284
  Content(`
268
285
  if ($env["${PROJUPPER}_TEST_LIVE"] === "TRUE") {
269
286
  $merged_opts = Vs::merge([
270
- [${apikeyLiveField}
287
+ // FIRST, so the generated fields below win: sdk-test-control.json's
288
+ // test.client.options adds to the live client, it does not redirect it.
289
+ Runner::live_client_options(),
290
+ [${apikeyLiveField}${serverLiveField}
271
291
  ],
272
- $extra ?? [],
292
+ // ismap, not a plain "?? []" default: an empty PHP array is a
293
+ // LIST, and a non-map later entry REPLACES the accumulated map in
294
+ // merge - so the no-extras call discarded live_client_options()
295
+ // and the apikey/server map above it.
296
+ Vs::ismap($extra) ? $extra : new \\stdClass(),
273
297
  ]);
274
298
  $client = new ${model.const.Name}SDK(Helpers::to_map($merged_opts));
275
299
  }
@@ -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_php'
@@ -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: 'ExistsTest.' + target.ext }, () => {
22
26
  Content(`<?php
@@ -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
 
@@ -39,7 +40,10 @@ const Config = cmp(async function Config(props: any) {
39
40
  const model: Model = ctx$.model
40
41
 
41
42
  const entity = getModelPath(model, `main.${KIT}.entity`)
42
- const feature = getModelPath(model, `main.${KIT}.feature`)
43
+ // Gated by the applicability tags, so this target never imports or
44
+ // registers a feature it has no source for. One rule, one place:
45
+ // helpers/applicability.
46
+ const feature = targetFeatures(model, target)
43
47
 
44
48
  const headers = getModelPath(model, `main.${KIT}.config.headers`) || {}
45
49
 
@@ -5,6 +5,8 @@ import {
5
5
  cmp, each, names, cmap,
6
6
  List, File, Content, Copy, Folder, Fragment, Line, FeatureHook,
7
7
  entityClassName, entityCollection,
8
+ targetFeatures,
9
+ TEST_CONTROL_EXCLUDE
8
10
  } from '@voxgig/sdkgen'
9
11
 
10
12
 
@@ -32,7 +34,10 @@ const Main = cmp(async function Main(props: any) {
32
34
  const { model } = props.ctx$
33
35
 
34
36
  const entity: ModelEntity = getModelPath(model, `main.${KIT}.entity`)
35
- const feature = getModelPath(model, `main.${KIT}.feature`)
37
+ // Gated by the applicability tags, so this target never imports or
38
+ // registers a feature it has no source for. One rule, one place:
39
+ // helpers/applicability.
40
+ const feature = targetFeatures(model, target)
36
41
 
37
42
  // The one package directory everything the SDK owns lives in.
38
43
  const pkgdir = model.const.Name.toLowerCase() + '_sdk'
@@ -46,7 +51,7 @@ const Main = cmp(async function Main(props: any) {
46
51
  // below.
47
52
  Copy({
48
53
  from: 'tm/' + target.name,
49
- exclude: [/src\//, /pkg\//],
54
+ exclude: [/src\//, /pkg\//, TEST_CONTROL_EXCLUDE],
50
55
  replace: {
51
56
  ...props.ctx$.stdrep,
52
57
  }
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, each, Content, isAuthActive, envName, canonKey, canonScalarKey, opRequestShape, entityIdField, entityDataIdField, entityOps, safeVarName, exampleVarName, matchArg, idLiteral } from '@voxgig/sdkgen'
2
+ import { cmp, each, Content, isAuthActive, envName, canonKey, canonScalarKey, opRequestShape, entityIdField, entityDataIdField, entityOps, safeVarName, exampleVarName, matchArg, idLiteral , serverVariables} from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -38,10 +38,23 @@ const ReadmeQuick = cmp(function ReadmeQuick(props: any) {
38
38
  ) as any
39
39
 
40
40
  const authActive = isAuthActive(model)
41
+
42
+ // Server variables (a templated server URL) are REQUIRED at construction
43
+ // - the SDK refuses rather than request a URL with a literal
44
+ // {account_id} in it - so a quickstart that omits them is a quickstart
45
+ // that fails on its first line.
46
+ const svars = serverVariables(model)
47
+ const pyServerField = 0 === svars.length ? '' :
48
+ `\n "server": {` +
49
+ svars.map((v: any) => `\n "${v.name}": "<${v.name}>",`).join('') +
50
+ `\n },`
51
+
41
52
  const apikeyImport = authActive ? `import os\n` : ''
42
53
  const ctor = authActive
43
- ? `${model.const.Name}SDK({\n "apikey": os.environ.get("${envName(model)}_APIKEY"),\n})`
44
- : `${model.const.Name}SDK()`
54
+ ? `${model.const.Name}SDK({\n "apikey": os.environ.get("${envName(model)}_APIKEY"),${pyServerField}\n})`
55
+ : ('' === pyServerField
56
+ ? `${model.const.Name}SDK()`
57
+ : `${model.const.Name}SDK({${pyServerField}\n})`)
45
58
 
46
59
  Content(`### 1. Create a client
47
60
 
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, Content, isAuthActive, envName, canonKey, canonScalarKey, entityIdField, opRequestShape, safeVarName, exampleVarName, matchArg, idLiteral } from '@voxgig/sdkgen'
2
+ import { cmp, Content, isAuthActive, envName, canonKey, canonScalarKey, entityIdField, opRequestShape, safeVarName, exampleVarName, matchArg, idLiteral , serverVariables} from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -41,10 +41,23 @@ const ReadmeTopQuick = cmp(function ReadmeTopQuick(props: any) {
41
41
  const exampleEntity = Object.values(entity).find((e: any) => e.active !== false) as any
42
42
 
43
43
  const authActive = isAuthActive(model)
44
+
45
+ // Server variables (a templated server URL) are REQUIRED at construction
46
+ // - the SDK refuses rather than request a URL with a literal
47
+ // {account_id} in it - so a quickstart that omits them is a quickstart
48
+ // that fails on its first line.
49
+ const svars = serverVariables(model)
50
+ const pyServerField = 0 === svars.length ? '' :
51
+ `\n "server": {` +
52
+ svars.map((v: any) => `\n "${v.name}": "<${v.name}>",`).join('') +
53
+ `\n },`
54
+
44
55
  const apikeyImport = authActive ? `import os\n` : ''
45
56
  const ctor = authActive
46
- ? `${model.const.Name}SDK({\n "apikey": os.environ.get("${envName(model)}_APIKEY"),\n})`
47
- : `${model.const.Name}SDK()`
57
+ ? `${model.const.Name}SDK({\n "apikey": os.environ.get("${envName(model)}_APIKEY"),${pyServerField}\n})`
58
+ : ('' === pyServerField
59
+ ? `${model.const.Name}SDK()`
60
+ : `${model.const.Name}SDK({${pyServerField}\n})`)
48
61
 
49
62
  Content(`\`\`\`python
50
63
  ${apikeyImport}from ${model.const.Name.toLowerCase()}_sdk import ${model.const.Name}SDK
@@ -11,7 +11,9 @@ import {
11
11
  File,
12
12
  cmp,
13
13
  snakify,
14
- isAuthActive, envName, envToken
14
+ isAuthActive,
15
+ serverVarEnv,
16
+ serverVariables, envName, envToken
15
17
  } from '@voxgig/sdkgen'
16
18
 
17
19
 
@@ -64,12 +66,26 @@ const TestDirect = cmp(function TestDirect(props: any) {
64
66
 
65
67
  const authActive = isAuthActive(model)
66
68
  const apikeyEnvEntry = authActive
67
- ? `\n "${PROJECTNAME}_APIKEY": "NONE",`
69
+ ? `\n "${PROJECTNAME}_APIKEY": "",`
68
70
  : ''
69
71
  const apikeyLiveField = authActive
70
72
  ? `\n "apikey": env.get("${PROJECTNAME}_APIKEY"),`
71
73
  : ''
72
74
 
75
+ // A templated server URL (OpenAPI server variables) makes a LIVE client
76
+ // impossible to construct without values: make_options raises rather than
77
+ // request a URL with a literal `{account_id}` in it. Taken from the
78
+ // environment, the same way the apikey is.
79
+ const svars = serverVariables(model)
80
+ const serverEnvEntry = svars
81
+ .map((v: any) => `\n "${serverVarEnv(PROJECTNAME, v.name)}": ${JSON.stringify(v.dflt)},`).join('')
82
+ const serverLiveField = 0 === svars.length ? '' :
83
+ `\n "server": {` +
84
+ svars.map((v: any) =>
85
+ `\n "${v.name}": env.get("${serverVarEnv(PROJECTNAME, v.name)}"),`).join('') +
86
+ `\n },`
87
+
88
+
73
89
  const opnames = Object.keys(entity.op || {})
74
90
  const hasLoad = opnames.includes('load')
75
91
  const hasList = opnames.includes('list')
@@ -337,14 +353,17 @@ def _${entity.name}_direct_setup(mockres):
337
353
 
338
354
  env = runner.env_override({
339
355
  "${entidEnvVar}": {},
340
- "${PROJECTNAME}_TEST_LIVE": "FALSE",${apikeyEnvEntry}
356
+ "${PROJECTNAME}_TEST_LIVE": "FALSE",${apikeyEnvEntry}${serverEnvEntry}
341
357
  })
342
358
 
343
359
  live = env.get("${PROJECTNAME}_TEST_LIVE") == "TRUE"
344
360
 
345
361
  if live:
346
- merged_opts = {${apikeyLiveField}
347
- }
362
+ # sdk-test-control.json's test.client.options seeds the live
363
+ # client; the generated fields below overwrite anything they name.
364
+ merged_opts = dict(runner.live_client_options())
365
+ merged_opts.update({${apikeyLiveField}${serverLiveField}
366
+ })
348
367
  client = ${model.const.Name}SDK(merged_opts)
349
368
  return {
350
369
  "client": client,
@@ -24,6 +24,8 @@ import {
24
24
  buildIdNames,
25
25
  getMatchEntries,
26
26
  isAuthActive,
27
+ serverVarEnv,
28
+ serverVariables,
27
29
  entityDataIdField, envName, envToken
28
30
  } from '@voxgig/sdkgen'
29
31
 
@@ -60,12 +62,26 @@ const TestEntity = cmp(function TestEntity(props: any) {
60
62
 
61
63
  const authActive = isAuthActive(model)
62
64
  const apikeyEnvEntry = authActive
63
- ? `\n "${PROJUPPER}_APIKEY": "NONE",`
65
+ ? `\n "${PROJUPPER}_APIKEY": "",`
64
66
  : ''
65
67
  const apikeyLiveField = authActive
66
68
  ? `\n "apikey": env.get("${PROJUPPER}_APIKEY"),`
67
69
  : ''
68
70
 
71
+ // A templated server URL (OpenAPI server variables) makes a LIVE client
72
+ // impossible to construct without values: make_options raises rather than
73
+ // request a URL with a literal `{account_id}` in it. Taken from the
74
+ // environment, the same way the apikey is.
75
+ const svars = serverVariables(model)
76
+ const serverEnvEntry = svars
77
+ .map((v: any) => `\n "${serverVarEnv(PROJUPPER, v.name)}": ${JSON.stringify(v.dflt)},`).join('')
78
+ const serverLiveField = 0 === svars.length ? '' :
79
+ `\n "server": {` +
80
+ svars.map((v: any) =>
81
+ `\n "${v.name}": env.get("${serverVarEnv(PROJUPPER, v.name)}"),`).join('') +
82
+ `\n },`
83
+
84
+
69
85
  const idnames = buildIdNames(entity, basicflow)
70
86
  const idnamesStr = idnames.map(n => `"${n}"`).join(', ')
71
87
 
@@ -235,7 +251,7 @@ def _${entity.name}_basic_setup(extra):
235
251
  env = runner.env_override({
236
252
  "${PROJUPPER}_TEST_${envToken(entity.name)}_ENTID": idmap,
237
253
  "${PROJUPPER}_TEST_LIVE": "FALSE",
238
- "${PROJUPPER}_TEST_EXPLAIN": "FALSE",${apikeyEnvEntry}
254
+ "${PROJUPPER}_TEST_EXPLAIN": "FALSE",${apikeyEnvEntry}${serverEnvEntry}
239
255
  })
240
256
 
241
257
  idmap_resolved = helpers.to_map(
@@ -254,7 +270,11 @@ def _${entity.name}_basic_setup(extra):
254
270
  Content(`
255
271
  if env.get("${PROJUPPER}_TEST_LIVE") == "TRUE":
256
272
  merged_opts = vs.merge([
257
- {${apikeyLiveField}
273
+ # FIRST, so the generated fields below win: sdk-test-control.json's
274
+ # test.client.options adds to the live client, it does not
275
+ # redirect it.
276
+ runner.live_client_options(),
277
+ {${apikeyLiveField}${serverLiveField}
258
278
  },
259
279
  extra or {},
260
280
  ])
@@ -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_py'
@@ -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 __init__.py for test package
21
25
  File({ name: '__init__.' + target.ext }, () => {
22
26
  Content(``)
@@ -16,6 +16,7 @@ import {
16
16
  rawStringLiteral,
17
17
  resolveAuthPrefix,
18
18
  serverVariables,
19
+ targetFeatures,
19
20
  } from '@voxgig/sdkgen'
20
21
 
21
22
 
@@ -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/rb 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
  }
@@ -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 { formatRubyValue } from './utility_rb'
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)}" => ${formatRubyValue(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')
@@ -349,14 +366,16 @@ def ${entity.name}_direct_setup(mockres)
349
366
 
350
367
  env = Runner.env_override({
351
368
  "${entidEnvVar}" => {},
352
- "${PROJECTNAME}_TEST_LIVE" => "FALSE",${apikeyEnvEntry}
369
+ "${PROJECTNAME}_TEST_LIVE" => "FALSE",${apikeyEnvEntry}${serverEnvEntry}
353
370
  })
354
371
 
355
372
  live = env["${PROJECTNAME}_TEST_LIVE"] == "TRUE"
356
373
 
357
374
  if live
358
- merged_opts = {${apikeyLiveField}
359
- }
375
+ # Merged so the generated fields win: sdk-test-control.json's
376
+ # test.client.options adds to the live client, it does not redirect it.
377
+ merged_opts = Runner.live_client_options.merge({${apikeyLiveField}${serverLiveField}
378
+ })
360
379
  client = ${model.const.Name}SDK.new(merged_opts)
361
380
  return {
362
381
  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 { formatRubyValue } from './utility_rb'
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)}" => ${formatRubyValue(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
 
@@ -227,7 +244,7 @@ end
227
244
  env = Runner.env_override({
228
245
  "${PROJUPPER}_TEST_${envToken(entity.name)}_ENTID" => idmap,
229
246
  "${PROJUPPER}_TEST_LIVE" => "FALSE",
230
- "${PROJUPPER}_TEST_EXPLAIN" => "FALSE",${apikeyEnvEntry}
247
+ "${PROJUPPER}_TEST_EXPLAIN" => "FALSE",${apikeyEnvEntry}${serverEnvEntry}
231
248
  })
232
249
 
233
250
  idmap_resolved = Helpers.to_map(
@@ -248,7 +265,10 @@ end
248
265
  Content(`
249
266
  if env["${PROJUPPER}_TEST_LIVE"] == "TRUE"
250
267
  merged_opts = Vs.merge([
251
- {${apikeyLiveField}
268
+ # FIRST, so the generated fields below win: sdk-test-control.json's
269
+ # test.client.options adds to the live client, it does not redirect it.
270
+ Runner.live_client_options,
271
+ {${apikeyLiveField}${serverLiveField}
252
272
  },
253
273
  extra || {},
254
274
  ])
@@ -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_rb'
@@ -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
@@ -48,7 +48,9 @@ function formatRubyValue(val: any, indent: number = 0): string {
48
48
  return 'nil'
49
49
  }
50
50
  if (typeof val === 'string') {
51
- return `"${val.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`
51
+ // `#` is escaped only where it OPENS an interpolation (#{, #$, #@), so
52
+ // this changes output solely for values that would otherwise interpolate.
53
+ return `"${val.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/#(?=[{$@])/g, '\\#')}"`
52
54
  }
53
55
  if (typeof val === 'number') {
54
56
  return String(val)