@voxgig/sdkgen-infrapack 0.0.15 → 0.0.16
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/.sdk/model/target/seneca-provider.aontu +5 -0
- package/.sdk/src/cmp/seneca-provider/Extras_seneca-provider.ts +110 -48
- package/.sdk/src/cmp/seneca-provider/Main_seneca-provider.ts +51 -11
- package/.sdk/src/cmp/seneca-provider/Makefile_seneca-provider.ts +216 -0
- package/.sdk/src/cmp/seneca-provider/Standalone_seneca-provider.ts +201 -0
- package/README.md +72 -1
- package/package.json +1 -1
- package/sdkgen-package.json +1 -1
- package/.sdk/tm/seneca-provider/Makefile +0 -131
|
@@ -23,7 +23,12 @@ const SdkPin = cmp(function SdkPin(props: any) {
|
|
|
23
23
|
|
|
24
24
|
File({ name: 'sdk-pin.json' }, () => {
|
|
25
25
|
Content(JSON.stringify({
|
|
26
|
-
note:
|
|
26
|
+
note: provider.standalone ?
|
|
27
|
+
'GENERATED. The SDK this provider depends on and is generated from. ' +
|
|
28
|
+
'`make sdk-src` fetches it; `make regen` replaces the API definition ' +
|
|
29
|
+
'and guide in .sdk/ with its own and regenerates this repo. Set the version in ' +
|
|
30
|
+
'.sdk/model/project.aontu, not in this file.' :
|
|
31
|
+
'GENERATED. The SDK this provider is generated from. ' +
|
|
27
32
|
'`make sdk-src` fetches it; `make regen` regenerates this repo ' +
|
|
28
33
|
'from it. Edit the SDK project model, not this file.',
|
|
29
34
|
repo: provider.sdkRepoUrl,
|
|
@@ -1718,7 +1723,8 @@ const seneca = Seneca()
|
|
|
1718
1723
|
},
|
|
1719
1724
|
},
|
|
1720
1725
|
})
|
|
1721
|
-
.use('${provider.pkgName}'
|
|
1726
|
+
.use('${provider.pkgName}'${'' === provider.specBase ?
|
|
1727
|
+
`, { sdk: { base: 'https://${provider.lower}.example.com' } }` : ''})
|
|
1722
1728
|
|
|
1723
1729
|
await seneca.ready()
|
|
1724
1730
|
|
|
@@ -1734,7 +1740,7 @@ await seneca.ready()
|
|
|
1734
1740
|
`'${cparts.map((p: string) => 'some-' + p).join(
|
|
1735
1741
|
null != subject.idsep && '' !== String(subject.idsep) ?
|
|
1736
1742
|
String(subject.idsep) : '/')}'` :
|
|
1737
|
-
0 === subject.parents.length ? `'some-id'` :
|
|
1743
|
+
0 === subject.parents.length ? (loadHasKey(subject) ? `'some-id'` : '') :
|
|
1738
1744
|
`{ ` + subject.parents.map((p: string) => `${jsKey(p)}: 'some-${p}'`).join(', ') +
|
|
1739
1745
|
`, id: 'some-id' }`
|
|
1740
1746
|
Content(`const ${subject.name} = await seneca
|
|
@@ -1898,7 +1904,10 @@ whose logs cannot be read.
|
|
|
1898
1904
|
each(provider.entities, (e: any) => {
|
|
1899
1905
|
each(e.cmds, (cmd: any) => {
|
|
1900
1906
|
const c = String(cmd.val$ ?? cmd)
|
|
1901
|
-
|
|
1907
|
+
const desc = 'save' !== c ? CMD_DESC[c] :
|
|
1908
|
+
!e.ops.includes('update') ? 'Create a record' :
|
|
1909
|
+
!e.ops.includes('create') ? 'Update a record' : CMD_DESC[c]
|
|
1910
|
+
Content(`| \`sys:entity,cmd:${c},zone:provider,base:${provider.lower},name:${e.name}\` | ${desc}. |
|
|
1902
1911
|
`)
|
|
1903
1912
|
})
|
|
1904
1913
|
})
|
|
@@ -1978,9 +1987,15 @@ const sdk = seneca.export('${provider.pluginName}/sdk')()
|
|
|
1978
1987
|
|
|
1979
1988
|
## Contributing
|
|
1980
1989
|
|
|
1981
|
-
|
|
1990
|
+
${provider.standalone ?
|
|
1991
|
+
`This plugin is GENERATED, by the builder in \`.sdk/\` from the API definition
|
|
1992
|
+
of the SDK it depends on. Changes to the API belong in the SDK project's
|
|
1993
|
+
model, this package's own decisions in \`.sdk/model/project.aontu\`, and
|
|
1994
|
+
everything else in the components that build this target — anything edited
|
|
1995
|
+
elsewhere in this repository is overwritten by the next \`make regen\`.` :
|
|
1996
|
+
`This plugin is GENERATED. Changes belong in the SDK project's model and
|
|
1982
1997
|
components, not here — anything edited in this repository is overwritten by
|
|
1983
|
-
the next generation run
|
|
1998
|
+
the next generation run.`}
|
|
1984
1999
|
|
|
1985
2000
|
The [Senecajs org](http://senecajs.org) encourages open participation. If you
|
|
1986
2001
|
feel you can help in any way, be it with bug reporting, documentation,
|
|
@@ -2677,6 +2692,7 @@ const DocHowto = cmp(function DocHowto(props: any) {
|
|
|
2677
2692
|
// an absent value to the same thing, so a missing base is treated as absent
|
|
2678
2693
|
// rather than printed as a default nobody can use.
|
|
2679
2694
|
const liveBase = provider.liveBase || ''
|
|
2695
|
+
const specBase = provider.specBase || ''
|
|
2680
2696
|
|
|
2681
2697
|
// The same choice the tests and the manual scripts make: fewest parent keys
|
|
2682
2698
|
// (nothing to arrange), then most cmds. Recipes prefer it, so one entity
|
|
@@ -2698,6 +2714,7 @@ const DocHowto = cmp(function DocHowto(props: any) {
|
|
|
2698
2714
|
// the API calls it. `apiKey` is that name, for the SDK-direct examples.
|
|
2699
2715
|
const idf = (_e: any) => 'id'
|
|
2700
2716
|
const apiKey = (e: any) => e.rk || 'id'
|
|
2717
|
+
const modelsId = (e: any) => null != e.ent?.id || null != (e.ent?.fields || {})[apiKey(e)]
|
|
2701
2718
|
|
|
2702
2719
|
// A parent key's example value. This MIRRORS seedRecord rather than
|
|
2703
2720
|
// inventing something more readable: the offline recipe below seeds with
|
|
@@ -2767,7 +2784,15 @@ const DocHowto = cmp(function DocHowto(props: any) {
|
|
|
2767
2784
|
|
|
2768
2785
|
const eList = forCmd('list')
|
|
2769
2786
|
const eLoad = forCmd('load')
|
|
2770
|
-
const
|
|
2787
|
+
const forOp = (op: string) => {
|
|
2788
|
+
const able = ents.filter((e: any) => e.ops.includes(op))
|
|
2789
|
+
return able.find((e: any) => e === subject) ||
|
|
2790
|
+
able.find((e: any) => 0 === e.parents.length) ||
|
|
2791
|
+
able[0] || null
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
const eCreate = forOp('create')
|
|
2795
|
+
const eUpdate = forOp('update')
|
|
2771
2796
|
const eRemove = forCmd('remove')
|
|
2772
2797
|
|
|
2773
2798
|
// Sections as data, so the contents list and the sections cannot disagree.
|
|
@@ -2841,53 +2866,63 @@ genuinely wrong.`)
|
|
|
2841
2866
|
}
|
|
2842
2867
|
|
|
2843
2868
|
|
|
2844
|
-
if (null !=
|
|
2845
|
-
const created = literal(createData(
|
|
2869
|
+
if (null != eCreate) {
|
|
2870
|
+
const created = literal(createData(eCreate))
|
|
2871
|
+
const updates = eCreate.ops.includes('update')
|
|
2846
2872
|
|
|
2847
|
-
sec('Create a record', `\`make$\` builds an entity and \`save$\` writes it.
|
|
2848
|
-
|
|
2873
|
+
sec('Create a record', `\`make$\` builds an entity and \`save$\` writes it. ${updates ?
|
|
2874
|
+
'An entity with no id\nis a create:' :
|
|
2875
|
+
`The API has no update\nfor a \`${eCreate.name}\`, so \`save$\` always creates one, even from an entity\nthat carries an id:`}
|
|
2849
2876
|
|
|
2850
2877
|
\`\`\`js
|
|
2851
|
-
const ${
|
|
2852
|
-
.entity('${canon(
|
|
2878
|
+
const ${eCreate.name} = await seneca
|
|
2879
|
+
.entity('${canon(eCreate)}')
|
|
2853
2880
|
.make$(${created})
|
|
2854
2881
|
.save$()
|
|
2855
2882
|
|
|
2856
|
-
console.log(${
|
|
2883
|
+
console.log(${eCreate.name}${modelsId(eCreate) ? '.' + idf(eCreate) : ''})
|
|
2857
2884
|
\`\`\`
|
|
2858
|
-
${0 ===
|
|
2859
|
-
Note that \`${
|
|
2860
|
-
not in a query: a \`${
|
|
2885
|
+
${0 === eCreate.parents.length ? '' : `
|
|
2886
|
+
Note that \`${eCreate.parents.join('`, `')}\` travels in the DATA for a write,
|
|
2887
|
+
not in a query: a \`${eCreate.name}\` is created inside its parent.
|
|
2861
2888
|
`}
|
|
2862
|
-
|
|
2889
|
+
${modelsId(eCreate) ?
|
|
2890
|
+
`\`save$\` resolves to the record as the API returned it, which is the only
|
|
2863
2891
|
reliable source of the id. Read it from there rather than predicting it:
|
|
2864
2892
|
what an API does with an id you supply on create is its own business, and
|
|
2865
|
-
several ignore it entirely.`
|
|
2893
|
+
several ignore it entirely.` :
|
|
2894
|
+
`\`save$\` resolves to the record as the API returned it. The API definition
|
|
2895
|
+
declares no id for a \`${eCreate.name}\`, so the record is the only place to
|
|
2896
|
+
read what identifies one.`}`)
|
|
2897
|
+
}
|
|
2866
2898
|
|
|
2867
|
-
const f = changeable(eSave)
|
|
2868
2899
|
|
|
2869
|
-
|
|
2870
|
-
|
|
2900
|
+
if (null != eUpdate) {
|
|
2901
|
+
const f = changeable(eUpdate)
|
|
2902
|
+
|
|
2903
|
+
sec('Update a record', `${eUpdate.ops.includes('create') ?
|
|
2904
|
+
'The same call updates. `save$` dispatches on the id: an entity carrying\none is an update, an entity without one is a create.' :
|
|
2905
|
+
`\`save$\` updates: the API has no create for a \`${eUpdate.name}\`.`} So the safe shape is
|
|
2871
2906
|
load, change, save:
|
|
2872
2907
|
|
|
2873
|
-
\`\`\`js${
|
|
2874
|
-
const ${
|
|
2875
|
-
.entity('${canon(
|
|
2876
|
-
.load$(${oneArgs(
|
|
2908
|
+
\`\`\`js${eUpdate.cmds.includes('load') ? `
|
|
2909
|
+
const ${eUpdate.name} = await seneca
|
|
2910
|
+
.entity('${canon(eUpdate)}')
|
|
2911
|
+
.load$(${oneArgs(eUpdate)})
|
|
2877
2912
|
` : `
|
|
2878
|
-
const ${
|
|
2879
|
-
.entity('${canon(
|
|
2880
|
-
.make$(${literal(0 ===
|
|
2881
|
-
{ [idf(
|
|
2882
|
-
{ ...Object.fromEntries(
|
|
2883
|
-
(k: string) => [k, parentVal(
|
|
2884
|
-
[idf(
|
|
2913
|
+
const ${eUpdate.name} = seneca
|
|
2914
|
+
.entity('${canon(eUpdate)}')
|
|
2915
|
+
.make$(${literal(0 === eUpdate.parents.length ?
|
|
2916
|
+
{ [idf(eUpdate)]: `${eUpdate.name}0` } :
|
|
2917
|
+
{ ...Object.fromEntries(eUpdate.parents.map(
|
|
2918
|
+
(k: string) => [k, parentVal(eUpdate, k)])),
|
|
2919
|
+
[idf(eUpdate)]: `${eUpdate.name}0` })})
|
|
2885
2920
|
`}${null == f ? `
|
|
2886
2921
|
// change the fields you need
|
|
2887
2922
|
` : `
|
|
2888
|
-
${
|
|
2923
|
+
${eUpdate.name}.${f.name} = ${newValue(f)}
|
|
2889
2924
|
`}
|
|
2890
|
-
await ${
|
|
2925
|
+
await ${eUpdate.name}.save$()
|
|
2891
2926
|
\`\`\`
|
|
2892
2927
|
|
|
2893
2928
|
Mutating the record you loaded sends it as it stood plus your change, so
|
|
@@ -3027,13 +3062,16 @@ constructor, so \`base\` chooses the host:
|
|
|
3027
3062
|
})
|
|
3028
3063
|
\`\`\`
|
|
3029
3064
|
|
|
3030
|
-
${'' ===
|
|
3065
|
+
${'' === specBase ?
|
|
3031
3066
|
`The API definition declares no server, so there is no default worth
|
|
3032
3067
|
relying on: set \`base\` explicitly, or run against the mock instead (see
|
|
3033
3068
|
[${OFFLINE_TITLE}](${anchor(OFFLINE_TITLE)})).` :
|
|
3034
|
-
|
|
3069
|
+
specBase === liveBase ?
|
|
3070
|
+
`The SDK's own default is \`${liveBase}\`, which is where the
|
|
3035
3071
|
companion test server listens, so local development usually needs no
|
|
3036
|
-
\`base\` at all.`
|
|
3072
|
+
\`base\` at all.` :
|
|
3073
|
+
`The SDK's own default is \`${specBase}\`, the server the API definition
|
|
3074
|
+
declares, so \`base\` is needed only to reach another one.`}`)
|
|
3037
3075
|
|
|
3038
3076
|
|
|
3039
3077
|
if (!provider.authActive) {
|
|
@@ -3321,10 +3359,15 @@ $ npm run repo-publish
|
|
|
3321
3359
|
Only \`dist\`, the TypeScript sources and the licence file are published;
|
|
3322
3360
|
the test suite and its build output stay in the repository.
|
|
3323
3361
|
|
|
3324
|
-
|
|
3362
|
+
${'npm' === provider.sdkDepKind ?
|
|
3363
|
+
`Before publishing, check that \`package.json\` still depends on the
|
|
3325
3364
|
published SDK by version range and not on a local path: a \`file:\`
|
|
3326
3365
|
dependency left behind from local development installs perfectly on your
|
|
3327
|
-
own machine and cannot be resolved by anybody else
|
|
3366
|
+
own machine and cannot be resolved by anybody else.` :
|
|
3367
|
+
`Publish the SDK to npm first. \`package.json\` depends on it as
|
|
3368
|
+
\`${provider.sdkDep}\`, which everyone installing this package would
|
|
3369
|
+
have to fetch${'git' === provider.sdkDepKind ? ' with git' : ''}. Then drop \`sdk.dep\` from the model, regenerate,
|
|
3370
|
+
and check that the dependency is a version range.`}
|
|
3328
3371
|
|
|
3329
3372
|
One last thing: this repository is GENERATED from the ${provider.api} API
|
|
3330
3373
|
model by [@voxgig/sdkgen](https://github.com/voxgig/sdkgen). An edit made
|
|
@@ -3501,6 +3544,14 @@ Seneca({ legacy: false })
|
|
|
3501
3544
|
if (live) {
|
|
3502
3545
|
Content(` .use('${provider.pkgName}', { sdk: { base: '${provider.liveBase}' } })
|
|
3503
3546
|
\`\`\`
|
|
3547
|
+
`)
|
|
3548
|
+
}
|
|
3549
|
+
else if ('' !== provider.specBase) {
|
|
3550
|
+
Content(` .use('${provider.pkgName}')
|
|
3551
|
+
\`\`\`
|
|
3552
|
+
|
|
3553
|
+
The SDK's default base URL is \`${provider.specBase}\`, the server the
|
|
3554
|
+
${provider.api} definition declares. Pass \`sdk: { base }\` to reach another.
|
|
3504
3555
|
`)
|
|
3505
3556
|
}
|
|
3506
3557
|
else {
|
|
@@ -3531,8 +3582,8 @@ Any option the \`${provider.sdkClass}\` constructor accepts:
|
|
|
3531
3582
|
|
|
3532
3583
|
| Key | Effect |
|
|
3533
3584
|
| --- | ------ |
|
|
3534
|
-
| \`base\` | Base URL for API requests. ${
|
|
3535
|
-
`The SDK's own default is \`${provider.
|
|
3585
|
+
| \`base\` | Base URL for API requests. ${'' !== provider.specBase ?
|
|
3586
|
+
`The SDK's own default is \`${provider.specBase}\`, the server the API definition declares.` :
|
|
3536
3587
|
'There is no default: this API declares no server, so it must be set.'} |
|
|
3537
3588
|
| \`prefix\` / \`suffix\` | URL fragments placed around the path. |
|
|
3538
3589
|
| \`headers\` | Headers sent on every request. These win over the \`authorization\` header the provider adds from a configured key. |
|
|
@@ -3618,7 +3669,9 @@ before any request is made, rather than issuing one that would 404.
|
|
|
3618
3669
|
`)
|
|
3619
3670
|
}
|
|
3620
3671
|
if (e.cmds.includes('load')) {
|
|
3621
|
-
Content(
|
|
3672
|
+
Content(loadHasKey(e) ?
|
|
3673
|
+
`| \`load$(q)\` | ${reqd([...e.parents, 'id'])} | One \`${e.name}\`, or \`null\` if not found. |
|
|
3674
|
+
` : `| \`load$(q)\` | ${0 < e.parents.length ? reqd(e.parents) : 'nothing: the route names no record'} | The one \`${e.name}\`, or \`null\` when \`id\` names one it does not carry. |
|
|
3622
3675
|
`)
|
|
3623
3676
|
}
|
|
3624
3677
|
if (e.cmds.includes('save')) {
|
|
@@ -3636,7 +3689,8 @@ before any request is made, rather than issuing one that would 404.
|
|
|
3636
3689
|
|
|
3637
3690
|
if ('id' !== apiKeyOf(e)) {
|
|
3638
3691
|
Content(`
|
|
3639
|
-
The API
|
|
3692
|
+
The API ${e.ops.some((op: string) => ['load', 'remove', 'update'].includes(op)) ?
|
|
3693
|
+
'addresses' : 'identifies'} \`${e.name}\` records by \`${apiKeyOf(e)}\`; the provider
|
|
3640
3694
|
carries that value as the entity's \`id\`, so every query and entity above
|
|
3641
3695
|
uses \`id\`. A record the API returns with an unrelated \`id\` of its own
|
|
3642
3696
|
${false === e.parkfree ?
|
|
@@ -3661,7 +3715,7 @@ also defines are passed through unchanged in both directions.
|
|
|
3661
3715
|
| ----- | ---- | ----- |
|
|
3662
3716
|
`)
|
|
3663
3717
|
each(e.fields, (f: any) => {
|
|
3664
|
-
Content(`| \`${f.name}\` | ${f.kind} | ${f.name === (e.rk || 'id') ?
|
|
3718
|
+
Content(`| \`${f.name}\` | ${f.kind}${f.nullable ? ' or null' : ''} | ${f.name === (e.rk || 'id') ?
|
|
3665
3719
|
('id' === f.name ? 'Id field.' : 'API key; carried as the entity\'s `id`.') :
|
|
3666
3720
|
e.parents.includes(f.name) ? ('' === f.parentEntity ?
|
|
3667
3721
|
'Parent key. Required by every command.' :
|
|
@@ -4440,11 +4494,16 @@ rather than as a surprise in production.
|
|
|
4440
4494
|
`)
|
|
4441
4495
|
|
|
4442
4496
|
if ('' === provider.liveBase) {
|
|
4443
|
-
Content(
|
|
4497
|
+
Content(`${'' === provider.specBase ?
|
|
4498
|
+
`The API definition declares no server, so this plugin has no default host: the
|
|
4444
4499
|
base URL arrives through the \`sdk.base\` option, supplied by whoever configures
|
|
4445
4500
|
the plugin for a particular deployment. The tests therefore run entirely
|
|
4446
4501
|
against the SDK's mock transport, which is the one host that is always
|
|
4447
|
-
available
|
|
4502
|
+
available.` :
|
|
4503
|
+
`The SDK's default host is \`${provider.specBase}\`, the server the API
|
|
4504
|
+
definition declares, and the \`sdk.base\` option points the plugin at another.
|
|
4505
|
+
Nothing declares a test server, so the tests run entirely against the SDK's
|
|
4506
|
+
mock transport, which is the one host that is always available.`}
|
|
4448
4507
|
|
|
4449
4508
|
|
|
4450
4509
|
`)
|
|
@@ -4476,7 +4535,10 @@ that has to be applied again, silently, forever.
|
|
|
4476
4535
|
|
|
4477
4536
|
The source of truth is the SDK project's model — the repository and tag named
|
|
4478
4537
|
in \`sdk-pin.json\`, which \`make sdk-src\` fetches to \`${provider.sdkSrc}\` —
|
|
4479
|
-
together with the sdkgen component that emits this target.
|
|
4538
|
+
together with the sdkgen component that emits this target.${provider.standalone ? `
|
|
4539
|
+
This repository builds itself: the builder in \`.sdk/\` carries a copy of that
|
|
4540
|
+
SDK's API definition, which \`make regen\` refreshes from the fetched source,
|
|
4541
|
+
and generation refuses to write when the copy no longer matches it.` : ''} A change to *what*
|
|
4480
4542
|
the API offers belongs in the model; a change to
|
|
4481
4543
|
*how* the provider expresses it belongs in the component. Both are versioned,
|
|
4482
4544
|
both regenerate every provider built this way rather than just this one, and
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cmp, each,
|
|
3
3
|
File, Content, Copy, Folder,
|
|
4
|
-
entityCollection, entityOps, entityIdField, entityClassName,
|
|
4
|
+
entityCollection, entityOps, entityIdField, entityDataIdField, entityClassName,
|
|
5
5
|
entityActions,
|
|
6
6
|
opRequestShape, opParams, ownPoint, entityPath,
|
|
7
7
|
collectDeps, repoInfo, packageName, packageVersion, apiName, envName,
|
|
@@ -20,6 +20,10 @@ import {
|
|
|
20
20
|
Tests, Scripts, Workflow, Readme, Docs, SdkPin, SDK_SRC_DIR,
|
|
21
21
|
} from './Extras_seneca-provider'
|
|
22
22
|
import { Gitignore } from './Gitignore_seneca-provider'
|
|
23
|
+
import { Makefile } from './Makefile_seneca-provider'
|
|
24
|
+
import {
|
|
25
|
+
standaloneBuilder, sdkIdentity, checkSdkSource,
|
|
26
|
+
} from './Standalone_seneca-provider'
|
|
23
27
|
|
|
24
28
|
|
|
25
29
|
|
|
@@ -208,7 +212,7 @@ function recordKey(ent: any): string {
|
|
|
208
212
|
}
|
|
209
213
|
}
|
|
210
214
|
|
|
211
|
-
return '' !== fallback ? fallback : 'id'
|
|
215
|
+
return '' !== fallback ? fallback : (entityDataIdField(ent) || 'id')
|
|
212
216
|
}
|
|
213
217
|
|
|
214
218
|
|
|
@@ -278,9 +282,18 @@ function parentKeys(ent: any): string[] {
|
|
|
278
282
|
}
|
|
279
283
|
|
|
280
284
|
|
|
285
|
+
const sentinelKey = (type: any): string =>
|
|
286
|
+
String(type ?? '').replace(/[`$]/g, '').trim().toUpperCase()
|
|
287
|
+
|
|
288
|
+
const unionMembers = (type: any): any[] =>
|
|
289
|
+
Array.isArray(type) && 'ONE' === sentinelKey(type[0]) && Array.isArray(type[1]) ?
|
|
290
|
+
type[1] : []
|
|
291
|
+
|
|
292
|
+
|
|
281
293
|
function fieldKind(type: any): string {
|
|
282
294
|
if (Array.isArray(type)) {
|
|
283
|
-
|
|
295
|
+
const member = unionMembers(type).find((m: any) => 'NULL' !== sentinelKey(m))
|
|
296
|
+
return null == member ? 'string' : fieldKind(member)
|
|
284
297
|
}
|
|
285
298
|
|
|
286
299
|
const t = String(type || '').toUpperCase()
|
|
@@ -294,6 +307,11 @@ function fieldKind(type: any): string {
|
|
|
294
307
|
}
|
|
295
308
|
|
|
296
309
|
|
|
310
|
+
function fieldNullable(type: any): boolean {
|
|
311
|
+
return unionMembers(type).some((m: any) => 'NULL' === sentinelKey(m))
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
|
|
297
315
|
function parentEntityOf(key: string, names: string[]): string {
|
|
298
316
|
const stem = key.replace(/_id$/, '')
|
|
299
317
|
return names.includes(stem) ? stem : ''
|
|
@@ -326,8 +344,10 @@ const Main = cmp(function Main(props: any) {
|
|
|
326
344
|
const { target, ctx$ } = props
|
|
327
345
|
const { model } = ctx$
|
|
328
346
|
|
|
347
|
+
const standalone = standaloneBuilder(target)
|
|
348
|
+
|
|
329
349
|
const targets = model.main[KIT].target || {}
|
|
330
|
-
if (null == targets.ts) {
|
|
350
|
+
if (!standalone && null == targets.ts) {
|
|
331
351
|
throw new SdkGenError(
|
|
332
352
|
'seneca-provider requires the `ts` target in the same SDK: it imports ' +
|
|
333
353
|
'the TypeScript SDK that `ts` generates. Add it with:\n' +
|
|
@@ -347,8 +367,7 @@ const Main = cmp(function Main(props: any) {
|
|
|
347
367
|
// included, so the two can never disagree.
|
|
348
368
|
// The TypeScript SDK this provider WRAPS — a different target, so it
|
|
349
369
|
// keeps its own name and does not follow this provider's alias.
|
|
350
|
-
const sdkPkg =
|
|
351
|
-
const sdkVersion = packageVersion(model, 'ts')
|
|
370
|
+
const { sdkPkg, sdkVersion } = sdkIdentity(model, target)
|
|
352
371
|
|
|
353
372
|
const entityColl = entityCollection(model)
|
|
354
373
|
|
|
@@ -434,6 +453,7 @@ const Main = cmp(function Main(props: any) {
|
|
|
434
453
|
.map((f: any) => ({
|
|
435
454
|
name: f.n,
|
|
436
455
|
kind: fieldKind(f.t),
|
|
456
|
+
nullable: fieldNullable(f.t),
|
|
437
457
|
parentEntity: parentEntityOf(f.n, entityNames),
|
|
438
458
|
}))
|
|
439
459
|
|
|
@@ -444,6 +464,7 @@ const Main = cmp(function Main(props: any) {
|
|
|
444
464
|
req.push({
|
|
445
465
|
name: key,
|
|
446
466
|
kind: 'string',
|
|
467
|
+
nullable: false,
|
|
447
468
|
parentEntity: parentOf[key],
|
|
448
469
|
})
|
|
449
470
|
}
|
|
@@ -510,6 +531,7 @@ const Main = cmp(function Main(props: any) {
|
|
|
510
531
|
Name, lower, ENV, sdkClass, pluginName, fileBase,
|
|
511
532
|
sdkPkg, sdkVersion, entities,
|
|
512
533
|
sdkDep,
|
|
534
|
+
standalone,
|
|
513
535
|
// Whether that dependency comes from outside a registry. The generated
|
|
514
536
|
// CI note says so, because "npm install is all you need" stops being
|
|
515
537
|
// true the moment git or a tarball URL is in the path.
|
|
@@ -531,6 +553,8 @@ const Main = cmp(function Main(props: any) {
|
|
|
531
553
|
version: packageVersion(model, target.name),
|
|
532
554
|
liveBase,
|
|
533
555
|
liveApp,
|
|
556
|
+
// The server the API definition declares, which is the SDK's own default.
|
|
557
|
+
specBase,
|
|
534
558
|
publisher: PUBLISHER,
|
|
535
559
|
publisherUrl: PUBLISHER_URL,
|
|
536
560
|
probePath: (entities.find((e: any) =>
|
|
@@ -549,6 +573,10 @@ const Main = cmp(function Main(props: any) {
|
|
|
549
573
|
authPrefix: resolveAuthPrefix(model),
|
|
550
574
|
}
|
|
551
575
|
|
|
576
|
+
if (standalone) {
|
|
577
|
+
checkSdkSource(ctx$, provider, model)
|
|
578
|
+
}
|
|
579
|
+
|
|
552
580
|
// `.gitignore` is EMITTED rather than copied — npm strips that filename
|
|
553
581
|
// from the tarball, so as a template it reached only checkout users. See
|
|
554
582
|
// Gitignore_seneca-provider. Called before the Copy, as every language
|
|
@@ -560,6 +588,7 @@ const Main = cmp(function Main(props: any) {
|
|
|
560
588
|
replace: { ...ctx$.stdrep },
|
|
561
589
|
})
|
|
562
590
|
|
|
591
|
+
Makefile({ provider })
|
|
563
592
|
SdkPin({ provider })
|
|
564
593
|
PackageJson({ provider, target })
|
|
565
594
|
ProviderSource({ provider })
|
|
@@ -1218,7 +1247,12 @@ ${actionBranch('load',
|
|
|
1218
1247
|
` const hit = await ornull(() => this.shared.sdk.${e.acc}()[op$](${aq}))
|
|
1219
1248
|
return null == hit ? null : entize(${out('hit')})
|
|
1220
1249
|
`)}${guard('load', 'q')}${splitLine('load')} const res = await ornull(() => this.shared.sdk.${e.acc}().load(${sdkArg('load')}))
|
|
1221
|
-
|
|
1250
|
+
${0 < addressKeys(e.ent, 'load').length || 0 < eparts.length ? '' :
|
|
1251
|
+
` // The route names no record, so an id finds only the record carrying it.
|
|
1252
|
+
if (null != res && null != q.id && String(${jsProp('plain(res)', rk)}) !== String(q.id)) {
|
|
1253
|
+
return null
|
|
1254
|
+
}
|
|
1255
|
+
`} return null == res ? null : entize(${out('res', loadVals)})
|
|
1222
1256
|
}
|
|
1223
1257
|
|
|
1224
1258
|
`)
|
|
@@ -1268,7 +1302,12 @@ ${dropPark}
|
|
|
1268
1302
|
// which matches a request against a stored record, then looked for a
|
|
1269
1303
|
// record whose own \`id\` was that joined string and found none.
|
|
1270
1304
|
delete data.id
|
|
1271
|
-
` : !keyed ? '' : `
|
|
1305
|
+
` : !keyed ? '' : hasCreate && !hasUpdate && true !== e.rkoncreate ? `
|
|
1306
|
+
// The API assigns a ${e.name}'s \`${rk}\`, and a create names no record, so
|
|
1307
|
+
// Seneca's \`id\` is not sent: the id is read back from the response.
|
|
1308
|
+
const key: any = null
|
|
1309
|
+
${dropPark} delete data.id
|
|
1310
|
+
` : `
|
|
1272
1311
|
// This API keys a ${e.name} by \`${rk}\`; Seneca carries it as \`id\`.
|
|
1273
1312
|
// The key goes on the body under the API's own name, where the route
|
|
1274
1313
|
// reads it, and into the match, which the SDK consults first.
|
|
@@ -1369,9 +1408,10 @@ ${provider.authActive ? `
|
|
|
1369
1408
|
// the SDK was constructed with NO credential at all — the request went
|
|
1370
1409
|
// out unauthenticated and failed much later as a 401 or a 404 on
|
|
1371
1410
|
// anything private, with nothing at startup to point at the cause.
|
|
1372
|
-
// \`apikey\` wins when both are set, so a config that
|
|
1373
|
-
// unaffected.
|
|
1374
|
-
const apikey = res?.keymap?.apikey?.value
|
|
1411
|
+
// \`apikey\` wins when both are set and it is not empty, so a config that
|
|
1412
|
+
// has migrated is unaffected.
|
|
1413
|
+
const apikey = [res?.keymap?.apikey?.value, res?.keymap?.api?.value]
|
|
1414
|
+
.find((value: any) => null != value && '' !== value)
|
|
1375
1415
|
|
|
1376
1416
|
// Hand the credential to the SDK as \`apikey\`, NOT as an authorization
|
|
1377
1417
|
// HEADER. The SDK's own auth stage owns that header: it reads
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Content,
|
|
3
|
+
File,
|
|
4
|
+
cmp,
|
|
5
|
+
} from '@voxgig/sdkgen'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const HEAD = `# GENERATED by @voxgig/sdkgen. Do not edit.
|
|
9
|
+
|
|
10
|
+
.PHONY: all build test clean reset sdk-src sdk-clean regen
|
|
11
|
+
|
|
12
|
+
all: build test
|
|
13
|
+
|
|
14
|
+
build:
|
|
15
|
+
\tnpm run build
|
|
16
|
+
|
|
17
|
+
test:
|
|
18
|
+
\tnpm test
|
|
19
|
+
|
|
20
|
+
clean:
|
|
21
|
+
\trm -rf dist dist-test .tsbuildinfo
|
|
22
|
+
|
|
23
|
+
reset:
|
|
24
|
+
\tnpm run reset
|
|
25
|
+
|
|
26
|
+
`
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
const INTRO_SDK = `# ---------------------------------------------------------------------------
|
|
30
|
+
# THIS REPOSITORY IS GENERATED, AND CAN REGENERATE ITSELF.
|
|
31
|
+
#
|
|
32
|
+
# Everything below fetches the SDK named in sdk-pin.json and runs the
|
|
33
|
+
# generator from it. Nothing here needs a sibling checkout or a particular
|
|
34
|
+
# directory layout: the pin names a repository and a TAG, and the checkout
|
|
35
|
+
# lands inside this repo at a fixed path.
|
|
36
|
+
#
|
|
37
|
+
# NOT a git submodule, deliberately. A submodule pins a commit in git's own
|
|
38
|
+
# plumbing, where a stale one is invisible in a normal diff and updating it is
|
|
39
|
+
# a second repository operation. sdk-pin.json is an ordinary committed file,
|
|
40
|
+
# regenerated from the model with everything else, so it moves with the SDK
|
|
41
|
+
# version it belongs to and shows up in review.
|
|
42
|
+
# ---------------------------------------------------------------------------`
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
// A provider whose builder is its own repository's .sdk/ (`output: root`).
|
|
46
|
+
const INTRO_SELF = `# ---------------------------------------------------------------------------
|
|
47
|
+
# THIS REPOSITORY IS GENERATED, BY ITS OWN BUILDER IN .sdk/.
|
|
48
|
+
#
|
|
49
|
+
# The builder generates this package and nothing else. Its API definition
|
|
50
|
+
# and guide are the SDK's: everything below fetches the SDK named in
|
|
51
|
+
# sdk-pin.json, and \`make regen\` replaces them with its own before
|
|
52
|
+
# generating, so the package is built from the same model as the SDK version
|
|
53
|
+
# it depends on.
|
|
54
|
+
#
|
|
55
|
+
# NOT a git submodule, deliberately. A submodule pins a commit in git's own
|
|
56
|
+
# plumbing, where a stale one is invisible in a normal diff and updating it is
|
|
57
|
+
# a second repository operation. sdk-pin.json is an ordinary committed file,
|
|
58
|
+
# regenerated from the model with everything else, so it moves with the SDK
|
|
59
|
+
# version it belongs to and shows up in review.
|
|
60
|
+
# ---------------------------------------------------------------------------`
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
const FETCH = `
|
|
64
|
+
SDK_PIN := sdk-pin.json
|
|
65
|
+
SDK_REPO := $(shell node -p "require('./$(SDK_PIN)').repo" 2>/dev/null)
|
|
66
|
+
SDK_TAG := $(shell node -p "require('./$(SDK_PIN)').tag" 2>/dev/null)
|
|
67
|
+
SDK_DIR := $(shell node -p "require('./$(SDK_PIN)').dir" 2>/dev/null)
|
|
68
|
+
|
|
69
|
+
# Point at a checkout you already have instead of fetching one:
|
|
70
|
+
# make sdk-src SDK_SRC_FROM=../path/to/sdk
|
|
71
|
+
SDK_SRC_FROM ?=
|
|
72
|
+
|
|
73
|
+
# Fetch (or update) the SDK source at the pinned tag.
|
|
74
|
+
#
|
|
75
|
+
# A TAG, not a branch: the point is the exact revision this repository was
|
|
76
|
+
# generated from, so that regenerating reproduces what is committed here
|
|
77
|
+
# rather than whatever main happens to say today.
|
|
78
|
+
sdk-src:
|
|
79
|
+
\t@set -e; \\
|
|
80
|
+
\ttest -n "$(SDK_REPO)" || { echo "sdk-src: no repo in $(SDK_PIN)"; exit 1; }; \\
|
|
81
|
+
\ttest -n "$(SDK_TAG)" || { echo "sdk-src: no tag in $(SDK_PIN)"; exit 1; }; \\
|
|
82
|
+
\tif [ -n "$(SDK_SRC_FROM)" ]; then \\
|
|
83
|
+
\t echo "sdk-src: using $(SDK_SRC_FROM) (not fetching)"; \\
|
|
84
|
+
\t rm -rf "$(SDK_DIR)"; mkdir -p "$$(dirname "$(SDK_DIR)")"; \\
|
|
85
|
+
\t ln -s "$$(cd "$(SDK_SRC_FROM)" && pwd)" "$(SDK_DIR)"; \\
|
|
86
|
+
\t exit 0; \\
|
|
87
|
+
\tfi; \\
|
|
88
|
+
\tif [ -L "$(SDK_DIR)" ]; then \\
|
|
89
|
+
\t echo "sdk-src: $(SDK_DIR) links to $$(readlink "$(SDK_DIR)"), which is left as it is;"; \\
|
|
90
|
+
\t echo " make sdk-clean first to fetch $(SDK_TAG) instead"; \\
|
|
91
|
+
\t exit 0; \\
|
|
92
|
+
\tfi; \\
|
|
93
|
+
\tif [ -d "$(SDK_DIR)/.git" ]; then \\
|
|
94
|
+
\t echo "sdk-src: fetching $(SDK_TAG) in $(SDK_DIR)"; \\
|
|
95
|
+
\t git -C "$(SDK_DIR)" fetch --depth 1 origin "refs/tags/$(SDK_TAG):refs/tags/$(SDK_TAG)" 2>/dev/null || \\
|
|
96
|
+
\t git -C "$(SDK_DIR)" fetch origin "refs/tags/$(SDK_TAG):refs/tags/$(SDK_TAG)"; \\
|
|
97
|
+
\t git -C "$(SDK_DIR)" checkout -q "refs/tags/$(SDK_TAG)"; \\
|
|
98
|
+
\telse \\
|
|
99
|
+
\t echo "sdk-src: cloning $(SDK_REPO) at $(SDK_TAG) into $(SDK_DIR)"; \\
|
|
100
|
+
\t rm -rf "$(SDK_DIR)"; mkdir -p "$$(dirname "$(SDK_DIR)")"; \\
|
|
101
|
+
\t git clone --depth 1 --branch "$(SDK_TAG)" "$(SDK_REPO).git" "$(SDK_DIR)"; \\
|
|
102
|
+
\tfi; \\
|
|
103
|
+
\techo "sdk-src: $(SDK_DIR) is at $(SDK_TAG)"
|
|
104
|
+
|
|
105
|
+
# Removes the fetched checkout AND the folder that held it. \`rm -rf\` on the
|
|
106
|
+
# checkout alone left an empty \`.sdksrc/\` behind — gitignored, so harmless,
|
|
107
|
+
# but it makes \`sdk-clean\` look like it did not finish. rmdir rather than
|
|
108
|
+
# another rm -rf: if anything else is in there, someone put it there, and
|
|
109
|
+
# removing it is not this target's business.
|
|
110
|
+
sdk-clean:
|
|
111
|
+
\trm -rf "$(SDK_DIR)"
|
|
112
|
+
\t@rmdir "$$(dirname "$(SDK_DIR)")" 2>/dev/null || true
|
|
113
|
+
`
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
const REGEN_SDK = `# Regenerate this repository from the pinned SDK.
|
|
117
|
+
#
|
|
118
|
+
# The SDK's model says where its provider goes, and that value is a fact about
|
|
119
|
+
# ONE developer's checkout layout — it cannot describe being cloned in here.
|
|
120
|
+
# So the destination is given to the generator AT RUN TIME instead:
|
|
121
|
+
#
|
|
122
|
+
# path ../.. — from the SDK checkout up to this repository root
|
|
123
|
+
# enclosing the output folder CONTAINS the SDK project, which the
|
|
124
|
+
# generator refuses unless asked, because one \`..\` too many
|
|
125
|
+
# writes a package over an unrelated repo
|
|
126
|
+
#
|
|
127
|
+
# See @voxgig/sdkgen docs/explanation/out-of-tree-targets.md.
|
|
128
|
+
# The generate-time output override arrived in @voxgig/sdkgen 4.16.0, and
|
|
129
|
+
# older versions IGNORE it SILENTLY: generation falls back to the destination
|
|
130
|
+
# in the SDK's own model, which from in here resolves to a folder that does
|
|
131
|
+
# not exist, gets skipped, and reports success having written nothing. That is
|
|
132
|
+
# not hypothetical — it is what this target did on its first end-to-end run,
|
|
133
|
+
# against a pin whose floor was set one release too low.
|
|
134
|
+
#
|
|
135
|
+
# So the floor is the version that actually ships the feature, not the version
|
|
136
|
+
# that happened to be current when it was written.
|
|
137
|
+
SDKGEN_MIN := 4.16.0
|
|
138
|
+
|
|
139
|
+
regen: sdk-src
|
|
140
|
+
\t@set -e; \\
|
|
141
|
+
\tif [ -L "$(SDK_DIR)" ]; then \\
|
|
142
|
+
\t echo "regen: $(SDK_DIR) is a symlink, and regenerating through one writes"; \\
|
|
143
|
+
\t echo " the package somewhere else. Node resolves a module's own"; \\
|
|
144
|
+
\t echo " directory through symlinks, so the generator computes its"; \\
|
|
145
|
+
\t echo " destination from where the checkout REALLY is — not from"; \\
|
|
146
|
+
\t echo " here — and \\\`enclosing\\\` then permits the result."; \\
|
|
147
|
+
\t echo " SDK_SRC_FROM is for building and linking against a local"; \\
|
|
148
|
+
\t echo " SDK (make sdk-src). To regenerate, use a fetched checkout:"; \\
|
|
149
|
+
\t echo " make sdk-clean && make regen"; \\
|
|
150
|
+
\t exit 1; \\
|
|
151
|
+
\tfi; \\
|
|
152
|
+
\techo "regen: building the generator in $(SDK_DIR)/.sdk"; \\
|
|
153
|
+
\tnpm --prefix "$(SDK_DIR)/.sdk" install; \\
|
|
154
|
+
\thave=$$(node -e "console.log(JSON.parse(require('fs').readFileSync('$(SDK_DIR)/.sdk/node_modules/@voxgig/sdkgen/package.json','utf8')).version)"); \\
|
|
155
|
+
\tnode -e 'const a=process.argv[1].split(".").map(Number),b=process.argv[2].split(".").map(Number); \\
|
|
156
|
+
\t for(let i=0;i<3;i++){if(a[i]>b[i])process.exit(0);if(a[i]<b[i])process.exit(1)} process.exit(0)' \\
|
|
157
|
+
\t "$$have" "$(SDKGEN_MIN)" || { \\
|
|
158
|
+
\t echo "regen: $(SDK_TAG) uses @voxgig/sdkgen $$have, and regenerating from"; \\
|
|
159
|
+
\t echo " inside this repo needs >= $(SDKGEN_MIN) (the generate-time output"; \\
|
|
160
|
+
\t echo " override). Older versions IGNORE it and silently write nothing."; \\
|
|
161
|
+
\t echo " Pin a newer SDK tag, or regenerate from a checkout of the SDK."; \\
|
|
162
|
+
\t exit 1; }; \\
|
|
163
|
+
\techo "regen: generating"; \\
|
|
164
|
+
\tcd "$(SDK_DIR)/.sdk" && \\
|
|
165
|
+
\t SDKGEN_EXTERNAL='{"seneca-provider":{"path":"../..","enclosing":true}}' \\
|
|
166
|
+
\t npm run generate
|
|
167
|
+
\t@echo "regen: done — review the diff in this repository"
|
|
168
|
+
`
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
const REGEN_SELF = `# Regenerate this repository with its own builder.
|
|
172
|
+
#
|
|
173
|
+
# The API definition and guide are replaced by the SDK source's first, so the
|
|
174
|
+
# builder generates from the SDK's model at the pinned tag, and generation
|
|
175
|
+
# checks the result against that source before writing. A linked checkout
|
|
176
|
+
# (SDK_SRC_FROM) is read as it is, and only this repository is written.
|
|
177
|
+
#
|
|
178
|
+
# To move to a newer SDK, set its version in .sdk/model/project.aontu and
|
|
179
|
+
# fetch that tag as you regenerate:
|
|
180
|
+
#
|
|
181
|
+
# make regen SDK_TAG=v1.2.3
|
|
182
|
+
regen: sdk-src
|
|
183
|
+
\t@set -e; \\
|
|
184
|
+
\ttest -f .sdk/package.json || { echo "regen: no builder at .sdk/"; exit 1; }; \\
|
|
185
|
+
\tfor d in def model/guide; do \\
|
|
186
|
+
\t test -d "$(SDK_DIR)/.sdk/$$d" || { echo "regen: no $(SDK_DIR)/.sdk/$$d"; exit 1; }; \\
|
|
187
|
+
\tdone; \\
|
|
188
|
+
\techo "regen: replacing the API definition and guide with those in $(SDK_DIR)"; \\
|
|
189
|
+
\tfor d in def model/guide; do \\
|
|
190
|
+
\t rm -rf ".sdk/$$d"; mkdir -p ".sdk/$$d"; cp -R "$(SDK_DIR)/.sdk/$$d/." ".sdk/$$d/"; \\
|
|
191
|
+
\tdone; \\
|
|
192
|
+
\techo "regen: installing the builder"; \\
|
|
193
|
+
\tcd .sdk && npm install && \\
|
|
194
|
+
\techo "regen: generating" && \\
|
|
195
|
+
\tnpm run generate
|
|
196
|
+
\t@echo "regen: done — review the diff in this repository"
|
|
197
|
+
`
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
const Makefile = cmp(function Makefile(props: any) {
|
|
201
|
+
const { provider } = props
|
|
202
|
+
|
|
203
|
+
File({ name: 'Makefile' }, () => {
|
|
204
|
+
Content([
|
|
205
|
+
HEAD,
|
|
206
|
+
provider.standalone ? INTRO_SELF : INTRO_SDK,
|
|
207
|
+
FETCH,
|
|
208
|
+
provider.standalone ? REGEN_SELF : REGEN_SDK,
|
|
209
|
+
].join('\n'))
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
export {
|
|
215
|
+
Makefile,
|
|
216
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import Path from 'node:path'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
packageName, packageVersion,
|
|
5
|
+
SdkGenError,
|
|
6
|
+
} from '@voxgig/sdkgen'
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
KIT,
|
|
10
|
+
} from '@voxgig/apidef'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// `output: root: true` means this repository's own `.sdk/` is the builder:
|
|
14
|
+
// the SDK is released elsewhere, and is not a target of this project.
|
|
15
|
+
function standaloneBuilder(target: any): boolean {
|
|
16
|
+
return true === target?.output?.root
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
function sdkIdentity(model: any, target: any):
|
|
21
|
+
{ sdkPkg: string, sdkVersion: string } {
|
|
22
|
+
const sdk = target?.sdk || {}
|
|
23
|
+
const pkg = String(sdk.package || '')
|
|
24
|
+
const version = String(sdk.version || '')
|
|
25
|
+
const hasTs = null != model?.main?.[KIT]?.target?.ts
|
|
26
|
+
|
|
27
|
+
if (!standaloneBuilder(target)) {
|
|
28
|
+
if ('' !== pkg || '' !== version) {
|
|
29
|
+
throw new SdkGenError(
|
|
30
|
+
target.name + ': `sdk.package` and `sdk.version` apply only to a ' +
|
|
31
|
+
'provider generated at the root of its own repository (`output: ' +
|
|
32
|
+
'root: true`). Generated from the SDK project, the SDK is its `ts` ' +
|
|
33
|
+
'target: set `main.' + KIT + '.target.ts.publish.version` instead.')
|
|
34
|
+
}
|
|
35
|
+
return { sdkPkg: packageName(model, 'npm'), sdkVersion: packageVersion(model, 'ts') }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if ('' === version && !hasTs) {
|
|
39
|
+
throw new SdkGenError(
|
|
40
|
+
target.name + ': name the SDK version this provider depends on, as ' +
|
|
41
|
+
'`main.' + KIT + '.target.' + target.name + '.sdk.version`. Its ' +
|
|
42
|
+
'builder has no `ts` target to read it from, and a default would pin ' +
|
|
43
|
+
'whatever version it happens to be.')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
sdkPkg: '' !== pkg ? pkg : packageName(model, 'npm'),
|
|
48
|
+
sdkVersion: '' !== version ? version : packageVersion(model, 'ts'),
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
// What the SDK was generated from and the provider reads: titles,
|
|
54
|
+
// descriptions and contract metadata are left out.
|
|
55
|
+
const FIELD_KEYS = ['n', 't', 'r', 'a', 'ro', 'wo', 'fo']
|
|
56
|
+
const POINT_KEYS = ['a', 'k', 'm', 'o', 's', 'r', 't', 'g', 'q', 'gq']
|
|
57
|
+
|
|
58
|
+
function pick(obj: any, keys: string[]): Record<string, any> {
|
|
59
|
+
const out: Record<string, any> = {}
|
|
60
|
+
for (const key of keys) {
|
|
61
|
+
if (undefined !== obj?.[key]) {
|
|
62
|
+
out[key] = obj[key]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return out
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
// Keyed by path under `main.kit`, so a difference names where to look.
|
|
70
|
+
function apiSurface(model: any): Record<string, any> {
|
|
71
|
+
const kit = model?.main?.[KIT] || {}
|
|
72
|
+
const out: Record<string, any> = {
|
|
73
|
+
info: {
|
|
74
|
+
auth: kit.info?.auth,
|
|
75
|
+
security: kit.info?.security,
|
|
76
|
+
servers: (kit.info?.servers || []).map((server: any) => pick(server, ['url'])),
|
|
77
|
+
},
|
|
78
|
+
config: { auth: kit.config?.auth },
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const coll = kit.entity || {}
|
|
82
|
+
for (const name of Object.keys(coll).sort()) {
|
|
83
|
+
const ent = coll[name]
|
|
84
|
+
if (null != ent && false !== ent.active) {
|
|
85
|
+
out['entity.' + name] = {
|
|
86
|
+
id: ent.id,
|
|
87
|
+
alias: ent.alias,
|
|
88
|
+
relations: { ancestors: ent.relations?.ancestors },
|
|
89
|
+
fields: Object.fromEntries(Object.entries(ent.fields || {})
|
|
90
|
+
.map(([key, field]) => [key, pick(field, FIELD_KEYS)])),
|
|
91
|
+
op: Object.fromEntries(Object.entries(ent.op || {})
|
|
92
|
+
.map(([key, op]: [string, any]) => [key, {
|
|
93
|
+
input: op?.input,
|
|
94
|
+
points: (op?.points || []).map((point: any) => pick(point, POINT_KEYS)),
|
|
95
|
+
}])),
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return out
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
function brief(value: any): string {
|
|
105
|
+
const text = undefined === value ? 'absent' : JSON.stringify(value)
|
|
106
|
+
return 60 < text.length ? text.slice(0, 57) + '...' : text
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
// Keys ending in `$` are the generator's marks, present only on a live model.
|
|
111
|
+
function differences(here: any, there: any, path: string, out: string[]): void {
|
|
112
|
+
const node = (value: any) => null != value && 'object' === typeof value
|
|
113
|
+
if (node(here) && node(there) && Array.isArray(here) === Array.isArray(there)) {
|
|
114
|
+
const keys = Array.isArray(here) ?
|
|
115
|
+
Array.from({ length: Math.max(here.length, there.length) }, (_, i) => i) :
|
|
116
|
+
[...new Set([...Object.keys(here), ...Object.keys(there)])]
|
|
117
|
+
.filter((key) => !key.endsWith('$')).sort()
|
|
118
|
+
for (const key of keys) {
|
|
119
|
+
differences(here[key], there[key], Array.isArray(here) ?
|
|
120
|
+
path + '[' + key + ']' : path + '.' + key, out)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else if (JSON.stringify(here) !== JSON.stringify(there)) {
|
|
124
|
+
out.push(path + ': here ' + brief(here) + ', SDK ' + brief(there))
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
// The builder carries its own copy of the SDK's API definition, right only
|
|
130
|
+
// while it matches the SDK at the version depended on. `make sdk-src` fetches
|
|
131
|
+
// that SDK, whose compiled model is the comparison.
|
|
132
|
+
function checkSdkSource(ctx$: any, provider: any, model: any): void {
|
|
133
|
+
const fs = ctx$.fs()
|
|
134
|
+
const rel = provider.sdkSrc + '/.sdk/model/sdk.json'
|
|
135
|
+
const file = Path.join(ctx$.folder || '.', rel)
|
|
136
|
+
|
|
137
|
+
if (!fs.existsSync(file)) {
|
|
138
|
+
ctx$.log?.warn?.({
|
|
139
|
+
point: 'sdk-source-unchecked', file: rel,
|
|
140
|
+
note: 'seneca-provider: ' + rel + ' is absent, so this model was NOT ' +
|
|
141
|
+
'checked against ' + provider.sdkPkg + ' ' + provider.sdkVersion +
|
|
142
|
+
'. `make sdk-src` fetches it.'
|
|
143
|
+
})
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const sdk = JSON.parse(String(fs.readFileSync(file, 'utf8')))
|
|
148
|
+
const problems: string[] = []
|
|
149
|
+
|
|
150
|
+
// The SDK's own manifest, when fetched: this builder's naming rule need not
|
|
151
|
+
// be the one that built the SDK.
|
|
152
|
+
const manifestFile = Path.join(ctx$.folder || '.', provider.sdkSrc, 'ts', 'package.json')
|
|
153
|
+
const manifest = fs.existsSync(manifestFile) ?
|
|
154
|
+
JSON.parse(String(fs.readFileSync(manifestFile, 'utf8'))) : {}
|
|
155
|
+
|
|
156
|
+
const sdkVersion = String(manifest.version || packageVersion(sdk, 'ts'))
|
|
157
|
+
if (sdkVersion !== provider.sdkVersion) {
|
|
158
|
+
problems.push('version: this provider depends on ' + provider.sdkVersion +
|
|
159
|
+
', the fetched SDK is ' + sdkVersion +
|
|
160
|
+
' (make regen SDK_TAG=v' + provider.sdkVersion + ')')
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const sdkPkg = String(manifest.name || packageName(sdk, 'npm'))
|
|
164
|
+
if (sdkPkg !== provider.sdkPkg) {
|
|
165
|
+
problems.push('package: this provider depends on ' + provider.sdkPkg +
|
|
166
|
+
', the fetched SDK is ' + sdkPkg)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const here = apiSurface(model)
|
|
170
|
+
const there = apiSurface(sdk)
|
|
171
|
+
|
|
172
|
+
for (const part of [...new Set([...Object.keys(here), ...Object.keys(there)])].sort()) {
|
|
173
|
+
const found: string[] = []
|
|
174
|
+
differences(here[part], there[part], part, found)
|
|
175
|
+
problems.push(...found.slice(0, 5))
|
|
176
|
+
if (5 < found.length) {
|
|
177
|
+
problems.push(part + ': and ' + (found.length - 5) + ' more')
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (0 < problems.length) {
|
|
182
|
+
throw new SdkGenError(
|
|
183
|
+
'seneca-provider: this builder\'s model does not match the SDK it ' +
|
|
184
|
+
'depends on, as fetched to ' + provider.sdkSrc + ' (paths under main.' +
|
|
185
|
+
KIT + '):\n ' +
|
|
186
|
+
problems.join('\n ') +
|
|
187
|
+
'\n`make regen` replaces the API definition and guide in .sdk/ with the ' +
|
|
188
|
+
'SDK\'s before generating. A difference that remains comes from a decision in ' +
|
|
189
|
+
'the SDK project\'s own model, to be declared the same way in ' +
|
|
190
|
+
'.sdk/model/project.aontu, or from an @voxgig/apidef version other ' +
|
|
191
|
+
'than the one in the SDK\'s .sdk/package.json.')
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
export {
|
|
197
|
+
standaloneBuilder,
|
|
198
|
+
sdkIdentity,
|
|
199
|
+
apiSurface,
|
|
200
|
+
checkSdkSource,
|
|
201
|
+
}
|
package/README.md
CHANGED
|
@@ -27,7 +27,9 @@ sdkgen repository.
|
|
|
27
27
|
|
|
28
28
|
These are consumer targets: they switch every standard generation phase off and
|
|
29
29
|
emit their whole package from `Main`, and each one fails without the target it
|
|
30
|
-
wraps, deliberately. Add `ts` to your project before `seneca-provider`.
|
|
30
|
+
wraps, deliberately. Add `ts` to your project before `seneca-provider`. The
|
|
31
|
+
one exception is a provider that carries its own builder (below), which names
|
|
32
|
+
the SDK it wraps instead.
|
|
31
33
|
|
|
32
34
|
There is no manifest field for that requirement. Nothing in
|
|
33
35
|
`sdkgen-package.json` says "this target needs that one", so this paragraph and
|
|
@@ -84,6 +86,75 @@ first look at the output.
|
|
|
84
86
|
Set that in `model/project.aontu`, not in the target's own file — `target add`
|
|
85
87
|
overwrites the latter.
|
|
86
88
|
|
|
89
|
+
## Or the provider carries its own builder
|
|
90
|
+
|
|
91
|
+
The provider repository can instead hold a `.sdk/` of its own, which generates
|
|
92
|
+
the provider and nothing else, and leaves the SDK repository untouched. Its
|
|
93
|
+
`model/project.aontu` says so, and the standard `Root` that create-sdkgen
|
|
94
|
+
scaffolds reads it:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
main: kit: phase: top: active: false
|
|
98
|
+
main: kit: phase: build: active: false
|
|
99
|
+
main: kit: doc: active: false
|
|
100
|
+
main: kit: target: 'seneca-provider': output: root: true
|
|
101
|
+
main: kit: target: 'seneca-provider': sdk: version: '0.0.1'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`output: root: true` generates the provider at the repository root, and is how
|
|
105
|
+
this target knows the builder is the provider's own. That builder has no `ts`
|
|
106
|
+
target, so it names the SDK itself: `sdk.version` is required, and
|
|
107
|
+
`sdk.package` overrides the derived package name. Both are refused in an SDK
|
|
108
|
+
project, where the `ts` target is the SDK.
|
|
109
|
+
|
|
110
|
+
The builder carries a copy of the SDK's API definition and guide. `make regen`
|
|
111
|
+
replaces them with those of the SDK fetched at the tag in `sdk-pin.json` before
|
|
112
|
+
generating. Generation then refuses to write when the builder's model and that
|
|
113
|
+
SDK's compiled model differ in what the SDK was generated from: an entity, an
|
|
114
|
+
operation's routes, parameters and actions, a field's type or requiredness, the
|
|
115
|
+
identity, the ancestors, the authentication or the servers. Titles and
|
|
116
|
+
descriptions are not compared. It also refuses when the package name or version
|
|
117
|
+
differ from the SDK's own `ts/package.json`, which is read rather than the name
|
|
118
|
+
re-derived, because the SDK's generator may name packages by a rule this
|
|
119
|
+
builder's does not share. Generated without the SDK fetched, it warns that the
|
|
120
|
+
check did not run.
|
|
121
|
+
|
|
122
|
+
A symlinked SDK checkout, from `make sdk-src SDK_SRC_FROM=<path>`, is read as
|
|
123
|
+
it is: `make sdk-src` never fetches or checks out a tag inside one.
|
|
124
|
+
|
|
125
|
+
To move to a newer SDK, set `sdk.version` and fetch its tag as you regenerate:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
make regen SDK_TAG=v0.0.2
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## An SDK that is not on npm yet
|
|
132
|
+
|
|
133
|
+
The generated `package.json` depends on the SDK by version range, which
|
|
134
|
+
installs only once the SDK is published. Until then, `sdk.dep` points it
|
|
135
|
+
elsewhere:
|
|
136
|
+
|
|
137
|
+
| `sdk.dep` | The dependency |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `kind: 'release', ref: 'v1.0.0'` | the `npm pack` tarball attached to that GitHub release |
|
|
140
|
+
| `kind: 'git', ref: '<tag>'` | `github:<owner>/<repo>#<tag>`, installed from the root of that tag's tree |
|
|
141
|
+
| `spec: '<anything>'` | the value, verbatim |
|
|
142
|
+
|
|
143
|
+
npm cannot install a package from a subfolder of a git repository, and an
|
|
144
|
+
sdkgen SDK keeps its TypeScript package in `ts/`. A git dependency therefore
|
|
145
|
+
needs a tag whose tree is that folder, cut in the SDK repository:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
git tag ts-v1.0.0 $(git commit-tree HEAD:ts -p HEAD -m "ts/ at v1.0.0")
|
|
149
|
+
git push origin ts-v1.0.0
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
and then `sdk: dep: { kind: 'git', ref: 'ts-v1.0.0' }`. The tag carries what
|
|
153
|
+
the folder holds, so an SDK that commits `ts/dist` needs nothing built on
|
|
154
|
+
install. The generated CI passes `--allow-git=all`, which npm 12 needs for a
|
|
155
|
+
git dependency, and so would anyone installing a published provider that
|
|
156
|
+
depends on one: publish the SDK to npm before the provider.
|
|
157
|
+
|
|
87
158
|
## Parity
|
|
88
159
|
|
|
89
160
|
Every target here declares `CONSUMER`. That is not a coverage tier alongside
|
package/package.json
CHANGED
package/sdkgen-package.json
CHANGED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
# GENERATED by @voxgig/sdkgen. Do not edit.
|
|
2
|
-
|
|
3
|
-
.PHONY: all build test clean reset sdk-src sdk-clean regen
|
|
4
|
-
|
|
5
|
-
all: build test
|
|
6
|
-
|
|
7
|
-
build:
|
|
8
|
-
npm run build
|
|
9
|
-
|
|
10
|
-
test:
|
|
11
|
-
npm test
|
|
12
|
-
|
|
13
|
-
clean:
|
|
14
|
-
rm -rf dist dist-test .tsbuildinfo
|
|
15
|
-
|
|
16
|
-
reset:
|
|
17
|
-
npm run reset
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
# ---------------------------------------------------------------------------
|
|
21
|
-
# THIS REPOSITORY IS GENERATED, AND CAN REGENERATE ITSELF.
|
|
22
|
-
#
|
|
23
|
-
# Everything below fetches the SDK named in sdk-pin.json and runs the
|
|
24
|
-
# generator from it. Nothing here needs a sibling checkout or a particular
|
|
25
|
-
# directory layout: the pin names a repository and a TAG, and the checkout
|
|
26
|
-
# lands inside this repo at a fixed path.
|
|
27
|
-
#
|
|
28
|
-
# NOT a git submodule, deliberately. A submodule pins a commit in git's own
|
|
29
|
-
# plumbing, where a stale one is invisible in a normal diff and updating it is
|
|
30
|
-
# a second repository operation. sdk-pin.json is an ordinary committed file,
|
|
31
|
-
# regenerated from the model with everything else, so it moves with the SDK
|
|
32
|
-
# version it belongs to and shows up in review.
|
|
33
|
-
# ---------------------------------------------------------------------------
|
|
34
|
-
|
|
35
|
-
SDK_PIN := sdk-pin.json
|
|
36
|
-
SDK_REPO := $(shell node -p "require('./$(SDK_PIN)').repo" 2>/dev/null)
|
|
37
|
-
SDK_TAG := $(shell node -p "require('./$(SDK_PIN)').tag" 2>/dev/null)
|
|
38
|
-
SDK_DIR := $(shell node -p "require('./$(SDK_PIN)').dir" 2>/dev/null)
|
|
39
|
-
|
|
40
|
-
# Point at a checkout you already have instead of fetching one:
|
|
41
|
-
# make sdk-src SDK_SRC_FROM=../path/to/sdk
|
|
42
|
-
SDK_SRC_FROM ?=
|
|
43
|
-
|
|
44
|
-
# Fetch (or update) the SDK source at the pinned tag.
|
|
45
|
-
#
|
|
46
|
-
# A TAG, not a branch: the point is the exact revision this repository was
|
|
47
|
-
# generated from, so that regenerating reproduces what is committed here
|
|
48
|
-
# rather than whatever main happens to say today.
|
|
49
|
-
sdk-src:
|
|
50
|
-
@set -e; \
|
|
51
|
-
test -n "$(SDK_REPO)" || { echo "sdk-src: no repo in $(SDK_PIN)"; exit 1; }; \
|
|
52
|
-
test -n "$(SDK_TAG)" || { echo "sdk-src: no tag in $(SDK_PIN)"; exit 1; }; \
|
|
53
|
-
if [ -n "$(SDK_SRC_FROM)" ]; then \
|
|
54
|
-
echo "sdk-src: using $(SDK_SRC_FROM) (not fetching)"; \
|
|
55
|
-
rm -rf "$(SDK_DIR)"; mkdir -p "$$(dirname "$(SDK_DIR)")"; \
|
|
56
|
-
ln -s "$$(cd "$(SDK_SRC_FROM)" && pwd)" "$(SDK_DIR)"; \
|
|
57
|
-
exit 0; \
|
|
58
|
-
fi; \
|
|
59
|
-
if [ -d "$(SDK_DIR)/.git" ]; then \
|
|
60
|
-
echo "sdk-src: fetching $(SDK_TAG) in $(SDK_DIR)"; \
|
|
61
|
-
git -C "$(SDK_DIR)" fetch --depth 1 origin "refs/tags/$(SDK_TAG):refs/tags/$(SDK_TAG)" 2>/dev/null || \
|
|
62
|
-
git -C "$(SDK_DIR)" fetch origin "refs/tags/$(SDK_TAG):refs/tags/$(SDK_TAG)"; \
|
|
63
|
-
git -C "$(SDK_DIR)" checkout -q "refs/tags/$(SDK_TAG)"; \
|
|
64
|
-
else \
|
|
65
|
-
echo "sdk-src: cloning $(SDK_REPO) at $(SDK_TAG) into $(SDK_DIR)"; \
|
|
66
|
-
rm -rf "$(SDK_DIR)"; mkdir -p "$$(dirname "$(SDK_DIR)")"; \
|
|
67
|
-
git clone --depth 1 --branch "$(SDK_TAG)" "$(SDK_REPO).git" "$(SDK_DIR)"; \
|
|
68
|
-
fi; \
|
|
69
|
-
echo "sdk-src: $(SDK_DIR) is at $(SDK_TAG)"
|
|
70
|
-
|
|
71
|
-
# Removes the fetched checkout AND the folder that held it. `rm -rf` on the
|
|
72
|
-
# checkout alone left an empty `.sdksrc/` behind — gitignored, so harmless,
|
|
73
|
-
# but it makes `sdk-clean` look like it did not finish. rmdir rather than
|
|
74
|
-
# another rm -rf: if anything else is in there, someone put it there, and
|
|
75
|
-
# removing it is not this target's business.
|
|
76
|
-
sdk-clean:
|
|
77
|
-
rm -rf "$(SDK_DIR)"
|
|
78
|
-
@rmdir "$$(dirname "$(SDK_DIR)")" 2>/dev/null || true
|
|
79
|
-
|
|
80
|
-
# Regenerate this repository from the pinned SDK.
|
|
81
|
-
#
|
|
82
|
-
# The SDK's model says where its provider goes, and that value is a fact about
|
|
83
|
-
# ONE developer's checkout layout — it cannot describe being cloned in here.
|
|
84
|
-
# So the destination is given to the generator AT RUN TIME instead:
|
|
85
|
-
#
|
|
86
|
-
# path ../.. — from the SDK checkout up to this repository root
|
|
87
|
-
# enclosing the output folder CONTAINS the SDK project, which the
|
|
88
|
-
# generator refuses unless asked, because one `..` too many
|
|
89
|
-
# writes a package over an unrelated repo
|
|
90
|
-
#
|
|
91
|
-
# See @voxgig/sdkgen docs/explanation/out-of-tree-targets.md.
|
|
92
|
-
# The generate-time output override arrived in @voxgig/sdkgen 4.16.0, and
|
|
93
|
-
# older versions IGNORE it SILENTLY: generation falls back to the destination
|
|
94
|
-
# in the SDK's own model, which from in here resolves to a folder that does
|
|
95
|
-
# not exist, gets skipped, and reports success having written nothing. That is
|
|
96
|
-
# not hypothetical — it is what this target did on its first end-to-end run,
|
|
97
|
-
# against a pin whose floor was set one release too low.
|
|
98
|
-
#
|
|
99
|
-
# So the floor is the version that actually ships the feature, not the version
|
|
100
|
-
# that happened to be current when it was written.
|
|
101
|
-
SDKGEN_MIN := 4.16.0
|
|
102
|
-
|
|
103
|
-
regen: sdk-src
|
|
104
|
-
@set -e; \
|
|
105
|
-
if [ -L "$(SDK_DIR)" ]; then \
|
|
106
|
-
echo "regen: $(SDK_DIR) is a symlink, and regenerating through one writes"; \
|
|
107
|
-
echo " the package somewhere else. Node resolves a module's own"; \
|
|
108
|
-
echo " directory through symlinks, so the generator computes its"; \
|
|
109
|
-
echo " destination from where the checkout REALLY is — not from"; \
|
|
110
|
-
echo " here — and \`enclosing\` then permits the result."; \
|
|
111
|
-
echo " SDK_SRC_FROM is for building and linking against a local"; \
|
|
112
|
-
echo " SDK (make sdk-src). To regenerate, use a fetched checkout:"; \
|
|
113
|
-
echo " make sdk-clean && make regen"; \
|
|
114
|
-
exit 1; \
|
|
115
|
-
fi; \
|
|
116
|
-
echo "regen: building the generator in $(SDK_DIR)/.sdk"; \
|
|
117
|
-
npm --prefix "$(SDK_DIR)/.sdk" install; \
|
|
118
|
-
have=$$(node -e "console.log(JSON.parse(require('fs').readFileSync('$(SDK_DIR)/.sdk/node_modules/@voxgig/sdkgen/package.json','utf8')).version)"); \
|
|
119
|
-
node -e 'const a=process.argv[1].split(".").map(Number),b=process.argv[2].split(".").map(Number); \
|
|
120
|
-
for(let i=0;i<3;i++){if(a[i]>b[i])process.exit(0);if(a[i]<b[i])process.exit(1)} process.exit(0)' \
|
|
121
|
-
"$$have" "$(SDKGEN_MIN)" || { \
|
|
122
|
-
echo "regen: $(SDK_TAG) uses @voxgig/sdkgen $$have, and regenerating from"; \
|
|
123
|
-
echo " inside this repo needs >= $(SDKGEN_MIN) (the generate-time output"; \
|
|
124
|
-
echo " override). Older versions IGNORE it and silently write nothing."; \
|
|
125
|
-
echo " Pin a newer SDK tag, or regenerate from a checkout of the SDK."; \
|
|
126
|
-
exit 1; }; \
|
|
127
|
-
echo "regen: generating"; \
|
|
128
|
-
cd "$(SDK_DIR)/.sdk" && \
|
|
129
|
-
SDKGEN_EXTERNAL='{"seneca-provider":{"path":"../..","enclosing":true}}' \
|
|
130
|
-
npm run generate
|
|
131
|
-
@echo "regen: done — review the diff in this repository"
|