@voxgig/sdkgen-infrapack 0.0.15 → 0.0.17
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 +143 -51
- package/.sdk/src/cmp/seneca-provider/Main_seneca-provider.ts +55 -13
- 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 +5 -4
- package/sdkgen-package.json +2 -2
- package/.sdk/tm/seneca-provider/Makefile +0 -131
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
cmp, each,
|
|
3
3
|
File, Content, Folder,
|
|
4
4
|
jsKey, jsProp,
|
|
5
|
+
npmTrustScript,
|
|
5
6
|
pointSegments,
|
|
6
7
|
} from '@voxgig/sdkgen'
|
|
7
8
|
|
|
@@ -23,7 +24,12 @@ const SdkPin = cmp(function SdkPin(props: any) {
|
|
|
23
24
|
|
|
24
25
|
File({ name: 'sdk-pin.json' }, () => {
|
|
25
26
|
Content(JSON.stringify({
|
|
26
|
-
note:
|
|
27
|
+
note: provider.standalone ?
|
|
28
|
+
'GENERATED. The SDK this provider depends on and is generated from. ' +
|
|
29
|
+
'`make sdk-src` fetches it; `make regen` replaces the API definition ' +
|
|
30
|
+
'and guide in .sdk/ with its own and regenerates this repo. Set the version in ' +
|
|
31
|
+
'.sdk/model/project.aontu, not in this file.' :
|
|
32
|
+
'GENERATED. The SDK this provider is generated from. ' +
|
|
27
33
|
'`make sdk-src` fetches it; `make regen` regenerates this repo ' +
|
|
28
34
|
'from it. Edit the SDK project model, not this file.',
|
|
29
35
|
repo: provider.sdkRepoUrl,
|
|
@@ -1394,9 +1400,38 @@ async function run() {
|
|
|
1394
1400
|
|
|
1395
1401
|
|
|
1396
1402
|
|
|
1403
|
+
const PUBLISH_WORKFLOW = 'publish.yml'
|
|
1404
|
+
|
|
1405
|
+
|
|
1397
1406
|
const Workflow = cmp(function Workflow(props: any) {
|
|
1398
1407
|
const { provider } = props
|
|
1399
1408
|
|
|
1409
|
+
// The script runs from the provider's own .sdk, which only a standalone
|
|
1410
|
+
// builder has, and npm trusts GitHub Actions only on github.com.
|
|
1411
|
+
const repository = 'github.com' === String(provider.repoHost).toLowerCase() ?
|
|
1412
|
+
provider.repoPath : null
|
|
1413
|
+
const trusted = provider.standalone && null != repository
|
|
1414
|
+
const trustNote = !trusted ? '' :
|
|
1415
|
+
'#\n' +
|
|
1416
|
+
'# .sdk/admin/setup-npm-trust.sh registers exactly this, and with --check\n' +
|
|
1417
|
+
'# reports any drift from it:\n' +
|
|
1418
|
+
'#\n' +
|
|
1419
|
+
`# npm trust github ${provider.pkgName} \\\n` +
|
|
1420
|
+
`# --repository ${provider.repoPath} \\\n` +
|
|
1421
|
+
`# --file ${PUBLISH_WORKFLOW} \\\n` +
|
|
1422
|
+
'# --allow-publish\n'
|
|
1423
|
+
|
|
1424
|
+
if (provider.standalone) {
|
|
1425
|
+
Folder({ name: '.sdk' }, () => {
|
|
1426
|
+
Folder({ name: 'admin' }, () => {
|
|
1427
|
+
File({ name: 'setup-npm-trust.sh', mode: 0o755 }, () => {
|
|
1428
|
+
Content(npmTrustScript(repository,
|
|
1429
|
+
[{ pkg: provider.pkgName, file: PUBLISH_WORKFLOW }]))
|
|
1430
|
+
})
|
|
1431
|
+
})
|
|
1432
|
+
})
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1400
1435
|
Folder({ name: '.github' }, () => {
|
|
1401
1436
|
Folder({ name: 'workflows' }, () => {
|
|
1402
1437
|
File({ name: 'build.yml' }, () => {
|
|
@@ -1499,7 +1534,7 @@ ${!provider.liveApp ? '' : `
|
|
|
1499
1534
|
`)
|
|
1500
1535
|
})
|
|
1501
1536
|
|
|
1502
|
-
File({ name:
|
|
1537
|
+
File({ name: PUBLISH_WORKFLOW }, () => {
|
|
1503
1538
|
Content(`# Generated by @voxgig/sdkgen. Do not edit.
|
|
1504
1539
|
#
|
|
1505
1540
|
# Publishes ${provider.pkgName} to npm on a \`v*\` tag push, via GitHub OIDC
|
|
@@ -1524,9 +1559,9 @@ ${!provider.liveApp ? '' : `
|
|
|
1524
1559
|
# is what makes the isolation real rather than nominal.
|
|
1525
1560
|
#
|
|
1526
1561
|
# The trusted publisher must be registered on npmjs.com for this package
|
|
1527
|
-
# against THIS filename (
|
|
1562
|
+
# against THIS filename (${PUBLISH_WORKFLOW}); renaming this file breaks publishing
|
|
1528
1563
|
# until the npm-side config is updated to match.
|
|
1529
|
-
#
|
|
1564
|
+
${trustNote}#
|
|
1530
1565
|
# npm cannot configure a trusted publisher for a package that does not exist
|
|
1531
1566
|
# yet — the settings page appears once a version is on the registry. So the
|
|
1532
1567
|
# FIRST version of a new package is published by hand, once, with an
|
|
@@ -1718,7 +1753,8 @@ const seneca = Seneca()
|
|
|
1718
1753
|
},
|
|
1719
1754
|
},
|
|
1720
1755
|
})
|
|
1721
|
-
.use('${provider.pkgName}'
|
|
1756
|
+
.use('${provider.pkgName}'${'' === provider.specBase ?
|
|
1757
|
+
`, { sdk: { base: 'https://${provider.lower}.example.com' } }` : ''})
|
|
1722
1758
|
|
|
1723
1759
|
await seneca.ready()
|
|
1724
1760
|
|
|
@@ -1734,7 +1770,7 @@ await seneca.ready()
|
|
|
1734
1770
|
`'${cparts.map((p: string) => 'some-' + p).join(
|
|
1735
1771
|
null != subject.idsep && '' !== String(subject.idsep) ?
|
|
1736
1772
|
String(subject.idsep) : '/')}'` :
|
|
1737
|
-
0 === subject.parents.length ? `'some-id'` :
|
|
1773
|
+
0 === subject.parents.length ? (loadHasKey(subject) ? `'some-id'` : '') :
|
|
1738
1774
|
`{ ` + subject.parents.map((p: string) => `${jsKey(p)}: 'some-${p}'`).join(', ') +
|
|
1739
1775
|
`, id: 'some-id' }`
|
|
1740
1776
|
Content(`const ${subject.name} = await seneca
|
|
@@ -1898,7 +1934,10 @@ whose logs cannot be read.
|
|
|
1898
1934
|
each(provider.entities, (e: any) => {
|
|
1899
1935
|
each(e.cmds, (cmd: any) => {
|
|
1900
1936
|
const c = String(cmd.val$ ?? cmd)
|
|
1901
|
-
|
|
1937
|
+
const desc = 'save' !== c ? CMD_DESC[c] :
|
|
1938
|
+
!e.ops.includes('update') ? 'Create a record' :
|
|
1939
|
+
!e.ops.includes('create') ? 'Update a record' : CMD_DESC[c]
|
|
1940
|
+
Content(`| \`sys:entity,cmd:${c},zone:provider,base:${provider.lower},name:${e.name}\` | ${desc}. |
|
|
1902
1941
|
`)
|
|
1903
1942
|
})
|
|
1904
1943
|
})
|
|
@@ -1978,9 +2017,15 @@ const sdk = seneca.export('${provider.pluginName}/sdk')()
|
|
|
1978
2017
|
|
|
1979
2018
|
## Contributing
|
|
1980
2019
|
|
|
1981
|
-
|
|
2020
|
+
${provider.standalone ?
|
|
2021
|
+
`This plugin is GENERATED, by the builder in \`.sdk/\` from the API definition
|
|
2022
|
+
of the SDK it depends on. Changes to the API belong in the SDK project's
|
|
2023
|
+
model, this package's own decisions in \`.sdk/model/project.aontu\`, and
|
|
2024
|
+
everything else in the components that build this target — anything edited
|
|
2025
|
+
elsewhere in this repository is overwritten by the next \`make regen\`.` :
|
|
2026
|
+
`This plugin is GENERATED. Changes belong in the SDK project's model and
|
|
1982
2027
|
components, not here — anything edited in this repository is overwritten by
|
|
1983
|
-
the next generation run
|
|
2028
|
+
the next generation run.`}
|
|
1984
2029
|
|
|
1985
2030
|
The [Senecajs org](http://senecajs.org) encourages open participation. If you
|
|
1986
2031
|
feel you can help in any way, be it with bug reporting, documentation,
|
|
@@ -2677,6 +2722,7 @@ const DocHowto = cmp(function DocHowto(props: any) {
|
|
|
2677
2722
|
// an absent value to the same thing, so a missing base is treated as absent
|
|
2678
2723
|
// rather than printed as a default nobody can use.
|
|
2679
2724
|
const liveBase = provider.liveBase || ''
|
|
2725
|
+
const specBase = provider.specBase || ''
|
|
2680
2726
|
|
|
2681
2727
|
// The same choice the tests and the manual scripts make: fewest parent keys
|
|
2682
2728
|
// (nothing to arrange), then most cmds. Recipes prefer it, so one entity
|
|
@@ -2698,6 +2744,7 @@ const DocHowto = cmp(function DocHowto(props: any) {
|
|
|
2698
2744
|
// the API calls it. `apiKey` is that name, for the SDK-direct examples.
|
|
2699
2745
|
const idf = (_e: any) => 'id'
|
|
2700
2746
|
const apiKey = (e: any) => e.rk || 'id'
|
|
2747
|
+
const modelsId = (e: any) => null != e.ent?.id || null != (e.ent?.fields || {})[apiKey(e)]
|
|
2701
2748
|
|
|
2702
2749
|
// A parent key's example value. This MIRRORS seedRecord rather than
|
|
2703
2750
|
// inventing something more readable: the offline recipe below seeds with
|
|
@@ -2767,7 +2814,15 @@ const DocHowto = cmp(function DocHowto(props: any) {
|
|
|
2767
2814
|
|
|
2768
2815
|
const eList = forCmd('list')
|
|
2769
2816
|
const eLoad = forCmd('load')
|
|
2770
|
-
const
|
|
2817
|
+
const forOp = (op: string) => {
|
|
2818
|
+
const able = ents.filter((e: any) => e.ops.includes(op))
|
|
2819
|
+
return able.find((e: any) => e === subject) ||
|
|
2820
|
+
able.find((e: any) => 0 === e.parents.length) ||
|
|
2821
|
+
able[0] || null
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
const eCreate = forOp('create')
|
|
2825
|
+
const eUpdate = forOp('update')
|
|
2771
2826
|
const eRemove = forCmd('remove')
|
|
2772
2827
|
|
|
2773
2828
|
// Sections as data, so the contents list and the sections cannot disagree.
|
|
@@ -2841,53 +2896,63 @@ genuinely wrong.`)
|
|
|
2841
2896
|
}
|
|
2842
2897
|
|
|
2843
2898
|
|
|
2844
|
-
if (null !=
|
|
2845
|
-
const created = literal(createData(
|
|
2899
|
+
if (null != eCreate) {
|
|
2900
|
+
const created = literal(createData(eCreate))
|
|
2901
|
+
const updates = eCreate.ops.includes('update')
|
|
2846
2902
|
|
|
2847
|
-
sec('Create a record', `\`make$\` builds an entity and \`save$\` writes it.
|
|
2848
|
-
|
|
2903
|
+
sec('Create a record', `\`make$\` builds an entity and \`save$\` writes it. ${updates ?
|
|
2904
|
+
'An entity with no id\nis a create:' :
|
|
2905
|
+
`The API has no update\nfor a \`${eCreate.name}\`, so \`save$\` always creates one, even from an entity\nthat carries an id:`}
|
|
2849
2906
|
|
|
2850
2907
|
\`\`\`js
|
|
2851
|
-
const ${
|
|
2852
|
-
.entity('${canon(
|
|
2908
|
+
const ${eCreate.name} = await seneca
|
|
2909
|
+
.entity('${canon(eCreate)}')
|
|
2853
2910
|
.make$(${created})
|
|
2854
2911
|
.save$()
|
|
2855
2912
|
|
|
2856
|
-
console.log(${
|
|
2913
|
+
console.log(${eCreate.name}${modelsId(eCreate) ? '.' + idf(eCreate) : ''})
|
|
2857
2914
|
\`\`\`
|
|
2858
|
-
${0 ===
|
|
2859
|
-
Note that \`${
|
|
2860
|
-
not in a query: a \`${
|
|
2915
|
+
${0 === eCreate.parents.length ? '' : `
|
|
2916
|
+
Note that \`${eCreate.parents.join('`, `')}\` travels in the DATA for a write,
|
|
2917
|
+
not in a query: a \`${eCreate.name}\` is created inside its parent.
|
|
2861
2918
|
`}
|
|
2862
|
-
|
|
2919
|
+
${modelsId(eCreate) ?
|
|
2920
|
+
`\`save$\` resolves to the record as the API returned it, which is the only
|
|
2863
2921
|
reliable source of the id. Read it from there rather than predicting it:
|
|
2864
2922
|
what an API does with an id you supply on create is its own business, and
|
|
2865
|
-
several ignore it entirely.`
|
|
2923
|
+
several ignore it entirely.` :
|
|
2924
|
+
`\`save$\` resolves to the record as the API returned it. The API definition
|
|
2925
|
+
declares no id for a \`${eCreate.name}\`, so the record is the only place to
|
|
2926
|
+
read what identifies one.`}`)
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2866
2929
|
|
|
2867
|
-
|
|
2930
|
+
if (null != eUpdate) {
|
|
2931
|
+
const f = changeable(eUpdate)
|
|
2868
2932
|
|
|
2869
|
-
sec('Update a record',
|
|
2870
|
-
|
|
2933
|
+
sec('Update a record', `${eUpdate.ops.includes('create') ?
|
|
2934
|
+
'The same call updates. `save$` dispatches on the id: an entity carrying\none is an update, an entity without one is a create.' :
|
|
2935
|
+
`\`save$\` updates: the API has no create for a \`${eUpdate.name}\`.`} So the safe shape is
|
|
2871
2936
|
load, change, save:
|
|
2872
2937
|
|
|
2873
|
-
\`\`\`js${
|
|
2874
|
-
const ${
|
|
2875
|
-
.entity('${canon(
|
|
2876
|
-
.load$(${oneArgs(
|
|
2938
|
+
\`\`\`js${eUpdate.cmds.includes('load') ? `
|
|
2939
|
+
const ${eUpdate.name} = await seneca
|
|
2940
|
+
.entity('${canon(eUpdate)}')
|
|
2941
|
+
.load$(${oneArgs(eUpdate)})
|
|
2877
2942
|
` : `
|
|
2878
|
-
const ${
|
|
2879
|
-
.entity('${canon(
|
|
2880
|
-
.make$(${literal(0 ===
|
|
2881
|
-
{ [idf(
|
|
2882
|
-
{ ...Object.fromEntries(
|
|
2883
|
-
(k: string) => [k, parentVal(
|
|
2884
|
-
[idf(
|
|
2943
|
+
const ${eUpdate.name} = seneca
|
|
2944
|
+
.entity('${canon(eUpdate)}')
|
|
2945
|
+
.make$(${literal(0 === eUpdate.parents.length ?
|
|
2946
|
+
{ [idf(eUpdate)]: `${eUpdate.name}0` } :
|
|
2947
|
+
{ ...Object.fromEntries(eUpdate.parents.map(
|
|
2948
|
+
(k: string) => [k, parentVal(eUpdate, k)])),
|
|
2949
|
+
[idf(eUpdate)]: `${eUpdate.name}0` })})
|
|
2885
2950
|
`}${null == f ? `
|
|
2886
2951
|
// change the fields you need
|
|
2887
2952
|
` : `
|
|
2888
|
-
${
|
|
2953
|
+
${eUpdate.name}.${f.name} = ${newValue(f)}
|
|
2889
2954
|
`}
|
|
2890
|
-
await ${
|
|
2955
|
+
await ${eUpdate.name}.save$()
|
|
2891
2956
|
\`\`\`
|
|
2892
2957
|
|
|
2893
2958
|
Mutating the record you loaded sends it as it stood plus your change, so
|
|
@@ -3027,13 +3092,16 @@ constructor, so \`base\` chooses the host:
|
|
|
3027
3092
|
})
|
|
3028
3093
|
\`\`\`
|
|
3029
3094
|
|
|
3030
|
-
${'' ===
|
|
3095
|
+
${'' === specBase ?
|
|
3031
3096
|
`The API definition declares no server, so there is no default worth
|
|
3032
3097
|
relying on: set \`base\` explicitly, or run against the mock instead (see
|
|
3033
3098
|
[${OFFLINE_TITLE}](${anchor(OFFLINE_TITLE)})).` :
|
|
3034
|
-
|
|
3099
|
+
specBase === liveBase ?
|
|
3100
|
+
`The SDK's own default is \`${liveBase}\`, which is where the
|
|
3035
3101
|
companion test server listens, so local development usually needs no
|
|
3036
|
-
\`base\` at all.`
|
|
3102
|
+
\`base\` at all.` :
|
|
3103
|
+
`The SDK's own default is \`${specBase}\`, the server the API definition
|
|
3104
|
+
declares, so \`base\` is needed only to reach another one.`}`)
|
|
3037
3105
|
|
|
3038
3106
|
|
|
3039
3107
|
if (!provider.authActive) {
|
|
@@ -3321,10 +3389,15 @@ $ npm run repo-publish
|
|
|
3321
3389
|
Only \`dist\`, the TypeScript sources and the licence file are published;
|
|
3322
3390
|
the test suite and its build output stay in the repository.
|
|
3323
3391
|
|
|
3324
|
-
|
|
3392
|
+
${'npm' === provider.sdkDepKind ?
|
|
3393
|
+
`Before publishing, check that \`package.json\` still depends on the
|
|
3325
3394
|
published SDK by version range and not on a local path: a \`file:\`
|
|
3326
3395
|
dependency left behind from local development installs perfectly on your
|
|
3327
|
-
own machine and cannot be resolved by anybody else
|
|
3396
|
+
own machine and cannot be resolved by anybody else.` :
|
|
3397
|
+
`Publish the SDK to npm first. \`package.json\` depends on it as
|
|
3398
|
+
\`${provider.sdkDep}\`, which everyone installing this package would
|
|
3399
|
+
have to fetch${'git' === provider.sdkDepKind ? ' with git' : ''}. Then drop \`sdk.dep\` from the model, regenerate,
|
|
3400
|
+
and check that the dependency is a version range.`}
|
|
3328
3401
|
|
|
3329
3402
|
One last thing: this repository is GENERATED from the ${provider.api} API
|
|
3330
3403
|
model by [@voxgig/sdkgen](https://github.com/voxgig/sdkgen). An edit made
|
|
@@ -3501,6 +3574,14 @@ Seneca({ legacy: false })
|
|
|
3501
3574
|
if (live) {
|
|
3502
3575
|
Content(` .use('${provider.pkgName}', { sdk: { base: '${provider.liveBase}' } })
|
|
3503
3576
|
\`\`\`
|
|
3577
|
+
`)
|
|
3578
|
+
}
|
|
3579
|
+
else if ('' !== provider.specBase) {
|
|
3580
|
+
Content(` .use('${provider.pkgName}')
|
|
3581
|
+
\`\`\`
|
|
3582
|
+
|
|
3583
|
+
The SDK's default base URL is \`${provider.specBase}\`, the server the
|
|
3584
|
+
${provider.api} definition declares. Pass \`sdk: { base }\` to reach another.
|
|
3504
3585
|
`)
|
|
3505
3586
|
}
|
|
3506
3587
|
else {
|
|
@@ -3531,8 +3612,8 @@ Any option the \`${provider.sdkClass}\` constructor accepts:
|
|
|
3531
3612
|
|
|
3532
3613
|
| Key | Effect |
|
|
3533
3614
|
| --- | ------ |
|
|
3534
|
-
| \`base\` | Base URL for API requests. ${
|
|
3535
|
-
`The SDK's own default is \`${provider.
|
|
3615
|
+
| \`base\` | Base URL for API requests. ${'' !== provider.specBase ?
|
|
3616
|
+
`The SDK's own default is \`${provider.specBase}\`, the server the API definition declares.` :
|
|
3536
3617
|
'There is no default: this API declares no server, so it must be set.'} |
|
|
3537
3618
|
| \`prefix\` / \`suffix\` | URL fragments placed around the path. |
|
|
3538
3619
|
| \`headers\` | Headers sent on every request. These win over the \`authorization\` header the provider adds from a configured key. |
|
|
@@ -3618,7 +3699,9 @@ before any request is made, rather than issuing one that would 404.
|
|
|
3618
3699
|
`)
|
|
3619
3700
|
}
|
|
3620
3701
|
if (e.cmds.includes('load')) {
|
|
3621
|
-
Content(
|
|
3702
|
+
Content(loadHasKey(e) ?
|
|
3703
|
+
`| \`load$(q)\` | ${reqd([...e.parents, 'id'])} | One \`${e.name}\`, or \`null\` if not found. |
|
|
3704
|
+
` : `| \`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
3705
|
`)
|
|
3623
3706
|
}
|
|
3624
3707
|
if (e.cmds.includes('save')) {
|
|
@@ -3636,7 +3719,8 @@ before any request is made, rather than issuing one that would 404.
|
|
|
3636
3719
|
|
|
3637
3720
|
if ('id' !== apiKeyOf(e)) {
|
|
3638
3721
|
Content(`
|
|
3639
|
-
The API
|
|
3722
|
+
The API ${e.ops.some((op: string) => ['load', 'remove', 'update'].includes(op)) ?
|
|
3723
|
+
'addresses' : 'identifies'} \`${e.name}\` records by \`${apiKeyOf(e)}\`; the provider
|
|
3640
3724
|
carries that value as the entity's \`id\`, so every query and entity above
|
|
3641
3725
|
uses \`id\`. A record the API returns with an unrelated \`id\` of its own
|
|
3642
3726
|
${false === e.parkfree ?
|
|
@@ -3661,7 +3745,7 @@ also defines are passed through unchanged in both directions.
|
|
|
3661
3745
|
| ----- | ---- | ----- |
|
|
3662
3746
|
`)
|
|
3663
3747
|
each(e.fields, (f: any) => {
|
|
3664
|
-
Content(`| \`${f.name}\` | ${f.kind} | ${f.name === (e.rk || 'id') ?
|
|
3748
|
+
Content(`| \`${f.name}\` | ${f.kind}${f.nullable ? ' or null' : ''} | ${f.name === (e.rk || 'id') ?
|
|
3665
3749
|
('id' === f.name ? 'Id field.' : 'API key; carried as the entity\'s `id`.') :
|
|
3666
3750
|
e.parents.includes(f.name) ? ('' === f.parentEntity ?
|
|
3667
3751
|
'Parent key. Required by every command.' :
|
|
@@ -4440,11 +4524,16 @@ rather than as a surprise in production.
|
|
|
4440
4524
|
`)
|
|
4441
4525
|
|
|
4442
4526
|
if ('' === provider.liveBase) {
|
|
4443
|
-
Content(
|
|
4527
|
+
Content(`${'' === provider.specBase ?
|
|
4528
|
+
`The API definition declares no server, so this plugin has no default host: the
|
|
4444
4529
|
base URL arrives through the \`sdk.base\` option, supplied by whoever configures
|
|
4445
4530
|
the plugin for a particular deployment. The tests therefore run entirely
|
|
4446
4531
|
against the SDK's mock transport, which is the one host that is always
|
|
4447
|
-
available
|
|
4532
|
+
available.` :
|
|
4533
|
+
`The SDK's default host is \`${provider.specBase}\`, the server the API
|
|
4534
|
+
definition declares, and the \`sdk.base\` option points the plugin at another.
|
|
4535
|
+
Nothing declares a test server, so the tests run entirely against the SDK's
|
|
4536
|
+
mock transport, which is the one host that is always available.`}
|
|
4448
4537
|
|
|
4449
4538
|
|
|
4450
4539
|
`)
|
|
@@ -4476,7 +4565,10 @@ that has to be applied again, silently, forever.
|
|
|
4476
4565
|
|
|
4477
4566
|
The source of truth is the SDK project's model — the repository and tag named
|
|
4478
4567
|
in \`sdk-pin.json\`, which \`make sdk-src\` fetches to \`${provider.sdkSrc}\` —
|
|
4479
|
-
together with the sdkgen component that emits this target.
|
|
4568
|
+
together with the sdkgen component that emits this target.${provider.standalone ? `
|
|
4569
|
+
This repository builds itself: the builder in \`.sdk/\` carries a copy of that
|
|
4570
|
+
SDK's API definition, which \`make regen\` refreshes from the fetched source,
|
|
4571
|
+
and generation refuses to write when the copy no longer matches it.` : ''} A change to *what*
|
|
4480
4572
|
the API offers belongs in the model; a change to
|
|
4481
4573
|
*how* the provider expresses it belongs in the component. Both are versioned,
|
|
4482
4574
|
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 : ''
|
|
@@ -301,13 +319,13 @@ function parentEntityOf(key: string, names: string[]): string {
|
|
|
301
319
|
|
|
302
320
|
|
|
303
321
|
function providerRepo(model: any, lower: string, tname: string):
|
|
304
|
-
{ url: string, path: string } {
|
|
322
|
+
{ url: string, path: string, host: string } {
|
|
305
323
|
const host = model?.main?.[KIT]?.repo?.host || 'github.com'
|
|
306
324
|
const declared = model?.main?.[KIT]?.target?.[tname]?.output?.repo
|
|
307
325
|
const path = null != declared && '' !== declared ?
|
|
308
326
|
String(declared) : `senecajs/seneca-${lower}-provider`
|
|
309
327
|
|
|
310
|
-
return { url: `https://${host}/${path}`, path }
|
|
328
|
+
return { url: `https://${host}/${path}`, path, host }
|
|
311
329
|
}
|
|
312
330
|
|
|
313
331
|
|
|
@@ -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.
|
|
@@ -522,6 +544,8 @@ const Main = cmp(function Main(props: any) {
|
|
|
522
544
|
sdkInstallFlag: sdkDep.startsWith('github:') ? ' --allow-git=all' :
|
|
523
545
|
(sdkDep.startsWith('http') ? ' --allow-remote=all' : ''),
|
|
524
546
|
repoUrl: repo.url,
|
|
547
|
+
repoPath: repo.path,
|
|
548
|
+
repoHost: repo.host,
|
|
525
549
|
sdkRepoUrl,
|
|
526
550
|
sdkTag,
|
|
527
551
|
sdkRepoDir,
|
|
@@ -531,6 +555,8 @@ const Main = cmp(function Main(props: any) {
|
|
|
531
555
|
version: packageVersion(model, target.name),
|
|
532
556
|
liveBase,
|
|
533
557
|
liveApp,
|
|
558
|
+
// The server the API definition declares, which is the SDK's own default.
|
|
559
|
+
specBase,
|
|
534
560
|
publisher: PUBLISHER,
|
|
535
561
|
publisherUrl: PUBLISHER_URL,
|
|
536
562
|
probePath: (entities.find((e: any) =>
|
|
@@ -549,6 +575,10 @@ const Main = cmp(function Main(props: any) {
|
|
|
549
575
|
authPrefix: resolveAuthPrefix(model),
|
|
550
576
|
}
|
|
551
577
|
|
|
578
|
+
if (standalone) {
|
|
579
|
+
checkSdkSource(ctx$, provider, model)
|
|
580
|
+
}
|
|
581
|
+
|
|
552
582
|
// `.gitignore` is EMITTED rather than copied — npm strips that filename
|
|
553
583
|
// from the tarball, so as a template it reached only checkout users. See
|
|
554
584
|
// Gitignore_seneca-provider. Called before the Copy, as every language
|
|
@@ -560,6 +590,7 @@ const Main = cmp(function Main(props: any) {
|
|
|
560
590
|
replace: { ...ctx$.stdrep },
|
|
561
591
|
})
|
|
562
592
|
|
|
593
|
+
Makefile({ provider })
|
|
563
594
|
SdkPin({ provider })
|
|
564
595
|
PackageJson({ provider, target })
|
|
565
596
|
ProviderSource({ provider })
|
|
@@ -1218,7 +1249,12 @@ ${actionBranch('load',
|
|
|
1218
1249
|
` const hit = await ornull(() => this.shared.sdk.${e.acc}()[op$](${aq}))
|
|
1219
1250
|
return null == hit ? null : entize(${out('hit')})
|
|
1220
1251
|
`)}${guard('load', 'q')}${splitLine('load')} const res = await ornull(() => this.shared.sdk.${e.acc}().load(${sdkArg('load')}))
|
|
1221
|
-
|
|
1252
|
+
${0 < addressKeys(e.ent, 'load').length || 0 < eparts.length ? '' :
|
|
1253
|
+
` // The route names no record, so an id finds only the record carrying it.
|
|
1254
|
+
if (null != res && null != q.id && String(${jsProp('plain(res)', rk)}) !== String(q.id)) {
|
|
1255
|
+
return null
|
|
1256
|
+
}
|
|
1257
|
+
`} return null == res ? null : entize(${out('res', loadVals)})
|
|
1222
1258
|
}
|
|
1223
1259
|
|
|
1224
1260
|
`)
|
|
@@ -1268,7 +1304,12 @@ ${dropPark}
|
|
|
1268
1304
|
// which matches a request against a stored record, then looked for a
|
|
1269
1305
|
// record whose own \`id\` was that joined string and found none.
|
|
1270
1306
|
delete data.id
|
|
1271
|
-
` : !keyed ? '' : `
|
|
1307
|
+
` : !keyed ? '' : hasCreate && !hasUpdate && true !== e.rkoncreate ? `
|
|
1308
|
+
// The API assigns a ${e.name}'s \`${rk}\`, and a create names no record, so
|
|
1309
|
+
// Seneca's \`id\` is not sent: the id is read back from the response.
|
|
1310
|
+
const key: any = null
|
|
1311
|
+
${dropPark} delete data.id
|
|
1312
|
+
` : `
|
|
1272
1313
|
// This API keys a ${e.name} by \`${rk}\`; Seneca carries it as \`id\`.
|
|
1273
1314
|
// The key goes on the body under the API's own name, where the route
|
|
1274
1315
|
// reads it, and into the match, which the SDK consults first.
|
|
@@ -1369,9 +1410,10 @@ ${provider.authActive ? `
|
|
|
1369
1410
|
// the SDK was constructed with NO credential at all — the request went
|
|
1370
1411
|
// out unauthenticated and failed much later as a 401 or a 404 on
|
|
1371
1412
|
// 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
|
|
1413
|
+
// \`apikey\` wins when both are set and it is not empty, so a config that
|
|
1414
|
+
// has migrated is unaffected.
|
|
1415
|
+
const apikey = [res?.keymap?.apikey?.value, res?.keymap?.api?.value]
|
|
1416
|
+
.find((value: any) => null != value && '' !== value)
|
|
1375
1417
|
|
|
1376
1418
|
// Hand the credential to the SDK as \`apikey\`, NOT as an authorization
|
|
1377
1419
|
// 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voxgig/sdkgen-infrapack",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"description": "Infrastructure-provider targets for the Voxgig SDK Generator: Seneca provider.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
".sdk",
|
|
22
|
-
"sdkgen-package.json"
|
|
22
|
+
"sdkgen-package.json",
|
|
23
|
+
"README.md"
|
|
23
24
|
],
|
|
24
25
|
"scripts": {
|
|
25
26
|
"test": "node tools/comment-gate.cjs && node tools/dep-gate.cjs && node --test tools/dep-gate.test.cjs && node --enable-source-maps --test test/*.test.js",
|
|
@@ -105,11 +106,11 @@
|
|
|
105
106
|
],
|
|
106
107
|
"peerDependencies": {
|
|
107
108
|
"@voxgig/apidef": ">=8.17.0",
|
|
108
|
-
"@voxgig/sdkgen": ">=4.
|
|
109
|
+
"@voxgig/sdkgen": ">=4.30.0"
|
|
109
110
|
},
|
|
110
111
|
"devDependencies": {
|
|
111
112
|
"@voxgig/apidef": ">=8.17.0",
|
|
112
|
-
"@voxgig/sdkgen": ">=4.
|
|
113
|
+
"@voxgig/sdkgen": ">=4.30.0",
|
|
113
114
|
"aontu": ">=0.75.0",
|
|
114
115
|
"memfs": "4.68.1",
|
|
115
116
|
"typescript": "7.0.2",
|
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"
|