@lukoweb/apitogo 0.2.47 → 0.2.49
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/cli/cli.js +421 -323
- package/docs/cli/shape.md +4 -3
- package/docs/cli/walkthrough.md +14 -25
- package/docs/configuration/make-config.mdx +3 -2
- package/package.json +1 -1
- package/src/config/v2/__fixtures__/scenario-a-docs-only.json +6 -6
- package/src/config/v2/__fixtures__/scenario-b-managed-origin.json +14 -14
- package/src/config/v2/__fixtures__/scenario-c-proxy-oidc.json +15 -15
- package/src/config/v2/__fixtures__/scenario-h-full-stack.json +28 -28
- package/src/config/v2/billing.ts +59 -0
- package/src/config/v2/docs-navigation.ts +72 -72
- package/src/config/v2/errors.ts +42 -42
- package/src/config/v2/gateway-reference.ts +220 -220
- package/src/config/v2/gateway-request.ts +123 -123
- package/src/config/v2/index.ts +7 -0
- package/src/config/v2/layout.ts +237 -237
- package/src/config/v2/load.ts +70 -70
- package/src/config/v2/parse.ts +214 -214
- package/src/config/v2/portal-profile.ts +88 -88
- package/src/config/v2/resolve.ts +6 -1
- package/src/config/v2/rules.ts +27 -15
- package/src/config/v2/services.ts +5 -20
|
@@ -1,123 +1,123 @@
|
|
|
1
|
-
import type { ResolvedGateway } from "./resolve.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The body of `PUT /gateway/config`: the wanted state of the gateway (Gateway design doc, 8.9 and 8.12).
|
|
5
|
-
* Only names of secrets are in it, never values.
|
|
6
|
-
*/
|
|
7
|
-
export type GatewayConfigBody = {
|
|
8
|
-
provider: string;
|
|
9
|
-
origin: {
|
|
10
|
-
mode: "managed" | "proxy";
|
|
11
|
-
url?: string;
|
|
12
|
-
healthPath?: string;
|
|
13
|
-
timeoutSeconds?: number;
|
|
14
|
-
auth?: { type: string; name?: string; value?: string };
|
|
15
|
-
};
|
|
16
|
-
auth?: {
|
|
17
|
-
mode: string;
|
|
18
|
-
metadataUrl?: string;
|
|
19
|
-
issuers?: string[];
|
|
20
|
-
audiences?: string[];
|
|
21
|
-
};
|
|
22
|
-
export?: { access?: string };
|
|
23
|
-
/** Sites of this project that call the gateway from a browser. The platform adds them to the allowed origins. */
|
|
24
|
-
corsDerived?: DerivedCorsSite[];
|
|
25
|
-
policies?: {
|
|
26
|
-
access?: {
|
|
27
|
-
jwt?: boolean;
|
|
28
|
-
apiKey?: boolean;
|
|
29
|
-
apiKeyHeader?: string;
|
|
30
|
-
bindKeyToToken?: boolean;
|
|
31
|
-
};
|
|
32
|
-
rateLimit?: { calls: number; periodSeconds: number; scope?: string };
|
|
33
|
-
quota?: { calls: number; periodDays: number; scope?: string };
|
|
34
|
-
cors?: string[];
|
|
35
|
-
operations?: { public?: string[]; deny?: string[] };
|
|
36
|
-
};
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export type DerivedCorsSite = { origin: string; source: "portal" | "docs" };
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* The browser origins of this project's own sites: the portal (from `services.json`) and an external docs site
|
|
43
|
-
* (from the config). Only the origin is kept, never a path. Sorted, so the same sites always give the same body.
|
|
44
|
-
*/
|
|
45
|
-
export function derivedCorsSites(sites: {
|
|
46
|
-
portalUrl?: string;
|
|
47
|
-
docsUrl?: string;
|
|
48
|
-
}): DerivedCorsSite[] {
|
|
49
|
-
const originOf = (url?: string) => {
|
|
50
|
-
if (!url) return undefined;
|
|
51
|
-
try {
|
|
52
|
-
const { protocol, origin } = new URL(url);
|
|
53
|
-
return protocol === "https:" ? origin : undefined;
|
|
54
|
-
} catch {
|
|
55
|
-
return undefined;
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
const found = [
|
|
59
|
-
{ origin: originOf(sites.docsUrl), source: "docs" as const },
|
|
60
|
-
{ origin: originOf(sites.portalUrl), source: "portal" as const },
|
|
61
|
-
].flatMap((site) => (site.origin ? [{ ...site, origin: site.origin }] : []));
|
|
62
|
-
const seen = new Set<string>();
|
|
63
|
-
return found
|
|
64
|
-
.filter((site) => !seen.has(site.origin) && seen.add(site.origin))
|
|
65
|
-
.sort((a, b) => (a.origin < b.origin ? -1 : a.origin > b.origin ? 1 : 0));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const defined = <T extends Record<string, unknown>>(value: T): T =>
|
|
69
|
-
Object.fromEntries(
|
|
70
|
-
Object.entries(value).filter(([, item]) => item !== undefined),
|
|
71
|
-
) as T;
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Turns the resolved `gateway` section into the request body. The spec files are not part of it: they are uploaded
|
|
75
|
-
* with `PUT /gateway/spec`. Fields the platform fills with defaults are left out when the config does not set them,
|
|
76
|
-
* so the same config always gives the same body (and the same hash).
|
|
77
|
-
*/
|
|
78
|
-
export function gatewayConfigBody(
|
|
79
|
-
gateway: ResolvedGateway,
|
|
80
|
-
derivedCors: DerivedCorsSite[] = [],
|
|
81
|
-
): GatewayConfigBody {
|
|
82
|
-
const { origin, auth, policies } = gateway;
|
|
83
|
-
const body: GatewayConfigBody = {
|
|
84
|
-
provider: gateway.provider,
|
|
85
|
-
origin: defined({
|
|
86
|
-
mode: origin.mode,
|
|
87
|
-
url: origin.url,
|
|
88
|
-
healthPath: origin.healthPath,
|
|
89
|
-
timeoutSeconds: origin.timeoutSeconds,
|
|
90
|
-
auth: origin.auth ? defined({ ...origin.auth }) : undefined,
|
|
91
|
-
}),
|
|
92
|
-
};
|
|
93
|
-
if (auth) body.auth = defined({ ...auth });
|
|
94
|
-
if (gateway.export) body.export = defined({ ...gateway.export });
|
|
95
|
-
if (derivedCors.length > 0) body.corsDerived = derivedCors;
|
|
96
|
-
if (Object.keys(policies).length > 0) {
|
|
97
|
-
body.policies = defined({
|
|
98
|
-
access: policies.access ? defined({ ...policies.access }) : undefined,
|
|
99
|
-
rateLimit: policies.rateLimit
|
|
100
|
-
? defined({ ...policies.rateLimit })
|
|
101
|
-
: undefined,
|
|
102
|
-
quota: policies.quota ? defined({ ...policies.quota }) : undefined,
|
|
103
|
-
cors: policies.cors,
|
|
104
|
-
operations: policies.operations
|
|
105
|
-
? defined({ ...policies.operations })
|
|
106
|
-
: undefined,
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
return body;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/** Stable JSON text of a value: object keys sorted, so equal content gives equal text. */
|
|
113
|
-
export function stableJson(value: unknown): string {
|
|
114
|
-
return JSON.stringify(value, (_key, item) =>
|
|
115
|
-
item && typeof item === "object" && !Array.isArray(item)
|
|
116
|
-
? Object.fromEntries(
|
|
117
|
-
Object.entries(item as Record<string, unknown>).sort(([a], [b]) =>
|
|
118
|
-
a < b ? -1 : a > b ? 1 : 0,
|
|
119
|
-
),
|
|
120
|
-
)
|
|
121
|
-
: item,
|
|
122
|
-
);
|
|
123
|
-
}
|
|
1
|
+
import type { ResolvedGateway } from "./resolve.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The body of `PUT /gateway/config`: the wanted state of the gateway (Gateway design doc, 8.9 and 8.12).
|
|
5
|
+
* Only names of secrets are in it, never values.
|
|
6
|
+
*/
|
|
7
|
+
export type GatewayConfigBody = {
|
|
8
|
+
provider: string;
|
|
9
|
+
origin: {
|
|
10
|
+
mode: "managed" | "proxy";
|
|
11
|
+
url?: string;
|
|
12
|
+
healthPath?: string;
|
|
13
|
+
timeoutSeconds?: number;
|
|
14
|
+
auth?: { type: string; name?: string; value?: string };
|
|
15
|
+
};
|
|
16
|
+
auth?: {
|
|
17
|
+
mode: string;
|
|
18
|
+
metadataUrl?: string;
|
|
19
|
+
issuers?: string[];
|
|
20
|
+
audiences?: string[];
|
|
21
|
+
};
|
|
22
|
+
export?: { access?: string };
|
|
23
|
+
/** Sites of this project that call the gateway from a browser. The platform adds them to the allowed origins. */
|
|
24
|
+
corsDerived?: DerivedCorsSite[];
|
|
25
|
+
policies?: {
|
|
26
|
+
access?: {
|
|
27
|
+
jwt?: boolean;
|
|
28
|
+
apiKey?: boolean;
|
|
29
|
+
apiKeyHeader?: string;
|
|
30
|
+
bindKeyToToken?: boolean;
|
|
31
|
+
};
|
|
32
|
+
rateLimit?: { calls: number; periodSeconds: number; scope?: string };
|
|
33
|
+
quota?: { calls: number; periodDays: number; scope?: string };
|
|
34
|
+
cors?: string[];
|
|
35
|
+
operations?: { public?: string[]; deny?: string[] };
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type DerivedCorsSite = { origin: string; source: "portal" | "docs" };
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The browser origins of this project's own sites: the portal (from `services.json`) and an external docs site
|
|
43
|
+
* (from the config). Only the origin is kept, never a path. Sorted, so the same sites always give the same body.
|
|
44
|
+
*/
|
|
45
|
+
export function derivedCorsSites(sites: {
|
|
46
|
+
portalUrl?: string;
|
|
47
|
+
docsUrl?: string;
|
|
48
|
+
}): DerivedCorsSite[] {
|
|
49
|
+
const originOf = (url?: string) => {
|
|
50
|
+
if (!url) return undefined;
|
|
51
|
+
try {
|
|
52
|
+
const { protocol, origin } = new URL(url);
|
|
53
|
+
return protocol === "https:" ? origin : undefined;
|
|
54
|
+
} catch {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const found = [
|
|
59
|
+
{ origin: originOf(sites.docsUrl), source: "docs" as const },
|
|
60
|
+
{ origin: originOf(sites.portalUrl), source: "portal" as const },
|
|
61
|
+
].flatMap((site) => (site.origin ? [{ ...site, origin: site.origin }] : []));
|
|
62
|
+
const seen = new Set<string>();
|
|
63
|
+
return found
|
|
64
|
+
.filter((site) => !seen.has(site.origin) && seen.add(site.origin))
|
|
65
|
+
.sort((a, b) => (a.origin < b.origin ? -1 : a.origin > b.origin ? 1 : 0));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const defined = <T extends Record<string, unknown>>(value: T): T =>
|
|
69
|
+
Object.fromEntries(
|
|
70
|
+
Object.entries(value).filter(([, item]) => item !== undefined),
|
|
71
|
+
) as T;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Turns the resolved `gateway` section into the request body. The spec files are not part of it: they are uploaded
|
|
75
|
+
* with `PUT /gateway/spec`. Fields the platform fills with defaults are left out when the config does not set them,
|
|
76
|
+
* so the same config always gives the same body (and the same hash).
|
|
77
|
+
*/
|
|
78
|
+
export function gatewayConfigBody(
|
|
79
|
+
gateway: ResolvedGateway,
|
|
80
|
+
derivedCors: DerivedCorsSite[] = [],
|
|
81
|
+
): GatewayConfigBody {
|
|
82
|
+
const { origin, auth, policies } = gateway;
|
|
83
|
+
const body: GatewayConfigBody = {
|
|
84
|
+
provider: gateway.provider,
|
|
85
|
+
origin: defined({
|
|
86
|
+
mode: origin.mode,
|
|
87
|
+
url: origin.url,
|
|
88
|
+
healthPath: origin.healthPath,
|
|
89
|
+
timeoutSeconds: origin.timeoutSeconds,
|
|
90
|
+
auth: origin.auth ? defined({ ...origin.auth }) : undefined,
|
|
91
|
+
}),
|
|
92
|
+
};
|
|
93
|
+
if (auth) body.auth = defined({ ...auth });
|
|
94
|
+
if (gateway.export) body.export = defined({ ...gateway.export });
|
|
95
|
+
if (derivedCors.length > 0) body.corsDerived = derivedCors;
|
|
96
|
+
if (Object.keys(policies).length > 0) {
|
|
97
|
+
body.policies = defined({
|
|
98
|
+
access: policies.access ? defined({ ...policies.access }) : undefined,
|
|
99
|
+
rateLimit: policies.rateLimit
|
|
100
|
+
? defined({ ...policies.rateLimit })
|
|
101
|
+
: undefined,
|
|
102
|
+
quota: policies.quota ? defined({ ...policies.quota }) : undefined,
|
|
103
|
+
cors: policies.cors,
|
|
104
|
+
operations: policies.operations
|
|
105
|
+
? defined({ ...policies.operations })
|
|
106
|
+
: undefined,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return body;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Stable JSON text of a value: object keys sorted, so equal content gives equal text. */
|
|
113
|
+
export function stableJson(value: unknown): string {
|
|
114
|
+
return JSON.stringify(value, (_key, item) =>
|
|
115
|
+
item && typeof item === "object" && !Array.isArray(item)
|
|
116
|
+
? Object.fromEntries(
|
|
117
|
+
Object.entries(item as Record<string, unknown>).sort(([a], [b]) =>
|
|
118
|
+
a < b ? -1 : a > b ? 1 : 0,
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
: item,
|
|
122
|
+
);
|
|
123
|
+
}
|
package/src/config/v2/index.ts
CHANGED