@voxgig/sdkgen 4.5.3 → 4.6.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 (174) 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 +17 -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/helpers/applicability.d.ts +6 -0
  21. package/dist/helpers/applicability.js +103 -0
  22. package/dist/helpers/applicability.js.map +1 -0
  23. package/dist/helpers/collectDeps.js +4 -2
  24. package/dist/helpers/collectDeps.js.map +1 -1
  25. package/dist/helpers/serverVars.d.ts +2 -1
  26. package/dist/helpers/serverVars.js +17 -0
  27. package/dist/helpers/serverVars.js.map +1 -1
  28. package/dist/sdkgen.d.ts +3 -2
  29. package/dist/sdkgen.js +9 -2
  30. package/dist/sdkgen.js.map +1 -1
  31. package/dist/tsconfig.tsbuildinfo +1 -1
  32. package/dist/utility.js +7 -1
  33. package/dist/utility.js.map +1 -1
  34. package/model/sdkgen.aon +39 -0
  35. package/package.json +1 -1
  36. package/project/.sdk/model/feature/feature-index.aon +1 -0
  37. package/project/.sdk/model/feature/secrets.aon +118 -0
  38. package/project/.sdk/model/target/ts.aon +8 -1
  39. package/project/.sdk/src/cmp/c/Config_c.ts +5 -1
  40. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +5 -1
  41. package/project/.sdk/src/cmp/csharp/Config_csharp.ts +5 -1
  42. package/project/.sdk/src/cmp/dart/Config_dart.ts +5 -1
  43. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +5 -1
  44. package/project/.sdk/src/cmp/elixir/Main_elixir.ts +5 -1
  45. package/project/.sdk/src/cmp/go/Config_go.ts +5 -1
  46. package/project/.sdk/src/cmp/go/Main_go.ts +6 -2
  47. package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +16 -3
  48. package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +16 -3
  49. package/project/.sdk/src/cmp/go/TestDirect_go.ts +28 -4
  50. package/project/.sdk/src/cmp/go/TestEntity_go.ts +43 -10
  51. package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +5 -5
  52. package/project/.sdk/src/cmp/java/Config_java.ts +22 -1
  53. package/project/.sdk/src/cmp/java/Main_java.ts +5 -1
  54. package/project/.sdk/src/cmp/java/ReadmeQuick_java.ts +17 -3
  55. package/project/.sdk/src/cmp/java/ReadmeTopQuick_java.ts +17 -3
  56. package/project/.sdk/src/cmp/java/TestDirect_java.ts +23 -4
  57. package/project/.sdk/src/cmp/java/TestEntity_java.ts +29 -4
  58. package/project/.sdk/src/cmp/js/Config_js.ts +5 -1
  59. package/project/.sdk/src/cmp/js/Main_js.ts +5 -1
  60. package/project/.sdk/src/cmp/js/Package_js.ts +4 -1
  61. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +5 -1
  62. package/project/.sdk/src/cmp/lean/Config_lean.ts +5 -1
  63. package/project/.sdk/src/cmp/lua/Config_lua.ts +5 -1
  64. package/project/.sdk/src/cmp/lua/Main_lua.ts +5 -1
  65. package/project/.sdk/src/cmp/lua/Package_lua.ts +4 -1
  66. package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +5 -1
  67. package/project/.sdk/src/cmp/perl/Main_perl.ts +5 -1
  68. package/project/.sdk/src/cmp/php/Config_php.ts +5 -1
  69. package/project/.sdk/src/cmp/php/Main_php.ts +5 -1
  70. package/project/.sdk/src/cmp/py/Config_py.ts +5 -1
  71. package/project/.sdk/src/cmp/py/Main_py.ts +5 -1
  72. package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +16 -3
  73. package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +16 -3
  74. package/project/.sdk/src/cmp/py/TestDirect_py.ts +24 -5
  75. package/project/.sdk/src/cmp/py/TestEntity_py.ts +23 -3
  76. package/project/.sdk/src/cmp/rb/Config_rb.ts +5 -1
  77. package/project/.sdk/src/cmp/rb/Main_rb.ts +5 -1
  78. package/project/.sdk/src/cmp/rust/Config_rust.ts +5 -1
  79. package/project/.sdk/src/cmp/rust/Main_rust.ts +5 -1
  80. package/project/.sdk/src/cmp/scala/Config_scala.ts +5 -1
  81. package/project/.sdk/src/cmp/scala/Main_scala.ts +5 -1
  82. package/project/.sdk/src/cmp/swift/Config_swift.ts +5 -1
  83. package/project/.sdk/src/cmp/ts/Config_ts.ts +5 -1
  84. package/project/.sdk/src/cmp/ts/Main_ts.ts +57 -1
  85. package/project/.sdk/src/cmp/ts/Package_ts.ts +4 -1
  86. package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +15 -3
  87. package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +18 -5
  88. package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +19 -5
  89. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +67 -7
  90. package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +27 -4
  91. package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +9 -2
  92. package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +9 -2
  93. package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +7 -0
  94. package/project/.sdk/src/cmp/zig/Config_zig.ts +5 -1
  95. package/project/.sdk/tm/c/utility/make_options.c +22 -0
  96. package/project/.sdk/tm/clojure/src/sdk/core.clj +18 -1
  97. package/project/.sdk/tm/cpp/utility/pipeline.hpp +23 -0
  98. package/project/.sdk/tm/csharp/test/CustomUtilityTest.cs +15 -0
  99. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +23 -0
  100. package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +22 -0
  101. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +19 -0
  102. package/project/.sdk/tm/go/core/context.go +1 -1
  103. package/project/.sdk/tm/go/feature/cost_feature.go +1 -1
  104. package/project/.sdk/tm/go/feature/paging_feature.go +5 -5
  105. package/project/.sdk/tm/go/feature/test_feature.go +12 -5
  106. package/project/.sdk/tm/go/test/custom_utility_test.go +15 -0
  107. package/project/.sdk/tm/go/test/feature_corpus_test.go +7 -0
  108. package/project/.sdk/tm/go/test/runner_test.go +46 -0
  109. package/project/.sdk/tm/go/test/struct_runner_test.go +7 -7
  110. package/project/.sdk/tm/go/test/struct_utility_test.go +63 -27
  111. package/project/.sdk/tm/go/utility/fetcher.go +2 -2
  112. package/project/.sdk/tm/go/utility/make_options.go +24 -5
  113. package/project/.sdk/tm/go/utility/make_point.go +1 -1
  114. package/project/.sdk/tm/go/utility/make_spec.go +17 -1
  115. package/project/.sdk/tm/go/utility/prepare_auth.go +1 -1
  116. package/project/.sdk/tm/go/utility/struct/voxgigstruct.go +370 -307
  117. package/project/.sdk/tm/go/utility/transform_request.go +29 -1
  118. package/project/.sdk/tm/go/utility/transform_response.go +20 -1
  119. package/project/.sdk/tm/java/test/CustomUtilityTest.java +10 -0
  120. package/project/.sdk/tm/java/test/FeatureCorpusTest.java +7 -0
  121. package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +6 -0
  122. package/project/.sdk/tm/java/test/RunnerSupport.java +43 -0
  123. package/project/.sdk/tm/java/utility/MakeOptions.java +84 -1
  124. package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +24 -0
  125. package/project/.sdk/tm/js/src/utility/StructUtility.js +2076 -1837
  126. package/project/.sdk/tm/js/test/feature/Corpus.test.js +9 -0
  127. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +21 -0
  128. package/project/.sdk/tm/lean/src/SdkUtility.lean +12 -1
  129. package/project/.sdk/tm/ocaml/sdk_runtime.ml +11 -0
  130. package/project/.sdk/tm/perl/utility/make_options.pm +18 -0
  131. package/project/.sdk/tm/php/test/FeatureCorpusTest.php +10 -1
  132. package/project/.sdk/tm/php/utility/MakeOptions.php +24 -0
  133. package/project/.sdk/tm/py/pkg/utility/make_options.py +20 -0
  134. package/project/.sdk/tm/py/test/runner.py +32 -0
  135. package/project/.sdk/tm/py/test/test_feature_corpus.py +10 -1
  136. package/project/.sdk/tm/rb/test/feature_corpus_test.rb +9 -1
  137. package/project/.sdk/tm/rb/utility/make_options.rb +17 -0
  138. package/project/.sdk/tm/rust/utility/make_options.rs +25 -0
  139. package/project/.sdk/tm/scala/utility/Make.scala +17 -0
  140. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +21 -0
  141. package/project/.sdk/tm/ts/src/feature/secrets/SecretsFeature.ts +419 -0
  142. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Providers.ts +1221 -0
  143. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +381 -0
  144. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +151 -0
  145. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +42 -0
  146. package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +18 -0
  147. package/project/.sdk/tm/ts/src/utility/StructUtility.ts +1321 -710
  148. package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +9 -0
  149. package/project/.sdk/tm/ts/test/feature/secrets/Secrets.test.ts +764 -0
  150. package/project/.sdk/tm/ts/test/omni.test.ts +84 -0
  151. package/project/.sdk/tm/ts/test/omni.ts +86 -0
  152. package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +6 -2
  153. package/project/.sdk/tm/ts/test/utility/StructUtility.test.ts +2 -2
  154. package/project/.sdk/tm/ts/test/utility.ts +122 -0
  155. package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +692 -0
  156. package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +238 -0
  157. package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +188 -0
  158. package/project/.sdk/tm/ts/test/vendor/omni/index.ts +38 -0
  159. package/project/.sdk/tm/zig/core/utility.zig +23 -0
  160. package/project/sdkgen-package.json +2 -1
  161. package/src/action/check.ts +18 -0
  162. package/src/cmp/AgentGuide.ts +1 -1
  163. package/src/cmp/AgentGuideContent.ts +9 -2
  164. package/src/cmp/Feature.ts +19 -0
  165. package/src/cmp/FeatureDocs.ts +7 -1
  166. package/src/cmp/ReadmeExplanation.ts +4 -1
  167. package/src/cmp/ReadmeFeatures.ts +1 -1
  168. package/src/cmp/ReadmeRefFeatures.ts +1 -1
  169. package/src/helpers/applicability.ts +123 -0
  170. package/src/helpers/collectDeps.ts +5 -1
  171. package/src/helpers/serverVars.ts +19 -0
  172. package/src/sdkgen.ts +9 -1
  173. package/src/utility.ts +10 -1
  174. package/project/.sdk/tm/ts/test/runner.ts +0 -425
