-
Notifications
You must be signed in to change notification settings - Fork 51
@W-18575180@ Generate node SDK from OAS #418
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 43 commits
121441f
95744ba
1a6be2a
1bae476
35c2828
a7a44cb
c3b4c85
91b1886
2da8879
52ed46c
e100d66
3e0dc5f
9cc409a
fbc131e
3ad83a5
65ec2fc
6815191
94d281d
3c7141f
23aa529
c84aba2
4a69e2f
91f40e2
9f2fe25
0e6ffe3
2bb86af
10fbc49
bbbc94f
7901157
3fcf952
99340e1
ecb7ec6
ddcb8f9
4168bee
0252bda
e9e9d62
a0b2735
3541481
fd3aa05
1821845
48f70bf
cc4a92d
ddc3789
ba7f016
18a9bf1
5ea68fe
e482d92
5e14dc0
33a8049
935d536
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 |
|---|---|---|
|
|
@@ -19,23 +19,25 @@ | |
| ], | ||
| "scripts": { | ||
| "backupApis": "rm -rf updateApisTmp && mkdir updateApisTmp && mv apis updateApisTmp/oldApis", | ||
| "build": "npm run clean && npm run renderTemplates && tsc && eslint dist --ext .ts,.js --quiet --fix", | ||
| "build": "npm run clean && npm run renderTemplates && npm run compile", | ||
| "ci": "rm -rf node_modules && yarn install", | ||
vcua-mobify marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| "clean": "rm -rf renderedTemplates dist", | ||
| "compile": "tsc && eslint dist --ext .ts,.js --quiet --fix", | ||
| "depcheck": "depcheck", | ||
| "diffApis": "raml-toolkit diff --dir updateApisTmp/oldApis apis -f console -o updateApisTmp/ramlDiff.txt", | ||
| "diffApis": "raml-toolkit diff --dir updateApisTmp/oldApis apis -f console -o updateApisTmp/oasDiff.txt -s oas", | ||
| "doc": "npm run doc:generate && npm run doc:resources", | ||
| "doc:generate": "typedoc --mode modules --out ./docs renderedTemplates/** --tsconfig tsconfig.json --external-modulemap \".*/renderedTemplates/([\\w\\-_]+)/\"", | ||
| "doc:resources": "cp CHANGELOG.md ./docs", | ||
| "lint": "npm run lint:dev -- --quiet", | ||
| "lint:dev": "sort-package-json && eslint . --ext .ts --fix", | ||
| "prepack": "npm run build", | ||
| "prepare": "snyk protect", | ||
| "renderTemplates": "ts-node src/generate.ts", | ||
| "renderTemplates": "PACKAGE_VERSION=$(node -p \"require('./package.json').version\") ts-node src/generate-oas.ts", | ||
| "snyk:auth": "snyk auth", | ||
| "pretest": "npm run lint && npm run depcheck", | ||
vcua-mobify marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "pretest": "npm run lint", | ||
| "test": "nyc mocha \"src/**/*.test.ts\"", | ||
| "test:ci": "npm test -- --reporter=xunit --reporter-options output=./reports/generator.xml", | ||
| "updateApis": "npm run backupApis && ts-node src/updateApis.ts && git checkout apis/search && npm run diffApis" | ||
| "updateApis": "npm run backupApis && ts-node src/updateApis.ts && npm run diffApis" | ||
| }, | ||
| "mocha": { | ||
| "file": [ | ||
|
|
@@ -49,7 +51,7 @@ | |
| "tslib": "^2.4.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@commerce-apps/raml-toolkit": "^0.5.12", | ||
| "@commerce-apps/raml-toolkit": "file:../raml-toolkit", | ||
|
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. This should be updated to the raml-tooklit version that includes the changes in SalesforceCommerceCloud/raml-toolkit#231 |
||
| "@istanbuljs/nyc-config-typescript": "^1.0.2", | ||
| "@types/chai": "^4.3.0", | ||
| "@types/fs-extra": "^9.0.13", | ||
|
|
@@ -65,9 +67,8 @@ | |
| "eslint": "^8.38.0", | ||
| "eslint-config-prettier": "^8.8.0", | ||
| "eslint-plugin-header": "^3.1.1", | ||
| "eslint-plugin-jsdoc": "^41.1.1", | ||
| "eslint-plugin-jsdoc": "^50.7.1", | ||
|
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. I bumped up some versions during my attempt to fix the tsdoc issues. I ended up removing tsdoc but the other version bumps seems ok to proceed. |
||
| "eslint-plugin-prettier": "^4.2.1", | ||
| "eslint-plugin-tsdoc": "^0.2.17", | ||
| "fs-extra": "^9.1.0", | ||
| "handlebars-helpers": "^0.10.0", | ||
| "mocha": "^8.4.0", | ||
|
|
@@ -83,6 +84,9 @@ | |
| "typedoc-plugin-nojekyll": "^1.0.1", | ||
| "typescript": "^4.4.4" | ||
| }, | ||
| "resolutions": { | ||
| "@rdfjs/types": "v1.1.2" | ||
| }, | ||
| "publishConfig": { | ||
| "access": "public" | ||
| }, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,223 @@ | ||
| /* | ||
| * Copyright (c) 2025, salesforce.com, 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 path from "path"; | ||
| import fs from "fs-extra"; | ||
| import { generateFromOas, download } from "@commerce-apps/raml-toolkit"; | ||
| import Handlebars from "handlebars"; | ||
|
|
||
| type ApiSpecDetail = { | ||
| filepath: string; | ||
| filename: string; | ||
| name: string; | ||
| apiName: string; | ||
| directoryName: string; | ||
| }; | ||
|
|
||
| const API_DIRECTORY = path.resolve(`${__dirname}/../apis`); | ||
| const STATIC_DIRECTORY = path.join(__dirname, "./static"); | ||
| const TARGET_DIRECTORY = path.resolve(`${__dirname}/../renderedTemplates`); | ||
| const TEMPLATE_DIRECTORY = path.resolve(`${__dirname}/../templatesOas`); | ||
| const INDEX_TEMPLATE_LOCATION = path.resolve( | ||
| `${__dirname}/../templates/index.ts.hbs` | ||
| ); | ||
| const VERSION_TEMPLATE_LOCATION = path.resolve( | ||
| `${__dirname}/../templates/version.ts.hbs` | ||
| ); | ||
|
|
||
| function kebabToCamelCase(str: string): string { | ||
| return str.replace(/-([a-z])/g, (match, letter: string) => | ||
| letter.toUpperCase() | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Helper function. Also contains explicit workaround for some API names. | ||
| */ | ||
| export function resolveApiName(name: string): string { | ||
vcua-mobify marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (name === "Shopper orders OAS") { | ||
| return "ShopperOrders"; | ||
| } | ||
| if (name === "CDN API - Process APIs OAS") { | ||
| return "CDNZones"; | ||
| } | ||
| if (name === "SLAS Admin-UAP OAS") { | ||
| return "SlasAdmin"; | ||
vcua-mobify marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| if (name === "Shopper Seo OAS") { | ||
| return "ShopperSEO"; | ||
| } | ||
| if (name === "Shopper Context OAS") { | ||
| return "ShopperContexts"; | ||
| } | ||
| if (name === "Catalogs OAS") { | ||
| return "CatalogsAPI"; | ||
vcua-mobify marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| if (name === "Cors Preferences OAS") { | ||
| return "CORSPreferences"; | ||
| } | ||
| return name.replace(/\s+/g, "").replace("OAS", ""); | ||
| } | ||
|
|
||
| /** | ||
| * Helper that reads api details from exchange.json | ||
| */ | ||
| export function getAPIDetailsFromExchange(directory: string): ApiSpecDetail { | ||
| const exchangePath = path.join(directory, "exchange.json"); | ||
| if (fs.existsSync(exchangePath)) { | ||
| const exchangeConfig = fs.readJSONSync( | ||
| exchangePath | ||
| ) as download.ExchangeConfig; | ||
|
|
||
| // Special handling for shopper-baskets-v2 | ||
| if ( | ||
| exchangeConfig.assetId === "shopper-baskets-oas" && | ||
| exchangeConfig.apiVersion === "v2" | ||
| ) { | ||
vcua-mobify marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return { | ||
| filepath: path.join(directory, exchangeConfig.main), | ||
| filename: exchangeConfig.main, | ||
|
Collaborator
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. Does changing the filename to include V2 change the class name for the generated class? If so I think we should do that
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. This refers to the file name of the file that's defined in If we change the name of the .yaml file then we will need to come up with a different way to get that file name rather than relying on exchange.json. |
||
| directoryName: "ShopperBasketsV2", | ||
| name: "Shopper Baskets V2 OAS", | ||
| apiName: "ShopperBasketsV2", | ||
| }; | ||
| } | ||
|
|
||
| return { | ||
| filepath: path.join(directory, exchangeConfig.main), | ||
| filename: exchangeConfig.main, | ||
| directoryName: kebabToCamelCase( | ||
| exchangeConfig.assetId.replace("-oas", "") | ||
| ), | ||
| name: exchangeConfig.name, | ||
| apiName: resolveApiName(exchangeConfig.name), | ||
| }; | ||
| } | ||
| throw new Error(`Exchange file does not exist for ${directory}`); | ||
| } | ||
|
|
||
| /** | ||
| * Invokes openapi-generator via raml-toolkit to generate SDKs | ||
| */ | ||
| export function generateSDKs(apiSpecDetail: ApiSpecDetail): void { | ||
| const { filepath, name, directoryName } = apiSpecDetail; | ||
| if (fs.statSync(filepath).isFile()) { | ||
| try { | ||
| console.log(`Generating SDK for ${name}`); | ||
| const outputDir = `${TARGET_DIRECTORY}/${directoryName}`; | ||
| generateFromOas.generateFromOas({ | ||
| inputSpec: `${filepath}`, | ||
| outputDir: `${outputDir}`, | ||
| templateDir: `${TEMPLATE_DIRECTORY}`, | ||
| flags: `--reserved-words-mappings delete=delete`, | ||
| }); | ||
| } catch (error) { | ||
| // eslint-disable-next-line @typescript-eslint/restrict-template-expressions | ||
| console.error(`Error generating SDK for ${name}: ${error}`); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Generates the top level index file | ||
| */ | ||
| export function generateIndex(context: { | ||
| children: ApiSpecDetail[] | { name: string; apiName: string }[]; | ||
| }): void { | ||
| const indexTemplate = fs.readFileSync(INDEX_TEMPLATE_LOCATION, "utf8"); | ||
| const generatedIndex = Handlebars.compile(indexTemplate)(context); | ||
| fs.writeFileSync(`${TARGET_DIRECTORY}/index.ts`, generatedIndex); | ||
| } | ||
|
|
||
| /** | ||
| * Generates the version file | ||
| */ | ||
| export function generateVersionFile(): void { | ||
| const version = process.env.PACKAGE_VERSION || 'unknown'; | ||
| const versionTemplate = fs.readFileSync(VERSION_TEMPLATE_LOCATION, 'utf8'); | ||
| const generatedVersion = Handlebars.compile(versionTemplate)({ | ||
| metadata: {sdkVersion: version}, | ||
| }); | ||
| fs.writeFileSync(`${TARGET_DIRECTORY}/version.ts`, generatedVersion); | ||
| } | ||
|
|
||
|
|
||
| function getAllDirectoriesWithExchangeFiles(basePath: string, relativePath = ""): string[] { | ||
| const fullPath = path.join(basePath, relativePath); | ||
| const directories: string[] = []; | ||
|
|
||
| try { | ||
| const items = fs.readdirSync(fullPath); | ||
|
|
||
| for (const item of items) { | ||
| const itemPath = path.join(fullPath, item); | ||
| const relativeItemPath = relativePath | ||
| ? path.join(relativePath, item) | ||
| : item; | ||
|
|
||
| if (fs.lstatSync(itemPath).isDirectory()) { | ||
| if (fs.existsSync(path.join(itemPath, "exchange.json"))) { | ||
| directories.push(relativeItemPath); | ||
| } | ||
| directories.push(...getAllDirectoriesWithExchangeFiles(basePath, relativeItemPath)); | ||
| } | ||
| } | ||
| } catch (error) { | ||
| console.warn(`Warning: Could not read directory ${fullPath}:`, error); | ||
| } | ||
|
|
||
| return directories; | ||
| } | ||
|
|
||
| /** | ||
| * Copies the static files to the target directory | ||
| */ | ||
| export function copyStaticFiles(): void { | ||
| const skipTestFiles = (src: string): boolean => !/\.test\.[a-z]+$/.test(src); | ||
| fs.copySync(STATIC_DIRECTORY, TARGET_DIRECTORY, { filter: skipTestFiles }); | ||
| } | ||
|
|
||
| /** | ||
| * Main function | ||
| */ | ||
| export function main(): void { | ||
| console.log("Starting OAS generation script"); | ||
| fs.readdir(API_DIRECTORY, (err: Error, directories: string[]) => { | ||
| if (err) { | ||
| console.error("Error reading api directory:", err); | ||
| return; | ||
| } | ||
|
|
||
| const apiSpecDetails: ApiSpecDetail[] = []; | ||
| const subDirectories: string[] = getAllDirectoriesWithExchangeFiles(API_DIRECTORY); | ||
|
|
||
| subDirectories.forEach((directory: string) => { | ||
| try { | ||
| const details = getAPIDetailsFromExchange( | ||
| path.join(API_DIRECTORY, directory) | ||
| ); | ||
| apiSpecDetails.push(details); | ||
| } catch (error) { | ||
| console.log(`Skipping directory ${directory}: ${error}`); | ||
| } | ||
| }); | ||
|
|
||
| apiSpecDetails.forEach((apiSpecDetail: ApiSpecDetail) => { | ||
| generateSDKs(apiSpecDetail); | ||
| }); | ||
|
|
||
| generateIndex({ children: apiSpecDetails }); | ||
| generateVersionFile(); | ||
| copyStaticFiles(); | ||
|
|
||
| console.log( | ||
| `OAS generation script completed. Files outputted to ${TARGET_DIRECTORY}` | ||
| ); | ||
| }); | ||
| } | ||
|
|
||
| main(); | ||
This file was deleted.
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.
Removing tsdoc because it is not compatible with how we have elected to define object properties (ie. @param options.someProperty is valid jsdoc but invalid tsdoc) and tsdoc currently has no straightforward mechanism for toggling applied rules.