@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.
- package/bin/voxgig-sdkgen +78 -10
- package/dist/action/action.d.ts +5 -2
- package/dist/action/action.js +113 -6
- package/dist/action/action.js.map +1 -1
- package/dist/action/check.d.ts +15 -0
- package/dist/action/check.js +347 -0
- package/dist/action/check.js.map +1 -0
- package/dist/action/dispatch.d.ts +8 -0
- package/dist/action/dispatch.js +74 -0
- package/dist/action/dispatch.js.map +1 -0
- package/dist/action/docs.d.ts +4 -0
- package/dist/action/docs.js +236 -0
- package/dist/action/docs.js.map +1 -0
- package/dist/action/doctor.d.ts +5 -2
- package/dist/action/doctor.js +526 -74
- package/dist/action/doctor.js.map +1 -1
- package/dist/action/feature.js +96 -17
- package/dist/action/feature.js.map +1 -1
- package/dist/action/kind.d.ts +30 -0
- package/dist/action/kind.js +258 -0
- package/dist/action/kind.js.map +1 -0
- package/dist/action/package.d.ts +25 -0
- package/dist/action/package.js +852 -0
- package/dist/action/package.js.map +1 -0
- package/dist/action/resolve.d.ts +18 -0
- package/dist/action/resolve.js +280 -0
- package/dist/action/resolve.js.map +1 -0
- package/dist/action/target.d.ts +7 -1
- package/dist/action/target.js +240 -97
- package/dist/action/target.js.map +1 -1
- package/dist/cmp/Docs.d.ts +4 -0
- package/dist/cmp/Docs.js +98 -0
- package/dist/cmp/Docs.js.map +1 -0
- package/dist/cmp/ExternalDocs.d.ts +2 -0
- package/dist/cmp/ExternalDocs.js +43 -0
- package/dist/cmp/ExternalDocs.js.map +1 -0
- package/dist/cmp/ReadmeTop.js +5 -1
- package/dist/cmp/ReadmeTop.js.map +1 -1
- package/dist/helpers/definition.d.ts +5 -0
- package/dist/helpers/definition.js +72 -0
- package/dist/helpers/definition.js.map +1 -0
- package/dist/helpers/featureSource.d.ts +8 -2
- package/dist/helpers/featureSource.js +61 -12
- package/dist/helpers/featureSource.js.map +1 -1
- package/dist/helpers/junk.d.ts +11 -0
- package/dist/helpers/junk.js +149 -0
- package/dist/helpers/junk.js.map +1 -0
- package/dist/helpers/manifest.d.ts +50 -0
- package/dist/helpers/manifest.js +328 -0
- package/dist/helpers/manifest.js.map +1 -0
- package/dist/helpers/modelcheck.d.ts +22 -0
- package/dist/helpers/modelcheck.js +203 -0
- package/dist/helpers/modelcheck.js.map +1 -0
- package/dist/helpers/semver.d.ts +4 -0
- package/dist/helpers/semver.js +174 -0
- package/dist/helpers/semver.js.map +1 -0
- package/dist/helpers/shipped.d.ts +4 -0
- package/dist/helpers/shipped.js +36 -0
- package/dist/helpers/shipped.js.map +1 -0
- package/dist/helpers/stdrep.d.ts +9 -1
- package/dist/helpers/stdrep.js +22 -0
- package/dist/helpers/stdrep.js.map +1 -1
- package/dist/sdkgen.d.ts +15 -3
- package/dist/sdkgen.js +233 -67
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +10 -2
- package/dist/utility.js +17 -0
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aontu +69 -0
- package/package.json +3 -2
- package/project/.sdk/model/feature/audit.aontu +1 -0
- package/project/.sdk/model/feature/cache.aontu +1 -0
- package/project/.sdk/model/feature/clienttrack.aontu +1 -0
- package/project/.sdk/model/feature/debug.aontu +1 -0
- package/project/.sdk/model/feature/idempotency.aontu +1 -0
- package/project/.sdk/model/feature/log.aontu +1 -0
- package/project/.sdk/model/feature/metrics.aontu +1 -0
- package/project/.sdk/model/feature/netsim.aontu +1 -0
- package/project/.sdk/model/feature/paging.aontu +1 -0
- package/project/.sdk/model/feature/proxy.aontu +1 -0
- package/project/.sdk/model/feature/ratelimit.aontu +1 -0
- package/project/.sdk/model/feature/rbac.aontu +1 -0
- package/project/.sdk/model/feature/retry.aontu +1 -0
- package/project/.sdk/model/feature/streaming.aontu +1 -0
- package/project/.sdk/model/feature/telemetry.aontu +1 -0
- package/project/.sdk/model/feature/test.aontu +1 -0
- package/project/.sdk/model/feature/timeout.aontu +1 -0
- package/project/.sdk/model/target/c.aontu +1 -0
- package/project/.sdk/model/target/clojure.aontu +1 -0
- package/project/.sdk/model/target/cpp.aontu +1 -0
- package/project/.sdk/model/target/dart.aontu +1 -0
- package/project/.sdk/model/target/elixir.aontu +1 -0
- package/project/.sdk/model/target/go-cli.aontu +1 -0
- package/project/.sdk/model/target/go-mcp.aontu +1 -0
- package/project/.sdk/model/target/go.aontu +1 -0
- package/project/.sdk/model/target/haskell.aontu +1 -0
- package/project/.sdk/model/target/java.aontu +1 -0
- package/project/.sdk/model/target/js.aontu +1 -0
- package/project/.sdk/model/target/kotlin.aontu +1 -0
- package/project/.sdk/model/target/lean.aontu +1 -0
- package/project/.sdk/model/target/lua.aontu +1 -0
- package/project/.sdk/model/target/ocaml.aontu +1 -0
- package/project/.sdk/model/target/perl.aontu +1 -0
- package/project/.sdk/model/target/php.aontu +1 -0
- package/project/.sdk/model/target/py-data.aontu +1 -0
- package/project/.sdk/model/target/py.aontu +1 -0
- package/project/.sdk/model/target/rb.aontu +1 -0
- package/project/.sdk/model/target/rust.aontu +1 -0
- package/project/.sdk/model/target/scala.aontu +1 -0
- package/project/.sdk/model/target/seneca-provider.aontu +1 -0
- package/project/.sdk/model/target/zig.aontu +1 -0
- package/project/.sdk/src/cmp/c/Config_c.ts +44 -0
- package/project/.sdk/src/cmp/c/fragment/Main.fragment.c +3 -1
- package/project/.sdk/src/cmp/clojure/Config_clojure.ts +37 -42
- package/project/.sdk/src/cmp/clojure/utility_clojure.ts +42 -0
- package/project/.sdk/src/cmp/cpp/Config_cpp.ts +18 -0
- package/project/.sdk/src/cmp/csharp/Config_csharp.ts +125 -52
- package/project/.sdk/src/cmp/csharp/fragment/Main.fragment.cs +3 -1
- package/project/.sdk/src/cmp/csharp/utility_csharp.ts +94 -6
- package/project/.sdk/src/cmp/dart/Config_dart.ts +56 -3
- package/project/.sdk/src/cmp/dart/fragment/Config.data.fragment.dart +74 -0
- package/project/.sdk/src/cmp/dart/fragment/Config.fragment.dart +6 -9
- package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +16 -5
- package/project/.sdk/src/cmp/dart/utility_dart.ts +18 -0
- package/project/.sdk/src/cmp/elixir/Config_elixir.ts +128 -13
- package/project/.sdk/src/cmp/elixir/fragment/Main.fragment.ex +3 -1
- package/project/.sdk/src/cmp/elixir/utility_elixir.ts +6 -0
- package/project/.sdk/src/cmp/haskell/Config_haskell.ts +35 -52
- package/project/.sdk/src/cmp/haskell/Package_haskell.ts +1 -0
- package/project/.sdk/src/cmp/haskell/Test_haskell.ts +1 -1
- package/project/.sdk/src/cmp/haskell/utility_haskell.ts +15 -5
- package/project/.sdk/src/cmp/java/Config_java.ts +21 -0
- package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +15 -0
- package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +42 -39
- package/project/.sdk/src/cmp/perl/Config_perl.ts +15 -0
- package/project/.sdk/src/cmp/perl/fragment/Main.fragment.pm +3 -1
- package/project/.sdk/src/cmp/rust/Config_rust.ts +44 -0
- package/project/.sdk/src/cmp/rust/fragment/Main.fragment.rs +4 -2
- package/project/.sdk/src/cmp/scala/Config_scala.ts +15 -0
- package/project/.sdk/src/cmp/swift/Config_swift.ts +17 -0
- package/project/.sdk/src/cmp/swift/fragment/Main.fragment.swift +3 -1
- package/project/.sdk/src/cmp/zig/Config_zig.ts +44 -0
- package/project/.sdk/src/cmp/zig/TestEntity_zig.ts +9 -2
- package/project/.sdk/src/cmp/zig/fragment/Main.fragment.zig +31 -18
- package/project/.sdk/tm/c/core/sdk.h +1 -0
- package/project/.sdk/tm/c/utility/make_options.c +5 -1
- package/project/.sdk/tm/clojure/src/sdk/core.clj +51 -3
- package/project/.sdk/tm/cpp/core/types.hpp +4 -1
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +5 -1
- package/project/.sdk/tm/csharp/test/PipelineTest.cs +15 -0
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +67 -1
- package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +43 -0
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +58 -2
- package/project/.sdk/tm/haskell/{test/TestJson.hs → src/SdkJson.hs} +8 -4
- package/project/.sdk/tm/haskell/src/SdkRuntime.hs +55 -2
- package/project/.sdk/tm/haskell/test/Runner.hs +1 -1
- package/project/.sdk/tm/haskell/test/StructCorpus.hs +1 -1
- package/project/.sdk/tm/java/core/SdkClient.java +3 -1
- package/project/.sdk/tm/java/utility/MakeOptions.java +5 -1
- package/project/.sdk/tm/kotlin/core/SdkClient.kt +3 -1
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +5 -1
- package/project/.sdk/tm/ocaml/Makefile +3 -2
- package/project/.sdk/tm/ocaml/sdk_json.ml +106 -0
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +56 -0
- package/project/.sdk/tm/ocaml/test/struct_corpus.ml +4 -86
- package/project/.sdk/tm/perl/utility/make_options.pm +5 -1
- package/project/.sdk/tm/rust/utility/make_options.rs +79 -1
- package/project/.sdk/tm/scala/core/SdkClient.scala +3 -1
- package/project/.sdk/tm/scala/utility/Make.scala +5 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +5 -1
- package/project/.sdk/tm/zig/core/utility.zig +85 -1
- package/project/.sdk/tm/zig/test/gotcha_test.zig +8 -8
- package/project/sdkgen-package.json +57 -0
- package/src/action/action.ts +136 -6
- package/src/action/check.ts +495 -0
- package/src/action/dispatch.ts +105 -0
- package/src/action/docs.ts +310 -0
- package/src/action/doctor.ts +692 -81
- package/src/action/feature.ts +109 -17
- package/src/action/kind.ts +375 -0
- package/src/action/package.ts +1173 -0
- package/src/action/resolve.ts +362 -0
- package/src/action/target.ts +290 -108
- package/src/cmp/Docs.ts +125 -0
- package/src/cmp/ExternalDocs.ts +54 -0
- package/src/cmp/ReadmeTop.ts +6 -1
- package/src/helpers/definition.ts +82 -0
- package/src/helpers/featureSource.ts +83 -13
- package/src/helpers/junk.ts +168 -0
- package/src/helpers/manifest.ts +474 -0
- package/src/helpers/modelcheck.ts +262 -0
- package/src/helpers/semver.ts +211 -0
- package/src/helpers/shipped.ts +41 -0
- package/src/helpers/stdrep.ts +70 -0
- package/src/sdkgen.ts +294 -66
- package/src/types.ts +33 -3
- 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
|
+
}
|