@@ -0,0 +1,84 @@
1
+ // Smoke tests for the vendored omni runner itself: a runner that cannot
2
+ // FAIL a bad entry would turn every corpus suite vacuously green, so pin
3
+ // the failure paths, not just the happy one.
4
+
5
+ import { test, describe } from 'node:test'
6
+ import assert from 'node:assert'
7
+
8
+ import { SDK } from '..'
9
+
10
+ import { OmniError, makeRunner } from './omni'
11
+
12
+
13
+ // A minimal in-memory spec: no fixture file, no OMNI block (lenient v0,
14
+ // like the shared corpus).
15
+ const SPEC = {
16
+ primary: {
17
+ smoke: {
18
+ basic: {
19
+ set: [
20
+ { in: 1, out: 2 },
21
+ { in: 41, out: 42 },
22
+ ],
23
+ },
24
+ bad: {
25
+ set: [
26
+ { in: 1, out: 999 },
27
+ ],
28
+ },
29
+ err: {
30
+ set: [
31
+ { in: 0, err: 'zero refused' },
32
+ ],
33
+ },
34
+ },
35
+ },
36
+ }
37
+
38
+ const inc = (n: number) => {
39
+ if (0 === n) {
40
+ throw new Error('smoke: zero refused')
41
+ }
42
+ return n + 1
43
+ }
44
+
45
+
46
+ describe('omni', () => {
47
+
48
+ test('runset passes a correct subject', async () => {
49
+ const runner = await makeRunner(SPEC, SDK.test())
50
+ const R: any = await runner('smoke')
51
+ await R.runset(R.spec.basic, inc)
52
+ })
53
+
54
+
55
+ test('runset FAILS a wrong result, with OmniError', async () => {
56
+ const runner = await makeRunner(SPEC, SDK.test())
57
+ const R: any = await runner('smoke')
58
+ await assert.rejects(
59
+ () => R.runset(R.spec.bad, inc),
60
+ (err: any) => {
61
+ assert.equal(err.name, 'OmniError')
62
+ assert.ok(err instanceof OmniError)
63
+ assert.match(err.message, /result mismatch/)
64
+ return true
65
+ })
66
+ })
67
+
68
+
69
+ test('an expected error is matched, an unexpected one fails', async () => {
70
+ const runner = await makeRunner(SPEC, SDK.test())
71
+ const R: any = await runner('smoke')
72
+
73
+ await R.runset(R.spec.err, inc)
74
+
75
+ await assert.rejects(
76
+ () => R.runset(R.spec.err, (n: number) => n),
77
+ (err: any) => {
78
+ assert.equal(err.name, 'OmniError')
79
+ assert.match(err.message, /expected error did not occur/)
80
+ return true
81
+ })
82
+ })
83
+
84
+ })
@@ -0,0 +1,86 @@
1
+ // The corpus test runner: vendored @voxgig/omni behind the struct-runner
2
+ // API the corpus tests already use (`import { makeRunner } from '../omni'`
3
+ // instead of the old hand-vendored '../runner').
4
+ //
5
+ // Two local decisions, both required (see vendor/omni/compat.ts):
6
+ //
7
+ // 1. SPEC PATH. The compat shim's caller-directory heuristic resolves a
8
+ // relative test-file path one level too deep for this layout (loader in
9
+ // dist-test/, test files in dist-test/utility/) - its own header says a
10
+ // port must resolve the path itself. This module compiles to
11
+ // dist-test/omni.js, the same depth as the old dist-test/runner.js, so
12
+ // the existing TEST_JSON_FILE constant keeps working verbatim.
13
+ //
14
+ // 2. PROVIDER DELEGATION. Corpus-driven contexts get `ctx.client` set to
15
+ // the runner's provider. The stock structprovider forwards utility()
16
+ // and tester() but not options()/_features/_rootctx/_mode, which the
17
+ // corpus subjects and pipeline utilities (prepareHeaders via
18
+ // client.options(), test helpers via client._rootctx) do reach. The
19
+ // provider below is built by PROTOTYPE DELEGATION over the live SDK
20
+ // instance, so every SDK member resolves while the four omni hooks sit
21
+ // on top. Upstream omni's compat shim could adopt the same shape.
22
+
23
+ import { isAbsolute, join } from 'node:path'
24
+
25
+ import {
26
+ EXISTSMARK,
27
+ NULLMARK,
28
+ UNDEFMARK,
29
+ nullModifier,
30
+ structprovider,
31
+ } from './vendor/omni/compat'
32
+
33
+ import type { StructProvider, StructRunner } from './vendor/omni/compat'
34
+
35
+ import { OmniError, makeRunner as omnimakerunner } from './vendor/omni/index'
36
+
37
+ import type { Json } from './vendor/omni/index'
38
+
39
+
40
+ // Wrap the SDK as an omni provider WITHOUT hiding it: hooks from the stock
41
+ // structprovider, everything else through the prototype chain.
42
+ function sdkprovider(sdk: any): StructProvider {
43
+ const hooks = structprovider(sdk)
44
+ const provider = Object.assign(Object.create(sdk), hooks)
45
+
46
+ // DEF.client entries become SDK instances too - rewrap with the same
47
+ // delegating shape, not the stock one.
48
+ provider.client = async (options: Json) => sdkprovider(await sdk.tester(options))
49
+
50
+ return provider
51
+ }
52
+
53
+
54
+ // struct's makeRunner(testfile, client) signature, backed by vendored omni.
55
+ // Also accepts an already-parsed spec object (omni's own capability), which
56
+ // keeps smoke tests free of fixture files.
57
+ async function makeRunner(testfile: string | Json, client: any): Promise<StructRunner> {
58
+ const specref = 'string' !== typeof testfile ? testfile
59
+ : isAbsolute(testfile) ? testfile
60
+ : join(__dirname, testfile)
61
+
62
+ const provider = sdkprovider(client)
63
+ const runner = await omnimakerunner(specref, provider)
64
+
65
+ return async function structrunner(name: string, store?: any) {
66
+ const runpack = await runner(name, store)
67
+
68
+ return {
69
+ spec: runpack.spec,
70
+ runset: runpack.runset,
71
+ runsetflags: runpack.runsetflags,
72
+ subject: runpack.subject,
73
+ client: provider,
74
+ }
75
+ }
76
+ }
77
+
78
+
79
+ export {
80
+ EXISTSMARK,
81
+ NULLMARK,
82
+ UNDEFMARK,
83
+ OmniError,
84
+ makeRunner,
85
+ nullModifier,
86
+ }
@@ -5,7 +5,7 @@ import assert from 'node:assert'
5
5
 
