@voxgig/sdkgen 3.6.0 → 3.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/AgentGuideTop.js +17 -0
- package/dist/cmp/AgentGuideTop.js.map +1 -1
- package/dist/cmp/Readme.js +2 -0
- package/dist/cmp/Readme.js.map +1 -1
- package/dist/cmp/ReadmeStation.d.ts +2 -0
- package/dist/cmp/ReadmeStation.js +57 -0
- package/dist/cmp/ReadmeStation.js.map +1 -0
- package/dist/helpers/naming.d.ts +3 -1
- package/dist/helpers/naming.js +39 -2
- package/dist/helpers/naming.js.map +1 -1
- package/dist/sdkgen.d.ts +2 -2
- package/dist/sdkgen.js +4 -2
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.d.ts +1 -1
- package/dist/utility.js +16 -2
- package/dist/utility.js.map +1 -1
- package/package.json +1 -1
- package/project/.sdk/src/cmp/c/Config_c.ts +34 -13
- package/project/.sdk/src/cmp/cpp/Config_cpp.ts +9 -46
- package/project/.sdk/src/cmp/csharp/Config_csharp.ts +7 -1
- package/project/.sdk/src/cmp/dart/Config_dart.ts +14 -2
- package/project/.sdk/src/cmp/dart/fragment/Config.data.fragment.dart +5 -0
- package/project/.sdk/src/cmp/dart/fragment/Config.fragment.dart +6 -0
- package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +13 -4
- package/project/.sdk/src/cmp/elixir/Config_elixir.ts +12 -2
- package/project/.sdk/src/cmp/elixir/Package_elixir.ts +41 -2
- package/project/.sdk/src/cmp/go/Config_go.ts +8 -1
- package/project/.sdk/src/cmp/java/Config_java.ts +8 -1
- package/project/.sdk/src/cmp/js/Config_js.ts +9 -1
- package/project/.sdk/src/cmp/js/fragment/Config.data.fragment.js +7 -0
- package/project/.sdk/src/cmp/js/fragment/Config.fragment.js +8 -0
- package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +33 -5
- package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +8 -1
- package/project/.sdk/src/cmp/lua/Config_lua.ts +11 -2
- package/project/.sdk/src/cmp/lua/Package_lua.ts +24 -1
- package/project/.sdk/src/cmp/perl/Config_perl.ts +8 -60
- package/project/.sdk/src/cmp/php/Config_php.ts +11 -2
- package/project/.sdk/src/cmp/php/Main_php.ts +25 -0
- package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +23 -6
- package/project/.sdk/src/cmp/py/Config_py.ts +11 -2
- package/project/.sdk/src/cmp/py/Main_py.ts +17 -7
- package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +23 -6
- package/project/.sdk/src/cmp/rb/Config_rb.ts +7 -1
- package/project/.sdk/src/cmp/rust/Config_rust.ts +5 -2
- package/project/.sdk/src/cmp/swift/Config_swift.ts +8 -54
- package/project/.sdk/src/cmp/swift/Package_swift.ts +52 -10
- package/project/.sdk/src/cmp/ts/Config_ts.ts +9 -1
- package/project/.sdk/src/cmp/ts/EntityTypes_ts.ts +5 -2
- package/project/.sdk/src/cmp/ts/Entity_ts.ts +8 -2
- package/project/.sdk/src/cmp/ts/fragment/Config.data.fragment.ts +7 -0
- package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +8 -0
- package/project/.sdk/src/cmp/ts/fragment/Entity.fragment.ts +1 -1
- package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +12 -4
- package/project/.sdk/tm/c/Makefile +6 -1
- package/project/.sdk/tm/c/utility/make_options.c +12 -1
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +9 -1
- package/project/.sdk/tm/csharp/utility/Fetcher.cs +23 -5
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +13 -0
- package/project/.sdk/tm/dart/lib/utility/FetcherUtility.dart +9 -0
- package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +17 -0
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +39 -3
- package/project/.sdk/tm/elixir/test/feature_test.exs +19 -0
- package/project/.sdk/tm/elixir/test/primary_utility_test.exs +72 -0
- package/project/.sdk/tm/go/test/primary_utility_test.go +42 -20
- package/project/.sdk/tm/go/test/runner_test.go +1 -1
- package/project/.sdk/tm/go/utility/fetcher.go +14 -0
- package/project/.sdk/tm/go/utility/make_options.go +31 -4
- package/project/.sdk/tm/java/utility/Fetcher.java +11 -1
- package/project/.sdk/tm/java/utility/MakeOptions.java +11 -0
- package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +20 -2
- package/project/.sdk/tm/kotlin/utility/Fetcher.kt +10 -1
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +11 -0
- package/project/.sdk/tm/lean/test/TPrimaryUtility.lean +13 -2
- package/project/.sdk/tm/lua/utility/make_options.lua +44 -0
- package/project/.sdk/tm/perl/utility/fetcher.pm +10 -1
- package/project/.sdk/tm/perl/utility/make_options.pm +36 -0
- package/project/.sdk/tm/php/test/PipelineTest.php +20 -0
- package/project/.sdk/tm/php/utility/Fetcher.php +12 -1
- package/project/.sdk/tm/php/utility/MakeOptions.php +58 -0
- package/project/.sdk/tm/py/pkg/utility/fetcher.py +13 -1
- package/project/.sdk/tm/py/pkg/utility/make_options.py +23 -0
- package/project/.sdk/tm/rb/utility/make_options.rb +18 -0
- package/project/.sdk/tm/rust/tests/common/mod.rs +1 -1
- package/project/.sdk/tm/rust/tests/feature_test.rs +27 -0
- package/project/.sdk/tm/rust/utility/fetcher.rs +8 -0
- package/project/.sdk/tm/rust/utility/make_options.rs +15 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Fetcher.swift +24 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +12 -0
- package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +20 -2
- package/project/.sdk/tm/ts/test/utility/Corpus.test.ts +85 -0
- package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +10 -17
- package/project/sdkgen-package.json +1 -1
- package/src/cmp/AgentGuideTop.ts +18 -0
- package/src/cmp/Readme.ts +2 -0
- package/src/cmp/ReadmeStation.ts +69 -0
- package/src/helpers/naming.ts +45 -2
- package/src/sdkgen.ts +3 -1
- package/src/utility.ts +17 -2
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
File,
|
|
5
5
|
cmp,
|
|
6
6
|
collectDeps,
|
|
7
|
+
each,
|
|
7
8
|
pkgDescription,
|
|
8
9
|
keywords,
|
|
9
10
|
repoInfo, packageName,
|
|
@@ -11,6 +12,11 @@ import {
|
|
|
11
12
|
} from '@voxgig/sdkgen'
|
|
12
13
|
|
|
13
14
|
|
|
15
|
+
import {
|
|
16
|
+
KIT,
|
|
17
|
+
getModelPath,
|
|
18
|
+
} from '@voxgig/apidef'
|
|
19
|
+
|
|
14
20
|
import type {
|
|
15
21
|
Model,
|
|
16
22
|
} from '@voxgig/apidef'
|
|
@@ -66,6 +72,23 @@ dependencies = {
|
|
|
66
72
|
`)
|
|
67
73
|
}
|
|
68
74
|
|
|
75
|
+
// Feature modules must be listed too, or an install-from-rock ships a
|
|
76
|
+
// features.lua whose requires cannot resolve. Emitted from the model
|
|
77
|
+
// (each = sorted order, byte-stable), plus the base feature every
|
|
78
|
+
// factory falls back to. The station feature additionally carries the
|
|
79
|
+
// VENDORED voxgig_station library beside its adapter (no voxgig-station
|
|
80
|
+
// rock exists to depend on - station design 9.2's registry-less tier).
|
|
81
|
+
const feature = getModelPath(model, `main.${KIT}.feature`)
|
|
82
|
+
let featureModules = ` ["feature.base_feature"] = "feature/base_feature.lua",\n`
|
|
83
|
+
each(feature, (f: any) => {
|
|
84
|
+
featureModules +=
|
|
85
|
+
` ["feature.${f.name}_feature"] = "feature/${f.name}_feature.lua",\n`
|
|
86
|
+
if ('station' === f.name) {
|
|
87
|
+
featureModules +=
|
|
88
|
+
` ["feature.station.voxgig_station"] = "feature/station/voxgig_station.lua",\n`
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
|
|
69
92
|
Content(`}
|
|
70
93
|
build = {
|
|
71
94
|
type = "builtin",
|
|
@@ -74,7 +97,7 @@ build = {
|
|
|
74
97
|
["config"] = "config.lua",
|
|
75
98
|
["config_shared"] = "config_shared.lua",
|
|
76
99
|
["features"] = "features.lua",
|
|
77
|
-
}
|
|
100
|
+
${featureModules} }
|
|
78
101
|
}
|
|
79
102
|
`)
|
|
80
103
|
})
|
|
@@ -3,24 +3,15 @@ import {
|
|
|
3
3
|
Content,
|
|
4
4
|
File,
|
|
5
5
|
cmp,
|
|
6
|
-
|
|
7
|
-
isAuthActive,
|
|
8
|
-
resolveAuthPrefix,
|
|
6
|
+
configDefinition,
|
|
9
7
|
} from '@voxgig/sdkgen'
|
|
10
8
|
|
|
11
9
|
|
|
12
10
|
import {
|
|
13
|
-
KIT,
|
|
14
11
|
Model,
|
|
15
|
-
getModelPath,
|
|
16
12
|
} from '@voxgig/apidef'
|
|
17
13
|
|
|
18
14
|
|
|
19
|
-
import {
|
|
20
|
-
clean,
|
|
21
|
-
} from './utility_perl'
|
|
22
|
-
|
|
23
|
-
|
|
24
15
|
// The config is emitted as a JSON heredoc parsed at load time by the
|
|
25
16
|
// vendored struct utility (Voxgig::Struct::parse_json). This keeps
|
|
26
17
|
// booleans/nulls faithful (Perl has no native boolean scalar) and yields
|
|
@@ -32,57 +23,14 @@ const Config = cmp(async function Config(props: any) {
|
|
|
32
23
|
|
|
33
24
|
const model: Model = ctx$.model
|
|
34
25
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
// config.auth.prefix override -> spec-derived info.security.prefix -> 'Bearer'
|
|
42
|
-
const authPrefix = resolveAuthPrefix(model)
|
|
43
|
-
|
|
44
|
-
let baseUrl = ''
|
|
45
|
-
try { baseUrl = getModelPath(model, `main.${KIT}.info.servers.0.url`) } catch (_e) { }
|
|
46
|
-
|
|
47
|
-
// Build the config structure with deterministic (sorted) key order for
|
|
48
|
-
// the model-derived collections (each() iterates sorted).
|
|
49
|
-
const featureBlock: any = {}
|
|
50
|
-
each(feature, (f: any) => {
|
|
51
|
-
featureBlock[f.name] = clean(f.config || {})
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
const entityOptions: any = {}
|
|
55
|
-
each(entity, (ent: any) => {
|
|
56
|
-
entityOptions[ent.name] = {}
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
const entityBlock: any = {}
|
|
60
|
-
each(entity, (ent: any) => {
|
|
61
|
-
entityBlock[ent.name] = clean({
|
|
62
|
-
fields: ent.fields,
|
|
63
|
-
name: ent.name,
|
|
64
|
-
op: ent.op,
|
|
65
|
-
relations: ent.relations,
|
|
66
|
-
}, true)
|
|
67
|
-
})
|
|
26
|
+
// THE canonical config object, from the shared helper - this component
|
|
27
|
+
// used to hand-assemble its own (and had already drifted: no server
|
|
28
|
+
// block, no identity beyond main.name). Passing the target name opts
|
|
29
|
+
// in to the main slug/version/target identity fields (station
|
|
30
|
+
// descriptor inputs), matching the ts/js/rb targets.
|
|
31
|
+
const { def: configDef } = configDefinition(model, target.name)
|
|
68
32
|
|
|
69
|
-
const
|
|
70
|
-
base: baseUrl,
|
|
71
|
-
}
|
|
72
|
-
if (authActive) {
|
|
73
|
-
options.auth = { prefix: authPrefix }
|
|
74
|
-
}
|
|
75
|
-
options.headers = headers
|
|
76
|
-
options.entity = entityOptions
|
|
77
|
-
|
|
78
|
-
const config = {
|
|
79
|
-
main: { name: model.const.Name },
|
|
80
|
-
feature: featureBlock,
|
|
81
|
-
options,
|
|
82
|
-
entity: entityBlock,
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const configJson = JSON.stringify(config, null, 2)
|
|
33
|
+
const configJson = JSON.stringify(configDef, null, 2)
|
|
86
34
|
|
|
87
35
|
File({ name: 'config.pm' }, () => {
|
|
88
36
|
|
|
@@ -70,8 +70,10 @@ const Config = cmp(async function Config(props: any) {
|
|
|
70
70
|
// The same config as an OBJECT, built by the shared helper so this target's
|
|
71
71
|
// literal and the data that replaces it above the threshold are the same
|
|
72
72
|
// config by construction. The JSON is what the threshold is measured on -
|
|
73
|
-
// emitted source size varies by language, the model does not.
|
|
74
|
-
|
|
73
|
+
// emitted source size varies by language, the model does not. Passing the
|
|
74
|
+
// target name opts in to the main.slug/version/target identity fields -
|
|
75
|
+
// both representations below carry them, keeping the reps interchangeable.
|
|
76
|
+
const { def: configDef, json: configJson } = configDefinition(model, target.name)
|
|
75
77
|
const asData = isConfigData(configJson, configReprSetting(model))
|
|
76
78
|
|
|
77
79
|
File({ name: 'config.' + target.ext }, () => {
|
|
@@ -174,6 +176,10 @@ class ${model.const.Name}Config
|
|
|
174
176
|
}
|
|
175
177
|
else {
|
|
176
178
|
|
|
179
|
+
// Identity beyond the camel Name: values from configDefinition's def, not
|
|
180
|
+
// re-derived here, so the literal rep and the data rep cannot disagree on
|
|
181
|
+
// identity (the slug is CARRIED, never derived from the camel name -
|
|
182
|
+
// station's descriptor reads all three; mirrors cmp/ts's #MainMeta).
|
|
177
183
|
Content(` /**
|
|
178
184
|
* Build a fresh, fully materialised config array. Every call rebuilds the
|
|
179
185
|
* whole structure, so prefer shared_config unless you need a private copy.
|
|
@@ -183,6 +189,9 @@ class ${model.const.Name}Config
|
|
|
183
189
|
return [
|
|
184
190
|
"main" => [
|
|
185
191
|
"name" => "${model.const.Name}",
|
|
192
|
+
"slug" => ${phps(configDef.main.slug)},
|
|
193
|
+
"version" => ${phps(configDef.main.version)},
|
|
194
|
+
"target" => ${phps(configDef.main.target)},
|
|
186
195
|
],
|
|
187
196
|
"feature" => [
|
|
188
197
|
`)
|
|
@@ -130,6 +130,31 @@ class ${model.const.Name}Features
|
|
|
130
130
|
return new ${model.const.Name}BaseFeature();
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Does a generated feature class back this name? False for a name only
|
|
136
|
+
* an options extend instance can supply (the station adopt path) - the
|
|
137
|
+
* constructor uses this to skip make_feature for such names instead of
|
|
138
|
+
* adding a stray BaseFeature.
|
|
139
|
+
*/
|
|
140
|
+
public static function has_feature(string $name): bool
|
|
141
|
+
{
|
|
142
|
+
switch ($name) {
|
|
143
|
+
case "base":
|
|
144
|
+
`)
|
|
145
|
+
|
|
146
|
+
each(feature, (feat: any) => {
|
|
147
|
+
if (feat.name !== 'base') {
|
|
148
|
+
Content(` case "${feat.name}":
|
|
149
|
+
`)
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
Content(` return true;
|
|
154
|
+
default:
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
133
158
|
}
|
|
134
159
|
`)
|
|
135
160
|
})
|
|
@@ -58,6 +58,12 @@ class ProjectNameSDK
|
|
|
58
58
|
|
|
59
59
|
$this->_rootctx->options = $this->options;
|
|
60
60
|
|
|
61
|
+
// Feature INSTANCES supplied at construction (the station adopt
|
|
62
|
+
// path) are read from the RAW construction options - extend is
|
|
63
|
+
// consumed exactly once, here; make_options strips it from the
|
|
64
|
+
// processed map so options_map() stays clean data.
|
|
65
|
+
$extend_val = is_array($options["extend"] ?? null) ? $options["extend"] : [];
|
|
66
|
+
|
|
61
67
|
// Add features in the resolved order (make_options puts an explicit
|
|
62
68
|
// list order first, else defaults to test-first). Ordering matters: the
|
|
63
69
|
// `test` feature installs the base mock transport and the transport
|
|
@@ -70,6 +76,20 @@ class ProjectNameSDK
|
|
|
70
76
|
foreach ($featureorder as $fname) {
|
|
71
77
|
$fopts = ProjectNameHelpers::to_map($feature_opts[$fname] ?? null);
|
|
72
78
|
if ($fopts && isset($fopts["active"]) && $fopts["active"] === true) {
|
|
79
|
+
// An active name with no generated feature class is
|
|
80
|
+
// legal when an extend-supplied instance carries that
|
|
81
|
+
// name (station's adopt path): the instance is added
|
|
82
|
+
// below, positioned by its own __after__ entry, so
|
|
83
|
+
// skip it here rather than add a BaseFeature stray
|
|
84
|
+
// that would silently shift feature positions.
|
|
85
|
+
if (!ProjectNameFeatures::has_feature($fname)) {
|
|
86
|
+
foreach ($extend_val as $ef) {
|
|
87
|
+
if (is_object($ef) && method_exists($ef, 'get_name')
|
|
88
|
+
&& $fname === $ef->get_name()) {
|
|
89
|
+
continue 2;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
73
93
|
($utility->feature_add)($this->_rootctx, ProjectNameFeatures::make_feature($fname));
|
|
74
94
|
}
|
|
75
95
|
}
|
|
@@ -77,12 +97,9 @@ class ProjectNameSDK
|
|
|
77
97
|
}
|
|
78
98
|
|
|
79
99
|
// Add extension features.
|
|
80
|
-
$extend_val
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (is_object($f) && method_exists($f, 'get_name')) {
|
|
84
|
-
($utility->feature_add)($this->_rootctx, $f);
|
|
85
|
-
}
|
|
100
|
+
foreach ($extend_val as $f) {
|
|
101
|
+
if (is_object($f) && method_exists($f, 'get_name')) {
|
|
102
|
+
($utility->feature_add)($this->_rootctx, $f);
|
|
86
103
|
}
|
|
87
104
|
}
|
|
88
105
|
|
|
@@ -67,8 +67,10 @@ const Config = cmp(async function Config(props: any) {
|
|
|
67
67
|
// The same config as an OBJECT, built by the shared helper so this target's
|
|
68
68
|
// literal and the data that replaces it above the threshold are the same
|
|
69
69
|
// config by construction. The JSON is what the threshold is measured on -
|
|
70
|
-
// emitted source size varies by language, the model does not.
|
|
71
|
-
|
|
70
|
+
// emitted source size varies by language, the model does not. Passing the
|
|
71
|
+
// target name opts in to the main.slug/version/target identity fields -
|
|
72
|
+
// the literal path below emits them too, keeping the two reps in step.
|
|
73
|
+
const { def: configDef, json: configJson } = configDefinition(model, target.name)
|
|
72
74
|
const asData = isConfigData(configJson, configReprSetting(model))
|
|
73
75
|
|
|
74
76
|
File({ name: 'config.' + target.ext }, () => {
|
|
@@ -130,6 +132,10 @@ def make_config():
|
|
|
130
132
|
return
|
|
131
133
|
}
|
|
132
134
|
|
|
135
|
+
// Identity values from configDefinition's def, not re-derived here, so
|
|
136
|
+
// the literal rep and the data rep cannot disagree on identity (the
|
|
137
|
+
// slug is CARRIED, never derived from the camel name - station's
|
|
138
|
+
// descriptor reads all three; see cmp/ts/Config_ts.ts #MainMeta).
|
|
133
139
|
Content(`def make_config():
|
|
134
140
|
"""Build a fresh, fully materialised config dict.
|
|
135
141
|
|
|
@@ -139,6 +145,9 @@ def make_config():
|
|
|
139
145
|
return {
|
|
140
146
|
"main": {
|
|
141
147
|
"name": "${model.const.Name}",
|
|
148
|
+
"slug": ${JSON.stringify(configDef.main.slug)},
|
|
149
|
+
"version": ${JSON.stringify(configDef.main.version)},
|
|
150
|
+
"target": ${JSON.stringify(configDef.main.target)},
|
|
142
151
|
},
|
|
143
152
|
"feature": {
|
|
144
153
|
`)
|
|
@@ -158,24 +158,34 @@ from ${model.const.Name.toLowerCase()}_sdk.feature.base_feature import ${model.c
|
|
|
158
158
|
|
|
159
159
|
Content(`
|
|
160
160
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
"base": lambda: ${model.const.Name}BaseFeature(),
|
|
161
|
+
_FEATURES = {
|
|
162
|
+
"base": lambda: ${model.const.Name}BaseFeature(),
|
|
164
163
|
`)
|
|
165
164
|
|
|
166
165
|
each(feature, (feat: any) => {
|
|
167
166
|
if (feat.name !== 'base') {
|
|
168
167
|
const fname = feat.name.charAt(0).toUpperCase() + feat.name.slice(1)
|
|
169
|
-
Content(`
|
|
168
|
+
Content(` "${feat.name}": lambda: ${model.const.Name}${fname}Feature(),
|
|
170
169
|
`)
|
|
171
170
|
}
|
|
172
171
|
})
|
|
173
172
|
|
|
174
|
-
Content(`
|
|
175
|
-
|
|
173
|
+
Content(`}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _make_feature(name):
|
|
177
|
+
factory = _FEATURES.get(name)
|
|
176
178
|
if factory is not None:
|
|
177
179
|
return factory()
|
|
178
|
-
return
|
|
180
|
+
return _FEATURES["base"]()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# True when this SDK was generated with the named feature class - the
|
|
184
|
+
# constructor's tolerance for extend-carried features reads this (an
|
|
185
|
+
# active name with no generated class must not become a BaseFeature
|
|
186
|
+
# stray when an extend instance carries it).
|
|
187
|
+
def _has_feature(name):
|
|
188
|
+
return name in _FEATURES
|
|
179
189
|
`)
|
|
180
190
|
})
|
|
181
191
|
|
|
@@ -10,7 +10,7 @@ from projectname_sdk.utility import register
|
|
|
10
10
|
|
|
11
11
|
# Load features
|
|
12
12
|
from projectname_sdk.feature.base_feature import ProjectNameBaseFeature
|
|
13
|
-
from projectname_sdk.features import _make_feature
|
|
13
|
+
from projectname_sdk.features import _has_feature, _make_feature
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class ProjectNameSDK:
|
|
@@ -46,21 +46,38 @@ class ProjectNameSDK:
|
|
|
46
46
|
# `test` feature installs the base mock transport and the transport
|
|
47
47
|
# features (retry/cache/netsim/proxy/ratelimit) wrap whatever is
|
|
48
48
|
# current, so `test` must be added before them to sit at the base.
|
|
49
|
+
# Extension feature INSTANCES come from the RAW construction
|
|
50
|
+
# options - extend is consumed exactly once, here. make_options
|
|
51
|
+
# strips the key before cloning (vs.clone flattens arbitrary
|
|
52
|
+
# objects), so self.options never carries the instances.
|
|
49
53
|
feature_opts = helpers.to_map(vs.getprop(self.options, "feature"))
|
|
54
|
+
extend = options.get("extend") if isinstance(options, dict) else None
|
|
55
|
+
if not isinstance(extend, list):
|
|
56
|
+
extend = []
|
|
50
57
|
if feature_opts is not None:
|
|
51
58
|
featureorder = vs.getpath(self.options, "__derived__.featureorder")
|
|
52
59
|
if isinstance(featureorder, list):
|
|
53
60
|
for fname in featureorder:
|
|
54
61
|
fopts = helpers.to_map(feature_opts.get(fname))
|
|
55
62
|
if fopts is not None and fopts.get("active") is True:
|
|
63
|
+
# An active name with no generated feature class is
|
|
64
|
+
# legal when an extend-supplied instance carries that
|
|
65
|
+
# name (station's adopt path): the instance is added
|
|
66
|
+
# below, positioned by its own __after__ entry, so
|
|
67
|
+
# skip it here rather than add a BaseFeature stray
|
|
68
|
+
# that would silently shift feature positions.
|
|
69
|
+
if not _has_feature(fname) and any(
|
|
70
|
+
fname == (f.get("name") if isinstance(f, dict)
|
|
71
|
+
else getattr(f, "name", None))
|
|
72
|
+
for f in extend
|
|
73
|
+
):
|
|
74
|
+
continue
|
|
56
75
|
utility.feature_add(self._rootctx, _make_feature(fname))
|
|
57
76
|
|
|
58
77
|
# Add extension features.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if isinstance(f, dict) or (hasattr(f, "get_name") and callable(f.get_name)):
|
|
63
|
-
utility.feature_add(self._rootctx, f)
|
|
78
|
+
for f in extend:
|
|
79
|
+
if isinstance(f, dict) or (hasattr(f, "get_name") and callable(f.get_name)):
|
|
80
|
+
utility.feature_add(self._rootctx, f)
|
|
64
81
|
|
|
65
82
|
# Initialize features.
|
|
66
83
|
for f in self.features:
|
|
@@ -71,7 +71,10 @@ const Config = cmp(async function Config(props: any) {
|
|
|
71
71
|
// literal and the data that replaces it above the threshold are the same
|
|
72
72
|
// config by construction. The JSON is what the threshold is measured on -
|
|
73
73
|
// emitted source size varies by language, the model does not.
|
|
74
|
-
|
|
74
|
+
// Passing the target name opts in to the main slug/version/target identity
|
|
75
|
+
// fields (station descriptor inputs) - the literal path below emits them
|
|
76
|
+
// too, keeping data and literal reps in step.
|
|
77
|
+
const { def: configDef, json: configJson } = configDefinition(model, target.name)
|
|
75
78
|
const asData = isConfigData(configJson, configReprSetting(model))
|
|
76
79
|
|
|
77
80
|
File({ name: 'config.' + target.ext }, () => {
|
|
@@ -127,6 +130,9 @@ module ${model.const.Name}Config
|
|
|
127
130
|
{
|
|
128
131
|
"main" => {
|
|
129
132
|
"name" => "${model.const.Name}",
|
|
133
|
+
"slug" => ${rbs(String(configDef.main.slug))},
|
|
134
|
+
"version" => ${rbs(String(configDef.main.version))},
|
|
135
|
+
"target" => ${rbs(String(configDef.main.target))},
|
|
130
136
|
},
|
|
131
137
|
"feature" => {
|
|
132
138
|
`)
|
|
@@ -50,8 +50,11 @@ const Config = cmp(async function Config(props: any) {
|
|
|
50
50
|
// representations render from the same `def`, so they cannot describe
|
|
51
51
|
// different configs - and this target picks up `options.server` (the
|
|
52
52
|
// OpenAPI server-variable defaults), which the hand-rolled build here
|
|
53
|
-
// omitted entirely.
|
|
54
|
-
|
|
53
|
+
// omitted entirely. Passing target.name opts into the main
|
|
54
|
+
// slug/version/target identity fields (station descriptor input, mirrors
|
|
55
|
+
// Config_ts) - both reps below render from this same def, so the data
|
|
56
|
+
// and literal branches pick the fields up together.
|
|
57
|
+
const { def: config, json: configJson } = configDefinition(model, target.name)
|
|
55
58
|
const asData = isConfigData(configJson, configReprSetting(model))
|
|
56
59
|
|
|
57
60
|
File({ name: 'config.' + target.ext }, () => {
|
|
@@ -3,9 +3,8 @@ import {
|
|
|
3
3
|
Content,
|
|
4
4
|
File,
|
|
5
5
|
cmp,
|
|
6
|
+
configDefinition,
|
|
6
7
|
each,
|
|
7
|
-
isAuthActive,
|
|
8
|
-
resolveAuthPrefix,
|
|
9
8
|
} from '@voxgig/sdkgen'
|
|
10
9
|
|
|
11
10
|
|
|
@@ -16,11 +15,6 @@ import {
|
|
|
16
15
|
} from '@voxgig/apidef'
|
|
17
16
|
|
|
18
17
|
|
|
19
|
-
import {
|
|
20
|
-
clean,
|
|
21
|
-
} from './utility_swift'
|
|
22
|
-
|
|
23
|
-
|
|
24
18
|
// Generates core/Config.swift: the SdkConfig enum holding the generated model
|
|
25
19
|
// config (makeConfig, materialised at runtime by parsing an embedded JSON
|
|
26
20
|
// literal - pure data, so a JSON round-trip is faithful and avoids emitting
|
|
@@ -32,59 +26,19 @@ const Config = cmp(async function Config(props: any) {
|
|
|
32
26
|
|
|
33
27
|
const model: Model = ctx$.model
|
|
34
28
|
|
|
35
|
-
const entity = getModelPath(model, `main.${KIT}.entity`)
|
|
36
29
|
const feature = getModelPath(model, `main.${KIT}.feature`)
|
|
37
30
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
try { baseUrl = getModelPath(model, `main.${KIT}.info.servers.0.url`) } catch (_e) { }
|
|
45
|
-
|
|
46
|
-
// Assemble the config object (pure JSON data).
|
|
47
|
-
const featureConfigs: any = {}
|
|
48
|
-
each(feature, (f: any) => {
|
|
49
|
-
featureConfigs[f.name] = f.config || {}
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
const optionsEntity: any = {}
|
|
53
|
-
each(entity, (ent: any) => {
|
|
54
|
-
optionsEntity[ent.name] = {}
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
const entityDefs: any = {}
|
|
58
|
-
each(entity, (ent: any) => {
|
|
59
|
-
entityDefs[ent.name] = clean({
|
|
60
|
-
fields: ent.fields,
|
|
61
|
-
name: ent.name,
|
|
62
|
-
op: ent.op,
|
|
63
|
-
relations: ent.relations,
|
|
64
|
-
}, true)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
const options: any = {
|
|
68
|
-
base: baseUrl,
|
|
69
|
-
headers,
|
|
70
|
-
entity: optionsEntity,
|
|
71
|
-
}
|
|
72
|
-
if (authActive) {
|
|
73
|
-
options.auth = { prefix: authPrefix }
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const configObj = {
|
|
77
|
-
main: { name: model.const.Name },
|
|
78
|
-
feature: featureConfigs,
|
|
79
|
-
options,
|
|
80
|
-
entity: entityDefs,
|
|
81
|
-
}
|
|
31
|
+
// The config as data, built by the shared helper so every target embeds
|
|
32
|
+
// the same model by construction. Passing target.name opts this target
|
|
33
|
+
// into main.slug / main.version / main.target (the three station
|
|
34
|
+
// descriptor identity fields, station design §4); swift has only the
|
|
35
|
+
// JSON-literal rep, so that one call covers every rep this target emits.
|
|
36
|
+
const { json } = configDefinition(model, target.name)
|
|
82
37
|
|
|
83
38
|
// Model-data defaults may carry the ProjectName placeholder (e.g. the
|
|
84
39
|
// clienttrack clientName); resolve it to the API name so the embedded JSON
|
|
85
40
|
// matches the token-replaced runtime.
|
|
86
|
-
const configJson =
|
|
87
|
-
.replace(/ProjectName/g, model.const.Name)
|
|
41
|
+
const configJson = json.replace(/ProjectName/g, model.const.Name)
|
|
88
42
|
|
|
89
43
|
File({ name: 'Config.' + target.ext }, () => {
|
|
90
44
|
|
|
@@ -17,8 +17,30 @@ import type {
|
|
|
17
17
|
// under Sources/ProjectNameSDK (the copied runtime + generated sources); the
|
|
18
18
|
// test target compiles Tests/ProjectNameSDKTests. Directory names are the
|
|
19
19
|
// verbatim copied paths (Copy does not rewrite path components); the target
|
|
20
|
-
// NAMES carry the API name.
|
|
21
|
-
//
|
|
20
|
+
// NAMES carry the API name.
|
|
21
|
+
//
|
|
22
|
+
// Dependencies: the runtime itself is dependency-free (Foundation + the
|
|
23
|
+
// vendored struct), but declared target/feature deps flow into the manifest
|
|
24
|
+
// via collectDeps. SwiftPM cannot name a package by product alone, so a
|
|
25
|
+
// swift deps entry carries its coordinates in a documented convention:
|
|
26
|
+
//
|
|
27
|
+
// deps: swift: {
|
|
28
|
+
// 'VoxgigStation': { active: true, version: '0.0.1', kind: prod,
|
|
29
|
+
// url: 'https://github.com/voxgig/station-swift' }
|
|
30
|
+
// }
|
|
31
|
+
//
|
|
32
|
+
// - the entry KEY is the SwiftPM PRODUCT name (also the module the
|
|
33
|
+
// generated source imports);
|
|
34
|
+
// - the extra `url` field is the git repository SwiftPM resolves
|
|
35
|
+
// (`.package(url:from:)`); the package identity SwiftPM derives from it
|
|
36
|
+
// is the URL's last path component (minus any .git), which is what the
|
|
37
|
+
// `.product(name:package:)` reference must use;
|
|
38
|
+
// - `version` feeds `from:` - a plain semver (any leading range operator
|
|
39
|
+
// like '>=' is stripped, since `from:` already means >=).
|
|
40
|
+
//
|
|
41
|
+
// An entry WITHOUT a `url` is not expressible in a SwiftPM manifest (no
|
|
42
|
+
// registry-less by-name dependencies), so it is skipped - unadorned entries
|
|
43
|
+
// keep today's zero-dependency output.
|
|
22
44
|
const Package = cmp(async function Package(props: any) {
|
|
23
45
|
const ctx$ = props.ctx$
|
|
24
46
|
const target = props.target
|
|
@@ -27,28 +49,48 @@ const Package = cmp(async function Package(props: any) {
|
|
|
27
49
|
|
|
28
50
|
const Name = model.const.Name
|
|
29
51
|
|
|
30
|
-
|
|
52
|
+
type SwiftDep = { product: string, url: string, from: string, identity: string }
|
|
53
|
+
const deps: SwiftDep[] = []
|
|
31
54
|
for (const d of collectDeps(model, target.name, target.deps, ctx$.log)) {
|
|
32
|
-
|
|
55
|
+
const url = 'string' === typeof (d.raw as any)?.url ? (d.raw as any).url : ''
|
|
56
|
+
if ('' === url) {
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
const from = (d.version || '0.0.0').replace(/^[^0-9]+/, '')
|
|
60
|
+
const identity = (url.split('/').pop() || '').replace(/\.git$/, '')
|
|
61
|
+
deps.push({ product: d.name, url, from, identity })
|
|
33
62
|
}
|
|
34
|
-
|
|
35
|
-
|
|
63
|
+
|
|
64
|
+
const pkgdeps = 0 === deps.length ? '' :
|
|
65
|
+
'\n dependencies: [\n' +
|
|
66
|
+
deps.map((d) =>
|
|
67
|
+
` .package(url: "${d.url}", from: "${d.from}"),\n`).join('') +
|
|
68
|
+
' ],'
|
|
69
|
+
|
|
70
|
+
const targetdeps = 0 === deps.length ? '' :
|
|
71
|
+
'\n dependencies: [\n' +
|
|
72
|
+
deps.map((d) =>
|
|
73
|
+
` .product(name: "${d.product}", package: "${d.identity}"),\n`)
|
|
74
|
+
.join('') +
|
|
75
|
+
' ],'
|
|
36
76
|
|
|
37
77
|
File({ name: 'Package.swift' }, () => {
|
|
38
78
|
Content(`// swift-tools-version:5.9
|
|
39
79
|
//
|
|
40
|
-
// ${Name} SDK - SwiftPM manifest.
|
|
41
|
-
// the vendored Voxgig Struct port under
|
|
80
|
+
// ${Name} SDK - SwiftPM manifest. The runtime itself is dependency-free
|
|
81
|
+
// (Foundation + the vendored Voxgig Struct port under
|
|
82
|
+
// Sources/ProjectNameSDK/Struct); declared feature/target deps (if any)
|
|
83
|
+
// appear below.
|
|
42
84
|
import PackageDescription
|
|
43
85
|
|
|
44
86
|
let package = Package(
|
|
45
87
|
name: "${Name}Sdk",
|
|
46
88
|
products: [
|
|
47
89
|
.library(name: "${Name}Sdk", targets: ["${Name}Sdk"]),
|
|
48
|
-
]
|
|
90
|
+
],${pkgdeps}
|
|
49
91
|
targets: [
|
|
50
92
|
.target(
|
|
51
|
-
name: "${Name}Sdk"
|
|
93
|
+
name: "${Name}Sdk",${targetdeps}
|
|
52
94
|
path: "Sources/ProjectNameSDK"),
|
|
53
95
|
.testTarget(
|
|
54
96
|
name: "${Name}SdkTests",
|
|
@@ -81,7 +81,7 @@ const Config = cmp(async function Config(props: any) {
|
|
|
81
81
|
// literal and the data that replaces it above the threshold are the same
|
|
82
82
|
// config by construction. The JSON is what the threshold is measured on -
|
|
83
83
|
// emitted source size varies by language, the model does not.
|
|
84
|
-
const { json: configJson } = configDefinition(model)
|
|
84
|
+
const { def: configDef, json: configJson } = configDefinition(model, target.name)
|
|
85
85
|
const asData = isConfigData(configJson, configReprSetting(model))
|
|
86
86
|
|
|
87
87
|
File({ name: 'Config.' + target.ext }, () => {
|
|
@@ -128,6 +128,14 @@ const Config = cmp(async function Config(props: any) {
|
|
|
128
128
|
`'./feature/${f.name}/${nom(f, 'Name')}Feature'`)
|
|
129
129
|
}),
|
|
130
130
|
|
|
131
|
+
// Values from configDefinition's def, not re-derived here, so the
|
|
132
|
+
// literal rep and the data rep cannot disagree on identity.
|
|
133
|
+
'// #MainMeta': () => {
|
|
134
|
+
Line(` slug: ${JSON.stringify(configDef.main.slug)},`)
|
|
135
|
+
Line(` version: ${JSON.stringify(configDef.main.version)},`)
|
|
136
|
+
Line(` target: ${JSON.stringify(configDef.main.target)},`)
|
|
137
|
+
},
|
|
138
|
+
|
|
131
139
|
'// #FeatureClasses': () => each(feature, (f: any) => {
|
|
132
140
|
// Trailing comma: the map has one entry per feature, so entries
|
|
133
141
|
// must be comma-separated (a single feature hid this until now).
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
File, Content,
|
|
22
22
|
} from '@voxgig/sdkgen'
|
|
23
23
|
|
|
24
|
-
import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions , deriveEntityNames, opActions } from '@voxgig/sdkgen'
|
|
24
|
+
import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions , deriveEntityNames, opActions, tsSafeTypeName } from '@voxgig/sdkgen'
|
|
25
25
|
|
|
26
26
|
import {
|
|
27
27
|
KIT,
|
|
@@ -74,11 +74,14 @@ const EntityTypes = cmp(function EntityTypes(props: any) {
|
|
|
74
74
|
|
|
75
75
|
entityList.forEach((ent: any) => {
|
|
76
76
|
const Name = ent.Name
|
|
77
|
+
// A TS/JS global (Record, Array, Promise, ...) would shadow itself
|
|
78
|
+
// for the rest of the file — see tsSafeTypeName.
|
|
79
|
+
const TypeName = tsSafeTypeName(Name)
|
|
77
80
|
const fields = (ent.fields ? each(ent.fields) : [])
|
|
78
81
|
.filter((f: any) => f.active !== false)
|
|
79
82
|
|
|
80
83
|
// Entity data model: one property per field, `req:false` -> optional.
|
|
81
|
-
Content(`export interface ${
|
|
84
|
+
Content(`export interface ${TypeName} {
|
|
82
85
|
`)
|
|
83
86
|
fields.forEach((f: any) => {
|
|
84
87
|
const opt = false === f.req ? '?' : ''
|