@voxgig/sdkgen 3.4.7 → 3.5.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 (194) hide show
  1. package/bin/voxgig-sdkgen +78 -10
  2. package/dist/action/action.d.ts +5 -2
  3. package/dist/action/action.js +113 -6
  4. package/dist/action/action.js.map +1 -1
  5. package/dist/action/check.d.ts +15 -0
  6. package/dist/action/check.js +347 -0
  7. package/dist/action/check.js.map +1 -0
  8. package/dist/action/dispatch.d.ts +8 -0
  9. package/dist/action/dispatch.js +74 -0
  10. package/dist/action/dispatch.js.map +1 -0
  11. package/dist/action/docs.d.ts +4 -0
  12. package/dist/action/docs.js +236 -0
  13. package/dist/action/docs.js.map +1 -0
  14. package/dist/action/doctor.d.ts +5 -2
  15. package/dist/action/doctor.js +526 -74
  16. package/dist/action/doctor.js.map +1 -1
  17. package/dist/action/feature.js +96 -17
  18. package/dist/action/feature.js.map +1 -1
  19. package/dist/action/kind.d.ts +30 -0
  20. package/dist/action/kind.js +258 -0
  21. package/dist/action/kind.js.map +1 -0
  22. package/dist/action/package.d.ts +25 -0
  23. package/dist/action/package.js +852 -0
  24. package/dist/action/package.js.map +1 -0
  25. package/dist/action/resolve.d.ts +18 -0
  26. package/dist/action/resolve.js +280 -0
  27. package/dist/action/resolve.js.map +1 -0
  28. package/dist/action/target.d.ts +7 -1
  29. package/dist/action/target.js +240 -97
  30. package/dist/action/target.js.map +1 -1
  31. package/dist/cmp/Docs.d.ts +4 -0
  32. package/dist/cmp/Docs.js +98 -0
  33. package/dist/cmp/Docs.js.map +1 -0
  34. package/dist/cmp/ExternalDocs.d.ts +2 -0
  35. package/dist/cmp/ExternalDocs.js +43 -0
  36. package/dist/cmp/ExternalDocs.js.map +1 -0
  37. package/dist/helpers/definition.d.ts +5 -0
  38. package/dist/helpers/definition.js +72 -0
  39. package/dist/helpers/definition.js.map +1 -0
  40. package/dist/helpers/featureSource.d.ts +8 -2
  41. package/dist/helpers/featureSource.js +61 -12
  42. package/dist/helpers/featureSource.js.map +1 -1
  43. package/dist/helpers/junk.d.ts +11 -0
  44. package/dist/helpers/junk.js +149 -0
  45. package/dist/helpers/junk.js.map +1 -0
  46. package/dist/helpers/manifest.d.ts +50 -0
  47. package/dist/helpers/manifest.js +328 -0
  48. package/dist/helpers/manifest.js.map +1 -0
  49. package/dist/helpers/modelcheck.d.ts +22 -0
  50. package/dist/helpers/modelcheck.js +203 -0
  51. package/dist/helpers/modelcheck.js.map +1 -0
  52. package/dist/helpers/semver.d.ts +4 -0
  53. package/dist/helpers/semver.js +174 -0
  54. package/dist/helpers/semver.js.map +1 -0
  55. package/dist/helpers/shipped.d.ts +4 -0
  56. package/dist/helpers/shipped.js +36 -0
  57. package/dist/helpers/shipped.js.map +1 -0
  58. package/dist/helpers/stdrep.d.ts +9 -1
  59. package/dist/helpers/stdrep.js +22 -0
  60. package/dist/helpers/stdrep.js.map +1 -1
  61. package/dist/sdkgen.d.ts +15 -3
  62. package/dist/sdkgen.js +233 -67
  63. package/dist/sdkgen.js.map +1 -1
  64. package/dist/tsconfig.tsbuildinfo +1 -1
  65. package/dist/types.d.ts +10 -2
  66. package/dist/utility.js +17 -0
  67. package/dist/utility.js.map +1 -1
  68. package/model/sdkgen.aontu +69 -0
  69. package/package.json +3 -2
  70. package/project/.sdk/model/feature/audit.aontu +1 -0
  71. package/project/.sdk/model/feature/cache.aontu +1 -0
  72. package/project/.sdk/model/feature/clienttrack.aontu +1 -0
  73. package/project/.sdk/model/feature/debug.aontu +1 -0
  74. package/project/.sdk/model/feature/idempotency.aontu +1 -0
  75. package/project/.sdk/model/feature/log.aontu +1 -0
  76. package/project/.sdk/model/feature/metrics.aontu +1 -0
  77. package/project/.sdk/model/feature/netsim.aontu +1 -0
  78. package/project/.sdk/model/feature/paging.aontu +1 -0
  79. package/project/.sdk/model/feature/proxy.aontu +1 -0
  80. package/project/.sdk/model/feature/ratelimit.aontu +1 -0
  81. package/project/.sdk/model/feature/rbac.aontu +1 -0
  82. package/project/.sdk/model/feature/retry.aontu +1 -0
  83. package/project/.sdk/model/feature/streaming.aontu +1 -0
  84. package/project/.sdk/model/feature/telemetry.aontu +1 -0
  85. package/project/.sdk/model/feature/test.aontu +1 -0
  86. package/project/.sdk/model/feature/timeout.aontu +1 -0
  87. package/project/.sdk/model/target/c.aontu +1 -0
  88. package/project/.sdk/model/target/clojure.aontu +1 -0
  89. package/project/.sdk/model/target/cpp.aontu +1 -0
  90. package/project/.sdk/model/target/dart.aontu +1 -0
  91. package/project/.sdk/model/target/elixir.aontu +1 -0
  92. package/project/.sdk/model/target/go-cli.aontu +1 -0
  93. package/project/.sdk/model/target/go-mcp.aontu +1 -0
  94. package/project/.sdk/model/target/go.aontu +1 -0
  95. package/project/.sdk/model/target/haskell.aontu +1 -0
  96. package/project/.sdk/model/target/java.aontu +1 -0
  97. package/project/.sdk/model/target/js.aontu +1 -0
  98. package/project/.sdk/model/target/kotlin.aontu +1 -0
  99. package/project/.sdk/model/target/lean.aontu +1 -0
  100. package/project/.sdk/model/target/lua.aontu +1 -0
  101. package/project/.sdk/model/target/ocaml.aontu +1 -0
  102. package/project/.sdk/model/target/perl.aontu +1 -0
  103. package/project/.sdk/model/target/php.aontu +1 -0
  104. package/project/.sdk/model/target/py-data.aontu +1 -0
  105. package/project/.sdk/model/target/py.aontu +1 -0
  106. package/project/.sdk/model/target/rb.aontu +1 -0
  107. package/project/.sdk/model/target/rust.aontu +1 -0
  108. package/project/.sdk/model/target/scala.aontu +1 -0
  109. package/project/.sdk/model/target/seneca-provider.aontu +1 -0
  110. package/project/.sdk/model/target/zig.aontu +1 -0
  111. package/project/.sdk/src/cmp/c/Config_c.ts +44 -0
  112. package/project/.sdk/src/cmp/c/fragment/Main.fragment.c +3 -1
  113. package/project/.sdk/src/cmp/clojure/Config_clojure.ts +37 -42
  114. package/project/.sdk/src/cmp/clojure/utility_clojure.ts +42 -0
  115. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +18 -0
  116. package/project/.sdk/src/cmp/csharp/Config_csharp.ts +125 -52
  117. package/project/.sdk/src/cmp/csharp/fragment/Main.fragment.cs +3 -1
  118. package/project/.sdk/src/cmp/csharp/utility_csharp.ts +94 -6
  119. package/project/.sdk/src/cmp/dart/Config_dart.ts +43 -5
  120. package/project/.sdk/src/cmp/dart/fragment/Config.data.fragment.dart +74 -0
  121. package/project/.sdk/src/cmp/dart/fragment/Config.fragment.dart +6 -9
  122. package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +16 -5
  123. package/project/.sdk/src/cmp/dart/utility_dart.ts +18 -0
  124. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +128 -13
  125. package/project/.sdk/src/cmp/elixir/fragment/Main.fragment.ex +3 -1
  126. package/project/.sdk/src/cmp/elixir/utility_elixir.ts +6 -0
  127. package/project/.sdk/src/cmp/haskell/Config_haskell.ts +35 -52
  128. package/project/.sdk/src/cmp/haskell/Package_haskell.ts +1 -0
  129. package/project/.sdk/src/cmp/haskell/Test_haskell.ts +1 -1
  130. package/project/.sdk/src/cmp/haskell/utility_haskell.ts +15 -5
  131. package/project/.sdk/src/cmp/java/Config_java.ts +21 -0
  132. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +15 -0
  133. package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +42 -39
  134. package/project/.sdk/src/cmp/perl/Config_perl.ts +15 -0
  135. package/project/.sdk/src/cmp/perl/fragment/Main.fragment.pm +3 -1
  136. package/project/.sdk/src/cmp/rust/Config_rust.ts +44 -0
  137. package/project/.sdk/src/cmp/rust/fragment/Main.fragment.rs +4 -2
  138. package/project/.sdk/src/cmp/scala/Config_scala.ts +15 -0
  139. package/project/.sdk/src/cmp/swift/Config_swift.ts +17 -0
  140. package/project/.sdk/src/cmp/swift/fragment/Main.fragment.swift +3 -1
  141. package/project/.sdk/src/cmp/zig/Config_zig.ts +44 -0
  142. package/project/.sdk/src/cmp/zig/TestEntity_zig.ts +9 -2
  143. package/project/.sdk/src/cmp/zig/fragment/Main.fragment.zig +3 -1
  144. package/project/.sdk/tm/c/core/sdk.h +1 -0
  145. package/project/.sdk/tm/c/utility/make_options.c +5 -1
  146. package/project/.sdk/tm/clojure/src/sdk/core.clj +51 -3
  147. package/project/.sdk/tm/cpp/core/types.hpp +4 -1
  148. package/project/.sdk/tm/cpp/utility/pipeline.hpp +5 -1
  149. package/project/.sdk/tm/csharp/test/PipelineTest.cs +15 -0
  150. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +67 -1
  151. package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +43 -0
  152. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +58 -2
  153. package/project/.sdk/tm/haskell/{test/TestJson.hs → src/SdkJson.hs} +8 -4
  154. package/project/.sdk/tm/haskell/src/SdkRuntime.hs +55 -2
  155. package/project/.sdk/tm/haskell/test/Runner.hs +1 -1
  156. package/project/.sdk/tm/haskell/test/StructCorpus.hs +1 -1
  157. package/project/.sdk/tm/java/core/SdkClient.java +3 -1
  158. package/project/.sdk/tm/java/utility/MakeOptions.java +5 -1
  159. package/project/.sdk/tm/kotlin/core/SdkClient.kt +3 -1
  160. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +5 -1
  161. package/project/.sdk/tm/ocaml/Makefile +3 -2
  162. package/project/.sdk/tm/ocaml/sdk_json.ml +106 -0
  163. package/project/.sdk/tm/ocaml/sdk_runtime.ml +56 -0
  164. package/project/.sdk/tm/ocaml/test/struct_corpus.ml +4 -86
  165. package/project/.sdk/tm/perl/utility/make_options.pm +5 -1
  166. package/project/.sdk/tm/rust/utility/make_options.rs +79 -1
  167. package/project/.sdk/tm/scala/core/SdkClient.scala +3 -1
  168. package/project/.sdk/tm/scala/utility/Make.scala +5 -1
  169. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +5 -1
  170. package/project/.sdk/tm/zig/core/utility.zig +85 -1
  171. package/project/sdkgen-package.json +57 -0
  172. package/src/action/action.ts +136 -6
  173. package/src/action/check.ts +495 -0
  174. package/src/action/dispatch.ts +105 -0
  175. package/src/action/docs.ts +310 -0
  176. package/src/action/doctor.ts +692 -81
  177. package/src/action/feature.ts +109 -17
  178. package/src/action/kind.ts +375 -0
  179. package/src/action/package.ts +1173 -0
  180. package/src/action/resolve.ts +362 -0
  181. package/src/action/target.ts +290 -108
  182. package/src/cmp/Docs.ts +125 -0
  183. package/src/cmp/ExternalDocs.ts +54 -0
  184. package/src/helpers/definition.ts +82 -0
  185. package/src/helpers/featureSource.ts +83 -13
  186. package/src/helpers/junk.ts +168 -0
  187. package/src/helpers/manifest.ts +474 -0
  188. package/src/helpers/modelcheck.ts +262 -0
  189. package/src/helpers/semver.ts +211 -0
  190. package/src/helpers/shipped.ts +41 -0
  191. package/src/helpers/stdrep.ts +70 -0
  192. package/src/sdkgen.ts +294 -66
  193. package/src/types.ts +33 -3
  194. package/src/utility.ts +17 -0
