@voxgig/sdkgen 3.4.6 → 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 (198) 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/cmp/ReadmeTop.js +5 -1
  38. package/dist/cmp/ReadmeTop.js.map +1 -1
  39. package/dist/helpers/definition.d.ts +5 -0
  40. package/dist/helpers/definition.js +72 -0
  41. package/dist/helpers/definition.js.map +1 -0
  42. package/dist/helpers/featureSource.d.ts +8 -2
  43. package/dist/helpers/featureSource.js +61 -12
  44. package/dist/helpers/featureSource.js.map +1 -1
  45. package/dist/helpers/junk.d.ts +11 -0
  46. package/dist/helpers/junk.js +149 -0
  47. package/dist/helpers/junk.js.map +1 -0
  48. package/dist/helpers/manifest.d.ts +50 -0
  49. package/dist/helpers/manifest.js +328 -0
  50. package/dist/helpers/manifest.js.map +1 -0
  51. package/dist/helpers/modelcheck.d.ts +22 -0
  52. package/dist/helpers/modelcheck.js +203 -0
  53. package/dist/helpers/modelcheck.js.map +1 -0
  54. package/dist/helpers/semver.d.ts +4 -0
  55. package/dist/helpers/semver.js +174 -0
  56. package/dist/helpers/semver.js.map +1 -0
  57. package/dist/helpers/shipped.d.ts +4 -0
  58. package/dist/helpers/shipped.js +36 -0
  59. package/dist/helpers/shipped.js.map +1 -0
  60. package/dist/helpers/stdrep.d.ts +9 -1
  61. package/dist/helpers/stdrep.js +22 -0
  62. package/dist/helpers/stdrep.js.map +1 -1
  63. package/dist/sdkgen.d.ts +15 -3
  64. package/dist/sdkgen.js +233 -67
  65. package/dist/sdkgen.js.map +1 -1
  66. package/dist/tsconfig.tsbuildinfo +1 -1
  67. package/dist/types.d.ts +10 -2
  68. package/dist/utility.js +17 -0
  69. package/dist/utility.js.map +1 -1
  70. package/model/sdkgen.aontu +69 -0
  71. package/package.json +3 -2
  72. package/project/.sdk/model/feature/audit.aontu +1 -0
  73. package/project/.sdk/model/feature/cache.aontu +1 -0
  74. package/project/.sdk/model/feature/clienttrack.aontu +1 -0
  75. package/project/.sdk/model/feature/debug.aontu +1 -0
  76. package/project/.sdk/model/feature/idempotency.aontu +1 -0
  77. package/project/.sdk/model/feature/log.aontu +1 -0
  78. package/project/.sdk/model/feature/metrics.aontu +1 -0
  79. package/project/.sdk/model/feature/netsim.aontu +1 -0
  80. package/project/.sdk/model/feature/paging.aontu +1 -0
  81. package/project/.sdk/model/feature/proxy.aontu +1 -0
  82. package/project/.sdk/model/feature/ratelimit.aontu +1 -0
  83. package/project/.sdk/model/feature/rbac.aontu +1 -0
  84. package/project/.sdk/model/feature/retry.aontu +1 -0
  85. package/project/.sdk/model/feature/streaming.aontu +1 -0
  86. package/project/.sdk/model/feature/telemetry.aontu +1 -0
  87. package/project/.sdk/model/feature/test.aontu +1 -0
  88. package/project/.sdk/model/feature/timeout.aontu +1 -0
  89. package/project/.sdk/model/target/c.aontu +1 -0
  90. package/project/.sdk/model/target/clojure.aontu +1 -0
  91. package/project/.sdk/model/target/cpp.aontu +1 -0
  92. package/project/.sdk/model/target/dart.aontu +1 -0
  93. package/project/.sdk/model/target/elixir.aontu +1 -0
  94. package/project/.sdk/model/target/go-cli.aontu +1 -0
  95. package/project/.sdk/model/target/go-mcp.aontu +1 -0
  96. package/project/.sdk/model/target/go.aontu +1 -0
  97. package/project/.sdk/model/target/haskell.aontu +1 -0
  98. package/project/.sdk/model/target/java.aontu +1 -0
  99. package/project/.sdk/model/target/js.aontu +1 -0
  100. package/project/.sdk/model/target/kotlin.aontu +1 -0
  101. package/project/.sdk/model/target/lean.aontu +1 -0
  102. package/project/.sdk/model/target/lua.aontu +1 -0
  103. package/project/.sdk/model/target/ocaml.aontu +1 -0
  104. package/project/.sdk/model/target/perl.aontu +1 -0
  105. package/project/.sdk/model/target/php.aontu +1 -0
  106. package/project/.sdk/model/target/py-data.aontu +1 -0
  107. package/project/.sdk/model/target/py.aontu +1 -0
  108. package/project/.sdk/model/target/rb.aontu +1 -0
  109. package/project/.sdk/model/target/rust.aontu +1 -0
  110. package/project/.sdk/model/target/scala.aontu +1 -0
  111. package/project/.sdk/model/target/seneca-provider.aontu +1 -0
  112. package/project/.sdk/model/target/zig.aontu +1 -0
  113. package/project/.sdk/src/cmp/c/Config_c.ts +44 -0
  114. package/project/.sdk/src/cmp/c/fragment/Main.fragment.c +3 -1
  115. package/project/.sdk/src/cmp/clojure/Config_clojure.ts +37 -42
  116. package/project/.sdk/src/cmp/clojure/utility_clojure.ts +42 -0
  117. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +18 -0
  118. package/project/.sdk/src/cmp/csharp/Config_csharp.ts +125 -52
  119. package/project/.sdk/src/cmp/csharp/fragment/Main.fragment.cs +3 -1
  120. package/project/.sdk/src/cmp/csharp/utility_csharp.ts +94 -6
  121. package/project/.sdk/src/cmp/dart/Config_dart.ts +56 -3
  122. package/project/.sdk/src/cmp/dart/fragment/Config.data.fragment.dart +74 -0
  123. package/project/.sdk/src/cmp/dart/fragment/Config.fragment.dart +6 -9
  124. package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +16 -5
  125. package/project/.sdk/src/cmp/dart/utility_dart.ts +18 -0
  126. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +128 -13
  127. package/project/.sdk/src/cmp/elixir/fragment/Main.fragment.ex +3 -1
  128. package/project/.sdk/src/cmp/elixir/utility_elixir.ts +6 -0
  129. package/project/.sdk/src/cmp/haskell/Config_haskell.ts +35 -52
  130. package/project/.sdk/src/cmp/haskell/Package_haskell.ts +1 -0
  131. package/project/.sdk/src/cmp/haskell/Test_haskell.ts +1 -1
  132. package/project/.sdk/src/cmp/haskell/utility_haskell.ts +15 -5
  133. package/project/.sdk/src/cmp/java/Config_java.ts +21 -0
  134. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +15 -0
  135. package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +42 -39
  136. package/project/.sdk/src/cmp/perl/Config_perl.ts +15 -0
  137. package/project/.sdk/src/cmp/perl/fragment/Main.fragment.pm +3 -1
  138. package/project/.sdk/src/cmp/rust/Config_rust.ts +44 -0
  139. package/project/.sdk/src/cmp/rust/fragment/Main.fragment.rs +4 -2
  140. package/project/.sdk/src/cmp/scala/Config_scala.ts +15 -0
  141. package/project/.sdk/src/cmp/swift/Config_swift.ts +17 -0
  142. package/project/.sdk/src/cmp/swift/fragment/Main.fragment.swift +3 -1
  143. package/project/.sdk/src/cmp/zig/Config_zig.ts +44 -0
  144. package/project/.sdk/src/cmp/zig/TestEntity_zig.ts +9 -2
  145. package/project/.sdk/src/cmp/zig/fragment/Main.fragment.zig +31 -18
  146. package/project/.sdk/tm/c/core/sdk.h +1 -0
  147. package/project/.sdk/tm/c/utility/make_options.c +5 -1
  148. package/project/.sdk/tm/clojure/src/sdk/core.clj +51 -3
  149. package/project/.sdk/tm/cpp/core/types.hpp +4 -1
  150. package/project/.sdk/tm/cpp/utility/pipeline.hpp +5 -1
  151. package/project/.sdk/tm/csharp/test/PipelineTest.cs +15 -0
  152. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +67 -1
  153. package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +43 -0
  154. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +58 -2
  155. package/project/.sdk/tm/haskell/{test/TestJson.hs → src/SdkJson.hs} +8 -4
  156. package/project/.sdk/tm/haskell/src/SdkRuntime.hs +55 -2
  157. package/project/.sdk/tm/haskell/test/Runner.hs +1 -1
  158. package/project/.sdk/tm/haskell/test/StructCorpus.hs +1 -1
  159. package/project/.sdk/tm/java/core/SdkClient.java +3 -1
  160. package/project/.sdk/tm/java/utility/MakeOptions.java +5 -1
  161. package/project/.sdk/tm/kotlin/core/SdkClient.kt +3 -1
  162. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +5 -1
  163. package/project/.sdk/tm/ocaml/Makefile +3 -2
  164. package/project/.sdk/tm/ocaml/sdk_json.ml +106 -0
  165. package/project/.sdk/tm/ocaml/sdk_runtime.ml +56 -0
  166. package/project/.sdk/tm/ocaml/test/struct_corpus.ml +4 -86
  167. package/project/.sdk/tm/perl/utility/make_options.pm +5 -1
  168. package/project/.sdk/tm/rust/utility/make_options.rs +79 -1
  169. package/project/.sdk/tm/scala/core/SdkClient.scala +3 -1
  170. package/project/.sdk/tm/scala/utility/Make.scala +5 -1
  171. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +5 -1
  172. package/project/.sdk/tm/zig/core/utility.zig +85 -1
  173. package/project/.sdk/tm/zig/test/gotcha_test.zig +8 -8
  174. package/project/sdkgen-package.json +57 -0
  175. package/src/action/action.ts +136 -6
  176. package/src/action/check.ts +495 -0
  177. package/src/action/dispatch.ts +105 -0
  178. package/src/action/docs.ts +310 -0
  179. package/src/action/doctor.ts +692 -81
  180. package/src/action/feature.ts +109 -17
  181. package/src/action/kind.ts +375 -0
  182. package/src/action/package.ts +1173 -0
  183. package/src/action/resolve.ts +362 -0
  184. package/src/action/target.ts +290 -108
  185. package/src/cmp/Docs.ts +125 -0
  186. package/src/cmp/ExternalDocs.ts +54 -0
  187. package/src/cmp/ReadmeTop.ts +6 -1
  188. package/src/helpers/definition.ts +82 -0
  189. package/src/helpers/featureSource.ts +83 -13
  190. package/src/helpers/junk.ts +168 -0
  191. package/src/helpers/manifest.ts +474 -0
  192. package/src/helpers/modelcheck.ts +262 -0
  193. package/src/helpers/semver.ts +211 -0
  194. package/src/helpers/shipped.ts +41 -0
  195. package/src/helpers/stdrep.ts +70 -0
  196. package/src/sdkgen.ts +294 -66
  197. package/src/types.ts +33 -3
  198. package/src/utility.ts +17 -0
