@voxgig/sdkgen 0.40.1 → 0.41.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 (47) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/helpers/buildIdNames.d.ts +11 -0
  3. package/dist/helpers/buildIdNames.js +56 -0
  4. package/dist/helpers/buildIdNames.js.map +1 -0
  5. package/dist/helpers/collectDeps.d.ts +9 -0
  6. package/dist/helpers/collectDeps.js +50 -0
  7. package/dist/helpers/collectDeps.js.map +1 -0
  8. package/dist/helpers/getMatchEntries.d.ts +2 -0
  9. package/dist/helpers/getMatchEntries.js +13 -0
  10. package/dist/helpers/getMatchEntries.js.map +1 -0
  11. package/dist/sdkgen.d.ts +6 -2
  12. package/dist/sdkgen.js +7 -1
  13. package/dist/sdkgen.js.map +1 -1
  14. package/dist/tsconfig.tsbuildinfo +1 -1
  15. package/package.json +1 -1
  16. package/project/.sdk/model/target/lua.jsonic +1 -1
  17. package/project/.sdk/model/target/php.jsonic +2 -3
  18. package/project/.sdk/model/target/py.jsonic +1 -1
  19. package/project/.sdk/src/cmp/go/Entity_go.ts +15 -3
  20. package/project/.sdk/src/cmp/go/Main_go.ts +3 -2
  21. package/project/.sdk/src/cmp/go/Package_go.ts +8 -29
  22. package/project/.sdk/src/cmp/go/TestDirect_go.ts +14 -7
  23. package/project/.sdk/src/cmp/go/TestEntity_go.ts +43 -30
  24. package/project/.sdk/src/cmp/js/TestDirect_js.ts +6 -3
  25. package/project/.sdk/src/cmp/lua/Package_lua.ts +5 -27
  26. package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +6 -3
  27. package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +5 -17
  28. package/project/.sdk/src/cmp/php/Config_php.ts +21 -0
  29. package/project/.sdk/src/cmp/php/MainEntity_php.ts +11 -1
  30. package/project/.sdk/src/cmp/php/Package_php.ts +5 -31
  31. package/project/.sdk/src/cmp/php/TestDirect_php.ts +6 -3
  32. package/project/.sdk/src/cmp/php/TestEntity_php.ts +25 -29
  33. package/project/.sdk/src/cmp/py/Main_py.ts +5 -5
  34. package/project/.sdk/src/cmp/py/Package_py.ts +11 -28
  35. package/project/.sdk/src/cmp/py/TestDirect_py.ts +9 -6
  36. package/project/.sdk/src/cmp/py/TestEntity_py.ts +7 -19
  37. package/project/.sdk/src/cmp/rb/Package_rb.ts +8 -47
  38. package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +6 -3
  39. package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +5 -17
  40. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +6 -3
  41. package/project/.sdk/tm/go/core/helpers.go +10 -0
  42. package/project/.sdk/tm/lua/Makefile +1 -1
  43. package/project/.sdk/tm/php/Makefile +1 -1
  44. package/src/helpers/buildIdNames.ts +70 -0
  45. package/src/helpers/collectDeps.ts +70 -0
  46. package/src/helpers/getMatchEntries.ts +14 -0
  47. package/src/sdkgen.ts +10 -0
