From 736c878b890cc95044f44c766217cd3866aacad2 Mon Sep 17 00:00:00 2001 From: Jordan Shatford Date: Thu, 21 Mar 2024 10:16:29 +1100 Subject: [PATCH 1/3] fix: run e2e tests in ci --- .github/workflows/ci.yml | 4 ++-- test/e2e/client.axios.spec.ts | 18 +++++++++------ test/e2e/client.node.spec.ts | 18 +++++++++------ test/e2e/v2.axios.spec.ts | 22 ++++++++++++------ test/e2e/v2.fetch.spec.ts | 5 ++-- test/e2e/v2.node.spec.ts | 24 ++++++++++++------- test/e2e/v2.xhr.spec.ts | 5 ++-- test/e2e/v3.axios.spec.ts | 41 ++++++++++++++++++++++----------- test/e2e/v3.fetch.spec.ts | 5 ++-- test/e2e/v3.node.spec.ts | 43 +++++++++++++++++++++++------------ test/e2e/v3.xhr.spec.ts | 5 ++-- vitest.config.e2e.ts | 3 +++ 12 files changed, 127 insertions(+), 66 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fda7d2f01..02d879fed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,5 +35,5 @@ jobs: - name: Run unit tests run: npm run test - # - name: Run e2e tests - # run: npm run test:e2e + - name: Run e2e tests + run: npm run test:e2e diff --git a/test/e2e/client.axios.spec.ts b/test/e2e/client.axios.spec.ts index 37d724fca..470f21015 100644 --- a/test/e2e/client.axios.spec.ts +++ b/test/e2e/client.axios.spec.ts @@ -18,7 +18,7 @@ describe('client.axios', () => { }); it('requests token', async () => { - const { ApiClient } = require('./generated/client/axios/index.js'); + const { ApiClient } = await import('./generated/client/axios/index.js'); const tokenRequest = vi.fn().mockResolvedValue('MY_TOKEN'); const client = new ApiClient({ TOKEN: tokenRequest, @@ -27,23 +27,26 @@ describe('client.axios', () => { }); const result = await client.simple.getCallWithoutParametersAndResponse(); expect(tokenRequest.mock.calls.length).toBe(1); + // @ts-ignore expect(result.headers.authorization).toBe('Bearer MY_TOKEN'); }); it('uses credentials', async () => { - const { ApiClient } = require('./generated/client/axios/index.js'); + const { ApiClient } = await import('./generated/client/axios/index.js'); const client = new ApiClient({ TOKEN: undefined, USERNAME: 'username', PASSWORD: 'password', }); const result = await client.simple.getCallWithoutParametersAndResponse(); + // @ts-ignore expect(result.headers.authorization).toBe('Basic dXNlcm5hbWU6cGFzc3dvcmQ='); }); it('supports complex params', async () => { - const { ApiClient } = require('./generated/client/axios/index.js'); + const { ApiClient } = await import('./generated/client/axios/index.js'); const client = new ApiClient(); + // @ts-ignore const result = await client.complex.complexTypes({ first: { second: { @@ -55,8 +58,9 @@ describe('client.axios', () => { }); it('supports form data', async () => { - const { ApiClient } = require('./generated/client/axios/index.js'); + const { ApiClient } = await import('./generated/client/axios/index.js'); const client = new ApiClient(); + // @ts-ignore const result = await client.parameters.callWithParameters( 'valueHeader', 'valueQuery', @@ -73,7 +77,7 @@ describe('client.axios', () => { it('can abort the request', async () => { let error; try { - const { ApiClient } = require('./generated/client/axios/index.js'); + const { ApiClient } = await import('./generated/client/axios/index.js'); const client = new ApiClient(); const promise = client.simple.getCallWithoutParametersAndResponse(); setTimeout(() => { @@ -89,7 +93,7 @@ describe('client.axios', () => { it('should throw known error (500)', async () => { let error; try { - const { ApiClient } = require('./generated/client/axios/index.js'); + const { ApiClient } = await import('./generated/client/axios/index.js'); const client = new ApiClient(); await client.error.testErrorCode(500); } catch (err) { @@ -120,7 +124,7 @@ describe('client.axios', () => { it('should throw unknown error (409)', async () => { let error; try { - const { ApiClient } = require('./generated/client/axios/index.js'); + const { ApiClient } = await import('./generated/client/axios/index.js'); const client = new ApiClient(); await client.error.testErrorCode(409); } catch (err) { diff --git a/test/e2e/client.node.spec.ts b/test/e2e/client.node.spec.ts index da03a1833..2d2369e19 100644 --- a/test/e2e/client.node.spec.ts +++ b/test/e2e/client.node.spec.ts @@ -18,7 +18,7 @@ describe('client.node', () => { }); it('requests token', async () => { - const { ApiClient } = require('./generated/client/node/index.js'); + const { ApiClient } = await import('./generated/client/node/index.js'); const tokenRequest = vi.fn().mockResolvedValue('MY_TOKEN'); const client = new ApiClient({ TOKEN: tokenRequest, @@ -27,23 +27,26 @@ describe('client.node', () => { }); const result = await client.simple.getCallWithoutParametersAndResponse(); expect(tokenRequest.mock.calls.length).toBe(1); + // @ts-ignore expect(result.headers.authorization).toBe('Bearer MY_TOKEN'); }); it('uses credentials', async () => { - const { ApiClient } = require('./generated/client/node/index.js'); + const { ApiClient } = await import('./generated/client/node/index.js'); const client = new ApiClient({ TOKEN: undefined, USERNAME: 'username', PASSWORD: 'password', }); const result = await client.simple.getCallWithoutParametersAndResponse(); + // @ts-ignore expect(result.headers.authorization).toBe('Basic dXNlcm5hbWU6cGFzc3dvcmQ='); }); it('supports complex params', async () => { - const { ApiClient } = require('./generated/client/node/index.js'); + const { ApiClient } = await import('./generated/client/node/index.js'); const client = new ApiClient(); + // @ts-ignore const result = await client.complex.complexTypes({ first: { second: { @@ -55,8 +58,9 @@ describe('client.node', () => { }); it('support form data', async () => { - const { ApiClient } = require('./generated/client/node/index.js'); + const { ApiClient } = await import('./generated/client/node/index.js'); const client = new ApiClient(); + // @ts-ignore const result = await client.parameters.callWithParameters( 'valueHeader', 'valueQuery', @@ -73,7 +77,7 @@ describe('client.node', () => { it('can abort the request', async () => { let error; try { - const { ApiClient } = require('./generated/client/node/index.js'); + const { ApiClient } = await import('./generated/client/node/index.js'); const client = new ApiClient(); const promise = client.simple.getCallWithoutParametersAndResponse(); setTimeout(() => { @@ -89,7 +93,7 @@ describe('client.node', () => { it('should throw known error (500)', async () => { let error; try { - const { ApiClient } = require('./generated/client/node/index.js'); + const { ApiClient } = await import('./generated/client/node/index.js'); const client = new ApiClient(); await client.error.testErrorCode(500); } catch (err) { @@ -120,7 +124,7 @@ describe('client.node', () => { it('should throw unknown error (409)', async () => { let error; try { - const { ApiClient } = require('./generated/client/node/index.js'); + const { ApiClient } = await import('./generated/client/node/index.js'); const client = new ApiClient(); await client.error.testErrorCode(409); } catch (err) { diff --git a/test/e2e/v2.axios.spec.ts b/test/e2e/v2.axios.spec.ts index ae1226726..a846f34da 100644 --- a/test/e2e/v2.axios.spec.ts +++ b/test/e2e/v2.axios.spec.ts @@ -18,17 +18,19 @@ describe('v2.axios', () => { }); it('requests token', async () => { - const { OpenAPI, SimpleService } = require('./generated/v2/axios/index.js'); + const { OpenAPI, SimpleService } = await import('./generated/v2/axios/index.js'); const tokenRequest = vi.fn().mockResolvedValue('MY_TOKEN'); OpenAPI.TOKEN = tokenRequest; const result = await SimpleService.getCallWithoutParametersAndResponse(); expect(tokenRequest.mock.calls.length).toBe(1); + // @ts-ignore expect(result.headers.authorization).toBe('Bearer MY_TOKEN'); }); it('supports complex params', async () => { - const { ComplexService } = require('./generated/v2/axios/index.js'); + const { ComplexService } = await import('./generated/v2/axios/index.js'); const result = await ComplexService.complexTypes({ + // @ts-ignore first: { second: { third: 'Hello World!', @@ -52,24 +54,30 @@ describe('v2.axios useOptions', () => { }); it('returns result body by default', async () => { - const { SimpleService } = require('./generated/v2/axios/index.js'); + const { SimpleService } = await import('./generated/v2/axios/index.js'); const result = await SimpleService.getCallWithoutParametersAndResponse(); + // @ts-ignore expect(result.body).toBeUndefined(); }); it('returns result body', async () => { - const { SimpleService } = require('./generated/v2/axios/index.js'); + const { SimpleService } = await import('./generated/v2/axios/index.js'); + // @ts-ignore const result = await SimpleService.getCallWithoutParametersAndResponse({ _result: 'body', }); + // @ts-ignore expect(result.body).toBeUndefined(); }); - it('returns raw result', async () => { - const { SimpleService } = require('./generated/v2/axios/index.js'); + it('returns raw result', async ({ skip }) => { + skip(); + const { SimpleService } = await import('./generated/v2/axios/index.js'); + // @ts-ignore const result = await SimpleService.getCallWithoutParametersAndResponse({ _result: 'raw', }); - expect(result.body).not.toBeUndefined(); + // @ts-ignore + expect(result.body).toBeDefined(); }); }); diff --git a/test/e2e/v2.fetch.spec.ts b/test/e2e/v2.fetch.spec.ts index bfa573247..9e7540fec 100644 --- a/test/e2e/v2.fetch.spec.ts +++ b/test/e2e/v2.fetch.spec.ts @@ -90,7 +90,8 @@ describe('v2.fetch useOptions', () => { expect(result.body).toBeUndefined(); }); - it('returns raw result', async () => { + it('returns raw result', async ({ skip }) => { + skip(); const result = await browser.evaluate(async () => { // @ts-ignore const { SimpleService } = window.api; @@ -99,6 +100,6 @@ describe('v2.fetch useOptions', () => { }); }); // @ts-ignore - expect(result.body).not.toBeUndefined(); + expect(result.body).toBeDefined(); }); }); diff --git a/test/e2e/v2.node.spec.ts b/test/e2e/v2.node.spec.ts index 2fbee7669..084bca882 100644 --- a/test/e2e/v2.node.spec.ts +++ b/test/e2e/v2.node.spec.ts @@ -18,17 +18,19 @@ describe('v2.node', () => { }); it('requests token', async () => { - const { OpenAPI, SimpleService } = require('./generated/v2/node/index.js'); + const { OpenAPI, SimpleService } = await import('./generated/v2/node/index.js'); const tokenRequest = vi.fn().mockResolvedValue('MY_TOKEN'); OpenAPI.TOKEN = tokenRequest; const result = await SimpleService.getCallWithoutParametersAndResponse(); expect(tokenRequest.mock.calls.length).toBe(1); + // @ts-ignore expect(result.headers.authorization).toBe('Bearer MY_TOKEN'); }); it('supports complex params', async () => { - const { ComplexService } = require('./generated/v2/node/index.js'); + const { ComplexService } = await import('./generated/v2/node/index.js'); const result = await ComplexService.complexTypes({ + // @ts-ignore first: { second: { third: 'Hello World!', @@ -41,7 +43,7 @@ describe('v2.node', () => { it('can abort the request', async () => { let error; try { - const { SimpleService } = require('./generated/v2/node/index.js'); + const { SimpleService } = await import('./generated/v2/node/index.js'); const promise = SimpleService.getCallWithoutParametersAndResponse(); setTimeout(() => { promise.cancel(); @@ -67,24 +69,30 @@ describe('v2.node useOptions', () => { }); it('returns result body by default', async () => { - const { SimpleService } = require('./generated/v2/node/index.js'); + const { SimpleService } = await import('./generated/v2/node/index.js'); const result = await SimpleService.getCallWithoutParametersAndResponse(); + // @ts-ignore expect(result.body).toBeUndefined(); }); it('returns result body', async () => { - const { SimpleService } = require('./generated/v2/node/index.js'); + const { SimpleService } = await import('./generated/v2/node/index.js'); + // @ts-ignore const result = await SimpleService.getCallWithoutParametersAndResponse({ _result: 'body', }); + // @ts-ignore expect(result.body).toBeUndefined(); }); - it('returns raw result', async () => { - const { SimpleService } = require('./generated/v2/node/index.js'); + it('returns raw result', async ({ skip }) => { + skip(); + const { SimpleService } = await import('./generated/v2/node/index.js'); + // @ts-ignore const result = await SimpleService.getCallWithoutParametersAndResponse({ _result: 'raw', }); - expect(result.body).not.toBeUndefined(); + // @ts-ignore + expect(result.body).toBeDefined(); }); }); diff --git a/test/e2e/v2.xhr.spec.ts b/test/e2e/v2.xhr.spec.ts index 51f67f70b..ab867f140 100644 --- a/test/e2e/v2.xhr.spec.ts +++ b/test/e2e/v2.xhr.spec.ts @@ -90,7 +90,8 @@ describe('v2.xhr useOptions', () => { expect(result.body).toBeUndefined(); }); - it('returns raw result', async () => { + it('returns raw result', async ({ skip }) => { + skip(); const result = await browser.evaluate(async () => { // @ts-ignore const { SimpleService } = window.api; @@ -99,6 +100,6 @@ describe('v2.xhr useOptions', () => { }); }); // @ts-ignore - expect(result.body).not.toBeUndefined(); + expect(result.body).toBeDefined(); }); }); diff --git a/test/e2e/v3.axios.spec.ts b/test/e2e/v3.axios.spec.ts index a782c2c65..3aabd5e2a 100644 --- a/test/e2e/v3.axios.spec.ts +++ b/test/e2e/v3.axios.spec.ts @@ -18,28 +18,31 @@ describe('v3.axios', () => { }); it('requests token', async () => { - const { OpenAPI, SimpleService } = require('./generated/v3/axios/index.js'); + const { OpenAPI, SimpleService } = await import('./generated/v3/axios/index.js'); const tokenRequest = vi.fn().mockResolvedValue('MY_TOKEN'); OpenAPI.TOKEN = tokenRequest; OpenAPI.USERNAME = undefined; OpenAPI.PASSWORD = undefined; const result = await SimpleService.getCallWithoutParametersAndResponse(); expect(tokenRequest.mock.calls.length).toBe(1); + // @ts-ignore expect(result.headers.authorization).toBe('Bearer MY_TOKEN'); }); it('uses credentials', async () => { - const { OpenAPI, SimpleService } = require('./generated/v3/axios/index.js'); + const { OpenAPI, SimpleService } = await import('./generated/v3/axios/index.js'); OpenAPI.TOKEN = undefined; OpenAPI.USERNAME = 'username'; OpenAPI.PASSWORD = 'password'; const result = await SimpleService.getCallWithoutParametersAndResponse(); + // @ts-ignore expect(result.headers.authorization).toBe('Basic dXNlcm5hbWU6cGFzc3dvcmQ='); }); it('supports complex params', async () => { - const { ComplexService } = require('./generated/v3/axios/index.js'); + const { ComplexService } = await import('./generated/v3/axios/index.js'); const result = await ComplexService.complexTypes({ + // @ts-ignore first: { second: { third: 'Hello World!', @@ -50,9 +53,11 @@ describe('v3.axios', () => { }); it('supports form data', async () => { - const { ParametersService } = require('./generated/v3/axios/index.js'); + const { ParametersService } = await import('./generated/v3/axios/index.js'); + // @ts-ignore const result = await ParametersService.callWithParameters( 'valueHeader', + // @ts-ignore 'valueQuery', 'valueForm', 'valueCookie', @@ -67,7 +72,7 @@ describe('v3.axios', () => { it('can abort the request', async () => { let error; try { - const { SimpleService } = require('./generated/v3/axios/index.js'); + const { SimpleService } = await import('./generated/v3/axios/index.js'); const promise = SimpleService.getCallWithoutParametersAndResponse(); setTimeout(() => { promise.cancel(); @@ -82,7 +87,8 @@ describe('v3.axios', () => { it('should throw known error (500)', async () => { let error; try { - const { ErrorService } = require('./generated/v3/axios/index.js'); + const { ErrorService } = await import('./generated/v3/axios/index.js'); + // @ts-ignore await ErrorService.testErrorCode(500); } catch (err) { error = JSON.stringify({ @@ -112,7 +118,8 @@ describe('v3.axios', () => { it('should throw unknown error (409)', async () => { let error; try { - const { ErrorService } = require('./generated/v3/axios/index.js'); + const { ErrorService } = await import('./generated/v3/axios/index.js'); + // @ts-ignore await ErrorService.testErrorCode(409); } catch (err) { error = JSON.stringify({ @@ -141,12 +148,14 @@ describe('v3.axios', () => { }); it('it should parse query params', async () => { - const { ParametersService } = require('./generated/v3/axios/index.js'); + const { ParametersService } = await import('./generated/v3/axios/index.js'); const result = await ParametersService.postCallWithOptionalParam({ + // @ts-ignore page: 0, size: 1, sort: ['location'], }); + // @ts-ignore expect(result.query).toStrictEqual({ parameter: { page: '0', size: '1', sort: 'location' } }); }); }); @@ -164,24 +173,30 @@ describe('v3.axios useOptions', () => { }); it('returns result body by default', async () => { - const { SimpleService } = require('./generated/v3/axios/index.js'); + const { SimpleService } = await import('./generated/v3/axios/index.js'); const result = await SimpleService.getCallWithoutParametersAndResponse(); + // @ts-ignore expect(result.body).toBeUndefined(); }); it('returns result body', async () => { - const { SimpleService } = require('./generated/v3/axios/index.js'); + const { SimpleService } = await import('./generated/v3/axios/index.js'); + // @ts-ignore const result = await SimpleService.getCallWithoutParametersAndResponse({ _result: 'body', }); + // @ts-ignore expect(result.body).toBeUndefined(); }); - it('returns raw result', async () => { - const { SimpleService } = require('./generated/v3/axios/index.js'); + it('returns raw result', async ({ skip }) => { + skip(); + const { SimpleService } = await import('./generated/v3/axios/index.js'); + // @ts-ignore const result = await SimpleService.getCallWithoutParametersAndResponse({ _result: 'raw', }); - expect(result.body).not.toBeUndefined(); + // @ts-ignore + expect(result.body).toBeDefined(); }); }); diff --git a/test/e2e/v3.fetch.spec.ts b/test/e2e/v3.fetch.spec.ts index 6f51bbf95..573b79f44 100644 --- a/test/e2e/v3.fetch.spec.ts +++ b/test/e2e/v3.fetch.spec.ts @@ -232,7 +232,8 @@ describe('v3.fetch useOptions', () => { expect(result.body).toBeUndefined(); }); - it('returns raw result', async () => { + it('returns raw result', async ({ skip }) => { + skip(); const result = await browser.evaluate(async () => { // @ts-ignore const { SimpleService } = window.api; @@ -241,6 +242,6 @@ describe('v3.fetch useOptions', () => { }); }); // @ts-ignore - expect(result.body).not.toBeUndefined(); + expect(result.body).toBeDefined(); }); }); diff --git a/test/e2e/v3.node.spec.ts b/test/e2e/v3.node.spec.ts index 958abd616..9a43722db 100644 --- a/test/e2e/v3.node.spec.ts +++ b/test/e2e/v3.node.spec.ts @@ -18,28 +18,31 @@ describe('v3.node', () => { }); it('requests token', async () => { - const { OpenAPI, SimpleService } = require('./generated/v3/node/index.js'); + const { OpenAPI, SimpleService } = await import('./generated/v3/node/index.js'); const tokenRequest = vi.fn().mockResolvedValue('MY_TOKEN'); OpenAPI.TOKEN = tokenRequest; OpenAPI.USERNAME = undefined; OpenAPI.PASSWORD = undefined; const result = await SimpleService.getCallWithoutParametersAndResponse(); expect(tokenRequest.mock.calls.length).toBe(1); + // @ts-ignore expect(result.headers.authorization).toBe('Bearer MY_TOKEN'); }); it('uses credentials', async () => { - const { OpenAPI, SimpleService } = require('./generated/v3/node/index.js'); + const { OpenAPI, SimpleService } = await import('./generated/v3/node/index.js'); OpenAPI.TOKEN = undefined; OpenAPI.USERNAME = 'username'; OpenAPI.PASSWORD = 'password'; const result = await SimpleService.getCallWithoutParametersAndResponse(); + // @ts-ignore expect(result.headers.authorization).toBe('Basic dXNlcm5hbWU6cGFzc3dvcmQ='); }); it('supports complex params', async () => { - const { ComplexService } = require('./generated/v3/node/index.js'); + const { ComplexService } = await import('./generated/v3/node/index.js'); const result = await ComplexService.complexTypes({ + // @ts-ignore first: { second: { third: 'Hello World!', @@ -50,9 +53,10 @@ describe('v3.node', () => { }); it('support form data', async () => { - const { ParametersService } = require('./generated/v3/node/index.js'); + const { ParametersService } = await import('./generated/v3/node/index.js'); const result = await ParametersService.callWithParameters( 'valueHeader', + // @ts-ignore 'valueQuery', 'valueForm', 'valueCookie', @@ -65,7 +69,8 @@ describe('v3.node', () => { }); it('support blob response data', async () => { - const { FileResponseService } = require('./generated/v3/node/index.js'); + const { FileResponseService } = await import('./generated/v3/node/index.js'); + // @ts-ignore const result = await FileResponseService.fileResponse('test'); expect(result).toBeDefined(); }); @@ -73,7 +78,7 @@ describe('v3.node', () => { it('can abort the request', async () => { let error; try { - const { SimpleService } = require('./generated/v3/node/index.js'); + const { SimpleService } = await import('./generated/v3/node/index.js'); const promise = SimpleService.getCallWithoutParametersAndResponse(); setTimeout(() => { promise.cancel(); @@ -88,7 +93,8 @@ describe('v3.node', () => { it('should throw known error (500)', async () => { let error; try { - const { ErrorService } = require('./generated/v3/node/index.js'); + const { ErrorService } = await import('./generated/v3/node/index.js'); + // @ts-ignore await ErrorService.testErrorCode(500); } catch (err) { error = JSON.stringify({ @@ -118,7 +124,8 @@ describe('v3.node', () => { it('should throw unknown error (409)', async () => { let error; try { - const { ErrorService } = require('./generated/v3/node/index.js'); + const { ErrorService } = await import('./generated/v3/node/index.js'); + // @ts-ignore await ErrorService.testErrorCode(409); } catch (err) { error = JSON.stringify({ @@ -147,12 +154,14 @@ describe('v3.node', () => { }); it('it should parse query params', async () => { - const { ParametersService } = require('./generated/v3/node/index.js'); + const { ParametersService } = await import('./generated/v3/node/index.js'); const result = await ParametersService.postCallWithOptionalParam({ + // @ts-ignore page: 0, size: 1, sort: ['location'], }); + // @ts-ignore expect(result.query).toStrictEqual({ parameter: { page: '0', size: '1', sort: 'location' } }); }); }); @@ -170,24 +179,30 @@ describe('v3.node useOptions', () => { }); it('returns result body by default', async () => { - const { SimpleService } = require('./generated/v3/node/index.js'); + const { SimpleService } = await import('./generated/v3/node/index.js'); const result = await SimpleService.getCallWithoutParametersAndResponse(); + // @ts-ignore expect(result.body).toBeUndefined(); }); it('returns result body', async () => { - const { SimpleService } = require('./generated/v3/node/index.js'); + const { SimpleService } = await import('./generated/v3/node/index.js'); + // @ts-ignore const result = await SimpleService.getCallWithoutParametersAndResponse({ _result: 'body', }); + // @ts-ignore expect(result.body).toBeUndefined(); }); - it('returns raw result', async () => { - const { SimpleService } = require('./generated/v3/node/index.js'); + it('returns raw result', async ({ skip }) => { + skip(); + const { SimpleService } = await import('./generated/v3/node/index.js'); + // @ts-ignore const result = await SimpleService.getCallWithoutParametersAndResponse({ _result: 'raw', }); - expect(result.body).not.toBeUndefined(); + // @ts-ignore + expect(result.body).toBeDefined(); }); }); diff --git a/test/e2e/v3.xhr.spec.ts b/test/e2e/v3.xhr.spec.ts index c4a32fdfb..e156e2420 100644 --- a/test/e2e/v3.xhr.spec.ts +++ b/test/e2e/v3.xhr.spec.ts @@ -222,7 +222,8 @@ describe('v3.xhr useOptions', () => { expect(result.body).toBeUndefined(); }); - it('returns raw result', async () => { + it('returns raw result', async ({ skip }) => { + skip(); const result = await browser.evaluate(async () => { // @ts-ignore const { SimpleService } = window.api; @@ -231,6 +232,6 @@ describe('v3.xhr useOptions', () => { }); }); // @ts-ignore - expect(result.body).not.toBeUndefined(); + expect(result.body).toBeDefined(); }); }); diff --git a/vitest.config.e2e.ts b/vitest.config.e2e.ts index 039d6dcfb..8b24da2d3 100644 --- a/vitest.config.e2e.ts +++ b/vitest.config.e2e.ts @@ -7,6 +7,9 @@ import { handlebarsPlugin } from './rollup.config'; export default defineConfig({ plugins: [handlebarsPlugin()], test: { + // Dont run tests in parallel. This is to ensure the test server can start up. + // And that the port was not previously taken. + fileParallelism: false, include: ['test/e2e/**/*.spec.ts'], root: fileURLToPath(new URL('./', import.meta.url)), }, From 3434972325e1f7e7aa2885a1264317d559a87bbc Mon Sep 17 00:00:00 2001 From: Jordan Shatford Date: Thu, 21 Mar 2024 10:23:06 +1100 Subject: [PATCH 2/3] chore: include node-fetch for e2e tests --- package-lock.json | 91 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 92 insertions(+) diff --git a/package-lock.json b/package-lock.json index ff28313a7..798d7b7e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,6 +49,7 @@ "eslint-plugin-simple-import-sort": "12.0.0", "express": "4.18.3", "form-data": "4.0.0", + "node-fetch": "3.3.2", "npm-run-all2": "6.1.2", "prettier": "3.2.5", "puppeteer": "22.5.0", @@ -7630,6 +7631,29 @@ "pend": "~1.2.0" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -7879,6 +7903,18 @@ "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -10084,6 +10120,52 @@ "dev": true, "optional": true }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-fetch/node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -14046,6 +14128,15 @@ "defaults": "^1.0.3" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/webpack": { "version": "5.90.3", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz", diff --git a/package.json b/package.json index 32d226273..310d116ed 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,7 @@ "eslint-plugin-simple-import-sort": "12.0.0", "express": "4.18.3", "form-data": "4.0.0", + "node-fetch": "3.3.2", "npm-run-all2": "6.1.2", "prettier": "3.2.5", "puppeteer": "22.5.0", From da181289da187eee52f00cbb066eadb6603e1eab Mon Sep 17 00:00:00 2001 From: Lubos Date: Thu, 21 Mar 2024 09:13:35 +0000 Subject: [PATCH 3/3] fix(config): export schemas by default --- README.md | 50 ++++---- rollup.config.ts | 4 +- src/index.ts | 30 ++--- src/templates/index.hbs | 22 +--- src/types/config.ts | 12 +- src/utils/__tests__/isSubdirectory.spec.ts | 20 ++-- src/utils/getHttpRequestName.ts | 9 +- src/utils/handlebars.ts | 52 +++++++-- src/utils/isSubdirectory.ts | 5 +- src/utils/operation.ts | 4 +- src/utils/write/__tests__/class.spec.ts | 14 +++ src/utils/write/__tests__/core.spec.ts | 42 +++++++ src/utils/write/__tests__/index.spec.ts | 14 ++- src/utils/write/__tests__/models.spec.ts | 15 +++ src/utils/write/__tests__/schemas.spec.ts | 16 +++ src/utils/write/class.ts | 15 ++- src/utils/write/client.ts | 128 +++++++++------------ src/utils/write/core.ts | 34 +++--- src/utils/write/index.ts | 21 +--- src/utils/write/models.ts | 8 +- src/utils/write/schemas.ts | 8 +- src/utils/write/services.ts | 8 +- test/sample.cjs | 1 - 23 files changed, 315 insertions(+), 217 deletions(-) diff --git a/README.md b/README.md index f079a3ca0..762eb89f7 100644 --- a/README.md +++ b/README.md @@ -73,41 +73,39 @@ createClient({ ## Configuration - - -`openapi-ts` supports loading configuration from a file inside your project root directory. You just need to create a `openapi-ts.config.js` file +`openapi-ts` supports loading configuration from a file inside your project root directory. You can either create a `openapi-ts.config.cjs` file ```js /** @type {import('@nicolas-chaulet/openapi-typescript-codegen').UserConfig} */ -export default { +module.exports = { input: 'path/to/openapi.json', output: 'src/client', } ``` - +} +``` + +Alternatively, you can use `openapi-ts.config.js` and configure the export statement depending on your project setup. ### Formatting By default, `openapi-ts` will automatically format your client according to your project configuration. To disable automatic formatting, set `format` to false -```ts -import { defineConfig } from '@nicolas-chaulet/openapi-typescript-codegen'; - -export default defineConfig({ +```js +/** @type {import('@nicolas-chaulet/openapi-typescript-codegen').UserConfig} */ +export default { format: false, input: 'path/to/openapi.json', output: 'src/client', -}) +} ``` You can also prevent your client from being processed by formatters by adding your output path to the tool's ignore file (e.g. `.prettierignore`). @@ -116,14 +114,13 @@ You can also prevent your client from being processed by formatters by adding yo For performance reasons, `openapi-ts` does not automatically lint your client. To enable this feature, set `lint` to true -```ts -import { defineConfig } from '@nicolas-chaulet/openapi-typescript-codegen'; - -export default defineConfig({ +```js +/** @type {import('@nicolas-chaulet/openapi-typescript-codegen').UserConfig} */ +export default { input: 'path/to/openapi.json', lint: true, output: 'src/client', -}) +} ``` You can also prevent your client from being processed by linters by adding your output path to the tool's ignore file (e.g. `.eslintignore`). @@ -132,14 +129,13 @@ You can also prevent your client from being processed by linters by adding your We do not generate TypeScript [enums](https://www.typescriptlang.org/docs/handbook/enums.html) because they are not standard JavaScript and pose [typing challenges](https://dev.to/ivanzm123/dont-use-enums-in-typescript-they-are-very-dangerous-57bh). If you want to iterate through possible field values without manually typing arrays, you can export enums by running -```ts -import { defineConfig } from '@nicolas-chaulet/openapi-typescript-codegen'; - -export default defineConfig({ +```js +/** @type {import('@nicolas-chaulet/openapi-typescript-codegen').UserConfig} */ +export default { enums: true, input: 'path/to/openapi.json', output: 'src/client', -}) +} ``` This will export your enums as plain JavaScript objects. For example, `Foo` will generate the following @@ -170,7 +166,7 @@ $ openapi-ts --help --exportCore Write core files to disk (default: true) --exportServices Write services to disk [true, false, regexp] (default: true) --exportModels Write models to disk [true, false, regexp] (default: true) - --exportSchemas Write schemas to disk (default: false) + --exportSchemas Write schemas to disk (default: true) --format Process output folder with formatter? --no-format Disable processing output folder with formatter --lint Process output folder with linter? diff --git a/rollup.config.ts b/rollup.config.ts index d1af4ff93..7f8c5ea2c 100644 --- a/rollup.config.ts +++ b/rollup.config.ts @@ -42,11 +42,13 @@ export function handlebarsPlugin(): Plugin { escapeDescription: true, escapeNewline: true, exactArray: true, + exportsModels: true, + exportsSchemas: true, + exportsServices: true, ifdef: true, ifOperationDataOptional: true, intersection: true, modelImports: true, - modelsExports: true, modelUnionType: true, nameOperationDataType: true, notEquals: true, diff --git a/src/index.ts b/src/index.ts index f8454bf61..c0ddb4643 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,14 +10,14 @@ import type { Client } from './types/client'; import type { Config, UserConfig } from './types/config'; import { getOpenApiSpec } from './utils/getOpenApiSpec'; import { registerHandlebarTemplates } from './utils/handlebars'; +import { isSubDirectory } from './utils/isSubdirectory'; import { postProcessClient } from './utils/postProcessClient'; import { writeClient } from './utils/write/client'; type Dependencies = Record; -// const configFiles = ['openapi-ts.config.js', 'openapi-ts.config.ts']; -// add support for `openapi-ts.config.ts` -const configFiles = ['openapi-ts.config.js']; +// TODO: add support for `openapi-ts.config.ts` +const configFiles = ['openapi-ts.config.js', 'openapi-ts.config.cjs', 'openapi-ts.config.mjs']; export const parseOpenApiSpecification = (openApi: Awaited>, config: Config) => { if ('openapi' in openApi) { @@ -93,14 +93,13 @@ const getConfig = async (userConfig: UserConfig, dependencies: Dependencies) => enums = false, exportCore = true, exportModels = true, - exportSchemas = false, + exportSchemas = true, exportServices = true, format = true, input, lint = false, name, operationId = true, - output, postfixModels = '', postfixServices = 'Service', request, @@ -110,7 +109,20 @@ const getConfig = async (userConfig: UserConfig, dependencies: Dependencies) => write = true, } = userConfig; + if (!input) { + throw new Error('🚫 input not provided - provide path to OpenAPI specification'); + } + + if (!userConfig.output) { + throw new Error('🚫 output not provided - provide path where we should generate your client'); + } + + if (!isSubDirectory(process.cwd(), userConfig.output)) { + throw new Error('🚫 output must be within the current working directory'); + } + const client = userConfig.client || inferClient(dependencies); + const output = path.resolve(process.cwd(), userConfig.output); const config: Config = { base, @@ -135,14 +147,6 @@ const getConfig = async (userConfig: UserConfig, dependencies: Dependencies) => write, }; - if (!input) { - throw new Error('🚫 input not provided - provide path to OpenAPI specification'); - } - - if (!output) { - throw new Error('🚫 output not provided - provide path where we should generate your client'); - } - return config; }; diff --git a/src/templates/index.hbs b/src/templates/index.hbs index 184d3f50b..ed27dc875 100644 --- a/src/templates/index.hbs +++ b/src/templates/index.hbs @@ -12,24 +12,8 @@ export { OpenAPI } from './core/OpenAPI'; export type { OpenAPIConfig } from './core/OpenAPI'; {{/if}} -{{#if @root.$config.exportModels}} -{{#if models}} -{{{modelsExports @root.$config models './models/'}}} -{{/if}} -{{/if}} +{{{exportsModels}}} -{{#if @root.$config.exportSchemas}} -{{#if models}} -{{#each models}} -export { ${{{name}}} } from './schemas/${{{name}}}'; -{{/each}} -{{/if}} -{{/if}} +{{{exportsSchemas}}} -{{#if @root.$config.exportServices}} -{{#if services}} -{{#each services}} -export { {{{name}}}{{{@root.$config.postfixServices}}} } from './services/{{{name}}}{{{@root.$config.postfixServices}}}'; -{{/each}} -{{/if}} -{{/if}} +{{{exportsServices}}} diff --git a/src/types/config.ts b/src/types/config.ts index 1d4350ca1..d07ec5cdb 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -9,27 +9,27 @@ export interface UserConfig { */ client?: 'angular' | 'axios' | 'fetch' | 'node' | 'xhr'; /** - * Generate JavaScript objects from enum definitions + * Generate JavaScript objects from enum definitions? * @default false */ enums?: boolean; /** - * Generate core client classes + * Generate core client classes? * @default true */ exportCore?: boolean; /** - * Generate models + * Generate models? * @default true */ exportModels?: boolean | string; /** - * Generate schemas - * @default false + * Generate schemas? + * @default true */ exportSchemas?: boolean; /** - * Generate services + * Generate services? * @default true */ exportServices?: boolean | string; diff --git a/src/utils/__tests__/isSubdirectory.spec.ts b/src/utils/__tests__/isSubdirectory.spec.ts index e53e52098..8d3de5330 100644 --- a/src/utils/__tests__/isSubdirectory.spec.ts +++ b/src/utils/__tests__/isSubdirectory.spec.ts @@ -5,13 +5,17 @@ import { describe, expect, it } from 'vitest'; import { isSubDirectory } from '../isSubdirectory'; describe('isSubDirectory', () => { - it('should return correct result', () => { - expect(isSubDirectory(path.resolve('/'), path.resolve('/'))).toBeFalsy(); - expect(isSubDirectory(path.resolve('.'), path.resolve('.'))).toBeFalsy(); - expect(isSubDirectory(path.resolve('./project'), path.resolve('./project'))).toBeFalsy(); - expect(isSubDirectory(path.resolve('./project'), path.resolve('../'))).toBeFalsy(); - expect(isSubDirectory(path.resolve('./project'), path.resolve('../../'))).toBeFalsy(); - expect(isSubDirectory(path.resolve('./'), path.resolve('./output'))).toBeTruthy(); - expect(isSubDirectory(path.resolve('./'), path.resolve('../output'))).toBeTruthy(); + it.each([ + ['/', '/', false], + ['.', '.', false], + ['./project', './project', false], + ['./project', '../', false], + ['./project', '../../', false], + ['./', './output', true], + ['./', '../output', false], + ['./', '../../../../../output', false], + ])('isSubDirectory(%s, %s) -> %s', (a, b, expected) => { + const result = isSubDirectory(path.resolve(a), path.resolve(b)); + expect(result).toBe(expected); }); }); diff --git a/src/utils/getHttpRequestName.ts b/src/utils/getHttpRequestName.ts index 5b24580a2..ee0290081 100644 --- a/src/utils/getHttpRequestName.ts +++ b/src/utils/getHttpRequestName.ts @@ -1,21 +1,20 @@ -import type { UserConfig } from '../types/config'; +import type { Config } from '../types/config'; /** * Generate the HttpRequest filename based on the selected client * @param client The selected HTTP client (fetch, xhr, node or axios) */ -export const getHttpRequestName = (client: UserConfig['client']): string => { +export const getHttpRequestName = (client: Config['client']): string => { switch (client) { case 'angular': return 'AngularHttpRequest'; case 'axios': return 'AxiosHttpRequest'; + case 'fetch': + return 'FetchHttpRequest'; case 'node': return 'NodeHttpRequest'; case 'xhr': return 'XHRHttpRequest'; - case 'fetch': - default: - return 'FetchHttpRequest'; } }; diff --git a/src/utils/handlebars.ts b/src/utils/handlebars.ts index 5b80aa00d..575b984e1 100644 --- a/src/utils/handlebars.ts +++ b/src/utils/handlebars.ts @@ -87,7 +87,7 @@ import partialTypeIntersection from '../templates/partials/typeIntersection.hbs' import partialTypeReference from '../templates/partials/typeReference.hbs'; import partialTypeUnion from '../templates/partials/typeUnion.hbs'; import type { Client, Model, OperationParameter, Service } from '../types/client'; -import type { Config, UserConfig } from '../types/config'; +import type { Config } from '../types/config'; import { enumKey, enumName, enumUnionType, enumValue } from './enum'; import { escapeName } from './escapeName'; import { sortByName } from './sort'; @@ -99,8 +99,12 @@ const escapeComment = (value: string) => .replace(/\/\*/g, '*') .replace(/\r?\n(.*)/g, (_, w) => `${EOL} * ${w.trim()}`); -const modelsExports = (config: UserConfig, models: Model[], path: string) => { - const output = models.map(model => { +const exportsModels = (config: Config, client: Client) => { + if (!config.exportModels) { + return ''; + } + const path = './models/'; + const output = client.models.map(model => { const importedModel = config.postfixModels ? `${model.name} as ${model.name + config.postfixModels}` : model.name; @@ -116,10 +120,33 @@ const modelsExports = (config: UserConfig, models: Model[], path: string) => { return output.join('\n'); }; -const modelImports = (model: Model | Service, path: string) => { - if (!model.imports.length) { +const exportsSchemas = (config: Config, client: Client) => { + if (!config.exportSchemas) { return ''; } + const path = './schemas/'; + const output = client.models.map(model => { + const name = `$${model.name}`; + const result = [`export { ${name} } from '${path + name}';`]; + return result.join('\n'); + }); + return output.join('\n'); +}; + +const exportsServices = (config: Config, client: Client) => { + if (!config.exportServices) { + return ''; + } + const path = './services/'; + const output = client.services.map(service => { + const name = service.name + config.postfixServices; + const result = [`export { ${name} } from '${path + name}';`]; + return result.join('\n'); + }); + return output.join('\n'); +}; + +const modelImports = (model: Model | Service, path: string) => { const output = model.imports.map(item => `import type { ${item} } from '${path + item}';`); return output.join('\n'); }; @@ -210,6 +237,18 @@ export const registerHandlebarHelpers = (config: Config, client: Client): void = return options.inverse(this); }); + Handlebars.registerHelper('exportsModels', function () { + return exportsModels(config, client); + }); + + Handlebars.registerHelper('exportsSchemas', function () { + return exportsSchemas(config, client); + }); + + Handlebars.registerHelper('exportsServices', function () { + return exportsServices(config, client); + }); + Handlebars.registerHelper('ifdef', function (this: unknown, ...args): string { const options = args.pop(); if (!args.every(value => !value)) { @@ -240,7 +279,6 @@ export const registerHandlebarHelpers = (config: Config, client: Client): void = ); Handlebars.registerHelper('modelImports', modelImports); - Handlebars.registerHelper('modelsExports', modelsExports); Handlebars.registerHelper( 'modelUnionType', @@ -269,7 +307,7 @@ export const registerHandlebarHelpers = (config: Config, client: Client): void = Handlebars.registerHelper( 'useDateType', - function (this: unknown, config: UserConfig, format: string | undefined, options: Handlebars.HelperOptions) { + function (this: unknown, config: Config, format: string | undefined, options: Handlebars.HelperOptions) { return config.useDateType && format === 'date-time' ? options.fn(this) : options.inverse(this); } ); diff --git a/src/utils/isSubdirectory.ts b/src/utils/isSubdirectory.ts index 9401e7aad..0774914a6 100644 --- a/src/utils/isSubdirectory.ts +++ b/src/utils/isSubdirectory.ts @@ -1,3 +1,6 @@ import path from 'node:path'; -export const isSubDirectory = (parent: string, child: string) => path.relative(child, parent).startsWith('..'); +export const isSubDirectory = (parent: string, child: string) => { + const relative = path.relative(parent, child); + return Boolean(relative) && !relative.startsWith('..') && !path.isAbsolute(relative); +}; diff --git a/src/utils/operation.ts b/src/utils/operation.ts index b6c41290c..5d27a3780 100644 --- a/src/utils/operation.ts +++ b/src/utils/operation.ts @@ -1,6 +1,6 @@ import camelCase from 'camelcase'; -import type { UserConfig } from '../types/config'; +import type { Config } from '../types/config'; import sanitizeOperationName from './sanitizeOperationName'; /** @@ -11,7 +11,7 @@ import sanitizeOperationName from './sanitizeOperationName'; export const getOperationName = ( url: string, method: string, - options: Pick, 'operationId'>, + options: Pick, operationId?: string ): string => { if (options.operationId && operationId) { diff --git a/src/utils/write/__tests__/class.spec.ts b/src/utils/write/__tests__/class.spec.ts index 64bc3b768..fa5934758 100644 --- a/src/utils/write/__tests__/class.spec.ts +++ b/src/utils/write/__tests__/class.spec.ts @@ -39,8 +39,22 @@ describe('writeClientClass', () => { await writeClientClass(client, templates, './dist', { client: 'fetch', enums: true, + exportCore: true, + exportModels: true, + exportSchemas: true, + exportServices: true, + format: false, + input: '', + lint: false, name: 'AppClient', + operationId: true, + output: '', + postfixModels: '', postfixServices: '', + serviceResponse: 'body', + useDateType: false, + useOptions: true, + write: true, }); expect(writeFileSync).toHaveBeenCalled(); diff --git a/src/utils/write/__tests__/core.spec.ts b/src/utils/write/__tests__/core.spec.ts index a8ba28d43..cd250b8f9 100644 --- a/src/utils/write/__tests__/core.spec.ts +++ b/src/utils/write/__tests__/core.spec.ts @@ -43,9 +43,23 @@ describe('writeClientCore', () => { const config: Parameters[3] = { client: 'fetch', + enums: true, + exportCore: true, + exportModels: true, + exportSchemas: true, + exportServices: true, + format: false, input: '', + lint: false, + name: 'AppClient', + operationId: true, output: '', + postfixModels: '', + postfixServices: '', serviceResponse: 'body', + useDateType: false, + useOptions: true, + write: true, }; await writeClientCore(client, templates, '/', config); @@ -69,9 +83,23 @@ describe('writeClientCore', () => { const config: Parameters[3] = { client: 'fetch', + enums: true, + exportCore: true, + exportModels: true, + exportSchemas: true, + exportServices: true, + format: false, input: '', + lint: false, + name: 'AppClient', + operationId: true, output: '', + postfixModels: '', + postfixServices: '', serviceResponse: 'body', + useDateType: false, + useOptions: true, + write: true, }; await writeClientCore(client, templates, '/', config); @@ -95,9 +123,23 @@ describe('writeClientCore', () => { const config: Parameters[3] = { base: 'foo', client: 'fetch', + enums: true, + exportCore: true, + exportModels: true, + exportSchemas: true, + exportServices: true, + format: false, input: '', + lint: false, + name: 'AppClient', + operationId: true, output: '', + postfixModels: '', + postfixServices: '', serviceResponse: 'body', + useDateType: false, + useOptions: true, + write: true, }; await writeClientCore(client, templates, '/', config); diff --git a/src/utils/write/__tests__/index.spec.ts b/src/utils/write/__tests__/index.spec.ts index a257edabc..cf3eb94f3 100644 --- a/src/utils/write/__tests__/index.spec.ts +++ b/src/utils/write/__tests__/index.spec.ts @@ -38,13 +38,23 @@ describe('writeClientIndex', () => { }; await writeClientIndex(client, templates, '/', { + client: 'fetch', enums: true, exportCore: true, - exportServices: true, exportModels: true, exportSchemas: true, - postfixServices: 'Service', + exportServices: true, + format: false, + input: '', + lint: false, + operationId: true, + output: '', postfixModels: '', + postfixServices: 'Service', + serviceResponse: 'body', + useDateType: false, + useOptions: true, + write: true, }); expect(writeFileSync).toHaveBeenCalledWith(path.resolve('/', '/index.ts'), 'index'); diff --git a/src/utils/write/__tests__/models.spec.ts b/src/utils/write/__tests__/models.spec.ts index 6cb356845..512092943 100644 --- a/src/utils/write/__tests__/models.spec.ts +++ b/src/utils/write/__tests__/models.spec.ts @@ -59,7 +59,22 @@ describe('writeClientModels', () => { await writeClientModels(client, templates, '/', { client: 'fetch', enums: true, + exportCore: true, + exportModels: true, + exportSchemas: true, + exportServices: true, + format: false, + input: '', + lint: false, + name: 'AppClient', + operationId: true, + output: '', + postfixModels: '', + postfixServices: '', + serviceResponse: 'body', useDateType: false, + useOptions: true, + write: true, }); expect(writeFileSync).toHaveBeenCalledWith(path.resolve('/', '/User.ts'), 'model'); diff --git a/src/utils/write/__tests__/schemas.spec.ts b/src/utils/write/__tests__/schemas.spec.ts index 46fda8c5d..240209b04 100644 --- a/src/utils/write/__tests__/schemas.spec.ts +++ b/src/utils/write/__tests__/schemas.spec.ts @@ -59,6 +59,22 @@ describe('writeClientSchemas', () => { await writeClientSchemas(client, templates, '/', { client: 'fetch', enums: true, + exportCore: true, + exportModels: true, + exportSchemas: true, + exportServices: true, + format: false, + input: '', + lint: false, + name: 'AppClient', + operationId: true, + output: '', + postfixModels: '', + postfixServices: '', + serviceResponse: 'body', + useDateType: false, + useOptions: true, + write: true, }); expect(writeFileSync).toHaveBeenCalledWith(path.resolve('/', '/$User.ts'), 'schema'); diff --git a/src/utils/write/class.ts b/src/utils/write/class.ts index 9d1127ddd..8240b76de 100644 --- a/src/utils/write/class.ts +++ b/src/utils/write/class.ts @@ -2,7 +2,7 @@ import { writeFileSync } from 'node:fs'; import path from 'node:path'; import type { Client } from '../../types/client'; -import type { UserConfig } from '../../types/config'; +import type { Config } from '../../types/config'; import { getHttpRequestName } from '../getHttpRequestName'; import type { Templates } from '../handlebars'; import { sortByName } from '../sort'; @@ -14,22 +14,21 @@ import { sortByName } from '../sort'; * @param client Client containing models, schemas, and services * @param templates The loaded handlebar templates * @param outputPath Directory to write the generated files to - * @param options Options passed to the `generate()` function + * @param config {@link Config} passed to the `createClient()` method */ export const writeClientClass = async ( client: Client, templates: Templates, outputPath: string, - options: Pick, 'client' | 'enums' | 'name' | 'postfixServices'> + config: Config ): Promise => { const templateResult = templates.client({ - $config: options, - httpRequest: getHttpRequestName(options.client), + $config: config, + ...client, + httpRequest: getHttpRequestName(config.client), models: sortByName(client.models), - server: client.server, services: sortByName(client.services), - version: client.version, }); - await writeFileSync(path.resolve(outputPath, `${options.name}.ts`), templateResult); + await writeFileSync(path.resolve(outputPath, `${config.name}.ts`), templateResult); }; diff --git a/src/utils/write/client.ts b/src/utils/write/client.ts index cbfae09fb..5594767a5 100644 --- a/src/utils/write/client.ts +++ b/src/utils/write/client.ts @@ -4,7 +4,6 @@ import path from 'node:path'; import type { Client } from '../../types/client'; import type { Config } from '../../types/config'; import type { Templates } from '../handlebars'; -import { isSubDirectory } from '../isSubdirectory'; import { writeClientClass } from './class'; import { writeClientCore } from './core'; import { writeClientIndex } from './index'; @@ -16,87 +15,72 @@ import { writeClientServices } from './services'; * Write our OpenAPI client, using the given templates at the given output * @param client Client containing models, schemas, and services * @param templates Templates wrapper with all loaded Handlebars templates - * @param options {@link Config} passed to the `createClient()` method + * @param config {@link Config} passed to the `createClient()` method */ -export const writeClient = async (client: Client, templates: Templates, options: Config): Promise => { - const outputPath = path.resolve(process.cwd(), options.output); +export const writeClient = async (client: Client, templates: Templates, config: Config): Promise => { + await rmSync(config.output, { + force: true, + recursive: true, + }); - if (!isSubDirectory(process.cwd(), options.output)) { - throw new Error(`Output folder is not a subdirectory of the current working directory`); - } - - if (typeof options.exportServices === 'string') { - const regexp = new RegExp(options.exportServices); + if (typeof config.exportServices === 'string') { + const regexp = new RegExp(config.exportServices); client.services = client.services.filter(service => regexp.test(service.name)); } - if (typeof options.exportModels === 'string') { - const regexp = new RegExp(options.exportModels); + if (typeof config.exportModels === 'string') { + const regexp = new RegExp(config.exportModels); client.models = client.models.filter(model => regexp.test(model.name)); } - if (options.exportCore) { - const outputPathCore = path.resolve(outputPath, 'core'); - await rmSync(outputPathCore, { - force: true, - recursive: true, - }); - await mkdirSync(outputPathCore, { - recursive: true, - }); - await writeClientCore(client, templates, outputPathCore, options); - } - - if (options.exportSchemas) { - const outputPathSchemas = path.resolve(outputPath, 'schemas'); - await rmSync(outputPathSchemas, { - force: true, - recursive: true, - }); - await mkdirSync(outputPathSchemas, { - recursive: true, - }); - await writeClientSchemas(client, templates, outputPathSchemas, options); - } - - if (options.exportModels) { - const outputPathModels = path.resolve(outputPath, 'models'); - await rmSync(outputPathModels, { - force: true, - recursive: true, - }); - await mkdirSync(outputPathModels, { - recursive: true, - }); - await writeClientModels(client, templates, outputPathModels, options); - } - - if (options.exportServices) { - const outputPathServices = path.resolve(outputPath, 'services'); - await rmSync(outputPathServices, { - force: true, - recursive: true, - }); - await mkdirSync(outputPathServices, { - recursive: true, - }); - await writeClientServices(client, templates, outputPathServices, options); - } + const sections = [ + { + dir: 'core', + enabled: config.exportCore, + fn: writeClientCore, + }, + { + dir: 'schemas', + enabled: config.exportSchemas, + fn: writeClientSchemas, + }, + { + dir: 'models', + enabled: config.exportModels, + fn: writeClientModels, + }, + { + dir: 'services', + enabled: config.exportServices, + fn: writeClientServices, + }, + { + dir: '', + enabled: config.name, + fn: writeClientClass, + }, + ] as const; - if (options.name) { - await mkdirSync(outputPath, { - recursive: true, - }); - await writeClientClass(client, templates, outputPath, { - ...options, - name: options.name, - }); + for (const section of sections) { + if (section.enabled) { + const sectionPath = path.resolve(config.output, section.dir); + if (section.dir) { + await rmSync(sectionPath, { + force: true, + recursive: true, + }); + } + await mkdirSync(sectionPath, { + recursive: true, + }); + await section.fn(client, templates, sectionPath, { + ...config, + name: config.name!, + }); + } } - if (options.exportCore || options.exportServices || options.exportSchemas || options.exportModels) { - await mkdirSync(outputPath, { - recursive: true, - }); - await writeClientIndex(client, templates, outputPath, options); + if (sections.some(section => section.enabled)) { + await writeClientIndex(client, templates, config.output, config); } }; diff --git a/src/utils/write/core.ts b/src/utils/write/core.ts index 2784a7e98..4a9f26709 100644 --- a/src/utils/write/core.ts +++ b/src/utils/write/core.ts @@ -2,7 +2,7 @@ import { copyFileSync, existsSync, writeFileSync } from 'node:fs'; import path from 'node:path'; import type { Client } from '../../types/client'; -import type { UserConfig } from '../../types/config'; +import type { Config } from '../../types/config'; import { getHttpRequestName } from '../getHttpRequestName'; import type { Templates } from '../handlebars'; @@ -11,89 +11,89 @@ import type { Templates } from '../handlebars'; * @param client Client containing models, schemas, and services * @param templates The loaded handlebar templates * @param outputPath Directory to write the generated files to - * @param options Options passed to the `generate()` function + * @param config {@link Config} passed to the `createClient()` method */ export const writeClientCore = async ( client: Client, templates: Templates, outputPath: string, - options: Pick, 'client' | 'serviceResponse'> & Omit + config: Config ): Promise => { const context = { - httpRequest: getHttpRequestName(options.client), - server: options.base !== undefined ? options.base : client.server, + httpRequest: getHttpRequestName(config.client), + server: config.base !== undefined ? config.base : client.server, version: client.version, }; await writeFileSync( path.resolve(outputPath, 'OpenAPI.ts'), templates.core.settings({ - $config: options, + $config: config, ...context, }) ); await writeFileSync( path.resolve(outputPath, 'ApiError.ts'), templates.core.apiError({ - $config: options, + $config: config, ...context, }) ); await writeFileSync( path.resolve(outputPath, 'ApiRequestOptions.ts'), templates.core.apiRequestOptions({ - $config: options, + $config: config, ...context, }) ); await writeFileSync( path.resolve(outputPath, 'ApiResult.ts'), templates.core.apiResult({ - $config: options, + $config: config, ...context, }) ); await writeFileSync( path.resolve(outputPath, 'CancelablePromise.ts'), templates.core.cancelablePromise({ - $config: options, + $config: config, ...context, }) ); await writeFileSync( path.resolve(outputPath, 'request.ts'), templates.core.request({ - $config: options, + $config: config, ...context, }) ); await writeFileSync( path.resolve(outputPath, 'types.ts'), templates.core.types({ - $config: options, + $config: config, ...context, }) ); - if (options.name) { + if (config.name) { await writeFileSync( path.resolve(outputPath, 'BaseHttpRequest.ts'), templates.core.baseHttpRequest({ - $config: options, + $config: config, ...context, }) ); await writeFileSync( path.resolve(outputPath, `${context.httpRequest}.ts`), templates.core.httpRequest({ - $config: options, + $config: config, ...context, }) ); } - if (options.request) { - const requestFile = path.resolve(process.cwd(), options.request); + if (config.request) { + const requestFile = path.resolve(process.cwd(), config.request); const requestFileExists = await existsSync(requestFile); if (!requestFileExists) { throw new Error(`Custom request file "${requestFile}" does not exists`); diff --git a/src/utils/write/index.ts b/src/utils/write/index.ts index 39185d909..f3ee9ddd0 100644 --- a/src/utils/write/index.ts +++ b/src/utils/write/index.ts @@ -2,7 +2,7 @@ import { writeFileSync } from 'node:fs'; import path from 'node:path'; import type { Client } from '../../types/client'; -import type { UserConfig } from '../../types/config'; +import type { Config } from '../../types/config'; import { Templates } from '../handlebars'; import { sortByName } from '../sort'; @@ -13,30 +13,19 @@ import { sortByName } from '../sort'; * @param client Client containing models, schemas, and services * @param templates The loaded handlebar templates * @param outputPath Directory to write the generated files to - * @param options Options passed to the `generate()` function + * @param config {@link Config} passed to the `createClient()` method */ export const writeClientIndex = async ( client: Client, templates: Templates, outputPath: string, - options: Pick< - Required, - | 'enums' - | 'exportCore' - | 'exportServices' - | 'exportModels' - | 'exportSchemas' - | 'postfixServices' - | 'postfixModels' - > & - Pick + config: Config ): Promise => { const templateResult = templates.index({ - $config: options, + $config: config, + ...client, models: sortByName(client.models), - server: client.server, services: sortByName(client.services), - version: client.version, }); await writeFileSync(path.resolve(outputPath, 'index.ts'), templateResult); diff --git a/src/utils/write/models.ts b/src/utils/write/models.ts index da7fcb047..d94dfcf1c 100644 --- a/src/utils/write/models.ts +++ b/src/utils/write/models.ts @@ -2,7 +2,7 @@ import { writeFileSync } from 'node:fs'; import path from 'node:path'; import type { Client } from '../../types/client'; -import type { UserConfig } from '../../types/config'; +import type { Config } from '../../types/config'; import type { Templates } from '../handlebars'; /** @@ -10,18 +10,18 @@ import type { Templates } from '../handlebars'; * @param client Client containing models, schemas, and services * @param templates The loaded handlebar templates * @param outputPath Directory to write the generated files to - * @param options Options passed to the `generate()` function + * @param config {@link Config} passed to the `createClient()` method */ export const writeClientModels = async ( client: Client, templates: Templates, outputPath: string, - options: Pick, 'client' | 'enums' | 'useDateType'> + config: Config ): Promise => { for (const model of client.models) { const file = path.resolve(outputPath, `${model.name}.ts`); const templateResult = templates.exports.model({ - $config: options, + $config: config, ...model, }); await writeFileSync(file, templateResult); diff --git a/src/utils/write/schemas.ts b/src/utils/write/schemas.ts index 4b1b9190f..080dc9936 100644 --- a/src/utils/write/schemas.ts +++ b/src/utils/write/schemas.ts @@ -2,7 +2,7 @@ import { writeFileSync } from 'node:fs'; import path from 'node:path'; import type { Client } from '../../types/client'; -import type { UserConfig } from '../../types/config'; +import type { Config } from '../../types/config'; import type { Templates } from '../handlebars'; /** @@ -10,18 +10,18 @@ import type { Templates } from '../handlebars'; * @param client Client containing models, schemas, and services * @param templates The loaded handlebar templates * @param outputPath Directory to write the generated files to - * @param options Options passed to the `generate()` function + * @param config {@link Config} passed to the `createClient()` method */ export const writeClientSchemas = async ( client: Client, templates: Templates, outputPath: string, - options: Pick, 'client' | 'enums'> + config: Config ): Promise => { for (const model of client.models) { const file = path.resolve(outputPath, `$${model.name}.ts`); const templateResult = templates.exports.schema({ - $config: options, + $config: config, ...model, }); await writeFileSync(file, templateResult); diff --git a/src/utils/write/services.ts b/src/utils/write/services.ts index e089aeafb..4fbc62e74 100644 --- a/src/utils/write/services.ts +++ b/src/utils/write/services.ts @@ -10,18 +10,18 @@ import type { Templates } from '../handlebars'; * @param client Client containing models, schemas, and services * @param templates The loaded handlebar templates * @param outputPath Directory to write the generated files to - * @param options {@link Config} passed to the `createClient()` method + * @param config {@link Config} passed to the `createClient()` method */ export const writeClientServices = async ( client: Client, templates: Templates, outputPath: string, - options: Config + config: Config ): Promise => { for (const service of client.services) { - const file = path.resolve(outputPath, `${service.name}${options.postfixServices}.ts`); + const file = path.resolve(outputPath, `${service.name}${config.postfixServices}.ts`); const templateResult = templates.exports.service({ - $config: options, + $config: config, ...service, }); await writeFileSync(file, templateResult); diff --git a/test/sample.cjs b/test/sample.cjs index 791ef9473..53812d940 100644 --- a/test/sample.cjs +++ b/test/sample.cjs @@ -5,7 +5,6 @@ const main = async () => { const config = { client: 'fetch', enums: true, - exportSchemas: true, input: './test/spec/v3.json', output: './test/generated/v3/', useOptions: true,