package/src/sdkgen.ts CHANGED
@@ -5,7 +5,6 @@ import Path from 'node:path'
5
5
 
6
6
  import { prettyPino, Pino } from '@voxgig/util'
7
7
 
8
- import { Jsonic } from '@tabnas/jsonic'
9
8
  import * as JostracaModule from 'jostraca'
10
9
  import { Aontu } from 'aontu'
11
10
 
@@ -25,6 +24,8 @@ import { SdkGenError, requirePath, isAuthActive, resolveAuthPrefix,
25
24
 
26
25
  import { Main } from './cmp/Main'
27
26
  import { ExternalTarget } from './cmp/ExternalTarget'
27
+ import { Docs, DocsItem } from './cmp/Docs'
28
+ import { ExternalDocs } from './cmp/ExternalDocs'
28
29
 
29
30
  import { KIT } from '@voxgig/apidef'
30
31
  import { Deploy } from './cmp/Deploy'
@@ -73,6 +74,19 @@ import {
73
74
  srcFeatureExcludes,
74
75
  } from './helpers/featureSource'
75
76
  import type { FeatureSource } from './helpers/featureSource'
77
+ import {
78
+ definitionPath,
79
+ definitionFolder,
80
+ definitionNames,
81
+ } from './helpers/definition'
82
+ import { isNoise, copyOpts } from './helpers/junk'
83
+ import {
84
+ MANIFEST,
85
+ manifestPath,
86
+ readManifest,
87
+ validateManifest,
88
+ } from './helpers/manifest'
89
+ import type { Manifest, ManifestRead } from './helpers/manifest'
76
90
  import {
77
91
  packageName,
78
92
  installCommand,
@@ -117,6 +131,19 @@ import {
117
131
  } from './action/doctor'
118
132
  import type { DoctorReport } from './action/doctor'
119
133
 
134
+ import {
135
+ action_package,
136
+ package_add,
137
+ package_update,
138
+ } from './action/package'
139
+
140
+ import { cmd_package_check } from './action/check'
141
+
142
+ import { docs_add } from './action/docs'
143
+
144
+ // The verbs, built from the kind registry — see action/dispatch.
145
+ import { ACTION_MAP, actionNames, needsModel } from './action/dispatch'
146
+
120
147
 
121
148
 
122
149
  // TODO: use shape
@@ -149,13 +176,6 @@ type SdkGenOptions = {
149
176
  const { Jostraca } = JostracaModule
150
177
 
151
178
 
152
- const ACTION_MAP: any = {
153
- target: action_target,
154
- feature: action_feature,
155
- doctor: action_doctor,
156
- }
157
-
158
-
159
179
  const dlog = getdlog('sdkgen', __filename)
160
180
 
161
181
 
@@ -188,7 +208,12 @@ function SdkGen(opts: SdkGenOptions) {
188
208
  write: true,
189
209
  merge: false
190
210
  }
191
- }
211
+ },
212
+ // No copied tree carries a maintainer's `.DS_Store` or `__pycache__` into
213
+ // an SDK. Set here AND per call (see helpers/junk) — the actions run on
214
+ // whatever instance their caller supplies, so neither placement covers
215
+ // every path on its own.
216
+ cmp: copyOpts()
192
217
  }
