@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
@@ -27,7 +27,7 @@ const ReadmeFeatures = cmp(function ReadmeFeatures(props: any) {
27
27
  return
28
28
  }
29
29
 
30
- const features: FeatureDoc[] = featureDocs(model)
30
+ const features: FeatureDoc[] = featureDocs(model, target)
31
31
 
32
32
  if (0 === features.length) {
33
33
  return
@@ -19,7 +19,7 @@ const ReadmeRefFeatures = cmp(function ReadmeRefFeatures(props: any) {
19
19
  const { target, ctx$ } = props
20
20
  const { model } = ctx$
21
21
 
22
- const features: FeatureDoc[] = featureDocs(model)
22
+ const features: FeatureDoc[] = featureDocs(model, target)
23
23
 
24
24
  if (0 === features.length) {
25
25
  return
@@ -0,0 +1,64 @@
1
+ import Path from 'node:path'
2
+
3
+ import {
4
+ cmp,
5
+ File,
6
+ Fragment,
7
+ } from 'jostraca'
8
+
9
+
10
+ const TEST_CONTROL_FILE = 'sdk-test-control.json'
11
+
12
+
13
+ // Keep the blanket per-target `Copy({ from: 'tm/<lang>' })` from restoring the
14
+ // template default over a project's edited control file. One regex for every
15
+ // target: the file sits under `test/`, `t/`, `tests/` or `sdktest/` depending
16
+ // on the language, and the name is what identifies it either way.
17
+ const TEST_CONTROL_EXCLUDE = new RegExp('(^|/)' + TEST_CONTROL_FILE + '$')
18
+
19
+
20
+ // `sdk-test-control.json` is the ONE generated file a project is meant to edit
21
+ // by hand: it names the tests to skip, the live pacing, and the extra client
22
+ // options a live run needs (`test.client.options`).
23
+ //
24
+ // It shipped as part of each target's blanket `Copy({ from: 'tm/<lang>' })`,
25
+ // and a Copy has no per-file "leave it alone if it already exists" — so every
26
+ // `npm run generate` overwrote whatever the project had put there. The
27
+ // documented workaround, editing the template master under `.sdk/tm/`, is
28
+ // worse: `doctor` reports it as drift and the next `target add <lang>` reverts
29
+ // it.
30
+ //
31
+ // So it is emitted HERE instead, write-once: `File({ exclude: true })` returns
32
+ // early when the file already exists (FileOp), which is exactly the semantics
33
+ // the file is documented to have. A project without one yet gets the
34
+ // template's default; a project that has edited one keeps its edits across
35
+ // both `generate` and `target add`.
36
+ //
37
+ // The content still comes from the target's own template, so there remains
38
+ // exactly one copy of the default on disk.
39
+ //
40
+ // The template is addressed the SAME way the Copy this replaces addressed it:
41
+ // `tm/<lang>/...` relative to the working directory, which is the project's
42
+ // `.sdk`. Resolved to absolute here because Fragment resolves a RELATIVE
43
+ // `from` against the OUTPUT folder instead — a different base, and the one
44
+ // place these two could silently disagree.
45
+ const TestControl = cmp(function TestControl(props: {
46
+ target: { name: string }
47
+ dir: string
48
+ ctx$?: any
49
+ }) {
50
+ const { target, dir } = props
51
+
52
+ File({ name: TEST_CONTROL_FILE, exclude: true }, () => {
53
+ Fragment({
54
+ from: Path.resolve('tm', target.name, dir, TEST_CONTROL_FILE)
55
+ })
56
+ })
57
+ })
58
+
59
+
60
+ export {
61
+ TestControl,
62
+ TEST_CONTROL_FILE,
63
+ TEST_CONTROL_EXCLUDE,
64
+ }
@@ -0,0 +1,123 @@
1
+ // Which features a target can actually take.
2
+ //
3
+ // THE ONE PLACE THIS RULE LIVES. `Feature` (what source is copied), the
4
+ // per-language `Config_<lang>` and `Main_<lang>` components (what the
5
+ // generated registry, imports and metadata contain) and `configDefinition`
6
+ // (the embedded config) must all reach the same answer for one
7
+ // (feature, target) pair. They previously each read the raw feature map,
8
+ // which is how a feature with no source for a target still reached that
9
+ // target's imports.
10
+ //
11
+ // The shape is `docs/design/feature-tags.md`: a feature declares what it
12
+ // NEEDS, a target declares what it PROVIDES, and the feature applies when
13
+ // `needs` is a subset of `provides`. Both default to empty, so a feature
14
+ // that needs nothing applies everywhere — every feature that existed
15
+ // before this gate keeps its behaviour with no model edit, which is what
16
+ // makes the change additive.
17
+ //
18
+ // A named target list was the cheaper design and fails on time: it has to
19
+ // be edited for targets the feature's author has never heard of, so a
20
+ // published package starves every target added after its release. A tag is
21
+ // a claim about the feature that stays true as the target set grows.
22
+
23
+ import { KIT, getModelPath } from '@voxgig/apidef'
24
+
25
+
26
+ // Tags are a MAP keyed by tag name, `{ sekreto: true }` — see the schema
27
+ // note on `needs`. A map because aontu unifies maps by key, so a package
28
+ // can add one tag without restating the others; and because a defaulted
29
+ // list disjunction in this position hangs the unifier on a real project's
30
+ // model. A tag counts only when its value is true, so a tag can be turned
31
+ // off by overriding it rather than by deleting the key.
32
+ //
33
+ // A list is still accepted, for a hand-written model that used the shape
34
+ // docs/design/feature-tags.md first proposed.
35
+ function tags(val: any): string[] {
36
+ if (Array.isArray(val)) {
37
+ return val.filter((t: any) => 'string' === typeof t)
38
+ }
39
+
40
+ if (null == val || 'object' !== typeof val) {
41
+ return []
42
+ }
43
+
44
+ return Object.keys(val).filter((name: string) => true === val[name])
45
+ }
46
+
47
+
48
+ // Does this feature apply to this target?
49
+ function featureApplies(feature: any, target: any): boolean {
50
+ const needs = tags(feature && feature.needs)
51
+
52
+ // The common case, and the reason this is additive: no needs, applies
53
+ // anywhere. Checked first so a target with no `provides` is unaffected.
54
+ if (0 === needs.length) {
55
+ return true
56
+ }
57
+
58
+ const provides = tags(target && target.provides)
59
+
60
+ return needs.every((need: string) => provides.includes(need))
61
+ }
62
+
63
+
64
+ // The features that apply to `target`, in the same map shape
65
+ // `getModelPath(model, 'main.<kit>.feature')` returns — already
66
+ // active-filtered by getModelPath, then gated by the tags.
67
+ //
68
+ // `target` may be the target object or its name; components hold the
69
+ // object, `configDefinition` is only given the name.
70
+ function targetFeatures(model: any, target: any): Record<string, any> {
71
+ const feature = getModelPath(model, `main.${KIT}.feature`, { required: false }) || {}
72
+
73
+ const t = 'string' === typeof target ?
74
+ (getModelPath(model, `main.${KIT}.target`, { required: false }) || {})[target] :
75
+ target
76
+
77
+ // A name that names no target gates nothing: a caller that cannot say
78
+ // which target it is generating for must not silently lose features.
79
+ if (null == t) {
80
+ return feature
81
+ }
82
+
83
+ // Keyed off the map, not off `f.name`: the key is what every consumer
84
+ // indexes by, and rebuilding from a field would drop an entry whose
85
+ // model never set one. Order is not preserved on purpose — consumers
86
+ // re-sort with `each`, which is what keeps output byte-stable.
87
+ const applies: Record<string, any> = {}
88
+ for (const [name, f] of Object.entries(feature as Record<string, any>)) {
89
+ if (featureApplies(f, t)) {
90
+ applies[name] = f
91
+ }
92
+ }
93
+
94
+ return applies
95
+ }
96
+
97
+
98
+ // THE CLOSED VOCABULARY. The schema comment says applicability tags are a
99
+ // closed set; without this, `provides: &: boolean` accepts any key, so a
100
+ // typo (`sekrreto`) compiles cleanly and silently makes the feature apply
101
+ // NOWHERE — the worst failure shape, because the feature simply vanishes
102
+ // with no diagnostic.
103
+ //
104
+ // Extended by adding a tag here AND documenting it in model/sdkgen.aon.
105
+ const TAGS = [
106
+ // A vendored sekreto port lives in this target's feature container.
107
+ 'sekreto',
108
+ ]
109
+
110
+
111
+ // The tags named by `needs`/`provides` that are not in the vocabulary.
112
+ function unknownTags(val: any): string[] {
113
+ return tags(val).filter((t: string) => !TAGS.includes(t))
114
+ }
115
+
116
+
117
+ export {
118
+ featureApplies,
119
+ targetFeatures,
120
+ tags as featureTags,
121
+ unknownTags,
122
+ TAGS,
123
+ }
@@ -11,6 +11,8 @@
11
11
  // need extra fields like `dep.replace` (go module replace directives) or
12
12
  // `dep.kind` (prod/dev/peer).
13
13
 
14
+ import { targetFeatures } from './applicability'
15
+
14
16
  import { each } from 'jostraca'
15
17
  import { KIT, getModelPath } from '@voxgig/apidef'
16
18
 
@@ -30,7 +32,9 @@ function collectDeps(
30
32
  log?: any,
31
33
  ): DepEntry[] {
32
34
  const out: DepEntry[] = []
33
- const feature = getModelPath(model, `main.${KIT}.feature`)
35
+ // Gated: a feature that does not apply to this target must not flow a
36
+ // dependency into its generated manifest.
37
+ const feature = targetFeatures(model, targetName)
34
38
 
35
39
  // Deduplicate by package name. Two features can require the same package
36
40
  // (or a feature and the target itself can), and every Package_<lang>.ts
@@ -260,6 +260,134 @@ function srcFeatureExcludes(model: any): RegExp[] {
260
260
  }
261
261
 
262
262
 
263
+ // The same rule as srcFeatureExcludes, ONE LEVEL DEEPER: a feature's
264
+ // declared-but-inactive PLUGINS.
265
+ //
266
+ // A plugin is a feature's optional part — the `secrets` feature over
267
+ // sekreto has one per provider kind, and each carries its own platform
268
+ // cost (node:crypto for AWS request signing, fetch for seven HTTP vault
269
+ // clients). A project whose chain is `[dotenv, env]` should carry neither,
270
+ // and before the plugin trim it carried both, because every kind was
271
+ // reachable from a single import.
272
+ //
273
+ // Layout is `src/feature/<feature>/plugin/<plugin>/`, so a plugin's tree
274
+ // is excluded exactly as a feature's is. Only declared-but-inactive
275
+ // plugins are excluded: a directory the model never mentions is left
276
+ // alone, matching srcFeatureExcludes' treatment of `base`.
277
+ //
278
+ // Note this trims a plugin belonging to an ACTIVE feature. An inactive
279
+ // feature's whole tree — plugins included — is already gone via
280
+ // srcFeatureExcludes, so walking its plugins here would be redundant, and
281
+ // asking the model for them would fail on the `only_active` filter anyway.
282
+ // One feature's inactive-plugin patterns.
283
+ //
284
+ // Separate from pluginExcludes(model) because the two COPIES that need it
285
+ // see different things: Main copies the whole target tree and has the
286
+ // model, while the Feature component copies one feature's tree and has
287
+ // only that feature. Feature's copy is the one that actually matters —
288
+ // it runs for every ACTIVE feature, so without this an active feature's
289
+ // plugins were all copied regardless, and Main's exclude never saw them.
290
+ function pluginExcludesFor(model: any, fname: string): RegExp[] {
291
+ if (null == model || null == fname) {
292
+ return []
293
+ }
294
+
295
+ // `only_active: false`, and this is the whole subtlety. The feature
296
+ // object a component is handed has ALREADY been filtered, so its
297
+ // `plugin` map contains only the ACTIVE plugins — and a function looking
298
+ // there for something inactive finds nothing and excludes nothing. That
299
+ // is exactly the trap srcFeatureExcludes documents, hit again one level
300
+ // down: the first cut of this read the filtered feature and silently
301
+ // trimmed nothing while every test passed.
302
+ const plugins = getModelPath(model,
303
+ `main.${KIT}.feature.${fname}.plugin`,
304
+ { required: false, only_active: false }) || {}
305
+
306
+ const out: RegExp[] = []
307
+ const esc = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
308
+
309
+ for (const pname of Object.keys(plugins).sort()) {
310
+ const plugin = plugins[pname]
311
+ if (false !== plugin?.active) continue
312
+
313
+ const declared = plugin.path || []
314
+
315
+ if (0 < declared.length) {
316
+ for (const one of declared) {
317
+ // FEATURE-RELATIVE, because jostraca tests a Copy's candidates
318
+ // against the path RELATIVE TO THAT COPY'S ROOT — `sekreto/
319
+ // provider/aws.ts`, not `src/feature/secrets/sekreto/provider/
320
+ // aws.ts`. The Feature component's Copy is rooted at
321
+ // `tm/<target>/src/feature/<feature>`, so a full declared path
322
+ // never matches and the trim silently does nothing.
323
+ //
324
+ // Worth stating because it is invisible: an exclude that matches
325
+ // nothing is indistinguishable from no exclude at all, and both
326
+ // the patterns and the model were correct while nothing was
327
+ // trimmed. Main's copy IS rooted at the target root, which is why
328
+ // pluginExcludes below keeps the full path.
329
+ const rel = String(one).replace(
330
+ new RegExp('^src/feature/' + esc(fname) + '/'), '')
331
+ const pat = esc(rel)
332
+ out.push(new RegExp('(^|/)' + pat.replace(/\\\/$/, '') +
333
+ (/\/$/.test(rel) ? '/' : '$')))
334
+ }
335
+ continue
336
+ }
337
+
338
+ out.push(new RegExp('(^|/)plugin/' + esc(pname) + '/'))
339
+ }
340
+
341
+ return out
342
+ }
343
+
344
+
345
+ function pluginExcludes(model: any): RegExp[] {
346
+ const active = getModelPath(model, `main.${KIT}.feature`,
347
+ { required: false }) || {}
348
+
349
+ const out: RegExp[] = []
350
+ const esc = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
351
+
352
+ for (const fname of Object.keys(active)) {
353
+ const all = getModelPath(model,
354
+ `main.${KIT}.feature.${fname}.plugin`,
355
+ { required: false, only_active: false }) || {}
356
+ const on = getModelPath(model,
357
+ `main.${KIT}.feature.${fname}.plugin`,
358
+ { required: false }) || {}
359
+
360
+ for (const pname of Object.keys(all)) {
361
+ if (null != on[pname]) continue
362
+
363
+ // DECLARED paths first. A plugin's files are often not free to
364
+ // move — sekreto's provider modules are vendored, and both the
365
+ // vendoring guard and their own relative imports pin them at
366
+ // upstream's directory depth — so a plugin says which paths it
367
+ // owns rather than being assumed to own a directory.
368
+ const declared = all[pname].path || []
369
+
370
+ if (0 < declared.length) {
371
+ for (const one of declared) {
372
+ const pat = esc(String(one))
373
+ // A trailing slash means a folder and everything under it;
374
+ // anything else matches that path exactly, as featureExcludes
375
+ // does for a file source.
376
+ out.push(new RegExp('(^|/)' + pat.replace(/\\\/$/, '') +
377
+ (/\/$/.test(String(one)) ? '/' : '$')))
378
+ }
379
+ continue
380
+ }
381
+
382
+ out.push(new RegExp(
383
+ '(^|/)src/feature/' + esc(fname) + '/plugin/' + esc(pname) + '/'))
384
+ }
385
+ }
386
+
387
+ return out
388
+ }
389
+
390
+
263
391
  export type {
264
392
  FeatureSource,
265
393
  FeatureEntry,
@@ -275,4 +403,6 @@ export {
275
403
  featureExcludes,
276
404
  fullsetExcludes,
277
405
  srcFeatureExcludes,
406
+ pluginExcludes,
407
+ pluginExcludesFor,
278
408
  }
@@ -88,6 +88,24 @@ function serverVariables(model: any): ServerVar[] {
88
88
  }
89
89
 
90
90
 
91
+ // The environment variable a generated LIVE test reads one server variable
92
+ // from: `<PROJ>_SERVER_<NAME>`, e.g. ELEMENTDEMO_SERVER_ACCOUNT_ID.
93
+ //
94
+ // A live client CANNOT BE CONSTRUCTED without every required server
95
+ // variable — makeOptions raises rather than issue requests to a URL with a
96
+ // literal `{account_id}` in it — so an SDK whose spec templates its server
97
+ // URL had no runnable live suite at all until the generated tests could be
98
+ // told the values. The apikey has had `<PROJ>_APIKEY` since the beginning;
99
+ // this is the same idea for the other half of "where do I point".
100
+ //
101
+ // One variable per NAME rather than one JSON blob: the names come from the
102
+ // spec, they are few, and a shell export per name is what a CI secret store
103
+ // and a .env file both hold naturally.
104
+ function serverVarEnv(projenvname: string, name: string): string {
105
+ return projenvname + '_SERVER_' + String(name).toUpperCase()
106
+ }
107
+
108
+
91
109
  // Does the model's server URL contain any {name} placeholders at all?
92
110
  function hasServerVariables(model: any): boolean {
93
111
  const info = model?.main?.kit?.info || model?.main?.KIT?.info
@@ -100,6 +118,7 @@ function hasServerVariables(model: any): boolean {
100
118
  export {
101
119
  serverVariables,
102
120
  hasServerVariables,
121
+ serverVarEnv,
103
122
  }
104
123
 
105
124
  export type { ServerVar }
package/src/sdkgen.ts CHANGED
@@ -22,6 +22,7 @@ import { SdkGenError, requirePath, isAuthActive, resolveAuthPrefix, isHttpBasicA
22
22
  configReprSetting, configDefinition, clean, rawStringLiteral } from './utility'
23
23
 
24
24
  import { Main } from './cmp/Main'
25
+ import { featureApplies, targetFeatures, featureTags, unknownTags, TAGS } from './helpers/applicability'
25
26
  import { ExternalTarget } from './cmp/ExternalTarget'
26
27
  import { Docs, DocsItem } from './cmp/Docs'
27
28
  import { ExternalDocs } from './cmp/ExternalDocs'
@@ -39,6 +40,7 @@ import { License } from './cmp/License'
39
40
  import { Security } from './cmp/Security'
40
41
  import { Changelog } from './cmp/Changelog'
41
42
  import { Test } from './cmp/Test'
43
+ import { TestControl, TEST_CONTROL_EXCLUDE } from './cmp/TestControl'
42
44
  import { ReadmeInstall } from './cmp/ReadmeInstall'
43
45
  import { ReadmeQuick } from './cmp/ReadmeQuick'
44
46
  import { ReadmeErrors } from './cmp/ReadmeErrors'
@@ -61,7 +63,7 @@ import type { DepEntry } from './helpers/collectDeps'
61
63
  import { canonToType, canonToDtype, canonKey, canonScalarKey } from './helpers/canonType'
62
64
  import { OP_SUFFIX, opTypeName, opParams, ownPoint, opActions, entityActions, entityPath, opRequestShape, entityIdField, entityDataIdField, entityOps, entityPrimaryOp, pickExampleEntity, entityClassName, entityTypeCollisions, warnEntityTypeCollisions, deriveEntityNames, entityCollection } from './helpers/opShape'
63
65
  import { isReservedName, safeVarName, exampleVarName, phpEntityAccessor, entityCacheField, isRbCoreConstant, isRbSdkConstant, rbSafeTypeName, isSwiftSdkType, swiftSafeTypeName, isPhpReservedType, isPhpSdkClass, phpSafeTypeName, isTsReservedType, tsSafeTypeName, jsProp, jsOptProp, jsKey } from './helpers/naming'
64
- import { serverVariables, hasServerVariables } from './helpers/serverVars'
66
+ import { serverVariables, hasServerVariables, serverVarEnv } from './helpers/serverVars'
65
67
  import { primaryOpCall, idLiteral, matchArg, dataArg, litFor } from './helpers/opExample'
66
68
  import type { ExampleLang } from './helpers/opExample'
67
69
  import { liveStrict } from './helpers/testPolicy'
@@ -72,6 +74,8 @@ import {
72
74
  featureExcludes,
73
75
  fullsetExcludes,
74
76
  srcFeatureExcludes,
77
+ pluginExcludes,
78
+ pluginExcludesFor,
75
79
  } from './helpers/featureSource'
76
80
  import type { FeatureSource } from './helpers/featureSource'
77
81
  import { stationLibrary } from './helpers/station'
@@ -1071,6 +1075,8 @@ export {
1071
1075
  Entity,
1072
1076
  Feature,
1073
1077
  Test,
1078
+ TestControl,
1079
+ TEST_CONTROL_EXCLUDE,
1074
1080
  Readme,
1075
1081
  ReadmeTop,
1076
1082
  AgentGuideTop,
@@ -1153,6 +1159,7 @@ export {
1153
1159
  tsSafeTypeName,
1154
1160
  serverVariables,
1155
1161
  hasServerVariables,
1162
+ serverVarEnv,
1156
1163
  liveStrict,
1157
1164
  primaryOpCall,
1158
1165
  idLiteral,
@@ -1165,8 +1172,16 @@ export {
1165
1172
  featureExcludes,
1166
1173
  fullsetExcludes,
1167
1174
  srcFeatureExcludes,
1175
+ pluginExcludes,
1176
+ pluginExcludesFor,
1168
1177
  stationLibrary,
1169
1178
 
1179
+ featureApplies,
1180
+ targetFeatures,
1181
+ featureTags,
1182
+ unknownTags,
1183
+ TAGS,
1184
+
1170
1185
  definitionPath,
1171
1186
  definitionFolder,
1172
1187
  definitionNames,
package/src/utility.ts CHANGED
@@ -5,6 +5,8 @@ import { JostracaResult, each } from 'jostraca'
5
5
 
6
6
  import { KIT, getModelPath } from '@voxgig/apidef'
7
7
 
8
+ import { targetFeatures } from './helpers/applicability'
9
+
8
10
  import { serverVariables } from './helpers/serverVars'
9
11
  import { packageVersion } from './helpers/packageMeta'
10
12
 
@@ -80,6 +82,34 @@ function isHttpBasicAuth(model: any): boolean {
80
82
  }
81
83
 
82
84
 
85
+ // The API's ACCESS-TOKEN EXCHANGE, as apidef recorded it from the spec
86
+ // (main.kit.info.security.exchange): where the token endpoint lives, and the
87
+ // field names it sends and answers with. Null when the spec describes none,
88
+ // which is the common case.
89
+ //
90
+ // These are FACTS ABOUT THE API, so they belong in the generated config
91
+ // rather than in a project's hand-written model: an SDK whose spec says the
92
+ // exchange is at `auth/token` should not need to be told so again. A feature
93
+ // opts in to receiving them with `spec: { authexchange: '<options-key>' }`;
94
+ // configDefinition does the overlay.
95
+ //
96
+ // Deliberately NOT included: `active`. Whether a client performs the
97
+ // exchange is the project's decision (some resolve a static credential
98
+ // through the same feature on an API that also has a token endpoint), so
99
+ // apidef records only what the exchange IS, never that it should run.
100
+ function resolveAuthExchange(model: any): Record<string, any> | null {
101
+ const security = getModelPath(model, `main.${KIT}.info.security`,
102
+ { only_active: false, required: false })
103
+
104
+ const exchange = security?.exchange
105
+ if (null == exchange || 'object' !== typeof exchange) {
106
+ return null
107
+ }
108
+
109
+ return exchange
110
+ }
111
+
112
+
83
113
  function requirePath(ctx$: any, path: string, flags?: { ignore?: boolean }): any {
84
114
  const fullpath = resolvePath(ctx$, path)
85
115
  const ignore = null == flags?.ignore ? false : flags.ignore
@@ -116,6 +146,7 @@ export {
116
146
  requirePath,
117
147
  isAuthActive,
118
148
  resolveAuthPrefix,
149
+ resolveAuthExchange,
119
150
  isHttpBasicAuth,
120
151
  SdkGenError,
121
152
  CONFIG_DATA_THRESHOLD,
@@ -299,6 +330,15 @@ function rawStringLiteral(s: string): string {
299
330
  }
300
331
 
301
332
 
333
+ // The closed vocabulary of spec-derived facts a feature may ask for by
334
+ // declaring `spec: { <fact>: <options-key> }`. Closed, and resolved through
335
+ // one function each, so a feature cannot reach arbitrarily into the model
336
+ // and a fact's shape is defined in exactly one place.
337
+ const SPEC_FACTS: Record<string, (model: any) => any> = {
338
+ authexchange: resolveAuthExchange,
339
+ }
340
+
341
+
302
342
  // THE CANONICAL CONFIG OBJECT, and the JSON the threshold is measured on.
303
343
  //
304
344
  // Every target builds its config from this one function, so the literal a
@@ -312,7 +352,14 @@ function rawStringLiteral(s: string): string {
312
352
  // across runs exactly like the literal it replaces.
313
353
  function configDefinition(model: any, targetname?: string): { def: any, json: string } {
314
354
  const entity = getModelPath(model, `main.${KIT}.entity`)
315
- const feature = getModelPath(model, `main.${KIT}.feature`)
355
+
356
+ // Gated by the target when one is named, so the embedded config cannot
357
+ // advertise a feature this target has no implementation for — the exact
358
+ // hybrid state the applicability gate exists to prevent. With no
359
+ // targetname (a caller that cannot say which target it is building) the
360
+ // helper returns every active feature, i.e. the old behaviour.
361
+ const feature = targetFeatures(model, targetname)
362
+
316
363
  const headers = getModelPath(model, `main.${KIT}.config.headers`) || {}
317
364
 
318
365
  const authActive = isAuthActive(model)
@@ -352,6 +399,29 @@ function configDefinition(model: any, targetname?: string): { def: any, json: st
352
399
  if (null != f.transport && '' !== f.transport) {
353
400
  fdef.transport = String(f.transport)
354
401
  }
402
+
403
+ // SPEC-DERIVED OPTIONS. A feature declares `spec: { <fact>: <options
404
+ // key> }` to receive facts apidef recorded from the OpenAPI spec — the
405
+ // same declare-never-infer rule `needs` and `transport` follow, and for
406
+ // the same reason: the alternative is this function knowing feature
407
+ // names, which is exactly the coupling the generic feature loop exists
408
+ // to avoid.
409
+ //
410
+ // The fact overlays the feature's DECLARED DEFAULTS, and rightly: a
411
+ // default like `path: 'auth/token'` is a generic guess, while the spec
412
+ // states where this API's endpoint actually is. A project that needs
413
+ // something else still overrides at runtime through `options.feature`,
414
+ // which beats the embedded config either way.
415
+ for (const factname of Object.keys(f.spec || {}).sort()) {
416
+ const optkey = f.spec[factname]
417
+ const fact = SPEC_FACTS[factname]?.(model)
418
+ if (null == fact || null == optkey || '' === optkey) {
419
+ continue
420
+ }
421
+ fdef.options = { ...(fdef.options || {}) }
422
+ fdef.options[optkey] = { ...(fdef.options[optkey] || {}), ...fact }
423
+ }
424
+
355
425
  featureDefs[f.name] = fdef
356
426
  })
357
427