@voxgig/sdkgen 3.4.7 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/bin/voxgig-sdkgen +78 -10
  2. package/dist/action/action.d.ts +5 -2
  3. package/dist/action/action.js +113 -6
  4. package/dist/action/action.js.map +1 -1
  5. package/dist/action/check.d.ts +15 -0
  6. package/dist/action/check.js +347 -0
  7. package/dist/action/check.js.map +1 -0
  8. package/dist/action/dispatch.d.ts +8 -0
  9. package/dist/action/dispatch.js +74 -0
  10. package/dist/action/dispatch.js.map +1 -0
  11. package/dist/action/docs.d.ts +4 -0
  12. package/dist/action/docs.js +236 -0
  13. package/dist/action/docs.js.map +1 -0
  14. package/dist/action/doctor.d.ts +5 -2
  15. package/dist/action/doctor.js +526 -74
  16. package/dist/action/doctor.js.map +1 -1
  17. package/dist/action/feature.js +96 -17
  18. package/dist/action/feature.js.map +1 -1
  19. package/dist/action/kind.d.ts +30 -0
  20. package/dist/action/kind.js +258 -0
  21. package/dist/action/kind.js.map +1 -0
  22. package/dist/action/package.d.ts +25 -0
  23. package/dist/action/package.js +852 -0
  24. package/dist/action/package.js.map +1 -0
  25. package/dist/action/resolve.d.ts +18 -0
  26. package/dist/action/resolve.js +280 -0
  27. package/dist/action/resolve.js.map +1 -0
  28. package/dist/action/target.d.ts +7 -1
  29. package/dist/action/target.js +240 -97
  30. package/dist/action/target.js.map +1 -1
  31. package/dist/cmp/Docs.d.ts +4 -0
  32. package/dist/cmp/Docs.js +98 -0
  33. package/dist/cmp/Docs.js.map +1 -0
  34. package/dist/cmp/ExternalDocs.d.ts +2 -0
  35. package/dist/cmp/ExternalDocs.js +43 -0
  36. package/dist/cmp/ExternalDocs.js.map +1 -0
  37. package/dist/helpers/definition.d.ts +5 -0
  38. package/dist/helpers/definition.js +72 -0
  39. package/dist/helpers/definition.js.map +1 -0
  40. package/dist/helpers/featureSource.d.ts +8 -2
  41. package/dist/helpers/featureSource.js +61 -12
  42. package/dist/helpers/featureSource.js.map +1 -1
  43. package/dist/helpers/junk.d.ts +11 -0
  44. package/dist/helpers/junk.js +149 -0
  45. package/dist/helpers/junk.js.map +1 -0
  46. package/dist/helpers/manifest.d.ts +50 -0
  47. package/dist/helpers/manifest.js +328 -0
  48. package/dist/helpers/manifest.js.map +1 -0
  49. package/dist/helpers/modelcheck.d.ts +22 -0
  50. package/dist/helpers/modelcheck.js +203 -0
  51. package/dist/helpers/modelcheck.js.map +1 -0
  52. package/dist/helpers/semver.d.ts +4 -0
  53. package/dist/helpers/semver.js +174 -0
  54. package/dist/helpers/semver.js.map +1 -0
  55. package/dist/helpers/shipped.d.ts +4 -0
  56. package/dist/helpers/shipped.js +36 -0
  57. package/dist/helpers/shipped.js.map +1 -0
  58. package/dist/helpers/stdrep.d.ts +9 -1
  59. package/dist/helpers/stdrep.js +22 -0
  60. package/dist/helpers/stdrep.js.map +1 -1
  61. package/dist/sdkgen.d.ts +15 -3
  62. package/dist/sdkgen.js +233 -67
  63. package/dist/sdkgen.js.map +1 -1
  64. package/dist/tsconfig.tsbuildinfo +1 -1
  65. package/dist/types.d.ts +10 -2
  66. package/dist/utility.js +17 -0
  67. package/dist/utility.js.map +1 -1
  68. package/model/sdkgen.aontu +69 -0
  69. package/package.json +3 -2
  70. package/project/.sdk/model/feature/audit.aontu +1 -0
  71. package/project/.sdk/model/feature/cache.aontu +1 -0
  72. package/project/.sdk/model/feature/clienttrack.aontu +1 -0
  73. package/project/.sdk/model/feature/debug.aontu +1 -0
  74. package/project/.sdk/model/feature/idempotency.aontu +1 -0
  75. package/project/.sdk/model/feature/log.aontu +1 -0
  76. package/project/.sdk/model/feature/metrics.aontu +1 -0
  77. package/project/.sdk/model/feature/netsim.aontu +1 -0
  78. package/project/.sdk/model/feature/paging.aontu +1 -0
  79. package/project/.sdk/model/feature/proxy.aontu +1 -0
  80. package/project/.sdk/model/feature/ratelimit.aontu +1 -0
  81. package/project/.sdk/model/feature/rbac.aontu +1 -0
  82. package/project/.sdk/model/feature/retry.aontu +1 -0
  83. package/project/.sdk/model/feature/streaming.aontu +1 -0
  84. package/project/.sdk/model/feature/telemetry.aontu +1 -0
  85. package/project/.sdk/model/feature/test.aontu +1 -0
  86. package/project/.sdk/model/feature/timeout.aontu +1 -0
  87. package/project/.sdk/model/target/c.aontu +1 -0
  88. package/project/.sdk/model/target/clojure.aontu +1 -0
  89. package/project/.sdk/model/target/cpp.aontu +1 -0
  90. package/project/.sdk/model/target/dart.aontu +1 -0
  91. package/project/.sdk/model/target/elixir.aontu +1 -0
  92. package/project/.sdk/model/target/go-cli.aontu +1 -0
  93. package/project/.sdk/model/target/go-mcp.aontu +1 -0
  94. package/project/.sdk/model/target/go.aontu +1 -0
  95. package/project/.sdk/model/target/haskell.aontu +1 -0
  96. package/project/.sdk/model/target/java.aontu +1 -0
  97. package/project/.sdk/model/target/js.aontu +1 -0
  98. package/project/.sdk/model/target/kotlin.aontu +1 -0
  99. package/project/.sdk/model/target/lean.aontu +1 -0
  100. package/project/.sdk/model/target/lua.aontu +1 -0
  101. package/project/.sdk/model/target/ocaml.aontu +1 -0
  102. package/project/.sdk/model/target/perl.aontu +1 -0
  103. package/project/.sdk/model/target/php.aontu +1 -0
  104. package/project/.sdk/model/target/py-data.aontu +1 -0
  105. package/project/.sdk/model/target/py.aontu +1 -0
  106. package/project/.sdk/model/target/rb.aontu +1 -0
  107. package/project/.sdk/model/target/rust.aontu +1 -0
  108. package/project/.sdk/model/target/scala.aontu +1 -0
  109. package/project/.sdk/model/target/seneca-provider.aontu +1 -0
  110. package/project/.sdk/model/target/zig.aontu +1 -0
  111. package/project/.sdk/src/cmp/c/Config_c.ts +44 -0
  112. package/project/.sdk/src/cmp/c/fragment/Main.fragment.c +3 -1
  113. package/project/.sdk/src/cmp/clojure/Config_clojure.ts +37 -42
  114. package/project/.sdk/src/cmp/clojure/utility_clojure.ts +42 -0
  115. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +18 -0
  116. package/project/.sdk/src/cmp/csharp/Config_csharp.ts +125 -52
  117. package/project/.sdk/src/cmp/csharp/fragment/Main.fragment.cs +3 -1
  118. package/project/.sdk/src/cmp/csharp/utility_csharp.ts +94 -6
  119. package/project/.sdk/src/cmp/dart/Config_dart.ts +43 -5
  120. package/project/.sdk/src/cmp/dart/fragment/Config.data.fragment.dart +74 -0
  121. package/project/.sdk/src/cmp/dart/fragment/Config.fragment.dart +6 -9
  122. package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +16 -5
  123. package/project/.sdk/src/cmp/dart/utility_dart.ts +18 -0
  124. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +128 -13
  125. package/project/.sdk/src/cmp/elixir/fragment/Main.fragment.ex +3 -1
  126. package/project/.sdk/src/cmp/elixir/utility_elixir.ts +6 -0
  127. package/project/.sdk/src/cmp/haskell/Config_haskell.ts +35 -52
  128. package/project/.sdk/src/cmp/haskell/Package_haskell.ts +1 -0
  129. package/project/.sdk/src/cmp/haskell/Test_haskell.ts +1 -1
  130. package/project/.sdk/src/cmp/haskell/utility_haskell.ts +15 -5
  131. package/project/.sdk/src/cmp/java/Config_java.ts +21 -0
  132. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +15 -0
  133. package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +42 -39
  134. package/project/.sdk/src/cmp/perl/Config_perl.ts +15 -0
  135. package/project/.sdk/src/cmp/perl/fragment/Main.fragment.pm +3 -1
  136. package/project/.sdk/src/cmp/rust/Config_rust.ts +44 -0
  137. package/project/.sdk/src/cmp/rust/fragment/Main.fragment.rs +4 -2
  138. package/project/.sdk/src/cmp/scala/Config_scala.ts +15 -0
  139. package/project/.sdk/src/cmp/swift/Config_swift.ts +17 -0
  140. package/project/.sdk/src/cmp/swift/fragment/Main.fragment.swift +3 -1
  141. package/project/.sdk/src/cmp/zig/Config_zig.ts +44 -0
  142. package/project/.sdk/src/cmp/zig/TestEntity_zig.ts +9 -2
  143. package/project/.sdk/src/cmp/zig/fragment/Main.fragment.zig +3 -1
  144. package/project/.sdk/tm/c/core/sdk.h +1 -0
  145. package/project/.sdk/tm/c/utility/make_options.c +5 -1
  146. package/project/.sdk/tm/clojure/src/sdk/core.clj +51 -3
  147. package/project/.sdk/tm/cpp/core/types.hpp +4 -1
  148. package/project/.sdk/tm/cpp/utility/pipeline.hpp +5 -1
  149. package/project/.sdk/tm/csharp/test/PipelineTest.cs +15 -0
  150. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +67 -1
  151. package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +43 -0
  152. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +58 -2
  153. package/project/.sdk/tm/haskell/{test/TestJson.hs → src/SdkJson.hs} +8 -4
  154. package/project/.sdk/tm/haskell/src/SdkRuntime.hs +55 -2
  155. package/project/.sdk/tm/haskell/test/Runner.hs +1 -1
  156. package/project/.sdk/tm/haskell/test/StructCorpus.hs +1 -1
  157. package/project/.sdk/tm/java/core/SdkClient.java +3 -1
  158. package/project/.sdk/tm/java/utility/MakeOptions.java +5 -1
  159. package/project/.sdk/tm/kotlin/core/SdkClient.kt +3 -1
  160. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +5 -1
  161. package/project/.sdk/tm/ocaml/Makefile +3 -2
  162. package/project/.sdk/tm/ocaml/sdk_json.ml +106 -0
  163. package/project/.sdk/tm/ocaml/sdk_runtime.ml +56 -0
  164. package/project/.sdk/tm/ocaml/test/struct_corpus.ml +4 -86
  165. package/project/.sdk/tm/perl/utility/make_options.pm +5 -1
  166. package/project/.sdk/tm/rust/utility/make_options.rs +79 -1
  167. package/project/.sdk/tm/scala/core/SdkClient.scala +3 -1
  168. package/project/.sdk/tm/scala/utility/Make.scala +5 -1
  169. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +5 -1
  170. package/project/.sdk/tm/zig/core/utility.zig +85 -1
  171. package/project/sdkgen-package.json +57 -0
  172. package/src/action/action.ts +136 -6
  173. package/src/action/check.ts +495 -0
  174. package/src/action/dispatch.ts +105 -0
  175. package/src/action/docs.ts +310 -0
  176. package/src/action/doctor.ts +692 -81
  177. package/src/action/feature.ts +109 -17
  178. package/src/action/kind.ts +375 -0
  179. package/src/action/package.ts +1173 -0
  180. package/src/action/resolve.ts +362 -0
  181. package/src/action/target.ts +290 -108
  182. package/src/cmp/Docs.ts +125 -0
  183. package/src/cmp/ExternalDocs.ts +54 -0
  184. package/src/helpers/definition.ts +82 -0
  185. package/src/helpers/featureSource.ts +83 -13
  186. package/src/helpers/junk.ts +168 -0
  187. package/src/helpers/manifest.ts +474 -0
  188. package/src/helpers/modelcheck.ts +262 -0
  189. package/src/helpers/semver.ts +211 -0
  190. package/src/helpers/shipped.ts +41 -0
  191. package/src/helpers/stdrep.ts +70 -0
  192. package/src/sdkgen.ts +294 -66
  193. package/src/types.ts +33 -3
  194. package/src/utility.ts +17 -0
