@pikku/openapi-parser 0.12.13 → 0.12.15
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/CHANGELOG.md +12 -0
- package/dist/auth-config.d.ts +1 -0
- package/dist/auth-config.js +7 -0
- package/dist/codegen.js +13 -4
- package/package.json +1 -1
- package/src/auth-config.ts +7 -0
- package/src/codegen.test.ts +23 -0
- package/src/codegen.ts +17 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @pikku/openapi-parser
|
|
2
2
|
|
|
3
|
+
## 0.12.15
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 854c342: Fix workspace addon integration: exclude nested pikku projects from inspection (prevents "More than one CoreUserSession/CoreConfig found" when a workspace addon is linked), widen the generated addon service `call()` data param to `unknown` so schema-less function inputs compile, and add `@pikku/inspector` + `@standard-schema/spec` to the generated addon devDependencies so its `.pikku` gen files typecheck.
|
|
8
|
+
|
|
9
|
+
## 0.12.14
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- a10e88d: auth-config: new `extraHeaders` field — static headers baked into every generated request (the delegated login call and all proxied service calls), for upstreams that route on a header such as multi-tenant APIs resolving the tenant from `Origin`.
|
|
14
|
+
|
|
3
15
|
## 0.12.13
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/auth-config.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ export declare const AuthConfigSchema: z.ZodObject<{
|
|
|
39
39
|
raw: "raw";
|
|
40
40
|
bearer: "bearer";
|
|
41
41
|
}>>;
|
|
42
|
+
extraHeaders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
42
43
|
delegated: z.ZodOptional<z.ZodObject<{
|
|
43
44
|
loginPath: z.ZodString;
|
|
44
45
|
loginMethod: z.ZodDefault<z.ZodString>;
|
package/dist/auth-config.js
CHANGED
|
@@ -38,6 +38,13 @@ export const AuthConfigSchema = z.object({
|
|
|
38
38
|
headerName: z.string().optional(),
|
|
39
39
|
/** 'raw' sends the bare token; 'bearer' prefixes it. Default: bearer for Authorization, raw for custom headers. */
|
|
40
40
|
headerFormat: z.enum(['raw', 'bearer']).optional(),
|
|
41
|
+
/**
|
|
42
|
+
* Static headers sent on EVERY request — the delegated login call and all
|
|
43
|
+
* proxied API calls. Needed for upstreams that route on a header, e.g. a
|
|
44
|
+
* multi-tenant API that resolves the tenant from an `Origin` header and
|
|
45
|
+
* rejects requests without it.
|
|
46
|
+
*/
|
|
47
|
+
extraHeaders: z.record(z.string(), z.string()).optional(),
|
|
41
48
|
/** Present when end-users sign in with their existing upstream credentials (delegated login). */
|
|
42
49
|
delegated: DelegatedLoginSchema.optional(),
|
|
43
50
|
});
|
package/dist/codegen.js
CHANGED
|
@@ -839,7 +839,12 @@ function generateUpstreamAuthFile(vars, authConfig) {
|
|
|
839
839
|
lines.push(` credentials: ${pascalName}UpstreamCredentials,`);
|
|
840
840
|
lines.push(' baseUrl: string');
|
|
841
841
|
lines.push(`): Promise<${pascalName}UpstreamIdentity | null> => {`);
|
|
842
|
-
lines.push(
|
|
842
|
+
lines.push(' const headers: Record<string, string> = {');
|
|
843
|
+
lines.push(" 'Content-Type': 'application/json',");
|
|
844
|
+
for (const [header, value] of Object.entries(authConfig.extraHeaders ?? {})) {
|
|
845
|
+
lines.push(` ${JSON.stringify(header)}: ${JSON.stringify(value)},`);
|
|
846
|
+
}
|
|
847
|
+
lines.push(' }');
|
|
843
848
|
lines.push(' const body: Record<string, string> = {}');
|
|
844
849
|
if (delegated.credentials.includes('email')) {
|
|
845
850
|
lines.push(' if (credentials.email) body.email = credentials.email');
|
|
@@ -1030,10 +1035,11 @@ function generateServiceFile(spec, opPairs, vars, flags) {
|
|
|
1030
1035
|
lines.push(' async call<T>(');
|
|
1031
1036
|
lines.push(" method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH',");
|
|
1032
1037
|
lines.push(' path: string,');
|
|
1033
|
-
lines.push(' data?:
|
|
1038
|
+
lines.push(' data?: unknown');
|
|
1034
1039
|
lines.push(' ): Promise<T> {');
|
|
1040
|
+
lines.push(' const input = data as Record<string, unknown> | undefined');
|
|
1035
1041
|
if (flags.camelCase) {
|
|
1036
|
-
lines.push(' const rawData =
|
|
1042
|
+
lines.push(' const rawData = input ? _toSnakeCase(input) : input');
|
|
1037
1043
|
}
|
|
1038
1044
|
lines.push(' const route = ROUTES[`${method} ${path}`]');
|
|
1039
1045
|
lines.push(' let endpoint = path');
|
|
@@ -1041,10 +1047,13 @@ function generateServiceFile(spec, opPairs, vars, flags) {
|
|
|
1041
1047
|
lines.push(' const query: Record<string, string> = {}');
|
|
1042
1048
|
lines.push(' const headers: Record<string, string> = {');
|
|
1043
1049
|
lines.push(" 'Content-Type': 'application/json',");
|
|
1050
|
+
for (const [header, value] of Object.entries(flags.authConfig?.extraHeaders ?? {})) {
|
|
1051
|
+
lines.push(` ${JSON.stringify(header)}: ${JSON.stringify(value)},`);
|
|
1052
|
+
}
|
|
1044
1053
|
lines.push(' }');
|
|
1045
1054
|
lines.push('');
|
|
1046
1055
|
// When camelCase flag is on, use rawData (snake_case converted) for route matching
|
|
1047
|
-
const dataVar = flags.camelCase ? 'rawData' : '
|
|
1056
|
+
const dataVar = flags.camelCase ? 'rawData' : 'input';
|
|
1048
1057
|
lines.push(` if (${dataVar} && route) {`);
|
|
1049
1058
|
lines.push(' // Interpolate path params');
|
|
1050
1059
|
lines.push(' for (const param of route.path) {');
|
package/package.json
CHANGED
package/src/auth-config.ts
CHANGED
|
@@ -41,6 +41,13 @@ export const AuthConfigSchema = z.object({
|
|
|
41
41
|
headerName: z.string().optional(),
|
|
42
42
|
/** 'raw' sends the bare token; 'bearer' prefixes it. Default: bearer for Authorization, raw for custom headers. */
|
|
43
43
|
headerFormat: z.enum(['raw', 'bearer']).optional(),
|
|
44
|
+
/**
|
|
45
|
+
* Static headers sent on EVERY request — the delegated login call and all
|
|
46
|
+
* proxied API calls. Needed for upstreams that route on a header, e.g. a
|
|
47
|
+
* multi-tenant API that resolves the tenant from an `Origin` header and
|
|
48
|
+
* rejects requests without it.
|
|
49
|
+
*/
|
|
50
|
+
extraHeaders: z.record(z.string(), z.string()).optional(),
|
|
44
51
|
/** Present when end-users sign in with their existing upstream credentials (delegated login). */
|
|
45
52
|
delegated: DelegatedLoginSchema.optional(),
|
|
46
53
|
})
|
package/src/codegen.test.ts
CHANGED
|
@@ -932,6 +932,29 @@ describe('auth config', () => {
|
|
|
932
932
|
)
|
|
933
933
|
})
|
|
934
934
|
|
|
935
|
+
test('extraHeaders are baked into the service and the upstream-auth login', () => {
|
|
936
|
+
const spec = makeSpec({ operations: [makeOp()] })
|
|
937
|
+
const files = generateAddonFromOpenAPI(spec, makeVars(), {
|
|
938
|
+
oauth: false,
|
|
939
|
+
secret: false,
|
|
940
|
+
credential: 'bearer',
|
|
941
|
+
authConfig: {
|
|
942
|
+
...delegatedConfig,
|
|
943
|
+
extraHeaders: { Origin: 'https://tenant.example.com' },
|
|
944
|
+
},
|
|
945
|
+
})
|
|
946
|
+
const service = files['src/test-api-api.service.ts']
|
|
947
|
+
assert.ok(
|
|
948
|
+
service.includes('"Origin": "https://tenant.example.com",'),
|
|
949
|
+
'service headers init should carry the static header'
|
|
950
|
+
)
|
|
951
|
+
const authFile = files['src/test-api-upstream-auth.ts']
|
|
952
|
+
assert.ok(
|
|
953
|
+
authFile.includes('"Origin": "https://tenant.example.com",'),
|
|
954
|
+
'login fetch should carry the static header'
|
|
955
|
+
)
|
|
956
|
+
})
|
|
957
|
+
|
|
935
958
|
test('no delegated config → no upstream-auth file', () => {
|
|
936
959
|
const spec = makeSpec({ operations: [makeOp()] })
|
|
937
960
|
const files = generateAddonFromOpenAPI(spec, makeVars(), {
|
package/src/codegen.ts
CHANGED
|
@@ -1132,9 +1132,12 @@ function generateUpstreamAuthFile(
|
|
|
1132
1132
|
lines.push(` credentials: ${pascalName}UpstreamCredentials,`)
|
|
1133
1133
|
lines.push(' baseUrl: string')
|
|
1134
1134
|
lines.push(`): Promise<${pascalName}UpstreamIdentity | null> => {`)
|
|
1135
|
-
lines.push(
|
|
1136
|
-
|
|
1137
|
-
)
|
|
1135
|
+
lines.push(' const headers: Record<string, string> = {')
|
|
1136
|
+
lines.push(" 'Content-Type': 'application/json',")
|
|
1137
|
+
for (const [header, value] of Object.entries(authConfig.extraHeaders ?? {})) {
|
|
1138
|
+
lines.push(` ${JSON.stringify(header)}: ${JSON.stringify(value)},`)
|
|
1139
|
+
}
|
|
1140
|
+
lines.push(' }')
|
|
1138
1141
|
lines.push(' const body: Record<string, string> = {}')
|
|
1139
1142
|
if (delegated.credentials.includes('email')) {
|
|
1140
1143
|
lines.push(' if (credentials.email) body.email = credentials.email')
|
|
@@ -1394,10 +1397,13 @@ function generateServiceFile(
|
|
|
1394
1397
|
lines.push(' async call<T>(')
|
|
1395
1398
|
lines.push(" method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH',")
|
|
1396
1399
|
lines.push(' path: string,')
|
|
1397
|
-
lines.push(' data?:
|
|
1400
|
+
lines.push(' data?: unknown')
|
|
1398
1401
|
lines.push(' ): Promise<T> {')
|
|
1402
|
+
lines.push(
|
|
1403
|
+
' const input = data as Record<string, unknown> | undefined'
|
|
1404
|
+
)
|
|
1399
1405
|
if (flags.camelCase) {
|
|
1400
|
-
lines.push(' const rawData =
|
|
1406
|
+
lines.push(' const rawData = input ? _toSnakeCase(input) : input')
|
|
1401
1407
|
}
|
|
1402
1408
|
lines.push(' const route = ROUTES[`${method} ${path}`]')
|
|
1403
1409
|
lines.push(' let endpoint = path')
|
|
@@ -1405,10 +1411,15 @@ function generateServiceFile(
|
|
|
1405
1411
|
lines.push(' const query: Record<string, string> = {}')
|
|
1406
1412
|
lines.push(' const headers: Record<string, string> = {')
|
|
1407
1413
|
lines.push(" 'Content-Type': 'application/json',")
|
|
1414
|
+
for (const [header, value] of Object.entries(
|
|
1415
|
+
flags.authConfig?.extraHeaders ?? {}
|
|
1416
|
+
)) {
|
|
1417
|
+
lines.push(` ${JSON.stringify(header)}: ${JSON.stringify(value)},`)
|
|
1418
|
+
}
|
|
1408
1419
|
lines.push(' }')
|
|
1409
1420
|
lines.push('')
|
|
1410
1421
|
// When camelCase flag is on, use rawData (snake_case converted) for route matching
|
|
1411
|
-
const dataVar = flags.camelCase ? 'rawData' : '
|
|
1422
|
+
const dataVar = flags.camelCase ? 'rawData' : 'input'
|
|
1412
1423
|
lines.push(` if (${dataVar} && route) {`)
|
|
1413
1424
|
lines.push(' // Interpolate path params')
|
|
1414
1425
|
lines.push(' for (const param of route.path) {')
|