@pikku/openapi-parser 0.12.20 → 0.12.22
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 +37 -0
- package/dist/auth-config.d.ts +3 -3
- package/dist/codegen.js +32 -21
- package/package.json +8 -6
- package/src/auth-config.ts +0 -94
- package/src/codegen.test.ts +0 -973
- package/src/codegen.ts +0 -1670
- package/src/index.ts +0 -24
- package/src/naming.test.ts +0 -260
- package/src/naming.ts +0 -369
- package/src/openapi-to-zod-schema.test.ts +0 -664
- package/src/openapi-to-zod-schema.ts +0 -583
- package/src/parse-openapi.test.ts +0 -316
- package/src/parse-openapi.ts +0 -467
- package/src/zod-codegen.ts +0 -9
- package/tsconfig.json +0 -12
- package/tsconfig.tsbuildinfo +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# @pikku/openapi-parser
|
|
2
2
|
|
|
3
|
+
## 0.12.22
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 17d8746: An addon generated with `pikku new addon --openapi` never built. Every function file declared its zod schemas next to an import of `#pikku/function`, and `pikku all` loads the file that declares a schema to convert it. At runtime `#pikku` resolves through the addon's `imports` into `dist/`, which the first build has not written yet, so every schema failed with `Could not convert Zod schema … Cannot find module …/dist/.pikku/function/index.js` and the build stopped there. Each operation's schemas now go in a sibling `<operation>.schemas.ts` that imports only zod, and the function file imports them from it.
|
|
8
|
+
|
|
9
|
+
The generated imports also named the wrong tree. An addon's generated code lives under `.pikku/addon/`, so `#pikku/function` and `#pikku/variables/…` pointed at leaves that do not exist, and `tsc` failed even once codegen had run. They are now `#pikku/addon/function` and `#pikku/addon/variables/…`, as the hand-written addon scaffold already had them.
|
|
10
|
+
|
|
11
|
+
`pikku validate` now reports an installed Pikku package that resolves a different copy of a type-identity package (`zod`, `kysely`, `@pikku/core`, `better-auth`) than the project as an error, `skewed-type-identity-…`, and the codegen preflight warns about it as `PKU719`. Under bun's isolated layout `@pikku/cli` can carry its own `zod` beside it in the store. Codegen then reads the app's schemas with a different zod than wrote them, and correct schemas fail to convert. The existing check only looked at dependencies linked from outside the project, so it never saw this case.
|
|
12
|
+
|
|
13
|
+
The `pikku-build` skill now recognises an OpenAPI spec or an n8n export handed over with the request, says so, converts it first, then carries on building the app. `pikku-addon` gains an OpenAPI reference and corrects its addon import paths and build steps. `pikku-n8n-import` joins the `core` install group.
|
|
14
|
+
|
|
15
|
+
## 0.12.21
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- 4c7a1b5: Run the monorepo's own scripts through bun instead of yarn. What moves is the
|
|
20
|
+
package manager each package's `prepublishOnly` and build scripts invoke, plus
|
|
21
|
+
the two manifest fixes bun needs to resolve the tree: `uWebSockets.js` is
|
|
22
|
+
declared with an explicit `github:` specifier, and `@pikku/uws-handler` marks
|
|
23
|
+
its `uWebSockets.js` peer optional so a bun install of a consumer that brings
|
|
24
|
+
its own uWS app does not try to fetch it from the registry.
|
|
25
|
+
|
|
26
|
+
Three published behaviours change, all of them cases where an isolated
|
|
27
|
+
`node_modules` or bun as the runtime had been papered over by yarn's hoisting:
|
|
28
|
+
|
|
29
|
+
- `@pikku/migrator-sql` turns foreign keys on when it opens a sqlite database
|
|
30
|
+
through bun. `node:sqlite` enforces them by default and `bun:sqlite` does not,
|
|
31
|
+
which silently turned every `ON DELETE CASCADE` into a no-op under
|
|
32
|
+
`bunx --bun pikku`.
|
|
33
|
+
- `@pikku/cli` resolves a deploy provider against the project being deployed
|
|
34
|
+
rather than against wherever the CLI itself is installed, which is what its
|
|
35
|
+
own "is not installed" error asks the user to arrange.
|
|
36
|
+
- `@pikku/cli` treats a specifier a runtime hands straight back — bun does this
|
|
37
|
+
for the modules it implements itself — as not resolved from the project, so
|
|
38
|
+
it falls back rather than loading the runtime's own copy.
|
|
39
|
+
|
|
3
40
|
## 0.12.20
|
|
4
41
|
|
|
5
42
|
### Patch Changes
|
package/dist/auth-config.d.ts
CHANGED
|
@@ -14,9 +14,9 @@ export declare const DelegatedLoginSchema: z.ZodObject<{
|
|
|
14
14
|
loginPath: z.ZodString;
|
|
15
15
|
loginMethod: z.ZodDefault<z.ZodString>;
|
|
16
16
|
credentials: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
17
|
+
apiKey: "apiKey";
|
|
17
18
|
email: "email";
|
|
18
19
|
password: "password";
|
|
19
|
-
apiKey: "apiKey";
|
|
20
20
|
}>>>;
|
|
21
21
|
apiKeyHeader: z.ZodDefault<z.ZodString>;
|
|
22
22
|
tokenPath: z.ZodString;
|
|
@@ -36,17 +36,17 @@ export declare const DelegatedLoginSchema: z.ZodObject<{
|
|
|
36
36
|
export declare const AuthConfigSchema: z.ZodObject<{
|
|
37
37
|
headerName: z.ZodOptional<z.ZodString>;
|
|
38
38
|
headerFormat: z.ZodOptional<z.ZodEnum<{
|
|
39
|
-
raw: "raw";
|
|
40
39
|
bearer: "bearer";
|
|
40
|
+
raw: "raw";
|
|
41
41
|
}>>;
|
|
42
42
|
extraHeaders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
43
43
|
delegated: z.ZodOptional<z.ZodObject<{
|
|
44
44
|
loginPath: z.ZodString;
|
|
45
45
|
loginMethod: z.ZodDefault<z.ZodString>;
|
|
46
46
|
credentials: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
47
|
+
apiKey: "apiKey";
|
|
47
48
|
email: "email";
|
|
48
49
|
password: "password";
|
|
49
|
-
apiKey: "apiKey";
|
|
50
50
|
}>>>;
|
|
51
51
|
apiKeyHeader: z.ZodDefault<z.ZodString>;
|
|
52
52
|
tokenPath: z.ZodString;
|
package/dist/codegen.js
CHANGED
|
@@ -281,8 +281,11 @@ export function generateAddonFromOpenAPI(spec, vars, flags) {
|
|
|
281
281
|
camelCase: flags.camelCase,
|
|
282
282
|
});
|
|
283
283
|
const inlineSchemas = singleSchemasPerOp.get(i) ?? new Set();
|
|
284
|
-
const
|
|
285
|
-
files[`src/functions/${named.functionName}.function.ts`] =
|
|
284
|
+
const { functionFile, schemasFile } = generateFunctionFile(named, parsed, vars, funcCtx, spec, flags, inlineSchemas, sharedWithDeps, schemaIdentityMap);
|
|
285
|
+
files[`src/functions/${named.functionName}.function.ts`] = functionFile;
|
|
286
|
+
if (schemasFile) {
|
|
287
|
+
files[`src/functions/${named.functionName}.schemas.ts`] = schemasFile;
|
|
288
|
+
}
|
|
286
289
|
functionExports.push(named.functionName);
|
|
287
290
|
}
|
|
288
291
|
// Delegated login: emit the upstream authenticate() implementation
|
|
@@ -516,14 +519,19 @@ function generateFunctionFile(named, parsed, vars, ctx, spec, flags, inlineSchem
|
|
|
516
519
|
}
|
|
517
520
|
// Determine error imports needed
|
|
518
521
|
const errorClasses = getErrorClassesForResponses(parsed.errorResponses);
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
lines.push("import { pikkuSessionlessFunc } from '#pikku/function'");
|
|
522
|
+
const schemaExports = [
|
|
523
|
+
...(hasInput && inputCode ? [inputName] : []),
|
|
524
|
+
...(parsed.responseSchema && outputCode ? [outputName] : []),
|
|
525
|
+
];
|
|
526
|
+
lines.push("import { pikkuSessionlessFunc } from '#pikku/addon/function'");
|
|
524
527
|
if (errorClasses.length > 0) {
|
|
525
528
|
lines.push(`import { ${errorClasses.join(', ')} } from '@pikku/core/errors'`);
|
|
526
529
|
}
|
|
530
|
+
if (schemaExports.length > 0) {
|
|
531
|
+
lines.push(`import { ${schemaExports.join(', ')} } from './${named.functionName}.schemas.js'`);
|
|
532
|
+
}
|
|
533
|
+
lines.push('');
|
|
534
|
+
const schemaLines = ["import { z } from 'zod'"];
|
|
527
535
|
// Import referenced component schemas from the types file.
|
|
528
536
|
// Only import schemas that are in the shared set (not inlined ones).
|
|
529
537
|
// In addition to the refs tracked through usedRefs, scan the generated Zod
|
|
@@ -543,10 +551,10 @@ function generateFunctionFile(named, parsed, vars, ctx, spec, flags, inlineSchem
|
|
|
543
551
|
.filter(Boolean)),
|
|
544
552
|
].sort();
|
|
545
553
|
if (schemaImports.length > 0) {
|
|
546
|
-
|
|
554
|
+
schemaLines.push(`import { ${schemaImports.join(', ')} } from '../${name}.types.js'`);
|
|
547
555
|
}
|
|
548
556
|
}
|
|
549
|
-
|
|
557
|
+
schemaLines.push('');
|
|
550
558
|
// Emit inline (single-use) schema definitions before input/output
|
|
551
559
|
if (inlineSchemas.size > 0) {
|
|
552
560
|
// Build a mini component schemas map for just the inline schemas
|
|
@@ -607,7 +615,7 @@ function generateFunctionFile(named, parsed, vars, ctx, spec, flags, inlineSchem
|
|
|
607
615
|
const emittedTypeNames = new Set();
|
|
608
616
|
// Forward declarations for cycles and forward references within inline schemas
|
|
609
617
|
if (forwardRefs.size > 0) {
|
|
610
|
-
|
|
618
|
+
schemaLines.push('// Forward declarations for circular references');
|
|
611
619
|
for (const sName of forwardRefs) {
|
|
612
620
|
if (!inlineSchemas.has(sName))
|
|
613
621
|
continue;
|
|
@@ -615,8 +623,8 @@ function generateFunctionFile(named, parsed, vars, ctx, spec, flags, inlineSchem
|
|
|
615
623
|
const typeN = sanitizeTypeName(sName);
|
|
616
624
|
emittedVarNames.add(varN);
|
|
617
625
|
emittedTypeNames.add(typeN);
|
|
618
|
-
|
|
619
|
-
|
|
626
|
+
schemaLines.push(`const ${varN}: z.ZodType<any> = z.lazy(() => _${varN})`);
|
|
627
|
+
schemaLines.push('');
|
|
620
628
|
}
|
|
621
629
|
}
|
|
622
630
|
for (const sName of inlineSorted) {
|
|
@@ -624,24 +632,24 @@ function generateFunctionFile(named, parsed, vars, ctx, spec, flags, inlineSchem
|
|
|
624
632
|
emittedVarNames.add(varN);
|
|
625
633
|
const zodCode = generatedCode.get(sName);
|
|
626
634
|
if (forwardRefs.has(sName)) {
|
|
627
|
-
|
|
635
|
+
schemaLines.push(`const _${varN} = ${zodCode}`);
|
|
628
636
|
}
|
|
629
637
|
else {
|
|
630
|
-
|
|
638
|
+
schemaLines.push(`const ${varN} = ${zodCode}`);
|
|
631
639
|
}
|
|
632
|
-
|
|
640
|
+
schemaLines.push('');
|
|
633
641
|
}
|
|
634
642
|
}
|
|
635
643
|
}
|
|
636
644
|
// Build Input schema (exported for pikku schema discovery)
|
|
637
645
|
if (hasInput && inputCode) {
|
|
638
|
-
|
|
639
|
-
|
|
646
|
+
schemaLines.push(`export const ${inputName} = ${inputCode}`);
|
|
647
|
+
schemaLines.push('');
|
|
640
648
|
}
|
|
641
649
|
// Build Output schema (exported for pikku schema discovery)
|
|
642
650
|
if (parsed.responseSchema && outputCode) {
|
|
643
|
-
|
|
644
|
-
|
|
651
|
+
schemaLines.push(`export const ${outputName} = ${outputCode}`);
|
|
652
|
+
schemaLines.push('');
|
|
645
653
|
}
|
|
646
654
|
const description = humanDescription(parsed);
|
|
647
655
|
const method = parsed.method.toUpperCase();
|
|
@@ -671,7 +679,10 @@ function generateFunctionFile(named, parsed, vars, ctx, spec, flags, inlineSchem
|
|
|
671
679
|
lines.push(funcConfig.join('\n'));
|
|
672
680
|
lines.push('})');
|
|
673
681
|
lines.push('');
|
|
674
|
-
return
|
|
682
|
+
return {
|
|
683
|
+
functionFile: lines.join('\n'),
|
|
684
|
+
schemasFile: schemaExports.length > 0 ? schemaLines.join('\n') : undefined,
|
|
685
|
+
};
|
|
675
686
|
}
|
|
676
687
|
function buildInputSchema(parsed, ctx) {
|
|
677
688
|
const props = [];
|
|
@@ -950,7 +961,7 @@ function generateServiceFile(spec, opPairs, vars, flags) {
|
|
|
950
961
|
if (allErrorClasses.size > 0) {
|
|
951
962
|
lines.push(`import { ${[...allErrorClasses].sort().join(', ')} } from '@pikku/core/errors'`);
|
|
952
963
|
}
|
|
953
|
-
lines.push(`import type { TypedVariablesService } from '#pikku/variables/pikku-variables.gen.js'`);
|
|
964
|
+
lines.push(`import type { TypedVariablesService } from '#pikku/addon/variables/pikku-variables.gen.js'`);
|
|
954
965
|
lines.push('');
|
|
955
966
|
// Generate route map from parsed operations
|
|
956
967
|
const routes = {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/openapi-parser",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.22",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/pikkujs/pikku.git",
|
|
7
|
+
"directory": "packages/openapi-parser"
|
|
8
|
+
},
|
|
4
9
|
"description": "OpenAPI spec parser and code generator for Pikku",
|
|
5
10
|
"author": "yasser.fadl@gmail.com",
|
|
6
11
|
"license": "MIT",
|
|
@@ -12,7 +17,7 @@
|
|
|
12
17
|
"ncu": "npx npm-check-updates",
|
|
13
18
|
"build": "tsc -b",
|
|
14
19
|
"test": "node --import tsx --test src/**/*.test.ts",
|
|
15
|
-
"prepublishOnly": "
|
|
20
|
+
"prepublishOnly": "bun run build"
|
|
16
21
|
},
|
|
17
22
|
"exports": {
|
|
18
23
|
".": "./dist/index.js"
|
|
@@ -20,8 +25,5 @@
|
|
|
20
25
|
"dependencies": {
|
|
21
26
|
"yaml": "^2.9.0",
|
|
22
27
|
"zod": "^4"
|
|
23
|
-
},
|
|
24
|
-
"devDependencies": {
|
|
25
|
-
"typescript": "^6.0.3"
|
|
26
28
|
}
|
|
27
|
-
}
|
|
29
|
+
}
|
package/src/auth-config.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Operator/agent-supplied auth configuration that overrides or augments an
|
|
3
|
-
* OpenAPI spec's securitySchemes. Needed for real-world enterprise specs that
|
|
4
|
-
* carry no machine-readable auth at all (auth described only in prose), or
|
|
5
|
-
* that use a custom auth header the spec doesn't declare.
|
|
6
|
-
*/
|
|
7
|
-
import { readFile } from 'fs/promises'
|
|
8
|
-
import { z } from 'zod'
|
|
9
|
-
|
|
10
|
-
export const DelegatedClaimsSchema = z.object({
|
|
11
|
-
/** Where identity claims live: the decoded login-JWT payload, or the login response body. */
|
|
12
|
-
source: z.enum(['jwt', 'response']),
|
|
13
|
-
/** Dot-path to the stable upstream user id (e.g. 'user._id'). */
|
|
14
|
-
externalId: z.string(),
|
|
15
|
-
email: z.string(),
|
|
16
|
-
/** One dot-path, or several joined with a space (e.g. first + last name). */
|
|
17
|
-
name: z.union([z.string(), z.array(z.string())]).optional(),
|
|
18
|
-
role: z.string().optional(),
|
|
19
|
-
tenantId: z.string().optional(),
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
export const DelegatedLoginSchema = z.object({
|
|
23
|
-
/** Spec-relative path of the login operation (e.g. '/users/login'). */
|
|
24
|
-
loginPath: z.string(),
|
|
25
|
-
loginMethod: z.string().default('post'),
|
|
26
|
-
/** Which credential fields the login accepts. email/password go in the JSON body; apiKey in a header. */
|
|
27
|
-
credentials: z
|
|
28
|
-
.array(z.enum(['email', 'password', 'apiKey']))
|
|
29
|
-
.default(['email', 'password']),
|
|
30
|
-
/** Header carrying the apiKey when 'apiKey' is a supported credential. */
|
|
31
|
-
apiKeyHeader: z.string().default('x-api-key'),
|
|
32
|
-
/** Dot-path to the token in the login response (e.g. 'token'). */
|
|
33
|
-
tokenPath: z.string(),
|
|
34
|
-
/** Dot-path to an epoch-seconds expiry in the response; defaults to the JWT `exp` claim when source is jwt. */
|
|
35
|
-
expiresAtPath: z.string().optional(),
|
|
36
|
-
claims: DelegatedClaimsSchema,
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
export const AuthConfigSchema = z.object({
|
|
40
|
-
/** Auth header the API expects on authenticated calls (e.g. 'authentication'). Overrides the spec. */
|
|
41
|
-
headerName: z.string().optional(),
|
|
42
|
-
/** 'raw' sends the bare token; 'bearer' prefixes it. Default: bearer for Authorization, raw for custom headers. */
|
|
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(),
|
|
51
|
-
/** Present when end-users sign in with their existing upstream credentials (delegated login). */
|
|
52
|
-
delegated: DelegatedLoginSchema.optional(),
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
export type DelegatedLoginConfig = z.infer<typeof DelegatedLoginSchema>
|
|
56
|
-
export type AuthConfig = z.infer<typeof AuthConfigSchema>
|
|
57
|
-
|
|
58
|
-
/** The effective value template for the auth header. */
|
|
59
|
-
export const authHeaderValue = (
|
|
60
|
-
config: AuthConfig,
|
|
61
|
-
tokenExpr: string
|
|
62
|
-
): { header: string; value: string } => {
|
|
63
|
-
const header = config.headerName ?? 'Authorization'
|
|
64
|
-
const format =
|
|
65
|
-
config.headerFormat ??
|
|
66
|
-
(config.headerName && config.headerName !== 'Authorization'
|
|
67
|
-
? 'raw'
|
|
68
|
-
: 'bearer')
|
|
69
|
-
return {
|
|
70
|
-
header,
|
|
71
|
-
value: format === 'bearer' ? `\`Bearer \${${tokenExpr}}\`` : tokenExpr,
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** Load and validate an auth-config JSON file; throws with the zod issue list on mismatch. */
|
|
76
|
-
export async function loadAuthConfig(filePath: string): Promise<AuthConfig> {
|
|
77
|
-
const raw = await readFile(filePath, 'utf-8')
|
|
78
|
-
let parsed: unknown
|
|
79
|
-
try {
|
|
80
|
-
parsed = JSON.parse(raw)
|
|
81
|
-
} catch (error) {
|
|
82
|
-
throw new Error(
|
|
83
|
-
`Auth config ${filePath} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
84
|
-
)
|
|
85
|
-
}
|
|
86
|
-
const result = AuthConfigSchema.safeParse(parsed)
|
|
87
|
-
if (!result.success) {
|
|
88
|
-
const issues = result.error.issues
|
|
89
|
-
.map((i) => ` ${i.path.join('.') || '(root)'}: ${i.message}`)
|
|
90
|
-
.join('\n')
|
|
91
|
-
throw new Error(`Auth config ${filePath} is invalid:\n${issues}`)
|
|
92
|
-
}
|
|
93
|
-
return result.data
|
|
94
|
-
}
|