@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.
Files changed (100) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/AgentGuideTop.js +17 -0
  3. package/dist/cmp/AgentGuideTop.js.map +1 -1
  4. package/dist/cmp/Readme.js +2 -0
  5. package/dist/cmp/Readme.js.map +1 -1
  6. package/dist/cmp/ReadmeStation.d.ts +2 -0
  7. package/dist/cmp/ReadmeStation.js +57 -0
  8. package/dist/cmp/ReadmeStation.js.map +1 -0
  9. package/dist/helpers/naming.d.ts +3 -1
  10. package/dist/helpers/naming.js +39 -2
  11. package/dist/helpers/naming.js.map +1 -1
  12. package/dist/sdkgen.d.ts +2 -2
  13. package/dist/sdkgen.js +4 -2
  14. package/dist/sdkgen.js.map +1 -1
  15. package/dist/tsconfig.tsbuildinfo +1 -1
  16. package/dist/utility.d.ts +1 -1
  17. package/dist/utility.js +16 -2
  18. package/dist/utility.js.map +1 -1
  19. package/package.json +1 -1
  20. package/project/.sdk/src/cmp/c/Config_c.ts +34 -13
  21. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +9 -46
  22. package/project/.sdk/src/cmp/csharp/Config_csharp.ts +7 -1
  23. package/project/.sdk/src/cmp/dart/Config_dart.ts +14 -2
  24. package/project/.sdk/src/cmp/dart/fragment/Config.data.fragment.dart +5 -0
  25. package/project/.sdk/src/cmp/dart/fragment/Config.fragment.dart +6 -0
  26. package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +13 -4
  27. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +12 -2
  28. package/project/.sdk/src/cmp/elixir/Package_elixir.ts +41 -2
  29. package/project/.sdk/src/cmp/go/Config_go.ts +8 -1
  30. package/project/.sdk/src/cmp/java/Config_java.ts +8 -1
  31. package/project/.sdk/src/cmp/js/Config_js.ts +9 -1
  32. package/project/.sdk/src/cmp/js/fragment/Config.data.fragment.js +7 -0
  33. package/project/.sdk/src/cmp/js/fragment/Config.fragment.js +8 -0
  34. package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +33 -5
  35. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +8 -1
  36. package/project/.sdk/src/cmp/lua/Config_lua.ts +11 -2
  37. package/project/.sdk/src/cmp/lua/Package_lua.ts +24 -1
  38. package/project/.sdk/src/cmp/perl/Config_perl.ts +8 -60
  39. package/project/.sdk/src/cmp/php/Config_php.ts +11 -2
  40. package/project/.sdk/src/cmp/php/Main_php.ts +25 -0
  41. package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +23 -6
  42. package/project/.sdk/src/cmp/py/Config_py.ts +11 -2
  43. package/project/.sdk/src/cmp/py/Main_py.ts +17 -7
  44. package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +23 -6
  45. package/project/.sdk/src/cmp/rb/Config_rb.ts +7 -1
  46. package/project/.sdk/src/cmp/rust/Config_rust.ts +5 -2
  47. package/project/.sdk/src/cmp/swift/Config_swift.ts +8 -54
  48. package/project/.sdk/src/cmp/swift/Package_swift.ts +52 -10
  49. package/project/.sdk/src/cmp/ts/Config_ts.ts +9 -1
  50. package/project/.sdk/src/cmp/ts/EntityTypes_ts.ts +5 -2
  51. package/project/.sdk/src/cmp/ts/Entity_ts.ts +8 -2
  52. package/project/.sdk/src/cmp/ts/fragment/Config.data.fragment.ts +7 -0
  53. package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +8 -0
  54. package/project/.sdk/src/cmp/ts/fragment/Entity.fragment.ts +1 -1
  55. package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +12 -4
  56. package/project/.sdk/tm/c/Makefile +6 -1
  57. package/project/.sdk/tm/c/utility/make_options.c +12 -1
  58. package/project/.sdk/tm/cpp/utility/pipeline.hpp +9 -1
  59. package/project/.sdk/tm/csharp/utility/Fetcher.cs +23 -5
  60. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +13 -0
  61. package/project/.sdk/tm/dart/lib/utility/FetcherUtility.dart +9 -0
  62. package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +17 -0
  63. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +39 -3
  64. package/project/.sdk/tm/elixir/test/feature_test.exs +19 -0
  65. package/project/.sdk/tm/elixir/test/primary_utility_test.exs +72 -0
  66. package/project/.sdk/tm/go/test/primary_utility_test.go +42 -20
  67. package/project/.sdk/tm/go/test/runner_test.go +1 -1
  68. package/project/.sdk/tm/go/utility/fetcher.go +14 -0
  69. package/project/.sdk/tm/go/utility/make_options.go +31 -4
  70. package/project/.sdk/tm/java/utility/Fetcher.java +11 -1
  71. package/project/.sdk/tm/java/utility/MakeOptions.java +11 -0
  72. package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +20 -2
  73. package/project/.sdk/tm/kotlin/utility/Fetcher.kt +10 -1
  74. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +11 -0
  75. package/project/.sdk/tm/lean/test/TPrimaryUtility.lean +13 -2
  76. package/project/.sdk/tm/lua/utility/make_options.lua +44 -0
  77. package/project/.sdk/tm/perl/utility/fetcher.pm +10 -1
  78. package/project/.sdk/tm/perl/utility/make_options.pm +36 -0
  79. package/project/.sdk/tm/php/test/PipelineTest.php +20 -0
  80. package/project/.sdk/tm/php/utility/Fetcher.php +12 -1
  81. package/project/.sdk/tm/php/utility/MakeOptions.php +58 -0
  82. package/project/.sdk/tm/py/pkg/utility/fetcher.py +13 -1
  83. package/project/.sdk/tm/py/pkg/utility/make_options.py +23 -0
  84. package/project/.sdk/tm/rb/utility/make_options.rb +18 -0
  85. package/project/.sdk/tm/rust/tests/common/mod.rs +1 -1
  86. package/project/.sdk/tm/rust/tests/feature_test.rs +27 -0
  87. package/project/.sdk/tm/rust/utility/fetcher.rs +8 -0
  88. package/project/.sdk/tm/rust/utility/make_options.rs +15 -0
  89. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Fetcher.swift +24 -1
  90. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +12 -0
  91. package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +20 -2
  92. package/project/.sdk/tm/ts/test/utility/Corpus.test.ts +85 -0
  93. package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +10 -17
  94. package/project/sdkgen-package.json +1 -1
  95. package/src/cmp/AgentGuideTop.ts +18 -0
  96. package/src/cmp/Readme.ts +2 -0
  97. package/src/cmp/ReadmeStation.ts +69 -0
  98. package/src/helpers/naming.ts +45 -2
  99. package/src/sdkgen.ts +3 -1
  100. 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
