@voxgig/sdkgen 4.6.0 → 4.8.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/helpers/opShape.js +8 -11
- package/dist/helpers/opShape.js.map +1 -1
- package/dist/helpers/pointPath.d.ts +10 -0
- package/dist/helpers/pointPath.js +54 -0
- package/dist/helpers/pointPath.js.map +1 -0
- package/dist/sdkgen.d.ts +6 -3
- package/dist/sdkgen.js +14 -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 +78 -1
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aon +58 -0
- package/package.json +2 -2
- package/project/.sdk/model/feature/secrets.aon +82 -0
- package/project/.sdk/src/cmp/c/Config_c.ts +7 -8
- package/project/.sdk/src/cmp/c/TestDirect_c.ts +4 -3
- package/project/.sdk/src/cmp/cpp/Main_cpp.ts +2 -1
- package/project/.sdk/src/cmp/cpp/TestDirect_cpp.ts +4 -3
- 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 +34 -9
- 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/Config_dart.ts +7 -8
- package/project/.sdk/src/cmp/dart/Main_dart.ts +2 -1
- package/project/.sdk/src/cmp/dart/TestDirect_dart.ts +28 -10
- 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/elixir/Config_elixir.ts +9 -8
- package/project/.sdk/src/cmp/go/Config_go.ts +1 -7
- package/project/.sdk/src/cmp/go/Main_go.ts +2 -1
- package/project/.sdk/src/cmp/go/TestDirect_go.ts +5 -4
- package/project/.sdk/src/cmp/go/Test_go.ts +5 -1
- package/project/.sdk/src/cmp/java/Config_java.ts +7 -7
- package/project/.sdk/src/cmp/java/Main_java.ts +2 -1
- package/project/.sdk/src/cmp/java/TestDirect_java.ts +5 -4
- package/project/.sdk/src/cmp/java/Test_java.ts +5 -1
- package/project/.sdk/src/cmp/js/Config_js.ts +7 -8
- package/project/.sdk/src/cmp/js/Main_js.ts +2 -1
- package/project/.sdk/src/cmp/js/TestDirect_js.ts +34 -8
- 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/Config_kotlin.ts +7 -7
- package/project/.sdk/src/cmp/kotlin/Main_kotlin.ts +2 -1
- package/project/.sdk/src/cmp/kotlin/TestDirect_kotlin.ts +5 -4
- package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +5 -1
- package/project/.sdk/src/cmp/lean/Config_lean.ts +9 -8
- package/project/.sdk/src/cmp/lua/Config_lua.ts +1 -7
- package/project/.sdk/src/cmp/lua/Main_lua.ts +2 -1
- package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +32 -7
- 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 +29 -7
- 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/Config_php.ts +1 -7
- package/project/.sdk/src/cmp/php/Main_php.ts +2 -1
- package/project/.sdk/src/cmp/php/TestDirect_php.ts +28 -8
- 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/Config_py.ts +1 -7
- package/project/.sdk/src/cmp/py/Main_py.ts +2 -1
- package/project/.sdk/src/cmp/py/TestDirect_py.ts +5 -4
- package/project/.sdk/src/cmp/py/Test_py.ts +5 -1
- package/project/.sdk/src/cmp/rb/Config_rb.ts +1 -7
- package/project/.sdk/src/cmp/rb/Main_rb.ts +2 -1
- package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +28 -8
- 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 +35 -7
- 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/Config_scala.ts +9 -7
- package/project/.sdk/src/cmp/scala/Main_scala.ts +2 -1
- package/project/.sdk/src/cmp/scala/TestDirect_scala.ts +4 -3
- package/project/.sdk/src/cmp/scala/Test_scala.ts +5 -1
- package/project/.sdk/src/cmp/seneca-provider/Extras_seneca-provider.ts +3 -1
- package/project/.sdk/src/cmp/seneca-provider/Main_seneca-provider.ts +9 -5
- package/project/.sdk/src/cmp/ts/Config_ts.ts +57 -16
- package/project/.sdk/src/cmp/ts/Main_ts.ts +10 -2
- package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +15 -7
- 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/helpers/opShape.ts +9 -14
- package/src/helpers/pointPath.ts +88 -0
- package/src/sdkgen.ts +18 -0
- package/src/utility.ts +92 -1
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Providers.ts +0 -1221
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/provider/Registry.ts)
|
|
2
|
+
// Source: https://github.com/voxgig/sekreto @ 65009cb5758850db767785ab666e71895f86086b
|
|
3
|
+
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
|
+
/* Copyright (c) 2025 Voxgig Ltd, MIT License */
|
|
5
|
+
|
|
6
|
+
// THE PROVIDER REGISTRY — the seam that replaced the `makeprovider`
|
|
7
|
+
// switch, and the reason an SDK no longer carries thirteen providers to
|
|
8
|
+
// use two.
|
|
9
|
+
//
|
|
10
|
+
// The switch named every kind in one function, so every kind was
|
|
11
|
+
// statically reachable from `Sekreto` and no bundler could drop one. Its
|
|
12
|
+
// replacement is a map that a provider ADDS ITSELF to. A kind that is
|
|
13
|
+
// never imported is never registered, is never reachable, and is not in
|
|
14
|
+
// the build — which is the whole point.
|
|
15
|
+
//
|
|
16
|
+
// The vocabulary is voxgig/plugin's (docs/design/plugin-providers.md): a
|
|
17
|
+
// provider kind is a plugin `Definition`, a configured provider is an
|
|
18
|
+
// instance addressed by name+tag. What sekreto does NOT route through
|
|
19
|
+
// plugin is the lookup itself: `Provider.lookup` is async, and plugin's
|
|
20
|
+
// dispatch is synchronous by design — twenty ports, many with no
|
|
21
|
+
// async/await. So plugin owns the catalog, the instances and their
|
|
22
|
+
// order; sekreto walks them in each language's own idiom.
|
|
23
|
+
//
|
|
24
|
+
// `define` is the definition's factory: spec in, Provider out. It is
|
|
25
|
+
// separate from the plugin `Definition.define` callback on purpose —
|
|
26
|
+
// this one runs at construction and returns a value, while plugin's runs
|
|
27
|
+
// at load and binds. They are bridged in Catalog.ts.
|
|
28
|
+
|
|
29
|
+
import { Provider, ProviderSpec, SekretoError } from './support'
|
|
30
|
+
|
|
31
|
+
export type ProviderDefinition = {
|
|
32
|
+
/** The `kind` a ProviderSpec names. */
|
|
33
|
+
name: string
|
|
34
|
+
/** What this provider needs of its runtime, so a host can refuse or
|
|
35
|
+
* report before a lookup fails: 'fs', 'fetch', 'crypto', 'process'. */
|
|
36
|
+
needs?: string[]
|
|
37
|
+
/** Build the provider from its declarative spec. */
|
|
38
|
+
define: (spec: ProviderSpec) => Provider
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const registry: { [kind: string]: ProviderDefinition } = {}
|
|
42
|
+
|
|
43
|
+
/** Register a provider kind. Called by each provider module at import,
|
|
44
|
+
* so importing the module IS installing it. Re-registering the same name
|
|
45
|
+
* replaces it, which is how a host substitutes an implementation. */
|
|
46
|
+
export function register(def: ProviderDefinition): void {
|
|
47
|
+
if (null == def || 'string' !== typeof def.name || '' === def.name) {
|
|
48
|
+
throw new SekretoError('sekreto: invalid provider definition')
|
|
49
|
+
}
|
|
50
|
+
registry[def.name] = def
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function registered(kind: string): ProviderDefinition | undefined {
|
|
54
|
+
return registry[kind]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function kinds(): string[] {
|
|
58
|
+
return Object.keys(registry).sort()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Build a provider from its spec.
|
|
62
|
+
*
|
|
63
|
+
* An UNREGISTERED kind is not the same failure as an unknown one, and the
|
|
64
|
+
* message says which: a kind sekreto has never heard of is a typo, while
|
|
65
|
+
* a known kind that was not imported is the leanness mechanism working as
|
|
66
|
+
* designed and telling you to import it. Collapsing the two was the first
|
|
67
|
+
* thing that made the split confusing to use. */
|
|
68
|
+
export function makeprovider(spec: ProviderSpec): Provider {
|
|
69
|
+
const kind = null == spec ? undefined : (spec as any).kind
|
|
70
|
+
const def = registry[kind as string]
|
|
71
|
+
|
|
72
|
+
if (null == def) {
|
|
73
|
+
throw new SekretoError(
|
|
74
|
+
'sekreto: unknown provider kind: ' + String(kind) +
|
|
75
|
+
' (registered: ' + (kinds().join(', ') || 'none') + ')' +
|
|
76
|
+
(KNOWN.indexOf(String(kind)) < 0 ? '' :
|
|
77
|
+
" - that kind exists but its module was not imported; import" +
|
|
78
|
+
" '@voxgig/sekreto/provider/" + String(kind) + "' to register it"))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return def.define(spec)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Every kind this library ships, registered or not. Used only to tell a
|
|
85
|
+
* typo from a module that was not imported. */
|
|
86
|
+
const KNOWN = [
|
|
87
|
+
'env', 'memory', 'dotenv', 'file', 'hashicorp', 'boru',
|
|
88
|
+
'awssecrets', 'awsparams', 'gcpsecrets', 'azuresecrets',
|
|
89
|
+
'onepassword', 'doppler', 'infisical',
|
|
90
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/provider/addr.ts)
|
|
2
|
+
// Source: https://github.com/voxgig/sekreto @ 65009cb5758850db767785ab666e71895f86086b
|
|
3
|
+
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
|
+
/* Copyright (c) 2025 Voxgig Ltd, MIT License */
|
|
5
|
+
|
|
6
|
+
import { SekretoError } from './support'
|
|
7
|
+
|
|
8
|
+
export function checkaddr(addr: string): void {
|
|
9
|
+
if (addr.startsWith('https://')) {
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!addr.startsWith('http://')) {
|
|
14
|
+
throw new SekretoError('sekreto: not an http(s) address: ' + addr)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Parsed, not split on ':'. Splitting the authority on the first colon
|
|
18
|
+
// yields '[' for a bracketed IPv6 address, so `http://[::1]:8200` could
|
|
19
|
+
// never match — which made the '::1' and '[::1]' entries below
|
|
20
|
+
// unreachable, and refused a legitimate local vault.
|
|
21
|
+
//
|
|
22
|
+
// The ALLOWLIST is unchanged. URL only fixes what the host IS: it strips
|
|
23
|
+
// userinfo (so `http://127.0.0.1@evil.com/` reads as evil.com, as
|
|
24
|
+
// before), lowercases, and normalises numeric forms — `0177.0.0.1` is
|
|
25
|
+
// 127.0.0.1 and is now recognised as the loopback it actually is.
|
|
26
|
+
// IPv4-mapped IPv6 (`[::ffff:127.0.0.1]`) is deliberately still refused:
|
|
27
|
+
// widening the set is a separate decision from parsing it correctly.
|
|
28
|
+
let host: string
|
|
29
|
+
try {
|
|
30
|
+
host = new URL(addr).hostname
|
|
31
|
+
} catch (err: any) {
|
|
32
|
+
throw new SekretoError('sekreto: not a valid http(s) address: ' + addr)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if ('localhost' === host || '127.0.0.1' === host || '::1' === host || '[::1]' === host) {
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
throw new SekretoError(
|
|
40
|
+
'sekreto: refusing to send a token in plaintext to ' + addr + ' (use https)',
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** How long any single vault round-trip may take before it is treated as
|
|
45
|
+
* unreachable. Ports carry the same bound. */
|
|
46
|
+
|
|
47
|
+
/** One JSON round-trip. Network failure is always an error - an
|
|
48
|
+
* unreachable store is a store that could not answer. */
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/provider/aws.ts)
|
|
2
|
+
// Source: https://github.com/voxgig/sekreto @ 65009cb5758850db767785ab666e71895f86086b
|
|
3
|
+
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
|
+
/* Copyright (c) 2025 Voxgig Ltd, MIT License */
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
ProviderSpec, Provider, SekretoError, awsparam, flatname, vaultref,
|
|
8
|
+
} from './support'
|
|
9
|
+
import { checkaddr } from './addr'
|
|
10
|
+
import { sigv4 } from '../Sigv4'
|
|
11
|
+
import { fetchjson } from './http'
|
|
12
|
+
|
|
13
|
+
function awsnow(): string {
|
|
14
|
+
return new Date()
|
|
15
|
+
.toISOString()
|
|
16
|
+
.replace(/[-:]/g, '')
|
|
17
|
+
.replace(/\.\d+Z$/, 'Z')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type Awsopts = {
|
|
21
|
+
region?: string
|
|
22
|
+
keyid?: string
|
|
23
|
+
secret?: string
|
|
24
|
+
session?: string
|
|
25
|
+
addr?: string
|
|
26
|
+
prefix?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Region and credentials, from config first and the standard AWS_*
|
|
30
|
+
* environment variables second - those are AWS's own convention, and a
|
|
31
|
+
* pod or CI job that has them set should just work. Missing either is
|
|
32
|
+
* an error: an AWS store with no credentials could not answer. */
|
|
33
|
+
function awsauth(opts: Awsopts): {
|
|
34
|
+
region: string
|
|
35
|
+
keyid: string
|
|
36
|
+
secret: string
|
|
37
|
+
session?: string
|
|
38
|
+
} {
|
|
39
|
+
const env = process.env
|
|
40
|
+
|
|
41
|
+
const region = opts.region || env.AWS_REGION || env.AWS_DEFAULT_REGION || ''
|
|
42
|
+
const keyid = opts.keyid || env.AWS_ACCESS_KEY_ID || ''
|
|
43
|
+
const secret = opts.secret || env.AWS_SECRET_ACCESS_KEY || ''
|
|
44
|
+
const session = opts.session || env.AWS_SESSION_TOKEN || undefined
|
|
45
|
+
|
|
46
|
+
if ('' === region) {
|
|
47
|
+
throw new SekretoError('sekreto: aws: no region (set region or AWS_REGION)')
|
|
48
|
+
}
|
|
49
|
+
if ('' === keyid || '' === secret) {
|
|
50
|
+
throw new SekretoError(
|
|
51
|
+
'sekreto: aws: no credentials (set keyid/secret or AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY)',
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { region, keyid, secret, session }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** One signed call to an AWS JSON-1.1 API. */
|
|
59
|
+
async function awscall(
|
|
60
|
+
opts: Awsopts,
|
|
61
|
+
service: string,
|
|
62
|
+
target: string,
|
|
63
|
+
payload: any,
|
|
64
|
+
): Promise<{ status: number; body: any }> {
|
|
65
|
+
const auth = awsauth(opts)
|
|
66
|
+
// The China partition lives under its own suffix; every other
|
|
67
|
+
// commercial region is plain amazonaws.com.
|
|
68
|
+
const suffix = auth.region.startsWith('cn-') ? '.amazonaws.com.cn' : '.amazonaws.com'
|
|
69
|
+
const addr = opts.addr || 'https://' + service + '.' + auth.region + suffix
|
|
70
|
+
checkaddr(addr)
|
|
71
|
+
|
|
72
|
+
const url = addr.replace(/\/$/, '') + '/'
|
|
73
|
+
const body = JSON.stringify(payload)
|
|
74
|
+
const headers: Record<string, string> = {
|
|
75
|
+
'content-type': 'application/x-amz-json-1.1',
|
|
76
|
+
'x-amz-target': target,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const signed = sigv4({
|
|
80
|
+
method: 'POST',
|
|
81
|
+
url,
|
|
82
|
+
headers,
|
|
83
|
+
body,
|
|
84
|
+
service,
|
|
85
|
+
region: auth.region,
|
|
86
|
+
keyid: auth.keyid,
|
|
87
|
+
secret: auth.secret,
|
|
88
|
+
session: auth.session,
|
|
89
|
+
datetime: awsnow(),
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
return fetchjson('POST', url, { ...headers, ...signed }, body)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Does this AWS error body name one of the not-found types? Those are
|
|
96
|
+
* a miss; every other failure is a store that could not answer. */
|
|
97
|
+
function awsmiss(body: any, types: string[]): boolean {
|
|
98
|
+
const errtype = body && 'string' === typeof body.__type ? body.__type : ''
|
|
99
|
+
return types.some((name) => errtype.includes(name))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** AWS Secrets Manager.
|
|
103
|
+
*
|
|
104
|
+
* `api.token` reads the secret named `api` (the vaultref path, so
|
|
105
|
+
* `db.pass.main` reads `db/pass`) and takes the `token` field of its
|
|
106
|
+
* JSON SecretString - the AWS idiom of one JSON map per secret. A
|
|
107
|
+
* SecretString that is not JSON is the value itself, under the
|
|
108
|
+
* conventional field `value`. Requests are SigV4-signed in-tree; see
|
|
109
|
+
* Sigv4.ts. */
|
|
110
|
+
export function awssecretsprovider(options?: Awsopts): Provider {
|
|
111
|
+
const opts = options || {}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
lookup: async (name: string) => {
|
|
115
|
+
const ref = vaultref(name)
|
|
116
|
+
|
|
117
|
+
const res = await awscall(opts, 'secretsmanager', 'secretsmanager.GetSecretValue', {
|
|
118
|
+
SecretId: ref.path,
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
if (400 === res.status && awsmiss(res.body, ['ResourceNotFoundException'])) {
|
|
122
|
+
return undefined
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (200 !== res.status) {
|
|
126
|
+
throw new SekretoError('sekreto: aws secretsmanager error: ' + res.status)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const text = res.body && res.body.SecretString
|
|
130
|
+
|
|
131
|
+
if ('string' !== typeof text) {
|
|
132
|
+
// A binary secret has no fields to address; only the conventional
|
|
133
|
+
// `value` field can mean "the bytes themselves".
|
|
134
|
+
const bin = res.body && res.body.SecretBinary
|
|
135
|
+
if ('string' === typeof bin && 'value' === ref.field) {
|
|
136
|
+
return Buffer.from(bin, 'base64').toString('utf8')
|
|
137
|
+
}
|
|
138
|
+
return undefined
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let parsed: any
|
|
142
|
+
try {
|
|
143
|
+
parsed = JSON.parse(text)
|
|
144
|
+
} catch (err: any) {
|
|
145
|
+
parsed = undefined
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (parsed && 'object' === typeof parsed && !Array.isArray(parsed)) {
|
|
149
|
+
const value = parsed[ref.field]
|
|
150
|
+
return undefined === value || null === value ? undefined : String(value)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// A plain-string secret is the whole value; it has no named fields.
|
|
154
|
+
return 'value' === ref.field ? text : undefined
|
|
155
|
+
},
|
|
156
|
+
// Config only, never the environment: describe() feeds the spec's
|
|
157
|
+
// sources group, which must answer the same everywhere.
|
|
158
|
+
describe: () => 'awssecrets:' + (opts.region || ''),
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** AWS SSM Parameter Store.
|
|
163
|
+
*
|
|
164
|
+
* `db.pass.main` reads the parameter `/db/pass/main` (under an optional
|
|
165
|
+
* prefix path), decrypted. Parameter Store carries flat strings, so
|
|
166
|
+
* there is no field indirection. */
|
|
167
|
+
export function awsparamsprovider(options?: Awsopts): Provider {
|
|
168
|
+
const opts = options || {}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
lookup: async (name: string) => {
|
|
172
|
+
const res = await awscall(opts, 'ssm', 'AmazonSSM.GetParameter', {
|
|
173
|
+
Name: awsparam(name, opts.prefix),
|
|
174
|
+
WithDecryption: true,
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
if (400 === res.status && awsmiss(res.body, ['ParameterNotFound'])) {
|
|
178
|
+
return undefined
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (200 !== res.status) {
|
|
182
|
+
throw new SekretoError('sekreto: aws ssm error: ' + res.status)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const value = res.body && res.body.Parameter && res.body.Parameter.Value
|
|
186
|
+
return undefined === value || null === value ? undefined : String(value)
|
|
187
|
+
},
|
|
188
|
+
describe: () => 'awsparams:' + (opts.region || '') + (opts.prefix || ''),
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** GCP Secret Manager.
|
|
193
|
+
*
|
|
194
|
+
* `api.token` reads secret `api_token` (dots flattened to `_`; Secret
|
|
195
|
+
* Manager ids have no hierarchy and reject dots), latest version. The
|
|
196
|
+
* token comes from config, then `GOOGLE_OAUTH_ACCESS_TOKEN`, then the
|
|
197
|
+
* GCE/GKE metadata server - so on Google's own platform no credential
|
|
198
|
+
* configuration is needed at all.
|
|
199
|
+
*
|
|
200
|
+
* The metadata call itself is plain http to a link-local host by
|
|
201
|
+
* platform design; no credential rides on it, so `checkaddr` guards the
|
|
202
|
+
* Secret Manager address instead. */
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
// Registering at import is what makes this module's presence the only
|
|
206
|
+
// thing that decides whether the kind exists in a build.
|
|
207
|
+
import { register } from './Registry'
|
|
208
|
+
|
|
209
|
+
register({
|
|
210
|
+
name: 'awssecrets',
|
|
211
|
+
needs: ['fetch', 'crypto'],
|
|
212
|
+
define: (spec: ProviderSpec) => awssecretsprovider(spec),
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
register({
|
|
216
|
+
name: 'awsparams',
|
|
217
|
+
needs: ['fetch', 'crypto'],
|
|
218
|
+
define: (spec: ProviderSpec) => awsparamsprovider(spec),
|
|
219
|
+
})
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/provider/azuresecrets.ts)
|
|
2
|
+
// Source: https://github.com/voxgig/sekreto @ 65009cb5758850db767785ab666e71895f86086b
|
|
3
|
+
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
|
+
/* Copyright (c) 2025 Voxgig Ltd, MIT License */
|
|
5
|
+
|
|
6
|
+
import { ProviderSpec, Provider, SekretoError, flatname } from './support'
|
|
7
|
+
import { checkaddr } from './addr'
|
|
8
|
+
import { fetchjson } from './http'
|
|
9
|
+
|
|
10
|
+
export function azuresecretsprovider(options?: {
|
|
11
|
+
vault?: string
|
|
12
|
+
token?: string
|
|
13
|
+
tenant?: string
|
|
14
|
+
clientid?: string
|
|
15
|
+
clientsecret?: string
|
|
16
|
+
loginaddr?: string
|
|
17
|
+
imdsaddr?: string
|
|
18
|
+
apiversion?: string
|
|
19
|
+
}): Provider {
|
|
20
|
+
const opts = options || {}
|
|
21
|
+
const resource = 'https://vault.azure.net'
|
|
22
|
+
|
|
23
|
+
// A configured token is kept forever; logged-in and IMDS tokens carry
|
|
24
|
+
// expires_in and are renewed shortly before they run out.
|
|
25
|
+
let livetoken: string | undefined
|
|
26
|
+
let renewat = Infinity
|
|
27
|
+
|
|
28
|
+
const expiry = (expires: any): number => {
|
|
29
|
+
const seconds = Number(expires)
|
|
30
|
+
return 0 < seconds ? Date.now() + Math.max(seconds - 60, 1) * 1000 : Infinity
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const login = async (): Promise<string> => {
|
|
34
|
+
if (opts.token) {
|
|
35
|
+
return opts.token
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (opts.tenant && opts.clientid && opts.clientsecret) {
|
|
39
|
+
const loginaddr = opts.loginaddr || 'https://login.microsoftonline.com'
|
|
40
|
+
checkaddr(loginaddr)
|
|
41
|
+
|
|
42
|
+
const url = loginaddr.replace(/\/$/, '') + '/' + opts.tenant + '/oauth2/v2.0/token'
|
|
43
|
+
const form =
|
|
44
|
+
'grant_type=client_credentials&client_id=' +
|
|
45
|
+
encodeURIComponent(opts.clientid) +
|
|
46
|
+
'&client_secret=' +
|
|
47
|
+
encodeURIComponent(opts.clientsecret) +
|
|
48
|
+
'&scope=' +
|
|
49
|
+
encodeURIComponent(resource + '/.default')
|
|
50
|
+
|
|
51
|
+
const res = await fetchjson(
|
|
52
|
+
'POST',
|
|
53
|
+
url,
|
|
54
|
+
{ 'content-type': 'application/x-www-form-urlencoded' },
|
|
55
|
+
form,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const got = res.body && res.body.access_token
|
|
59
|
+
if (200 !== res.status || !got) {
|
|
60
|
+
throw new SekretoError('sekreto: azure login failed: ' + res.status)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
renewat = expiry(res.body.expires_in)
|
|
64
|
+
return String(got)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const imds =
|
|
68
|
+
(opts.imdsaddr || 'http://169.254.169.254').replace(/\/$/, '') +
|
|
69
|
+
'/metadata/identity/oauth2/token?api-version=2018-02-01&resource=' +
|
|
70
|
+
encodeURIComponent(resource)
|
|
71
|
+
|
|
72
|
+
const res = await fetchjson('GET', imds, { Metadata: 'true' })
|
|
73
|
+
|
|
74
|
+
const got = res.body && res.body.access_token
|
|
75
|
+
if (200 !== res.status || !got) {
|
|
76
|
+
throw new SekretoError(
|
|
77
|
+
'sekreto: azure: no token, no client credentials, and IMDS did not answer',
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
renewat = expiry(res.body.expires_in)
|
|
82
|
+
return String(got)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
lookup: async (name: string) => {
|
|
87
|
+
const vault = opts.vault || ''
|
|
88
|
+
if ('' === vault) {
|
|
89
|
+
throw new SekretoError('sekreto: azure: no vault')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Only an explicit scheme is a URL; a vault NAMED httpvault must
|
|
93
|
+
// still become https://httpvault.vault.azure.net.
|
|
94
|
+
const vaulturl =
|
|
95
|
+
vault.startsWith('http://') || vault.startsWith('https://')
|
|
96
|
+
? vault
|
|
97
|
+
: 'https://' + vault + '.vault.azure.net'
|
|
98
|
+
checkaddr(vaulturl)
|
|
99
|
+
|
|
100
|
+
if (undefined === livetoken || Date.now() >= renewat) {
|
|
101
|
+
livetoken = await login()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const url =
|
|
105
|
+
vaulturl.replace(/\/$/, '') +
|
|
106
|
+
'/secrets/' +
|
|
107
|
+
flatname(name, '-') +
|
|
108
|
+
'?api-version=' +
|
|
109
|
+
(opts.apiversion || '7.4')
|
|
110
|
+
|
|
111
|
+
const res = await fetchjson('GET', url, { authorization: 'Bearer ' + livetoken })
|
|
112
|
+
|
|
113
|
+
if (404 === res.status) {
|
|
114
|
+
return undefined
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (200 !== res.status) {
|
|
118
|
+
throw new SekretoError('sekreto: azure error: ' + res.status + ': ' + url.split('?')[0])
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const value = res.body && res.body.value
|
|
122
|
+
return undefined === value || null === value ? undefined : String(value)
|
|
123
|
+
},
|
|
124
|
+
describe: () => 'azuresecrets:' + (opts.vault || ''),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** 1Password, through a Connect server.
|
|
129
|
+
*
|
|
130
|
+
* The item titled `api.token` (titles keep their dots), in the named
|
|
131
|
+
* vault. The value is the field with purpose PASSWORD, or the field
|
|
132
|
+
* labelled `value`. A vault that cannot be found is an error - config
|
|
133
|
+
* names it, so its absence is a broken store, not a missing secret. */
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
// Registering at import is what makes this module's presence the only
|
|
137
|
+
// thing that decides whether the kind exists in a build.
|
|
138
|
+
import { register } from './Registry'
|
|
139
|
+
|
|
140
|
+
register({
|
|
141
|
+
name: 'azuresecrets',
|
|
142
|
+
needs: ['fetch'],
|
|
143
|
+
define: (spec: ProviderSpec) => azuresecretsprovider(spec),
|
|
144
|
+
})
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/provider/boru.ts)
|
|
2
|
+
// Source: https://github.com/voxgig/sekreto @ 65009cb5758850db767785ab666e71895f86086b
|
|
3
|
+
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
|
+
/* Copyright (c) 2025 Voxgig Ltd, MIT License */
|
|
5
|
+
|
|
6
|
+
import { ProviderSpec, Provider, SekretoError, checkname, nodemod } from './support'
|
|
7
|
+
import { checkaddr } from './addr'
|
|
8
|
+
import { fetchjson } from './http'
|
|
9
|
+
|
|
10
|
+
export function boruprovider(options?: {
|
|
11
|
+
command?: string
|
|
12
|
+
namespace?: string
|
|
13
|
+
home?: string
|
|
14
|
+
addr?: string
|
|
15
|
+
token?: string
|
|
16
|
+
mount?: string
|
|
17
|
+
}): Provider {
|
|
18
|
+
const opts = options || {}
|
|
19
|
+
const command = opts.command || 'boru'
|
|
20
|
+
|
|
21
|
+
if (opts.addr) {
|
|
22
|
+
const addr = opts.addr.replace(/\/$/, '')
|
|
23
|
+
const mount = opts.mount || 'secret'
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
lookup: async (name: string) => {
|
|
27
|
+
checkname(name)
|
|
28
|
+
checkaddr(addr)
|
|
29
|
+
|
|
30
|
+
const alias = opts.namespace ? opts.namespace + '/' + name : name
|
|
31
|
+
const url = addr + '/v1/' + mount + '/data/' + alias
|
|
32
|
+
|
|
33
|
+
const res = await fetchjson('GET', url, { 'X-Vault-Token': opts.token || '' })
|
|
34
|
+
|
|
35
|
+
if (404 === res.status) {
|
|
36
|
+
return undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (200 !== res.status) {
|
|
40
|
+
throw new SekretoError('sekreto: boru serve error: ' + res.status + ': ' + url)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const data = res.body && res.body.data && res.body.data.data
|
|
44
|
+
const value = data ? data['value'] : undefined
|
|
45
|
+
return undefined === value || null === value ? undefined : String(value)
|
|
46
|
+
},
|
|
47
|
+
describe: () => 'boru:' + addr,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
lookup: (name: string) => {
|
|
53
|
+
checkname(name)
|
|
54
|
+
|
|
55
|
+
const alias = opts.namespace ? opts.namespace + ':' + name : name
|
|
56
|
+
const env = opts.home ? { ...process.env, BORU_HOME: opts.home } : process.env
|
|
57
|
+
|
|
58
|
+
const { spawnSync } = nodemod<typeof import('node:child_process')>('node:child_process')
|
|
59
|
+
const run = spawnSync(command, ['vault', 'get', '--reveal', alias], {
|
|
60
|
+
encoding: 'utf8',
|
|
61
|
+
env,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
if (run.error) {
|
|
65
|
+
throw new SekretoError('sekreto: cannot run ' + command + ': ' + run.error.message)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (0 === run.status) {
|
|
69
|
+
// boru prints the value and one newline, and nothing else.
|
|
70
|
+
return run.stdout.replace(/\n$/, '')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const why = (run.stderr || '').trim()
|
|
74
|
+
|
|
75
|
+
// "no alias named" is boru saying it does not hold this secret, which
|
|
76
|
+
// is a miss: the chain carries on to the next provider. A locked vault
|
|
77
|
+
// or a wrong passphrase is not a miss - treating it as one would fall
|
|
78
|
+
// through to a weaker store without saying so.
|
|
79
|
+
if (borumiss(why)) {
|
|
80
|
+
return undefined
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
throw new SekretoError('sekreto: boru vault error: ' + (why || 'exit ' + run.status))
|
|
84
|
+
},
|
|
85
|
+
describe: () => 'boru' + (opts.namespace ? ':' + opts.namespace : ''),
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Does this boru failure mean "no such secret" rather than "I could not
|
|
90
|
+
* answer"? Matched on boru's own wording for a missing alias. */
|
|
91
|
+
function borumiss(why: string): boolean {
|
|
92
|
+
return /no alias named/.test(why)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The `YYYYMMDDTHHMMSSZ` timestamp SigV4 wants, for now. */
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
// Registering at import is what makes this module's presence the only
|
|
99
|
+
// thing that decides whether the kind exists in a build.
|
|
100
|
+
import { register } from './Registry'
|
|
101
|
+
|
|
102
|
+
register({
|
|
103
|
+
name: 'boru',
|
|
104
|
+
needs: ['fetch'],
|
|
105
|
+
define: (spec: ProviderSpec) => boruprovider({
|
|
106
|
+
command: spec.command,
|
|
107
|
+
namespace: spec.namespace,
|
|
108
|
+
home: spec.home,
|
|
109
|
+
addr: spec.addr,
|
|
110
|
+
token: spec.token,
|
|
111
|
+
mount: spec.mount,
|
|
112
|
+
}),
|
|
113
|
+
})
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/provider/doppler.ts)
|
|
2
|
+
// Source: https://github.com/voxgig/sekreto @ 65009cb5758850db767785ab666e71895f86086b
|
|
3
|
+
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
|
+
/* Copyright (c) 2025 Voxgig Ltd, MIT License */
|
|
5
|
+
|
|
6
|
+
import { ProviderSpec, Provider, SekretoError, envkey } from './support'
|
|
7
|
+
import { checkaddr } from './addr'
|
|
8
|
+
import { fetchjson } from './http'
|
|
9
|
+
|
|
10
|
+
export function dopplerprovider(options?: {
|
|
11
|
+
token?: string
|
|
12
|
+
project?: string
|
|
13
|
+
config?: string
|
|
14
|
+
addr?: string
|
|
15
|
+
}): Provider {
|
|
16
|
+
const opts = options || {}
|
|
17
|
+
|
|
18
|
+
let values: Record<string, string> | undefined
|
|
19
|
+
|
|
20
|
+
const load = async (): Promise<Record<string, string>> => {
|
|
21
|
+
if (undefined !== values) {
|
|
22
|
+
return values
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const addr = (opts.addr || 'https://api.doppler.com').replace(/\/$/, '')
|
|
26
|
+
checkaddr(addr)
|
|
27
|
+
|
|
28
|
+
let url = addr + '/v3/configs/config/secrets/download?format=json'
|
|
29
|
+
if (opts.project) {
|
|
30
|
+
url += '&project=' + encodeURIComponent(opts.project)
|
|
31
|
+
}
|
|
32
|
+
if (opts.config) {
|
|
33
|
+
url += '&config=' + encodeURIComponent(opts.config)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const res = await fetchjson('GET', url, {
|
|
37
|
+
authorization: 'Bearer ' + (opts.token || ''),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
if (200 !== res.status || !res.body || 'object' !== typeof res.body) {
|
|
41
|
+
throw new SekretoError('sekreto: doppler error: ' + res.status)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
values = {}
|
|
45
|
+
for (const [key, value] of Object.entries(res.body)) {
|
|
46
|
+
if (null !== value && undefined !== value) {
|
|
47
|
+
values[key] = String(value)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return values
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
lookup: async (name: string) => (await load())[envkey(name)],
|
|
56
|
+
describe: () =>
|
|
57
|
+
'doppler' + (opts.project ? ':' + opts.project + '/' + (opts.config || '') : ''),
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Infisical.
|
|
62
|
+
*
|
|
63
|
+
* `api.token` reads the secret keyed `API_TOKEN` (Infisical's own
|
|
64
|
+
* convention is environment-style keys) at a secret path in one
|
|
65
|
+
* environment of a project. Auth is a token, or a universal-auth
|
|
66
|
+
* (machine identity) login with clientid/clientsecret. */
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
// Registering at import is what makes this module's presence the only
|
|
70
|
+
// thing that decides whether the kind exists in a build.
|
|
71
|
+
import { register } from './Registry'
|
|
72
|
+
|
|
73
|
+
register({
|
|
74
|
+
name: 'doppler',
|
|
75
|
+
needs: ['fetch'],
|
|
76
|
+
define: (spec: ProviderSpec) => dopplerprovider(spec),
|
|
77
|
+
})
|