6
6
  import {
7
7
  makeRunner,
8
- } from '../runner'
8
+ } from '../omni'
9
9
 
10
10
  import {
11
11
  SDK,
@@ -70,7 +70,11 @@ describe('PrimaryUtility', async () => {
70
70
  spec = run.spec
71
71
  runset = run.runset
72
72
  runsetflags = run.runsetflags
73
- client = run.client
73
+ // Under the old hand-vendored runner, run.client WAS the SDK; under
74
+ // omni it is the provider wrapping it. This suite treats the client as
75
+ // the SDK — including ASSIGNING to client._features, which prototype
76
+ // delegation cannot forward — so unwrap the real instance.
77
+ client = (run.client as any).sdk
74
78
  utility = client.utility()
75
79
  struct = utility.struct
76
80
  })
@@ -1,4 +1,4 @@
1
- // VERSION: @voxgig/struct 0.0.10
1
+ // VERSION: @voxgig/struct 0.3.2 (vendored via omni compat; see ../omni.ts)
2
2
  // RUN: npm test
3
3
  // RUN-SOME: npm run test-some --pattern=getpath
4
4
 
@@ -9,7 +9,7 @@ import {
9
9
  makeRunner,
10
10
  nullModifier,
11
11
  NULLMARK,
12
- } from '../runner'
12
+ } from '../omni'
13
13
 
