-
Notifications
You must be signed in to change notification settings - Fork 35
@W-17701711@ Generate isomorphic SDK from OAS #194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 30 commits
bdb11d4
1176306
1fd3f58
1568fc6
dca3c90
adf102f
1e296d6
34ece5e
45aa643
35894e1
c0b4578
3a9082b
6aeb85e
1a3f6f8
d6ac769
1fa97bd
ed22fe5
709f8b4
5f9e3e5
7e8685c
75ec28f
00b774c
ab5160e
a7db4ee
bbbb255
2813adc
726d8ff
8aa5b6f
de5f943
98adab1
d4d34cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json", | ||
| "spaces": 2, | ||
| "generator-cli": { | ||
| "version": "7.13.0" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,17 +12,16 @@ export const PRIMITIVE_DATA_TYPE_MAP = { | |
| 'http://www.w3.org/2001/XMLSchema#boolean': 'boolean', | ||
| }; | ||
| export const API_LIST: string[] = [ | ||
| 'shopper-baskets', | ||
| 'shopper-context', | ||
| 'shopper-customers', | ||
| 'shopper-discovery-search', | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is shopper-discovery-search no longer needed?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, according to Kay, both shopper-discovery-search and slas-shopper-login-uap artifact are out of scope for the OAS work
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume we are not using these in PWA? |
||
| 'shopper-experience', | ||
| 'shopper-gift-certificates', | ||
| 'slas-shopper-login-uap', | ||
| 'shopper-orders', | ||
| 'shopper-products', | ||
| 'shopper-promotions', | ||
| 'shopper-search', | ||
| 'shopper-seo', | ||
| 'shopper-stores', | ||
| 'shopper-baskets-oas', | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I left a comment in the other PR for just replacing the APIs, but do we want to add shopper baskets v1 as well?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at anypoint exchange, there is only one version of We should bring up getting an OAS version of baskets v1 with the api team. |
||
| 'shopper-context-oas', | ||
| 'shopper-customers-oas', | ||
| 'shopper-experience-oas', | ||
| 'shopper-gift-certificates-oas', | ||
| 'shopper-login-oas', | ||
| 'shopper-orders-oas', | ||
| 'shopper-products-oas', | ||
| 'shopper-promotions-oas', | ||
| 'shopper-search-oas', | ||
| 'shopper-seo-oas', | ||
| 'shopper-stores-oas', | ||
| ]; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,202 @@ | ||
| /* | ||
| * Copyright (c) 2025, Salesforce, Inc. | ||
| * All rights reserved. | ||
| * SPDX-License-Identifier: BSD-3-Clause | ||
| * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause | ||
| */ | ||
| import fs from 'fs-extra'; | ||
| import path from 'path'; | ||
| import {generateFromOas} from '@commerce-apps/raml-toolkit'; | ||
| import Handlebars from 'handlebars'; | ||
| import { | ||
| resolveApiName, | ||
| getAPIDetailsFromExchange, | ||
| generateSDKs, | ||
| generateIndex, | ||
| main, | ||
| generateVersionFile, | ||
| } from './generate-oas'; | ||
|
|
||
| // Mock dependencies | ||
| jest.mock('fs-extra'); | ||
|
|
||
| jest.mock('@commerce-apps/raml-toolkit', () => ({ | ||
| generateFromOas: { | ||
| generateFromOas: jest.fn(), | ||
| }, | ||
| })); | ||
|
|
||
| describe('generate-oas', () => { | ||
| const mockApiDirectory = '/mock/api/directory'; | ||
| let handlebarsSpy: jest.SpyInstance; | ||
|
|
||
| beforeEach(() => { | ||
| // Reset all mocks before each test | ||
| jest.clearAllMocks(); | ||
|
|
||
| // Set up Handlebars spy | ||
| handlebarsSpy = jest.spyOn(Handlebars, 'compile'); | ||
|
|
||
| // Mock fs-extra methods | ||
| (fs.existsSync as jest.Mock).mockReturnValue(true); | ||
| (fs.readJSONSync as jest.Mock).mockReturnValue({ | ||
| main: 'api.yaml', | ||
| assetId: 'shopper-orders-oas', | ||
| name: 'Shopper orders OAS', | ||
| }); | ||
| (fs.statSync as jest.Mock).mockReturnValue({isFile: () => true}); | ||
| (fs.readdir as jest.Mock).mockImplementation((dir, callback) => { | ||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-call | ||
| callback(null, ['shopper-orders']); | ||
| }); | ||
| (fs.readFileSync as jest.Mock).mockReturnValue('template content'); | ||
| // eslint-disable-next-line @typescript-eslint/no-empty-function | ||
| (fs.writeFileSync as jest.Mock).mockImplementation(() => {}); | ||
| // eslint-disable-next-line @typescript-eslint/no-empty-function | ||
| (fs.copySync as jest.Mock).mockImplementation(() => {}); | ||
| (fs.lstatSync as jest.Mock).mockReturnValue({isDirectory: () => true}); | ||
| }); | ||
|
|
||
| describe('Main execution', () => { | ||
| it('should process all API directories and generate SDKs', () => { | ||
| process.env.COMMERCE_SDK_INPUT_DIR = mockApiDirectory; | ||
| process.env.PACKAGE_VERSION = '1.0.0'; | ||
|
|
||
| main(); | ||
|
|
||
| expect(fs.copySync).toHaveBeenCalledWith( | ||
| path.join(__dirname, '../src/static'), | ||
| path.join(__dirname, '../src/lib'), | ||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment | ||
| {filter: expect.any(Function)} | ||
| ); | ||
| expect(fs.readdir).toHaveBeenCalledWith( | ||
| mockApiDirectory, | ||
| expect.any(Function) | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| describe('resolveApiName', () => { | ||
| it('should handle special case for Shopper orders OAS', () => { | ||
| const result = resolveApiName('Shopper orders OAS'); | ||
| expect(result).toBe('ShopperOrders'); | ||
| }); | ||
|
|
||
| it('should handle special case for Shopper Seo OAS', () => { | ||
| const result = resolveApiName('Shopper Seo OAS'); | ||
| expect(result).toBe('ShopperSEO'); | ||
| }); | ||
|
|
||
| it('should handle regular API names', () => { | ||
| const result = resolveApiName('Shopper Baskets OAS'); | ||
| expect(result).toBe('ShopperBaskets'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('getAPIDetailsFromExchange', () => { | ||
| it('should return correct API details from exchange.json', () => { | ||
| const result = getAPIDetailsFromExchange( | ||
| path.join(mockApiDirectory, 'shopperOrders') | ||
| ); | ||
| expect(result).toEqual({ | ||
| filepath: path.join(mockApiDirectory, 'shopperOrders', 'api.yaml'), | ||
| filename: 'api.yaml', | ||
| directoryName: 'shopperOrders', | ||
| name: 'Shopper orders OAS', | ||
| apiName: 'ShopperOrders', | ||
| }); | ||
| }); | ||
|
|
||
| it('should throw error when exchange.json does not exist', () => { | ||
| (fs.existsSync as jest.Mock).mockReturnValueOnce(false); | ||
| expect(() => getAPIDetailsFromExchange('nonexistent')).toThrow( | ||
| 'Exchange file does not exist' | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| describe('generateSDKs', () => { | ||
| it('should generate SDK for shopper API', () => { | ||
| const apiSpecDetail = { | ||
| filepath: '/path/to/shopper/api.yaml', | ||
| filename: 'api.yaml', | ||
| name: 'Test API', | ||
| directoryName: 'test-api', | ||
| apiName: 'TestAPI', | ||
| }; | ||
|
|
||
| generateSDKs(apiSpecDetail); | ||
|
|
||
| expect(generateFromOas.generateFromOas).toHaveBeenCalledWith({ | ||
| inputSpec: '/path/to/shopper/api.yaml', | ||
| outputDir: path.join(__dirname, '../src/lib/test-api'), | ||
| templateDir: path.join(__dirname, '../templatesOas'), | ||
| skipValidateSpec: true, | ||
| }); | ||
| }); | ||
|
|
||
| it('should not generate SDK for non-api files', () => { | ||
| const apiSpecDetail = { | ||
| filepath: '/path/to/shopper/not-api.yaml', | ||
| filename: '/not-api.yaml', | ||
| name: 'Not API', | ||
| directoryName: 'none', | ||
| apiName: 'NotAPI', | ||
| }; | ||
|
|
||
| (fs.statSync as jest.Mock).mockReturnValueOnce({isFile: () => false}); | ||
| generateSDKs(apiSpecDetail); | ||
|
|
||
| expect(generateFromOas.generateFromOas).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('should not generate SDK for non-shopper API', () => { | ||
| const apiSpecDetail = { | ||
| filepath: '/path/to/non/api.yaml', | ||
| filename: 'api.yaml', | ||
| name: 'Non Shopper API', | ||
| directoryName: 'none', | ||
| apiName: 'NonShopper', | ||
| }; | ||
|
|
||
| (fs.statSync as jest.Mock).mockReturnValueOnce({isFile: () => true}); | ||
| generateSDKs(apiSpecDetail); | ||
|
|
||
| expect(generateFromOas.generateFromOas).not.toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('generateIndex', () => { | ||
| it('should generate index file with correct context', () => { | ||
| const context = { | ||
| children: [ | ||
| { | ||
| name: 'Test API', | ||
| apiName: 'TestAPI', | ||
| }, | ||
| ], | ||
| }; | ||
|
|
||
| generateIndex(context); | ||
|
|
||
| expect(fs.writeFileSync).toHaveBeenCalledWith( | ||
| path.join(__dirname, '../src/lib/index.ts'), | ||
| expect.any(String) | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| describe('generateVersionFile', () => { | ||
| it('should generate version file', () => { | ||
| process.env.PACKAGE_VERSION = '1.0.0'; | ||
|
|
||
| generateVersionFile(); | ||
|
|
||
| expect(fs.writeFileSync).toHaveBeenCalledWith( | ||
| path.join(__dirname, '../src/lib/version.ts'), | ||
| expect.any(String) | ||
| ); | ||
| }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this file here? Will it not use from raml-toolkit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. I just tried generating the sdk again using the raml-toolkit command after deleting it and it didn't return.
This was probably an artifact from when I ran openapi-generator directly in the sdk. I'll delete it.