diff --git a/.changeset/quiet-ghosts-itch.md b/.changeset/quiet-ghosts-itch.md new file mode 100644 index 000000000..871f858e6 --- /dev/null +++ b/.changeset/quiet-ghosts-itch.md @@ -0,0 +1,15 @@ +--- +"@shopware/api-gen": minor +--- + +Added `apiType` option in `loadSchema` command. With `SHOPWARE_ADMIN_USERNAME` and `SHOPWARE_ADMIN_PASSWORD` env variables we can now authorize Admin API schema. + +example: + +```bash +# load schema from store API +pnpx @shopware/api-gen loadSchema --apiType=store --filename=storeApiSchema.json + +# load schema from admin API +pnpx @shopware/api-gen loadSchema --apiType=admin --filename=adminApiSchema.json +``` diff --git a/packages/api-client-next/package.json b/packages/api-client-next/package.json index 729f9a07f..66ab4829e 100644 --- a/packages/api-client-next/package.json +++ b/packages/api-client-next/package.json @@ -42,7 +42,6 @@ "build": "export NODE_ENV=production && unbuild && pnpm build:types", "build:types": "tsc ./src/*.ts --declaration --allowJs --emitDeclarationOnly --outDir ./temp --skipLibCheck", "dev": "export NODE_ENV=development && unbuild --stub", - "generate": "esno ../api-gen/src/cli.ts generate", "lint": "eslint src/**/*.ts* --fix --max-warnings=0 && pnpm run typecheck", "typecheck": "tsc --noEmit", "test": "vitest run --typecheck", @@ -51,7 +50,6 @@ }, "devDependencies": { "@codspeed/vitest-plugin": "^2.3.1", - "@shopware/api-gen": "workspace:*", "@types/prettier": "^3.0.0", "@vitest/coverage-v8": "^1.2.2", "eslint-config-shopware": "workspace:*", diff --git a/packages/api-gen/README.md b/packages/api-gen/README.md index 6b787f737..a574aff69 100644 --- a/packages/api-gen/README.md +++ b/packages/api-gen/README.md @@ -19,12 +19,18 @@ npx i @shopware/api-gen [command] ### `generate` -Transform OpenAPI specification from JSON file to Typescript schemas. +Transform OpenAPI specification from JSON file to Typescript schemas. Use `loadSchema` command first. options: ```bash pnpx @shopware/api-gen generate --help + +# generate schemas from store API +pnpx @shopware/api-gen generate --filename=storeApiSchema.json + +# generate schemas from admin API +pnpx @shopware/api-gen generate --filename=adminApiSchema.json ``` ### `loadSchema` @@ -35,6 +41,21 @@ options: ```bash pnpx @shopware/api-gen loadSchema --help + +# load schema from store API +pnpx @shopware/api-gen loadSchema --apiType=store --filename=storeApiSchema.json + +# load schema from admin API +pnpx @shopware/api-gen loadSchema --apiType=admin --filename=adminApiSchema.json +``` + +Remember to add `.env` file in order to authenticate with Shopware instance. + +```bash +OPENAPI_JSON_URL="https://your-shop-instance.shopware.store" +OPENAPI_ACCESS_KEY="YOUR_STORE_API_ACCESS_KEY" +SHOPWARE_ADMIN_USERNAME="my@username.com" +SHOPWARE_ADMIN_PASSWORD="my-password" ``` diff --git a/packages/api-gen/package.json b/packages/api-gen/package.json index 1ccb8b0a1..319694c71 100644 --- a/packages/api-gen/package.json +++ b/packages/api-gen/package.json @@ -47,6 +47,7 @@ "vitest": "^1.2.2" }, "dependencies": { + "@shopware/api-client": "workspace:*", "ofetch": "^1.3.3", "openapi-typescript": "^6.7.4", "prettier": "^3.2.4", diff --git a/packages/api-gen/src/cli.ts b/packages/api-gen/src/cli.ts index 4226dbfd0..c896c82fe 100644 --- a/packages/api-gen/src/cli.ts +++ b/packages/api-gen/src/cli.ts @@ -21,7 +21,7 @@ function commonOptions(args: Argv): Argv { // eslint-disable-next-line no-unused-expressions yargs(hideBin(process.argv)) - .scriptName("client") + .scriptName("@shopware/api-gen") .usage("$0 [args]") .command( "generate", @@ -42,6 +42,12 @@ yargs(hideBin(process.argv)) "Load JSON schema from your API instance. You need to have proper .env file", (args) => { return commonOptions(args) + .option("apiType", { + describe: + "Type of the API schema to load. It can be 'store' or 'admin'", + default: "store", + choices: ["store", "admin"], + }) .positional("filename", { type: "string", default: "apiSchema.json", diff --git a/packages/api-gen/src/commands/loadSchema.ts b/packages/api-gen/src/commands/loadSchema.ts index da79e51ba..c3563a4cd 100644 --- a/packages/api-gen/src/commands/loadSchema.ts +++ b/packages/api-gen/src/commands/loadSchema.ts @@ -2,28 +2,98 @@ import { writeFileSync } from "node:fs"; import * as dotenv from "dotenv"; import c from "picocolors"; import { format } from "prettier"; +import { createAdminAPIClient, createAPIClient } from "@shopware/api-client"; +import type { + operations as adminOperations, + operationPaths as adminOperationPaths, +} from "@shopware/api-client/admin-api-types"; +// TODO: change adminOperations to storeOperations after fixing NEXT-33506 +// import type { +// operations, +// operationPaths, +// } from "@shopware/api-client/api-types"; const config = dotenv.config().parsed || {}; -export async function loadSchema(args: { cwd: string; filename: string }) { +const SCHEMA_ENDPOINT = "_info/openapi3.json"; +const STORE_API_ENDPOINT = `/store-api/${SCHEMA_ENDPOINT}`; +const ADMIN_API_ENDPOINT = `/api/${SCHEMA_ENDPOINT}`; + +export async function loadSchema(args: { + cwd: string; + filename: string; + apiType: string; +}) { + if (!["store", "admin"].includes(args.apiType)) { + console.error( + c.red(`Invalid "apiType" argument. It should be "store" or "admin"`), + ); + process.exit(1); + } + const isAdminApi = args.apiType === "admin"; + + const requiredEnvVars = ["OPENAPI_JSON_URL"]; + if (isAdminApi) { + requiredEnvVars.push("SHOPWARE_ADMIN_USERNAME"); + requiredEnvVars.push("SHOPWARE_ADMIN_PASSWORD"); + } else { + requiredEnvVars.push("OPENAPI_ACCESS_KEY"); + } + + const missingEnvVars = requiredEnvVars.filter((envVar) => !config[envVar]); try { - if (!config.OPENAPI_JSON_URL || !config.OPENAPI_ACCESS_KEY) { + if (missingEnvVars.length) { console.error( c.red( - `Missing ${c.bold("OPENAPI_JSON_URL")} or ${c.bold( - "OPENAPI_ACCESS_KEY", - )} env variables.\n\nCheck whether the .env file is created.\n`, + `Missing ${c.bold(missingEnvVars.join(","))} env variables.\n\nCheck whether the .env file is created.\n`, ), ); process.exit(1); } - const apiJSON = await fetch(config.OPENAPI_JSON_URL, { - headers: { - "sw-access-key": config.OPENAPI_ACCESS_KEY, - Authorization: config.OPENAPI_ACCESS_KEY, - }, - }).then((res) => res.json()); + const configUrl = config.OPENAPI_JSON_URL.replace( + "/api/_info/openapi3.json", + "", + ).replace("/atore-api/_info/openapi3.json", ""); + + const downloadUrl = + configUrl + (isAdminApi ? ADMIN_API_ENDPOINT : STORE_API_ENDPOINT); + + let apiJSON; + + if (isAdminApi) { + const adminClient = createAdminAPIClient< + adminOperations, + adminOperationPaths + >({ + baseURL: `${configUrl}/api`, + credentials: { + grant_type: "password", + client_id: "administration", + scopes: "write", + username: config.SHOPWARE_ADMIN_USERNAME, + password: config.SHOPWARE_ADMIN_PASSWORD, + }, + }); + apiJSON = await adminClient.invoke( + "api-info get /_info/openapi3.json?type", + { + type: "json", + }, + ); + } else { + // TODO: change adminOperations to storeOperations after fixing NEXT-33506 + const apiClient = createAPIClient({ + baseURL: `${configUrl}/store-api`, + accessToken: config.OPENAPI_ACCESS_KEY, + }); + apiJSON = await apiClient.invoke( + "api-info get /_info/openapi3.json?type", + { + type: "json", + }, + ); + } const formatted = await format(JSON.stringify(apiJSON), { semi: false, @@ -40,7 +110,7 @@ export async function loadSchema(args: { cwd: string; filename: string }) { console.log( c.green( `Schema file loaded from ${c.bold( - config.OPENAPI_JSON_URL, + downloadUrl, )} and saved to ${c.bold(args.filename)}`, ), ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eeecb7b4f..a2744b401 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -576,9 +576,6 @@ importers: '@codspeed/vitest-plugin': specifier: ^2.3.1 version: 2.3.1(vitest@1.2.2) - '@shopware/api-gen': - specifier: workspace:* - version: link:../api-gen '@types/prettier': specifier: ^3.0.0 version: 3.0.0 @@ -603,6 +600,9 @@ importers: packages/api-gen: dependencies: + '@shopware/api-client': + specifier: workspace:* + version: link:../api-client-next ofetch: specifier: ^1.3.3 version: 1.3.3 @@ -2217,7 +2217,7 @@ packages: /@codspeed/vitest-plugin@2.3.1(vitest@1.2.2): resolution: {integrity: sha512-/e4G2B/onX/hG/EjUU/NpDxnIryeTDamVRTBeWfgQDoex3g7GDzTwoQktaU5l/Asw3ZjEErQg+oQVToQ6jYZlA==} peerDependencies: - vite: ^4.5.1 + vite: ^5.0.12 vitest: '>=1.0.0-beta.4 || >=1' peerDependenciesMeta: vite: @@ -4354,7 +4354,6 @@ packages: - vls - vti - vue-tsc - dev: true /@nuxt/vite-builder@3.9.3(@types/node@20.8.6)(typescript@5.3.3)(vue@3.4.15): resolution: {integrity: sha512-HruOrxn0g6TS31j3jycJvGZ7pt3JNEbcXNByVh7YJwQx6ToFX8kPWRu4LPeMhrLYvZzeUr2w3iELBECFxbDmvw==} @@ -7798,7 +7797,7 @@ packages: '@vueuse/core': 10.7.2(vue@3.4.15) '@vueuse/metadata': 10.7.2 local-pkg: 0.5.0 - nuxt: 3.9.3(@types/node@20.11.5)(eslint@8.56.0)(typescript@5.3.3) + nuxt: 3.9.3(@types/node@20.8.6)(typescript@5.3.3)(vue-tsc@1.8.27) vue-demi: 0.14.6(vue@3.4.15) transitivePeerDependencies: - '@vue/composition-api' @@ -14632,7 +14631,6 @@ packages: - vti - vue-tsc - xml2js - dev: true /nypm@0.3.4: resolution: {integrity: sha512-1JLkp/zHBrkS3pZ692IqOaIKSYHmQXgqfELk6YTOfVBnwealAmPA1q2kKK7PHJAHSMBozerThEFZXP3G6o7Ukg==} @@ -18546,7 +18544,6 @@ packages: - sugarss - supports-color - terser - dev: true /vite-node@1.2.2(@types/node@20.11.5): resolution: {integrity: sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==} @@ -18698,7 +18695,6 @@ packages: vscode-languageserver-textdocument: 1.0.8 vscode-uri: 3.0.7 vue-tsc: 1.8.27(typescript@5.3.3) - dev: true /vite-plugin-inspect@0.8.1(@nuxt/kit@3.9.3): resolution: {integrity: sha512-oPBPVGp6tBd5KdY/qY6lrbLXqrbHRG0hZLvEaJfiZ/GQfDB+szRuLHblQh1oi1Hhh8GeLit/50l4xfs2SA+TCA==}