@@ -0,0 +1,852 @@
1
+ "use strict";
2
+ // `package add` / `package list` — the whole-package verbs.
3
+ //
4
+ // See docs/design/sdkgen-packages.md §9.
5
+ //
6
+ // WHAT `package add` IS
7
+ //
8
+ // Not a new copy pipeline. It resolves a package ROOT, validates its manifest,
9
+ // and then runs the SAME per-kind add that `target add` / `feature add` run,
10
+ // once per provided item. Index handling, provenance stamping, feature
11
+ // fan-out, dry run and logging all come along unchanged, because they are not
12
+ // reimplemented. The value it adds over typing the individual adds is:
13
+ //
14
+ // - the manifest is REQUIRED and validated first, so a package that lies
15
+ // about what it provides fails before anything is written rather than
16
+ // halfway through;
17
+ // - the engine range is checked once, for the package, rather than never;
18
+ // - ordering: targets before features, because `feature add` fans a
19
+ // feature's source out across the targets already in the model, so a
20
+ // feature installed first would find none of the package's own targets.
21
+ //
22
+ // WHY VALIDATION COMES FIRST, ALL OF IT
23
+ //
24
+ // The items are installed in a loop. If the fourth one turns out not to be in
25
+ // the package, the first three are already written and the project is left
26
+ // half-installed with a partial index. Validating the whole claim up front is
27
+ // what makes the loop safe to run at all.
28
+ var __importDefault = (this && this.__importDefault) || function (mod) {
29
+ return (mod && mod.__esModule) ? mod : { "default": mod };
30
+ };
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.SDKGEN_VERSION = void 0;
33
+ exports.action_package = action_package;
34
+ exports.package_add = package_add;
35
+ exports.package_update = package_update;
36
+ exports.installedFrom = installedFrom;
37
+ exports.resolvePackage = resolvePackage;
38
+ exports.selectItems = selectItems;
39
+ exports.parseAliases = parseAliases;
40
+ exports.registerAdder = registerAdder;
41
+ const node_path_1 = __importDefault(require("node:path"));
42
+ const types_1 = require("../types");
43
+ const utility_1 = require("../utility");
44
+ const manifest_1 = require("../helpers/manifest");
45
+ const semver_1 = require("../helpers/semver");
46
+ const kind_1 = require("./kind");
47
+ const check_1 = require("./check");
48
+ const doctor_1 = require("./doctor");
49
+ const resolve_1 = require("./resolve");
50
+ const CMD_MAP = Object.assign(Object.create(null), {
51
+ add: cmd_package_add,
52
+ check: check_1.cmd_package_check,
53
+ list: cmd_package_list,
54
+ update: cmd_package_update,
55
+ });
56
+ // The kinds `package add` installs, IN ORDER.
57
+ //
58
+ // Targets first. `feature add` copies a feature's per-target source into every
59
+ // target already in the model, so a feature installed before the package's own
60
+ // targets would silently ship no source for them — the `feature-source-missing`
61
+ // warning, once per target, and a feature that does nothing.
62
+ //
63
+ // Docs LAST, for the mirror-image reason: a docs item is ABOUT the targets and
64
+ // features a project has (a page per SDK, a page per feature), and while it
65
+ // reads them at generate time rather than at add time, installing it last
66
+ // keeps the add log in the order a reader would expect and leaves the model
67
+ // complete before anything reads it.
68
+ const ADD_ORDER = ['target', 'feature', 'docs'];
69
+ async function action_package(args, actx) {
70
+ const cmdname = args[1];
71
+ const cmd = CMD_MAP[cmdname];
72
+ if (null == cmd) {
73
+ throw new utility_1.SdkGenError('Unknown package cmd: ' + cmdname + ' (expected: ' +
74
+ Object.keys(CMD_MAP).sort().join(', ') + ')');
75
+ }
76
+ return await cmd(args, actx);
77
+ }
78
+ function resolvePackage(ref, actx) {
79
+ const { found, search } = (0, manifest_1.probePackage)(actx.fs(), actx.folder ?? '.', ref);
80
+ if (null == found) {
81
+ throw new utility_1.SdkGenError('Package not found: ' + ref + '\n looked for a `.sdk` folder in:\n ' +
82
+ search.join('\n '));
83
+ }
84
+ const { root, sdk, read } = found;
85
+ // A `.sdk` folder with no manifest is a legal source for a DIRECT ref and
86
+ // is what every pre-manifest fixture is — but `package add` is the verb
87
+ // that acts on a manifest, so here its absence is the error, and it says
88
+ // which of the two commands the user wants.
89
+ if (null == read.manifest) {
90
+ throw new utility_1.SdkGenError('No package manifest: ' + read.file +
91
+ (null == read.err ? '' : '\n ' + read.err) +
92
+ '\n `package add` installs what a manifest declares. For a folder' +
93
+ ' without one, add its items directly:' +
94
+ '\n voxgig-sdkgen target add ' + ref + '/<name>');
95
+ }
96
+ return { ref, root, sdk, manifest: read.manifest };
97
+ }
98
+ // Everything wrong with the package, or nothing. Reported as ONE message
99
+ // rather than one-per-throw, because an author fixing a manifest wants the
100
+ // whole list, not the first line of it.
101
+ function refuse(src, found, log) {
102
+ // The non-errors are still worth saying — an unclaimed extra is nearly
103
+ // always a forgotten manifest edit — but they do not stop the install.
104
+ for (const f of found) {
105
+ if ('error' !== f.level) {
106
+ log[f.level](f);
107
+ }
108
+ }
109
+ const errors = found.filter((f) => 'error' === f.level);
110
+ if (0 === errors.length) {
111
+ return;
112
+ }
113
+ throw new utility_1.SdkGenError(src.ref + ': package manifest does not match the package (' +
114
+ node_path_1.default.join(src.root, manifest_1.MANIFEST) + ')\n ' +
115
+ errors.map((f) => f.note).join('\n '));
116
+ }
117
+ // Is this generator new enough for the package?
118
+ //
119
+ // `undefined` from `satisfies` means the range is outside the subset it
120
+ // understands — see helpers/semver. That is reported and ALLOWED: refusing on
121
+ // a range nobody could parse would block a package that works, which is worse
122
+ // than the incompatibility being guarded against.
123
+ function checkEngine(src, actx) {
124
+ const range = src.manifest.engines?.sdkgen;
125
+ if (null == range || '' === range) {
126
+ return;
127
+ }
128
+ const ok = (0, semver_1.satisfies)(SDKGEN_VERSION, range);
129
+ if (false === ok) {
130
+ throw new utility_1.SdkGenError(src.ref + ': needs @voxgig/sdkgen ' + range +
131
+ ', this is ' + SDKGEN_VERSION +
132
+ '\n upgrade @voxgig/sdkgen, or install an earlier version of ' + src.ref);
133
+ }
134
+ if (null == ok) {
135
+ actx.log.warn({
136
+ point: 'package-engine-unparsed', package: src.manifest.name,
137
+ range, version: SDKGEN_VERSION,
138
+ note: src.ref + ': cannot compare `engines.sdkgen` range ' +
139
+ JSON.stringify(range) + ' against ' + SDKGEN_VERSION +
140
+ '; proceeding — see helpers/semver for the supported subset'
141
+ });
142
+ }
143
+ }
144
+ // This generator's own version, read from the package.json beside `dist`.
145
+ // Not from the consumer's model: `engines.sdkgen` is about the GENERATOR
146
+ // doing the installing, and the model describes the SDK being generated.
147
+ const SDKGEN_VERSION = (() => {
148
+ try {
149
+ return require('../../package.json').version;
150
+ }
151
+ catch (err) {
152
+ return '0.0.0';
153
+ }
154
+ })();
155
+ exports.SDKGEN_VERSION = SDKGEN_VERSION;
156
+ // The items to install, as `<kind>` -> names, after `--only` is applied.
157
+ //
158
+ // `--only target:iot-go,feature:circuitbreaker` — a subset of what the
159
+ // manifest provides, named the same way the manifest keys them. A name that
160
+ // the package does not provide is an ERROR listing what it does, rather than
161
+ // a silent no-op: a typo'd `--only` that installed nothing and reported
162
+ // success is the failure this whole verb exists to remove.
163
+ function selectItems(src, only, log) {
164
+ const provides = src.manifest.provides ?? {};
165
+ // ABSENT means "everything"; EXPLICITLY EMPTY does not.
166
+ //
167
+ // `--only=` and `{ only: '' }` are what a script gets when it builds the
168
+ // flag from an empty variable, and treating that as absent installs the
169
+ // whole package — the opposite of what the operator asked for, at the one
170
+ // moment nobody is watching.
171
+ if (null == only) {
172
+ return provides;
173
+ }
174
+ const wanted = Object.create(null);
175
+ const missing = [];
176
+ const specs = only.split(',').map((s) => s.trim()).filter(Boolean);
177
+ if (0 === specs.length) {
178
+ throw new utility_1.SdkGenError('--only was given but selects nothing: ' + JSON.stringify(only) +
179
+ '\n omit the flag to install everything the package provides');
180
+ }
181
+ for (const spec of specs) {
182
+ const colon = spec.indexOf(':');
183
+ if (colon < 0) {
184
+ throw new utility_1.SdkGenError('--only expects <kind>:<name> entries, got: ' + spec +
185
+ '\n for example: --only target:iot-go,feature:circuitbreaker');
186
+ }
187
+ const kind = spec.slice(0, colon);
188
+ const name = spec.slice(colon + 1);
189
+ if (!(provides[kind] ?? []).includes(name)) {
190
+ missing.push(spec);
191
+ continue;
192
+ }
193
+ (wanted[kind] = wanted[kind] ?? []).push(name);
194
+ }
195
+ if (0 < missing.length) {
196
+ throw new utility_1.SdkGenError(src.ref + ': does not provide ' + missing.join(', ') +
197
+ '\n it provides: ' + describeProvides(provides));
198
+ }
199
+ return wanted;
200
+ }
201
+ function describeProvides(provides) {
202
+ const parts = [];
203
+ for (const kind of Object.keys(provides).sort()) {
204
+ for (const name of provides[kind]) {
205
+ parts.push(kind + ' `' + name + '`');
206
+ }
207
+ }
208
+ return 0 === parts.length ? '(nothing)' : parts.join(', ');
209
+ }
210
+ // `--alias iot-go=acme-go,other=thing` — the install-time renames, by ORIGIN
211
+ // name. Only kinds that permit aliasing may appear; a feature alias is
212
+ // refused here with the same explanation `feature add` gives, so the two
213
+ // entry points cannot disagree about what is allowed.
214
+ function parseAliases(alias, wanted) {
215
+ // NULL-PROTOTYPE. A manifest may legally provide an item called
216
+ // `constructor` or `toString` — the name grammar admits them — and on a
217
+ // plain object `aliases['constructor']` is Object.prototype.constructor,
218
+ // which is truthy, so an unaliased item got `~function Object() { … }`
219
+ // appended and installed under that as a name.
220
+ const out = Object.create(null);
221
+ if (null == alias || '' === alias) {
222
+ return out;
223
+ }
224
+ for (const spec of alias.split(',').map((s) => s.trim()).filter(Boolean)) {
225
+ const eq = spec.indexOf('=');
226
+ if (eq < 0) {
227
+ throw new utility_1.SdkGenError('--alias expects <name>=<alias> entries, got: ' + spec +
228
+ '\n for example: --alias iot-go=acme-go');
229
+ }
230
+ const from = spec.slice(0, eq);
231
+ const to = spec.slice(eq + 1);
232
+ // Checked HERE as well as in the resolver, because the two catch
233
+ // different things. The resolver sees whatever survives ref parsing, so
234
+ // it catches `iotgo=..`; but `iotgo=../../elsewhere` is concatenated into
235
+ // `<root>/iotgo~../../elsewhere`, whose last segment is `elsewhere` and
236
+ // which therefore stops looking like an alias at all — it resolves as a
237
+ // ref to a different item and fails confusingly instead. Same grammar in
238
+ // both places, so they cannot disagree about what a name is.
239
+ if (!manifest_1.ITEM_NAME_RE.test(to)) {
240
+ throw new utility_1.SdkGenError('Invalid alias in --alias ' + JSON.stringify(spec) + ': ' +
241
+ JSON.stringify(to) + ' is not a name (matching ' +
242
+ manifest_1.ITEM_NAME_RE.source + ')' +
243
+ '\n an alias becomes the directory the item is installed into');
244
+ }
245
+ const kind = Object.keys(wanted)
246
+ .find((k) => (wanted[k] ?? []).includes(from));
247
+ if (null == kind) {
248
+ throw new utility_1.SdkGenError('--alias names ' + JSON.stringify(from) +
249
+ ', which is not among the items being installed: ' +
250
+ describeProvides(wanted));
251
+ }
252
+ if (!(0, kind_1.kindDef)(kind).alias) {
253
+ throw new utility_1.SdkGenError(capitalise(kind) + ' aliasing is not supported: ' + spec +
254
+ '\n A ' + kind + ' name is part of the generated config ' +
255
+ '(options.' + kind + '.<name>) and of the hook wiring in every ' +
256
+ 'target, so it cannot be renamed at install time.');
257
+ }
258
+ out[from] = to;
259
+ }
260
+ return out;
261
+ }
262
+ async function cmd_package_add(args, actx) {
263
+ const refs = args.slice(2).flatMap((a) => 'string' === typeof a ? a.split(',') : a)
264
+ .filter((r) => null != r && '' !== r);
265
+ if (0 === refs.length) {
266
+ throw new utility_1.SdkGenError('package add: no package given');
267
+ }
268
+ return package_add(refs, actx);
269
+ }
270
+ async function package_add(refs, actx) {
271
+ const log = actx.log;
272
+ const flags = actx.flags ?? {};
273
+ // `--only` and `--alias` name items, so they only make sense for ONE
274
+ // package. Silently applying them to each of several would install the same
275
+ // alias twice.
276
+ if (1 < refs.length && (null != flags.only || null != flags.alias)) {
277
+ throw new utility_1.SdkGenError('--only and --alias apply to a single package; ' + refs.length +
278
+ ' were given: ' + refs.join(', '));
279
+ }
280
+ // PREFLIGHT EVERY PACKAGE BEFORE INSTALLING ANY OF THEM.
281
+ //
282
+ // The same argument that makes this verb validate a manifest in full before
283
+ // writing anything applies across refs: `package add good,bad` that
284
+ // installed `good` and then failed would leave exactly the half-completed
285
+ // command the guarantee is about. Resolution, the engine gate, manifest
286
+ // validation, `--only` selection and the name-collision check all happen
287
+ // here, for all of them, before the first file is written.
288
+ const plan = refs.map((ref) => plan_one(ref, flags, actx));
289
+ checkCollisions(plan, actx);
290
+ const results = [];
291
+ for (const { src, wanted, items } of plan) {
292
+ log.info({
293
+ point: 'package-add-start', package: src.manifest.name, ref: src.ref,
294
+ version: src.manifest.version, root: src.root,
295
+ note: src.manifest.name +
296
+ (null == src.manifest.version ? '' : '@' + src.manifest.version) +
297
+ ' <- ' + src.root
298
+ });
299
+ for (const kind of orderedKinds(wanted)) {
300
+ const add = adderFor(kind);
301
+ const itemrefs = items[kind] ?? [];
302
+ if (0 === itemrefs.length) {
303
+ continue;
304
+ }
305
+ if (null == add) {
306
+ // Validation already rejected an unknown kind, so this is a kind the
307
+ // registry knows and nothing can install yet — a `docs` entry before
308
+ // its action exists. Say so rather than skipping in silence.
309
+ log.warn({
310
+ point: 'package-kind-unsupported', package: src.manifest.name, kind,
311
+ names: wanted[kind],
312
+ note: src.manifest.name + ': nothing can install `' + kind +
313
+ '` items yet; skipped ' + (wanted[kind] ?? []).join(', ')
314
+ });
315
+ continue;
316
+ }
317
+ results.push(await add(itemrefs, actx));
318
+ (0, resolve_1.registerInstalled)(kind, itemrefs, actx);
319
+ }
320
+ log.info({
321
+ point: 'package-add-end', package: src.manifest.name, ref: src.ref,
322
+ note: src.manifest.name + ': added ' + describeProvides(wanted)
323
+ });
324
+ }
325
+ // The LAST jostraca result, for the CLI's change summary. Each per-kind add
326
+ // already reported its own changes as it ran.
327
+ return { jres: results[results.length - 1]?.jres };
328
+ }
329
+ function plan_one(ref, flags, actx) {
330
+ const src = resolvePackage(ref, actx);
331
+ checkEngine(src, actx);
332
+ refuse(src, (0, manifest_1.validateManifest)(actx.fs(), src.sdk, src.manifest, kind_1.KINDS), actx.log);
333
+ const wanted = selectItems(src, flags.only, actx.log);
334
+ const aliases = parseAliases(flags.alias, wanted);
335
+ const items = Object.create(null);
336
+ for (const kind of Object.keys(wanted)) {
337
+ items[kind] = (wanted[kind] ?? []).map((name) => node_path_1.default.join(src.root, name) +
338
+ (Object.prototype.hasOwnProperty.call(aliases, name) ?
339
+ '~' + aliases[name] : ''));
340
+ }
341
+ return { src, wanted, items };
342
+ }
343
+ // Would any of this REPLACE something the project got from elsewhere?
344
+ //
345
+ // `add` is overwrite, deliberately — that is how a resync works. But
346
+ // overwriting one package's `go` with a different package's `go` is not a
347
+ // resync: it silently replaces a working target's model, components and
348
+ // templates. The project asked for a package, not for that.
349
+ //
350
+ // Checked across the WHOLE plan, so two packages in one command claiming the
351
+ // same name are caught too — the second would otherwise conflict with nothing,
352
+ // because the first is not installed yet either.
353
+ function checkCollisions(plan, actx) {
354
+ const claimed = new Map();
355
+ const clashes = [];
356
+ for (const { src, items } of plan) {
357
+ for (const kind of Object.keys(items)) {
358
+ for (const ref of items[kind]) {
359
+ let source;
360
+ try {
361
+ source = (0, resolve_1.resolveSource)(ref, kind, actx);
362
+ }
363
+ catch (err) {
364
+ // Unresolvable here means the add will fail too, with a better
365
+ // message than this check could give. Let it.
366
+ continue;
367
+ }
368
+ const key = kind + ':' + source.name;
369
+ const earlier = claimed.get(key);
370
+ if (null != earlier && earlier !== src.manifest.name) {
371
+ clashes.push(kind + ' `' + source.name + '`: both ' + earlier +
372
+ ' and ' + src.manifest.name + ' provide it');
373
+ continue;
374
+ }
375
+ claimed.set(key, src.manifest.name);
376
+ const conflict = (0, resolve_1.nameConflict)(kind, source, actx);
377
+ if (null != conflict) {
378
+ clashes.push(kind + ' `' + source.name + '`: already installed from ' +
379
+ (conflict.package || conflict.base) +
380
+ ', and ' + src.manifest.name + ' provides it too');
381
+ }
382
+ }
383
+ }
384
+ }
385
+ if (0 === clashes.length) {
386
+ return;
387
+ }
388
+ throw new utility_1.SdkGenError('Name collision, nothing installed:\n ' + clashes.join('\n ') +
389
+ '\n\n Either install the one you want by its own ref:' +
390
+ '\n voxgig-sdkgen target add <package>/<name>' +
391
+ '\n or install this package\'s under a different name:' +
392
+ '\n voxgig-sdkgen package add <package> --alias <name>=<alias>');
393
+ }
394
+ // `ADD_ORDER` first, then anything else the registry knows, so a kind added
395
+ // later installs without editing this list.
396
+ function orderedKinds(wanted) {
397
+ const rest = Object.keys(wanted)
398
+ .filter((k) => !ADD_ORDER.includes(k)).sort();
399
+ return [...ADD_ORDER.filter((k) => null != wanted[k]), ...rest];
400
+ }
401
+ // Registered by `dispatch`, to keep this module out of a require cycle with
402
+ // `target.ts` (which imports `feature.ts`, which imports `kind.ts`).
403
+ const ADDERS = Object.create(null);
404
+ function registerAdder(kind, add) {
405
+ ADDERS[kind] = add;
406
+ }
407
+ // The adder for a kind, LOADING THE REGISTRATIONS IF NOBODY HAS YET.
408
+ //
409
+ // `dispatch` is what calls `registerAdder`, and until this, whether it had
410
+ // been loaded depended on the importer: `sdkgen.ts` pulls it in, so the CLI
411
+ // worked, but anything importing this module directly got an empty table —
412
+ // and `package add` / `package update` then skipped every item with a
413
+ // `package-kind-unsupported` warning and reported success. It silently did
414
+ // nothing.
415
+ //
416
+ // Found the hard way: a throwaway script written to reproduce a review
417
+ // finding imported this module alone, and the "update" it performed was a
418
+ // no-op that looked like a passing result. A capability that depends on some
419
+ // other module having been imported first is not a capability.
420
+ //
421
+ // Required lazily rather than at the top, because `dispatch` imports THIS
422
+ // module; by call time the cycle has resolved.
423
+ function adderFor(kind) {
424
+ if (0 === Object.keys(ADDERS).length) {
425
+ require('./dispatch');
426
+ }
427
+ return ADDERS[kind];
428
+ }
429
+ // `package update <pkg>` — refresh everything a package supplied.
430
+ //
431
+ // See docs/design/sdkgen-packages.md §13.
432
+ //
433
+ // THE ORDER IS THE SAFETY PROPERTY, which is why this command owns the fetch
434
+ // instead of telling the operator to run `npm update` first:
435
+ //
436
+ // 1. CHECK the project's copies against the source AS CURRENTLY INSTALLED
437
+ // 2. FETCH the new version
438
+ // 3. RE-ADD each item
439
+ //
440
+ // Measured at step 1, a copy that differs from its source means the project
441
+ // changed it. Run the other way round — fetch first, then check — every item
442
+ // legitimately differs from the new source, the gate fires on all of them,
443
+ // and the operator learns to pass `--force` every time. That ordering bug
444
+ // would make the gate worse than useless, because the same signal (copy
445
+ // differs from source) carries both meanings and only sequence separates
446
+ // them.
447
+ //
448
+ // WHAT THE GATE CANNOT DO, and says so
449
+ //
450
+ // It cannot prove which meaning applies. If the operator already ran
451
+ // `npm update` in another shell, step 1 is measuring against the NEW source
452
+ // and a difference means "stale", not "forked" — and nothing recorded in the
453
+ // project distinguishes the two. So the refusal states both readings and
454
+ // gives a runnable out for each, rather than asserting a fork it cannot
455
+ // diagnose. (A per-file digest recorded at add time would make it exact;
456
+ // deferred, design §17.9.)
457
+ async function cmd_package_update(args, actx) {
458
+ const names = args.slice(2).flatMap((a) => 'string' === typeof a ? a.split(',') : a)
459
+ .filter((r) => null != r && '' !== r);
460
+ if (0 === names.length) {
461
+ throw new utility_1.SdkGenError('package update: no package given' +
462
+ '\n `voxgig-sdkgen package list` shows what this project has installed');
463
+ }
464
+ return package_update(names, actx);
465
+ }
466
+ // Everything the model says came from `pkgname`.
467
+ //
468
+ // By RECORDED PROVENANCE, not by asking the package what it provides: what
469
+ // this refreshes is what the project actually installed, which may be a
470
+ // subset (`--only`) or carry aliases the package never mentions. Asking the
471
+ // package would refresh things the project does not have and miss the ones it
472
+ // renamed.
473
+ function installedFrom(pkgname, actx) {
474
+ const kit = actx.model?.main?.[types_1.KIT] ?? {};
475
+ const found = [];
476
+ for (const kind of Object.keys(kind_1.KINDS).sort()) {
477
+ const items = kit[kind] ?? {};
478
+ for (const name of Object.keys(items).sort()) {
479
+ const item = items[name];
480
+ if (null == item || 'object' !== typeof item ||
481
+ item.package !== pkgname) {
482
+ continue;
483
+ }
484
+ const origname = item.origname || name;
485
+ found.push({
486
+ kind, name, origname,
487
+ base: item.base || '',
488
+ aliased: (0, kind_1.kindDef)(kind).alias && origname !== name,
489
+ });
490
+ }
491
+ }
492
+ return found;
493
+ }
494
+ async function package_update(names, actx) {
495
+ const log = actx.log;
496
+ const flags = actx.flags ?? {};
497
+ const results = [];
498
+ // EVERY package's items resolved and CHECKED before any of them is
499
+ // fetched. `package update A,B` that finished A and then refused B would
500
+ // exit as failed having already changed A's dependencies and `.sdk` — the
501
+ // partial command the gate exists to prevent, one level up from where it
502
+ // was already prevented.
503
+ const plan = names.map((pkgname) => {
504
+ const installed = installedFrom(pkgname, actx);
505
+ if (0 === installed.length) {
506
+ throw new utility_1.SdkGenError('Nothing installed from ' + pkgname +
507
+ '\n `voxgig-sdkgen package list` shows which packages this project ' +
508
+ 'has, and what each supplied');
509
+ }
510
+ return { pkgname, installed };
511
+ });
512
+ // STEP 1 — before anything moves, for all of them.
513
+ for (const { pkgname, installed } of plan) {
514
+ await preCheck(pkgname, installed, actx);
515
+ }
516
+ for (const { pkgname, installed } of plan) {
517
+ log.info({
518
+ point: 'package-update-start', package: pkgname,
519
+ items: installed.length,
520
+ note: pkgname + ': updating ' + installed.length + ' item(s)'
521
+ });
522
+ // STEP 2 — now the source may change.
523
+ await fetchPackage(pkgname, installed, actx);
524
+ // STEP 2b — the fetched version is a DIFFERENT package from the one
525
+ // step 1 measured, and nothing has validated it. `package add` refuses a
526
+ // package whose manifest lies or whose `engines.sdkgen` is beyond this
527
+ // generator; an update that skipped those checks would overwrite `.sdk`
528
+ // with components written for a generator this is not.
529
+ validateFetched(pkgname, installed, actx);
530
+ // STEP 3.
531
+ results.push(...await reAdd(pkgname, installed, actx));
532
+ log.info({
533
+ point: 'package-update-end', package: pkgname,
534
+ note: pkgname + ': updated'
535
+ });
536
+ }
537
+ return { jres: results[results.length - 1]?.jres };
538
+ }
539
+ // EVERYTHING THE RE-ADD WILL WRITE, not just the package's own items.
540
+ //
541
+ // The gate is only worth having if it covers what step 3 actually touches,
542
+ // and step 3 touches more than it is asked to: `target_add` re-runs
543
+ // `feature_add` for EVERY active feature in the model, whoever supplied it.
544
+ // So updating a target package rewrites the model file of a feature that came
545
+ // from somewhere else — and a scope of "this package's items" never looked at
546
+ // it, so a local edit there was overwritten without `--force`, by the command
547
+ // whose whole promise is that it asks first.
548
+ //
549
+ // The features are added to the scope, not the fan-out narrowed: what the
550
+ // re-add does is `target add`'s long-standing behaviour, and changing it here
551
+ // would make `package update` write something different from what a
552
+ // hand-typed add writes — the equivalence every other part of this verb is
553
+ // built on.
554
+ function blastRadius(installed, actx) {
555
+ const wanted = new Set(installed.map((i) => i.kind + ':' + i.name));
556
+ if (!installed.some((i) => 'target' === i.kind)) {
557
+ return wanted;
558
+ }
559
+ const features = actx.model?.main?.[types_1.KIT]?.feature ?? {};
560
+ for (const name of Object.keys(features)) {
561
+ if (false !== features[name]?.active) {
562
+ wanted.add('feature:' + name);
563
+ }
564
+ }
565
+ return wanted;
566
+ }
567
+ // STEP 1: is the project's copy of this package's items unmodified?
568
+ //
569
+ // Runs the SAME comparison `doctor` runs, scoped to these items — a gate that
570
+ // decides whether to overwrite a project's files must not have its own idea
571
+ // of what counts as a difference.
572
+ async function preCheck(pkgname, installed, actx) {
573
+ const flags = actx.flags ?? {};
574
+ const wanted = blastRadius(installed, actx);
575
+ const res = await (0, doctor_1.doctor)(actx, (kind, name) => wanted.has(kind + ':' + name));
576
+ const report = res.report;
577
+ // `forked` and `edited` only. `missing` means the project is short of what
578
+ // add would write, which an update FIXES; `resyncPending` is provenance
579
+ // catching up, which an update also fixes; `aliasedDiff` is the project's
580
+ // own differentiation of an alias, which step 3 does not touch anyway.
581
+ const changed = [...report.forked, ...report.edited];
582
+ if (0 === changed.length) {
583
+ return;
584
+ }
585
+ if (true === flags.force) {
586
+ actx.log.warn({
587
+ point: 'package-update-forced', package: pkgname, files: changed,
588
+ note: pkgname + ': --force, overwriting ' + changed.length +
589
+ ' locally-changed file(s): ' + changed.join(', ')
590
+ });
591
+ return;
592
+ }
593
+ throw new utility_1.SdkGenError(pkgname + ': ' + changed.length + ' file(s) differ from the installed ' +
594
+ 'source, so updating would overwrite them:\n ' + changed.join('\n ') +
595
+ '\n\n This means one of two things, and nothing recorded in the project ' +
596
+ 'tells them apart:' +
597
+ '\n - they are LOCAL EDITS, and `--force` will discard them;' +
598
+ '\n - or ' + pkgname + ' was already updated out of band (an ' +
599
+ '`npm update` in another shell), in which case they are merely STALE ' +
600
+ 'and nothing is at risk.' +
601
+ '\n\n If you did not update it: copy anything you want to keep into ' +
602
+ '.sdk/model/, then re-run with --force.' +
603
+ '\n If you did: reinstall the version you had, re-run this command, ' +
604
+ 'and it will check against the right source.');
605
+ }
606
+ // STEP 2: fetch. Injectable, so tests do not shell out and a caller with its
607
+ // own dependency management can supply one.
608
+ //
609
+ // `--no-fetch` covers the operator who has already fetched deliberately and
610
+ // accepts that step 1 measured against the new source. It is not the default
611
+ // because then this command would only ever re-apply the source it already
612
+ // has, which is `package add`.
613
+ async function fetchPackage(pkgname, installed, actx) {
614
+ const flags = actx.flags ?? {};
615
+ if (true === flags.nofetch) {
616
+ actx.log.info({
617
+ point: 'package-update-nofetch', package: pkgname,
618
+ note: pkgname + ': --no-fetch, using the source already installed'
619
+ });
620
+ return;
621
+ }
622
+ // A DRY RUN MUST NOT FETCH. The adders honour `actx.opts.dryrun` and write
623
+ // nothing, but an unconditional `npm install --save-dev` rewrites
624
+ // package.json, the lockfile and node_modules — mutating dependency state
625
+ // in the one mode whose entire promise is that nothing changes. This
626
+ // workstream has already fixed one dry-run defect (pruneStaleTemplates
627
+ // deleting files during a dry run); the fetch is the same failure in a
628
+ // louder place, because it reaches outside the project.
629
+ if (true === actx.opts?.dryrun) {
630
+ actx.log.info({
631
+ point: 'package-update-dryrun-fetch', package: pkgname,
632
+ note: pkgname + ': ** DRY RUN ** not fetching; the check and the ' +
633
+ 're-add below run against the source already installed'
634
+ });
635
+ return;
636
+ }
637
+ const fetch = actx.fetchPackage ?? npmFetch;
638
+ // npm CAN ONLY UPDATE WHAT NPM INSTALLED. A package added by a local path
639
+ // or an absolute checkout records that base, and the re-add reads from it —
640
+ // so an `npm install` would write a fresh copy into `node_modules`, leave
641
+ // the recorded source untouched, and the command would then recopy the OLD
642
+ // content while reporting success and having changed the project's
643
+ // dependencies. Refused rather than half-done, naming the way to do it.
644
+ if (null == actx.fetchPackage) {
645
+ const local = installed.find((i) => '' !== i.base && !isNodeModules(i.base));
646
+ if (null != local) {
647
+ throw new utility_1.SdkGenError(pkgname + ': installed from ' + local.base + ', which npm does not ' +
648
+ 'manage, so fetching would update a different copy and change ' +
649
+ 'nothing here.' +
650
+ '\n Update that source yourself (git pull, rebuild, …) and re-run ' +
651
+ 'with --no-fetch.');
652
+ }
653
+ }
654
+ await fetch(pkgname, actx);
655
+ }
656
+ // Is this base inside a `node_modules` directory — i.e. is it npm's to
657
+ // update? Checked on the '/'-normalised recorded value, which is how `base`
658
+ // is written (see helpers/stdrep), plus the platform separator for an
659
+ // absolute base recorded on Windows.
660
+ function isNodeModules(base) {
661
+ const norm = base.split(node_path_1.default.sep).join('/');
662
+ return norm.startsWith('node_modules/') || norm.includes('/node_modules/');
663
+ }
664
+ // The default fetch: hand it to npm, in the project's own directory.
665
+ //
666
+ // SHELLING OUT IS DELIBERATE and is the one place this generator runs another
667
+ // tool. The alternative — telling the operator to fetch first — is what makes
668
+ // the pre-check unable to distinguish a fork from a stale copy, which is the
669
+ // entire point of the ordering above.
670
+ async function npmFetch(pkgname, actx) {
671
+ const { execFile } = require('node:child_process');
672
+ const { promisify } = require('node:util');
673
+ const run = promisify(execFile);
674
+ const cwd = actx.folder ?? '.';
675
+ actx.log.info({
676
+ point: 'package-update-fetch', package: pkgname, cwd,
677
+ note: pkgname + ': npm install ' + pkgname + '@latest'
678
+ });
679
+ try {
680
+ // `npm.cmd` ON WINDOWS. `execFile` does no PATHEXT resolution, so plain
681
+ // `npm` — which is a `.cmd` shim there — fails with ENOENT. Not caught by
682
+ // CI, because every test injects its own fetcher and this function never
683
+ // runs; found by reading it rather than by it breaking.
684
+ //
685
+ // Arguments as an ARRAY and no `shell: true`, so a package name is never
686
+ // interpreted by a shell.
687
+ //
688
+ // maxBuffer raised well past execFile's 1MB default: `npm install` output
689
+ // for a large tree exceeds it, and the resulting ENOBUFS would be caught
690
+ // below and reported as a failed fetch — after npm had in fact SUCCEEDED,
691
+ // leaving the project's dependencies updated and its `.sdk` not.
692
+ const out = await run('win32' === process.platform ? 'npm.cmd' : 'npm', ['install', '--save-dev', pkgname + '@latest'], { cwd, maxBuffer: 64 * 1024 * 1024 });
693
+ actx.log.debug({
694
+ point: 'package-update-fetched', package: pkgname,
695
+ stdout: out.stdout, stderr: out.stderr
696
+ });
697
+ }
698
+ catch (err) {
699
+ throw new utility_1.SdkGenError(pkgname + ': fetch failed — ' + (err.message || String(err)) +
700
+ '\n nothing has been overwritten. Fetch it yourself and re-run with ' +
701
+ '--no-fetch, or fix the install and try again.' +
702
+ (null == err.stderr ? '' : '\n\n' + err.stderr));
703
+ }
704
+ }
705
+ // STEP 2b: the fetched package is a DIFFERENT package from the one checked.
706
+ //
707
+ // Resolved from a recorded base rather than by name, because that is where
708
+ // the re-add will read from — validating some other copy of the package would
709
+ // be validating the wrong thing.
710
+ //
711
+ // A source with no manifest is not an error here, unlike in `package add`:
712
+ // the project demonstrably installed from it once, and refusing to refresh it
713
+ // now would strand a project whose package predates the manifest. Nothing is
714
+ // validated in that case, which is the same amount as before this command
715
+ // existed.
716
+ function validateFetched(pkgname, installed, actx) {
717
+ const base = installed.find((i) => '' !== i.base)?.base;
718
+ if (null == base) {
719
+ return;
720
+ }
721
+ const root = node_path_1.default.join(node_path_1.default.isAbsolute(base) ? base : node_path_1.default.join(actx.folder ?? '.', base), '..');
722
+ const read = (0, manifest_1.readManifest)(actx.fs(), node_path_1.default.join(root, '.sdk'));
723
+ if (null == read.manifest) {
724
+ actx.log.info({
725
+ point: 'package-update-unmanifested', package: pkgname, file: read.file,
726
+ note: pkgname + ': the source declares no manifest, so the fetched ' +
727
+ 'version could not be validated'
728
+ });
729
+ return;
730
+ }
731
+ const src = {
732
+ ref: pkgname, root, sdk: node_path_1.default.join(root, '.sdk'), manifest: read.manifest,
733
+ };
734
+ checkEngine(src, actx);
735
+ refuse(src, (0, manifest_1.validateManifest)(actx.fs(), src.sdk, src.manifest, kind_1.KINDS), actx.log);
736
+ }
737
+ // STEP 3: re-add each item from its recorded base.
738
+ //
739
+ // An ALIASED item's model file is left alone, and that is not special-cased
740
+ // here: `kindModel` already creates it `exclude: true` for a kind whose
741
+ // aliased definition is project-owned, so a re-add refreshes `src/cmp` and
742
+ // `tm` from the new origin and leaves the file the project is MEANT to edit
743
+ // untouched. Reported, so the author knows to port upstream model changes by
744
+ // hand rather than discovering later that they were never applied.
745
+ async function reAdd(pkgname, installed, actx) {
746
+ const log = actx.log;
747
+ const results = [];
748
+ const skipped = installed.filter((i) => i.aliased);
749
+ if (0 < skipped.length) {
750
+ log.info({
751
+ point: 'package-update-alias-model-kept', package: pkgname,
752
+ items: skipped.map((i) => i.kind + '/' + i.name),
753
+ note: pkgname + ': keeping the model file of ' + skipped.length +
754
+ ' aliased item(s) — that file is where an alias is differentiated, ' +
755
+ 'so upstream model changes to ' +
756
+ skipped.map((i) => i.origname).join(', ') +
757
+ ' must be ported by hand: ' +
758
+ skipped.map((i) => 'model/' + i.kind + '/' + i.name + '.aontu').join(', ')
759
+ });
760
+ }
761
+ // Same order as `package add`, for the same reason: `feature add` fans a
762
+ // feature's source across the targets in the model.
763
+ const byKind = Object.create(null);
764
+ for (const item of installed) {
765
+ // The ref that reinstalls it — exactly what `recordedRef` reconstructs
766
+ // for doctor, so an update and a check agree about where an item is from.
767
+ const ref = node_path_1.default.join(item.base, '..', item.origname) +
768
+ (item.origname === item.name ? '' : '~' + item.name);
769
+ (byKind[item.kind] = byKind[item.kind] ?? []).push(ref);
770
+ }
771
+ for (const kind of orderedKinds(byKind)) {
772
+ const add = adderFor(kind);
773
+ if (null == add) {
774
+ log.warn({
775
+ point: 'package-kind-unsupported', package: pkgname, kind,
776
+ note: pkgname + ': nothing can install `' + kind + '` items yet'
777
+ });
778
+ continue;
779
+ }
780
+ results.push(await add(byKind[kind], actx));
781
+ }
782
+ return results;
783
+ }
784
+ // `package list` — what this project has installed, and where each item came
785
+ // from. Read entirely from the MODEL's recorded provenance (§4), which is why
786
+ // there is no lockfile to consult and nothing that can disagree with it.
787
+ async function cmd_package_list(_args, actx) {
788
+ const log = actx.log;
789
+ const fs = actx.fs();
790
+ const kit = actx.model?.main?.[types_1.KIT] ?? {};
791
+ // package name -> kind -> [{name, base, origname}]
792
+ const groups = Object.create(null);
793
+ for (const kind of Object.keys(kind_1.KINDS).sort()) {
794
+ const items = kit[kind] ?? {};
795
+ for (const name of Object.keys(items).sort()) {
796
+ const item = items[name];
797
+ if (null == item || 'object' !== typeof item) {
798
+ continue;
799
+ }
800
+ // An item with no recorded base predates provenance; it is still
801
+ // installed, and saying "(unrecorded)" is more use than omitting it.
802
+ const pkg = ('' === item.package || null == item.package) ?
803
+ UNRECORDED : item.package;
804
+ (groups[pkg] = groups[pkg] ?? []).push({
805
+ kind, name,
806
+ origname: item.origname || name,
807
+ base: item.base || '',
808
+ });
809
+ }
810
+ }
811
+ const packages = Object.keys(groups).sort();
812
+ for (const pkg of packages) {
813
+ // The version ON DISK, not one recorded at add time: what `package
814
+ // update` would compare against is the source as it is now.
815
+ const version = installedVersion(fs, actx.folder ?? '.', groups[pkg]);
816
+ log.info({
817
+ point: 'package-list-entry', package: pkg, version,
818
+ items: groups[pkg],
819
+ note: pkg + (null == version ? '' : '@' + version) + ': ' +
820
+ groups[pkg].map((i) => i.kind + ' `' + i.name + '`' +
821
+ (i.origname === i.name ? '' : ' (' + i.origname + ')')).join(', ')
822
+ });
823
+ }
824
+ log.info({
825
+ point: 'package-list-end', packages: packages.length,
826
+ note: 0 === packages.length ?
827
+ 'nothing installed' : packages.length + ' package(s)'
828
+ });
829
+ return { jres: undefined, report: { packages, groups } };
830
+ }
831
+ const UNRECORDED = '(unrecorded)';
832
+ // Read the version from the manifest the items say they came from. All items
833
+ // of one package share a base in practice; the first that yields a manifest
834
+ // answers.
835
+ function installedVersion(fs, project, items) {
836
+ for (const item of items) {
837
+ if ('' === item.base) {
838
+ continue;
839
+ }
840
+ const sdk = node_path_1.default.isAbsolute(item.base) ?
841
+ item.base : node_path_1.default.join(project, item.base);
842
+ const read = (0, manifest_1.readManifest)(fs, sdk);
843
+ if (null != read.manifest?.version) {
844
+ return read.manifest.version;
845
+ }
846
+ }
847
+ return undefined;
848
+ }
849
+ function capitalise(s) {
850
+ return s.charAt(0).toUpperCase() + s.slice(1);
851
+ }
852
+ //# sourceMappingURL=package.js.map