@voxgig/sdkgen 4.6.0 → 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 (115) 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/sdkgen.d.ts +3 -2
  11. package/dist/sdkgen.js +9 -4
  12. package/dist/sdkgen.js.map +1 -1
  13. package/dist/tsconfig.tsbuildinfo +1 -1
  14. package/dist/utility.d.ts +2 -1
  15. package/dist/utility.js +52 -0
  16. package/dist/utility.js.map +1 -1
  17. package/model/sdkgen.aon +58 -0
  18. package/package.json +1 -1
  19. package/project/.sdk/model/feature/secrets.aon +82 -0
  20. package/project/.sdk/src/cmp/cpp/Main_cpp.ts +2 -1
  21. package/project/.sdk/src/cmp/cpp/Test_cpp.ts +5 -1
  22. package/project/.sdk/src/cmp/csharp/Main_csharp.ts +2 -1
  23. package/project/.sdk/src/cmp/csharp/TestDirect_csharp.ts +30 -6
  24. package/project/.sdk/src/cmp/csharp/TestEntity_csharp.ts +29 -5
  25. package/project/.sdk/src/cmp/csharp/Test_csharp.ts +5 -1
  26. package/project/.sdk/src/cmp/dart/Main_dart.ts +2 -1
  27. package/project/.sdk/src/cmp/dart/TestDirect_dart.ts +23 -6
  28. package/project/.sdk/src/cmp/dart/TestEntity_dart.ts +28 -7
  29. package/project/.sdk/src/cmp/dart/Test_dart.ts +5 -1
  30. package/project/.sdk/src/cmp/go/Main_go.ts +2 -1
  31. package/project/.sdk/src/cmp/go/Test_go.ts +5 -1
  32. package/project/.sdk/src/cmp/java/Main_java.ts +2 -1
  33. package/project/.sdk/src/cmp/java/Test_java.ts +5 -1
  34. package/project/.sdk/src/cmp/js/Main_js.ts +2 -1
  35. package/project/.sdk/src/cmp/js/TestDirect_js.ts +30 -5
  36. package/project/.sdk/src/cmp/js/TestEntity_js.ts +35 -5
  37. package/project/.sdk/src/cmp/js/Test_js.ts +5 -1
  38. package/project/.sdk/src/cmp/js/fragment/Direct.test.fragment.js +7 -1
  39. package/project/.sdk/src/cmp/js/fragment/Entity.test.fragment.js +7 -1
  40. package/project/.sdk/src/cmp/kotlin/Main_kotlin.ts +2 -1
  41. package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +5 -1
  42. package/project/.sdk/src/cmp/lua/Main_lua.ts +2 -1
  43. package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +28 -4
  44. package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +24 -4
  45. package/project/.sdk/src/cmp/lua/Test_lua.ts +5 -1
  46. package/project/.sdk/src/cmp/ocaml/Gitignore_ocaml.ts +1 -0
  47. package/project/.sdk/src/cmp/perl/Main_perl.ts +2 -1
  48. package/project/.sdk/src/cmp/perl/TestDirect_perl.ts +25 -4
  49. package/project/.sdk/src/cmp/perl/TestEntity_perl.ts +24 -4
  50. package/project/.sdk/src/cmp/perl/Test_perl.ts +5 -1
  51. package/project/.sdk/src/cmp/perl/utility_perl.ts +10 -0
  52. package/project/.sdk/src/cmp/php/Main_php.ts +2 -1
  53. package/project/.sdk/src/cmp/php/TestDirect_php.ts +24 -5
  54. package/project/.sdk/src/cmp/php/TestEntity_php.ts +28 -4
  55. package/project/.sdk/src/cmp/php/Test_php.ts +5 -1
  56. package/project/.sdk/src/cmp/py/Main_py.ts +2 -1
  57. package/project/.sdk/src/cmp/py/Test_py.ts +5 -1
  58. package/project/.sdk/src/cmp/rb/Main_rb.ts +2 -1
  59. package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +24 -5
  60. package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +24 -4
  61. package/project/.sdk/src/cmp/rb/Test_rb.ts +5 -1
  62. package/project/.sdk/src/cmp/rb/utility_rb.ts +3 -1
  63. package/project/.sdk/src/cmp/rust/Main_rust.ts +2 -1
  64. package/project/.sdk/src/cmp/rust/TestDirect_rust.ts +31 -4
  65. package/project/.sdk/src/cmp/rust/TestEntity_rust.ts +37 -4
  66. package/project/.sdk/src/cmp/rust/Test_rust.ts +5 -1
  67. package/project/.sdk/src/cmp/scala/Main_scala.ts +2 -1
  68. package/project/.sdk/src/cmp/scala/Test_scala.ts +5 -1
  69. package/project/.sdk/src/cmp/ts/Config_ts.ts +50 -8
  70. package/project/.sdk/src/cmp/ts/Main_ts.ts +10 -2
  71. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +10 -3
  72. package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +11 -3
  73. package/project/.sdk/src/cmp/ts/Test_ts.ts +5 -1
  74. package/project/.sdk/tm/csharp/test/Runner.cs +45 -0
  75. package/project/.sdk/tm/dart/test/utility.dart +39 -0
  76. package/project/.sdk/tm/js/test/sdk-test-control.json +19 -0
  77. package/project/.sdk/tm/js/test/utility.js +91 -1
  78. package/project/.sdk/tm/lua/test/runner.lua +40 -0
  79. package/project/.sdk/tm/ocaml/test/harness.ml +14 -4
  80. package/project/.sdk/tm/perl/t/runner.pm +30 -0
  81. package/project/.sdk/tm/php/test/Runner.php +36 -0
  82. package/project/.sdk/tm/rb/test/runner.rb +25 -0
  83. package/project/.sdk/tm/rust/tests/common/mod.rs +32 -0
  84. package/project/.sdk/tm/rust/utility/make_options.rs +1 -1
  85. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +8 -2
  86. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +29 -5
  87. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +30 -21
  88. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/Registry.ts +90 -0
  89. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/addr.ts +48 -0
  90. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/aws.ts +219 -0
  91. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/azuresecrets.ts +144 -0
  92. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/boru.ts +113 -0
  93. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/doppler.ts +77 -0
  94. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/dotenv.ts +51 -0
  95. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/env.ts +31 -0
  96. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/file.ts +54 -0
  97. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/gcpsecrets.ts +118 -0
  98. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/hashicorp.ts +159 -0
  99. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/http.ts +66 -0
  100. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/infisical.ts +107 -0
  101. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/memory.ts +33 -0
  102. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/onepassword.ts +120 -0
  103. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/support.ts +176 -0
  104. package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +15 -13
  105. package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +25 -19
  106. package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +1 -1
  107. package/project/.sdk/tm/ts/test/vendor/omni/index.ts +1 -1
  108. package/project/.sdk/tm/zig/core/utility.zig +1 -1
  109. package/project/sdkgen-package.json +1 -1
  110. package/src/cmp/Feature.ts +5 -0
  111. package/src/cmp/TestControl.ts +64 -0
  112. package/src/helpers/featureSource.ts +130 -0
  113. package/src/sdkgen.ts +7 -0
  114. package/src/utility.ts +61 -0
  115. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Providers.ts +0 -1221
