@voxgig/sdkgen 4.6.0 → 4.7.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 +1 -1
- package/dist/cmp/Feature.js +5 -0
- package/dist/cmp/Feature.js.map +1 -1
- package/dist/cmp/TestControl.d.ts +4 -0
- package/dist/cmp/TestControl.js +51 -0
- package/dist/cmp/TestControl.js.map +1 -0
- package/dist/helpers/featureSource.d.ts +3 -1
- package/dist/helpers/featureSource.js +105 -0
- package/dist/helpers/featureSource.js.map +1 -1
- package/dist/sdkgen.d.ts +3 -2
- package/dist/sdkgen.js +9 -4
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.d.ts +2 -1
- package/dist/utility.js +52 -0
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aon +58 -0
- package/package.json +1 -1
- package/project/.sdk/model/feature/secrets.aon +82 -0
- package/project/.sdk/src/cmp/cpp/Main_cpp.ts +2 -1
- package/project/.sdk/src/cmp/cpp/Test_cpp.ts +5 -1
- package/project/.sdk/src/cmp/csharp/Main_csharp.ts +2 -1
- package/project/.sdk/src/cmp/csharp/TestDirect_csharp.ts +30 -6
- package/project/.sdk/src/cmp/csharp/TestEntity_csharp.ts +29 -5
- package/project/.sdk/src/cmp/csharp/Test_csharp.ts +5 -1
- package/project/.sdk/src/cmp/dart/Main_dart.ts +2 -1
- package/project/.sdk/src/cmp/dart/TestDirect_dart.ts +23 -6
- package/project/.sdk/src/cmp/dart/TestEntity_dart.ts +28 -7
- package/project/.sdk/src/cmp/dart/Test_dart.ts +5 -1
- package/project/.sdk/src/cmp/go/Main_go.ts +2 -1
- package/project/.sdk/src/cmp/go/Test_go.ts +5 -1
- package/project/.sdk/src/cmp/java/Main_java.ts +2 -1
- package/project/.sdk/src/cmp/java/Test_java.ts +5 -1
- package/project/.sdk/src/cmp/js/Main_js.ts +2 -1
- package/project/.sdk/src/cmp/js/TestDirect_js.ts +30 -5
- package/project/.sdk/src/cmp/js/TestEntity_js.ts +35 -5
- package/project/.sdk/src/cmp/js/Test_js.ts +5 -1
- package/project/.sdk/src/cmp/js/fragment/Direct.test.fragment.js +7 -1
- package/project/.sdk/src/cmp/js/fragment/Entity.test.fragment.js +7 -1
- package/project/.sdk/src/cmp/kotlin/Main_kotlin.ts +2 -1
- package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +5 -1
- package/project/.sdk/src/cmp/lua/Main_lua.ts +2 -1
- package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +28 -4
- package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +24 -4
- package/project/.sdk/src/cmp/lua/Test_lua.ts +5 -1
- package/project/.sdk/src/cmp/ocaml/Gitignore_ocaml.ts +1 -0
- package/project/.sdk/src/cmp/perl/Main_perl.ts +2 -1
- package/project/.sdk/src/cmp/perl/TestDirect_perl.ts +25 -4
- package/project/.sdk/src/cmp/perl/TestEntity_perl.ts +24 -4
- package/project/.sdk/src/cmp/perl/Test_perl.ts +5 -1
- package/project/.sdk/src/cmp/perl/utility_perl.ts +10 -0
- package/project/.sdk/src/cmp/php/Main_php.ts +2 -1
- package/project/.sdk/src/cmp/php/TestDirect_php.ts +24 -5
- package/project/.sdk/src/cmp/php/TestEntity_php.ts +28 -4
- package/project/.sdk/src/cmp/php/Test_php.ts +5 -1
- package/project/.sdk/src/cmp/py/Main_py.ts +2 -1
- package/project/.sdk/src/cmp/py/Test_py.ts +5 -1
- package/project/.sdk/src/cmp/rb/Main_rb.ts +2 -1
- package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +24 -5
- package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +24 -4
- package/project/.sdk/src/cmp/rb/Test_rb.ts +5 -1
- package/project/.sdk/src/cmp/rb/utility_rb.ts +3 -1
- package/project/.sdk/src/cmp/rust/Main_rust.ts +2 -1
- package/project/.sdk/src/cmp/rust/TestDirect_rust.ts +31 -4
- package/project/.sdk/src/cmp/rust/TestEntity_rust.ts +37 -4
- package/project/.sdk/src/cmp/rust/Test_rust.ts +5 -1
- package/project/.sdk/src/cmp/scala/Main_scala.ts +2 -1
- package/project/.sdk/src/cmp/scala/Test_scala.ts +5 -1
- package/project/.sdk/src/cmp/ts/Config_ts.ts +50 -8
- package/project/.sdk/src/cmp/ts/Main_ts.ts +10 -2
- package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +10 -3
- package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +11 -3
- package/project/.sdk/src/cmp/ts/Test_ts.ts +5 -1
- package/project/.sdk/tm/csharp/test/Runner.cs +45 -0
- package/project/.sdk/tm/dart/test/utility.dart +39 -0
- package/project/.sdk/tm/js/test/sdk-test-control.json +19 -0
- package/project/.sdk/tm/js/test/utility.js +91 -1
- package/project/.sdk/tm/lua/test/runner.lua +40 -0
- package/project/.sdk/tm/ocaml/test/harness.ml +14 -4
- package/project/.sdk/tm/perl/t/runner.pm +30 -0
- package/project/.sdk/tm/php/test/Runner.php +36 -0
- package/project/.sdk/tm/rb/test/runner.rb +25 -0
- package/project/.sdk/tm/rust/tests/common/mod.rs +32 -0
- package/project/.sdk/tm/rust/utility/make_options.rs +1 -1
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +8 -2
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +29 -5
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +30 -21
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/Registry.ts +90 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/addr.ts +48 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/aws.ts +219 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/azuresecrets.ts +144 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/boru.ts +113 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/doppler.ts +77 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/dotenv.ts +51 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/env.ts +31 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/file.ts +54 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/gcpsecrets.ts +118 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/hashicorp.ts +159 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/http.ts +66 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/infisical.ts +107 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/memory.ts +33 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/onepassword.ts +120 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/support.ts +176 -0
- package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +15 -13
- package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +25 -19
- package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +1 -1
- package/project/.sdk/tm/ts/test/vendor/omni/index.ts +1 -1
- package/project/.sdk/tm/zig/core/utility.zig +1 -1
- package/project/sdkgen-package.json +1 -1
- package/src/cmp/Feature.ts +5 -0
- package/src/cmp/TestControl.ts +64 -0
- package/src/helpers/featureSource.ts +130 -0
- package/src/sdkgen.ts +7 -0
- package/src/utility.ts +61 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Providers.ts +0 -1221
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// VENDORED: @voxgig/omni 0.1.2 (typescript/src/Runner.ts)
|
|
2
|
-
// Source: https://github.com/voxgig/omni @
|
|
2
|
+
// Source: https://github.com/voxgig/omni @ 5956cc4e5ecdaeebd11eab8bb4b9462dfc76e018
|
|
3
3
|
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
4
|
// Omni: the shared multi-language test runner.
|
|
5
5
|
//
|
|
@@ -352,20 +352,22 @@ function fixjsonval(val: Json, donull: boolean): Json {
|
|
|
352
352
|
}
|
|
353
353
|
|
|
354
354
|
// The JSON form of an error: always at least {name,message}.
|
|
355
|
-
//
|
|
356
|
-
//
|
|
357
|
-
//
|
|
358
|
-
// fixture's
|
|
359
|
-
//
|
|
360
|
-
//
|
|
361
|
-
//
|
|
355
|
+
//
|
|
356
|
+
// A thrown value need not be an Error. Ports commonly rethrow an
|
|
357
|
+
// error-SHAPED map ({name, message, ...}) - voxgig/sdkgen's generated
|
|
358
|
+
// makeError rethrows the fixture's own error object verbatim - and
|
|
359
|
+
// collapsing that to String(err) yields '[object Object]', which fails both
|
|
360
|
+
// the `err` check and every `match.err.*` leaf. The struct repository's
|
|
361
|
+
// original runner read `.message` regardless of the thrown value's class.
|
|
362
362
|
function errify(err: any): Json {
|
|
363
363
|
if (err instanceof Error) {
|
|
364
364
|
return { ...err, name: err.name, message: err.message }
|
|
365
365
|
}
|
|
366
|
+
|
|
366
367
|
if (null != err && 'object' === typeof err) {
|
|
367
368
|
return { name: 'Error', ...err }
|
|
368
369
|
}
|
|
370
|
+
|
|
369
371
|
return { name: 'Error', message: String(err) }
|
|
370
372
|
}
|
|
371
373
|
|
|
@@ -481,11 +483,11 @@ function handleerror(flags: Flags, index: number, entry: Json, err: any) {
|
|
|
481
483
|
|
|
482
484
|
// Check that every leaf of `check` is present, and matches, in `base`.
|
|
483
485
|
function match(flags: Flags, index: number, entry: Json, check: Json, base: Json) {
|
|
484
|
-
//
|
|
485
|
-
//
|
|
486
|
-
//
|
|
487
|
-
//
|
|
488
|
-
//
|
|
486
|
+
// Read the base DIRECTLY. The clone bought nothing - the walk below only
|
|
487
|
+
// reads, via getpath - and it blows the stack on a cyclic base. A port
|
|
488
|
+
// that drives entries with live objects rather than pure JSON produces
|
|
489
|
+
// those routinely: voxgig/sdkgen's corpus matches against a live client
|
|
490
|
+
// context whose root context reaches the client again.
|
|
489
491
|
const cbase = base
|
|
490
492
|
|
|
491
493
|
const at = (path: Json[]) => (0 === path.length ? '<root>' : pathify(path))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// VENDORED: @voxgig/omni 0.1.2 (typescript/src/Util.ts)
|
|
2
|
-
// Source: https://github.com/voxgig/omni @
|
|
2
|
+
// Source: https://github.com/voxgig/omni @ 5956cc4e5ecdaeebd11eab8bb4b9462dfc76e018
|
|
3
3
|
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
4
|
// Omni internal JSON utilities.
|
|
5
5
|
//
|
|
@@ -158,12 +158,17 @@ function deepequal(a: Json, b: Json): boolean {
|
|
|
158
158
|
|
|
159
159
|
// Compact JSON text with map keys sorted, so that messages are identical
|
|
160
160
|
// in every port regardless of local map ordering.
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
|
|
161
|
+
//
|
|
162
|
+
// CYCLE SAFE. Building a FAILURE MESSAGE must never be the thing that
|
|
163
|
+
// crashes: a port driving entries with live objects rather than pure JSON
|
|
164
|
+
// can carry a cyclic value in the entry bookkeeping fail() prints, and this
|
|
165
|
+
// recursed until the stack gave out. A cycle renders as "[Circular]", as
|
|
166
|
+
// the struct repository's original runner did.
|
|
167
|
+
//
|
|
168
|
+
// `seen` tracks the ANCESTORS of the current value, not every value
|
|
169
|
+
// visited: it is removed again on the way out, so the same object appearing
|
|
170
|
+
// twice as siblings - a DAG, not a cycle - still renders in full.
|
|
171
|
+
function jsonstr(val: Json, seen?: Set<any>): string {
|
|
167
172
|
if (undefined === val) {
|
|
168
173
|
return 'undefined'
|
|
169
174
|
}
|
|
@@ -184,23 +189,24 @@ function jsonstr(val: Json, seen?: WeakSet<object>): string {
|
|
|
184
189
|
return val ? 'true' : 'false'
|
|
185
190
|
}
|
|
186
191
|
|
|
187
|
-
if (islist(val)) {
|
|
188
|
-
seen = seen || new
|
|
189
|
-
if (seen.has(val)) {
|
|
190
|
-
return '"[Circular]"'
|
|
191
|
-
}
|
|
192
|
-
seen.add(val)
|
|
193
|
-
return '[' + val.map((entry: Json) => jsonstr(entry, seen)).join(',') + ']'
|
|
194
|
-
}
|
|
192
|
+
if (islist(val) || ismap(val)) {
|
|
193
|
+
seen = seen || new Set()
|
|
195
194
|
|
|
196
|
-
if (ismap(val)) {
|
|
197
|
-
seen = seen || new WeakSet()
|
|
198
195
|
if (seen.has(val)) {
|
|
199
196
|
return '"[Circular]"'
|
|
200
197
|
}
|
|
198
|
+
|
|
201
199
|
seen.add(val)
|
|
202
|
-
|
|
203
|
-
|
|
200
|
+
|
|
201
|
+
const out = islist(val)
|
|
202
|
+
? '[' + val.map((entry: Json) => jsonstr(entry, seen)).join(',') + ']'
|
|
203
|
+
: '{' + Object.keys(val).sort()
|
|
204
|
+
.map((key) => JSON.stringify(key) + ':' + jsonstr((val as any)[key], seen))
|
|
205
|
+
.join(',') + '}'
|
|
206
|
+
|
|
207
|
+
seen.delete(val)
|
|
208
|
+
|
|
209
|
+
return out
|
|
204
210
|
}
|
|
205
211
|
|
|
206
212
|
if ('function' === typeof val) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// VENDORED: @voxgig/omni 0.1.2 (typescript/compat/struct.ts), import path adapted ('../src' -> './index').
|
|
2
|
-
// Source: https://github.com/voxgig/omni @
|
|
2
|
+
// Source: https://github.com/voxgig/omni @ 5956cc4e5ecdaeebd11eab8bb4b9462dfc76e018
|
|
3
3
|
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
4
|
// Drop-in replacement for the in-situ test runner in the voxgig/struct
|
|
5
5
|
// repository (`typescript/test/runner.ts`).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// VENDORED: @voxgig/omni 0.1.2 (typescript/src/index.ts)
|
|
2
|
-
// Source: https://github.com/voxgig/omni @
|
|
2
|
+
// Source: https://github.com/voxgig/omni @ 5956cc4e5ecdaeebd11eab8bb4b9462dfc76e018
|
|
3
3
|
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
4
|
// @voxgig/omni - shared multi-language test runner.
|
|
5
5
|
|
|
@@ -372,7 +372,7 @@ pub fn make_options_util(ctx: *Context) Value {
|
|
|
372
372
|
.{ "base", h.vstr("http://localhost:8000") },
|
|
373
373
|
.{ "prefix", h.vstr("") },
|
|
374
374
|
.{ "suffix", h.vstr("") },
|
|
375
|
-
.{ "auth", h.jo(&.{.{ "prefix", h.vstr("") }}) },
|
|
375
|
+
.{ "auth", h.jo(&.{ .{ "prefix", h.vstr("") }, .{ "basic", h.vbool(false) } }) },
|
|
376
376
|
.{ "headers", h.jo(&.{.{ "`$CHILD`", h.vstr("`$STRING`") }}) },
|
|
377
377
|
.{ "allow", h.jo(&.{
|
|
378
378
|
.{ "method", h.vstr("GET,PUT,POST,PATCH,DELETE,OPTIONS") },
|
package/src/cmp/Feature.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { cmp, Copy, Folder } from 'jostraca'
|
|
|
3
3
|
|
|
4
4
|
import { ensureStdrep } from '../helpers/stdrep'
|
|
5
5
|
import { featureApplies, featureTags } from '../helpers/applicability'
|
|
6
|
+
import { pluginExcludesFor } from '../helpers/featureSource'
|
|
6
7
|
|
|
7
8
|
const Feature = cmp(function Feature(props: any) {
|
|
8
9
|
const { target, feature, ctx$ } = props
|
|
@@ -31,6 +32,10 @@ const Feature = cmp(function Feature(props: any) {
|
|
|
31
32
|
// TODO: Copy should just warn if from not found
|
|
32
33
|
Copy({
|
|
33
34
|
from: 'tm/' + target.name + '/src/feature/' + feature.name,
|
|
35
|
+
// An ACTIVE feature's INACTIVE plugins do not come with it. This
|
|
36
|
+
// is the copy that decides it: Main's exclude never sees these
|
|
37
|
+
// files, because this Copy has already written them.
|
|
38
|
+
exclude: pluginExcludesFor(ctx$.model, feature.name),
|
|
34
39
|
replace: {
|
|
35
40
|
// Feature templates reference the SDK class by placeholder — e.g.
|
|
36
41
|
// tm/ts/.../TestFeature.ts imports `ProjectNameSDK`. Without the
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import Path from 'node:path'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
cmp,
|
|
5
|
+
File,
|
|
6
|
+
Fragment,
|
|
7
|
+
} from 'jostraca'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const TEST_CONTROL_FILE = 'sdk-test-control.json'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// Keep the blanket per-target `Copy({ from: 'tm/<lang>' })` from restoring the
|
|
14
|
+
// template default over a project's edited control file. One regex for every
|
|
15
|
+
// target: the file sits under `test/`, `t/`, `tests/` or `sdktest/` depending
|
|
16
|
+
// on the language, and the name is what identifies it either way.
|
|
17
|
+
const TEST_CONTROL_EXCLUDE = new RegExp('(^|/)' + TEST_CONTROL_FILE + '$')
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
// `sdk-test-control.json` is the ONE generated file a project is meant to edit
|
|
21
|
+
// by hand: it names the tests to skip, the live pacing, and the extra client
|
|
22
|
+
// options a live run needs (`test.client.options`).
|
|
23
|
+
//
|
|
24
|
+
// It shipped as part of each target's blanket `Copy({ from: 'tm/<lang>' })`,
|
|
25
|
+
// and a Copy has no per-file "leave it alone if it already exists" — so every
|
|
26
|
+
// `npm run generate` overwrote whatever the project had put there. The
|
|
27
|
+
// documented workaround, editing the template master under `.sdk/tm/`, is
|
|
28
|
+
// worse: `doctor` reports it as drift and the next `target add <lang>` reverts
|
|
29
|
+
// it.
|
|
30
|
+
//
|
|
31
|
+
// So it is emitted HERE instead, write-once: `File({ exclude: true })` returns
|
|
32
|
+
// early when the file already exists (FileOp), which is exactly the semantics
|
|
33
|
+
// the file is documented to have. A project without one yet gets the
|
|
34
|
+
// template's default; a project that has edited one keeps its edits across
|
|
35
|
+
// both `generate` and `target add`.
|
|
36
|
+
//
|
|
37
|
+
// The content still comes from the target's own template, so there remains
|
|
38
|
+
// exactly one copy of the default on disk.
|
|
39
|
+
//
|
|
40
|
+
// The template is addressed the SAME way the Copy this replaces addressed it:
|
|
41
|
+
// `tm/<lang>/...` relative to the working directory, which is the project's
|
|
42
|
+
// `.sdk`. Resolved to absolute here because Fragment resolves a RELATIVE
|
|
43
|
+
// `from` against the OUTPUT folder instead — a different base, and the one
|
|
44
|
+
// place these two could silently disagree.
|
|
45
|
+
const TestControl = cmp(function TestControl(props: {
|
|
46
|
+
target: { name: string }
|
|
47
|
+
dir: string
|
|
48
|
+
ctx$?: any
|
|
49
|
+
}) {
|
|
50
|
+
const { target, dir } = props
|
|
51
|
+
|
|
52
|
+
File({ name: TEST_CONTROL_FILE, exclude: true }, () => {
|
|
53
|
+
Fragment({
|
|
54
|
+
from: Path.resolve('tm', target.name, dir, TEST_CONTROL_FILE)
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
export {
|
|
61
|
+
TestControl,
|
|
62
|
+
TEST_CONTROL_FILE,
|
|
63
|
+
TEST_CONTROL_EXCLUDE,
|
|
64
|
+
}
|
|
@@ -260,6 +260,134 @@ function srcFeatureExcludes(model: any): RegExp[] {
|
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
|
|
263
|
+
// The same rule as srcFeatureExcludes, ONE LEVEL DEEPER: a feature's
|
|
264
|
+
// declared-but-inactive PLUGINS.
|
|
265
|
+
//
|
|
266
|
+
// A plugin is a feature's optional part — the `secrets` feature over
|
|
267
|
+
// sekreto has one per provider kind, and each carries its own platform
|
|
268
|
+
// cost (node:crypto for AWS request signing, fetch for seven HTTP vault
|
|
269
|
+
// clients). A project whose chain is `[dotenv, env]` should carry neither,
|
|
270
|
+
// and before the plugin trim it carried both, because every kind was
|
|
271
|
+
// reachable from a single import.
|
|
272
|
+
//
|
|
273
|
+
// Layout is `src/feature/<feature>/plugin/<plugin>/`, so a plugin's tree
|
|
274
|
+
// is excluded exactly as a feature's is. Only declared-but-inactive
|
|
275
|
+
// plugins are excluded: a directory the model never mentions is left
|
|
276
|
+
// alone, matching srcFeatureExcludes' treatment of `base`.
|
|
277
|
+
//
|
|
278
|
+
// Note this trims a plugin belonging to an ACTIVE feature. An inactive
|
|
279
|
+
// feature's whole tree — plugins included — is already gone via
|
|
280
|
+
// srcFeatureExcludes, so walking its plugins here would be redundant, and
|
|
281
|
+
// asking the model for them would fail on the `only_active` filter anyway.
|
|
282
|
+
// One feature's inactive-plugin patterns.
|
|
283
|
+
//
|
|
284
|
+
// Separate from pluginExcludes(model) because the two COPIES that need it
|
|
285
|
+
// see different things: Main copies the whole target tree and has the
|
|
286
|
+
// model, while the Feature component copies one feature's tree and has
|
|
287
|
+
// only that feature. Feature's copy is the one that actually matters —
|
|
288
|
+
// it runs for every ACTIVE feature, so without this an active feature's
|
|
289
|
+
// plugins were all copied regardless, and Main's exclude never saw them.
|
|
290
|
+
function pluginExcludesFor(model: any, fname: string): RegExp[] {
|
|
291
|
+
if (null == model || null == fname) {
|
|
292
|
+
return []
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// `only_active: false`, and this is the whole subtlety. The feature
|
|
296
|
+
// object a component is handed has ALREADY been filtered, so its
|
|
297
|
+
// `plugin` map contains only the ACTIVE plugins — and a function looking
|
|
298
|
+
// there for something inactive finds nothing and excludes nothing. That
|
|
299
|
+
// is exactly the trap srcFeatureExcludes documents, hit again one level
|
|
300
|
+
// down: the first cut of this read the filtered feature and silently
|
|
301
|
+
// trimmed nothing while every test passed.
|
|
302
|
+
const plugins = getModelPath(model,
|
|
303
|
+
`main.${KIT}.feature.${fname}.plugin`,
|
|
304
|
+
{ required: false, only_active: false }) || {}
|
|
305
|
+
|
|
306
|
+
const out: RegExp[] = []
|
|
307
|
+
const esc = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
308
|
+
|
|
309
|
+
for (const pname of Object.keys(plugins).sort()) {
|
|
310
|
+
const plugin = plugins[pname]
|
|
311
|
+
if (false !== plugin?.active) continue
|
|
312
|
+
|
|
313
|
+
const declared = plugin.path || []
|
|
314
|
+
|
|
315
|
+
if (0 < declared.length) {
|
|
316
|
+
for (const one of declared) {
|
|
317
|
+
// FEATURE-RELATIVE, because jostraca tests a Copy's candidates
|
|
318
|
+
// against the path RELATIVE TO THAT COPY'S ROOT — `sekreto/
|
|
319
|
+
// provider/aws.ts`, not `src/feature/secrets/sekreto/provider/
|
|
320
|
+
// aws.ts`. The Feature component's Copy is rooted at
|
|
321
|
+
// `tm/<target>/src/feature/<feature>`, so a full declared path
|
|
322
|
+
// never matches and the trim silently does nothing.
|
|
323
|
+
//
|
|
324
|
+
// Worth stating because it is invisible: an exclude that matches
|
|
325
|
+
// nothing is indistinguishable from no exclude at all, and both
|
|
326
|
+
// the patterns and the model were correct while nothing was
|
|
327
|
+
// trimmed. Main's copy IS rooted at the target root, which is why
|
|
328
|
+
// pluginExcludes below keeps the full path.
|
|
329
|
+
const rel = String(one).replace(
|
|
330
|
+
new RegExp('^src/feature/' + esc(fname) + '/'), '')
|
|
331
|
+
const pat = esc(rel)
|
|
332
|
+
out.push(new RegExp('(^|/)' + pat.replace(/\\\/$/, '') +
|
|
333
|
+
(/\/$/.test(rel) ? '/' : '$')))
|
|
334
|
+
}
|
|
335
|
+
continue
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
out.push(new RegExp('(^|/)plugin/' + esc(pname) + '/'))
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return out
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
function pluginExcludes(model: any): RegExp[] {
|
|
346
|
+
const active = getModelPath(model, `main.${KIT}.feature`,
|
|
347
|
+
{ required: false }) || {}
|
|
348
|
+
|
|
349
|
+
const out: RegExp[] = []
|
|
350
|
+
const esc = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
351
|
+
|
|
352
|
+
for (const fname of Object.keys(active)) {
|
|
353
|
+
const all = getModelPath(model,
|
|
354
|
+
`main.${KIT}.feature.${fname}.plugin`,
|
|
355
|
+
{ required: false, only_active: false }) || {}
|
|
356
|
+
const on = getModelPath(model,
|
|
357
|
+
`main.${KIT}.feature.${fname}.plugin`,
|
|
358
|
+
{ required: false }) || {}
|
|
359
|
+
|
|
360
|
+
for (const pname of Object.keys(all)) {
|
|
361
|
+
if (null != on[pname]) continue
|
|
362
|
+
|
|
363
|
+
// DECLARED paths first. A plugin's files are often not free to
|
|
364
|
+
// move — sekreto's provider modules are vendored, and both the
|
|
365
|
+
// vendoring guard and their own relative imports pin them at
|
|
366
|
+
// upstream's directory depth — so a plugin says which paths it
|
|
367
|
+
// owns rather than being assumed to own a directory.
|
|
368
|
+
const declared = all[pname].path || []
|
|
369
|
+
|
|
370
|
+
if (0 < declared.length) {
|
|
371
|
+
for (const one of declared) {
|
|
372
|
+
const pat = esc(String(one))
|
|
373
|
+
// A trailing slash means a folder and everything under it;
|
|
374
|
+
// anything else matches that path exactly, as featureExcludes
|
|
375
|
+
// does for a file source.
|
|
376
|
+
out.push(new RegExp('(^|/)' + pat.replace(/\\\/$/, '') +
|
|
377
|
+
(/\/$/.test(String(one)) ? '/' : '$')))
|
|
378
|
+
}
|
|
379
|
+
continue
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
out.push(new RegExp(
|
|
383
|
+
'(^|/)src/feature/' + esc(fname) + '/plugin/' + esc(pname) + '/'))
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return out
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
|
|
263
391
|
export type {
|
|
264
392
|
FeatureSource,
|
|
265
393
|
FeatureEntry,
|
|
@@ -275,4 +403,6 @@ export {
|
|
|
275
403
|
featureExcludes,
|
|
276
404
|
fullsetExcludes,
|
|
277
405
|
srcFeatureExcludes,
|
|
406
|
+
pluginExcludes,
|
|
407
|
+
pluginExcludesFor,
|
|
278
408
|
}
|
package/src/sdkgen.ts
CHANGED
|
@@ -40,6 +40,7 @@ import { License } from './cmp/License'
|
|
|
40
40
|
import { Security } from './cmp/Security'
|
|
41
41
|
import { Changelog } from './cmp/Changelog'
|
|
42
42
|
import { Test } from './cmp/Test'
|
|
43
|
+
import { TestControl, TEST_CONTROL_EXCLUDE } from './cmp/TestControl'
|
|
43
44
|
import { ReadmeInstall } from './cmp/ReadmeInstall'
|
|
44
45
|
import { ReadmeQuick } from './cmp/ReadmeQuick'
|
|
45
46
|
import { ReadmeErrors } from './cmp/ReadmeErrors'
|
|
@@ -73,6 +74,8 @@ import {
|
|
|
73
74
|
featureExcludes,
|
|
74
75
|
fullsetExcludes,
|
|
75
76
|
srcFeatureExcludes,
|
|
77
|
+
pluginExcludes,
|
|
78
|
+
pluginExcludesFor,
|
|
76
79
|
} from './helpers/featureSource'
|
|
77
80
|
import type { FeatureSource } from './helpers/featureSource'
|
|
78
81
|
import { stationLibrary } from './helpers/station'
|
|
@@ -1072,6 +1075,8 @@ export {
|
|
|
1072
1075
|
Entity,
|
|
1073
1076
|
Feature,
|
|
1074
1077
|
Test,
|
|
1078
|
+
TestControl,
|
|
1079
|
+
TEST_CONTROL_EXCLUDE,
|
|
1075
1080
|
Readme,
|
|
1076
1081
|
ReadmeTop,
|
|
1077
1082
|
AgentGuideTop,
|
|
@@ -1167,6 +1172,8 @@ export {
|
|
|
1167
1172
|
featureExcludes,
|
|
1168
1173
|
fullsetExcludes,
|
|
1169
1174
|
srcFeatureExcludes,
|
|
1175
|
+
pluginExcludes,
|
|
1176
|
+
pluginExcludesFor,
|
|
1170
1177
|
stationLibrary,
|
|
1171
1178
|
|
|
1172
1179
|
featureApplies,
|
package/src/utility.ts
CHANGED
|
@@ -82,6 +82,34 @@ function isHttpBasicAuth(model: any): boolean {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
|
|
85
|
+
// The API's ACCESS-TOKEN EXCHANGE, as apidef recorded it from the spec
|
|
86
|
+
// (main.kit.info.security.exchange): where the token endpoint lives, and the
|
|
87
|
+
// field names it sends and answers with. Null when the spec describes none,
|
|
88
|
+
// which is the common case.
|
|
89
|
+
//
|
|
90
|
+
// These are FACTS ABOUT THE API, so they belong in the generated config
|
|
91
|
+
// rather than in a project's hand-written model: an SDK whose spec says the
|
|
92
|
+
// exchange is at `auth/token` should not need to be told so again. A feature
|
|
93
|
+
// opts in to receiving them with `spec: { authexchange: '<options-key>' }`;
|
|
94
|
+
// configDefinition does the overlay.
|
|
95
|
+
//
|
|
96
|
+
// Deliberately NOT included: `active`. Whether a client performs the
|
|
97
|
+
// exchange is the project's decision (some resolve a static credential
|
|
98
|
+
// through the same feature on an API that also has a token endpoint), so
|
|
99
|
+
// apidef records only what the exchange IS, never that it should run.
|
|
100
|
+
function resolveAuthExchange(model: any): Record<string, any> | null {
|
|
101
|
+
const security = getModelPath(model, `main.${KIT}.info.security`,
|
|
102
|
+
{ only_active: false, required: false })
|
|
103
|
+
|
|
104
|
+
const exchange = security?.exchange
|
|
105
|
+
if (null == exchange || 'object' !== typeof exchange) {
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return exchange
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
85
113
|
function requirePath(ctx$: any, path: string, flags?: { ignore?: boolean }): any {
|
|
86
114
|
const fullpath = resolvePath(ctx$, path)
|
|
87
115
|
const ignore = null == flags?.ignore ? false : flags.ignore
|
|
@@ -118,6 +146,7 @@ export {
|
|
|
118
146
|
requirePath,
|
|
119
147
|
isAuthActive,
|
|
120
148
|
resolveAuthPrefix,
|
|
149
|
+
resolveAuthExchange,
|
|
121
150
|
isHttpBasicAuth,
|
|
122
151
|
SdkGenError,
|
|
123
152
|
CONFIG_DATA_THRESHOLD,
|
|
@@ -301,6 +330,15 @@ function rawStringLiteral(s: string): string {
|
|
|
301
330
|
}
|
|
302
331
|
|
|
303
332
|
|
|
333
|
+
// The closed vocabulary of spec-derived facts a feature may ask for by
|
|
334
|
+
// declaring `spec: { <fact>: <options-key> }`. Closed, and resolved through
|
|
335
|
+
// one function each, so a feature cannot reach arbitrarily into the model
|
|
336
|
+
// and a fact's shape is defined in exactly one place.
|
|
337
|
+
const SPEC_FACTS: Record<string, (model: any) => any> = {
|
|
338
|
+
authexchange: resolveAuthExchange,
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
|
|
304
342
|
// THE CANONICAL CONFIG OBJECT, and the JSON the threshold is measured on.
|
|
305
343
|
//
|
|
306
344
|
// Every target builds its config from this one function, so the literal a
|
|
@@ -361,6 +399,29 @@ function configDefinition(model: any, targetname?: string): { def: any, json: st
|
|
|
361
399
|
if (null != f.transport && '' !== f.transport) {
|
|
362
400
|
fdef.transport = String(f.transport)
|
|
363
401
|
}
|
|
402
|
+
|
|
403
|
+
// SPEC-DERIVED OPTIONS. A feature declares `spec: { <fact>: <options
|
|
404
|
+
// key> }` to receive facts apidef recorded from the OpenAPI spec — the
|
|
405
|
+
// same declare-never-infer rule `needs` and `transport` follow, and for
|
|
406
|
+
// the same reason: the alternative is this function knowing feature
|
|
407
|
+
// names, which is exactly the coupling the generic feature loop exists
|
|
408
|
+
// to avoid.
|
|
409
|
+
//
|
|
410
|
+
// The fact overlays the feature's DECLARED DEFAULTS, and rightly: a
|
|
411
|
+
// default like `path: 'auth/token'` is a generic guess, while the spec
|
|
412
|
+
// states where this API's endpoint actually is. A project that needs
|
|
413
|
+
// something else still overrides at runtime through `options.feature`,
|
|
414
|
+
// which beats the embedded config either way.
|
|
415
|
+
for (const factname of Object.keys(f.spec || {}).sort()) {
|
|
416
|
+
const optkey = f.spec[factname]
|
|
417
|
+
const fact = SPEC_FACTS[factname]?.(model)
|
|
418
|
+
if (null == fact || null == optkey || '' === optkey) {
|
|
419
|
+
continue
|
|
420
|
+
}
|
|
421
|
+
fdef.options = { ...(fdef.options || {}) }
|
|
422
|
+
fdef.options[optkey] = { ...(fdef.options[optkey] || {}), ...fact }
|
|
423
|
+
}
|
|
424
|
+
|
|
364
425
|
featureDefs[f.name] = fdef
|
|
365
426
|
})
|
|
366
427
|
|