@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,262 @@
1
+ // THE MODEL RULES A PACKAGE'S `.aontu` FILES MUST SATISFY.
2
+ //
3
+ // See docs/design/sdkgen-packages.md §10 (the rules) and §14 (the battery).
4
+ //
5
+ // WHY THIS IS A MODULE AND NOT A TEST
6
+ //
7
+ // Every rule below was already enforced — on the BUNDLED scaffold only, and
8
+ // from inside `ts/test/model-compile.test.ts`, where nothing else can call
9
+ // it. `package check` has to apply the same rules to a package this
10
+ // generator has never seen, and a rule written twice diverges: this
11
+ // workstream has produced that exact defect five times (path refs, replace
12
+ // maps, provenance reconstruction, the tilde parse, the installed-registry
13
+ // call). So the rules move here and BOTH callers read them — the guard suite
14
+ // over `ts/project`, which is itself an sdkgen package, and the verb.
15
+ //
16
+ // WHAT MAKES A MODEL FILE WRONG
17
+ //
18
+ // Three failure modes, and they surface at three different times, which is
19
+ // why all three are checked:
20
+ //
21
+ // 1. It does not PARSE under the parser a consumer actually uses. Aontu
22
+ // takes `#` comments; the npm engine's jsonic enables `//` and `/* */`
23
+ // by default and `@voxgig/model` switches them back off, so a `//` line
24
+ // compiles here and fails in the consumer's build. Seven shipped
25
+ // targets went out broken that way.
26
+ // 2. It parses but does not UNIFY with the base schema — a missing
27
+ // non-defaulted key (`ext`, `comment.line`, `module.name`, a feature's
28
+ // `title`). The file alone is fine, so nothing at add time notices; the
29
+ // consumer's whole model compile is what fails.
30
+ // 3. It unifies but PINS a key the project owns (`publish.version`,
31
+ // `publish.registry.package`). Concrete-vs-concrete is a conflict in
32
+ // aontu, so the project cannot override it — and the failure names the
33
+ // project's own file, not the package's.
34
+
35
+ import { Aontu } from 'aontu'
36
+
37
+ import { schemaFile } from './shipped'
38
+
39
+
40
+ // The provenance anchor. The literal line a shipped definition carries so the
41
+ // stamp has somewhere to hang (helpers/stdrep), and therefore the one thing
42
+ // a package's definition must not lose.
43
+ const ANCHOR = "base: 'BASE'"
44
+
45
+
46
+ // Matched as a WHOLE LINE, never as a substring — the same rule doctor
47
+ // learned the hard way (see AGENTS.md, "Provenance is matched by EXACT LINE").
48
+ //
49
+ // `database: 'BASE'` contains the anchor text, and so does a comment
50
+ // mentioning it. Either would satisfy a substring test while leaving the
51
+ // definition with no anchor of its own: the stamp then rewrites that
52
+ // unrelated occurrence, the item records no usable `base`, and `doctor` and
53
+ // `package update` can never locate its source — with `package check` having
54
+ // reported the file as fine.
55
+ const ANCHOR_RE = /^[ \t]*base: 'BASE'[ \t]*$/m
56
+
57
+
58
+ // An aontu map key that is safe unquoted. Everything else — a hyphen
59
+ // (`go-cli`), a dot (`go.v2`), a leading digit (`2go`) — has to be quoted or
60
+ // the file does not parse, and the ITEM name grammar admits all three.
61
+ const BARE_KEY_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/
62
+
63
+
64
+ // The key as it must be WRITTEN in a model file.
65
+ function aontuKey(name: string): string {
66
+ return BARE_KEY_RE.test(name) ? name : "'" + name + "'"
67
+ }
68
+
69
+
70
+ // Blank out quoted spans before looking for comment markers, so a `//` inside
71
+ // a string — a url, or the `comment: line: '//'` every C-family target model
72
+ // legitimately declares — is not mistaken for a comment.
73
+ function unquoted(line: string): string {
74
+ return line.replace(/'[^']*'|"[^"]*"|`[^`]*`/g, '')
75
+ }
76
+
77
+
78
+ // What the PARSER sees on this line: quoted spans blanked, then everything
79
+ // from the first `#` dropped.
80
+ //
81
+ // Both steps, in that order. Without the second, a perfectly valid comment
82
+ // that happens to mention the token — `# some languages use // comments` —
83
+ // is reported as a parse error and the CLI exits non-zero on a correct
84
+ // package. Without the first, a `#` inside a string would truncate the line
85
+ // early and hide a real `//` after it.
86
+ function code(line: string): string {
87
+ return unquoted(line).split('#')[0]
88
+ }
89
+
90
+
91
+ // Every line carrying a slash comment, 1-based, as a consumer's parser would
92
+ // see it.
93
+ function slashComments(text: string): { line: number, text: string }[] {
94
+ const found: { line: number, text: string }[] = []
95
+
96
+ String(text).split('\n').forEach((line: string, i: number) => {
97
+ if (/(^|\s)(\/\/|\/\*)/.test(code(line))) {
98
+ found.push({ line: i + 1, text: line.trim() })
99
+ }
100
+ })
101
+
102
+ return found
103
+ }
104
+
105
+
106
+ // An Aontu configured the way `@voxgig/model` configures it — which is what
107
+ // actually compiles a consumer's models. A plain `new Aontu()` accepts `//`
108
+ // and `/* */`; the Go engine has no such extension, so the npm build switches
109
+ // them off to match, and this must too or the check is checking a parser
110
+ // nobody runs.
111
+ //
112
+ // A FRESH INSTANCE each time: the option call mutates the instance's jsonic,
113
+ // so a cached one would leak its configuration into the bare compile that is
114
+ // deliberately paired with it.
115
+ function strictAontu(): any {
116
+ const aontu: any = new Aontu()
117
+ aontu.lang.jsonic.options({ comment: { def: { slash: null, multi: null } } })
118
+ return aontu
119
+ }
120
+
121
+
122
+ // An `@` include line for a path, quoted so a path containing a quote or a
123
+ // backslash — a Windows path, or the pathological ones the provenance tests
124
+ // exercise — cannot end the string early.
125
+ function includeLine(file: string): string {
126
+ return "@'" + String(file).replace(/\\/g, '\\\\').replace(/'/g, "\\'") + "'"
127
+ }
128
+
129
+
130
+ type CompileResult = {
131
+ model?: any
132
+ errors: string[]
133
+ }
134
+
135
+
136
+ // aontu reports a resolution problem through the `errs` collector and THROWS
137
+ // a parse problem, so both routes have to be handled or the interesting
138
+ // failure escapes as an opaque AontuError. Its message carries a source
139
+ // excerpt with ANSI colouring and line numbers that refer to the text
140
+ // COMPILED, which is not the file on disk once a schema include is prepended
141
+ // — so the excerpt is cut and only the diagnosis is kept.
142
+ function tidy(msg: string): string {
143
+ const lines = String(msg)
144
+ .replace(new RegExp(String.fromCharCode(27) + "\\[[0-9;]*m", 'g'), '')
145
+ .split('\n')
146
+
147
+ const cut = lines.findIndex((l: string) => /^\s*-->/.test(l))
148
+
149
+ return (cut < 0 ? lines : lines.slice(0, cut))
150
+ .map((l: string) => l.trim())
151
+ .filter((l: string) => '' !== l)
152
+ .join(' ')
153
+ }
154
+
155
+
156
+ // Compile one model file's TEXT.
157
+ //
158
+ // strict: under the parser @voxgig/model configures (the consumer's
159
+ // reality) rather than a bare Aontu().
160
+ // schema: unified with the base schema, which is what makes a missing
161
+ // non-defaulted key a failure instead of an absent key nobody
162
+ // notices until the consumer compiles.
163
+ //
164
+ // `path` must be the file's real path even when the text is synthesised:
165
+ // aontu resolves includes relative to it, and stats it.
166
+ function compileModel(
167
+ src: string,
168
+ path: string,
169
+ opts?: { strict?: boolean, schema?: boolean },
170
+ ): CompileResult {
171
+ // `errs`, NOT `err`. They are different options with opposite behaviour:
172
+ //
173
+ // errs — aontu THROWS on the first problem it cannot resolve.
174
+ // err — aontu COLLECTS problems and returns no model, which sounds
175
+ // better and is not: a PARSE problem is then swallowed entirely.
176
+ // `// nope` under the strict parser yields a model and an EMPTY
177
+ // collector, and a slash comment is the single most common thing
178
+ // this check exists to catch.
179
+ //
180
+ // So the throw is the signal, and it is caught below. The collector is
181
+ // still passed and still read: it costs nothing, and an aontu that one day
182
+ // collects instead of throwing must not report "no errors" here.
183
+ const errs: any[] = []
184
+
185
+ const text = true === opts?.schema ?
186
+ includeLine(schemaFile()) + '\n' + src : src
187
+
188
+ try {
189
+ const aontu = false === opts?.strict ? new Aontu() : strictAontu()
190
+ const model = aontu.generate(text, { path, errs })
191
+
192
+ return {
193
+ model,
194
+ errors: errs.map((e: any) =>
195
+ tidy((null == e.why ? '' : '[' + e.why + '] ') + (e.msg ?? String(e)))),
196
+ }
197
+ }
198
+ catch (err: any) {
199
+ return { errors: [tidy(err.message ?? String(err))] }
200
+ }
201
+ }
202
+
203
+
204
+ // Every key the schema DEFAULTS and a project therefore expects to set, with
205
+ // TWO values to try setting it to. A target model that declares any of them
206
+ // makes the project's own declaration a concrete-vs-concrete conflict — which
207
+ // fails the consumer's entire model compile, naming the consumer's file.
208
+ //
209
+ // Two values because aontu unifies two EQUAL concrete scalars happily: a
210
+ // target pinning `publish: tag: active: false` — which is one of only two
211
+ // values that key can take — passed a single-sentinel probe while still
212
+ // making `true` impossible for the consumer. One alternative value per key is
213
+ // enough: a pin can equal one sentinel or the other, never both.
214
+ const PUBLISH_OVERRIDES: [string, string, string][] = [
215
+ ['publish: version', "'9.9.9'", "'8.8.8'"],
216
+ ['publish: tag: active', 'false', 'true'],
217
+ ['publish: registry: state', "'active'", "'inactive'"],
218
+ ['publish: registry: active', 'true', 'false'],
219
+ ['publish: registry: package', "'@acme/pinned'", "'@acme/other'"],
220
+ ]
221
+
222
+
223
+ // The probe: unify the target model with a project that sets each of them,
224
+ // once per sentinel set. EITHER conflicting means the key is pinned.
225
+ function publishOverrideProbe(
226
+ src: string, path: string, tname: string,
227
+ ): CompileResult {
228
+ const key = aontuKey(tname)
229
+
230
+ const run = (pick: (o: [string, string, string]) => string): CompileResult =>
231
+ compileModel(
232
+ [src, ...PUBLISH_OVERRIDES.map(
233
+ (o) => 'main: kit: target: ' + key + ': ' + o[0] + ': ' + pick(o))]
234
+ .join('\n'),
235
+ path)
236
+
237
+ const first = run((o) => o[1])
238
+ const second = run((o) => o[2])
239
+
240
+ return {
241
+ model: first.model ?? second.model,
242
+ errors: [...first.errors, ...second.errors],
243
+ }
244
+ }
245
+
246
+
247
+ export type {
248
+ CompileResult,
249
+ }
250
+
251
+ export {
252
+ ANCHOR,
253
+ ANCHOR_RE,
254
+ PUBLISH_OVERRIDES,
255
+ aontuKey,
256
+ compileModel,
257
+ includeLine,
258
+ publishOverrideProbe,
259
+ slashComments,
260
+ strictAontu,
261
+ unquoted,
262
+ }
@@ -0,0 +1,211 @@
1
+ // A DELIBERATELY SMALL semver range check, for `engines.sdkgen`.
2
+ //
3
+ // WHY NOT node-semver
4
+ //
5
+ // This package has no runtime dependencies (`dependencies: {}`), and adding
6
+ // one to read a single optional manifest field is a poor trade. What the field
7
+ // actually carries is a floor — `">=3.5"` — so the subset below covers the
8
+ // spellings that occur, and REFUSES TO GUESS at anything else.
9
+ //
10
+ // THE FAILURE DIRECTION MATTERS MORE THAN THE COVERAGE
11
+ //
12
+ // Getting this wrong in the strict direction refuses a package that would
13
+ // have worked, which is worse than the incompatibility it is trying to
14
+ // prevent. So `satisfies` has three outcomes, not two: yes, no, and
15
+ // `undefined` for "this range is outside the subset I understand". A caller
16
+ // that gets `undefined` must let the install proceed and say so, never treat
17
+ // it as a failure.
18
+ //
19
+ // SUPPORTED
20
+ // * x X '' any version
21
+ // >=1.2.3 >1.2 <=2 <2 =1.2.3 1.2.3
22
+ // ^1.2.3 ~1.2.3
23
+ // space-separated conjunction: ">=3.5 <4"
24
+ // ||-separated alternatives: "^3 || ^4"
25
+ //
26
+ // NOT SUPPORTED (returns undefined)
27
+ // hyphen ranges (`1.2.3 - 2.3.4`), prerelease/build metadata comparison,
28
+ // and anything else. Prereleases are compared by their numeric core only,
29
+ // which is why a range mentioning one is rejected rather than mishandled.
30
+
31
+
32
+ type Parts = [number, number, number]
33
+
34
+
35
+ const COMPARATOR_RE = /^(>=|<=|>|<|=|\^|~)?v?(\d+)(?:\.(\d+))?(?:\.(\d+))?$/
36
+
37
+ const ANY_RE = /^(\*|x|X)$/
38
+
39
+
40
+ // A version as three numbers, or undefined if it is not a plain `X.Y.Z`.
41
+ // A prerelease or build suffix returns undefined deliberately: comparing them
42
+ // properly is most of what makes semver hard, and this subset does not.
43
+ function parseVersion(v: string): Parts | undefined {
44
+ const m = /^v?(\d+)\.(\d+)\.(\d+)$/.exec(String(v).trim())
45
+
46
+ return null == m ? undefined :
47
+ [Number(m[1]), Number(m[2]), Number(m[3])]
48
+ }
49
+
50
+
51
+ function compare(a: Parts, b: Parts): number {
52
+ for (let i = 0; i < 3; i++) {
53
+ if (a[i] !== b[i]) {
54
+ return a[i] < b[i] ? -1 : 1
55
+ }
56
+ }
57
+ return 0
58
+ }
59
+
60
+
61
+ // The upper bound `^` and `~` imply, exclusive.
62
+ //
63
+ // ^1.2.3 -> <2.0.0 ^0.2.3 -> <0.3.0 ^0.0.3 -> <0.0.4
64
+ // ~1.2.3 -> <1.3.0 ~1.2 -> <1.3.0 ~1 -> <2.0.0
65
+ //
66
+ // `^0.x` is the case worth stating: npm treats a leading zero as unstable, so
67
+ // the caret narrows to the first NON-ZERO component. Getting that wrong would
68
+ // admit `0.3.0` for `^0.2.3`, which is a breaking change by that convention.
69
+ function caretBound(p: Parts): Parts {
70
+ if (0 !== p[0]) return [p[0] + 1, 0, 0]
71
+ if (0 !== p[1]) return [0, p[1] + 1, 0]
72
+ return [0, 0, p[2] + 1]
73
+ }
74
+
75
+
76
+ function tildeBound(p: Parts, given: number): Parts {
77
+ return nextBound(p, given)
78
+ }
79
+
80
+
81
+ // The version just past everything a PARTIAL version covers: `3.4` covers all
82
+ // of 3.4.x, so the next bound is 3.5.0; `3` covers all of 3.x, so 4.0.0.
83
+ // Shared by `~` and by the two strict comparators that need it, so they
84
+ // cannot disagree about where a partial version ends.
85
+ function nextBound(p: Parts, given: number): Parts {
86
+ return 1 === given ? [p[0] + 1, 0, 0] : [p[0], p[1] + 1, 0]
87
+ }
88
+
89
+
90
+ // One comparator against one version. `undefined` if the comparator is
91
+ // outside the subset.
92
+ function satisfiesOne(version: Parts, comparator: string): boolean | undefined {
93
+ const c = comparator.trim()
94
+
95
+ if ('' === c || ANY_RE.test(c)) {
96
+ return true
97
+ }
98
+
99
+ const m = COMPARATOR_RE.exec(c)
100
+
101
+ if (null == m) {
102
+ return undefined
103
+ }
104
+
105
+ const op = m[1] ?? '='
106
+
107
+ // How many components were actually written — `~1.2` and `~1` bound
108
+ // differently, and a partial version means "any" in its missing places.
109
+ const given = null != m[4] ? 3 : null != m[3] ? 2 : 1
110
+ const p: Parts = [Number(m[2]), Number(m[3] ?? 0), Number(m[4] ?? 0)]
111
+
112
+ const cmp = compare(version, p)
113
+
114
+ // A PARTIAL version is a RANGE, not a version with zeroes in it, and two of
115
+ // the four strict comparators have to move their bound to say so:
116
+ //
117
+ // >3.4 means ">= all of 3.4.x" -> >=3.5.0 (not >3.4.0)
118
+ // <=3.4 means "<= all of 3.4.x" -> <3.5.0 (not <=3.4.0)
119
+ // >=3.4 -> >=3.4.0 <3.4 -> <3.4.0 (already right)
120
+ //
121
+ // Read as literal zeroes, `>3.4` admitted 3.4.8 (an incompatible package
122
+ // let through) and `<=3.4` rejected it (a compatible one refused) — the two
123
+ // failures this module's whole shape is meant to avoid, in one comparator.
124
+ const bound = 3 === given ? p : nextBound(p, given)
125
+
126
+ if ('>=' === op) return 0 <= cmp
127
+ if ('<' === op) return 0 > cmp
128
+ if ('>' === op) return 3 === given ? 0 < cmp : 0 <= compare(version, bound)
129
+ if ('<=' === op) return 3 === given ? 0 >= cmp : 0 > compare(version, bound)
130
+
131
+ if ('^' === op) {
132
+ return 0 <= cmp && 0 > compare(version, caretBound(p))
133
+ }
134
+
135
+ if ('~' === op) {
136
+ return 0 <= cmp && 0 > compare(version, tildeBound(p, given))
137
+ }
138
+
139
+ // `=` / bare. A partial version is a PREFIX match: `1.2` admits any 1.2.x,
140
+ // which is what every other tool means by it.
141
+ if (3 === given) return 0 === cmp
142
+ if (2 === given) return version[0] === p[0] && version[1] === p[1]
143
+ return version[0] === p[0]
144
+ }
145
+
146
+
147
+ // Does `version` satisfy `range`?
148
+ //
149
+ // `true` / `false` / `undefined` — see the note at the top: undefined means
150
+ // "not understood", and the caller must not treat it as a failure.
151
+ function satisfies(version: string, range: string): boolean | undefined {
152
+ const v = parseVersion(version)
153
+
154
+ if (null == v) {
155
+ return undefined
156
+ }
157
+
158
+ const text = String(range ?? '').trim()
159
+
160
+ if ('' === text || ANY_RE.test(text)) {
161
+ return true
162
+ }
163
+
164
+ // A hyphen range is not supported, and its parts would otherwise be read as
165
+ // a conjunction of two bare versions — which is never satisfiable, i.e. a
166
+ // false REFUSAL. Rejected explicitly rather than by accident.
167
+ if (/\s-\s/.test(text)) {
168
+ return undefined
169
+ }
170
+
171
+ // An alternative that MATCHES wins immediately, whatever the others say.
172
+ // But a `false` is only reportable if every comparator was understood:
173
+ // otherwise the honest answer is "do not know", and refusing on it would be
174
+ // the strict-direction failure this module is shaped to avoid.
175
+ let unknown = false
176
+
177
+ for (const alt of text.split('||')) {
178
+ const comparators = alt.trim().split(/\s+/).filter((s) => '' !== s)
179
+
180
+ if (0 === comparators.length) {
181
+ unknown = true
182
+ continue
183
+ }
184
+
185
+ let all = true
186
+
187
+ for (const c of comparators) {
188
+ const one = satisfiesOne(v, c)
189
+
190
+ if (null == one) {
191
+ unknown = true
192
+ all = false
193
+ }
194
+ else if (!one) {
195
+ all = false
196
+ }
197
+ }
198
+
199
+ if (all) {
200
+ return true
201
+ }
202
+ }
203
+
204
+ return unknown ? undefined : false
205
+ }
206
+
207
+
208
+ export {
209
+ satisfies,
210
+ parseVersion,
211
+ }
@@ -0,0 +1,41 @@
1
+ // WHERE THIS GENERATOR'S OWN FILES ARE, resolved from the compiled code.
2
+ //
3
+ // Every other path in `action/` is relative to the PROJECT being operated on
4
+ // — `node_modules/@voxgig/sdkgen/project/.sdk` is how a consumer reaches the
5
+ // bundled scaffold, and that is correct there. `package check` has no project:
6
+ // it is run by an AUTHOR against a package that may sit anywhere, and it
7
+ // still needs this generator's base schema (to unify against) and its feature
8
+ // catalogue (to know which names denote features). Those come from the
9
+ // installation doing the checking, which is this one.
10
+ //
11
+ // `__dirname` is `dist/helpers` in the shipped package, so the package root is
12
+ // two levels up. npm can only ship files under that root, which is why the
13
+ // canonical `model/` is mirrored into `ts/model/` (the model-mirror guard
14
+ // fails on drift).
15
+
16
+ import Path from 'node:path'
17
+
18
+
19
+ function packageRoot(): string {
20
+ return Path.resolve(__dirname, '..', '..')
21
+ }
22
+
23
+
24
+ // The base model schema — `@voxgig/sdkgen/model/sdkgen.aontu`.
25
+ function schemaFile(): string {
26
+ return Path.join(packageRoot(), 'model', 'sdkgen.aontu')
27
+ }
28
+
29
+
30
+ // The bundled scaffold — the `.sdk` of `ts/project`, which is itself an
31
+ // sdkgen package.
32
+ function scaffoldFolder(): string {
33
+ return Path.join(packageRoot(), 'project', '.sdk')
34
+ }
35
+
36
+
37
+ export {
38
+ packageRoot,
39
+ schemaFile,
40
+ scaffoldFolder,
41
+ }
@@ -50,7 +50,77 @@ function templateReplacements(model: any, tname: string): Record<string, string>
50
50
  }
