@voxgig/sdkgen 4.6.0 → 4.7.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/bin/voxgig-sdkgen +1 -1
- package/dist/cmp/Feature.js +5 -0
- package/dist/cmp/Feature.js.map +1 -1
- package/dist/cmp/TestControl.d.ts +4 -0
- package/dist/cmp/TestControl.js +51 -0
- package/dist/cmp/TestControl.js.map +1 -0
- package/dist/helpers/featureSource.d.ts +3 -1
- package/dist/helpers/featureSource.js +105 -0
- package/dist/helpers/featureSource.js.map +1 -1
- package/dist/sdkgen.d.ts +3 -2
- package/dist/sdkgen.js +9 -4
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.d.ts +2 -1
- package/dist/utility.js +52 -0
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aon +58 -0
- package/package.json +1 -1
- package/project/.sdk/model/feature/secrets.aon +82 -0
- package/project/.sdk/src/cmp/cpp/Main_cpp.ts +2 -1
- package/project/.sdk/src/cmp/cpp/Test_cpp.ts +5 -1
- package/project/.sdk/src/cmp/csharp/Main_csharp.ts +2 -1
- package/project/.sdk/src/cmp/csharp/TestDirect_csharp.ts +30 -6
- package/project/.sdk/src/cmp/csharp/TestEntity_csharp.ts +29 -5
- package/project/.sdk/src/cmp/csharp/Test_csharp.ts +5 -1
- package/project/.sdk/src/cmp/dart/Main_dart.ts +2 -1
- package/project/.sdk/src/cmp/dart/TestDirect_dart.ts +23 -6
- package/project/.sdk/src/cmp/dart/TestEntity_dart.ts +28 -7
- package/project/.sdk/src/cmp/dart/Test_dart.ts +5 -1
- package/project/.sdk/src/cmp/go/Main_go.ts +2 -1
- package/project/.sdk/src/cmp/go/Test_go.ts +5 -1
- package/project/.sdk/src/cmp/java/Main_java.ts +2 -1
- package/project/.sdk/src/cmp/java/Test_java.ts +5 -1
- package/project/.sdk/src/cmp/js/Main_js.ts +2 -1
- package/project/.sdk/src/cmp/js/TestDirect_js.ts +30 -5
- package/project/.sdk/src/cmp/js/TestEntity_js.ts +35 -5
- package/project/.sdk/src/cmp/js/Test_js.ts +5 -1
- package/project/.sdk/src/cmp/js/fragment/Direct.test.fragment.js +7 -1
- package/project/.sdk/src/cmp/js/fragment/Entity.test.fragment.js +7 -1
- package/project/.sdk/src/cmp/kotlin/Main_kotlin.ts +2 -1
- package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +5 -1
- package/project/.sdk/src/cmp/lua/Main_lua.ts +2 -1
- package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +28 -4
- package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +24 -4
- package/project/.sdk/src/cmp/lua/Test_lua.ts +5 -1
- package/project/.sdk/src/cmp/ocaml/Gitignore_ocaml.ts +1 -0
- package/project/.sdk/src/cmp/perl/Main_perl.ts +2 -1
- package/project/.sdk/src/cmp/perl/TestDirect_perl.ts +25 -4
- package/project/.sdk/src/cmp/perl/TestEntity_perl.ts +24 -4
- package/project/.sdk/src/cmp/perl/Test_perl.ts +5 -1
- package/project/.sdk/src/cmp/perl/utility_perl.ts +10 -0
- package/project/.sdk/src/cmp/php/Main_php.ts +2 -1
- package/project/.sdk/src/cmp/php/TestDirect_php.ts +24 -5
- package/project/.sdk/src/cmp/php/TestEntity_php.ts +28 -4
- package/project/.sdk/src/cmp/php/Test_php.ts +5 -1
- package/project/.sdk/src/cmp/py/Main_py.ts +2 -1
- package/project/.sdk/src/cmp/py/Test_py.ts +5 -1
- package/project/.sdk/src/cmp/rb/Main_rb.ts +2 -1
- package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +24 -5
- package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +24 -4
- package/project/.sdk/src/cmp/rb/Test_rb.ts +5 -1
- package/project/.sdk/src/cmp/rb/utility_rb.ts +3 -1
- package/project/.sdk/src/cmp/rust/Main_rust.ts +2 -1
- package/project/.sdk/src/cmp/rust/TestDirect_rust.ts +31 -4
- package/project/.sdk/src/cmp/rust/TestEntity_rust.ts +37 -4
- package/project/.sdk/src/cmp/rust/Test_rust.ts +5 -1
- package/project/.sdk/src/cmp/scala/Main_scala.ts +2 -1
- package/project/.sdk/src/cmp/scala/Test_scala.ts +5 -1
- package/project/.sdk/src/cmp/ts/Config_ts.ts +50 -8
- package/project/.sdk/src/cmp/ts/Main_ts.ts +10 -2
- package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +10 -3
- package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +11 -3
- package/project/.sdk/src/cmp/ts/Test_ts.ts +5 -1
- package/project/.sdk/tm/csharp/test/Runner.cs +45 -0
- package/project/.sdk/tm/dart/test/utility.dart +39 -0
- package/project/.sdk/tm/js/test/sdk-test-control.json +19 -0
- package/project/.sdk/tm/js/test/utility.js +91 -1
- package/project/.sdk/tm/lua/test/runner.lua +40 -0
- package/project/.sdk/tm/ocaml/test/harness.ml +14 -4
- package/project/.sdk/tm/perl/t/runner.pm +30 -0
- package/project/.sdk/tm/php/test/Runner.php +36 -0
- package/project/.sdk/tm/rb/test/runner.rb +25 -0
- package/project/.sdk/tm/rust/tests/common/mod.rs +32 -0
- package/project/.sdk/tm/rust/utility/make_options.rs +1 -1
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +8 -2
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +29 -5
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +30 -21
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/Registry.ts +90 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/addr.ts +48 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/aws.ts +219 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/azuresecrets.ts +144 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/boru.ts +113 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/doppler.ts +77 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/dotenv.ts +51 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/env.ts +31 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/file.ts +54 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/gcpsecrets.ts +118 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/hashicorp.ts +159 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/http.ts +66 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/infisical.ts +107 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/memory.ts +33 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/onepassword.ts +120 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/support.ts +176 -0
- package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +15 -13
- package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +25 -19
- package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +1 -1
- package/project/.sdk/tm/ts/test/vendor/omni/index.ts +1 -1
- package/project/.sdk/tm/zig/core/utility.zig +1 -1
- package/project/sdkgen-package.json +1 -1
- package/src/cmp/Feature.ts +5 -0
- package/src/cmp/TestControl.ts +64 -0
- package/src/helpers/featureSource.ts +130 -0
- package/src/sdkgen.ts +7 -0
- package/src/utility.ts +61 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Providers.ts +0 -1221
|
@@ -17,9 +17,13 @@ import {
|
|
|
17
17
|
each,
|
|
18
18
|
buildIdNames,
|
|
19
19
|
getMatchEntries,
|
|
20
|
-
isAuthActive, envName, envToken
|
|
20
|
+
isAuthActive, envName, envToken,
|
|
21
|
+
serverVarEnv,
|
|
22
|
+
serverVariables
|
|
21
23
|
} from '@voxgig/sdkgen'
|
|
22
24
|
|
|
25
|
+
import { perlStringLiteral } from './utility_perl'
|
|
26
|
+
|
|
23
27
|
|
|
24
28
|
// See TestEntity_ts.ts for the GenCtx/OpGen contract. Flow-step variables
|
|
25
29
|
// are held in a single declared perl hash (%V) so generated steps never
|
|
@@ -54,12 +58,25 @@ const TestEntity = cmp(function TestEntity(props: any) {
|
|
|
54
58
|
|
|
55
59
|
const authActive = isAuthActive(model)
|
|
56
60
|
const apikeyEnvEntry = authActive
|
|
57
|
-
? `\n '${PROJUPPER}_APIKEY' => '
|
|
61
|
+
? `\n '${PROJUPPER}_APIKEY' => '',`
|
|
58
62
|
: ''
|
|
59
63
|
const apikeyLiveField = authActive
|
|
60
64
|
? `\n 'apikey' => $env->{'${PROJUPPER}_APIKEY'},`
|
|
61
65
|
: ''
|
|
62
66
|
|
|
67
|
+
// A templated server URL (OpenAPI server variables) makes a LIVE client
|
|
68
|
+
// impossible to construct without values: makeOptions raises rather than
|
|
69
|
+
// request a URL with a literal `{account_id}` in it. So the live suite
|
|
70
|
+
// takes them from the environment the same way it takes the apikey.
|
|
71
|
+
const svars = serverVariables(model)
|
|
72
|
+
const serverEnvEntry = svars
|
|
73
|
+
.map((v: any) => `\n '${serverVarEnv(PROJUPPER, v.name)}' => ${perlStringLiteral(v.dflt)},`).join('')
|
|
74
|
+
const serverLiveField = 0 === svars.length ? '' : `
|
|
75
|
+
'server' => {${svars
|
|
76
|
+
.map((v: any) => `
|
|
77
|
+
'${v.name}' => $env->{'${serverVarEnv(PROJUPPER, v.name)}'},`).join('')}
|
|
78
|
+
},`
|
|
79
|
+
|
|
63
80
|
const idnames = buildIdNames(entity, basicflow)
|
|
64
81
|
const idnamesStr = idnames.map(n => `'${n}'`).join(', ')
|
|
65
82
|
|
|
@@ -193,7 +210,7 @@ BASIC_FLOW: {
|
|
|
193
210
|
my $env = ${N}TestRunner::env_override({
|
|
194
211
|
'${ENTIDVAR}' => $idmap,
|
|
195
212
|
'${PROJUPPER}_TEST_LIVE' => 'FALSE',
|
|
196
|
-
'${PROJUPPER}_TEST_EXPLAIN' => 'FALSE',${apikeyEnvEntry}
|
|
213
|
+
'${PROJUPPER}_TEST_EXPLAIN' => 'FALSE',${apikeyEnvEntry}${serverEnvEntry}
|
|
197
214
|
});
|
|
198
215
|
|
|
199
216
|
my $idmap_resolved = ${N}Helpers::to_map($env->{'${ENTIDVAR}'});
|
|
@@ -213,7 +230,10 @@ BASIC_FLOW: {
|
|
|
213
230
|
Content(`
|
|
214
231
|
if ((($env->{'${PROJUPPER}_TEST_LIVE'}) || '') eq 'TRUE') {
|
|
215
232
|
my $merged_opts = Voxgig::Struct::merge([
|
|
216
|
-
|
|
233
|
+
# FIRST, so the generated fields below win: sdk-test-control.json's
|
|
234
|
+
# test.client.options adds to the live client, it does not redirect it.
|
|
235
|
+
${N}TestRunner::live_client_options(),
|
|
236
|
+
{${apikeyLiveField}${serverLiveField}
|
|
217
237
|
},
|
|
218
238
|
(Voxgig::Struct::ismap($extra) ? $extra : {}),
|
|
219
239
|
]);
|
|
@@ -3,7 +3,8 @@ import type {
|
|
|
3
3
|
ModelEntity
|
|
4
4
|
} from '@voxgig/apidef'
|
|
5
5
|
|
|
6
|
-
import { cmp, each, Folder, File, Content, entityCollection
|
|
6
|
+
import { cmp, each, Folder, File, Content, entityCollection,
|
|
7
|
+
TestControl } from '@voxgig/sdkgen'
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
import { TestEntity } from './TestEntity_perl'
|
|
@@ -17,6 +18,9 @@ const Test = cmp(function Test(props: any) {
|
|
|
17
18
|
|
|
18
19
|
Folder({ name: 't' }, () => {
|
|
19
20
|
|
|
21
|
+
// Write-once: a project's edited control file survives regeneration.
|
|
22
|
+
TestControl({ target, dir: 't' })
|
|
23
|
+
|
|
20
24
|
// Generate exists test
|
|
21
25
|
File({ name: 'exists.t' }, () => {
|
|
22
26
|
Content(`#!perl
|
|
@@ -75,7 +75,17 @@ function clean(o: any, dropDefaults?: boolean): any {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
|
|
78
|
+
// A Perl SINGLE-quoted string literal. Single quotes do not interpolate, so a
|
|
79
|
+
// spec-derived value containing $foo or @foo survives verbatim; only the
|
|
80
|
+
// backslash and the quote itself need escaping. A double-quoted literal (what
|
|
81
|
+
// JSON.stringify emits) would interpolate them.
|
|
82
|
+
function perlStringLiteral(val: string): string {
|
|
83
|
+
return "'" + String(val).replace(/\\/g, '\\\\').replace(/'/g, "\\'") + "'"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
78
87
|
export {
|
|
88
|
+
perlStringLiteral,
|
|
79
89
|
clean,
|
|
80
90
|
projectPath,
|
|
81
91
|
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
cmp, each, names, cmap,
|
|
6
6
|
List, File, Content, Copy, Folder, Fragment, Line, FeatureHook,
|
|
7
7
|
targetFeatures,
|
|
8
|
+
TEST_CONTROL_EXCLUDE
|
|
8
9
|
} from '@voxgig/sdkgen'
|
|
9
10
|
|
|
10
11
|
|
|
@@ -44,7 +45,7 @@ const Main = cmp(async function Main(props: any) {
|
|
|
44
45
|
// Copy tm/php files with replacements
|
|
45
46
|
Copy({
|
|
46
47
|
from: 'tm/' + target.name,
|
|
47
|
-
exclude: [/src
|
|
48
|
+
exclude: [/src\//, TEST_CONTROL_EXCLUDE],
|
|
48
49
|
replace: {
|
|
49
50
|
...props.ctx$.stdrep,
|
|
50
51
|
}
|
|
@@ -11,9 +11,13 @@ import {
|
|
|
11
11
|
File,
|
|
12
12
|
cmp,
|
|
13
13
|
snakify,
|
|
14
|
-
isAuthActive, envName, envToken
|
|
14
|
+
isAuthActive, envName, envToken,
|
|
15
|
+
serverVarEnv,
|
|
16
|
+
serverVariables
|
|
15
17
|
} from '@voxgig/sdkgen'
|
|
16
18
|
|
|
19
|
+
import { formatPhpValue } from './utility_php'
|
|
20
|
+
|
|
17
21
|
|
|
18
22
|
function normalizePathParams(
|
|
19
23
|
parts: string[],
|
|
@@ -64,12 +68,25 @@ const TestDirect = cmp(function TestDirect(props: any) {
|
|
|
64
68
|
|
|
65
69
|
const authActive = isAuthActive(model)
|
|
66
70
|
const apikeyEnvEntry = authActive
|
|
67
|
-
? `\n "${PROJECTNAME}_APIKEY" => "
|
|
71
|
+
? `\n "${PROJECTNAME}_APIKEY" => "",`
|
|
68
72
|
: ''
|
|
69
73
|
const apikeyLiveField = authActive
|
|
70
74
|
? `\n "apikey" => $env["${PROJECTNAME}_APIKEY"],`
|
|
71
75
|
: ''
|
|
72
76
|
|
|
77
|
+
// A templated server URL (OpenAPI server variables) makes a LIVE client
|
|
78
|
+
// impossible to construct without values: makeOptions raises rather than
|
|
79
|
+
// request a URL with a literal `{account_id}` in it. So the live suite
|
|
80
|
+
// takes them from the environment the same way it takes the apikey.
|
|
81
|
+
const svars = serverVariables(model)
|
|
82
|
+
const serverEnvEntry = svars
|
|
83
|
+
.map((v: any) => `\n "${serverVarEnv(PROJECTNAME, v.name)}" => ${formatPhpValue(v.dflt)},`).join('')
|
|
84
|
+
const serverLiveField = 0 === svars.length ? '' : `
|
|
85
|
+
"server" => [${svars
|
|
86
|
+
.map((v: any) => `
|
|
87
|
+
"${v.name}" => $env["${serverVarEnv(PROJECTNAME, v.name)}"],`).join('')}
|
|
88
|
+
],`
|
|
89
|
+
|
|
73
90
|
const opnames = Object.keys(entity.op || {})
|
|
74
91
|
const hasLoad = opnames.includes('load')
|
|
75
92
|
const hasList = opnames.includes('list')
|
|
@@ -356,14 +373,16 @@ function ${entity.name}_direct_setup($mockres)
|
|
|
356
373
|
|
|
357
374
|
$env = Runner::env_override([
|
|
358
375
|
"${entidEnvVar}" => [],
|
|
359
|
-
"${PROJECTNAME}_TEST_LIVE" => "FALSE",${apikeyEnvEntry}
|
|
376
|
+
"${PROJECTNAME}_TEST_LIVE" => "FALSE",${apikeyEnvEntry}${serverEnvEntry}
|
|
360
377
|
]);
|
|
361
378
|
|
|
362
379
|
$live = $env["${PROJECTNAME}_TEST_LIVE"] === "TRUE";
|
|
363
380
|
|
|
364
381
|
if ($live) {
|
|
365
|
-
|
|
366
|
-
|
|
382
|
+
// Merged so the generated fields win: sdk-test-control.json's
|
|
383
|
+
// test.client.options adds to the live client, it does not redirect it.
|
|
384
|
+
$merged_opts = array_merge(Runner::live_client_options(), [${apikeyLiveField}${serverLiveField}
|
|
385
|
+
]);
|
|
367
386
|
$client = new ${model.const.Name}SDK($merged_opts);
|
|
368
387
|
return [
|
|
369
388
|
"client" => $client,
|
|
@@ -24,10 +24,14 @@ import {
|
|
|
24
24
|
buildIdNames,
|
|
25
25
|
getMatchEntries,
|
|
26
26
|
isAuthActive,
|
|
27
|
+
serverVarEnv,
|
|
28
|
+
serverVariables,
|
|
27
29
|
entityDataIdField, envName, envToken,
|
|
28
30
|
phpEntityAccessor,
|
|
29
31
|
} from '@voxgig/sdkgen'
|
|
30
32
|
|
|
33
|
+
import { formatPhpValue } from './utility_php'
|
|
34
|
+
|
|
31
35
|
|
|
32
36
|
// PHP's GenCtx mirrors the shared shape (see TestEntity_ts.ts) plus an
|
|
33
37
|
// `accessor` slot used to mangle the entity factory name when it collides
|
|
@@ -75,12 +79,25 @@ const TestEntity = cmp(function TestEntity(props: any) {
|
|
|
75
79
|
|
|
76
80
|
const authActive = isAuthActive(model)
|
|
77
81
|
const apikeyEnvEntry = authActive
|
|
78
|
-
? `\n "${PROJUPPER}_APIKEY" => "
|
|
82
|
+
? `\n "${PROJUPPER}_APIKEY" => "",`
|
|
79
83
|
: ''
|
|
80
84
|
const apikeyLiveField = authActive
|
|
81
85
|
? `\n "apikey" => $env["${PROJUPPER}_APIKEY"],`
|
|
82
86
|
: ''
|
|
83
87
|
|
|
88
|
+
// A templated server URL (OpenAPI server variables) makes a LIVE client
|
|
89
|
+
// impossible to construct without values: makeOptions raises rather than
|
|
90
|
+
// request a URL with a literal `{account_id}` in it. So the live suite
|
|
91
|
+
// takes them from the environment the same way it takes the apikey.
|
|
92
|
+
const svars = serverVariables(model)
|
|
93
|
+
const serverEnvEntry = svars
|
|
94
|
+
.map((v: any) => `\n "${serverVarEnv(PROJUPPER, v.name)}" => ${formatPhpValue(v.dflt)},`).join('')
|
|
95
|
+
const serverLiveField = 0 === svars.length ? '' : `
|
|
96
|
+
"server" => [${svars
|
|
97
|
+
.map((v: any) => `
|
|
98
|
+
"${v.name}" => $env["${serverVarEnv(PROJUPPER, v.name)}"],`).join('')}
|
|
99
|
+
],`
|
|
100
|
+
|
|
84
101
|
const idnames = buildIdNames(entity, basicflow)
|
|
85
102
|
const idnamesStr = idnames.map(n => `"${n}"`).join(', ')
|
|
86
103
|
|
|
@@ -246,7 +263,7 @@ ${hasList ? `
|
|
|
246
263
|
$env = Runner::env_override([
|
|
247
264
|
"${PROJUPPER}_TEST_${envToken(entity.name)}_ENTID" => $idmap,
|
|
248
265
|
"${PROJUPPER}_TEST_LIVE" => "FALSE",
|
|
249
|
-
"${PROJUPPER}_TEST_EXPLAIN" => "FALSE",${apikeyEnvEntry}
|
|
266
|
+
"${PROJUPPER}_TEST_EXPLAIN" => "FALSE",${apikeyEnvEntry}${serverEnvEntry}
|
|
250
267
|
]);
|
|
251
268
|
|
|
252
269
|
$idmap_resolved = Helpers::to_map(
|
|
@@ -267,9 +284,16 @@ ${hasList ? `
|
|
|
267
284
|
Content(`
|
|
268
285
|
if ($env["${PROJUPPER}_TEST_LIVE"] === "TRUE") {
|
|
269
286
|
$merged_opts = Vs::merge([
|
|
270
|
-
|
|
287
|
+
// FIRST, so the generated fields below win: sdk-test-control.json's
|
|
288
|
+
// test.client.options adds to the live client, it does not redirect it.
|
|
289
|
+
Runner::live_client_options(),
|
|
290
|
+
[${apikeyLiveField}${serverLiveField}
|
|
271
291
|
],
|
|
272
|
-
|
|
292
|
+
// ismap, not a plain "?? []" default: an empty PHP array is a
|
|
293
|
+
// LIST, and a non-map later entry REPLACES the accumulated map in
|
|
294
|
+
// merge - so the no-extras call discarded live_client_options()
|
|
295
|
+
// and the apikey/server map above it.
|
|
296
|
+
Vs::ismap($extra) ? $extra : new \\stdClass(),
|
|
273
297
|
]);
|
|
274
298
|
$client = new ${model.const.Name}SDK(Helpers::to_map($merged_opts));
|
|
275
299
|
}
|
|
@@ -3,7 +3,8 @@ import type {
|
|
|
3
3
|
ModelEntity
|
|
4
4
|
} from '@voxgig/apidef'
|
|
5
5
|
|
|
6
|
-
import { cmp, each, Folder, File, Content, entityCollection
|
|
6
|
+
import { cmp, each, Folder, File, Content, entityCollection,
|
|
7
|
+
TestControl } from '@voxgig/sdkgen'
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
import { TestEntity } from './TestEntity_php'
|
|
@@ -17,6 +18,9 @@ const Test = cmp(function Test(props: any) {
|
|
|
17
18
|
|
|
18
19
|
Folder({ name: 'test' }, () => {
|
|
19
20
|
|
|
21
|
+
// Write-once: a project's edited control file survives regeneration.
|
|
22
|
+
TestControl({ target, dir: 'test' })
|
|
23
|
+
|
|
20
24
|
// Generate exists test
|
|
21
25
|
File({ name: 'ExistsTest.' + target.ext }, () => {
|
|
22
26
|
Content(`<?php
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
List, File, Content, Copy, Folder, Fragment, Line, FeatureHook,
|
|
7
7
|
entityClassName, entityCollection,
|
|
8
8
|
targetFeatures,
|
|
9
|
+
TEST_CONTROL_EXCLUDE
|
|
9
10
|
} from '@voxgig/sdkgen'
|
|
10
11
|
|
|
11
12
|
|
|
@@ -50,7 +51,7 @@ const Main = cmp(async function Main(props: any) {
|
|
|
50
51
|
// below.
|
|
51
52
|
Copy({
|
|
52
53
|
from: 'tm/' + target.name,
|
|
53
|
-
exclude: [/src\//, /pkg
|
|
54
|
+
exclude: [/src\//, /pkg\//, TEST_CONTROL_EXCLUDE],
|
|
54
55
|
replace: {
|
|
55
56
|
...props.ctx$.stdrep,
|
|
56
57
|
}
|
|
@@ -3,7 +3,8 @@ import type {
|
|
|
3
3
|
ModelEntity
|
|
4
4
|
} from '@voxgig/apidef'
|
|
5
5
|
|
|
6
|
-
import { cmp, each, Folder, File, Content, entityCollection
|
|
6
|
+
import { cmp, each, Folder, File, Content, entityCollection,
|
|
7
|
+
TestControl } from '@voxgig/sdkgen'
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
import { TestEntity } from './TestEntity_py'
|
|
@@ -17,6 +18,9 @@ const Test = cmp(function Test(props: any) {
|
|
|
17
18
|
|
|
18
19
|
Folder({ name: 'test' }, () => {
|
|
19
20
|
|
|
21
|
+
// Write-once: a project's edited control file survives regeneration.
|
|
22
|
+
TestControl({ target, dir: 'test' })
|
|
23
|
+
|
|
20
24
|
// Generate __init__.py for test package
|
|
21
25
|
File({ name: '__init__.' + target.ext }, () => {
|
|
22
26
|
Content(``)
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
cmp, each, names, cmap,
|
|
6
6
|
List, File, Content, Copy, Folder, Fragment, Line, FeatureHook,
|
|
7
7
|
targetFeatures,
|
|
8
|
+
TEST_CONTROL_EXCLUDE
|
|
8
9
|
} from '@voxgig/sdkgen'
|
|
9
10
|
|
|
10
11
|
|
|
@@ -44,7 +45,7 @@ const Main = cmp(async function Main(props: any) {
|
|
|
44
45
|
// Copy tm/rb files with replacements
|
|
45
46
|
Copy({
|
|
46
47
|
from: 'tm/' + target.name,
|
|
47
|
-
exclude: [/src
|
|
48
|
+
exclude: [/src\//, TEST_CONTROL_EXCLUDE],
|
|
48
49
|
replace: {
|
|
49
50
|
...props.ctx$.stdrep,
|
|
50
51
|
}
|
|
@@ -11,9 +11,13 @@ import {
|
|
|
11
11
|
File,
|
|
12
12
|
cmp,
|
|
13
13
|
snakify,
|
|
14
|
-
isAuthActive, envName, envToken
|
|
14
|
+
isAuthActive, envName, envToken,
|
|
15
|
+
serverVarEnv,
|
|
16
|
+
serverVariables
|
|
15
17
|
} from '@voxgig/sdkgen'
|
|
16
18
|
|
|
19
|
+
import { formatRubyValue } from './utility_rb'
|
|
20
|
+
|
|
17
21
|
|
|
18
22
|
function normalizePathParams(
|
|
19
23
|
parts: string[],
|
|
@@ -64,12 +68,25 @@ const TestDirect = cmp(function TestDirect(props: any) {
|
|
|
64
68
|
|
|
65
69
|
const authActive = isAuthActive(model)
|
|
66
70
|
const apikeyEnvEntry = authActive
|
|
67
|
-
? `\n "${PROJECTNAME}_APIKEY" => "
|
|
71
|
+
? `\n "${PROJECTNAME}_APIKEY" => "",`
|
|
68
72
|
: ''
|
|
69
73
|
const apikeyLiveField = authActive
|
|
70
74
|
? `\n "apikey" => env["${PROJECTNAME}_APIKEY"],`
|
|
71
75
|
: ''
|
|
72
76
|
|
|
77
|
+
// A templated server URL (OpenAPI server variables) makes a LIVE client
|
|
78
|
+
// impossible to construct without values: makeOptions raises rather than
|
|
79
|
+
// request a URL with a literal `{account_id}` in it. So the live suite
|
|
80
|
+
// takes them from the environment the same way it takes the apikey.
|
|
81
|
+
const svars = serverVariables(model)
|
|
82
|
+
const serverEnvEntry = svars
|
|
83
|
+
.map((v: any) => `\n "${serverVarEnv(PROJECTNAME, v.name)}" => ${formatRubyValue(v.dflt)},`).join('')
|
|
84
|
+
const serverLiveField = 0 === svars.length ? '' : `
|
|
85
|
+
"server" => {${svars
|
|
86
|
+
.map((v: any) => `
|
|
87
|
+
"${v.name}" => env["${serverVarEnv(PROJECTNAME, v.name)}"],`).join('')}
|
|
88
|
+
},`
|
|
89
|
+
|
|
73
90
|
const opnames = Object.keys(entity.op || {})
|
|
74
91
|
const hasLoad = opnames.includes('load')
|
|
75
92
|
const hasList = opnames.includes('list')
|
|
@@ -349,14 +366,16 @@ def ${entity.name}_direct_setup(mockres)
|
|
|
349
366
|
|
|
350
367
|
env = Runner.env_override({
|
|
351
368
|
"${entidEnvVar}" => {},
|
|
352
|
-
"${PROJECTNAME}_TEST_LIVE" => "FALSE",${apikeyEnvEntry}
|
|
369
|
+
"${PROJECTNAME}_TEST_LIVE" => "FALSE",${apikeyEnvEntry}${serverEnvEntry}
|
|
353
370
|
})
|
|
354
371
|
|
|
355
372
|
live = env["${PROJECTNAME}_TEST_LIVE"] == "TRUE"
|
|
356
373
|
|
|
357
374
|
if live
|
|
358
|
-
|
|
359
|
-
|
|
375
|
+
# Merged so the generated fields win: sdk-test-control.json's
|
|
376
|
+
# test.client.options adds to the live client, it does not redirect it.
|
|
377
|
+
merged_opts = Runner.live_client_options.merge({${apikeyLiveField}${serverLiveField}
|
|
378
|
+
})
|
|
360
379
|
client = ${model.const.Name}SDK.new(merged_opts)
|
|
361
380
|
return {
|
|
362
381
|
client: client,
|
|
@@ -24,9 +24,13 @@ import {
|
|
|
24
24
|
buildIdNames,
|
|
25
25
|
getMatchEntries,
|
|
26
26
|
isAuthActive,
|
|
27
|
-
entityDataIdField, envName, envToken
|
|
27
|
+
entityDataIdField, envName, envToken,
|
|
28
|
+
serverVarEnv,
|
|
29
|
+
serverVariables
|
|
28
30
|
} from '@voxgig/sdkgen'
|
|
29
31
|
|
|
32
|
+
import { formatRubyValue } from './utility_rb'
|
|
33
|
+
|
|
30
34
|
|
|
31
35
|
// See TestEntity_ts.ts for the GenCtx/OpGen contract.
|
|
32
36
|
type GenCtx = {
|
|
@@ -62,12 +66,25 @@ const TestEntity = cmp(function TestEntity(props: any) {
|
|
|
62
66
|
|
|
63
67
|
const authActive = isAuthActive(model)
|
|
64
68
|
const apikeyEnvEntry = authActive
|
|
65
|
-
? `\n "${PROJUPPER}_APIKEY" => "
|
|
69
|
+
? `\n "${PROJUPPER}_APIKEY" => "",`
|
|
66
70
|
: ''
|
|
67
71
|
const apikeyLiveField = authActive
|
|
68
72
|
? `\n "apikey" => env["${PROJUPPER}_APIKEY"],`
|
|
69
73
|
: ''
|
|
70
74
|
|
|
75
|
+
// A templated server URL (OpenAPI server variables) makes a LIVE client
|
|
76
|
+
// impossible to construct without values: makeOptions raises rather than
|
|
77
|
+
// request a URL with a literal `{account_id}` in it. So the live suite
|
|
78
|
+
// takes them from the environment the same way it takes the apikey.
|
|
79
|
+
const svars = serverVariables(model)
|
|
80
|
+
const serverEnvEntry = svars
|
|
81
|
+
.map((v: any) => `\n "${serverVarEnv(PROJUPPER, v.name)}" => ${formatRubyValue(v.dflt)},`).join('')
|
|
82
|
+
const serverLiveField = 0 === svars.length ? '' : `
|
|
83
|
+
"server" => {${svars
|
|
84
|
+
.map((v: any) => `
|
|
85
|
+
"${v.name}" => env["${serverVarEnv(PROJUPPER, v.name)}"],`).join('')}
|
|
86
|
+
},`
|
|
87
|
+
|
|
71
88
|
const idnames = buildIdNames(entity, basicflow)
|
|
72
89
|
const idnamesStr = idnames.map(n => `"${n}"`).join(', ')
|
|
73
90
|
|
|
@@ -227,7 +244,7 @@ end
|
|
|
227
244
|
env = Runner.env_override({
|
|
228
245
|
"${PROJUPPER}_TEST_${envToken(entity.name)}_ENTID" => idmap,
|
|
229
246
|
"${PROJUPPER}_TEST_LIVE" => "FALSE",
|
|
230
|
-
"${PROJUPPER}_TEST_EXPLAIN" => "FALSE",${apikeyEnvEntry}
|
|
247
|
+
"${PROJUPPER}_TEST_EXPLAIN" => "FALSE",${apikeyEnvEntry}${serverEnvEntry}
|
|
231
248
|
})
|
|
232
249
|
|
|
233
250
|
idmap_resolved = Helpers.to_map(
|
|
@@ -248,7 +265,10 @@ end
|
|
|
248
265
|
Content(`
|
|
249
266
|
if env["${PROJUPPER}_TEST_LIVE"] == "TRUE"
|
|
250
267
|
merged_opts = Vs.merge([
|
|
251
|
-
|
|
268
|
+
# FIRST, so the generated fields below win: sdk-test-control.json's
|
|
269
|
+
# test.client.options adds to the live client, it does not redirect it.
|
|
270
|
+
Runner.live_client_options,
|
|
271
|
+
{${apikeyLiveField}${serverLiveField}
|
|
252
272
|
},
|
|
253
273
|
extra || {},
|
|
254
274
|
])
|
|
@@ -3,7 +3,8 @@ import type {
|
|
|
3
3
|
ModelEntity
|
|
4
4
|
} from '@voxgig/apidef'
|
|
5
5
|
|
|
6
|
-
import { cmp, each, Folder, File, Content, entityCollection
|
|
6
|
+
import { cmp, each, Folder, File, Content, entityCollection,
|
|
7
|
+
TestControl } from '@voxgig/sdkgen'
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
import { TestEntity } from './TestEntity_rb'
|
|
@@ -17,6 +18,9 @@ const Test = cmp(function Test(props: any) {
|
|
|
17
18
|
|
|
18
19
|
Folder({ name: 'test' }, () => {
|
|
19
20
|
|
|
21
|
+
// Write-once: a project's edited control file survives regeneration.
|
|
22
|
+
TestControl({ target, dir: 'test' })
|
|
23
|
+
|
|
20
24
|
// Generate exists test
|
|
21
25
|
File({ name: 'exists_test.' + target.ext }, () => {
|
|
22
26
|
Content(`# ${model.const.Name} SDK exists test
|
|
@@ -48,7 +48,9 @@ function formatRubyValue(val: any, indent: number = 0): string {
|
|
|
48
48
|
return 'nil'
|
|
49
49
|
}
|
|
50
50
|
if (typeof val === 'string') {
|
|
51
|
-
|
|
51
|
+
// `#` is escaped only where it OPENS an interpolation (#{, #$, #@), so
|
|
52
|
+
// this changes output solely for values that would otherwise interpolate.
|
|
53
|
+
return `"${val.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/#(?=[{$@])/g, '\\#')}"`
|
|
52
54
|
}
|
|
53
55
|
if (typeof val === 'number') {
|
|
54
56
|
return String(val)
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
File, Content, Copy, Folder, Fragment,
|
|
7
7
|
entityClassName,
|
|
8
8
|
targetFeatures,
|
|
9
|
+
TEST_CONTROL_EXCLUDE
|
|
9
10
|
} from '@voxgig/sdkgen'
|
|
10
11
|
|
|
11
12
|
|
|
@@ -54,7 +55,7 @@ const Main = cmp(async function Main(props: any) {
|
|
|
54
55
|
// here exactly like the go target.
|
|
55
56
|
Copy({
|
|
56
57
|
from: 'tm/' + target.name,
|
|
57
|
-
exclude: [/src
|
|
58
|
+
exclude: [/src\//, TEST_CONTROL_EXCLUDE],
|
|
58
59
|
replace: {
|
|
59
60
|
...props.ctx$.stdrep,
|
|
60
61
|
RUSTCRATE: rustcrate,
|
|
@@ -11,7 +11,9 @@ import {
|
|
|
11
11
|
File,
|
|
12
12
|
cmp,
|
|
13
13
|
snakify,
|
|
14
|
-
isAuthActive, envName, envToken
|
|
14
|
+
isAuthActive, envName, envToken,
|
|
15
|
+
serverVarEnv,
|
|
16
|
+
serverVariables
|
|
15
17
|
} from '@voxgig/sdkgen'
|
|
16
18
|
|
|
17
19
|
|
|
@@ -67,12 +69,28 @@ const TestDirect = cmp(function TestDirect(props: any) {
|
|
|
67
69
|
|
|
68
70
|
const authActive = isAuthActive(model)
|
|
69
71
|
const apikeyEnvEntry = authActive
|
|
70
|
-
? `\n ("${PROJECTNAME}_APIKEY", Value::str("
|
|
72
|
+
? `\n ("${PROJECTNAME}_APIKEY", Value::str("")),`
|
|
71
73
|
: ''
|
|
72
74
|
const apikeyLiveField = authActive
|
|
73
75
|
? `("apikey", getp(&env, "${PROJECTNAME}_APIKEY"))`
|
|
74
76
|
: ''
|
|
75
77
|
|
|
78
|
+
// A templated server URL (OpenAPI server variables) makes a LIVE client
|
|
79
|
+
// impossible to construct without values: makeOptions raises rather than
|
|
80
|
+
// request a URL with a literal `{account_id}` in it. So the live suite
|
|
81
|
+
// takes them from the environment the same way it takes the apikey.
|
|
82
|
+
//
|
|
83
|
+
// Emitted as a jo() tuple entry, so it carries its own leading comma only
|
|
84
|
+
// when an apikey entry precedes it - otherwise `jo(vec![, ...])`.
|
|
85
|
+
const svars = serverVariables(model)
|
|
86
|
+
const serverEnvEntry = svars
|
|
87
|
+
.map((v: any) => `\n ("${serverVarEnv(PROJECTNAME, v.name)}", Value::str(${JSON.stringify(v.dflt)})),`).join('')
|
|
88
|
+
const serverLiveEntry = 0 === svars.length ? '' :
|
|
89
|
+
`("server", jo(vec![${svars
|
|
90
|
+
.map((v: any) => `("${v.name}", getp(&env, "${serverVarEnv(PROJECTNAME, v.name)}"))`).join(', ')}]))`
|
|
91
|
+
const serverLiveField = '' === serverLiveEntry ? '' :
|
|
92
|
+
('' === apikeyLiveField ? serverLiveEntry : ', ' + serverLiveEntry)
|
|
93
|
+
|
|
76
94
|
const opnames = Object.keys(entity.op || {})
|
|
77
95
|
const hasLoad = opnames.includes('load')
|
|
78
96
|
const hasList = opnames.includes('list')
|
|
@@ -148,13 +166,22 @@ fn ${evar}_direct_setup(mockres: Value) -> ${entity.Name}DirectSetup {
|
|
|
148
166
|
|
|
149
167
|
let env = env_override(jo(vec![
|
|
150
168
|
("${entidEnvVar}", Value::empty_map()),
|
|
151
|
-
("${PROJECTNAME}_TEST_LIVE", Value::str("FALSE")),${apikeyEnvEntry}
|
|
169
|
+
("${PROJECTNAME}_TEST_LIVE", Value::str("FALSE")),${apikeyEnvEntry}${serverEnvEntry}
|
|
152
170
|
]));
|
|
153
171
|
|
|
154
172
|
let live = getp(&env, "${PROJECTNAME}_TEST_LIVE") == Value::str("TRUE");
|
|
155
173
|
|
|
156
174
|
if live {
|
|
157
|
-
|
|
175
|
+
// live_client_options() FIRST, so the generated entries below win:
|
|
176
|
+
// sdk-test-control.json's test.client.options adds to the live
|
|
177
|
+
// client, it does not redirect it.
|
|
178
|
+
let client = ${model.const.Name}SDK::new(to_map(&vs::merge(
|
|
179
|
+
&ja(vec![
|
|
180
|
+
live_client_options(),
|
|
181
|
+
jo(vec![${apikeyLiveField}${serverLiveField}]),
|
|
182
|
+
]),
|
|
183
|
+
None,
|
|
184
|
+
)));
|
|
158
185
|
let idmap = match to_map(&getp(&env, "${entidEnvVar}")) {
|
|
159
186
|
Value::Map(m) => Value::Map(m),
|
|
160
187
|
_ => Value::empty_map(),
|
|
@@ -17,7 +17,9 @@ import {
|
|
|
17
17
|
each,
|
|
18
18
|
buildIdNames,
|
|
19
19
|
getMatchEntries,
|
|
20
|
-
isAuthActive, envName, envToken
|
|
20
|
+
isAuthActive, envName, envToken,
|
|
21
|
+
serverVarEnv,
|
|
22
|
+
serverVariables
|
|
21
23
|
} from '@voxgig/sdkgen'
|
|
22
24
|
|
|
23
25
|
|
|
@@ -56,12 +58,28 @@ const TestEntity = cmp(function TestEntity(props: any) {
|
|
|
56
58
|
|
|
57
59
|
const authActive = isAuthActive(model)
|
|
58
60
|
const apikeyEnvEntry = authActive
|
|
59
|
-
? `\n ("${PROJUPPER}_APIKEY", Value::str("
|
|
61
|
+
? `\n ("${PROJUPPER}_APIKEY", Value::str("")),`
|
|
60
62
|
: ''
|
|
61
63
|
const apikeyLiveField = authActive
|
|
62
64
|
? `("apikey", getp(&env, "${PROJUPPER}_APIKEY"))`
|
|
63
65
|
: ''
|
|
64
66
|
|
|
67
|
+
// A templated server URL (OpenAPI server variables) makes a LIVE client
|
|
68
|
+
// impossible to construct without values: makeOptions raises rather than
|
|
69
|
+
// request a URL with a literal `{account_id}` in it. So the live suite
|
|
70
|
+
// takes them from the environment the same way it takes the apikey.
|
|
71
|
+
//
|
|
72
|
+
// Emitted as a jo() tuple entry, so it carries its own leading comma only
|
|
73
|
+
// when an apikey entry precedes it - otherwise `jo(vec![, ...])`.
|
|
74
|
+
const svars = serverVariables(model)
|
|
75
|
+
const serverEnvEntry = svars
|
|
76
|
+
.map((v: any) => `\n ("${serverVarEnv(PROJUPPER, v.name)}", Value::str(${JSON.stringify(v.dflt)})),`).join('')
|
|
77
|
+
const serverLiveEntry = 0 === svars.length ? '' :
|
|
78
|
+
`("server", jo(vec![${svars
|
|
79
|
+
.map((v: any) => `("${v.name}", getp(&env, "${serverVarEnv(PROJUPPER, v.name)}"))`).join(', ')}]))`
|
|
80
|
+
const serverLiveField = '' === serverLiveEntry ? '' :
|
|
81
|
+
('' === apikeyLiveField ? serverLiveEntry : ', ' + serverLiveEntry)
|
|
82
|
+
|
|
65
83
|
const idnames = buildIdNames(entity, basicflow)
|
|
66
84
|
const idnamesStr = idnames.map(n => `Value::str("${n}")`).join(', ')
|
|
67
85
|
|
|
@@ -268,7 +286,7 @@ ${allSteps.length > 0 ? ' let client = setup.client.clone();\n' : ''}`)
|
|
|
268
286
|
let env = env_override(jo(vec![
|
|
269
287
|
("${PROJUPPER}_TEST_${ENTUPPER}_ENTID", idmap.clone()),
|
|
270
288
|
("${PROJUPPER}_TEST_LIVE", Value::str("FALSE")),
|
|
271
|
-
("${PROJUPPER}_TEST_EXPLAIN", Value::str("FALSE")),${apikeyEnvEntry}
|
|
289
|
+
("${PROJUPPER}_TEST_EXPLAIN", Value::str("FALSE")),${apikeyEnvEntry}${serverEnvEntry}
|
|
272
290
|
]));
|
|
273
291
|
|
|
274
292
|
let idmap_resolved = match to_map(&getp(&env, "${PROJUPPER}_TEST_${ENTUPPER}_ENTID")) {
|
|
@@ -293,7 +311,22 @@ ${allSteps.length > 0 ? ' let client = setup.client.clone();\n' : ''}`)
|
|
|
293
311
|
|
|
294
312
|
let client = if live {
|
|
295
313
|
let merged = vs::merge(
|
|
296
|
-
|
|
314
|
+
// live_client_options() FIRST, so the generated entries below win:
|
|
315
|
+
// sdk-test-control.json's test.client.options adds to the live
|
|
316
|
+
// client, it does not redirect it.
|
|
317
|
+
&ja(vec![
|
|
318
|
+
live_client_options(),
|
|
319
|
+
jo(vec![${apikeyLiveField}${serverLiveField}]),
|
|
320
|
+
// A NON-NODE later entry REPLACES the accumulated map in
|
|
321
|
+
// vs::merge, and the normal call passes Value::Noval - so a
|
|
322
|
+
// a bare extra discarded live_client_options() and the
|
|
323
|
+
// apikey/server map above it, and the live client was
|
|
324
|
+
// constructed with nothing.
|
|
325
|
+
match extra {
|
|
326
|
+
Value::Map(m) => Value::Map(m),
|
|
327
|
+
_ => Value::empty_map(),
|
|
328
|
+
},
|
|
329
|
+
]),
|
|
297
330
|
None,
|
|
298
331
|
);
|
|
299
332
|
${model.const.Name}SDK::new(to_map(&merged))
|
|
@@ -3,7 +3,8 @@ import type {
|
|
|
3
3
|
ModelEntity
|
|
4
4
|
} from '@voxgig/apidef'
|
|
5
5
|
|
|
6
|
-
import { cmp, each, Folder, File, Content, entityCollection
|
|
6
|
+
import { cmp, each, Folder, File, Content, entityCollection,
|
|
7
|
+
TestControl } from '@voxgig/sdkgen'
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
import { TestEntity } from './TestEntity_rust'
|
|
@@ -20,6 +21,9 @@ const Test = cmp(function Test(props: any) {
|
|
|
20
21
|
|
|
21
22
|
Folder({ name: 'tests' }, () => {
|
|
22
23
|
|
|
24
|
+
// Write-once: a project's edited control file survives regeneration.
|
|
25
|
+
TestControl({ target, dir: 'tests' })
|
|
26
|
+
|
|
23
27
|
// exists test — the SDK constructs in test mode.
|
|
24
28
|
File({ name: 'exists_test.' + target.ext }, () => {
|
|
25
29
|
Content(`// Generated existence test: the SDK constructs in test mode.
|