@voxgig/sdkgen 1.3.18 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cmp/Deploy.js +16 -16
- package/dist/cmp/Deploy.js.map +1 -1
- package/dist/cmp/ReadmeTop.js +13 -11
- package/dist/cmp/ReadmeTop.js.map +1 -1
- package/dist/helpers/naming.d.ts +3 -1
- package/dist/helpers/naming.js +65 -0
- package/dist/helpers/naming.js.map +1 -1
- package/dist/helpers/serverVars.d.ts +10 -0
- package/dist/helpers/serverVars.js +87 -0
- package/dist/helpers/serverVars.js.map +1 -0
- package/dist/sdkgen.d.ts +3 -2
- package/dist/sdkgen.js +6 -1
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/project/.sdk/model/target/go-cli.aontu +2 -2
- package/project/.sdk/src/cmp/csharp/TestEntity_csharp.ts +22 -15
- package/project/.sdk/src/cmp/go/Config_go.ts +10 -1
- package/project/.sdk/src/cmp/go-cli/Main_go-cli.ts +54 -35
- package/project/.sdk/src/cmp/go-cli/fragment/main.fragment.go +7 -7
- package/project/.sdk/src/cmp/go-cli/fragment/words.fragment.go +21 -18
- package/project/.sdk/src/cmp/js/Config_js.ts +11 -0
- package/project/.sdk/src/cmp/js/fragment/Config.fragment.js +2 -2
- package/project/.sdk/src/cmp/js/fragment/Direct.test.fragment.js +4 -0
- package/project/.sdk/src/cmp/lua/Config_lua.ts +10 -1
- package/project/.sdk/src/cmp/php/Config_php.ts +13 -2
- package/project/.sdk/src/cmp/py/Config_py.ts +10 -1
- package/project/.sdk/src/cmp/rb/Config_rb.ts +12 -1
- package/project/.sdk/src/cmp/rust/TestEntity_rust.ts +25 -18
- package/project/.sdk/src/cmp/swift/EntityTypes_swift.ts +10 -3
- package/project/.sdk/src/cmp/ts/Config_ts.ts +11 -0
- package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +2 -2
- package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +4 -0
- package/project/.sdk/tm/c/utility/prepare_method.c +18 -0
- package/project/.sdk/tm/clojure/src/sdk/core.clj +9 -1
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +13 -0
- package/project/.sdk/tm/csharp/Makefile +6 -6
- package/project/.sdk/tm/csharp/utility/PrepareMethod.cs +11 -0
- package/project/.sdk/tm/dart/Makefile +3 -3
- package/project/.sdk/tm/dart/lib/utility/PrepareMethodUtility.dart +10 -0
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +8 -1
- package/project/.sdk/tm/go/Makefile +6 -6
- package/project/.sdk/tm/go/test/primary_utility_test.go +5 -0
- package/project/.sdk/tm/go/utility/make_options.go +48 -0
- package/project/.sdk/tm/go/utility/prepare_method.go +14 -1
- package/project/.sdk/tm/haskell/src/SdkRuntime.hs +13 -4
- package/project/.sdk/tm/java/Makefile +6 -6
- package/project/.sdk/tm/java/utility/PrepareMethod.java +9 -0
- package/project/.sdk/tm/js/Makefile +6 -6
- package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +39 -1
- package/project/.sdk/tm/js/src/utility/PrepareMethodUtility.js +9 -0
- package/project/.sdk/tm/js/test/utility/PrimaryUtility.test.js +5 -0
- package/project/.sdk/tm/kotlin/Makefile +6 -6
- package/project/.sdk/tm/kotlin/utility/Prepare.kt +9 -0
- package/project/.sdk/tm/lean/src/SdkUtility.lean +6 -1
- package/project/.sdk/tm/lua/Makefile +7 -7
- package/project/.sdk/tm/lua/feature/test_feature.lua +29 -1
- package/project/.sdk/tm/lua/test/primary_utility_test.lua +4 -0
- package/project/.sdk/tm/lua/utility/make_options.lua +39 -0
- package/project/.sdk/tm/lua/utility/prepare_method.lua +11 -0
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +9 -3
- package/project/.sdk/tm/perl/Makefile +6 -6
- package/project/.sdk/tm/perl/utility/prepare_method.pm +17 -0
- package/project/.sdk/tm/php/Makefile +6 -6
- package/project/.sdk/tm/php/feature/TestFeature.php +30 -2
- package/project/.sdk/tm/php/test/PrimaryUtilityTest.php +5 -0
- package/project/.sdk/tm/php/utility/MakeOptions.php +44 -0
- package/project/.sdk/tm/php/utility/PrepareMethod.php +11 -0
- package/project/.sdk/tm/py/Makefile +6 -6
- package/project/.sdk/tm/py/test/test_primary_utility.py +5 -0
- package/project/.sdk/tm/py/utility/make_options.py +42 -0
- package/project/.sdk/tm/py/utility/prepare_method.py +10 -0
- package/project/.sdk/tm/rb/Makefile +6 -6
- package/project/.sdk/tm/rb/test/primary_utility_test.rb +4 -0
- package/project/.sdk/tm/rb/utility/make_options.rb +37 -0
- package/project/.sdk/tm/rb/utility/prepare_method.rb +10 -1
- package/project/.sdk/tm/rust/Makefile +6 -6
- package/project/.sdk/tm/rust/utility/prepare_method.rs +12 -0
- package/project/.sdk/tm/scala/utility/Prepare.scala +14 -8
- package/project/.sdk/tm/swift/Makefile +6 -6
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +8 -0
- package/project/.sdk/tm/ts/Makefile +6 -6
- package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +39 -1
- package/project/.sdk/tm/ts/src/utility/PrepareMethodUtility.ts +9 -0
- package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +4 -0
- package/project/.sdk/tm/zig/core/utility.zig +15 -0
- package/src/cmp/Deploy.ts +16 -16
- package/src/cmp/ReadmeTop.ts +13 -11
- package/src/helpers/naming.ts +71 -0
- package/src/helpers/serverVars.ts +105 -0
- package/src/sdkgen.ts +6 -1
|
@@ -10,26 +10,26 @@ clean:
|
|
|
10
10
|
@echo "No clean step for Lua"
|
|
11
11
|
|
|
12
12
|
# --- Release: publish to LuaRocks -----------------------------------------
|
|
13
|
-
# Upload the rockspec to LuaRocks, then tag lua/vX.Y.Z. LuaRocks has no
|
|
13
|
+
# Upload the rockspec to LuaRocks, then tag lua/vX.Y.Z. LuaRocks has no boru
|
|
14
14
|
# recipe, so inject its API key + the GitHub token as custom env vars
|
|
15
|
-
# (all-positional —
|
|
16
|
-
#
|
|
15
|
+
# (all-positional — boru cannot mix --for recipes with positional env):
|
|
16
|
+
# boru vault exec '<alias>=LUAROCKS_API_KEY,<gh-alias>=GITHUB_TOKEN' -- make publish
|
|
17
17
|
# --skip-pack uploads only the rockspec (its git+https source is fetched at the
|
|
18
18
|
# lua/vX.Y.Z tag on install; that tag is pushed in the LAST step, so we must not
|
|
19
19
|
# pack here). LuaRocks requires the rockspec filename to be
|
|
20
|
-
# <package>-<version>.rockspec, so we upload a canonically-named copy.
|
|
20
|
+
# <package>-<version>.rockspec, so we upload a canonically-named copy. boru
|
|
21
21
|
# `--dry-run` rehearses (no upload/tag). set -e + tag-last: a failed upload
|
|
22
22
|
# never leaves a tag behind.
|
|
23
23
|
VERSION := $(shell sed -nE 's/^version *= *"([0-9.]+).*/\1/p' *.rockspec | head -1)
|
|
24
24
|
TAG := lua/v$(VERSION)
|
|
25
|
-
|
|
25
|
+
BORU_DRY_RUN_FILLER := BORU-DRY-RUN-FILLER-NOT-A-REAL-SECRET
|
|
26
26
|
publish: test
|
|
27
27
|
@if git rev-parse -q --verify "refs/tags/$(TAG)" >/dev/null; then \
|
|
28
28
|
echo "tag $(TAG) already exists — bump the rockspec version first"; exit 1; fi
|
|
29
29
|
@set -e; \
|
|
30
30
|
token="$${GITHUB_TOKEN:-$$GH_TOKEN}"; \
|
|
31
|
-
if [ "$$token" = "$(
|
|
32
|
-
echo "[dry-run]
|
|
31
|
+
if [ "$$token" = "$(BORU_DRY_RUN_FILLER)" ] || [ "$$LUAROCKS_API_KEY" = "$(BORU_DRY_RUN_FILLER)" ]; then \
|
|
32
|
+
echo "[dry-run] boru filler token detected: would upload to LuaRocks and tag $(TAG); nothing pushed."; \
|
|
33
33
|
exit 0; \
|
|
34
34
|
fi; \
|
|
35
35
|
rock=$$(ls *.rockspec | head -1); \
|
|
@@ -41,11 +41,39 @@ function TestFeature:init(ctx, options)
|
|
|
41
41
|
local test_self = self
|
|
42
42
|
|
|
43
43
|
local function test_fetcher(fctx, _fullurl, _fetchdef)
|
|
44
|
+
-- Shape the mock payload the way the real API would, so the op's
|
|
45
|
+
-- response transform recovers the entity from it. A point carrying
|
|
46
|
+
-- transform.res of `body.item` describes an API that answers
|
|
47
|
+
-- { item = {...} }; handing back the bare entity means the transform
|
|
48
|
+
-- unwraps a property that is not there and the caller gets nil.
|
|
49
|
+
-- The mock has to agree with the model, or it only ever simulates
|
|
50
|
+
-- APIs whose responses happen to be unwrapped. Mirrors the ts/py mocks.
|
|
51
|
+
local function envelope(data)
|
|
52
|
+
local point = fctx.point
|
|
53
|
+
if data == nil or type(point) ~= "table" then
|
|
54
|
+
return data
|
|
55
|
+
end
|
|
56
|
+
local transform = point.transform
|
|
57
|
+
if type(transform) ~= "table" then
|
|
58
|
+
return data
|
|
59
|
+
end
|
|
60
|
+
local restf = transform.res
|
|
61
|
+
if type(restf) ~= "string" then
|
|
62
|
+
return data
|
|
63
|
+
end
|
|
64
|
+
local key = string.match(restf, "^`body%.([^`%.]+)`$")
|
|
65
|
+
if key == nil then
|
|
66
|
+
return data
|
|
67
|
+
end
|
|
68
|
+
return { [key] = data }
|
|
69
|
+
end
|
|
70
|
+
|
|
44
71
|
local function respond(status, data, extra)
|
|
72
|
+
local payload = envelope(data)
|
|
45
73
|
local out = {
|
|
46
74
|
status = status,
|
|
47
75
|
statusText = "OK",
|
|
48
|
-
json = function() return
|
|
76
|
+
json = function() return payload end,
|
|
49
77
|
body = "not-used",
|
|
50
78
|
}
|
|
51
79
|
if type(extra) == "table" then
|
|
@@ -576,6 +576,9 @@ describe("PrimaryUtility", function()
|
|
|
576
576
|
it("fetcher-live", function()
|
|
577
577
|
local calls = {}
|
|
578
578
|
local live_client = sdk.new({
|
|
579
|
+
-- Concrete base: a live construction must satisfy any server variables
|
|
580
|
+
-- a templated base URL declares; a literal base sidesteps the requirement.
|
|
581
|
+
base = "http://localhost:8080",
|
|
579
582
|
system = {
|
|
580
583
|
fetch = function(url, fetchdef)
|
|
581
584
|
table.insert(calls, { url = url, init = fetchdef })
|
|
@@ -600,6 +603,7 @@ describe("PrimaryUtility", function()
|
|
|
600
603
|
|
|
601
604
|
it("fetcher-blocked-test-mode", function()
|
|
602
605
|
local blocked_client = sdk.new({
|
|
606
|
+
base = "http://localhost:8080",
|
|
603
607
|
system = {
|
|
604
608
|
fetch = function(url, fetchdef)
|
|
605
609
|
return {}, nil
|
|
@@ -92,6 +92,13 @@ local function make_options_util(ctx)
|
|
|
92
92
|
clean = {
|
|
93
93
|
keys = "key,token,id",
|
|
94
94
|
},
|
|
95
|
+
-- Server-variable values for a templated base URL (OpenAPI server
|
|
96
|
+
-- variables): {name} placeholders in `base` are substituted from this
|
|
97
|
+
-- map at construction. Spec defaults arrive via the generated config;
|
|
98
|
+
-- user values override them.
|
|
99
|
+
server = {
|
|
100
|
+
["`$CHILD`"] = "",
|
|
101
|
+
},
|
|
95
102
|
}
|
|
96
103
|
|
|
97
104
|
-- Preserve system.fetch before merge/validate.
|
|
@@ -104,6 +111,38 @@ local function make_options_util(ctx)
|
|
|
104
111
|
end
|
|
105
112
|
opts = validated
|
|
106
113
|
|
|
114
|
+
-- Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
|
|
115
|
+
-- Every placeholder must resolve to a non-empty value: from
|
|
116
|
+
-- options.server (user), else the config default. A placeholder that
|
|
117
|
+
-- resolves to "" is a construction ERROR in live mode — the URL cannot
|
|
118
|
+
-- work — but in test mode substitutes the deterministic value
|
|
119
|
+
-- "test-<name>" so offline tests need no configuration.
|
|
120
|
+
local base = opts.base
|
|
121
|
+
if type(base) == "string" and string.find(base, "{", 1, true) then
|
|
122
|
+
local testmode = vs.getpath(opts, "test.active") == true
|
|
123
|
+
or vs.getpath(opts, "feature.test.active") == true
|
|
124
|
+
local server = type(opts.server) == "table" and opts.server or {}
|
|
125
|
+
local sdkname = vs.getpath(config, "main.name")
|
|
126
|
+
if type(sdkname) ~= "string" or sdkname == "" then
|
|
127
|
+
sdkname = "SDK"
|
|
128
|
+
end
|
|
129
|
+
opts.base = string.gsub(base, "{([%w_]+)}", function(name)
|
|
130
|
+
local val = server[name]
|
|
131
|
+
if type(val) ~= "string" then
|
|
132
|
+
val = ""
|
|
133
|
+
end
|
|
134
|
+
if val == "" then
|
|
135
|
+
if testmode then
|
|
136
|
+
return "test-" .. name
|
|
137
|
+
end
|
|
138
|
+
error(sdkname .. ": the server variable '" .. name .. "' is required: " ..
|
|
139
|
+
"the API base URL is '" .. base .. "' — pass " ..
|
|
140
|
+
"{ server = { " .. name .. " = \"...\" } } in the SDK options", 0)
|
|
141
|
+
end
|
|
142
|
+
return val
|
|
143
|
+
end)
|
|
144
|
+
end
|
|
145
|
+
|
|
107
146
|
-- Restore system.fetch.
|
|
108
147
|
if sys_fetch ~= nil then
|
|
109
148
|
if type(opts["system"]) == "table" then
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
-- ProjectName SDK utility: prepare_method
|
|
2
2
|
|
|
3
|
+
local vs = require("utility.struct.struct")
|
|
4
|
+
|
|
3
5
|
local METHOD_MAP = {
|
|
4
6
|
create = "POST",
|
|
5
7
|
update = "PUT",
|
|
@@ -11,6 +13,15 @@ local METHOD_MAP = {
|
|
|
11
13
|
|
|
12
14
|
local function prepare_method_util(ctx)
|
|
13
15
|
local opname = ctx.op.name
|
|
16
|
+
|
|
17
|
+
-- The API definition is authoritative: a POST-only or PATCH-based API
|
|
18
|
+
-- exposes `update` as POST or PATCH, not the PUT the op name implies.
|
|
19
|
+
-- Only fall back to the op-name convention when the point has no method.
|
|
20
|
+
local method = vs.getprop(ctx.point, "method")
|
|
21
|
+
if "string" == type(method) and "" ~= method then
|
|
22
|
+
return string.upper(method)
|
|
23
|
+
end
|
|
24
|
+
|
|
14
25
|
return METHOD_MAP[opname] or "GET"
|
|
15
26
|
end
|
|
16
27
|
|
|
@@ -336,10 +336,16 @@ let feature_init_util (ctx : ctx) (f : feature) : unit =
|
|
|
336
336
|
|
|
337
337
|
(* ----- prepare / param ----- *)
|
|
338
338
|
|
|
339
|
+
(* The API definition is authoritative: a POST-only or PATCH-based API
|
|
340
|
+
exposes `update` as POST or PATCH, not the PUT the op name implies.
|
|
341
|
+
Only fall back to the op-name convention when the point has no method. *)
|
|
339
342
|
let prepare_method_util (ctx : ctx) : string =
|
|
340
|
-
match ctx.
|
|
341
|
-
|
|
|
342
|
-
|
|
|
343
|
+
match getp ctx.c_point "method" with
|
|
344
|
+
| Str m when m <> "" -> String.uppercase_ascii m
|
|
345
|
+
| _ ->
|
|
346
|
+
match ctx.c_op.op_name with
|
|
347
|
+
| "create" -> "POST" | "update" -> "PUT" | "load" -> "GET"
|
|
348
|
+
| "list" -> "GET" | "remove" -> "DELETE" | "patch" -> "PATCH" | _ -> "GET"
|
|
343
349
|
|
|
344
350
|
let prepare_headers_util (ctx : ctx) : value =
|
|
345
351
|
let options = client_options_map (cc ctx) in
|
|
@@ -20,10 +20,10 @@ clean:
|
|
|
20
20
|
# Build a CPAN dist in a throwaway .release/ dir to validate the package,
|
|
21
21
|
# then tag perl/vX.Y.Z (version from $VERSION in lib/*SDK.pm). The CPAN
|
|
22
22
|
# registry entry for this port is declared but pending, so publish is
|
|
23
|
-
# tag-only: no upload happens. Run headless under the
|
|
23
|
+
# tag-only: no upload happens. Run headless under the boru key vault, which
|
|
24
24
|
# injects the GitHub token (never on disk or argv):
|
|
25
|
-
#
|
|
26
|
-
#
|
|
25
|
+
# boru vault exec --for=github=<alias> -- make publish
|
|
26
|
+
# boru `--dry-run` injects a filler token we detect to rehearse (build only,
|
|
27
27
|
# no tag). set -e + tag-last: a failed build never leaves a tag behind.
|
|
28
28
|
#
|
|
29
29
|
# NOTE: `perl Makefile.PL` (ExtUtils::MakeMaker) GENERATES a file named
|
|
@@ -31,7 +31,7 @@ clean:
|
|
|
31
31
|
# dist build always happens inside .release/, never in place.
|
|
32
32
|
VERSION := $(shell sed -nE "s/.*VERSION *= *'([^']+)'.*/\1/p" lib/*SDK.pm | head -1)
|
|
33
33
|
TAG := perl/v$(VERSION)
|
|
34
|
-
|
|
34
|
+
BORU_DRY_RUN_FILLER := BORU-DRY-RUN-FILLER-NOT-A-REAL-SECRET
|
|
35
35
|
publish: test
|
|
36
36
|
@if git rev-parse -q --verify "refs/tags/$(TAG)" >/dev/null; then \
|
|
37
37
|
echo "tag $(TAG) already exists — bump VERSION in lib/*SDK.pm first"; exit 1; fi
|
|
@@ -42,8 +42,8 @@ publish: test
|
|
|
42
42
|
{ [ -f README.md ] && cp README.md .release/ || true; }; \
|
|
43
43
|
(cd .release && $(PERL) Makefile.PL >/dev/null && echo "dist metadata OK"); \
|
|
44
44
|
rm -rf .release; \
|
|
45
|
-
if [ "$$token" = "$(
|
|
46
|
-
echo "[dry-run]
|
|
45
|
+
if [ "$$token" = "$(BORU_DRY_RUN_FILLER)" ]; then \
|
|
46
|
+
echo "[dry-run] boru filler token detected: would tag $(TAG); nothing pushed."; \
|
|
47
47
|
exit 0; \
|
|
48
48
|
fi; \
|
|
49
49
|
git tag -a "$(TAG)" -m "perl v$(VERSION)"; \
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
use strict;
|
|
4
4
|
use warnings;
|
|
5
5
|
|
|
6
|
+
use File::Basename ();
|
|
7
|
+
use Cwd ();
|
|
8
|
+
|
|
9
|
+
my $__dir;
|
|
10
|
+
BEGIN { $__dir = File::Basename::dirname(Cwd::abs_path(__FILE__)) }
|
|
11
|
+
require(Cwd::abs_path("$__dir/../core/helpers.pm"));
|
|
12
|
+
|
|
6
13
|
package ProjectNameUtilities;
|
|
7
14
|
|
|
8
15
|
our %REGISTRY;
|
|
@@ -18,6 +25,16 @@ my %METHOD_MAP = (
|
|
|
18
25
|
|
|
19
26
|
$REGISTRY{prepare_method} = sub {
|
|
20
27
|
my ($ctx) = @_;
|
|
28
|
+
|
|
29
|
+
# The API definition is authoritative: a POST-only or PATCH-based API
|
|
30
|
+
# exposes `update` as POST or PATCH, not the PUT the op name implies.
|
|
31
|
+
# Only fall back to the op-name convention when the point has no method.
|
|
32
|
+
my $point = $ctx->{point};
|
|
33
|
+
if ($point) {
|
|
34
|
+
my $pm = ProjectNameHelpers::gp($point, 'method');
|
|
35
|
+
return uc($pm) if defined $pm && !ref($pm) && '' ne $pm;
|
|
36
|
+
}
|
|
37
|
+
|
|
21
38
|
my $m = $METHOD_MAP{ $ctx->{op}{name} };
|
|
22
39
|
return defined $m ? $m : 'GET';
|
|
23
40
|
};
|
|
@@ -13,20 +13,20 @@ clean:
|
|
|
13
13
|
# Packagist has no upload — it tracks the repo and indexes each git tag
|
|
14
14
|
# php/vX.Y.Z (a configured GitHub webhook, or the Packagist "Update" API,
|
|
15
15
|
# refreshes the package). So publishing a version means pushing the tag. Run
|
|
16
|
-
# headless under the
|
|
17
|
-
#
|
|
18
|
-
#
|
|
16
|
+
# headless under the boru key vault, which injects the GitHub token:
|
|
17
|
+
# boru vault exec --for=github=<alias> -- make publish
|
|
18
|
+
# boru `--dry-run` injects a filler token we detect to rehearse (no tag/push).
|
|
19
19
|
# set -e + tag-last: a failed push never leaves a tag behind.
|
|
20
20
|
VERSION := $(shell cat VERSION)
|
|
21
21
|
TAG := php/v$(VERSION)
|
|
22
|
-
|
|
22
|
+
BORU_DRY_RUN_FILLER := BORU-DRY-RUN-FILLER-NOT-A-REAL-SECRET
|
|
23
23
|
publish: test
|
|
24
24
|
@if git rev-parse -q --verify "refs/tags/$(TAG)" >/dev/null; then \
|
|
25
25
|
echo "tag $(TAG) already exists — bump VERSION first"; exit 1; fi
|
|
26
26
|
@set -e; \
|
|
27
27
|
token="$${GITHUB_TOKEN:-$$GH_TOKEN}"; \
|
|
28
|
-
if [ "$$token" = "$(
|
|
29
|
-
echo "[dry-run]
|
|
28
|
+
if [ "$$token" = "$(BORU_DRY_RUN_FILLER)" ]; then \
|
|
29
|
+
echo "[dry-run] boru filler token detected: would tag $(TAG) for Packagist; nothing pushed."; \
|
|
30
30
|
exit 0; \
|
|
31
31
|
fi; \
|
|
32
32
|
git tag -a "$(TAG)" -m "php v$(VERSION)"; \
|
|
@@ -48,11 +48,39 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
|
|
|
48
48
|
$entity->data = $entity_data;
|
|
49
49
|
|
|
50
50
|
$test_fetcher = function (ProjectNameContext $fctx, string $_fullurl, array $_fetchdef) use ($entity): array {
|
|
51
|
-
|
|
51
|
+
// Shape the mock payload the way the real API would, so the op's
|
|
52
|
+
// response transform recovers the entity from it. A point carrying
|
|
53
|
+
// transform.res of `body.item` describes an API that answers
|
|
54
|
+
// {"item": {...}}; handing back the bare entity means the
|
|
55
|
+
// transform unwraps a property that is not there and the caller
|
|
56
|
+
// gets null. The mock has to agree with the model, or it only
|
|
57
|
+
// ever simulates APIs whose responses happen to be unwrapped.
|
|
58
|
+
// Mirrors the ts/py mocks.
|
|
59
|
+
$envelope = function (mixed $data) use ($fctx): mixed {
|
|
60
|
+
$point = $fctx->point ?? null;
|
|
61
|
+
if ($data === null || !is_array($point)) {
|
|
62
|
+
return $data;
|
|
63
|
+
}
|
|
64
|
+
$transform = $point['transform'] ?? null;
|
|
65
|
+
if (!is_array($transform)) {
|
|
66
|
+
return $data;
|
|
67
|
+
}
|
|
68
|
+
$restf = $transform['res'] ?? null;
|
|
69
|
+
if (!is_string($restf)) {
|
|
70
|
+
return $data;
|
|
71
|
+
}
|
|
72
|
+
if (!preg_match('/^`body\.([^`.]+)`$/', $restf, $m)) {
|
|
73
|
+
return $data;
|
|
74
|
+
}
|
|
75
|
+
return [$m[1] => $data];
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
$respond = function (int $status, mixed $data, ?array $extra = null) use ($envelope): array {
|
|
79
|
+
$payload = $envelope($data);
|
|
52
80
|
$out = [
|
|
53
81
|
'status' => $status,
|
|
54
82
|
'statusText' => 'OK',
|
|
55
|
-
'json' => function () use ($
|
|
83
|
+
'json' => function () use ($payload) { return $payload; },
|
|
56
84
|
'body' => 'not-used',
|
|
57
85
|
];
|
|
58
86
|
if ($extra) {
|
|
@@ -587,6 +587,10 @@ class PrimaryUtilityTest extends TestCase
|
|
|
587
587
|
{
|
|
588
588
|
$calls = [];
|
|
589
589
|
$live_client = new ProjectNameSDK([
|
|
590
|
+
// Concrete base: a live construction must satisfy any server
|
|
591
|
+
// variables a templated base URL declares; a literal base
|
|
592
|
+
// sidesteps the requirement.
|
|
593
|
+
'base' => 'http://localhost:8080',
|
|
590
594
|
'system' => [
|
|
591
595
|
'fetch' => function (string $url, array $fetchdef) use (&$calls) {
|
|
592
596
|
$calls[] = ['url' => $url, 'init' => $fetchdef];
|
|
@@ -614,6 +618,7 @@ class PrimaryUtilityTest extends TestCase
|
|
|
614
618
|
public function test_fetcher_blocked_test_mode(): void
|
|
615
619
|
{
|
|
616
620
|
$blocked_client = new ProjectNameSDK([
|
|
621
|
+
'base' => 'http://localhost:8080',
|
|
617
622
|
'system' => [
|
|
618
623
|
'fetch' => function (string $url, array $fetchdef) {
|
|
619
624
|
return [[], null];
|
|
@@ -88,6 +88,11 @@ class ProjectNameMakeOptions
|
|
|
88
88
|
'system' => (object)[],
|
|
89
89
|
'test' => ['active' => false, 'entity' => ['`$OPEN`' => true]],
|
|
90
90
|
'clean' => ['keys' => 'key,token,id'],
|
|
91
|
+
// Server-variable values for a templated base URL (OpenAPI server
|
|
92
|
+
// variables): {name} placeholders in 'base' are substituted from
|
|
93
|
+
// this map at construction. Spec defaults arrive via the generated
|
|
94
|
+
// config; user values override them.
|
|
95
|
+
'server' => ['`$CHILD`' => ''],
|
|
91
96
|
];
|
|
92
97
|
|
|
93
98
|
// Empty [] would be treated as a list and clobber the map under merge;
|
|
@@ -101,6 +106,45 @@ class ProjectNameMakeOptions
|
|
|
101
106
|
$opts = [];
|
|
102
107
|
}
|
|
103
108
|
|
|
109
|
+
// Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
|
|
110
|
+
// Every placeholder must resolve to a non-empty value: from
|
|
111
|
+
// $options['server'] (user), else the config default. A placeholder
|
|
112
|
+
// that resolves to '' is a construction ERROR in live mode — the URL
|
|
113
|
+
// cannot work — but in test mode substitutes the deterministic value
|
|
114
|
+
// "test-<name>" so offline tests need no configuration.
|
|
115
|
+
$base = $opts['base'] ?? null;
|
|
116
|
+
if (is_string($base) && strpos($base, '{') !== false) {
|
|
117
|
+
$testmode =
|
|
118
|
+
\Voxgig\Struct\Struct::getpath($opts, 'test.active') === true
|
|
119
|
+
|| \Voxgig\Struct\Struct::getpath($opts, 'feature.test.active') === true;
|
|
120
|
+
$server = is_array($opts['server'] ?? null) ? $opts['server'] : [];
|
|
121
|
+
$sdkname = \Voxgig\Struct\Struct::getpath($config, 'main.name');
|
|
122
|
+
if (!is_string($sdkname) || $sdkname === '') {
|
|
123
|
+
$sdkname = 'SDK';
|
|
124
|
+
}
|
|
125
|
+
$opts['base'] = preg_replace_callback(
|
|
126
|
+
'/\{([A-Za-z0-9_]+)\}/',
|
|
127
|
+
function ($m) use ($server, $testmode, $sdkname, $base) {
|
|
128
|
+
$name = $m[1];
|
|
129
|
+
$val = $server[$name] ?? '';
|
|
130
|
+
if (!is_string($val)) {
|
|
131
|
+
$val = '';
|
|
132
|
+
}
|
|
133
|
+
if ($val === '') {
|
|
134
|
+
if ($testmode) {
|
|
135
|
+
return 'test-' . $name;
|
|
136
|
+
}
|
|
137
|
+
throw new \InvalidArgumentException(
|
|
138
|
+
$sdkname . ": the server variable '" . $name . "' is required: " .
|
|
139
|
+
"the API base URL is '" . $base . "' — pass " .
|
|
140
|
+
"['server' => ['" . $name . "' => '...']] in the SDK options");
|
|
141
|
+
}
|
|
142
|
+
return $val;
|
|
143
|
+
},
|
|
144
|
+
$base
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
104
148
|
if ($sys_fetch) {
|
|
105
149
|
if (!is_array($opts['system'] ?? null)) {
|
|
106
150
|
$opts['system'] = [];
|
|
@@ -16,6 +16,17 @@ class ProjectNamePrepareMethod
|
|
|
16
16
|
|
|
17
17
|
public static function call(ProjectNameContext $ctx): string
|
|
18
18
|
{
|
|
19
|
+
// The API definition is authoritative: a POST-only or PATCH-based API
|
|
20
|
+
// exposes `update` as POST or PATCH, not the PUT the op name implies.
|
|
21
|
+
// Only fall back to the op-name convention when the point has no method.
|
|
22
|
+
$point = $ctx->point;
|
|
23
|
+
if ($point) {
|
|
24
|
+
$method = \Voxgig\Struct\Struct::getprop($point, 'method');
|
|
25
|
+
if (is_string($method) && '' !== $method) {
|
|
26
|
+
return strtoupper($method);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
19
30
|
return self::METHOD_MAP[$ctx->op->name] ?? 'GET';
|
|
20
31
|
}
|
|
21
32
|
}
|
|
@@ -10,16 +10,16 @@ clean:
|
|
|
10
10
|
find . -name __pycache__ -exec rm -rf {} + 2>/dev/null; true
|
|
11
11
|
|
|
12
12
|
# --- Release: publish to PyPI ---------------------------------------------
|
|
13
|
-
# Publish this package to PyPI, then tag py/vX.Y.Z. Run headless under the
|
|
13
|
+
# Publish this package to PyPI, then tag py/vX.Y.Z. Run headless under the boru
|
|
14
14
|
# key vault, which injects the PyPI + GitHub tokens as environment variables
|
|
15
15
|
# (never written to disk or argv):
|
|
16
|
-
#
|
|
17
|
-
#
|
|
16
|
+
# boru vault exec --for=pypi=<alias> --for=github=<alias> -- make publish
|
|
17
|
+
# boru `--dry-run` injects a filler token we detect to rehearse: builds the dist
|
|
18
18
|
# but performs no upload, tag, or push. set -e + tag-last: a failed upload never
|
|
19
19
|
# leaves a tag behind. Needs python `build` + `twine` (pip install build twine).
|
|
20
20
|
VERSION := $(shell sed -nE 's/^version *= *"([^"]+)".*/\1/p' pyproject.toml | head -1)
|
|
21
21
|
TAG := py/v$(VERSION)
|
|
22
|
-
|
|
22
|
+
BORU_DRY_RUN_FILLER := BORU-DRY-RUN-FILLER-NOT-A-REAL-SECRET
|
|
23
23
|
publish: test
|
|
24
24
|
@if git rev-parse -q --verify "refs/tags/$(TAG)" >/dev/null; then \
|
|
25
25
|
echo "tag $(TAG) already exists — bump pyproject.toml version first"; exit 1; fi
|
|
@@ -27,8 +27,8 @@ publish: test
|
|
|
27
27
|
token="$${GITHUB_TOKEN:-$$GH_TOKEN}"; \
|
|
28
28
|
rm -rf dist; \
|
|
29
29
|
python3 -m build; \
|
|
30
|
-
if [ "$$token" = "$(
|
|
31
|
-
echo "[dry-run]
|
|
30
|
+
if [ "$$token" = "$(BORU_DRY_RUN_FILLER)" ] || [ "$$TWINE_PASSWORD" = "$(BORU_DRY_RUN_FILLER)" ]; then \
|
|
31
|
+
echo "[dry-run] boru filler token detected: built the dist; would upload to PyPI and tag $(TAG); nothing pushed."; \
|
|
32
32
|
exit 0; \
|
|
33
33
|
fi; \
|
|
34
34
|
python3 -m twine upload dist/*; \
|
|
@@ -468,6 +468,10 @@ class TestPrimaryUtility:
|
|
|
468
468
|
return {"status": 200, "statusText": "OK"}, None
|
|
469
469
|
|
|
470
470
|
live_client = ProjectNameSDK({
|
|
471
|
+
# Concrete base: a live construction must satisfy any server
|
|
472
|
+
# variables a templated base URL declares; a literal base
|
|
473
|
+
# sidesteps the requirement.
|
|
474
|
+
"base": "http://localhost:8080",
|
|
471
475
|
"system": {
|
|
472
476
|
"fetch": mock_fetch,
|
|
473
477
|
},
|
|
@@ -490,6 +494,7 @@ class TestPrimaryUtility:
|
|
|
490
494
|
return {}, None
|
|
491
495
|
|
|
492
496
|
blocked_client = ProjectNameSDK({
|
|
497
|
+
"base": "http://localhost:8080",
|
|
493
498
|
"system": {
|
|
494
499
|
"fetch": mock_fetch,
|
|
495
500
|
},
|
|
@@ -84,6 +84,13 @@ def make_options_util(ctx):
|
|
|
84
84
|
"clean": {
|
|
85
85
|
"keys": "key,token,id",
|
|
86
86
|
},
|
|
87
|
+
# Server-variable values for a templated base URL (OpenAPI server
|
|
88
|
+
# variables): {name} placeholders in "base" are substituted from this
|
|
89
|
+
# map at construction. Spec defaults arrive via the generated config;
|
|
90
|
+
# user values override them.
|
|
91
|
+
"server": {
|
|
92
|
+
"`$CHILD`": "",
|
|
93
|
+
},
|
|
87
94
|
}
|
|
88
95
|
|
|
89
96
|
# Preserve system.fetch before merge/validate.
|
|
@@ -95,6 +102,41 @@ def make_options_util(ctx):
|
|
|
95
102
|
validated = {}
|
|
96
103
|
opts = validated
|
|
97
104
|
|
|
105
|
+
# Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
|
|
106
|
+
# Every placeholder must resolve to a non-empty value: from
|
|
107
|
+
# options["server"] (user), else the config default. A placeholder that
|
|
108
|
+
# resolves to "" is a construction ERROR in live mode — the URL cannot
|
|
109
|
+
# work — but in test mode substitutes the deterministic value
|
|
110
|
+
# "test-<name>" so offline tests need no configuration.
|
|
111
|
+
base = opts.get("base")
|
|
112
|
+
if isinstance(base, str) and "{" in base:
|
|
113
|
+
import re as _re
|
|
114
|
+
testmode = (
|
|
115
|
+
vs.getpath(opts, "test.active") is True
|
|
116
|
+
or vs.getpath(opts, "feature.test.active") is True
|
|
117
|
+
)
|
|
118
|
+
server = opts.get("server") if isinstance(opts.get("server"), dict) else {}
|
|
119
|
+
sdkname = "SDK"
|
|
120
|
+
if isinstance(config, dict):
|
|
121
|
+
mn = vs.getpath(config, "main.name")
|
|
122
|
+
if isinstance(mn, str) and mn != "":
|
|
123
|
+
sdkname = mn
|
|
124
|
+
|
|
125
|
+
def _sub(m):
|
|
126
|
+
name = m.group(1)
|
|
127
|
+
val = server.get(name)
|
|
128
|
+
val = val if isinstance(val, str) else ""
|
|
129
|
+
if val == "":
|
|
130
|
+
if testmode:
|
|
131
|
+
return "test-" + name
|
|
132
|
+
raise ValueError(
|
|
133
|
+
sdkname + ": the server variable '" + name + "' is required: "
|
|
134
|
+
"the API base URL is '" + base + "' — pass "
|
|
135
|
+
'{"server": {"' + name + '": "..."}} in the SDK options')
|
|
136
|
+
return val
|
|
137
|
+
|
|
138
|
+
opts["base"] = _re.sub(r"\{([A-Za-z0-9_]+)\}", _sub, base)
|
|
139
|
+
|
|
98
140
|
# Restore system.fetch.
|
|
99
141
|
if sys_fetch is not None:
|
|
100
142
|
if isinstance(opts.get("system"), dict):
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# ProjectName SDK utility: prepare_method
|
|
2
2
|
|
|
3
|
+
from utility.voxgig_struct import voxgig_struct as vs
|
|
4
|
+
|
|
3
5
|
METHOD_MAP = {
|
|
4
6
|
"create": "POST",
|
|
5
7
|
"update": "PUT",
|
|
@@ -12,4 +14,12 @@ METHOD_MAP = {
|
|
|
12
14
|
|
|
13
15
|
def prepare_method_util(ctx):
|
|
14
16
|
opname = ctx.op.name
|
|
17
|
+
|
|
18
|
+
# The API definition is authoritative: a POST-only or PATCH-based API
|
|
19
|
+
# exposes `update` as POST or PATCH, not the PUT the op name implies.
|
|
20
|
+
# Only fall back to the op-name convention when the point has no method.
|
|
21
|
+
method = vs.getprop(ctx.point, "method")
|
|
22
|
+
if isinstance(method, str) and "" != method:
|
|
23
|
+
return method.upper()
|
|
24
|
+
|
|
15
25
|
return METHOD_MAP.get(opname, "GET")
|
|
@@ -13,14 +13,14 @@ clean:
|
|
|
13
13
|
|
|
14
14
|
# --- Release: publish to RubyGems -----------------------------------------
|
|
15
15
|
# Build the gem and push it to RubyGems, then tag rb/vX.Y.Z. Run headless under
|
|
16
|
-
# the
|
|
16
|
+
# the boru key vault, which injects GEM_HOST_API_KEY + the GitHub token (never on
|
|
17
17
|
# disk or argv):
|
|
18
|
-
#
|
|
19
|
-
#
|
|
18
|
+
# boru vault exec --for=gem=<alias> --for=github=<alias> -- make publish
|
|
19
|
+
# boru `--dry-run` injects a filler token we detect to rehearse (build only, no
|
|
20
20
|
# push/tag). set -e + tag-last: a failed push never leaves a tag behind.
|
|
21
21
|
VERSION := $(shell sed -nE 's/.*\.version *= *"([^"]+)".*/\1/p' *.gemspec | head -1)
|
|
22
22
|
TAG := rb/v$(VERSION)
|
|
23
|
-
|
|
23
|
+
BORU_DRY_RUN_FILLER := BORU-DRY-RUN-FILLER-NOT-A-REAL-SECRET
|
|
24
24
|
publish: test
|
|
25
25
|
@if git rev-parse -q --verify "refs/tags/$(TAG)" >/dev/null; then \
|
|
26
26
|
echo "tag $(TAG) already exists — bump the gemspec version first"; exit 1; fi
|
|
@@ -28,8 +28,8 @@ publish: test
|
|
|
28
28
|
token="$${GITHUB_TOKEN:-$$GH_TOKEN}"; \
|
|
29
29
|
rm -f *.gem; \
|
|
30
30
|
gem build *.gemspec; \
|
|
31
|
-
if [ "$$token" = "$(
|
|
32
|
-
echo "[dry-run]
|
|
31
|
+
if [ "$$token" = "$(BORU_DRY_RUN_FILLER)" ] || [ "$$GEM_HOST_API_KEY" = "$(BORU_DRY_RUN_FILLER)" ]; then \
|
|
32
|
+
echo "[dry-run] boru filler token detected: built the gem; would push to RubyGems and tag $(TAG); nothing pushed."; \
|
|
33
33
|
exit 0; \
|
|
34
34
|
fi; \
|
|
35
35
|
gem push *.gem; \
|
|
@@ -176,6 +176,9 @@ class PrimaryUtilityTest < Minitest::Test
|
|
|
176
176
|
def test_fetcher_live
|
|
177
177
|
calls = []
|
|
178
178
|
live_client = ProjectNameSDK.new({
|
|
179
|
+
# Concrete base: a live construction must satisfy any server variables
|
|
180
|
+
# a templated base URL declares; a literal base sidesteps the requirement.
|
|
181
|
+
"base" => "http://localhost:8080",
|
|
179
182
|
"system" => {
|
|
180
183
|
"fetch" => ->(url, fetchdef) {
|
|
181
184
|
calls << { "url" => url, "init" => fetchdef }
|
|
@@ -199,6 +202,7 @@ class PrimaryUtilityTest < Minitest::Test
|
|
|
199
202
|
|
|
200
203
|
def test_fetcher_blocked_test_mode
|
|
201
204
|
blocked_client = ProjectNameSDK.new({
|
|
205
|
+
"base" => "http://localhost:8080",
|
|
202
206
|
"system" => {
|
|
203
207
|
"fetch" => ->(url, fetchdef) {
|
|
204
208
|
return {}, nil
|