@@ -98,18 +98,96 @@ pub fn make_options_util(ctx: &Rc<Context>) -> Value {
98
98
  ]),
99
99
  ),
100
100
  ("clean", jo(vec![("keys", Value::str("key,token,id"))])),
101
+ // Server-variable values for a templated base URL (OpenAPI server
102
+ // variables): {name} placeholders in "base" are substituted from this
103
+ // map at construction. Spec defaults arrive via the generated config;
104
+ // user values override them. Mirrors go's make_options optspec.
105
+ ("server", jo(vec![("`$CHILD`", Value::str(""))])),
101
106
  ]);
102
107
 
103
108
  // Preserve system.fetch before merge/validate (validation strips it).
104
109
  let sys_fetch = getpath(&["system", "fetch"], &opts);
105
110
 
106
- let merged = vs::merge(&ja(vec![Value::empty_map(), cfgopts, opts.clone()]), None);
111
+ // CLONE the config side: `config` is a per-thread singleton
112
+ // (core::config::shared_config) and merge uses its nested maps as merge
113
+ // TARGETS, so without this one client's options (headers, server, ...) are
114
+ // written into the shared config and inherited by every client after it.
115
+ let merged = vs::merge(
116
+ &ja(vec![Value::empty_map(), vs::clone(&cfgopts), opts.clone()]), None);
107
117
  if let Ok(validated) = vs::validate(&merged, &optspec, None) {
108
118
  if let Value::Map(_) = validated {
109
119
  opts = validated;
110
120
  }
111
121
  }