- each,
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
- const entity = getModelPath(model, `main.${KIT}.entity`)
36
- const feature = getModelPath(model, `main.${KIT}.feature`)
37
-
38
- const headers = getModelPath(model, `main.${KIT}.config.headers`) || {}
39
-
40
- const authActive = isAuthActive(model)
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 options: any = {
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
- const { json: configJson } = configDefinition(model)
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 = Struct::getprop($this->options, "extend");
81
- if (is_array($extend_val)) {
82
- foreach ($extend_val as $f) {
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
- const { json: configJson } = configDefinition(model)
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
- def _make_feature(name):
162
- features = {
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(` "${feat.name}": lambda: ${model.const.Name}${fname}Feature(),
168
+ Content(` "${feat.name}": lambda: ${model.const.Name}${fname}Feature(),
170
169
  `)
171
170
  }
172
171
  })
173
172
 
174
- Content(` }
175
- factory = features.get(name)
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 features["base"]()
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
- extend = vs.getprop(self.options, "extend")
60
- if isinstance(extend, list):
61
- for f in extend:
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
- const { json: configJson } = configDefinition(model)
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
- const { def: config, json: configJson } = configDefinition(model)
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
- const headers = getModelPath(model, `main.${KIT}.config.headers`) || {}
39
-
40
- const authActive = isAuthActive(model)
41
- const authPrefix = resolveAuthPrefix(model)
42
-
43
- let baseUrl = ''
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 = JSON.stringify(configObj, null, 2)
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. Runtime is dependency-free (Foundation + the
21
- // vendored struct); target/feature deps, when declared, become SwiftPM deps.
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
- const deps: Record<string, string> = {}
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
- deps[d.name] = d.source === 'target' ? (d.version || '0.0.0') : d.version
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
- // Vendored, dependency-free by design; declared deps (if any) are ignored
35
- // here since the runtime imports only Foundation + the in-tree struct.
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. Zero runtime dependencies (Foundation +
41
- // the vendored Voxgig Struct port under Sources/ProjectNameSDK/Struct).
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 ${Name} {
84
+ Content(`export interface ${TypeName} {
82
85
  `)
83
86
  fields.forEach((f: any) => {
84
87
  const opt = false === f.req ? '?' : ''