@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.
- package/bin/voxgig-sdkgen +1 -1
- package/dist/action/target.js +7 -1
- package/dist/action/target.js.map +1 -1
- package/dist/helpers/buildIdNames.d.ts +11 -0
- package/dist/helpers/buildIdNames.js +56 -0
- package/dist/helpers/buildIdNames.js.map +1 -0
- package/dist/helpers/collectDeps.d.ts +9 -0
- package/dist/helpers/collectDeps.js +50 -0
- package/dist/helpers/collectDeps.js.map +1 -0
- package/dist/helpers/getMatchEntries.d.ts +2 -0
- package/dist/helpers/getMatchEntries.js +13 -0
- package/dist/helpers/getMatchEntries.js.map +1 -0
- package/dist/sdkgen.d.ts +6 -2
- package/dist/sdkgen.js +17 -2
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/project/.sdk/model/target/lua.jsonic +1 -1
- package/project/.sdk/model/target/php.jsonic +2 -3
- package/project/.sdk/model/target/py.jsonic +1 -1
- package/project/.sdk/src/cmp/go/Entity_go.ts +15 -3
- package/project/.sdk/src/cmp/go/Main_go.ts +3 -2
- package/project/.sdk/src/cmp/go/Package_go.ts +8 -29
- package/project/.sdk/src/cmp/go/TestDirect_go.ts +14 -7
- package/project/.sdk/src/cmp/go/TestEntity_go.ts +43 -30
- package/project/.sdk/src/cmp/js/TestDirect_js.ts +6 -3
- package/project/.sdk/src/cmp/lua/Package_lua.ts +5 -27
- package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +6 -3
- package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +5 -17
- package/project/.sdk/src/cmp/php/Config_php.ts +21 -0
- package/project/.sdk/src/cmp/php/MainEntity_php.ts +11 -1
- package/project/.sdk/src/cmp/php/Package_php.ts +5 -31
- package/project/.sdk/src/cmp/php/TestDirect_php.ts +6 -3
- package/project/.sdk/src/cmp/php/TestEntity_php.ts +25 -29
- package/project/.sdk/src/cmp/py/Main_py.ts +5 -5
- package/project/.sdk/src/cmp/py/Package_py.ts +11 -28
- package/project/.sdk/src/cmp/py/TestDirect_py.ts +9 -6
- package/project/.sdk/src/cmp/py/TestEntity_py.ts +7 -19
- package/project/.sdk/src/cmp/rb/Package_rb.ts +8 -47
- package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +6 -3
- package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +5 -17
- package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +6 -3
- package/project/.sdk/tm/go/core/helpers.go +10 -0
- package/project/.sdk/tm/lua/Makefile +1 -1
- package/project/.sdk/tm/php/Makefile +1 -1
- package/src/action/target.ts +7 -1
- package/src/helpers/buildIdNames.ts +70 -0
- package/src/helpers/collectDeps.ts +70 -0
- package/src/helpers/getMatchEntries.ts +14 -0
- 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
|
|
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
|
-
|
|
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
|
-
${
|
|
117
|
-
var ${
|
|
118
|
-
if len(${
|
|
119
|
-
${
|
|
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
|
-
${
|
|
332
|
-
if
|
|
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.
|
|
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
|
|
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
|
-
|
|
307
|
-
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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 ${
|
|
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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
"${
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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->${
|
|
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::
|
|
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->${
|
|
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->${
|
|
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->${
|
|
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->${
|
|
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::
|
|
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->${
|
|
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
|
-
|
|
124
|
-
|
|
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(``)
|