@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
@@ -0,0 +1,474 @@
1
+ // THE PACKAGE MANIFEST: `sdkgen-package.json`, beside the package's `.sdk`.
2
+ //
3
+ // See docs/design/sdkgen-packages.md §2.
4
+ //
5
+ // <package-root>/
6
+ // ├── sdkgen-package.json <- this
7
+ // └── .sdk/ <- shaped exactly like ts/project/.sdk
8
+ //
9
+ // WHY JSON RATHER THAN AONTU
10
+ //
11
+ // Consumer models compile under `@voxgig/model`'s strictly-configured parser,
12
+ // which accepts `#` comments only — a `//` line is a parse error, and seven
13
+ // shipped targets once broke on exactly that (ts/test/model-compile.test.ts
14
+ // exists because of it). The manifest is read by the CLI and never unified
15
+ // into the model, so JSON keeps it outside that trap entirely.
16
+ //
17
+ // WHAT IT IS FOR
18
+ //
19
+ // A package's CLAIM about what it provides, so that:
20
+ //
21
+ // - `package add <pkg>` knows what to install without guessing from
22
+ // directory listings;
23
+ // - a typo'd ref fails naming what the package actually provides, rather
24
+ // than resolving to a folder that exists and failing later on a missing
25
+ // file;
26
+ // - an item records WHICH PACKAGE supplied it (`package:` provenance), so a
27
+ // project can be resynced against a newer version of that package.
28
+ //
29
+ // A claim is worth nothing unless it is checked, so `validateManifest`
30
+ // compares it against the trees actually on disk, in both directions: a
31
+ // manifest that lies is an error, on-disk extras are a warning.
32
+ //
33
+ // THE MANIFEST IS OPTIONAL FOR A DIRECT REF. `target add ../pkg/iot-go`
34
+ // against a bare `.sdk`-shaped folder keeps working exactly as it does today
35
+ // — that is what every existing consumer fixture is — and simply records no
36
+ // `package` provenance. Requiring one is `package add`'s business.
37
+
38
+ import Path from 'node:path'
39
+
40
+ import { definitionNames } from './definition'
41
+
42
+
43
+ const MANIFEST = 'sdkgen-package.json'
44
+
45
+ // The manifest schema version this generator understands. Bumped only for a
46
+ // BREAKING change to the manifest's own shape; new optional fields do not
47
+ // need it. Read from `sdkgen.package` so the gate is the very first thing in
48
+ // the file, before anything version-specific is interpreted.
49
+ const SCHEMA = 1
50
+
51
+
52
+ type Manifest = {
53
+ sdkgen: { package: number }
54
+ name: string
55
+ version?: string
56
+
57
+ // DECLARATIVE ONLY, so far. Nothing reads this range yet: the engine gate
58
+ // belongs with `package add` (design §9), which is the next phase, and
59
+ // there is no semver implementation here to compare with — this package has
60
+ // no runtime dependencies. Until then a package declaring an incompatible
61
+ // range installs anyway. Stated here rather than left to be discovered,
62
+ // because a field that looks enforced and is not is worse than an absent
63
+ // one.
64
+ engines?: { sdkgen?: string }
65
+
66
+ // Keyed BY KIND, so a new kind (docs, …) needs no schema change.
67
+ provides: Record<string, string[]>
68
+
69
+ // Optional: which targets a feature ships source for, so a missing overlay
70
+ // is reported as out-of-declared-coverage rather than broken.
71
+ targetsSupported?: Record<string, string[]>
72
+
73
+ // Optional: the author's declared parity tier per target, in
74
+ // ts/test/parity.test.ts's vocabulary.
75
+ parity?: Record<string, string>
76
+ }
77
+
78
+
79
+ // What `readManifest` found. THREE outcomes, deliberately distinct: a
80
+ // manifest, no manifest, or a manifest that could not be read. Collapsing the
81
+ // last two into "no manifest" is what would let a typo in a package's own
82
+ // JSON silently downgrade `package add` into a no-op.
83
+ type ManifestRead = {
84
+ // Where it was looked for, always — the value an error message needs.
85
+ file: string
86
+
87
+ // Present only when one was found AND parsed.
88
+ manifest?: Manifest
89
+
90
+ // Present only when one was found and could NOT be parsed.
91
+ err?: string
92
+ }
93
+
94
+
95
+ type Finding = {
96
+ level: 'error' | 'warn' | 'info'
97
+ point: string
98
+ note: string
99
+ kind?: string
100
+ name?: string
101
+ file?: string
102
+ }
103
+
104
+
105
+ // The manifest path for a `.sdk` folder: its SIBLING, not its child.
106
+ //
107
+ // The package root is the parent of `.sdk` — which is what `resolveSource`
108
+ // already assumes when it probes `<ref-dir>/.sdk`, so the manifest sits where
109
+ // the ref already pointed.
110
+ function manifestPath(sdkfolder: string): string {
111
+ return Path.join(sdkfolder, '..', MANIFEST)
112
+ }
113
+
114
+
115
+ function readManifest(fs: any, sdkfolder: string): ManifestRead {
116
+ const file = manifestPath(sdkfolder)
117
+
118
+ if (!fs.existsSync(file)) {
119
+ return { file }
120
+ }
121
+
122
+ let manifest: any
123
+ try {
124
+ manifest = JSON.parse(String(fs.readFileSync(file, 'utf8')))
125
+ }
126
+ catch (err: any) {
127
+ return { file, err: err.message }
128
+ }
129
+
130
+ // A JSON scalar or array parses fine and then fails much later, on a
131
+ // property access that reads `undefined`. Reject it here, where the file
132
+ // that caused it is still in hand.
133
+ if (null == manifest || 'object' !== typeof manifest ||
134
+ Array.isArray(manifest)) {
135
+ return { file, err: 'not a JSON object' }
136
+ }
137
+
138
+ return { file, manifest }
139
+ }
140
+
141
+
142
+ // Is the manifest itself well-formed? Checked before anything reads its
143
+ // contents, so a malformed file produces one clear finding rather than a
144
+ // cascade of consequences.
145
+ function checkShape(manifest: Manifest, file: string): Finding[] {
146
+ const found: Finding[] = []
147
+
148
+ const version = manifest?.sdkgen?.package
149
+
150
+ if (null == version) {
151
+ found.push({
152
+ level: 'error', point: 'manifest-unversioned', file,
153
+ note: file + ': no `sdkgen.package` schema version — this is the gate ' +
154
+ 'that says the file is an sdkgen package manifest at all'
155
+ })
156
+ }
157
+ else if (!Number.isInteger(version) || version < 1) {
158
+ // A NON-NUMBER passes `version > SCHEMA` silently — `'banana' > 1` and
159
+ // `({}) > 1` are both false — so the gate that decides whether this
160
+ // generator may interpret the rest of the file would have been skipped by
161
+ // anything that is not a number at all. The version is the one field that
162
+ // must be checked before anything version-specific is read.
163
+ found.push({
164
+ level: 'error', point: 'manifest-schema-invalid', file,
165
+ note: file + ': `sdkgen.package` must be a positive integer schema ' +
166
+ 'version, not ' + JSON.stringify(version)
167
+ })
168
+ }
169
+ else if (version > SCHEMA) {
170
+ // FORWARD, not backward: a manifest written for a later schema may use
171
+ // fields this generator would misread. Say both numbers — "too new" is
172
+ // actionable (upgrade sdkgen), "invalid" is not.
173
+ found.push({
174
+ level: 'error', point: 'manifest-schema-too-new', file,
175
+ note: file + ': manifest schema version ' + version +
176
+ ' is newer than this generator understands (' + SCHEMA +
177
+ ') — upgrade @voxgig/sdkgen'
178
+ })
179
+ }
180
+
181
+ if ('string' !== typeof manifest?.name || '' === manifest.name) {
182
+ found.push({
183
+ level: 'error', point: 'manifest-unnamed', file,
184
+ note: file + ': no `name` — the name is what an item records as its ' +
185
+ '`package` provenance and what `package update` is given'
186
+ })
187
+ }
188
+
189
+ const provides = manifest?.provides
190
+
191
+ if (null == provides || 'object' !== typeof provides ||
192
+ Array.isArray(provides)) {
193
+ found.push({
194
+ level: 'error', point: 'manifest-provides-missing', file,
195
+ note: file + ': no `provides` map — a package that provides nothing ' +
196
+ 'has nothing to add (use `{}` to say so deliberately)'
197
+ })
198
+ }
199
+ else {
200
+ for (const [kind, names] of Object.entries(provides)) {
201
+ if (!Array.isArray(names) ||
202
+ names.some((n: any) => 'string' !== typeof n || '' === n)) {
203
+ found.push({
204
+ level: 'error', point: 'manifest-provides-malformed', file, kind,
205
+ note: file + ': `provides.' + kind +
206
+ '` is not a list of names'
207
+ })
208
+ continue
209
+ }
210
+
211
+ // A NAME, not a path. `missingPaths` joins these onto the package root,
212
+ // so `../../outside` would send every existence check out of the
213
+ // package entirely and let a manifest validate clean against unrelated
214
+ // files. `~` is excluded for a different reason: it is the alias
215
+ // separator, so a name containing one could never be resolved back to
216
+ // this item.
217
+ for (const name of names as string[]) {
218
+ if (!ITEM_NAME_RE.test(name)) {
219
+ found.push({
220
+ level: 'error', point: 'manifest-item-name-invalid', file, kind,
221
+ name,
222
+ note: file + ': `provides.' + kind + '` lists ' +
223
+ JSON.stringify(name) + ' — an item name must match ' +
224
+ ITEM_NAME_RE.source + ' (a name, not a path)'
225
+ })
226
+ }
227
+ }
228
+
229
+ const seen = new Set<string>()
230
+ for (const name of names as string[]) {
231
+ if (seen.has(name)) {
232
+ found.push({
233
+ level: 'error', point: 'manifest-item-duplicated', file, kind,
234
+ name,
235
+ note: file + ': `provides.' + kind + '` lists ' +
236
+ JSON.stringify(name) + ' more than once'
237
+ })
238
+ }
239
+ seen.add(name)
240
+ }
241
+ }
242
+ }
243
+
244
+ return found
245
+ }
246
+
247
+
248
+ // What may name a target, feature, or any other item.
249
+ //
250
+ // Deliberately narrow: these become directory names, aontu model keys,
251
+ // generated config keys (`options.feature.<name>`) and component filename
252
+ // suffixes, so the intersection of what all of those accept is the real
253
+ // constraint. The shipped names — `go-cli`, `py-data`, `seneca-provider`,
254
+ // `clienttrack` — all fit.
255
+ const ITEM_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/
256
+
257
+
258
+ // Does the package's disk match its claim?
259
+ //
260
+ // BOTH DIRECTIONS, because they fail differently and neither implies the
261
+ // other:
262
+ //
263
+ // claim without disk -> ERROR. `package add` would try to install it and
264
+ // break partway through, having already written the
265
+ // items listed before it.
266
+ // disk without claim -> WARN. Everything works; the author has shipped
267
+ // something nobody can discover, which is nearly
268
+ // always a forgotten manifest edit.
269
+ //
270
+ // `kindRequires` is supplied by the caller rather than imported, because the
271
+ // kind registry lives in `action/` and this is a helper — the dependency has
272
+ // to point that way, not this way.
273
+ function validateManifest(
274
+ fs: any,
275
+ sdkfolder: string,
276
+ manifest: Manifest,
277
+ kinds: Record<string, { trees?: { path: string, required: boolean }[] }>,
278
+ ): Finding[] {
279
+ const file = manifestPath(sdkfolder)
280
+
281
+ const shape = checkShape(manifest, file)
282
+
283
+ // A malformed manifest cannot be compared against anything — the checks
284
+ // below would read `undefined` and invent findings about it.
285
+ if (0 < shape.length) {
286
+ return shape
287
+ }
288
+
289
+ const found: Finding[] = []
290
+ const provides = manifest.provides
291
+
292
+ for (const [kind, names] of Object.entries(provides)) {
293
+ const def = kinds[kind]
294
+
295
+ if (null == def) {
296
+ found.push({
297
+ level: 'error', point: 'manifest-unknown-kind', file, kind,
298
+ note: file + ': `provides.' + kind + '` — unknown kind; this ' +
299
+ 'generator knows: ' + Object.keys(kinds).sort().join(', ')
300
+ })
301
+ continue
302
+ }
303
+
304
+ // ONE ORACLE FOR BOTH DIRECTIONS. The unclaimed-extras loop below reads
305
+ // the directory listing; if this loop asked the filesystem instead
306
+ // (`existsSync`/`statSync`), the two would disagree on a case-insensitive
307
+ // filesystem — APFS and NTFS by default, which is most package authors'
308
+ // machines. A manifest claiming `IoTGo` beside a `model/target/iotgo.aontu`
309
+ // then validated CLEAN for the author and failed for every Linux consumer,
310
+ // which is precisely the "validated package that cannot install" this
311
+ // function exists to prevent. It also makes the duplicate check above
312
+ // meaningful: `['retry','Retry']` are two names to a Set and one file to
313
+ // the filesystem.
314
+ const defined = new Set(definitionNames(fs, sdkfolder, kind))
315
+
316
+ for (const name of names) {
317
+ for (const missing of
318
+ missingPaths(fs, sdkfolder, kind, name, def, defined)) {
319
+ found.push({
320
+ level: 'error', point: 'manifest-item-missing', file, kind, name,
321
+ note: file + ': claims ' + kind + ' `' + name +
322
+ '` but ' + missing + ' is not in the package'
323
+ })
324
+ }
325
+ }
326
+ }
327
+
328
+ // The other direction. Only for kinds the manifest MENTIONS plus the ones
329
+ // this generator knows — a directory named after a kind nobody registered
330
+ // is already reported above if claimed, and is not this check's business if
331
+ // not.
332
+ for (const kind of Object.keys(kinds)) {
333
+ const claimed = new Set(provides[kind] ?? [])
334
+
335
+ for (const name of definitionNames(fs, sdkfolder, kind)) {
336
+ if (!claimed.has(name)) {
337
+ found.push({
338
+ level: 'warn', point: 'manifest-item-unclaimed', file, kind, name,
339
+ note: file + ': model/' + kind + '/' + name +
340
+ '.aontu is in the package but not listed in `provides.' + kind +
341
+ '` — nothing will install it'
342
+ })
343
+ }
344
+ }
345
+ }
346
+
347
+ return found
348
+ }
349
+
350
+
351
+ // Every path an item of this kind needs, that is not there AS THE RIGHT KIND
352
+ // OF THING.
353
+ //
354
+ // The definition file is implied for every kind; the kind's REQUIRED trees
355
+ // add whatever else it needs (a target's component and template trees, a docs
356
+ // item's components). An optional tree — a docs item's templates — is not
357
+ // checked here, because a package that legitimately ships none must validate.
358
+ //
359
+ // FILE vs DIRECTORY is checked, not merely existence. A regular file at
360
+ // `src/cmp/<t>` satisfies `existsSync` and satisfies nothing else: `target
361
+ // add` walks both of those paths as trees, so a package validated on
362
+ // existence alone could still be incapable of installing a usable target —
363
+ // which is the one thing validation is supposed to rule out.
364
+ //
365
+ // Returned as package-relative strings, because that is what an error message
366
+ // should say: the absolute path is this machine's business, not the author's.
367
+ function missingPaths(
368
+ fs: any,
369
+ sdkfolder: string,
370
+ kind: string,
371
+ name: string,
372
+ def: { trees?: { path: string, required: boolean }[] },
373
+ defined: Set<string>,
374
+ ): string[] {
375
+ const missing: string[] = []
376
+
377
+ // The definition, by EXACT NAME from the directory listing — see the note
378
+ // at the call site about the two directions needing one oracle.
379
+ if (!defined.has(name)) {
380
+ missing.push('model/' + kind + '/' + name + '.aontu')
381
+ }
382
+
383
+ for (const tree of (def.trees ?? []).filter((t) => t.required)) {
384
+ const rel = tree.path.split('{name}').join(name)
385
+ const got = entryKind(fs, Path.join(sdkfolder, ...rel.split('/')))
386
+
387
+ if ('dir' !== got) {
388
+ missing.push(rel +
389
+ ('none' === got ? '' : ' (a file where a directory is required)'))
390
+ }
391
+ }
392
+
393
+ return missing
394
+ }
395
+
396
+
397
+ // What is at a path: a file, a directory, or nothing. `statSync` FOLLOWS
398
+ // symlinks, deliberately — a package may legitimately symlink a shared tree,
399
+ // and what matters is what `target add` will find when it walks it.
400
+ function entryKind(fs: any, path: string): 'file' | 'dir' | 'none' {
401
+ try {
402
+ const stat = fs.statSync(path)
403
+ return stat.isDirectory() ? 'dir' : 'file'
404
+ }
405
+ catch (err: any) {
406
+ return 'none'
407
+ }
408
+ }
409
+
410
+
411
+ // WHERE A PACKAGE ROOT IS, given a ref.
412
+ //
413
+ // The same probe chain `resolveSource` uses for an ITEM, one level up: an
414
+ // installed npm package first, then a path relative to the project, then an
415
+ // absolute path. A package root is the folder HOLDING `.sdk`, which is what
416
+ // makes `<root>/<item>` the item ref — so the two resolvers agree by
417
+ // construction about what a package is.
418
+ //
419
+ // It reports rather than throws, and it does not require a manifest, because
420
+ // its two callers want different things from the same probe: `package add`
421
+ // refuses a folder with no manifest (that is the verb's whole subject), while
422
+ // `package check` reports the absence as its first finding and carries on
423
+ // checking the `.sdk` — an author who has not written the manifest yet is
424
+ // exactly who needs the rest of the battery.
425
+ type PackageProbe = {
426
+ ref: string
427
+ root: string
428
+ sdk: string
429
+ read: ManifestRead
430
+ }
431
+
432
+
433
+ function probePackage(
434
+ fs: any, project: string, ref: string,
435
+ ): { found?: PackageProbe, search: string[] } {
436
+ const search: string[] = []
437
+
438
+ const candidates = Path.isAbsolute(ref) ? [ref] : [
439
+ Path.join(project, 'node_modules', ref),
440
+ Path.join(project, ref),
441
+ ]
442
+
443
+ for (const root of candidates) {
444
+ const sdk = Path.normalize(Path.join(root, '.sdk'))
445
+ search.push(sdk)
446
+
447
+ if (!fs.existsSync(sdk)) {
448
+ continue
449
+ }
450
+
451
+ return { found: { ref, root, sdk, read: readManifest(fs, sdk) }, search }
452
+ }
453
+
454
+ return { search }
455
+ }
456
+
457
+
458
+ export type {
459
+ Manifest,
460
+ ManifestRead,
461
+ PackageProbe,
462
+ Finding,
463
+ }
464
+
465
+ export {
466
+ MANIFEST,
467
+ SCHEMA,
468
+ ITEM_NAME_RE,
469
+ manifestPath,
470
+ probePackage,
471
+ readManifest,
472
+ validateManifest,
473
+ checkShape,
474
+ }