14
14
 
15
15
  import {
@@ -90,6 +90,7 @@ type TestControl = {
90
90
  unit?: { direct?: any[], entityOp?: any[] }
91
91
  }
92
92
  live?: { delayMs?: number }
93
+ client?: { options?: Record<string, any> }
93
94
  [k: string]: any
94
95
  }
95
96
  [k: string]: any
@@ -167,6 +168,55 @@ function skipIfMissingIds(t: any, setup: any, requiredKeys: string[]): boolean {
167
168
  }
168
169
 
169
170
 
171
+ // Extra SDK options every LIVE client is constructed with, from
172
+ // sdk-test-control.json `test.client.options`.
173
+ //
174
+ // The generated live client knows two things: the base URL (from the spec)
175
+ // and the credential (from the environment). Everything else about how a
176
+ // particular API wants to be talked to — which features to switch on, and
177
+ // with what settings — is a property of THAT API, known to the project and
178
+ // to nothing in the toolchain.
179
+ //
180
+ // The concrete case: an API that issues short-lived access tokens needs the
181
+ // `secrets` feature's exchange turned on and pointed at its token endpoint,
182
+ // or the live suite gets a handful of calls in and then fails 401 with
183
+ // nothing explaining why. There was no seam for that, so the suite could
184
+ // not be run at all.
185
+ //
186
+ // A committed FILE rather than an environment variable, because it is
187
+ // configuration, not a secret: it belongs in the repo next to the API it
188
+ // describes, where it can be read and reviewed. Secrets still come from the
189
+ // environment (the providers this block names read them).
190
+ //
191
+ // Merged UNDER the generated fields, so the suite's own base/apikey/server
192
+ // values win — this adds to the live client, it does not redirect it.
193
+ //
194
+ // That contract is enforced HERE rather than left to each merge site: the
195
+ // generated object only names a field when the model calls for one, so a
196
+ // `base` in this block would face no competing value and would silently
197
+ // redirect the whole suite — credential included — to another host. The
198
+ // reserved fields are stripped once, where the block is read, so every
199
+ // caller gets the same guarantee whether or not it happens to emit them.
200
+ const LIVE_RESERVED = ['base', 'prefix', 'suffix', 'server', 'apikey', 'secret']
201
+
202
+ function liveClientOptions(): Record<string, any> {
203
+ const opts = loadTestControl()?.test?.client?.options
204
+
205
+ if (null == opts || 'object' !== typeof opts) {
206
+ return {}
207
+ }
208
+
209
+ const out: Record<string, any> = {}
210
+ for (const key of Object.keys(opts)) {
211
+ if (!LIVE_RESERVED.includes(key)) {
212
+ out[key] = (opts as any)[key]
213
+ }
214
+ }
215
+
216
+ return out
217
+ }
218
+
219
+
170
220
  // Per-test live pacing delay (ms). Read from sdk-test-control.json