@@ -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
  }
@@ -5,6 +5,7 @@ import {
5
5
  cmp, each, names, cmap,
6
6
  List, File, Content, Copy, Folder, Fragment, Line, FeatureHook,
7
7
  targetFeatures,
8
+ TEST_CONTROL_EXCLUDE
8
9
  } from '@voxgig/sdkgen'
9
10
 
10
11
 
@@ -44,7 +45,7 @@ const Main = cmp(async function Main(props: any) {
44
45
  // Copy tm/php files with replacements
45
46
  Copy({
46
47
  from: 'tm/' + target.name,
47
- exclude: [/src\//],
48
+ exclude: [/src\//, TEST_CONTROL_EXCLUDE],
48
49
  replace: {
49
50
  ...props.ctx$.stdrep,
50
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
@@ -6,6 +6,7 @@ import {
6
6
  List, File, Content, Copy, Folder, Fragment, Line, FeatureHook,
7
7
  entityClassName, entityCollection,
8
8
  targetFeatures,
9
+ TEST_CONTROL_EXCLUDE
9
10
  } from '@voxgig/sdkgen'
10
11
 
11
12
 
@@ -50,7 +51,7 @@ const Main = cmp(async function Main(props: any) {
50
51
  // below.
51
52
  Copy({
52
53
  from: 'tm/' + target.name,
53
- exclude: [/src\//, /pkg\//],
54
+ exclude: [/src\//, /pkg\//, TEST_CONTROL_EXCLUDE],
54
55
  replace: {
55
56
  ...props.ctx$.stdrep,
56
57
  }
@@ -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(``)
@@ -5,6 +5,7 @@ import {
5
5
  cmp, each, names, cmap,
6
6
  List, File, Content, Copy, Folder, Fragment, Line, FeatureHook,
7
7
  targetFeatures,
8
+ TEST_CONTROL_EXCLUDE
8
9
  } from '@voxgig/sdkgen'
9
10
 
10
11
 
@@ -44,7 +45,7 @@ const Main = cmp(async function Main(props: any) {
44
45
  // Copy tm/rb files with replacements
45
46
  Copy({
46
47
  from: 'tm/' + target.name,
47
- exclude: [/src\//],
48
+ exclude: [/src\//, TEST_CONTROL_EXCLUDE],
48
49
  replace: {
49
50
  ...props.ctx$.stdrep,
50
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)
@@ -6,6 +6,7 @@ import {
6
6
  File, Content, Copy, Folder, Fragment,
7
7
  entityClassName,
8
8
  targetFeatures,
9
+ TEST_CONTROL_EXCLUDE
9
10
  } from '@voxgig/sdkgen'
10
11
 
11
12
 
@@ -54,7 +55,7 @@ const Main = cmp(async function Main(props: any) {
54
55
  // here exactly like the go target.
55
56
  Copy({
56
57
  from: 'tm/' + target.name,
57
- exclude: [/src\//],
58
+ exclude: [/src\//, TEST_CONTROL_EXCLUDE],
58
59
  replace: {
59
60
  ...props.ctx$.stdrep,
60
61
  RUSTCRATE: rustcrate,
@@ -11,7 +11,9 @@ 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
 
17
19
 
@@ -67,12 +69,28 @@ const TestDirect = cmp(function TestDirect(props: any) {
67
69
 
68
70
  const authActive = isAuthActive(model)
69
71
  const apikeyEnvEntry = authActive
70
- ? `\n ("${PROJECTNAME}_APIKEY", Value::str("NONE")),`
72
+ ? `\n ("${PROJECTNAME}_APIKEY", Value::str("")),`
71
73
  : ''
72
74
  const apikeyLiveField = authActive
73
75
  ? `("apikey", getp(&env, "${PROJECTNAME}_APIKEY"))`
74
76
  : ''
75
77
 
78
+ // A templated server URL (OpenAPI server variables) makes a LIVE client
79
+ // impossible to construct without values: makeOptions raises rather than
80
+ // request a URL with a literal `{account_id}` in it. So the live suite
81
+ // takes them from the environment the same way it takes the apikey.
82
+ //
83
+ // Emitted as a jo() tuple entry, so it carries its own leading comma only
84
+ // when an apikey entry precedes it - otherwise `jo(vec![, ...])`.
85
+ const svars = serverVariables(model)
86
+ const serverEnvEntry = svars
87
+ .map((v: any) => `\n ("${serverVarEnv(PROJECTNAME, v.name)}", Value::str(${JSON.stringify(v.dflt)})),`).join('')
88
+ const serverLiveEntry = 0 === svars.length ? '' :
89
+ `("server", jo(vec![${svars
90
+ .map((v: any) => `("${v.name}", getp(&env, "${serverVarEnv(PROJECTNAME, v.name)}"))`).join(', ')}]))`
91
+ const serverLiveField = '' === serverLiveEntry ? '' :
92
+ ('' === apikeyLiveField ? serverLiveEntry : ', ' + serverLiveEntry)
93
+
76
94
  const opnames = Object.keys(entity.op || {})
77
95
  const hasLoad = opnames.includes('load')
78
96
  const hasList = opnames.includes('list')
@@ -148,13 +166,22 @@ fn ${evar}_direct_setup(mockres: Value) -> ${entity.Name}DirectSetup {
148
166
 
149
167
  let env = env_override(jo(vec![
150
168
  ("${entidEnvVar}", Value::empty_map()),
151
- ("${PROJECTNAME}_TEST_LIVE", Value::str("FALSE")),${apikeyEnvEntry}
169
+ ("${PROJECTNAME}_TEST_LIVE", Value::str("FALSE")),${apikeyEnvEntry}${serverEnvEntry}
152
170
  ]));
153
171
 
154
172
  let live = getp(&env, "${PROJECTNAME}_TEST_LIVE") == Value::str("TRUE");
155
173
 
156
174
  if live {
157
- let client = ${model.const.Name}SDK::new(jo(vec![${apikeyLiveField}]));
175
+ // live_client_options() FIRST, so the generated entries below win:
176
+ // sdk-test-control.json's test.client.options adds to the live
177
+ // client, it does not redirect it.
178
+ let client = ${model.const.Name}SDK::new(to_map(&vs::merge(
179
+ &ja(vec![
180
+ live_client_options(),
181
+ jo(vec![${apikeyLiveField}${serverLiveField}]),
182
+ ]),
183
+ None,
184
+ )));
158
185
  let idmap = match to_map(&getp(&env, "${entidEnvVar}")) {
159
186
  Value::Map(m) => Value::Map(m),
160
187
  _ => Value::empty_map(),
@@ -17,7 +17,9 @@ 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
 
23
25
 
@@ -56,12 +58,28 @@ const TestEntity = cmp(function TestEntity(props: any) {
56
58
 
57
59
  const authActive = isAuthActive(model)
58
60
  const apikeyEnvEntry = authActive
59
- ? `\n ("${PROJUPPER}_APIKEY", Value::str("NONE")),`
61
+ ? `\n ("${PROJUPPER}_APIKEY", Value::str("")),`
60
62
  : ''
61
63
  const apikeyLiveField = authActive
62
64
  ? `("apikey", getp(&env, "${PROJUPPER}_APIKEY"))`
63
65
  : ''
64
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
+ //
72
+ // Emitted as a jo() tuple entry, so it carries its own leading comma only
73
+ // when an apikey entry precedes it - otherwise `jo(vec![, ...])`.
74
+ const svars = serverVariables(model)
75
+ const serverEnvEntry = svars
76
+ .map((v: any) => `\n ("${serverVarEnv(PROJUPPER, v.name)}", Value::str(${JSON.stringify(v.dflt)})),`).join('')
77
+ const serverLiveEntry = 0 === svars.length ? '' :
78
+ `("server", jo(vec![${svars
79
+ .map((v: any) => `("${v.name}", getp(&env, "${serverVarEnv(PROJUPPER, v.name)}"))`).join(', ')}]))`
80
+ const serverLiveField = '' === serverLiveEntry ? '' :
81
+ ('' === apikeyLiveField ? serverLiveEntry : ', ' + serverLiveEntry)
82
+
65
83
  const idnames = buildIdNames(entity, basicflow)
66
84
  const idnamesStr = idnames.map(n => `Value::str("${n}")`).join(', ')
67
85
 
@@ -268,7 +286,7 @@ ${allSteps.length > 0 ? ' let client = setup.client.clone();\n' : ''}`)
268
286
  let env = env_override(jo(vec![
269
287
  ("${PROJUPPER}_TEST_${ENTUPPER}_ENTID", idmap.clone()),
270
288
  ("${PROJUPPER}_TEST_LIVE", Value::str("FALSE")),
271
- ("${PROJUPPER}_TEST_EXPLAIN", Value::str("FALSE")),${apikeyEnvEntry}
289
+ ("${PROJUPPER}_TEST_EXPLAIN", Value::str("FALSE")),${apikeyEnvEntry}${serverEnvEntry}
272
290
  ]));
273
291
 
274
292
  let idmap_resolved = match to_map(&getp(&env, "${PROJUPPER}_TEST_${ENTUPPER}_ENTID")) {
@@ -293,7 +311,22 @@ ${allSteps.length > 0 ? ' let client = setup.client.clone();\n' : ''}`)
293
311
 
294
312
  let client = if live {
295
313
  let merged = vs::merge(
296
- &ja(vec![jo(vec![${apikeyLiveField}]), extra]),
314
+ // live_client_options() FIRST, so the generated entries below win:
315
+ // sdk-test-control.json's test.client.options adds to the live
316
+ // client, it does not redirect it.
317
+ &ja(vec![
318
+ live_client_options(),
319
+ jo(vec![${apikeyLiveField}${serverLiveField}]),
320
+ // A NON-NODE later entry REPLACES the accumulated map in
321
+ // vs::merge, and the normal call passes Value::Noval - so a
322
+ // a bare extra discarded live_client_options() and the
323
+ // apikey/server map above it, and the live client was
324
+ // constructed with nothing.
325
+ match extra {
326
+ Value::Map(m) => Value::Map(m),
327
+ _ => Value::empty_map(),
328
+ },
329
+ ]),
297
330
  None,
298
331
  );
299
332
  ${model.const.Name}SDK::new(to_map(&merged))
@@ -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_rust'
@@ -20,6 +21,9 @@ const Test = cmp(function Test(props: any) {
20
21
 
21
22
  Folder({ name: 'tests' }, () => {
22
23
 
24
+ // Write-once: a project's edited control file survives regeneration.
25
+ TestControl({ target, dir: 'tests' })
26
+
23
27
  // exists test — the SDK constructs in test mode.
24
28
  File({ name: 'exists_test.' + target.ext }, () => {
25
29
  Content(`// Generated existence test: the SDK constructs in test mode.