@contractkit/plugin-typescript 0.21.0 → 0.23.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/.turbo/turbo-build$colon$ci.log +6 -6
- package/.turbo/turbo-test$colon$ci.log +13 -13
- package/CHANGELOG.md +41 -0
- package/README.md +1 -1
- package/coverage/clover.xml +627 -626
- package/coverage/coverage-final.json +2 -2
- package/coverage/index.html +9 -9
- package/coverage/src/codegen-contract.ts.html +1 -1
- package/coverage/src/codegen-operation.ts.html +11 -8
- package/coverage/src/codegen-plain-types.ts.html +1 -1
- package/coverage/src/codegen-sdk.ts.html +14 -8
- package/coverage/src/index.html +15 -15
- package/coverage/src/index.ts.html +1 -1
- package/coverage/src/path-utils.ts.html +1 -1
- package/coverage/src/ts-render.ts.html +1 -1
- package/coverage/tests/helpers.ts.html +1 -1
- package/coverage/tests/index.html +1 -1
- package/dist/codegen-operation.d.ts +1 -0
- package/dist/codegen-operation.d.ts.map +1 -1
- package/dist/codegen-sdk.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/codegen-operation.ts +3 -2
- package/src/codegen-sdk.ts +4 -2
- package/tests/codegen-operation.test.ts +18 -19
- package/tests/codegen-sdk.test.ts +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contractkit/plugin-typescript",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "ContractKit built-in plugin: TypeScript codegen (SDK clients, Koa routers, Zod schemas, plain types)",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Marooned Software",
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
".": "./dist/index.js"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@contractkit/core": "0.
|
|
29
|
+
"@contractkit/core": "0.17.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@repo/config-
|
|
33
|
-
"@repo/config-
|
|
32
|
+
"@repo/config-typescript": "0.1.0",
|
|
33
|
+
"@repo/config-eslint": "0.3.1"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"build": "tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly --declaration",
|
package/src/codegen-operation.ts
CHANGED
|
@@ -125,6 +125,7 @@ export interface OpCodegenOptions {
|
|
|
125
125
|
includeInternal?: boolean;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
/** Generate a Koa router module for every operation in `root`, including the imports, type aliases, and handler list. */
|
|
128
129
|
export function generateOp(root: OpRootNode, options: OpCodegenOptions = {}): string {
|
|
129
130
|
// Collect all referenced types across all routes
|
|
130
131
|
const types = collectTypes(root, options.modelsWithInput, options.modelsWithOutput);
|
|
@@ -240,8 +241,8 @@ function generateHandler(route: OpRouteNode, op: OpOperationNode, root: OpRootNo
|
|
|
240
241
|
// Middleware list
|
|
241
242
|
const middlewares: string[] = [];
|
|
242
243
|
if (effectiveSecurity !== SECURITY_NONE) {
|
|
243
|
-
const
|
|
244
|
-
middlewares.push(`requireSecurity(
|
|
244
|
+
const args = effectiveSecurity && effectiveSecurity.requireMfa !== undefined ? `{ requireMfa: ${effectiveSecurity.requireMfa} }` : '';
|
|
245
|
+
middlewares.push(`requireSecurity(${args})`);
|
|
245
246
|
}
|
|
246
247
|
if (hasBody) {
|
|
247
248
|
const parserTokens = Array.from(new Set(bodies.map(b => bodyParserToken(b.contentType))));
|
package/src/codegen-sdk.ts
CHANGED
|
@@ -138,6 +138,7 @@ export function generateSdk(root: OpRootNode, options: SdkCodegenOptions = {}):
|
|
|
138
138
|
lines.push(' public readonly status: number,');
|
|
139
139
|
lines.push(' public readonly statusText: string,');
|
|
140
140
|
lines.push(' public readonly body: unknown,');
|
|
141
|
+
lines.push(' public readonly headers: Headers,');
|
|
141
142
|
lines.push(' ) {');
|
|
142
143
|
lines.push(' super(`${status} ${statusText}`);');
|
|
143
144
|
lines.push(" this.name = 'SdkError';");
|
|
@@ -168,7 +169,7 @@ export function generateSdk(root: OpRootNode, options: SdkCodegenOptions = {}):
|
|
|
168
169
|
lines.push(' const text = await res.text();');
|
|
169
170
|
lines.push(' let body: unknown;');
|
|
170
171
|
lines.push(' try { body = JSON.parse(text); } catch { body = text; }');
|
|
171
|
-
lines.push(' throw new SdkError(res.status, res.statusText, body);');
|
|
172
|
+
lines.push(' throw new SdkError(res.status, res.statusText, body, res.headers);');
|
|
172
173
|
lines.push(' }');
|
|
173
174
|
lines.push(' return res;');
|
|
174
175
|
lines.push(' };');
|
|
@@ -885,6 +886,7 @@ export function generateSdkOptions(): string {
|
|
|
885
886
|
' public readonly status: number,',
|
|
886
887
|
' public readonly statusText: string,',
|
|
887
888
|
' public readonly body: unknown,',
|
|
889
|
+
' public readonly headers: Headers,',
|
|
888
890
|
' ) {',
|
|
889
891
|
' super(`${status} ${statusText}`);',
|
|
890
892
|
" this.name = 'SdkError';",
|
|
@@ -931,7 +933,7 @@ export function generateSdkOptions(): string {
|
|
|
931
933
|
' const text = await res.text();',
|
|
932
934
|
' let body: unknown;',
|
|
933
935
|
' try { body = JSON.parse(text); } catch { body = text; }',
|
|
934
|
-
' throw new SdkError(res.status, res.statusText, body);',
|
|
936
|
+
' throw new SdkError(res.status, res.statusText, body, res.headers);',
|
|
935
937
|
' }',
|
|
936
938
|
' return res;',
|
|
937
939
|
' };',
|
|
@@ -807,9 +807,9 @@ describe('generateOp — route modifiers JSDoc', () => {
|
|
|
807
807
|
expect(out).toContain('anonymous access, no security required');
|
|
808
808
|
});
|
|
809
809
|
|
|
810
|
-
it('emits no annotation for security with
|
|
810
|
+
it('emits no annotation for security with requireMfa', () => {
|
|
811
811
|
const op = opOperation('get', {
|
|
812
|
-
security: {
|
|
812
|
+
security: { requireMfa: true, loc: { file: 'test.op', line: 1 } },
|
|
813
813
|
});
|
|
814
814
|
const root = opRoot([opRoute('/users', [op])]);
|
|
815
815
|
const out = generateOp(root);
|
|
@@ -867,7 +867,7 @@ describe('generateOp — route modifiers JSDoc', () => {
|
|
|
867
867
|
|
|
868
868
|
it('does not import requireSignature when no signature is set', () => {
|
|
869
869
|
const op = opOperation('get', {
|
|
870
|
-
security: {
|
|
870
|
+
security: { requireMfa: true, loc: { file: 'test.op', line: 1 } },
|
|
871
871
|
});
|
|
872
872
|
const root = opRoot([opRoute('/users', [op])]);
|
|
873
873
|
const out = generateOp(root);
|
|
@@ -876,7 +876,7 @@ describe('generateOp — route modifiers JSDoc', () => {
|
|
|
876
876
|
});
|
|
877
877
|
});
|
|
878
878
|
|
|
879
|
-
// ─── Security (
|
|
879
|
+
// ─── Security (requireMfa) middleware ───────────────────────────
|
|
880
880
|
|
|
881
881
|
describe('security middleware', () => {
|
|
882
882
|
it('injects requireSecurity() with no args for unannotated routes', () => {
|
|
@@ -884,27 +884,26 @@ describe('generateOp — route modifiers JSDoc', () => {
|
|
|
884
884
|
const root = opRoot([opRoute('/users', [op])]);
|
|
885
885
|
const out = generateOp(root);
|
|
886
886
|
expect(out).toContain(`import { ServerKitRouter, bodyParserMiddleware, requireSecurity }`);
|
|
887
|
-
expect(out).toContain(`requireSecurity(
|
|
887
|
+
expect(out).toContain(`requireSecurity()`);
|
|
888
888
|
});
|
|
889
889
|
|
|
890
|
-
it('injects requireSecurity with
|
|
890
|
+
it('injects requireSecurity with requireMfa: true when set', () => {
|
|
891
891
|
const op = opOperation('get', {
|
|
892
|
-
security: {
|
|
892
|
+
security: { requireMfa: true, loc: { file: 'test.op', line: 1 } },
|
|
893
893
|
});
|
|
894
894
|
const root = opRoot([opRoute('/users', [op])]);
|
|
895
895
|
const out = generateOp(root);
|
|
896
|
-
expect(out).toContain(`requireSecurity({
|
|
896
|
+
expect(out).toContain(`requireSecurity({ requireMfa: true })`);
|
|
897
897
|
});
|
|
898
898
|
|
|
899
|
-
it('
|
|
899
|
+
it('injects requireSecurity with requireMfa: false when set', () => {
|
|
900
900
|
const op = opOperation('get', {
|
|
901
|
-
security: {
|
|
901
|
+
security: { requireMfa: false, loc: { file: 'test.op', line: 1 } },
|
|
902
902
|
});
|
|
903
|
-
const
|
|
904
|
-
const routeLine = generateOp(root)
|
|
903
|
+
const routeLine = generateOp(opRoot([opRoute('/users', [op])]))
|
|
905
904
|
.split('\n')
|
|
906
905
|
.find(l => l.includes('.get('));
|
|
907
|
-
expect(routeLine).toContain(`requireSecurity({
|
|
906
|
+
expect(routeLine).toContain(`requireSecurity({ requireMfa: false })`);
|
|
908
907
|
});
|
|
909
908
|
|
|
910
909
|
it('does not inject requireSecurity for public (security: none) routes', () => {
|
|
@@ -924,7 +923,7 @@ describe('generateOp — route modifiers JSDoc', () => {
|
|
|
924
923
|
|
|
925
924
|
it('places requireSecurity before bodyParserMiddleware in the route line', () => {
|
|
926
925
|
const op = opOperation('post', {
|
|
927
|
-
security: {
|
|
926
|
+
security: { requireMfa: true, loc: { file: 'test.op', line: 1 } },
|
|
928
927
|
request: opRequest('Payload'),
|
|
929
928
|
});
|
|
930
929
|
const root = opRoot([opRoute('/users', [op])]);
|
|
@@ -941,7 +940,7 @@ describe('generateOp — route modifiers JSDoc', () => {
|
|
|
941
940
|
it('places requireSecurity before requireSignature when both are set', () => {
|
|
942
941
|
const op = opOperation('post', {
|
|
943
942
|
signature: 'MY_KEY',
|
|
944
|
-
security: {
|
|
943
|
+
security: { requireMfa: true, loc: { file: 'test.op', line: 1 } },
|
|
945
944
|
request: opRequest('Payload'),
|
|
946
945
|
});
|
|
947
946
|
const root = opRoot([opRoute('/webhooks', [op])]);
|
|
@@ -958,7 +957,7 @@ describe('generateOp — route modifiers JSDoc', () => {
|
|
|
958
957
|
it('imports both requireSecurity and requireSignature when both are set', () => {
|
|
959
958
|
const op = opOperation('post', {
|
|
960
959
|
signature: 'MY_KEY',
|
|
961
|
-
security: {
|
|
960
|
+
security: { requireMfa: true, loc: { file: 'test.op', line: 1 } },
|
|
962
961
|
request: opRequest('Payload'),
|
|
963
962
|
});
|
|
964
963
|
const root = opRoot([opRoute('/webhooks', [op])]);
|
|
@@ -966,13 +965,13 @@ describe('generateOp — route modifiers JSDoc', () => {
|
|
|
966
965
|
expect(out).toContain(`import { ServerKitRouter, bodyParserMiddleware, requireSecurity, requireSignature }`);
|
|
967
966
|
});
|
|
968
967
|
|
|
969
|
-
it('works with route-level
|
|
968
|
+
it('works with route-level requireMfa security', () => {
|
|
970
969
|
const op = opOperation('get');
|
|
971
970
|
const route = opRoute('/users', [op]);
|
|
972
|
-
route.security = {
|
|
971
|
+
route.security = { requireMfa: true, loc: { file: 'test.op', line: 1 } };
|
|
973
972
|
const root = opRoot([route]);
|
|
974
973
|
const out = generateOp(root);
|
|
975
|
-
expect(out).toContain(`requireSecurity({
|
|
974
|
+
expect(out).toContain(`requireSecurity({ requireMfa: true })`);
|
|
976
975
|
});
|
|
977
976
|
});
|
|
978
977
|
});
|
|
@@ -648,6 +648,8 @@ describe('generateSdk', () => {
|
|
|
648
648
|
expect(out).toContain('headers?:');
|
|
649
649
|
expect(out).toContain('fetch?: SdkFetch');
|
|
650
650
|
expect(out).toContain('export class SdkError extends Error');
|
|
651
|
+
expect(out).toContain('public readonly headers: Headers');
|
|
652
|
+
expect(out).toContain('throw new SdkError(res.status, res.statusText, body, res.headers)');
|
|
651
653
|
});
|
|
652
654
|
});
|
|
653
655
|
|
|
@@ -756,7 +758,8 @@ describe('generateSdkOptions', () => {
|
|
|
756
758
|
expect(out).toContain('export class SdkError extends Error');
|
|
757
759
|
expect(out).toContain('public readonly status: number');
|
|
758
760
|
expect(out).toContain('public readonly body: unknown');
|
|
759
|
-
expect(out).toContain('
|
|
761
|
+
expect(out).toContain('public readonly headers: Headers');
|
|
762
|
+
expect(out).toContain('throw new SdkError(res.status, res.statusText, body, res.headers)');
|
|
760
763
|
expect(out).toContain('export type SdkFetch');
|
|
761
764
|
expect(out).toContain('export function createSdkFetch(options: SdkOptions): SdkFetch');
|
|
762
765
|
expect(out).toContain('requestIdFactory ?? (() => crypto.randomUUID())');
|