@@ -302,10 +302,13 @@ function normalizePathParams(
302
302
  return part.replace(/\{([^}]+)\}/g, (match: string, rawName: string) => {
303
303
  const snaked = snakify(rawName)
304
304
  const depluralized = depluralize(snaked)
305
+ // Prefer exact name match — orig matches can collide when one param's
306
+ // original name was renamed to another param's current name (e.g. badge
307
+ // load: param 'group_id' has orig 'id', and another param has name 'id').
305
308
  const param = params.find((p: any) =>
306
- p.orig === snaked || p.name === snaked ||
307
- p.orig === depluralized || p.name === depluralized
308
- )
309
+ p.name === snaked || p.name === depluralized) ||
310
+ params.find((p: any) =>
311
+ p.orig === snaked || p.orig === depluralized)
309
312
  if (param) return '{' + param.name + '}'
310
313
 
311
314
  // Reverse-lookup through rename mapping: if rawName is a renamed value
@@ -3,14 +3,12 @@ import {
3
3
  Content,
4
4
  File,
5
5
  cmp,
6
- each,
6
+ collectDeps,
7
7
  } from '@voxgig/sdkgen'
8
8
 
9
9
 
10
- import {
11
- KIT,
10
+ import type {
12
11
  Model,
13
- getModelPath,
14
12
  } from '@voxgig/apidef'
15
13
 
16
14
 
@@ -20,8 +18,6 @@ const Package = cmp(async function Package(props: any) {
20
18
 
21
19
  const model: Model = ctx$.model
22
20
 
23
- const feature = getModelPath(model, `main.${KIT}.feature`)
24
-
25
21
  File({ name: model.name + '.rockspec' }, () => {
26
22
  Content(`package = "${model.name}-sdk"
27
23
  version = "0.0-1"
@@ -37,28 +33,10 @@ dependencies = {
37
33
  "dkjson >= 2.5",
38
34
  `)
39
35
 
40
- // Collect dependencies from features
41
- each(feature, (f: any) => {
42
- const luaDeps = f.deps?.lua
43
- if (luaDeps) {
44
- each(luaDeps, (dep: any) => {
45
- if (dep.active) {
46
- Content(` "${dep.key$} >= ${dep.version}",
47
- `)
48
- }
49
- })
50
- }
51
- })
52
-
53
- // Add target-level deps
54
- const targetDeps = target.deps
55
- if (targetDeps) {
56
- each(targetDeps, (dep: any) => {
57
- if (dep.active !== false) {
58
- Content(` "${dep.key$} >= ${dep.version || '0.0'}",
36
+ for (const d of collectDeps(model, target.name, target.deps)) {
37
+ const v = d.source === 'target' ? (d.version || '0.0') : d.version
38
+ Content(` "${d.name} >= ${v}",
59
39
  `)
60
- }
61
- })
62
40
  }
63
41
 
64
42
  Content(`}
@@ -20,10 +20,13 @@ function normalizePathParams(
20
20
  return part.replace(/\{([^}]+)\}/g, (match: string, rawName: string) => {
21
21
  const snaked = snakify(rawName)
22
22
  const depluralized = depluralize(snaked)
23
+ // Prefer exact name match — orig matches can collide when one param's
24
+ // original name was renamed to another param's current name (e.g. badge
25
+ // load: param 'group_id' has orig 'id', and another param has name 'id').
23
26
  const param = params.find((p: any) =>
24
- p.orig === snaked || p.name === snaked ||
25
- p.orig === depluralized || p.name === depluralized
26
- )
27
+ p.name === snaked || p.name === depluralized) ||
28
+ params.find((p: any) =>
29
+ p.orig === snaked || p.orig === depluralized)
27
30
  if (param) return '{' + param.name + '}'
28
31
 
29
32
  if (rename) {
@@ -16,6 +16,8 @@ import {
16
16
  File,
17
17
  cmp,
18
18
  each,
19
+ buildIdNames,
20
+ getMatchEntries,
19
21
  } from '@voxgig/sdkgen'
20
22
 
21
23
 
@@ -45,15 +47,7 @@ const TestEntity = cmp(function TestEntity(props: any) {
45
47
 
46
48
  const PROJUPPER = model.const.Name.toUpperCase().replace(/[^A-Z_]/g, '_')
47
49
 
48
- const ancestors = (entity.relations?.ancestors || []).flat()
49
-
50
- // Build idmap names
51
- const idnames: string[] = []
52
- for (let i = 1; i <= 3; i++) idnames.push(`${entity.name}0${i}`)
53
- for (const anc of ancestors) {
54
- for (let i = 1; i <= 3; i++) idnames.push(`${anc}0${i}`)
55
- }
56
-
50
+ const idnames = buildIdNames(entity, basicflow)
57
51
  const idnamesStr = idnames.map(n => `"${n}"`).join(', ')
58
52
 
59
53
  const allSteps = Object.values(basicflow.step) as any[]
@@ -95,7 +89,7 @@ describe("${entity.Name}Entity", function()
95
89
  if (!flowHasCreate) {
96
90
  Content(` -- Bootstrap entity data from existing test data.
97
91
  local ${entity.name}_ref01_data_raw = vs.items(helpers.to_map(
98
- vs.getprop(setup.data, "existing.${entity.name}")))
92
+ vs.getpath(setup.data, "existing.${entity.name}")))
99
93
  local ${entity.name}_ref01_data = nil
100
94
  if #${entity.name}_ref01_data_raw > 0 then
101
95
  ${entity.name}_ref01_data = helpers.to_map(${entity.name}_ref01_data_raw[1][2])
@@ -200,12 +194,6 @@ end
200
194
  })
201
195
 
202
196
 
203
- function getMatchEntries(step: any): [string, any][] {
204
- if (!step?.match) return []
205
- return Object.entries(step.match).filter(([k]: any) => !k.endsWith('$'))
206
- }
207
-
208
-
209
197
  const generateCreate: OpGen = (ctx, step, index) => {
210
198
  const { entity, flow } = ctx
211
199
  const ref = step.input?.ref ?? entity.name + '_ref01'
@@ -426,7 +414,7 @@ const generateLoad: OpGen = (ctx, step, index) => {
426
414
  }
427
415
  if (!hasSrcData) {
428
416
  Content(` local ${srcdatavar}_raw = vs.items(helpers.to_map(
429
- vs.getprop(setup.data, "existing.${entity.name}")))
417
+ vs.getpath(setup.data, "existing.${entity.name}")))
430
418
  local ${srcdatavar} = nil
431
419
  if #${srcdatavar}_raw > 0 then
432
420
  ${srcdatavar} = helpers.to_map(${srcdatavar}_raw[1][2])
@@ -67,6 +67,24 @@ class ${model.const.Name}Config
67
67
  `)
68
68
  })
69
69
 
70
+ // PHP can't distinguish empty list from empty map; the SDK runtime
71
+ // validator wants an object for `entity` and `feature.test.entity`. Use
72
+ // `(object)[]` when the map is empty so the merge preserves map shape.
73
+ const entityIsEmpty = Object.keys(entity || {}).length === 0
74
+ if (entityIsEmpty) {
75
+ Content(` ],
76
+ "options" => [
77
+ "base" => "${baseUrl}",
78
+ "auth" => [
79
+ "prefix" => "${authPrefix}",
80
+ ],
81
+ "headers" => ${formatPhpArray(headers, 4)},
82
+ "entity" => (object)[],
83
+ ],
84
+ "entity" => (object)[],
85
+ ];
86
+ `)
87
+ } else {
70
88
  Content(` ],
71
89
  "options" => [
72
90
  "base" => "${baseUrl}",
@@ -92,8 +110,11 @@ class ${model.const.Name}Config
92
110
  relations: n.relations,
93
111
  }), a), {}), 3)},
94
112
  ];
113
+ `)
95
114
  }
96
115
 
116
+ Content(` }
117
+
97
118
 
98
119
  public static function make_feature(string $name)
99
120
  {
@@ -3,12 +3,22 @@
3
3
  import { cmp, Content } from '@voxgig/sdkgen'
4
4
 
5
5
 
6
+ // Reserved PHP method names on the SDK class that an entity accessor must
7
+ // not collide with. PHP method names are case-insensitive at declaration
8
+ // time, so an entity literally named 'test' would collide with the static
9
+ // `test()` test-mode constructor. Mangle to `<Name>_` in that case.
10
+ const PHP_RESERVED_LOWER = new Set(['test'])
11
+
6
12
  const MainEntity = cmp(async function MainEntity(props: any) {
7
13
  const { entity } = props
8
14
  const { model } = props.ctx$
9
15
 
16
+ const accessor = PHP_RESERVED_LOWER.has(entity.Name.toLowerCase())
17
+ ? entity.Name + '_'
18
+ : entity.Name
19
+
10
20
  Content(`
11
- public function ${entity.Name}($data = null)
21
+ public function ${accessor}($data = null)
12
22
  {
13
23
  require_once __DIR__ . '/entity/${entity.name}_entity.php';
14
24
  return new ${entity.Name}Entity($this, $data);
@@ -3,14 +3,12 @@ import {
3
3
  Content,
4
4
  File,
5
5
  cmp,
6
- each,
6
+ collectDeps,
7
7
  } from '@voxgig/sdkgen'
8
8
 
9
9
 
10
- import {
11
- KIT,
10
+ import type {
12
11
  Model,
13
- getModelPath,
14
12
  } from '@voxgig/apidef'
15
13
 
16
14
 
@@ -20,8 +18,6 @@ const Package = cmp(async function Package(props: any) {
20
18
 
21
19
  const model: Model = ctx$.model
22
20
 
23
- const feature = getModelPath(model, `main.${KIT}.feature`)
24
-
25
21
  // Generate composer.json
26
22
  File({ name: 'composer.json' }, () => {
27
23
  Content(`{
@@ -33,32 +29,10 @@ const Package = cmp(async function Package(props: any) {
33
29
  "require": {
34
30
  "php": ">=8.2"`)
35
31
 
36
- // Collect dependencies from features
37
- const deps: { name: string, version: string }[] = []
38
- each(feature, (f: any) => {
39
- const phpDeps = f.deps?.php
40
- if (phpDeps) {
41
- each(phpDeps, (dep: any) => {
42
- if (dep.active) {
43
- deps.push({ name: dep.key$, version: dep.version })
44
- }
45
- })
46
- }
47
- })
48
-
49
- // Add target-level deps
50
- const targetDeps = target.deps
51
- if (targetDeps) {
52
- each(targetDeps, (dep: any) => {
53
- if (dep.active !== false) {
54
- deps.push({ name: dep.key$, version: dep.version || '0.0' })
55
- }
56
- })
57
- }
58
-
59
- for (const dep of deps) {
32
+ for (const d of collectDeps(model, target.name, target.deps)) {
33
+ const v = d.source === 'target' ? (d.version || '0.0') : d.version
60
34
  Content(`,
61
- "${dep.name}": "^${dep.version}"`)
35
+ "${d.name}": "^${v}"`)
62
36
  }
63
37
 
64
38
  Content(`
@@ -20,10 +20,13 @@ function normalizePathParams(
20
20
  return part.replace(/\{([^}]+)\}/g, (match: string, rawName: string) => {
21
21
  const snaked = snakify(rawName)
22
22
  const depluralized = depluralize(snaked)
23
+ // Prefer exact name match — orig matches can collide when one param's
24
+ // original name was renamed to another param's current name (e.g. badge
25
+ // load: param 'group_id' has orig 'id', and another param has name 'id').
23
26
  const param = params.find((p: any) =>
24
- p.orig === snaked || p.name === snaked ||
25
- p.orig === depluralized || p.name === depluralized
26
- )
27
+ p.name === snaked || p.name === depluralized) ||
28
+ params.find((p: any) =>
29
+ p.orig === snaked || p.orig === depluralized)
27
30
  if (param) return '{' + param.name + '}'
28
31
 
29
32
  if (rename) {
@@ -16,6 +16,8 @@ import {
16
16
  File,
17
17
  cmp,
18
18
  each,
19
+ buildIdNames,
20
+ getMatchEntries,
19
21
  } from '@voxgig/sdkgen'
20
22
 
21
23
 
@@ -26,6 +28,7 @@ type GenCtx = {
26
28
  entity: any
27
29
  flow: any
28
30
  PROJUPPER: string
31
+ accessor: string
29
32
  }
30
33
 
31
34
 
@@ -43,17 +46,16 @@ const TestEntity = cmp(function TestEntity(props: any) {
43
46
  return
44
47
  }
45
48
 
46
- const PROJUPPER = model.const.Name.toUpperCase().replace(/[^A-Z_]/g, '_')
47
-
48
- const ancestors = (entity.relations?.ancestors || []).flat()
49
+ // PHP method names are case-insensitive — an entity literally named 'test'
50
+ // collides with the static `test()` test-mode constructor on the SDK class.
51
+ // Mirror the mangling done in MainEntity_php.ts.
52
+ const accessor = 'test' === entity.Name.toLowerCase()
53
+ ? entity.Name + '_'
54
+ : entity.Name
49
55
 
50
- // Build idmap names
51
- const idnames: string[] = []
52
- for (let i = 1; i <= 3; i++) idnames.push(`${entity.name}0${i}`)
53
- for (const anc of ancestors) {
54
- for (let i = 1; i <= 3; i++) idnames.push(`${anc}0${i}`)
55
- }
56
+ const PROJUPPER = model.const.Name.toUpperCase().replace(/[^A-Z_]/g, '_')
56
57
 
58
+ const idnames = buildIdNames(entity, basicflow)
57
59
  const idnamesStr = idnames.map(n => `"${n}"`).join(', ')
58
60
 
59
61
  const allSteps = Object.values(basicflow.step) as any[]
@@ -63,7 +65,7 @@ const TestEntity = cmp(function TestEntity(props: any) {
63
65
  : []
64
66
  const aliases = updateData.map(([k, v]: any) => [k, v])
65
67
 
66
- const genCtx: GenCtx = { model, entity, flow: basicflow, PROJUPPER }
68
+ const genCtx: GenCtx = { model, entity, flow: basicflow, PROJUPPER, accessor }
67
69
 
68
70
  File({ name: entity.Name + 'EntityTest.' + target.ext }, () => {
69
71
 
@@ -83,7 +85,7 @@ class ${entity.Name}EntityTest extends TestCase
83
85
  public function test_create_instance(): void
84
86
  {
85
87
  $testsdk = ${model.const.Name}SDK::test(null, null);
86
- $ent = $testsdk->${entity.Name}(null);
88
+ $ent = $testsdk->${accessor}(null);
87
89
  $this->assertNotNull($ent);
88
90
  }
89
91
 
@@ -99,7 +101,7 @@ class ${entity.Name}EntityTest extends TestCase
99
101
  if (!flowHasCreate) {
100
102
  Content(` // Bootstrap entity data from existing test data.
101
103
  $${entity.name}_ref01_data_raw = Vs::items(Helpers::to_map(
102
- Vs::getprop($setup["data"], "existing.${entity.name}")));
104
+ Vs::getpath($setup["data"], "existing.${entity.name}")));
103
105
  $${entity.name}_ref01_data = null;
104
106
  if (count($${entity.name}_ref01_data_raw) > 0) {
105
107
  $${entity.name}_ref01_data = Helpers::to_map($${entity.name}_ref01_data_raw[0][1]);
@@ -194,14 +196,8 @@ class ${entity.Name}EntityTest extends TestCase
194
196
  })
195
197
 
196
198
 
197
- function getMatchEntries(step: any): [string, any][] {
198
- if (!step?.match) return []
199
- return Object.entries(step.match).filter(([k]: any) => !k.endsWith('$'))
200
- }
201
-
202
-
203
199
  const generateCreate: OpGen = (ctx, step, index) => {
204
- const { entity, flow } = ctx
200
+ const { entity, flow, accessor } = ctx
205
201
  const ref = step.input?.ref ?? entity.name + '_ref01'
206
202
  const entvar = step.input?.entvar ?? ref + '_ent'
207
203
  const datavar = step.input?.datavar ?? (ref + '_data' + (step.input?.suffix ?? ''))
@@ -222,7 +218,7 @@ const generateCreate: OpGen = (ctx, step, index) => {
222
218
  Content(` // CREATE
223
219
  `)
224
220
  if (needsEnt) {
225
- Content(` $${entvar} = $client->${entity.Name}(null);
221
+ Content(` $${entvar} = $client->${accessor}(null);
226
222
  `)
227
223
  }
228
224
 
@@ -253,7 +249,7 @@ const generateCreate: OpGen = (ctx, step, index) => {
253
249
 
254
250
 
255
251
  const generateList: OpGen = (ctx, step, index) => {
256
- const { entity, flow } = ctx
252
+ const { entity, flow, accessor } = ctx
257
253
  const ref = step.input?.ref ?? entity.name + '_ref01'
258
254
  const entvar = step.input?.entvar ?? ref + '_ent'
259
255
  const matchvar = step.input?.matchvar ?? (ref + '_match' + (step.input?.suffix ?? ''))
@@ -266,7 +262,7 @@ const generateList: OpGen = (ctx, step, index) => {
266
262
  Content(` // LIST
267
263
  `)
268
264
  if (needsEnt) {
269
- Content(` $${entvar} = $client->${entity.Name}(null);
265
+ Content(` $${entvar} = $client->${accessor}(null);
270
266
  `)
271
267
  }
272
268
 
@@ -322,7 +318,7 @@ const generateList: OpGen = (ctx, step, index) => {
322
318
 
323
319
 
324
320
  const generateUpdate: OpGen = (ctx, step, index) => {
325
- const { entity, flow } = ctx
321
+ const { entity, flow, accessor } = ctx
326
322
  const ref = step.input?.ref ?? entity.name + '_ref01'
327
323
  const entvar = step.input?.entvar ?? ref + '_ent'
328
324
  const datavar = step.input?.datavar ?? (ref + '_data' + (step.input?.suffix ?? ''))
@@ -337,7 +333,7 @@ const generateUpdate: OpGen = (ctx, step, index) => {
337
333
  Content(` // UPDATE
338
334
  `)
339
335
  if (needsEnt) {
340
- Content(` $${entvar} = $client->${entity.Name}(null);
336
+ Content(` $${entvar} = $client->${accessor}(null);
341
337
  `)
342
338
  }
343
339
  Content(` $${datavar}_up = [
@@ -389,7 +385,7 @@ const generateUpdate: OpGen = (ctx, step, index) => {
389
385
 
390
386
 
391
387
  const generateLoad: OpGen = (ctx, step, index) => {
392
- const { entity, flow } = ctx
388
+ const { entity, flow, accessor } = ctx
393
389
  const ref = step.input?.ref ?? entity.name + '_ref01'
394
390
  const entvar = step.input?.entvar ?? ref + '_ent'
395
391
  const matchvar = step.input?.matchvar ?? (ref + '_match' + (step.input?.suffix ?? ''))
@@ -415,12 +411,12 @@ const generateLoad: OpGen = (ctx, step, index) => {
415
411
  Content(` // LOAD
416
412
  `)
417
413
  if (!hasEntVar) {
418
- Content(` $${entvar} = $client->${entity.Name}(null);
414
+ Content(` $${entvar} = $client->${accessor}(null);
419
415
  `)
420
416
  }
421
417
  if (!hasSrcData) {
422
418
  Content(` $${srcdatavar}_raw = Vs::items(Helpers::to_map(
423
- Vs::getprop($setup["data"], "existing.${entity.name}")));
419
+ Vs::getpath($setup["data"], "existing.${entity.name}")));
424
420
  $${srcdatavar} = null;
425
421
  if (count($${srcdatavar}_raw) > 0) {
426
422
  $${srcdatavar} = Helpers::to_map($${srcdatavar}_raw[0][1]);
@@ -440,7 +436,7 @@ const generateLoad: OpGen = (ctx, step, index) => {
440
436
 
441
437
 
442
438
  const generateRemove: OpGen = (ctx, step, index) => {
443
- const { entity, flow } = ctx
439
+ const { entity, flow, accessor } = ctx
444
440
  const ref = step.input?.ref ?? entity.name + '_ref01'
445
441
  const entvar = step.input?.entvar ?? ref + '_ent'
446
442
  const matchvar = step.input?.matchvar ?? (ref + '_match' + (step.input?.suffix ?? ''))
@@ -453,7 +449,7 @@ const generateRemove: OpGen = (ctx, step, index) => {
453
449
  Content(` // REMOVE
454
450
  `)
455
451
  if (needsEnt) {
456
- Content(` $${entvar} = $client->${entity.Name}(null);
452
+ Content(` $${entvar} = $client->${accessor}(null);
457
453
  `)
458
454
  }
459
455
  Content(` $${matchvar} = [
@@ -119,11 +119,11 @@ def _make_feature(name):
119
119
  `)
120
120
  })
121
121
 
122
- // Generate __init__.py files for packages
123
- File({ name: '__init__.' + target.ext }, () => {
124
- Content(``)
125
- })
126
-
122
+ // Generate __init__.py files for sub-packages.
123
+ // NOTE: deliberately omit __init__.py at the language-root (py/) level
124
+ // making py/ a package collides with the third-party `py` module on PyPI
125
+ // (a single-file `py.py`), which causes pytest to construct test module
126
+ // paths as `py.test.<file>` and fail with "'py' is not a package".
127
127
  Folder({ name: 'core' }, () => {
128
128
  File({ name: '__init__.' + target.ext }, () => {
129
129
  Content(``)
@@ -3,14 +3,12 @@ import {
3
3
  Content,
4
4
  File,
5
5
  cmp,
6
- each,
6
+ collectDeps,
7
7
  } from '@voxgig/sdkgen'
8
8
 
9
9
 
10
- import {
11
- KIT,
10
+ import type {
12
11
  Model,
13
- getModelPath,
14
12
  } from '@voxgig/apidef'
15
13
 
16
14
 
@@ -20,12 +18,10 @@ const Package = cmp(async function Package(props: any) {
20
18
 
21
19
  const model: Model = ctx$.model
22
20
 
23
- const feature = getModelPath(model, `main.${KIT}.feature`)
24
-
25
21
  File({ name: 'pyproject.toml' }, () => {
26
22
  Content(`[build-system]
27
23
  requires = ["setuptools>=61.0"]
28
- build-backend = "setuptools.backends._legacy:_Backend"
24
+ build-backend = "setuptools.build_meta"
29
25
 
30
26
  [project]
31
27
  name = "${model.name}-sdk"
@@ -37,34 +33,21 @@ dependencies = [
37
33
  "requests>=2.33",
38
34
  `)
39
35
 
40
- // Collect dependencies from features
41
- each(feature, (f: any) => {
42
- const pyDeps = f.deps?.py
43
- if (pyDeps) {
44
- each(pyDeps, (dep: any) => {
45
- if (dep.active) {
46
- Content(` "${dep.key$}>=${dep.version}",
36
+ for (const d of collectDeps(model, target.name, target.deps)) {
37
+ const v = d.source === 'target' ? (d.version || '0.0') : d.version
38
+ Content(` "${d.name}>=${v}",
47
39
  `)
48
- }
49
- })
50
- }
51
- })
52
-
53
- // Add target-level deps
54
- const targetDeps = target.deps
55
- if (targetDeps) {
56
- each(targetDeps, (dep: any) => {
57
- if (dep.active !== false) {
58
- Content(` "${dep.key$}>=${dep.version || '0.0'}",
59
- `)
60
- }
61
- })
62
40
  }
63
41
 
64
42
  Content(`]
65
43
 
66
44
  [project.urls]
67
45
  Homepage = "https://github.com/voxgig/${model.name}-sdk"
46
+
47
+ # Explicit package list — setuptools auto-discovery refuses to pick when
48
+ # multiple top-level dirs (core/entity/feature/utility) are present.
49
+ [tool.setuptools.packages.find]
50
+ include = ["core*", "entity*", "feature*", "utility*"]
68
51
  `)
69
52
  })
70
53
  })
@@ -20,10 +20,13 @@ function normalizePathParams(
20
20
  return part.replace(/\{([^}]+)\}/g, (match: string, rawName: string) => {
21
21
  const snaked = snakify(rawName)
22
22
  const depluralized = depluralize(snaked)
23
+ // Prefer exact name match — orig matches can collide when one param's
24
+ // original name was renamed to another param's current name (e.g. badge
25
+ // load: param 'group_id' has orig 'id', and another param has name 'id').
23
26
  const param = params.find((p: any) =>
24
- p.orig === snaked || p.name === snaked ||
25
- p.orig === depluralized || p.name === depluralized
26
- )
27
+ p.name === snaked || p.name === depluralized) ||
28
+ params.find((p: any) =>
29
+ p.orig === snaked || p.orig === depluralized)
27
30
  if (param) return '{' + param.name + '}'
28
31
 
29
32
  if (rename) {
@@ -95,7 +98,7 @@ class Test${entity.Name}Direct:
95
98
 
96
99
  if (hasList && listPoint) {
97
100
  Content(` def test_should_direct_list_${entity.name}(self):
98
- setup = ${entity.name}_direct_setup([
101
+ setup = _${entity.name}_direct_setup([
99
102
  {"id": "direct01"},
100
103
  {"id": "direct02"},
101
104
  ])
@@ -147,7 +150,7 @@ class Test${entity.Name}Direct:
147
150
 
148
151
  if (hasLoad && loadPoint) {
149
152
  Content(` def test_should_direct_load_${entity.name}(self):
150
- setup = ${entity.name}_direct_setup({"id": "direct01"})
153
+ setup = _${entity.name}_direct_setup({"id": "direct01"})
151
154
  client = setup["client"]
152
155
 
153
156
  `)
@@ -190,7 +193,7 @@ class Test${entity.Name}Direct:
190
193
 
191
194
  Content(`
192
195
 
193
- def ${entity.name}_direct_setup(mockres):
196
+ def _${entity.name}_direct_setup(mockres):
194
197
  runner.load_env_local()
195
198
 
196
199
  calls = []
@@ -16,6 +16,8 @@ import {
16
16
  File,
17
17
  cmp,
18
18
  each,
19
+ buildIdNames,
20
+ getMatchEntries,
19
21
  } from '@voxgig/sdkgen'
20
22
 
21
23
 
@@ -45,15 +47,7 @@ const TestEntity = cmp(function TestEntity(props: any) {
45
47
 
46
48
  const PROJUPPER = model.const.Name.toUpperCase().replace(/[^A-Z_]/g, '_')
47
49
 
48
- const ancestors = (entity.relations?.ancestors || []).flat()
49
-
50
- // Build idmap names
51
- const idnames: string[] = []
52
- for (let i = 1; i <= 3; i++) idnames.push(`${entity.name}0${i}`)
53
- for (const anc of ancestors) {
54
- for (let i = 1; i <= 3; i++) idnames.push(`${anc}0${i}`)
55
- }
56
-
50
+ const idnames = buildIdNames(entity, basicflow)
57
51
  const idnamesStr = idnames.map(n => `"${n}"`).join(', ')
58
52
 
59
53
  const allSteps = Object.values(basicflow.step) as any[]
@@ -91,7 +85,7 @@ class Test${entity.Name}Entity:
91
85
  assert ent is not None
92
86
 
93
87
  def test_should_run_basic_flow(self):
94
- setup = ${entity.name}_basic_setup(None)
88
+ setup = _${entity.name}_basic_setup(None)
95
89
  client = setup["client"]
96
90
 
97
91
  `)
@@ -101,7 +95,7 @@ class Test${entity.Name}Entity:
101
95
  if (!flowHasCreate) {
102
96
  Content(` # Bootstrap entity data from existing test data.
103
97
  ${entity.name}_ref01_data_raw = vs.items(helpers.to_map(
104
- vs.getprop(setup["data"], "existing.${entity.name}")))
98
+ vs.getpath(setup["data"], "existing.${entity.name}")))
105
99
  ${entity.name}_ref01_data = None
106
100
  if len(${entity.name}_ref01_data_raw) > 0:
107
101
  ${entity.name}_ref01_data = helpers.to_map(${entity.name}_ref01_data_raw[0][1])
@@ -120,7 +114,7 @@ class Test${entity.Name}Entity:
120
114
 
121
115
  Content(`
122
116
 
123
- def ${entity.name}_basic_setup(extra):
117
+ def _${entity.name}_basic_setup(extra):
124
118
  runner.load_env_local()
125
119
 
126
120
  entity_data_file = os.path.join(_TEST_DIR, "../../.sdk/test/entity/${entity.name}/${entity.Name}TestData.json")
@@ -193,12 +187,6 @@ def ${entity.name}_basic_setup(extra):
193
187
  })
194
188
 
195
189
 
196
- function getMatchEntries(step: any): [string, any][] {
197
- if (!step?.match) return []
198
- return Object.entries(step.match).filter(([k]: any) => !k.endsWith('$'))
199
- }
200
-
201
-
202
190
  const generateCreate: OpGen = (ctx, step, index) => {
203
191
  const { entity, flow } = ctx
204
192
  const ref = step.input?.ref ?? entity.name + '_ref01'
@@ -419,7 +407,7 @@ const generateLoad: OpGen = (ctx, step, index) => {
419
407
  }
420
408
  if (!hasSrcData) {
421
409
  Content(` ${srcdatavar}_raw = vs.items(helpers.to_map(
422
- vs.getprop(setup["data"], "existing.${entity.name}")))
410
+ vs.getpath(setup["data"], "existing.${entity.name}")))
423
411
  ${srcdatavar} = None
424
412
  if len(${srcdatavar}_raw) > 0:
425
413
  ${srcdatavar} = helpers.to_map(${srcdatavar}_raw[0][1])