193
218
 
194
219
  const jostraca = Jostraca(jopts)
@@ -229,6 +254,9 @@ function SdkGen(opts: SdkGenOptions) {
229
254
  control: {
230
255
  dryrun: !!opts.dryrun
231
256
  },
257
+ // Per-call for the same reason as `control` above: an option the
258
+ // instance carries is not reliably the one a generate sees.
259
+ cmp: copyOpts(),
232
260
  }
233
261
 
234
262
  // Targets that write OUTSIDE the SDK repo (`output: path`) are generated
@@ -240,7 +268,7 @@ function SdkGen(opts: SdkGenOptions) {
240
268
  // so resolve it ONCE: every destination is compared against it, and a
241
269
  // comparison between a relative and an absolute path is meaningless.
242
270
  const root = Path.resolve(folder)
243
- const external = externalTargets(model, root)
271
+ const external = externalItems(model, root, ['target', 'docs'])
244
272
 
245
273
  // Before ANY file is written, in-tree included: a destination that turns
246
274
  // out to be wrong must abort the whole generation, not leave half of it
@@ -248,15 +276,85 @@ function SdkGen(opts: SdkGenOptions) {
248
276
  checkExternalFolders(external, root, fs)
249
277
 
250
278
  const jres = await jostraca.generate(
251
- jopts, () => Root({ model: 0 === external.length ? model : withoutTargets(model, external) }))
279
+ jopts, () => Root({ model: 0 === external.length ? model : withoutExternal(model, external) }))
252
280
 
253
281
  showChanges(jopts.log, 'generate-result', jres, Path.dirname(process.cwd()))
254
282
 
283
+ // IN-TREE DOCS, in their own pass over the same root.
284
+ //
285
+ // The alternative is one `Kinds()` call in the consumer's `Root.ts` —
286
+ // which create-sdkgen writes once and never revisits, so every project
287
+ // scaffolded before the docs kind existed would have to be hand-edited
288
+ // before `docs add` did anything. A second pass costs one more changes
289
+ // report and makes the kind work in projects that predate it, which is
290
+ // the whole point of putting the destinations in packages.
291
+ //
292
+ // Items with `output: path` are excluded here and generated by the loop
293
+ // below, exactly as out-of-tree targets are.
294
+ const intreedocs = Object.keys(model?.main?.[KIT]?.docs ?? {})
295
+ .filter((name: string) => {
296
+ const item = model.main[KIT].docs[name]
297
+ const path = item?.output?.path
298
+ return false !== item?.active && (null == path || '' === path)
299
+ })
300
+
301
+ // AN IN-TREE DOCS ITEM AND A TARGET CANNOT SHARE A NAME.
302
+ //
303
+ // `docs` and `target` are separate namespaces — deliberately, so an item
304
+ // of each may be called `api` — and their `.sdk` trees are nested apart
305
+ // (`src/cmp/docs/<n>` vs `src/cmp/<n>`), so installing both is fine.
306
+ //
307
+ // Their GENERATED destinations are not separate. Both render into
308
+ // `<sdk-repo>/<name>/`, the docs pass runs second, and jostraca is
309
+ // last-write-wins — so a docs item named `go` would quietly overwrite the
310
+ // Go SDK's README, and whatever else the two have in common, leaving a
311
+ // corrupt SDK and a green run.
312
+ //
313
+ // Refused rather than reordered, and refused BEFORE the docs pass writes
314
+ // anything: this is the in-tree half of the rule `checkExternalFolders`
315
+ // enforces for out-of-tree destinations, and the fix is the same either
316
+ // way — give one of them `output: path`, or rename it.
317
+ const targets: any = model?.main?.[KIT]?.target ?? {}
318
+
319
+ for (const name of intreedocs) {
320
+ const target = targets[name]
321
+
322
+ if (null == target || false === target.active) {
323
+ continue
324
+ }
325
+
326
+ const tpath = target.output?.path
327
+
328
+ if (null != tpath && '' !== tpath) {
329
+ continue
330
+ }
331
+
332
+ throw new SdkGenError(
333
+ 'Docs item "' + name + '" and target "' + name + '" would both ' +
334
+ 'generate into <sdk-project>/' + name + '/.\n' +
335
+ ' They are separate kinds, so sharing a name is legal — but they ' +
336
+ 'share one output folder, and the docs pass runs second, so it ' +
337
+ 'would overwrite the SDK.\n' +
338
+ ' Give one of them `output: path`, or rename it.')
339
+ }
340
+
341
+ if (0 < intreedocs.length) {
342
+ log.info({
343
+ point: 'generate-docs-start', docs: intreedocs,
344
+ note: intreedocs.join(', ')
345
+ })
346
+
347
+ const dres = await jostraca.generate(jopts, () => Docs({ model }))
348
+
349
+ showChanges(jopts.log, 'generate-docs-result', dres,
350
+ Path.dirname(process.cwd()))
351
+ }
352
+
255
353
  for (const ext of external) {
256
354
  // `active: false` is the project's only lever to stop the generator
257
355
  // writing into a repo it does not own, so it has to be honoured HERE —
258
356
  // the consumer Root iterates targets raw and does not check it. The
259
- // target is still removed from the in-tree model above (withoutTargets
357
+ // target is still removed from the in-tree model above (withoutExternal
260
358
  // takes every `output: path` target, active or not), so switching one
261
359
  // off generates it nowhere rather than relocating it into
262
360
  // `<sdk-repo>/<target>/`.
@@ -273,15 +371,23 @@ function SdkGen(opts: SdkGenOptions) {
273
371
  note: ext.name + ' -> ' + ext.folder
274
372
  })
275
373
 
276
- const eres = await jostraca.generate(
374
+ const sdkrelpath = externalSdkRel(ext, root, log)
375
+
376
+ const eres = 'docs' === ext.kind ?
377
+ await jostraca.generate(
378
+ { ...jopts, folder: ext.folder },
379
+ () => ExternalDocs({
380
+ model, item: ext.target, cmpfolder: folder, sdkrelpath,
381
+ })) :
382
+ await jostraca.generate(
277
383
  { ...jopts, folder: ext.folder },
278
384
  () => ExternalTarget({
279
385
  model, target: ext.target, cmpfolder: folder,
280
- // How to walk BACK to the SDK project from the destination. A
281
- // target generating out of tree usually sits beside the SDK in a
386
+ // How to walk BACK to the SDK project from the destination. An
387
+ // item generating out of tree usually sits beside the SDK in a
282
388
  // known layout, and its own docs, scripts and live tests need to
283
389
  // name that path.
284
- sdkrelpath: externalSdkRel(ext, root, log),
390
+ sdkrelpath,
285
391
  }))
286
392
 
287
393
  showChanges(jopts.log, 'generate-result', eres, Path.dirname(process.cwd()))
@@ -300,26 +406,47 @@ function SdkGen(opts: SdkGenOptions) {
300
406
  }
301
407
 
302
408
 
303
- async function action(args: string[]): Promise<any> {
304
- const pargs = args.map(arg => Jsonic(arg))
305
-
409
+ // Action arguments are PATHS AND NAMES, and they reach the actions as the
410
+ // raw strings the shell gave us.
411
+ //
412
+ // They used to be mapped through `Jsonic(arg)` first, which parses each one
413
+ // as relaxed JSON — so a Windows absolute ref arrived as an OBJECT:
414
+ // `Jsonic('C:\\pkg\\go')` is `{ C: '\\pkg\\go' }`, and every downstream path
415
+ // join then missed. (`ts,py` also became `['ts','py']`, which is why
416
+ // parseAddNames still carries a non-string branch; it splits on commas
417
+ // itself, so nothing is lost by handing it strings.)
418
+ //
419
+ // Nothing here wants structured arguments: every action takes names and
420
+ // refs. Parsing them was pure loss.
421
+ //
422
+ // `flags` is the second parameter because `--only` and `--alias` are
423
+ // arguments to ONE command, not generator configuration: passing them
424
+ // through `SdkGen({…})` like `debug`/`dryrun` would make a later
425
+ // `action()` call on the same instance silently inherit them.
426
+ async function action(
427
+ args: string[], flags?: Record<string, any>,
428
+ ): Promise<any> {
306
429
  const actname = args[0]
307
430
  const actionFunc = ACTION_MAP[actname]
308
431
 
309
432
  if (null == actionFunc) {
310
- throw new SdkGenError('Unknown action: ' + actname)
433
+ throw new SdkGenError(
434
+ 'Unknown action: ' + actname +
435
+ ' (expected: ' + actionNames().join(', ') + ')')
311
436
  }
312
437
 
313
- const ctx = resolveActionContext()
438
+ const ctx = resolveActionContext(flags, needsModel(args))
314
439
 
315
- return await actionFunc(pargs, ctx)
440
+ return await actionFunc(args, ctx)
316
441
  }
317
442
 
318
443
 
319
- function resolveActionContext(): ActionContext {
444
+ function resolveActionContext(
445
+ flags?: Record<string, any>, wantmodel?: boolean,
446
+ ): ActionContext {
320
447
 
321
448
  // TODO: use AsyncLocalStorage to avoid reloading model
322
- const { model, url } = resolveModel()
449
+ const { model, url } = resolveModel(false !== wantmodel)
323
450
 
324
451
  const ctx: ActionContext = {
325
452
  fs: () => fs,
@@ -329,16 +456,25 @@ function SdkGen(opts: SdkGenOptions) {
329
456
  url,
330
457
  jostraca,
331
458
  opts,
459
+ flags: flags ?? {},
332
460
  }
333
461
 
334
462
  return ctx
335
463
  }
336
464
 
337
465
 
338
- function resolveModel() {
466
+ function resolveModel(wanted: boolean) {
339
467
  const path = './model/sdk.aontu'
340
468
  const errs: any[] = []
341
469
 
470
+ // A verb that does not act on a project (see `needsModel`) is run where
471
+ // there is no project model, so its absence is not an error there. Its
472
+ // PRESENCE still is compiled — an author checking a package from inside a
473
+ // project should get the same model every other verb gets.
474
+ if (!wanted && !fs.existsSync(path)) {
475
+ return { model: { main: {} } as any, url: path }
476
+ }
477
+
342
478
  if (null == aontu) {
343
479
  aontu = new Aontu()
344
480
  }
@@ -388,6 +524,47 @@ function SdkGen(opts: SdkGenOptions) {
388
524
  }
389
525
  }
390
526
 
527
+ // The third kind. sdkgen ships no docs items, so every ref here is a path
528
+ // into a package — see action/docs.
529
+ const docs = {
530
+ add: async (items: string[]): Promise<ActionResult> => {
531
+ const ctx = resolveActionContext()
532
+ return docs_add(items, ctx)
533
+ }
534
+ }
535
+
536
+ // The whole-package verbs. `flags` mirrors the CLI's `--only` / `--alias`,
537
+ // for the same reason they are `action`'s second parameter rather than
538
+ // constructor options: they are arguments to one call.
539
+ const packages = {
540
+ add: async (
541
+ refs: string[], flags?: Record<string, any>,
542
+ ): Promise<ActionResult> => {
543
+ const ctx = resolveActionContext(flags)
544
+ return package_add(refs, ctx)
545
+ },
546
+
547
+ list: async (): Promise<ActionResult> => {
548
+ const ctx = resolveActionContext()
549
+ return action_package(['package', 'list'], ctx)
550
+ },
551
+
552
+ update: async (
553
+ names: string[], flags?: Record<string, any>,
554
+ ): Promise<ActionResult> => {
555
+ const ctx = resolveActionContext(flags)
556
+ return package_update(names, ctx)
557
+ },
558
+
559
+ // Validate a package being AUTHORED — see action/check. The only verb
560
+ // here that does not need a project model, because it does not act on a
561
+ // project; `false` says so to `resolveActionContext`.
562
+ check: async (refs?: string[]): Promise<ActionResult> => {
563
+ const ctx = resolveActionContext(undefined, false)
564
+ return cmd_package_check(['package', 'check', ...(refs ?? [])], ctx)
565
+ },
566
+ }
567
+
391
568
  // Has this project's `.sdk/` drifted from the scaffold? See action/doctor.
392
569
  const check = async (): Promise<ActionResult> => {
393
570
  const ctx = resolveActionContext()
@@ -401,8 +578,13 @@ function SdkGen(opts: SdkGenOptions) {
401
578
  generate,
402
579
  action,
403
580
  check,
581
+ docs,
404
582
  target,
405
583
  feature,
584
+
585
+ // `package` is a reserved word in a strict-mode object shorthand, so the
586
+ // local binding is `packages` and the PUBLIC name matches the CLI verb.
587
+ package: packages,
406
588
  }
407
589
 
408
590
  }
@@ -437,6 +619,9 @@ SdkGen.makeBuild = async function(opts: SdkGenOptions) {
437
619
 
438
620
 
439
621
  type ExternalSpec = {
622
+ // Which kind's collection this item came from — `target` or `docs`. The
623
+ // out-of-tree rules are the same for both; only the dispatch differs.
624
+ kind: string
440
625
  name: string
441
626
  target: any
442
627
  folder: string
@@ -453,22 +638,36 @@ type ExternalSpec = {
453
638
  // directory the command happened to be run from.
454
639
  //
455
640
  // An INACTIVE target is still listed: it must be taken out of the in-tree
456
- // model (see withoutTargets) so that switching it off does not silently
641
+ // model (see withoutExternal) so that switching it off does not silently
457
642
  // relocate it into `<sdk-repo>/<target>/`. The generate loop skips it.
458
- function externalTargets(model: any, folder: string): ExternalSpec[] {
459
- const targets = model?.main?.[KIT]?.target || {}
460
-
461
- return Object.keys(targets).sort()
462
- .map((name: string) => ({ name, target: targets[name] }))
463
- .filter((t: any) => {
464
- const path = t.target?.output?.path
465
- return null != path && '' !== path
466
- })
467
- .map((t: any) => ({
468
- ...t,
469
- folder: Path.resolve(folder, String(t.target.output.path)),
470
- active: false !== t.target.active,
471
- }))
643
+ // Every item, of every kind, that generates OUTSIDE the SDK repo.
644
+ //
645
+ // Keyed by kind because `docs` needs exactly this and a second copy of it
646
+ // would drift: the out-of-tree rules — a destination must be outside the
647
+ // project, unclaimed, and either empty or previously generated into — are
648
+ // properties of WRITING SOMEWHERE ELSE, not of being a target.
649
+ //
650
+ // The claim map spanning kinds is the load-bearing part: a docs item and a
651
+ // target pointed at the same folder would otherwise silently overwrite each
652
+ // other, in whatever order the passes happen to run.
653
+ function externalItems(
654
+ model: any, folder: string, kinds: string[],
655
+ ): ExternalSpec[] {
656
+ return kinds.flatMap((kind: string) => {
657
+ const items = model?.main?.[KIT]?.[kind] || {}
658
+
659
+ return Object.keys(items).sort()
660
+ .map((name: string) => ({ kind, name, target: items[name] }))
661
+ .filter((t: any) => {
662
+ const path = t.target?.output?.path
663
+ return null != path && '' !== path
664
+ })
665
+ .map((t: any) => ({
666
+ ...t,
667
+ folder: Path.resolve(folder, String(t.target.output.path)),
668
+ active: false !== t.target.active,
669
+ }))
670
+ })
472
671
  }
473
672
 
474
673
 
@@ -480,9 +679,12 @@ function externalTargets(model: any, folder: string): ExternalSpec[] {
480
679
  const EXTERNAL_MARKER = '.jostraca'
481
680
 
482
681
  // Entries that do not count as content when deciding whether a destination
483
- // is empty. A `git init` (or a clone of an empty repo) leaves only `.git`,
484
- // and that is exactly the destination a FIRST generation is aimed at.
485
- const EXTERNAL_EMPTY = ['.git', '.DS_Store']
682
+ // is empty. A `git init` (or a clone of an empty repo) leaves only `.git`, and
683
+ // that is exactly the destination a FIRST generation is aimed at — as is one
684
+ // the maintainer opened in the Finder on the way to declaring it.
685
+ //
686
+ // `isNoise`, not `isJunk`: a destination holding build output is not empty,
687
+ // whether or not sdkgen would ever copy such a thing. See helpers/junk.
486
688
 
487
689
 
488
690
  // Refuse a destination the project cannot have meant.
@@ -517,39 +719,44 @@ function checkExternalFolders(external: ExternalSpec[], root: string, fs: any) {
517
719
  // path valid.
518
720
  if (!ext.active) continue
519
721
 
520
- const where = 'Target "' + ext.name + '" has output path "' +
722
+ const label = ext.kind.charAt(0).toUpperCase() + ext.kind.slice(1)
723
+
724
+ const where = label + ' "' + ext.name + '" has output path "' +
521
725
  ext.target.output.path + '", which resolves to: ' + ext.folder +
522
726
  '\n (SDK project: ' + root + ')'
523
727
 
524
728
  if (ext.folder === root || folderContains(root, ext.folder)) {
525
729
  throw new SdkGenError(
526
- 'External target output path is inside the SDK project.\n ' + where +
527
- '\n A target generating into the SDK project must leave `output: ' +
730
+ 'External output path is inside the SDK project.\n ' + where +
731
+ '\n An item generating into the SDK project must leave `output: ' +
528
732
  'path` unset — it is then generated in-tree, as <sdk-project>/' +
529
733
  ext.name + '/.')
530
734
  }
531
735
 
532
736
  if (folderContains(ext.folder, root)) {
533
737
  throw new SdkGenError(
534
- 'External target output path contains the SDK project.\n ' + where +
738
+ 'External output path contains the SDK project.\n ' + where +
535
739
  '\n Generation would write this package over the directory holding ' +
536
740
  'the SDK project itself.')
537
741
  }
538
742
 
743
+ // ACROSS KINDS, not within one: `docs` and `target` are separate
744
+ // namespaces, so an item of each may legitimately be called `api` — and
745
+ // pointing both at one folder is the mistake this catches.
539
746
  if (null != claimed[ext.folder]) {
540
747
  throw new SdkGenError(
541
- 'External target output path is already claimed by target "' +
542
- claimed[ext.folder] + '".\n ' + where +
543
- '\n Two targets generating into the same folder overwrite each ' +
544
- 'other, in target-name order.')
748
+ 'External output path is already claimed by ' +
749
+ claimed[ext.folder] + '.\n ' + where +
750
+ '\n Two items generating into the same folder overwrite each ' +
751
+ 'other, in whatever order the passes run.')
545
752
  }
546
- claimed[ext.folder] = ext.name
753
+ claimed[ext.folder] = ext.kind + ' "' + ext.name + '"'
547
754
 
548
755
  if (!fs.existsSync(ext.folder)) continue
549
756
 
550
757
  if (!fs.statSync(ext.folder).isDirectory()) {
551
758
  throw new SdkGenError(
552
- 'External target output path is not a folder.\n ' + where)
759
+ 'External output path is not a folder.\n ' + where)
553
760
  }
554
761
 
555
762
  if (true === ext.target.output.adopt) continue
@@ -559,7 +766,7 @@ function checkExternalFolders(external: ExternalSpec[], root: string, fs: any) {
559
766
 
560
767
  if (entries.includes(EXTERNAL_MARKER)) continue
561
768
 
562
- const content = entries.filter((entry) => !EXTERNAL_EMPTY.includes(entry))
769
+ const content = entries.filter((entry) => !isNoise(entry))
563
770
 
564
771
  if (0 < content.length) {
565
772
  throw new SdkGenError(
@@ -635,23 +842,32 @@ function externalSdkRel(ext: ExternalSpec, root: string, log: any): string {
635
842
  // A shallow clone down to `target` only — the model is large, entities and
636
843
  // features are shared with the external pass, and a deep copy would both
637
844
  // cost and quietly break identity comparisons.
638
- function withoutTargets(model: any, external: { name: string }[]): any {
639
- const drop = new Set(external.map((e) => e.name))
640
- const targets = model?.main?.[KIT]?.target || {}
641
-
642
- const kept: any = {}
643
- for (const name of Object.keys(targets)) {
644
- if (!drop.has(name)) {
645
- kept[name] = targets[name]
845
+ // The model the IN-TREE pass sees: every out-of-tree item removed, whatever
846
+ // its kind. Rendering one in both passes would ALSO write it into
847
+ // `<sdk-repo>/<name>/`, which is the folder the item asked not to use.
848
+ function withoutExternal(
849
+ model: any, external: { kind: string, name: string }[],
850
+ ): any {
851
+ const drop = new Set(external.map((e) => e.kind + ':' + e.name))
852
+
853
+ const kit: any = { ...model.main[KIT] }
854
+
855
+ for (const kind of new Set(external.map((e) => e.kind))) {
856
+ const items = model?.main?.[KIT]?.[kind] || {}
857
+ const kept: any = {}
858
+
859
+ for (const name of Object.keys(items)) {
860
+ if (!drop.has(kind + ':' + name)) {
861
+ kept[name] = items[name]
862
+ }
646
863
  }
864
+
865
+ kit[kind] = kept
647
866
  }
648
867
 
649
868
  return {
650
869
  ...model,
651
- main: {
652
- ...model.main,
653
- [KIT]: { ...model.main[KIT], target: kept },
654
- },
870
+ main: { ...model.main, [KIT]: kit },
655
871
  }
656
872
  }
657
873
 
@@ -698,6 +914,8 @@ export type {
698
914
  FeatureSource,
699
915
  DoctorReport,
700
916
  RegisterOptions,
917
+ Manifest,
918
+ ManifestRead,
701
919
  }
702
920
 
703
921
  export type {
@@ -838,6 +1056,16 @@ export {
838
1056
  featureExcludes,
839
1057
  fullsetExcludes,
840
1058
  srcFeatureExcludes,
1059
+
1060
+ definitionPath,
1061
+ definitionFolder,
1062
+ definitionNames,
1063
+
1064
+ MANIFEST,
1065
+ manifestPath,
1066
+ readManifest,
1067
+ validateManifest,
1068
+
841
1069
  jsProp,
842
1070
  jsOptProp,
843
1071
  jsKey,
package/src/types.ts CHANGED
@@ -64,7 +64,7 @@ type ModelTarget = NameCases & {
64
64
  // the SDK repo — see cmp/ExternalTarget and
65
65
  // docs/explanation/out-of-tree-targets.
66
66
  //
67
- // Typed rather than left to the index signature because `externalTargets()`
67
+ // Typed rather than left to the index signature because `externalItems()`
68
68
  // decides from these keys whether to write OUTSIDE the repo, and a
69
69
  // destination path read off a bare `any` is one a rename can silently
70
70
  // change to undefined.
@@ -121,12 +121,41 @@ type ActionContext = {
121
121
  model: SdkModel,
122
122
  url: string,
123
123
  opts: any,
124
- jostraca: any
124
+ jostraca: any,
125
+
126
+ // PER-INVOCATION action arguments — `--only`, `--alias` — as opposed to
127
+ // generator configuration. `debug` and `dryrun` reach actions through the
128
+ // `SdkGen({…})` constructor because they describe the generator; these
129
+ // describe one command, so smuggling them through the constructor would
130
+ // make a second `action()` call on the same instance inherit them.
131
+ flags?: Record<string, any>
132
+
133
+ // How `package update` fetches a new version. Injectable so tests do not
134
+ // shell out, and so a caller with its own dependency management (a
135
+ // monorepo, a vendored checkout) can supply one. Defaults to npm.
136
+ fetchPackage?: (pkgname: string, actx: ActionContext) => Promise<void>
125
137
  }
126
138
 
127
139
 
140
+ // An action either WROTE something or REPORTED something, and the two kinds
141
+ // of verb return different things: `target add` returns jostraca's result,
142
+ // while `doctor` and `package check` return a report the CLI turns into an
143
+ // exit code. Both optional, because neither verb has anything to say about
144
+ // the other's field — a check that invented an empty `jres` would be lying
145
+ // about having generated nothing.
128
146
  type ActionResult = {
129
- jres: JostracaResult
147
+ jres?: JostracaResult
148
+ report?: ActionReport
149
+ }
150
+
151
+
152
+ // What a CHECKING verb returns. `ok` is the contract — `bin/voxgig-sdkgen`
153
+ // exits non-zero on `false` — and `summary` is the one line it prints, so the
154
+ // binary never has to know what any particular verb's findings look like.
155
+ type ActionReport = {
156
+ ok: boolean
157
+ summary?: string
158
+ [key: string]: any
130
159
  }
131
160
 
132
161
 
@@ -138,6 +167,7 @@ export {
138
167
  export type {
139
168
  ActionContext,
140
169
  ActionResult,
170
+ ActionReport,
141
171
  SdkModel,
142
172
  ModelKit,
143
173
  ModelTarget,
package/src/utility.ts CHANGED
@@ -133,6 +133,23 @@ export {
133
133
  //
134
134
  // The reader side is unchanged either way: make_config returns the same map,
135
135
  // so nothing downstream can tell which representation it got.
136
+ //
137
+ // For two targets the literal is not merely expensive but IMPOSSIBLE past a
138
+ // point, which is what fixes the threshold rather than leaving it a taste
139
+ // question:
140
+ //
141
+ // haskell GHC 9.4.7 refuses a large static structure outright -
142
+ // "sorry! (unimplemented feature or known bug) ... Trying to
143
+ // allocate more than 129024 bytes ... Suggestion: read data from a
144
+ // file instead of having large static data structures in code"
145
+ // (GHC issue 4505). Measured: the CV literal compiles at 828 KB of
146
+ // model and fails at 1.4 MB, so 256 KB clears it by more than 3x.
147
+ //
148
+ // clojure a string literal is a constant-pool UTF-8 entry capped at 65,535
149
+ // bytes, so the DATA constant has to be chunked - see cljStringChunks.
150
+ //
151
+ // Anything above the threshold therefore takes the data path in every target,
152
+ // and the languages with a hard ceiling are the ones with the most margin.
136
153
  const CONFIG_DATA_THRESHOLD = 256 * 1024
137
154
 
138
155