51
51
 
52
52
 
53
+ // PROVENANCE — where a copied target or feature came from.
54
+ //
55
+ // Every shipped model file carries a `base: 'BASE'` line as an ANCHOR, and
56
+ // the add actions rewrite that one line into the block the copy keeps:
57
+ //
58
+ // base: 'BASE' -> base: 'node_modules/@acme/sdkgen-iot/.sdk'
59
+ // origname: 'iot-go'
60
+ //
61
+ // The anchor is why every shipped model needs the line even though most
62
+ // installs are unaliased from the bundled scaffold: a `replace` map can only
63
+ // rewrite text that is already there, and there is nowhere else to hang the
64
+ // keys that do not appear in the source.
65
+ //
66
+ // ONE definition, for the same reason templateReplacements is one: `target
67
+ // add` and `feature add` WRITE with this map and `doctor` re-applies it to
68
+ // the scaffold before comparing. A writer and a reader that disagree by a
69
+ // single character make every project read as forked.
70
+ //
71
+ // `origname` and `package` are emitted only when they apply, so an ordinary
72
+ // install still records exactly one line and its model file is unchanged
73
+ // from what earlier versions wrote — apart from the base value itself.
74
+ type Provenance = {
75
+ // The `.sdk` folder the copy came from, project-relative and
76
+ // '/'-normalised (see resolveTarget).
77
+ base: string
78
+
79
+ // The name in the SOURCE and the name it was INSTALLED as. Equal for an
80
+ // ordinary add; different for `target add go~go2`.
81
+ origname?: string
82
+ name?: string
83
+
84
+ // The sdkgen package that provided it, when the source declared one.
85
+ package?: string
86
+ }
87
+
88
+ // Two spaces, matching every shipped model file's indentation for these keys.
89
+ const PROVENANCE_INDENT = ' '
90
+
91
+
92
+ // A value as a single-quoted aontu string. These are PATHS and NAMES from the
93
+ // filesystem, so they can legally contain a quote — `/home/o'connor/pkg` is a
94
+ // valid directory — and concatenating one between quotes closes the string
95
+ // early and leaves the copied model unparsable. Aontu accepts a backslash
96
+ // escape, which keeps the quoting style every shipped model already uses.
97
+ function aontuString(value: string): string {
98
+ return "'" + String(value).replace(/\\/g, '\\\\').replace(/'/g, "\\'") + "'"
99
+ }
100
+
101
+
102
+ function provenanceReplace(prov: Provenance): Record<string, string> {
103
+ const lines = ['base: ' + aontuString(prov.base)]
104
+
105
+ if (null != prov.origname && null != prov.name &&
106
+ prov.origname !== prov.name) {
107
+ lines.push('origname: ' + aontuString(prov.origname))
108
+ }
109
+
110
+ if (null != prov.package && '' !== prov.package) {
111
+ lines.push('package: ' + aontuString(prov.package))
112
+ }
113
+
114
+ return { "base: 'BASE'": lines.join('\n' + PROVENANCE_INDENT) }
115
+ }
116
+
117
+
118
+ export type {
119
+ Provenance,
120
+ }
121
+
53
122
  export {
54
123
  ensureStdrep,
55
124
  templateReplacements,
125
+ provenanceReplace,
56
126
  }