171
221
  // `test.live.delayMs`; defaults to 500ms if absent or invalid.
172
222
  function liveDelayMs(): number {
@@ -188,6 +238,76 @@ function liveDelay(liveEnvVar: string): () => Promise<void> {
188
238
  }
189
239
 
190
240
 
241
+ // Load a .env.local file into process.env, replacing the `dotenv`
242
+ // devDependency — the SDK's last non-tooling package.
243
+ //
244
+ // Same semantics dotenv gave these tests: a missing file is fine, and a key
245
+ // already present in the environment is never overridden, so an explicit
246
+ // export still beats the file.
247
+ //
248
+ // WHY NOT sekreto's parsedotenv, which does the same job and is already
249
+ // vendored: sekreto lives INSIDE the secrets feature container, so `target
250
+ // add` removes it whenever a project does not select that feature — while
251
+ // these entity tests need the loader either way. Importing it here would
252
+ // couple every generated test suite to an optional feature. The parser is
253
+ // small enough that a second, independent copy is cheaper than that
254
+ // coupling; it deliberately handles only what a .env.local holds.
255
+ function loadEnvLocal(file: string): void {
256
+ let text: string
257
+ try {
258
+ text = Fs.readFileSync(file, 'utf8')
259
+ }
260
+ catch (err: any) {
261
+ if ('ENOENT' === err.code) {
262
+ return
263
+ }
264
+ throw err
265
+ }
266
+
267
+ for (const raw of text.split(/\r?\n/)) {
268
+ const line = raw.trim()
269
+
270
+ // Blank and comment lines. A '#' INSIDE a value is not a comment.
271
+ if ('' === line || line.startsWith('#')) {
272
+ continue
273
+ }
274
+
275
+ const eq = line.indexOf('=')
276
+ if (0 >= eq) {
277
+ continue
278
+ }
279
+
280
+ const key = line.slice(0, eq).trim().replace(/^export\s+/, '')
281
+ let val = line.slice(eq + 1).trim()
282
+
283
+ const quote = ("'" === val[0] || '"' === val[0]) ? val[0] : ''
284
+
285
+ if ('' !== quote) {
286
+ // Quoted: the value runs to the CLOSING quote, and a '#' inside it is
287
+ // part of the value. Anything after the closing quote is a comment.
288
+ const close = val.indexOf(quote, 1)
289
+ val = 0 < close ? val.slice(1, close) : val.slice(1)
290
+ }
291
+ else {
292
+ // Unquoted: the first '#' starts an inline comment, with or without
293
+ // preceding whitespace — `A=a#b` is `a` to dotenv, not `a#b`.
294
+ // Dropping this made `KEY=secret # note` resolve to the whole string
295
+ // including the note, and a generated live test would then send that
296
+ // as the credential. Verified against dotenv's own parse().
297
+ const hash = val.indexOf('#')
298
+ if (0 <= hash) {
299
+ val = val.slice(0, hash)
300
+ }
301
+ val = val.trim()
302
+ }
303
+
304
+ if (undefined === process.env[key]) {
305
+ process.env[key] = val
306
+ }
307
+ }
308
+ }
309
+
310
+
191
311
  export {
192
312
  makeStepData,
193
313
  makeMatch,
@@ -199,6 +319,8 @@ export {
199
319
  isControlSkipped,
200
320
  maybeSkipControl,
201
321
  skipIfMissingIds,
322
+ liveClientOptions,
202
323
  liveDelayMs,
203
324
  liveDelay,
325
+ loadEnvLocal,
204
326
  }