@voxgig/sdkgen 0.39.1 → 0.40.2

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 (50) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/action/target.js +7 -1
  3. package/dist/action/target.js.map +1 -1
  4. package/dist/helpers/buildIdNames.d.ts +11 -0
  5. package/dist/helpers/buildIdNames.js +56 -0
  6. package/dist/helpers/buildIdNames.js.map +1 -0
  7. package/dist/helpers/collectDeps.d.ts +9 -0
  8. package/dist/helpers/collectDeps.js +50 -0
  9. package/dist/helpers/collectDeps.js.map +1 -0
  10. package/dist/helpers/getMatchEntries.d.ts +2 -0
  11. package/dist/helpers/getMatchEntries.js +13 -0
  12. package/dist/helpers/getMatchEntries.js.map +1 -0
  13. package/dist/sdkgen.d.ts +6 -2
  14. package/dist/sdkgen.js +17 -2
  15. package/dist/sdkgen.js.map +1 -1
  16. package/dist/tsconfig.tsbuildinfo +1 -1
  17. package/package.json +5 -5
  18. package/project/.sdk/model/target/lua.jsonic +1 -1
  19. package/project/.sdk/model/target/php.jsonic +2 -3
  20. package/project/.sdk/model/target/py.jsonic +1 -1
  21. package/project/.sdk/src/cmp/go/Entity_go.ts +15 -3
  22. package/project/.sdk/src/cmp/go/Main_go.ts +3 -2
  23. package/project/.sdk/src/cmp/go/Package_go.ts +8 -29
  24. package/project/.sdk/src/cmp/go/TestDirect_go.ts +14 -7
  25. package/project/.sdk/src/cmp/go/TestEntity_go.ts +43 -30
  26. package/project/.sdk/src/cmp/js/TestDirect_js.ts +6 -3
  27. package/project/.sdk/src/cmp/lua/Package_lua.ts +5 -27
  28. package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +6 -3
  29. package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +5 -17
  30. package/project/.sdk/src/cmp/php/Config_php.ts +21 -0
  31. package/project/.sdk/src/cmp/php/MainEntity_php.ts +11 -1
  32. package/project/.sdk/src/cmp/php/Package_php.ts +5 -31
  33. package/project/.sdk/src/cmp/php/TestDirect_php.ts +6 -3
  34. package/project/.sdk/src/cmp/php/TestEntity_php.ts +25 -29
  35. package/project/.sdk/src/cmp/py/Main_py.ts +5 -5
  36. package/project/.sdk/src/cmp/py/Package_py.ts +11 -28
  37. package/project/.sdk/src/cmp/py/TestDirect_py.ts +9 -6
  38. package/project/.sdk/src/cmp/py/TestEntity_py.ts +7 -19
  39. package/project/.sdk/src/cmp/rb/Package_rb.ts +8 -47
  40. package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +6 -3
  41. package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +5 -17
  42. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +6 -3
  43. package/project/.sdk/tm/go/core/helpers.go +10 -0
  44. package/project/.sdk/tm/lua/Makefile +1 -1
  45. package/project/.sdk/tm/php/Makefile +1 -1
  46. package/src/action/target.ts +7 -1
  47. package/src/helpers/buildIdNames.ts +70 -0
  48. package/src/helpers/collectDeps.ts +70 -0
  49. package/src/helpers/getMatchEntries.ts +14 -0
  50. package/src/sdkgen.ts +22 -1
@@ -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
 
