@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.
- 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/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 +43 -5
- 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 +3 -1
- 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/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/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,495 @@
|
|
|
1
|
+
// `package check` — the AUTHOR-side battery. Design §14.
|
|
2
|
+
//
|
|
3
|
+
// WHAT IT IS FOR
|
|
4
|
+
//
|
|
5
|
+
// Every other verb here acts on a project. This one acts on a PACKAGE, before
|
|
6
|
+
// anyone installs it, and its whole value is moving a failure from the
|
|
7
|
+
// consumer's build back to the author's terminal. The failures worth moving
|
|
8
|
+
// are the ones a package cannot detect by using itself:
|
|
9
|
+
//
|
|
10
|
+
// - a model file that compiles for the author and not for a consumer (the
|
|
11
|
+
// `//` comment asymmetry — seven shipped targets went out that way);
|
|
12
|
+
// - a key the base schema requires and the file omits, which nothing
|
|
13
|
+
// notices until a consumer unifies the whole model;
|
|
14
|
+
// - a key the PROJECT owns that the package pinned, which fails in the
|
|
15
|
+
// consumer's own file and reads as the consumer's mistake;
|
|
16
|
+
// - a manifest that disagrees with the disk, in either direction;
|
|
17
|
+
// - feature source the trim step will never recognise, so it ships to every
|
|
18
|
+
// project whatever the model selected.
|
|
19
|
+
//
|
|
20
|
+
// WHY IT REUSES EVERYTHING
|
|
21
|
+
//
|
|
22
|
+
// The battery asserts nothing of its own. Manifest agreement is
|
|
23
|
+
// `validateManifest`, the trees a kind needs are the kind registry's
|
|
24
|
+
// `requires`, the feature catalogue is `availableFeatures`, the source walk is
|
|
25
|
+
// `findFeatureEntries`, the model rules are `helpers/modelcheck`. A check that
|
|
26
|
+
// restated any of those would be a second copy of a rule the installer
|
|
27
|
+
// already has — and this workstream's most repeated defect is exactly that,
|
|
28
|
+
// two copies of one rule drifting apart. Where the check and the installer
|
|
29
|
+
// disagree, the check is wrong by construction.
|
|
30
|
+
//
|
|
31
|
+
// NO MANIFEST IS NOT A REFUSAL. `package add` requires one; here its absence
|
|
32
|
+
// is the first finding and the rest of the battery still runs, because an
|
|
33
|
+
// author who has not written it yet is precisely who needs the rest.
|
|
34
|
+
|
|
35
|
+
import Path from 'node:path'
|
|
36
|
+
|
|
37
|
+
import { KIT } from '../types'
|
|
38
|
+
|
|
39
|
+
import type {
|
|
40
|
+
ActionContext,
|
|
41
|
+
ActionResult,
|
|
42
|
+
} from '../types'
|
|
43
|
+
|
|
44
|
+
import { SdkGenError } from '../utility'
|
|
45
|
+
|
|
46
|
+
import {
|
|
47
|
+
MANIFEST,
|
|
48
|
+
probePackage,
|
|
49
|
+
validateManifest,
|
|
50
|
+
} from '../helpers/manifest'
|
|
51
|
+
|
|
52
|
+
import type { Finding, Manifest } from '../helpers/manifest'
|
|
53
|
+
|
|
54
|
+
import { definitionPath, definitionNames } from '../helpers/definition'
|
|
55
|
+
|
|
56
|
+
import {
|
|
57
|
+
ANCHOR,
|
|
58
|
+
ANCHOR_RE,
|
|
59
|
+
compileModel,
|
|
60
|
+
publishOverrideProbe,
|
|
61
|
+
slashComments,
|
|
62
|
+
} from '../helpers/modelcheck'
|
|
63
|
+
|
|
64
|
+
import {
|
|
65
|
+
BASE_FEATURE,
|
|
66
|
+
availableFeatures,
|
|
67
|
+
findFeatureEntries,
|
|
68
|
+
findFeatureSources,
|
|
69
|
+
} from '../helpers/featureSource'
|
|
70
|
+
|
|
71
|
+
import { scaffoldFolder } from '../helpers/shipped'
|
|
72
|
+
|
|
73
|
+
import { KINDS } from './kind'
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
// How many lines of one file's worth of the same problem to name before
|
|
77
|
+
// saying "and N more". A file with a hundred `//` lines is one mistake, and
|
|
78
|
+
// printing a hundred findings buries the other nine checks.
|
|
79
|
+
const SAME_FILE_LIMIT = 5
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
// A LIST OF NAMES from a manifest map, or nothing.
|
|
83
|
+
//
|
|
84
|
+
// The manifest is the one input here that is neither this generator's nor
|
|
85
|
+
// validated before it is read: `provides: { target: 42 }` is a manifest a
|
|
86
|
+
// person can write, `checkShape` already reports it, and spreading it then
|
|
87
|
+
// threw `TypeError: claimed is not iterable` — turning an actionable finding
|
|
88
|
+
// into a stack trace, from the verb whose entire job is actionable findings.
|
|
89
|
+
//
|
|
90
|
+
// Malformed reads as EMPTY rather than as its own finding: the shape checks
|
|
91
|
+
// have already said what is wrong with it, and saying so twice in different
|
|
92
|
+
// words helps nobody.
|
|
93
|
+
function claims(map: any, key: string): string[] {
|
|
94
|
+
const value = map?.[key]
|
|
95
|
+
|
|
96
|
+
return Array.isArray(value) ?
|
|
97
|
+
value.filter((n: any) => 'string' === typeof n && '' !== n) : []
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
type CheckReport = {
|
|
102
|
+
ok: boolean
|
|
103
|
+
ref: string
|
|
104
|
+
root: string
|
|
105
|
+
errors: number
|
|
106
|
+
warnings: number
|
|
107
|
+
findings: Finding[]
|
|
108
|
+
summary: string
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
async function cmd_package_check(
|
|
113
|
+
args: string[], actx: ActionContext,
|
|
114
|
+
): Promise<ActionResult> {
|
|
115
|
+
const refs = args.slice(2).flatMap(
|
|
116
|
+
(a: any) => 'string' === typeof a ? a.split(',') : a)
|
|
117
|
+
.filter((r: any) => null != r && '' !== r)
|
|
118
|
+
|
|
119
|
+
// No ref means "the package I am standing in", which is where an author
|
|
120
|
+
// runs it.
|
|
121
|
+
const reports = (0 === refs.length ? ['.'] : refs)
|
|
122
|
+
.map((ref: string) => checkPackage(ref, actx))
|
|
123
|
+
|
|
124
|
+
const ok = reports.every((r: CheckReport) => r.ok)
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
report: {
|
|
128
|
+
ok,
|
|
129
|
+
packages: reports,
|
|
130
|
+
// The one line the CLI prints when it exits non-zero. Built here
|
|
131
|
+
// because the counts are here; `bin/voxgig-sdkgen` must not have to
|
|
132
|
+
// know what a check finding is.
|
|
133
|
+
summary: reports.map((r: CheckReport) => r.summary).join('\n'),
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
// The battery, for one package. Logs as it goes and returns what it found, so
|
|
140
|
+
// a caller in a test can assert on findings without parsing log lines.
|
|
141
|
+
function checkPackage(ref: string, actx: ActionContext): CheckReport {
|
|
142
|
+
const fs = actx.fs()
|
|
143
|
+
const log = actx.log
|
|
144
|
+
|
|
145
|
+
const { found, search } = probePackage(fs, actx.folder ?? '.', ref)
|
|
146
|
+
|
|
147
|
+
// Nothing to check is not a finding — it is a wrong invocation, and the
|
|
148
|
+
// caller needs to know which paths were tried.
|
|
149
|
+
if (null == found) {
|
|
150
|
+
throw new SdkGenError(
|
|
151
|
+
'Package not found: ' + ref + '\n looked for a `.sdk` folder in:\n ' +
|
|
152
|
+
search.join('\n '))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const { root, sdk, read } = found
|
|
156
|
+
|
|
157
|
+
log.info({
|
|
158
|
+
point: 'package-check-start', package: ref, root,
|
|
159
|
+
note: ref + ': checking ' + root
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
const findings: Finding[] = [
|
|
163
|
+
...checkManifest(fs, sdk, read),
|
|
164
|
+
...checkItems(fs, sdk, read.manifest),
|
|
165
|
+
...checkFeatureSource(fs, sdk, read.manifest),
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
for (const f of findings) {
|
|
169
|
+
const level = 'error' === f.level ? 'error' : f.level
|
|
170
|
+
log[level](f)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const errors = findings.filter((f: Finding) => 'error' === f.level).length
|
|
174
|
+
const warnings = findings.filter((f: Finding) => 'warn' === f.level).length
|
|
175
|
+
|
|
176
|
+
const summary = ref + ': ' + (0 === errors ?
|
|
177
|
+
(0 === warnings ? 'no findings' : warnings + ' warning(s)') :
|
|
178
|
+
errors + ' error(s), ' + warnings + ' warning(s)')
|
|
179
|
+
|
|
180
|
+
log.info({
|
|
181
|
+
point: 'package-check-end', package: ref, root, errors, warnings,
|
|
182
|
+
note: summary
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
return { ok: 0 === errors, ref, root, errors, warnings, findings, summary }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
// The manifest, and its agreement with the disk in both directions.
|
|
190
|
+
function checkManifest(fs: any, sdk: string, read: any): Finding[] {
|
|
191
|
+
if (null != read.err) {
|
|
192
|
+
return [{
|
|
193
|
+
level: 'error', point: 'manifest-unreadable', file: read.file,
|
|
194
|
+
note: read.file + ': ' + read.err
|
|
195
|
+
}]
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (null == read.manifest) {
|
|
199
|
+
// A WARNING, not an error: the `.sdk` may be perfectly good, and a direct
|
|
200
|
+
// `target add <path>/<name>` installs from it today. What it cannot do is
|
|
201
|
+
// be installed as a package, which is what the manifest is for.
|
|
202
|
+
return [{
|
|
203
|
+
level: 'warn', point: 'manifest-absent', file: read.file,
|
|
204
|
+
note: read.file + ': no manifest, so `package add` cannot install this' +
|
|
205
|
+
' — its items can still be added directly by path'
|
|
206
|
+
}]
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return validateManifest(fs, sdk, read.manifest, KINDS)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
// Every item's definition file: the anchor, the comment dialect, the parse,
|
|
214
|
+
// the schema, and the kind-specific probes.
|
|
215
|
+
//
|
|
216
|
+
// The items are the manifest's claims UNION what is on disk, so a definition
|
|
217
|
+
// nobody claims is still checked (its being unclaimed is `validateManifest`'s
|
|
218
|
+
// finding, not a reason to skip it) and a claim with no file is skipped here
|
|
219
|
+
// (its absence is also already reported, and there is nothing to compile).
|
|
220
|
+
function checkItems(fs: any, sdk: string, manifest?: Manifest): Finding[] {
|
|
221
|
+
const found: Finding[] = []
|
|
222
|
+
|
|
223
|
+
for (const kind of Object.keys(KINDS).sort()) {
|
|
224
|
+
const claimed = claims(manifest?.provides, kind)
|
|
225
|
+
const ondisk = definitionNames(fs, sdk, kind)
|
|
226
|
+
|
|
227
|
+
const names = Array.from(new Set([...claimed, ...ondisk])).sort()
|
|
228
|
+
|
|
229
|
+
for (const name of names) {
|
|
230
|
+
const file = definitionPath(sdk, kind, name)
|
|
231
|
+
|
|
232
|
+
if (!fs.existsSync(file)) {
|
|
233
|
+
continue
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
found.push(...checkDefinition(fs, kind, name, file))
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return found
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
function checkDefinition(
|
|
245
|
+
fs: any, kind: string, name: string, file: string,
|
|
246
|
+
): Finding[] {
|
|
247
|
+
const found: Finding[] = []
|
|
248
|
+
const src = String(fs.readFileSync(file, 'utf8'))
|
|
249
|
+
|
|
250
|
+
const at = (level: 'error' | 'warn', point: string, note: string): Finding =>
|
|
251
|
+
({ level, point, kind, name, file, note: file + ': ' + note })
|
|
252
|
+
|
|
253
|
+
// 1. The provenance anchor. Its absence costs nothing at add time and
|
|
254
|
+
// everything afterwards: the copy records no source, so `doctor` and
|
|
255
|
+
// `package update` cannot find where it came from.
|
|
256
|
+
if (!ANCHOR_RE.test(src)) {
|
|
257
|
+
found.push(at('error', 'model-anchor-missing',
|
|
258
|
+
'no `' + ANCHOR + '` line — the copy would record no provenance, so ' +
|
|
259
|
+
'`package update` and `doctor` could never locate its source'))
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// 2. The comment dialect. Reported before the parse so the finding names
|
|
263
|
+
// the LINE rather than aontu's view of where the parse gave up.
|
|
264
|
+
const slashes = slashComments(src)
|
|
265
|
+
|
|
266
|
+
for (const s of slashes.slice(0, SAME_FILE_LIMIT)) {
|
|
267
|
+
found.push(at('error', 'model-slash-comment',
|
|
268
|
+
s.line + ': `' + s.text + '` — aontu takes `#` comments only; a `//` ' +
|
|
269
|
+
'or `/* */` line is a parse error in a consumer, which configures the ' +
|
|
270
|
+
'parser strictly even though a bare Aontu() accepts it'))
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (SAME_FILE_LIMIT < slashes.length) {
|
|
274
|
+
found.push(at('error', 'model-slash-comment',
|
|
275
|
+
'and ' + (slashes.length - SAME_FILE_LIMIT) + ' more slash-comment line(s)'))
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// A slash comment IS the parse failure below, and saying it twice — once
|
|
279
|
+
// with the line, once as aontu's `unexpected character(s): //` — reads as
|
|
280
|
+
// two problems. Everything after it would be reporting on a file the author
|
|
281
|
+
// is about to change anyway.
|
|
282
|
+
if (0 < slashes.length) {
|
|
283
|
+
return found
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 3. The parse, as a CONSUMER's parser sees it.
|
|
287
|
+
const strict = compileModel(src, file)
|
|
288
|
+
|
|
289
|
+
if (0 < strict.errors.length) {
|
|
290
|
+
// Which parser rejected it changes what the author must do, so say. A
|
|
291
|
+
// file that a bare Aontu() accepts and the strict one rejects is almost
|
|
292
|
+
// always the comment dialect above.
|
|
293
|
+
const bare = compileModel(src, file, { strict: false })
|
|
294
|
+
|
|
295
|
+
found.push(at('error', 'model-parse',
|
|
296
|
+
strict.errors.join(' | ') +
|
|
297
|
+
(0 === bare.errors.length ?
|
|
298
|
+
' (it DOES compile under a bare Aontu() — the difference is the ' +
|
|
299
|
+
'comment dialect a consumer configures)' : '')))
|
|
300
|
+
|
|
301
|
+
// Nothing below can run on a file that does not compile.
|
|
302
|
+
return found
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// 4. Does it declare the key its FILENAME promises? `model/target/iot-go.aontu`
|
|
306
|
+
// is copied to that name and included by it, so a file still declaring
|
|
307
|
+
// `main: kit: target: go:` installs an item the consumer's model never
|
|
308
|
+
// sees — the exact mistake a package author makes copying a bundled
|
|
309
|
+
// target as a starting point.
|
|
310
|
+
const declared = strict.model?.main?.[KIT]?.[kind]?.[name]
|
|
311
|
+
|
|
312
|
+
if (null == declared || 'object' !== typeof declared) {
|
|
313
|
+
found.push(at('error', 'model-key-missing',
|
|
314
|
+
'declares no `main: kit: ' + kind + ': ' + name + ':` block — the file ' +
|
|
315
|
+
'is installed and included under its own name, so nothing it declares ' +
|
|
316
|
+
'under another name is reachable'))
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// 5. The base schema. A non-defaulted key the file omits (`ext`,
|
|
320
|
+
// `comment.line`, `module.name`, a feature's `title`) compiles fine
|
|
321
|
+
// alone and fails the consumer's whole model.
|
|
322
|
+
const unified = compileModel(src, file, { schema: true })
|
|
323
|
+
|
|
324
|
+
for (const err of unified.errors.slice(0, SAME_FILE_LIMIT)) {
|
|
325
|
+
found.push(at('error', 'model-schema',
|
|
326
|
+
err + ' (unified with the base schema — this is what a consumer compiles)'))
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if ('target' === kind) {
|
|
330
|
+
found.push(...checkTargetModel(src, name, file, at))
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if ('feature' === kind) {
|
|
334
|
+
found.push(...checkFeatureModel(strict.model, name, at))
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return found
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
type At = (level: 'error' | 'warn', point: string, note: string) => Finding
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
// The publish-override probe: unify the target model with a project that sets
|
|
345
|
+
// the keys a project owns. Concrete-vs-concrete is a conflict in aontu, so a
|
|
346
|
+
// package that pins one makes it impossible for the consumer to set it — and
|
|
347
|
+
// the consumer's error names the CONSUMER's file.
|
|
348
|
+
function checkTargetModel(
|
|
349
|
+
src: string, name: string, file: string, at: At,
|
|
350
|
+
): Finding[] {
|
|
351
|
+
const probe = publishOverrideProbe(src, file, name)
|
|
352
|
+
|
|
353
|
+
if (0 === probe.errors.length) {
|
|
354
|
+
return []
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return [at('error', 'target-publish-pinned',
|
|
358
|
+
'a project cannot override its publication values: ' +
|
|
359
|
+
probe.errors.join(' | ') +
|
|
360
|
+
' (the target model sets a key the schema already defaults — leave ' +
|
|
361
|
+
'`publish.version`, `publish.registry.package`, `state` and `active` ' +
|
|
362
|
+
'unset; registry identity is yours, versions and names are the ' +
|
|
363
|
+
"project's)")]
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
// Per-target dependencies belong DIRECTLY under the feature
|
|
368
|
+
// (`deps: <target>: {…}`), which is the only path `collectDeps` reads. The
|
|
369
|
+
// other spelling — `feature.<f>.target.<t>.deps` — is schema-legal, because
|
|
370
|
+
// every target model declares that slot, so nothing errors and the
|
|
371
|
+
// dependency simply never reaches the generated manifest.
|
|
372
|
+
function checkFeatureModel(model: any, name: string, at: At): Finding[] {
|
|
373
|
+
const targets = model?.main?.[KIT]?.feature?.[name]?.target
|
|
374
|
+
|
|
375
|
+
if (null == targets || 'object' !== typeof targets) {
|
|
376
|
+
return []
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const found: Finding[] = []
|
|
380
|
+
|
|
381
|
+
for (const tname of Object.keys(targets).sort()) {
|
|
382
|
+
const deps = targets[tname]?.deps
|
|
383
|
+
|
|
384
|
+
if (null == deps || 'object' !== typeof deps) {
|
|
385
|
+
continue
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// The SLOT itself is legal and empty; only concrete entries are the
|
|
389
|
+
// mistake. An entry is concrete when it declares anything of its own.
|
|
390
|
+
const named = Object.keys(deps).filter((d: string) =>
|
|
391
|
+
null != deps[d] && 'object' === typeof deps[d] &&
|
|
392
|
+
0 < Object.keys(deps[d]).length)
|
|
393
|
+
|
|
394
|
+
if (0 === named.length) {
|
|
395
|
+
continue
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
found.push(at('warn', 'feature-deps-misplaced',
|
|
399
|
+
'declares ' + named.join(', ') + ' under `feature: ' + name +
|
|
400
|
+
': target: ' + tname + ': deps:` — nothing reads that path. ' +
|
|
401
|
+
'Per-target dependencies go directly under the feature: `deps: ' +
|
|
402
|
+
tname + ': { ' + named[0] + ': {…} }`'))
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return found
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
// The template trees: does a feature's declared coverage exist, and is
|
|
410
|
+
// anything in a target's tree feature-shaped but invisible to the trim?
|
|
411
|
+
function checkFeatureSource(
|
|
412
|
+
fs: any, sdk: string, manifest?: Manifest,
|
|
413
|
+
): Finding[] {
|
|
414
|
+
const found: Finding[] = []
|
|
415
|
+
const file = Path.join(sdk, '..', MANIFEST)
|
|
416
|
+
|
|
417
|
+
// The catalogue a CONSUMER will have: this generator's own features, plus
|
|
418
|
+
// the package's. A package shipping a copy of a bundled target ships source
|
|
419
|
+
// for features it does not provide, and those are not strays.
|
|
420
|
+
const bundled = availableFeatures(fs, scaffoldFolder())
|
|
421
|
+
|
|
422
|
+
const known = new Set([...bundled, ...availableFeatures(fs, sdk)])
|
|
423
|
+
|
|
424
|
+
// Declared coverage. `targetsSupported: { <feature>: [<target>, …] }` is
|
|
425
|
+
// the author's statement about which targets a feature ships source for, so
|
|
426
|
+
// it is checkable — unlike its absence, which means nothing either way.
|
|
427
|
+
const supported: any = manifest?.targetsSupported
|
|
428
|
+
|
|
429
|
+
for (const fname of Object.keys(supported ?? {}).sort()) {
|
|
430
|
+
for (const tname of claims(supported, fname)) {
|
|
431
|
+
const tm = Path.join(sdk, 'tm', tname)
|
|
432
|
+
|
|
433
|
+
if (0 === findFeatureSources(fs, tm, [fname.toLowerCase()]).length) {
|
|
434
|
+
found.push({
|
|
435
|
+
level: 'warn', point: 'feature-source-undelivered', file,
|
|
436
|
+
kind: 'feature', name: fname,
|
|
437
|
+
note: file + ': `targetsSupported.' + fname + '` claims ' + tname +
|
|
438
|
+
', but tm/' + tname + ' holds no source this generator can find ' +
|
|
439
|
+
'for it — see the naming conventions in ' +
|
|
440
|
+
'docs/how-to/add-a-feature.md'
|
|
441
|
+
})
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Strays: feature-shaped entries in a provided TARGET's tree whose name is
|
|
447
|
+
// in no catalogue. They are copied into every project regardless of what
|
|
448
|
+
// its model selects, because trim only drops what it can recognise.
|
|
449
|
+
//
|
|
450
|
+
// NOT REPORTED AT ALL if this generator's own feature definitions cannot be
|
|
451
|
+
// read: every bundled feature's source would then look like a stray, and a
|
|
452
|
+
// check that fires on a correct package because of a problem at ITS end is
|
|
453
|
+
// worse than one that stays quiet.
|
|
454
|
+
if (0 === bundled.length) {
|
|
455
|
+
return found
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const targets = new Set([
|
|
459
|
+
...claims(manifest?.provides, 'target'),
|
|
460
|
+
...definitionNames(fs, sdk, 'target'),
|
|
461
|
+
])
|
|
462
|
+
|
|
463
|
+
for (const tname of Array.from(targets).sort()) {
|
|
464
|
+
const tm = Path.join(sdk, 'tm', tname)
|
|
465
|
+
|
|
466
|
+
const strays = findFeatureEntries(fs, tm, known)
|
|
467
|
+
.filter((e) => e.shaped && !known.has(e.name) && BASE_FEATURE !== e.name)
|
|
468
|
+
|
|
469
|
+
if (0 === strays.length) {
|
|
470
|
+
continue
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
found.push({
|
|
474
|
+
level: 'warn', point: 'feature-source-unrecognised', file,
|
|
475
|
+
kind: 'target', name: tname,
|
|
476
|
+
note: 'tm/' + tname + ': ' + strays.map((e) => e.path).join(', ') +
|
|
477
|
+
' — named like feature source, but no `model/feature/<name>.aontu` ' +
|
|
478
|
+
'declares ' + strays.map((e) => e.name).join(', ') +
|
|
479
|
+
', so the trim cannot recognise them and every project gets them ' +
|
|
480
|
+
'whatever its model selects'
|
|
481
|
+
})
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return found
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
export type {
|
|
489
|
+
CheckReport,
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export {
|
|
493
|
+
cmd_package_check,
|
|
494
|
+
checkPackage,
|
|
495
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// WHAT `voxgig-sdkgen <action> …` CAN BE.
|
|
2
|
+
//
|
|
3
|
+
// Built FROM THE KIND REGISTRY rather than hand-listed, so registering a kind
|
|
4
|
+
// is the only edit a new kind needs — `docs add …` costs no dispatch code
|
|
5
|
+
// (docs/design/sdkgen-packages.md §9). The `package` and `doctor` verbs are
|
|
6
|
+
// not kinds and are added beside it.
|
|
7
|
+
//
|
|
8
|
+
// It lives in its own module because `package.ts` needs the per-kind add
|
|
9
|
+
// functions and `target.ts` imports `feature.ts` which imports `kind.ts`;
|
|
10
|
+
// wiring them together anywhere in that chain is a require cycle. Here,
|
|
11
|
+
// nothing imports back.
|
|
12
|
+
//
|
|
13
|
+
// NULL-PROTOTYPE, and this is not decoration. A plain object literal inherits
|
|
14
|
+
// Object.prototype, so `voxgig-sdkgen toString` (or `constructor`, `valueOf`,
|
|
15
|
+
// …) resolved to an inherited function, passed the `null == actionFunc`
|
|
16
|
+
// guard, and got CALLED with the action arguments instead of reporting an
|
|
17
|
+
// unknown action.
|
|
18
|
+
|
|
19
|
+
import type {
|
|
20
|
+
ActionContext,
|
|
21
|
+
ActionResult,
|
|
22
|
+
} from '../types'
|
|
23
|
+
|
|
24
|
+
import { KINDS } from './kind'
|
|
25
|
+
|
|
26
|
+
import { action_target, target_add } from './target'
|
|
27
|
+
import { action_feature, feature_add } from './feature'
|
|
28
|
+
import { action_docs, docs_add } from './docs'
|
|
29
|
+
import { action_doctor } from './doctor'
|
|
30
|
+
import { action_package, registerAdder } from './package'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
type ActionFunc =
|
|
34
|
+
(args: string[], actx: ActionContext) => Promise<ActionResult>
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
// The per-kind entry points, by kind name. A kind in the registry with no
|
|
38
|
+
// entry here is one nothing can install yet — reported when something asks
|
|
39
|
+
// for it, never silently ignored.
|
|
40
|
+
const KIND_ACTIONS: Record<string, ActionFunc> = Object.assign(
|
|
41
|
+
Object.create(null), {
|
|
42
|
+
target: action_target,
|
|
43
|
+
feature: action_feature,
|
|
44
|
+
docs: action_docs,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
// The same functions, as the direct `(refs, actx)` calls `package add` loops
|
|
49
|
+
// over. Registered rather than imported by `package.ts`, for the cycle above.
|
|
50
|
+
registerAdder('target', target_add)
|
|
51
|
+
registerAdder('feature', feature_add)
|
|
52
|
+
registerAdder('docs', docs_add)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
function actionMap(): Record<string, ActionFunc> {
|
|
56
|
+
const map: Record<string, ActionFunc> = Object.create(null)
|
|
57
|
+
|
|
58
|
+
for (const kind of Object.keys(KINDS)) {
|
|
59
|
+
if (null != KIND_ACTIONS[kind]) {
|
|
60
|
+
map[kind] = KIND_ACTIONS[kind]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
map.package = action_package
|
|
65
|
+
map.doctor = action_doctor
|
|
66
|
+
|
|
67
|
+
return map
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
const ACTION_MAP = actionMap()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
// The verbs, for help text and for an unknown-action message that says what
|
|
75
|
+
// IS available rather than only what is not.
|
|
76
|
+
function actionNames(): string[] {
|
|
77
|
+
return Object.keys(ACTION_MAP).sort()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
// Does this invocation need the PROJECT's model?
|
|
82
|
+
//
|
|
83
|
+
// Every verb but one operates on a project, and compiling `model/sdk.aontu`
|
|
84
|
+
// before dispatch is what makes them fail early and clearly when run from the
|
|
85
|
+
// wrong directory. `package check` is the exception on purpose: it operates
|
|
86
|
+
// on a PACKAGE, and an author runs it in their package root, where there is
|
|
87
|
+
// no project model to compile and nothing is wrong with that.
|
|
88
|
+
//
|
|
89
|
+
// Stated here, with the verb table, rather than in the CLI or in `sdkgen.ts`:
|
|
90
|
+
// it is a fact about the verb.
|
|
91
|
+
function needsModel(args: string[]): boolean {
|
|
92
|
+
return !('package' === args[0] && 'check' === args[1])
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
export type {
|
|
97
|
+
ActionFunc,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export {
|
|
101
|
+
ACTION_MAP,
|
|
102
|
+
actionMap,
|
|
103
|
+
actionNames,
|
|
104
|
+
needsModel,
|
|
105
|
+
}
|