112
122
 
123
+ // Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
124
+ // Every placeholder must resolve to a non-empty value: from options.server
125
+ // (user), else the Config default. A placeholder that resolves to "" is a
126
+ // construction ERROR in live mode - the URL cannot work - but in test mode
127
+ // substitutes the deterministic value "test-<name>" so offline tests need
128
+ // no configuration. The SDK constructor has no error return, so a missing
129
+ // required variable PANICS: construction-time misconfiguration.
130
+ //
131
+ // Scanned by hand rather than with vs::re_replace, whose replacement is a
132
+ // fixed string and cannot vary per placeholder.
133
+ if let Value::Str(base) = getp(&opts, "base") {
134
+ if base.contains('{') {
135
+ let testmode = matches!(getpath(&["test", "active"], &opts), Value::Bool(true))
136
+ || matches!(
137
+ getpath(&["feature", "test", "active"], &opts),
138
+ Value::Bool(true)
139
+ );
140
+ let server = getp(&opts, "server");
141
+ let sdkname = match getpath(&["main", "name"], &config) {
142
+ Value::Str(s) if !s.is_empty() => s,
143
+ _ => "SDK".to_string(),
144
+ };
145
+
146
+ let mut resolved = String::with_capacity(base.len());
147
+ let bytes: Vec<char> = base.chars().collect();
148
+ let mut i = 0usize;
149
+ while i < bytes.len() {
150
+ if '{' != bytes[i] {
151
+ resolved.push(bytes[i]);
152
+ i += 1;
153
+ continue;
154
+ }
155
+ // A placeholder only when it closes and the name is
156
+ // [A-Za-z0-9_]+; anything else is literal text.
157
+ let mut j = i + 1;
158
+ while j < bytes.len() && (bytes[j].is_ascii_alphanumeric() || '_' == bytes[j]) {
159
+ j += 1;
160
+ }
161
+ if j >= bytes.len() || '}' != bytes[j] || j == i + 1 {
162
+ resolved.push(bytes[i]);
163
+ i += 1;
164
+ continue;
165
+ }
166
+ let name: String = bytes[i + 1..j].iter().collect();
167
+ let val = match getp(&server, &name) {
168
+ Value::Str(s) => s,
169
+ _ => String::new(),
170
+ };
171
+ if val.is_empty() {
172
+ if testmode {
173
+ resolved.push_str(&format!("test-{}", name));
174
+ } else {
175
+ panic!(
176
+ "{}: the server variable '{}' is required: the API base \
177
+ URL is '{}' - pass server: {{ \"{}\": \"...\" }} in the \
178
+ SDK options",
179
+ sdkname, name, base, name
180
+ );
181
+ }
182
+ } else {
183
+ resolved.push_str(&val);
184
+ }
185
+ i = j + 1;
186
+ }
187
+ setp(&opts, "base", Value::str(&resolved));
188
+ }
189
+ }
190
+
113
191
  // Restore system.fetch.
114
192
  if !sys_fetch.is_noval() {
115
193
  let sys = getp(&opts, "system");
@@ -16,7 +16,9 @@ abstract class SdkClient(options0: JMap[String, Object]) {
16
16
  protected var rootctx: Context = null
17
17
 
18
18
  locally {
19
- val config = Config.makeConfig()
19
+ // The process-wide config (sdkgen rung L2): read-only on the request path,
20
+ // so every client shares one rather than rebuilding it.
21
+ val config = Config.sharedConfig()
20
22
 
21
23
  val ctxmap = new LinkedHashMap[String, Object]()
22
24
  ctxmap.put("client", this)
@@ -216,7 +216,11 @@ object MakeOptions {
216
216
 
217
217
  val mergeList = new ArrayList[Object]()
218
218
  mergeList.add(new LinkedHashMap[String, Object]())
219
- mergeList.add(cfgopts)
219
+ // CLONE the config side: `config` is a process-wide singleton
220
+ // (Config.sharedConfig) and merge uses its nested maps as merge TARGETS, so
221
+ // without this one client's options (headers, server, ...) are written into
222
+ // the shared config and inherited by every client constructed afterwards.
223
+ mergeList.add(Struct.clone(cfgopts))
220
224
  mergeList.add(opts)
221
225
  val merged = Struct.merge(mergeList)
222
226
 
@@ -98,7 +98,11 @@ func makeOptionsUtil(_ ctx: Context) -> VMap {
98
98
  // validation reshapes the system block).
99
99
  let sysFetch = gpath(opts, "system", "fetch")
100
100
 
101
- let merged = merge(.list([.map(VMap()), .map(cfgopts), .map(opts)]))
101
+ // CLONE the config side: `config` is a process-wide singleton
102
+ // (SdkConfig.sharedConfig) and merge uses its nested maps as merge TARGETS,
103
+ // so without this one client's options (headers, server, ...) are written
104
+ // into the shared config and inherited by every client built afterwards.
105
+ let merged = merge(.list([.map(VMap()), clone(.map(cfgopts)), .map(opts)]))
102
106
  let validated = validate(merged, optspec)
103
107
  let result = validated.asMap ?? VMap()
104
108
 
@@ -35,6 +35,14 @@ fn fmt(comptime f: []const u8, args: anytype) []const u8 {
35
35
  return std.fmt.allocPrint(h.A(), f, args) catch "";
36
36
  }
37
37
 
38
+ // Boolean-or-absent: an option that is unset, null or a non-boolean is false.
39
+ fn is_true(v: Value) bool {
40
+ return switch (v) {
41
+ .bool => |b| b,
42
+ else => false,
43
+ };
44
+ }
45
+
38
46
  // ============================================================================
39
47
  // Utility bundle
40
48
  // ============================================================================
@@ -368,17 +376,93 @@ pub fn make_options_util(ctx: *Context) Value {
368
376
  .{ "entity", h.jo(&.{.{ "`$OPEN`", h.vbool(true) }}) },
369
377
  }) },
370
378
  .{ "clean", h.jo(&.{.{ "keys", h.vstr("key,token,id") }}) },
379
+ // Server-variable values for a templated base URL (OpenAPI server
380
+ // variables): {name} placeholders in "base" are substituted from this
381
+ // map at construction. Spec defaults arrive via the generated config;
382
+ // user values override them. Mirrors go's make_options optspec.
383
+ .{ "server", h.jo(&.{.{ "`$CHILD`", h.vstr("") }}) },
371
384
  });
372
385
 
373
386
  // Preserve system.fetch before merge/validate (validation strips it).
374
387
  const sys_fetch = h.getpath(&.{ "system", "fetch" }, opts);
375
388
 
376
- const merged = vs.merge(h.A(), h.ja(&.{ h.omap(), cfgopts, opts }), vs.MAXDEPTH) catch opts;
389
+ // CLONE the config side: `config` is a process-wide singleton
390
+ // (config.shared_config) and merge uses its nested maps as merge TARGETS,
391
+ // so without this one client's options (headers, server, ...) are written
392
+ // into the shared config and inherited by every client built after it.
393
+ const merged = vs.merge(h.A(), h.ja(&.{ h.omap(), h.clone(cfgopts), opts }), vs.MAXDEPTH) catch opts;
377
394
  const vres = vs.validate(h.A(), merged, optspec) catch null;
378
395
  if (vres) |vr| {
379
396
  if (vr.err == null and vr.out == .object) opts = vr.out;
380
397
  }
381
398
 
399
+ // Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
400
+ // Every placeholder must resolve to a non-empty value: from options.server
401
+ // (user), else the Config default. A placeholder that resolves to "" is a
402
+ // construction ERROR in live mode - the URL cannot work - but in test mode
403
+ // substitutes the deterministic value "test-<name>" so offline tests need
404
+ // no configuration. The SDK constructor has no error return, so a missing
405
+ // required variable PANICS: construction-time misconfiguration.
406
+ //
407
+ // Scanned by hand: a placeholder is `{` followed by [A-Za-z0-9_]+ and `}`;
408
+ // anything else is literal text, so a stray brace in a URL is left alone.
409
+ switch (h.getp(opts, "base")) {
410
+ .string => |base| {
411
+ if (null != std.mem.indexOfScalar(u8, base, '{')) {
412
+ const testmode =
413
+ is_true(h.getpath(&.{ "test", "active" }, opts)) or
414
+ is_true(h.getpath(&.{ "feature", "test", "active" }, opts));
415
+ const server = h.getp(opts, "server");
416
+ const sdkname: []const u8 = switch (h.getpath(&.{ "main", "name" }, config)) {
417
+ .string => |s| if (0 < s.len) s else "SDK",
418
+ else => "SDK",
419
+ };
420
+
421
+ var out = std.ArrayList(u8).init(h.A());
422
+ var i: usize = 0;
423
+ while (i < base.len) {
424
+ if ('{' != base[i]) {
425
+ out.append(base[i]) catch {};
426
+ i += 1;
427
+ continue;
428
+ }
429
+ var j = i + 1;
430
+ while (j < base.len and (std.ascii.isAlphanumeric(base[j]) or '_' == base[j])) {
431
+ j += 1;
432
+ }
433
+ if (j >= base.len or '}' != base[j] or j == i + 1) {
434
+ out.append(base[i]) catch {};
435
+ i += 1;
436
+ continue;
437
+ }
438
+ const name = base[i + 1 .. j];
439
+ const val: []const u8 = switch (h.getp(server, name)) {
440
+ .string => |s| s,
441
+ else => "",
442
+ };
443
+ if (0 == val.len) {
444
+ if (testmode) {
445
+ out.appendSlice("test-") catch {};
446
+ out.appendSlice(name) catch {};
447
+ } else {
448
+ std.debug.panic(
449
+ "{s}: the server variable '{s}' is required: the API " ++
450
+ "base URL is '{s}' - pass .server = .{{ .{s} = \"...\" }} " ++
451
+ "in the SDK options",
452
+ .{ sdkname, name, base, name },
453
+ );
454
+ }
455
+ } else {
456
+ out.appendSlice(val) catch {};
457
+ }
458
+ i = j + 1;
459
+ }
460
+ h.setp(opts, "base", h.vstr(out.toOwnedSlice() catch base));
461
+ }
462
+ },
463
+ else => {},
464
+ }
465
+
382
466
  // Restore system.fetch.
383
467
  if (!h.is_noval(sys_fetch)) {
384
468
  const sysv = h.getp(opts, "system");
@@ -0,0 +1,57 @@
1
+ {
2
+ "sdkgen": {
3
+ "package": 1
4
+ },
5
+ "name": "@voxgig/sdkgen",
6
+ "version": "3.5.0",
7
+ "provides": {
8
+ "target": [
9
+ "c",
10
+ "clojure",
11
+ "cpp",
12
+ "csharp",
13
+ "dart",
14
+ "elixir",
15
+ "go",
16
+ "go-cli",
17
+ "go-mcp",
18
+ "haskell",
19
+ "java",
20
+ "js",
21
+ "kotlin",
22
+ "lean",
23
+ "lua",
24
+ "ocaml",
25
+ "perl",
26
+ "php",
27
+ "py",
28
+ "py-data",
29
+ "rb",
30
+ "rust",
31
+ "scala",
32
+ "seneca-provider",
33
+ "swift",
34
+ "ts",
35
+ "zig"
36
+ ],
37
+ "feature": [
38
+ "audit",
39
+ "cache",
40
+ "clienttrack",
41
+ "debug",
42
+ "idempotency",
43
+ "log",
44
+ "metrics",
45
+ "netsim",
46
+ "paging",
47
+ "proxy",
48
+ "ratelimit",
49
+ "rbac",
50
+ "retry",
51
+ "streaming",
52
+ "telemetry",
53
+ "test",
54
+ "timeout"
55
+ ]
56
+ }
57
+ }
@@ -12,6 +12,40 @@ import type {
12
12
  } from '../types'
13
13
 
14
14
 
15
+ const indexEntry = (name: string) => `@"${name}.aontu"`
16
+
17
+
18
+ // An index line that is an ACTIVE include, and the name it includes — or
19
+ // undefined for a blank line, a comment, or anything else.
20
+ //
21
+ // Parsed rather than compared as a string, because both spellings around an
22
+ // include are legal aontu and mean opposite things:
23
+ //
24
+ // @"go.aontu" -> active, name 'go'
25
+ // @"go.aontu" # pinned -> active, name 'go' (trailing comment)
26
+ // @"go.aontu" -> active, name 'go' (indented)
27
+ // # @"go.aontu" -> NOT active
28
+ //
29
+ // A substring test (what this used to be) reads the commented-out form as
30
+ // present, so `target add go` on a project that had switched the target off
31
+ // by hand appended nothing and reported success while the target stayed
32
+ // absent from the model. A whole-line equality test fixes that but then
33
+ // misses the trailing-comment form, and appends a SECOND active include.
34
+ const INDEX_ENTRY_RE = /^\s*@"([^"]+)\.aontu"\s*(?:#.*)?$/
35
+
36
+ function indexEntryName(line: string): string | undefined {
37
+ const m = line.match(INDEX_ENTRY_RE)
38
+ return null == m ? undefined : m[1]
39
+ }
40
+
41
+
42
+ // Is this name already included by the index?
43
+ function hasIndexEntry(content: string, name: string): boolean {
44
+ return content.split('\n')
45
+ .some((line: string) => indexEntryName(line) === name)
46
+ }
47
+
48
+
15
49
  // Append `@"<name>.aontu"` import lines for each name not already present in
16
50
  // the index content. Checking against the accumulating result (not the
17
51
  // original) means duplicate names in the same call are added at most once.
@@ -19,9 +53,8 @@ function appendIndexEntries(content: string, names: string[]): string {
19
53
  let out = content
20
54
 
21
55
  for (const n of names) {
22
- const entry = `@"${n}.aontu"`
23
- if (!out.includes(entry)) {
24
- out += '\n' + entry
56
+ if (!hasIndexEntry(out, n)) {
57
+ out += '\n' + indexEntry(n)
25
58
  }
26
59
  }
27
60
 
@@ -29,6 +62,25 @@ function appendIndexEntries(content: string, names: string[]): string {
29
62
  }
30
63
 
31
64
 
65
+ // Drop the `@"<name>.aontu"` line for each name — the inverse of
66
+ // appendIndexEntries, matching line-exact for the same reasons.
67
+ //
68
+ // Nothing calls this yet: a `remove` action is the fast-follow this exists
69
+ // for (see docs/design/sdkgen-packages.md), and it is written here beside its
70
+ // inverse so the two cannot drift on how an entry is recognised.
71
+ function removeIndexEntries(content: string, names: string[]): string {
72
+ const drop = new Set(names)
73
+
74
+ return content
75
+ .split('\n')
76
+ .filter((line: string) => {
77
+ const name = indexEntryName(line)
78
+ return undefined === name || !drop.has(name)
79
+ })
80
+ .join('\n')
81
+ }
82
+
83
+
32
84
  const UpdateIndex = cmp(function UpdateIndex(props: any) {
33
85
  Content(appendIndexEntries(props.content, props.names))
34
86
  })
@@ -45,7 +97,20 @@ function parseAddNames(args: any[]): string[] {
45
97
  }
46
98
 
47
99
 
48
- function loadContent(actx: ActionContext, which: string | string[]) {
100
+ // The current index file for each kind, which `UpdateIndex` appends to.
101
+ //
102
+ // `seed` IS THE UPGRADE PATH. A project scaffolded before a kind existed has
103
+ // no `model/<kind>/<kind>-index.aontu` — every project alive today is in
104
+ // exactly that position for `docs` — and reading it unguarded made the FIRST
105
+ // `docs add` in any existing project fail on ENOENT before it wrote anything.
106
+ //
107
+ // Seeded per call rather than defaulted for every kind: a missing
108
+ // `target-index.aontu` in a scaffolded project is a broken project, and
109
+ // quietly recreating it would hide that. A kind the project has never used is
110
+ // a different thing, and only its own action knows which case it is in.
111
+ function loadContent(
112
+ actx: ActionContext, which: string | string[], seed?: Record<string, string>,
113
+ ) {
49
114
  which = Array.isArray(which) ? which : [which]
50
115
 
51
116
  const content: any = {}
@@ -55,8 +120,9 @@ function loadContent(actx: ActionContext, which: string | string[]) {
55
120
 
56
121
  which.map((w: string) => {
57
122
  const indexfile = Path.join(modelfolder, w, w + '-index.aontu')
58
- const indexcontent = fs.readFileSync(indexfile, 'utf8')
59
- content[`${w}_index`] = indexcontent
123
+
124
+ content[`${w}_index`] = (null != seed?.[w] && !fs.existsSync(indexfile)) ?
125
+ seed[w] : fs.readFileSync(indexfile, 'utf8')
60
126
  })
61
127
 
62
128
  return content
@@ -64,9 +130,73 @@ function loadContent(actx: ActionContext, which: string | string[]) {
64
130
 
65
131
 
66
132
 
133
+ // ENSURE THE PROJECT'S OWN MODEL INCLUDES A KIND'S INDEX.
134
+ //
135
+ // `model/sdk.aontu` is written once, by create-sdkgen, and includes the
136
+ // indexes of the kinds that existed then. So a project scaffolded before a
137
+ // kind existed — which is every project alive today, for `docs` — never
138
+ // includes its index, and the item's model file is an orphan: it is on disk,
139
+ // `<kind>-index.aontu` includes it, and NOTHING includes that. `main.kit.docs`
140
+ // is then absent from the compiled model, so `package list`, `package update`
141
+ // and `doctor` cannot see the item at all.
142
+ //
143
+ // Appending one include line is additive and idempotent, and it is the only
144
+ // way an existing project can adopt a new kind without hand-editing. The
145
+ // entry is parsed, not substring-matched, for the reason `hasIndexEntry`
146
+ // documents: a commented-out include means the project switched it OFF, and
147
+ // re-adding it would override that.
148
+ function ensureModelInclude(actx: ActionContext, kind: string): boolean {
149
+ const fs = actx.fs()
150
+ const url = actx.url
151
+
152
+ if (!fs.existsSync(url)) {
153
+ // Said out loud rather than skipped silently: without the include the
154
+ // item is invisible to the next model compile, and a quiet no-op here
155
+ // would look exactly like success.
156
+ actx.log.warn({
157
+ point: 'model-include-absent', kind, file: url,
158
+ note: url + ' not found, so ' + indexEntry(kind + '/' + kind + '-index') +
159
+ ' could not be added — add it by hand, or nothing will see any ' +
160
+ kind + ' item'
161
+ })
162
+ return false
163
+ }
164
+
165
+ const name = kind + '/' + kind + '-index'
166
+ const content = String(fs.readFileSync(url, 'utf8'))
167
+
168
+ if (hasIndexEntry(content, name)) {
169
+ return false
170
+ }
171
+
172
+ if (actx.opts?.dryrun) {
173
+ actx.log.info({
174
+ point: 'model-include-dryrun', kind, file: url, entry: indexEntry(name),
175
+ note: '** DRY RUN ** would add ' + indexEntry(name) + ' to ' + url
176
+ })
177
+ return false
178
+ }
179
+
180
+ fs.writeFileSync(url,
181
+ content + (content.endsWith('\n') ? '' : '\n') +
182
+ indexEntry(name) + '\n')
183
+
184
+ actx.log.info({
185
+ point: 'model-include-added', kind, file: url, entry: indexEntry(name),
186
+ note: url + ': added ' + indexEntry(name) +
187
+ ' — without it the project model never sees any ' + kind + ' item'
188
+ })
189
+
190
+ return true
191
+ }
192
+
193
+
67
194
  export {
68
195
  UpdateIndex,
196
+ ensureModelInclude,
69
197
  appendIndexEntries,
198
+ removeIndexEntries,
199
+ hasIndexEntry,
70
200
  parseAddNames,
71
201
  loadContent
72
202
  }