@@ -53,15 +55,7 @@ const TestEntity = cmp(function TestEntity(props: any) {
53
55
 
54
56
  const PROJUPPER = model.const.Name.toUpperCase().replace(/[^A-Z_]/g, '_')
55
57
 
56
- const ancestors = (entity.relations?.ancestors || []).flat()
57
-
58
- // Build idmap names: entity's own + ancestor ids
59
- const idnames: string[] = []
60
- for (let i = 1; i <= 3; i++) idnames.push(`${entity.name}0${i}`)
61
- for (const anc of ancestors) {
62
- for (let i = 1; i <= 3; i++) idnames.push(`${anc}0${i}`)
63
- }
64
-
58
+ const idnames = buildIdNames(entity, basicflow)
65
59
  const idnamesStr = idnames.map(n => `"${n}"`).join(', ')
66
60
 
67
61
  // Get all update data entries for alias generation
@@ -74,13 +68,20 @@ const TestEntity = cmp(function TestEntity(props: any) {
74
68
 
75
69
  const genCtx: GenCtx = { model, entity, gomodule, flow: basicflow, PROJUPPER }
76
70
 
71
+ // fmt is only used by the TextFieldMark Update branch — omit the import
72
+ // when no step needs it, otherwise Go's strict unused-import check fails.
73
+ const needsFmt = allSteps.some((s: any) =>
74
+ s.op === 'update' &&
75
+ s.input?.textfield &&
76
+ Array.isArray(s.spec) &&
77
+ s.spec.some((sp: any) => sp.apply === 'TextFieldMark'))
78
+
77
79
  File({ name: entity.name + '_entity_test.' + target.ext }, () => {
78
80
 
79
81
  Content(`package sdktest
80
82
 
81
83
  import (
82
- "encoding/json"
83
- "fmt"
84
+ "encoding/json"${needsFmt ? '\n\t"fmt"' : ''}
84
85
  "os"
85
86
  "path/filepath"
86
87
  "runtime"
@@ -104,20 +105,22 @@ func Test${entity.Name}Entity(t *testing.T) {
104
105
 
105
106
  t.Run("basic", func(t *testing.T) {
106
107
  setup := ${entity.name}BasicSetup(nil)
107
- client := setup.client
108
-
109
- `)
108
+ ${allSteps.length > 0 ? '\t\tclient := setup.client\n\n' : ''}`)
110
109
 
111
110
  // Check if the flow has a create step; if not, bootstrap entity data
112
111
  const flowHasCreate = allSteps.some((s: any) => s.op === 'create')
113
112
  if (!flowHasCreate) {
114
113
  const preambleRef = entity.name + '_ref01'
114
+ const preambleVar = goVar(preambleRef)
115
115
  Content(` // Bootstrap entity data from existing test data (no create step in flow).
116
- ${goVar(preambleRef)}DataRaw := vs.Items(core.ToMapAny(vs.GetProp(setup.data, "existing.${entity.name}")))
117
- var ${goVar(preambleRef)}Data map[string]any
118
- if len(${goVar(preambleRef)}DataRaw) > 0 {
119
- ${goVar(preambleRef)}Data = core.ToMapAny(${goVar(preambleRef)}DataRaw[0][1])
116
+ ${preambleVar}DataRaw := vs.Items(core.ToMapAny(vs.GetPath("existing.${entity.name}", setup.data)))
117
+ var ${preambleVar}Data map[string]any
118
+ if len(${preambleVar}DataRaw) > 0 {
119
+ ${preambleVar}Data = core.ToMapAny(${preambleVar}DataRaw[0][1])
120
120
  }
121
+ // Discard guards against Go's unused-var check when the flow's steps
122
+ // happen not to consume the bootstrap data (e.g. list-only flows).
123
+ _ = ${preambleVar}Data
121
124
 
122
125
  `)
123
126
  }
@@ -222,13 +225,6 @@ func Test${entity.Name}Entity(t *testing.T) {
222
225
  })
223
226
 
224
227
 
225
- // Get match entries from a step, filtering out $ keys.
226
- function getMatchEntries(step: any): [string, any][] {
227
- if (!step?.match) return []
228
- return Object.entries(step.match).filter(([k]: any) => !k.endsWith('$'))
229
- }
230
-
231
-
232
228
  const generateCreate: OpGen = (ctx, step, index) => {
233
229
  const { entity, flow } = ctx
234
230
  const ref = step.input?.ref ?? entity.name + '_ref01'
@@ -323,19 +319,32 @@ const generateList: OpGen = (ctx, step, index) => {
323
319
  `)
324
320
  }
325
321
 
322
+ // Only declare ${listvar} as a real var when a downstream validator
323
+ // actually uses it; otherwise `_` to satisfy Go's unused-var check.
324
+ const allSteps = Object.values(flow.step) as any[]
325
+ const listvarUsed = !!step.valid?.some((v: any) => {
326
+ if ('ItemExists' !== v.apply && 'ItemNotExists' !== v.apply) return false
327
+ const validRef = v.def?.ref
328
+ return validRef && allSteps.some((s: any) => 'create' === s.op &&
329
+ ((s.input?.ref ?? entity.name + '_ref01') === validRef))
330
+ })
331
+ const listvarBind = listvarUsed ? listvar : '_'
332
+
333
+ // Use a list-step-unique `ok` name; if a prior list emitted plain `ok`,
334
+ // a second `_, ok :=` would be "no new variables on left side of :=".
335
+ const okvar = listvar + 'Ok'
326
336
  Content(`
327
337
  ${listvar}Result, err := ${entvar}.List(${matchvar}, nil)
328
338
  if err != nil {
329
339
  t.Fatalf("list failed: %v", err)
330
340
  }
331
- ${listvar}, ok := ${listvar}Result.([]any)
332
- if !ok {
341
+ ${listvarBind}, ${okvar} := ${listvar}Result.([]any)
342
+ if !${okvar} {
333
343
  t.Fatalf("expected list result to be an array, got %T", ${listvar}Result)
334
344
  }
335
345
  `)
336
346
 
337
347
  // Handle validators from step.valid
338
- const allSteps = Object.values(flow.step) as any[]
339
348
  if (step.valid) {
340
349
  for (const validator of step.valid) {
341
350
  const validRef = validator.def?.ref
@@ -474,7 +483,7 @@ const generateLoad: OpGen = (ctx, step, index) => {
474
483
  `)
475
484
  }
476
485
  if (!hasSrcData) {
477
- Content(` ${srcdatavar}Raw := vs.Items(core.ToMapAny(vs.GetProp(setup.data, "existing.${entity.name}")))
486
+ Content(` ${srcdatavar}Raw := vs.Items(core.ToMapAny(vs.GetPath("existing.${entity.name}", setup.data)))
478
487
  var ${srcdatavar} map[string]any
479
488
  if len(${srcdatavar}Raw) > 0 {
480
489
  ${srcdatavar} = core.ToMapAny(${srcdatavar}Raw[0][1])
@@ -510,6 +519,10 @@ const generateRemove: OpGen = (ctx, step, index) => {
510
519
  const needsEnt = !priorSteps.some((s: any) =>
511
520
  ['create', 'list', 'load', 'update', 'remove'].includes(s.op))
512
521
 
522
+ // Use `:=` when this is the first op step (so `err` gets declared);
523
+ // otherwise reuse the `err` from a prior op step.
524
+ const errOp = needsEnt ? ':=' : '='
525
+
513
526
  Content(` // REMOVE
514
527
  `)
515
528
  if (needsEnt) {
@@ -519,7 +532,7 @@ const generateRemove: OpGen = (ctx, step, index) => {
519
532
  Content(` ${matchvar} := map[string]any{
520
533
  "id": ${srcdatavar}["id"],
521
534
  }
522
- _, err = ${entvar}.Remove(${matchvar}, nil)
535
+ _, err ${errOp} ${entvar}.Remove(${matchvar}, nil)
523
536
  if err != nil {
524
537
  t.Fatalf("remove failed: %v", err)
525
538
  }
@@ -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(``)