feat(oas): medusa-oas-cli as OAS build tool (#3213)
## What Introduce a CLI for extracting OAS from the core `medusa` package. ## Why We need to decouple OAS tooling from documentation tooling in order to allow packages and external systems to leverage our OAS has a dependency. ## How Introduce a new OAS workspace within packages in order to organize current and future OAS related package. Only 1 OAS package for now. Introduce a new CLI only package to act as the main gateway for all upcoming OAS tooling. Only 1 command for now. Update documentation tooling pertaining to OAS to use the CLI instead. ## Test ### Prerequisite From the monorepo root: * `yarn install` * `yarn build` ### Documentation #### Case - validation only - success * Run `yarn openapi:generate --dry-run` * Expect console output `🟢 Valid OAS` but no mention of `🔵 Exported OAS` #### Case - validation only - invalid * Introduce a bug by renaming `@schema Cart` to `@schema Kart` in [models/cart.ts](https://github.com/medusajs/medusa/blob/0adb0d9ff96087613b7a634cbc97a7e301b01121/packages/medusa/src/models/cart.ts#L2) * Run `yarn build` to update `@medusajs/medusa` package with the bug. * Run `yarn openapi:generate --dry-run` * Expect console output `🔴 Invalid OAS` with a stack trace of the issue. #### Case - docs generation * Run `yarn openapi:generate` * Expect `docs/api/` directory to contain: * `admin.oas.json` (raw OAS) * `store.oas.json` (raw OAS) * `admin.oas.yaml` (sanitized OAS) * `store.oas.yaml` (sanitized OAS) * `admin/` (updated redocly split output) * `store/` (updated redocly split output) ### CLI #### Case - crawl additional paths * From a local medusa server (`medusa-starter-default`), add an `index.ts` file in `src/models/` * In the `index.ts`, add dummy OAS JSDoc like `/** @schema Foobar */` * From the root of the monorepo, run `yarn medusa-oas --type store --paths path-to-medusa-server/src` * Expect a `store.oas.json` to be created at the root of the monorepo. * The `store.oas.json` should contain an additional `Foobar` entry in `components.schemas`.
This commit is contained in:
1 parent
1bfbe27b9b
commit
8137061908
20 files changed
+613
-82708
No files matched your search
@@ -0,0 +1,342 @@
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
version: 1.0.0
|
||||
title: Medusa Admin API
|
||||
description: |
|
||||
API reference for Medusa's Admin endpoints. All endpoints are prefixed with `/admin`.
|
||||
|
||||
## Authentication
|
||||
|
||||
There are two ways to send authenticated requests to the Medusa server: Using a user's API token, or using a Cookie Session ID.
|
||||
|
||||
<!-- ReDoc-Inject: <SecurityDefinitions> -->
|
||||
|
||||
## Expanding Fields
|
||||
|
||||
In many endpoints you'll find an `expand` query parameter that can be passed to the endpoint. You can use the `expand` query parameter to unpack an entity's relations and return them in the response.
|
||||
|
||||
For example, when you list customers you can also retrieve their groups by passing to the `expand` query parameter the value `groups`.
|
||||
|
||||
You can expand more than one relation by separating the relations in the `expand` query parameter with a comma. For example, to retrieve both the orders and the groups of a customer, pass to the `expand` query parameter the value `groups,orders`.
|
||||
|
||||
Please note that the parameters you pass to `expand` replace any relations that are expanded by default.
|
||||
|
||||
## Selecting Fields
|
||||
|
||||
In many endpoints you'll find a `fields` query parameter that can be passed to the endpoint. You can use the `fields` query parameter to specify which fields in the entity should be returned in the response.
|
||||
|
||||
You can pass more than one field by seperating the field names in the `fields` query parameter with a comma.
|
||||
|
||||
Only the fields you pass to `field` will be retrieved and returned in the response. Any fields that are returned by default will not be returned in this case. This does not affect relations.
|
||||
|
||||
For example, to only select the `title` and `description` fields of a product, pass to the `fields` query parameter the value `title,description`.
|
||||
|
||||
## Pagination
|
||||
|
||||
### Query Parameters
|
||||
|
||||
In listing endpoints, such as list customers or list products, you can control the pagination using the query parameters `limit` and `offset`.
|
||||
|
||||
`limit` is used to specify the maximum number of items that can be return in the response. `offset` is used to specify how many items to skip before returning the resulting entities.
|
||||
|
||||
You can use the `offset` query parameter to change between pages. For example, if the limit is 50, at page 1 the offset should be 0; at page 2 the offset should be 50, and so on.
|
||||
|
||||
### Response Fields
|
||||
|
||||
In listing fields, aside from the entities retrieved, there are three pagination-related fields returned: `count`, `limit`, and `offset`.
|
||||
|
||||
Similarly to the query parameters, `limit` is the maximum number of items that can be returned in the response, and `field` is the number of items that were skipped before the entities in the result.
|
||||
|
||||
`count` is the total number of available items of this entity. It can be used to determine how many pages are there.
|
||||
|
||||
For example, if the `count` is 100 and the `limit` is 50, you can divide the `count` by the `limit` to get the number of pages: `100/50 = 2 pages`.
|
||||
|
||||
license:
|
||||
name: MIT
|
||||
url: https://github.com/medusajs/medusa/blob/master/LICENSE
|
||||
tags:
|
||||
- name: Auth
|
||||
description: Auth endpoints that allow authorization of admin Users and manages
|
||||
their sessions.
|
||||
- name: App
|
||||
description: App endpoints that allow handling apps in Medusa.
|
||||
- name: Batch Job
|
||||
description: Batch Job endpoints that allow handling batch jobs in Medusa.
|
||||
- name: Claim
|
||||
description: Claim endpoints that allow handling claims in Medusa.
|
||||
- name: Collection
|
||||
description: Collection endpoints that allow handling collections in Medusa.
|
||||
- name: Customer
|
||||
description: Customer endpoints that allow handling customers in Medusa.
|
||||
- name: Customer Group
|
||||
description: Customer Group endpoints that allow handling customer groups in Medusa.
|
||||
- name: Discount
|
||||
description: Discount endpoints that allow handling discounts in Medusa.
|
||||
- name: Discount Condition
|
||||
description: Discount Condition endpoints that allow handling discount conditions
|
||||
in Medusa.
|
||||
- name: Draft Order
|
||||
description: Draft Order endpoints that allow handling draft orders in Medusa.
|
||||
- name: Gift Card
|
||||
description: Gift Card endpoints that allow handling gift cards in Medusa.
|
||||
- name: Invite
|
||||
description: Invite endpoints that allow handling invites in Medusa.
|
||||
- name: Note
|
||||
description: Note endpoints that allow handling notes in Medusa.
|
||||
- name: Notification
|
||||
description: Notification endpoints that allow handling notifications in Medusa.
|
||||
- name: Order
|
||||
description: Order endpoints that allow handling orders in Medusa.
|
||||
- name: Price List
|
||||
description: Price List endpoints that allow handling price lists in Medusa.
|
||||
- name: Product
|
||||
description: Product endpoints that allow handling products in Medusa.
|
||||
- name: Product Tag
|
||||
description: Product Tag endpoints that allow handling product tags in Medusa.
|
||||
- name: Product Type
|
||||
description: Product Types endpoints that allow handling product types in Medusa.
|
||||
- name: Product Variant
|
||||
description: Product Variant endpoints that allow handling product variants in Medusa.
|
||||
- name: Region
|
||||
description: Region endpoints that allow handling regions in Medusa.
|
||||
- name: Return Reason
|
||||
description: Return Reason endpoints that allow handling return reasons in Medusa.
|
||||
- name: Return
|
||||
description: Return endpoints that allow handling returns in Medusa.
|
||||
- name: Sales Channel
|
||||
description: Sales Channel endpoints that allow handling sales channels in Medusa.
|
||||
- name: Shipping Option
|
||||
description: Shipping Option endpoints that allow handling shipping options in Medusa.
|
||||
- name: Shipping Profile
|
||||
description: Shipping Profile endpoints that allow handling shipping profiles in
|
||||
Medusa.
|
||||
- name: Store
|
||||
description: Store endpoints that allow handling stores in Medusa.
|
||||
- name: Swap
|
||||
description: Swap endpoints that allow handling swaps in Medusa.
|
||||
- name: Tax Rate
|
||||
description: Tax Rate endpoints that allow handling tax rates in Medusa.
|
||||
- name: Upload
|
||||
description: Upload endpoints that allow handling uploads in Medusa.
|
||||
- name: User
|
||||
description: User endpoints that allow handling users in Medusa.
|
||||
servers:
|
||||
- url: https://api.medusa-commerce.com/admin
|
||||
paths: { }
|
||||
components:
|
||||
responses:
|
||||
default_error:
|
||||
description: Default Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
code: "unknown_error"
|
||||
message: "An unknown error occurred."
|
||||
type: "unknown_error"
|
||||
invalid_state_error:
|
||||
description: Invalid State Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
code: "unknown_error"
|
||||
message: "The request conflicted with another request. You may retry the request with the provided Idempotency-Key."
|
||||
type: "QueryRunnerAlreadyReleasedError"
|
||||
invalid_request_error:
|
||||
description: Invalid Request Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
code: "invalid_request_error"
|
||||
message: "Discount with code TEST already exists."
|
||||
type: "duplicate_error"
|
||||
not_found_error:
|
||||
description: Not Found Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
message: "Entity with id 1 was not found"
|
||||
type: "not_found"
|
||||
400_error:
|
||||
description: Client Error or Multiple Errors
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/Error"
|
||||
- $ref: "#/components/schemas/MultipleErrors"
|
||||
examples:
|
||||
not_allowed:
|
||||
$ref: "#/components/examples/not_allowed_error"
|
||||
invalid_data:
|
||||
$ref: "#/components/examples/invalid_data_error"
|
||||
MultipleErrors:
|
||||
$ref: "#/components/examples/multiple_errors"
|
||||
500_error:
|
||||
description: Server Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
examples:
|
||||
database:
|
||||
$ref: "#/components/examples/database_error"
|
||||
unexpected_state:
|
||||
$ref: "#/components/examples/unexpected_state_error"
|
||||
invalid_argument:
|
||||
$ref: "#/components/examples/invalid_argument_error"
|
||||
default_error:
|
||||
$ref: "#/components/examples/default_error"
|
||||
unauthorized:
|
||||
description: 'User is not authorized. Must log in first'
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
default: Unauthorized
|
||||
example: Unauthorized
|
||||
incorrect_credentials:
|
||||
description: 'User does not exist or incorrect credentials'
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
default: Unauthorized
|
||||
example: Unauthorized
|
||||
examples:
|
||||
not_allowed_error:
|
||||
summary: Not Allowed Error
|
||||
value:
|
||||
message: "Discount must be set to dynamic"
|
||||
type: "not_allowed"
|
||||
invalid_data_error:
|
||||
summary: Invalid Data Error
|
||||
value:
|
||||
message: "first_name must be a string"
|
||||
type: "invalid_data"
|
||||
multiple_errors:
|
||||
summary: Multiple Errors
|
||||
value:
|
||||
message: "Provided request body contains errors. Please check the data and retry the request"
|
||||
errors:
|
||||
- message: "first_name must be a string"
|
||||
type: "invalid_data"
|
||||
- message: "Discount must be set to dynamic"
|
||||
type: "not_allowed"
|
||||
database_error:
|
||||
summary: Database Error
|
||||
value:
|
||||
code: "api_error"
|
||||
message: "An error occured while hashing password"
|
||||
type: "database_error"
|
||||
unexpected_state_error:
|
||||
summary: Unexpected State Error
|
||||
value:
|
||||
message: "cart.total must be defined"
|
||||
type: "unexpected_state"
|
||||
invalid_argument_error:
|
||||
summary: Invalid Argument Error
|
||||
value:
|
||||
message: "cart.total must be defined"
|
||||
type: "unexpected_state"
|
||||
default_error:
|
||||
summary: Default Error
|
||||
value:
|
||||
code: "unknown_error"
|
||||
message: "An unknown error occurred."
|
||||
type: "unknown_error"
|
||||
securitySchemes:
|
||||
api_token:
|
||||
type: http
|
||||
x-displayName: API Token
|
||||
description: |
|
||||
Use a user's API Token to send authenticated requests.
|
||||
|
||||
### How to Add API Token to a User
|
||||
|
||||
At the moment, there's no direct way of adding an API Token for a user. The only way it can be done is through directly editing the database.
|
||||
|
||||
If you're using a PostgreSQL database, you can run the following commands in your command line to add API token:
|
||||
|
||||
```bash
|
||||
psql -d <DB_NAME> -U <DB_USER>
|
||||
UPDATE public.user SET api_token='<API_TOKEN>' WHERE email='<USER_EMAIL>';
|
||||
```
|
||||
|
||||
Where:
|
||||
- `<DB_NAME>` is the name of the database schema you use for the Medusa server.
|
||||
- `<DB_USER>` is the name of the user that has privileges over the database schema.
|
||||
- `<API_TOKEN>` is the API token you want to associate with the user. You can use [this tool to generate a random token](https://randomkeygen.com/).
|
||||
- `<USER_EMAIL>` is the email address of the admin user you want to have this API token.
|
||||
|
||||
### How to Use the API Token
|
||||
|
||||
The API token can be used for Bearer Authentication. It's passed in the `Authorization` header as the following:
|
||||
|
||||
```
|
||||
Authorization: Bearer {api_token}
|
||||
```
|
||||
|
||||
In this API reference, you'll find in the cURL request samples the use of `{api_token}`. This is where you must pass the API token.
|
||||
|
||||
If you're following along with the JS Client request samples, you must provide the `apiKey` option when creating the Medusa client:
|
||||
|
||||
```ts
|
||||
const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3, apiKey: '{api_token}' })
|
||||
```
|
||||
|
||||
If you're using Medusa React, you can pass the `apiKey` prop to `MedusaProvider`:
|
||||
|
||||
```tsx
|
||||
<MedusaProvider
|
||||
apiKey='api_token}'
|
||||
//...
|
||||
>
|
||||
```
|
||||
scheme: bearer
|
||||
cookie_auth:
|
||||
type: apiKey
|
||||
in: cookie
|
||||
name: connect.sid
|
||||
x-displayName: Cookie Session ID
|
||||
description: |
|
||||
Use a cookie session to send authenticated requests.
|
||||
|
||||
### How to Obtain the Cookie Session
|
||||
|
||||
If you're sending requests through a browser, using JS Client, or using tools like Postman, the cookie session should be automatically set when the admin user is logged in.
|
||||
|
||||
If you're sending requests using cURL, you must set the Session ID in the cookie manually.
|
||||
|
||||
To do that, send a request to [authenticate the user](#tag/Auth/operation/PostAuth) and pass the cURL option `-v`:
|
||||
|
||||
```bash
|
||||
curl -v --location --request POST 'https://medusa-url.com/admin/auth' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"email": "user@example.com",
|
||||
"password": "supersecret"
|
||||
}'
|
||||
```
|
||||
|
||||
The headers will be logged in the terminal as well as the response. You should find in the headers a Cookie header similar to this:
|
||||
|
||||
```bash
|
||||
Set-Cookie: connect.sid=s%3A2Bu8BkaP9JUfHu9rG59G16Ma0QZf6Gj1.WT549XqX37PN8n0OecqnMCq798eLjZC5IT7yiDCBHPM;
|
||||
```
|
||||
|
||||
Copy the value after `connect.sid` (without the `;` at the end) and pass it as a cookie in subsequent requests as the following:
|
||||
|
||||
```bash
|
||||
curl --location --request GET 'https://medusa-url.com/admin/products' \
|
||||
--header 'Cookie: connect.sid={sid}'
|
||||
```
|
||||
|
||||
Where `{sid}` is the value of `connect.sid` that you copied.
|
||||
@@ -0,0 +1,256 @@
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
version: 1.0.0
|
||||
title: Medusa Storefront API
|
||||
description: |
|
||||
API reference for Medusa's Storefront endpoints. All endpoints are prefixed with `/store`.
|
||||
|
||||
## Authentication
|
||||
|
||||
To send requests as an authenticated customer, you must use the Cookie Session ID.
|
||||
|
||||
<!-- ReDoc-Inject: <SecurityDefinitions> -->
|
||||
|
||||
## Expanding Fields
|
||||
|
||||
In many endpoints you'll find an `expand` query parameter that can be passed to the endpoint. You can use the `expand` query parameter to unpack an entity's relations and return them in the response.
|
||||
|
||||
For example, when you list customers you can also retrieve their groups by passing to the `expand` query parameter the value `groups`.
|
||||
|
||||
You can expand more than one relation by separating the relations in the `expand` query parameter with a comma. For example, to retrieve both the orders and the groups of a customer, pass to the `expand` query parameter the value `groups,orders`.
|
||||
|
||||
Please note that the parameters you pass to `expand` replace any relations that are expanded by default.
|
||||
|
||||
## Selecting Fields
|
||||
|
||||
In many endpoints you'll find a `fields` query parameter that can be passed to the endpoint. You can use the `fields` query parameter to specify which fields in the entity should be returned in the response.
|
||||
|
||||
You can pass more than one field by seperating the field names in the `fields` query parameter with a comma.
|
||||
|
||||
Only the fields you pass to `field` will be retrieved and returned in the response. Any fields that are returned by default will not be returned in this case. This does not affect relations.
|
||||
|
||||
For example, to only select the `title` and `description` fields of a product, pass to the `fields` query parameter the value `title,description`.
|
||||
|
||||
## Pagination
|
||||
|
||||
### Query Parameters
|
||||
|
||||
In listing endpoints, such as list customers or list products, you can control the pagination using the query parameters `limit` and `offset`.
|
||||
|
||||
`limit` is used to specify the maximum number of items that can be return in the response. `offset` is used to specify how many items to skip before returning the resulting entities.
|
||||
|
||||
You can use the `offset` query parameter to change between pages. For example, if the limit is 50, at page 1 the offset should be 0; at page 2 the offset should be 50, and so on.
|
||||
|
||||
### Response Fields
|
||||
|
||||
In listing fields, aside from the entities retrieved, there are three pagination-related fields returned: `count`, `limit`, and `offset`.
|
||||
|
||||
Similarly to the query parameters, `limit` is the maximum number of items that can be returned in the response, and `field` is the number of items that were skipped before the entities in the result.
|
||||
|
||||
`count` is the total number of available items of this entity. It can be used to determine how many pages are there.
|
||||
|
||||
For example, if the `count` is 100 and the `limit` is 50, you can divide the `count` by the `limit` to get the number of pages: `100/50 = 2 pages`.
|
||||
|
||||
license:
|
||||
name: MIT
|
||||
url: https://github.com/medusajs/medusa/blob/master/LICENSE
|
||||
tags:
|
||||
- name: Auth
|
||||
description: Auth endpoints that allow authorization of customers and manages their
|
||||
sessions.
|
||||
- name: Cart
|
||||
description: Cart endpoints that allow handling carts in Medusa.
|
||||
- name: Collection
|
||||
description: Collection endpoints that allow handling collections in Medusa.
|
||||
- name: Customer
|
||||
description: Customer endpoints that allow handling customers in Medusa.
|
||||
- name: Gift Card
|
||||
description: Gift Card endpoints that allow handling gift cards in Medusa.
|
||||
- name: Order
|
||||
description: Order endpoints that allow handling orders in Medusa.
|
||||
- name: Product
|
||||
description: Product endpoints that allow handling products in Medusa.
|
||||
- name: Product Variant
|
||||
description: Product Variant endpoints that allow handling product variants in Medusa.
|
||||
- name: Region
|
||||
description: Region endpoints that allow handling regions in Medusa.
|
||||
- name: Return Reason
|
||||
description: Return Reason endpoints that allow handling return reasons in Medusa.
|
||||
- name: Return
|
||||
description: Return endpoints that allow handling returns in Medusa.
|
||||
- name: Shipping Option
|
||||
description: Shipping Option endpoints that allow handling shipping options in Medusa.
|
||||
- name: Swap
|
||||
description: Swap endpoints that allow handling swaps in Medusa.
|
||||
servers:
|
||||
- url: https://api.medusa-commerce.com/store
|
||||
paths: { }
|
||||
components:
|
||||
responses:
|
||||
default_error:
|
||||
description: Default Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
code: "unknown_error"
|
||||
message: "An unknown error occurred."
|
||||
type: "unknown_error"
|
||||
invalid_state_error:
|
||||
description: Invalid State Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
code: "unknown_error"
|
||||
message: "The request conflicted with another request. You may retry the request with the provided Idempotency-Key."
|
||||
type: "QueryRunnerAlreadyReleasedError"
|
||||
invalid_request_error:
|
||||
description: Invalid Request Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
code: "invalid_request_error"
|
||||
message: "Discount with code TEST already exists."
|
||||
type: "duplicate_error"
|
||||
not_found_error:
|
||||
description: Not Found Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
message: "Entity with id 1 was not found"
|
||||
type: "not_found"
|
||||
400_error:
|
||||
description: Client Error or Multiple Errors
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/Error"
|
||||
- $ref: "#/components/schemas/MultipleErrors"
|
||||
examples:
|
||||
not_allowed:
|
||||
$ref: "#/components/examples/not_allowed_error"
|
||||
invalid_data:
|
||||
$ref: "#/components/examples/invalid_data_error"
|
||||
MultipleErrors:
|
||||
$ref: "#/components/examples/multiple_errors"
|
||||
500_error:
|
||||
description: Server Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
examples:
|
||||
database:
|
||||
$ref: "#/components/examples/database_error"
|
||||
unexpected_state:
|
||||
$ref: "#/components/examples/unexpected_state_error"
|
||||
invalid_argument:
|
||||
$ref: "#/components/examples/invalid_argument_error"
|
||||
default_error:
|
||||
$ref: "#/components/examples/default_error"
|
||||
unauthorized:
|
||||
description: 'User is not authorized. Must log in first'
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
default: Unauthorized
|
||||
example: Unauthorized
|
||||
incorrect_credentials:
|
||||
description: 'User does not exist or incorrect credentials'
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
default: Unauthorized
|
||||
example: Unauthorized
|
||||
examples:
|
||||
not_allowed_error:
|
||||
summary: Not Allowed Error
|
||||
value:
|
||||
message: "Discount must be set to dynamic"
|
||||
type: "not_allowed"
|
||||
invalid_data_error:
|
||||
summary: Invalid Data Error
|
||||
value:
|
||||
message: "first_name must be a string"
|
||||
type: "invalid_data"
|
||||
multiple_errors:
|
||||
summary: Multiple Errors
|
||||
value:
|
||||
message: "Provided request body contains errors. Please check the data and retry the request"
|
||||
errors:
|
||||
- message: "first_name must be a string"
|
||||
type: "invalid_data"
|
||||
- message: "Discount must be set to dynamic"
|
||||
type: "not_allowed"
|
||||
database_error:
|
||||
summary: Database Error
|
||||
value:
|
||||
code: "api_error"
|
||||
message: "An error occured while hashing password"
|
||||
type: "database_error"
|
||||
unexpected_state_error:
|
||||
summary: Unexpected State Error
|
||||
value:
|
||||
message: "cart.total must be defined"
|
||||
type: "unexpected_state"
|
||||
invalid_argument_error:
|
||||
summary: Invalid Argument Error
|
||||
value:
|
||||
message: "cart.total must be defined"
|
||||
type: "unexpected_state"
|
||||
default_error:
|
||||
summary: Default Error
|
||||
value:
|
||||
code: "unknown_error"
|
||||
message: "An unknown error occurred."
|
||||
type: "unknown_error"
|
||||
securitySchemes:
|
||||
cookie_auth:
|
||||
type: apiKey
|
||||
x-displayName: Cookie Session ID
|
||||
in: cookie
|
||||
name: connect.sid
|
||||
description: |
|
||||
Use a cookie session to send authenticated requests.
|
||||
|
||||
### How to Obtain the Cookie Session
|
||||
|
||||
If you're sending requests through a browser, using JS Client, or using tools like Postman, the cookie session should be automatically set when the customer is logged in.
|
||||
|
||||
If you're sending requests using cURL, you must set the Session ID in the cookie manually.
|
||||
|
||||
To do that, send a request to [authenticate the customer](#tag/Auth/operation/PostAuth) and pass the cURL option `-v`:
|
||||
|
||||
```bash
|
||||
curl -v --location --request POST 'https://medusa-url.com/store/auth' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"email": "user@example.com",
|
||||
"password": "supersecret"
|
||||
}'
|
||||
```
|
||||
|
||||
The headers will be logged in the terminal as well as the response. You should find in the headers a Cookie header similar to this:
|
||||
|
||||
```bash
|
||||
Set-Cookie: connect.sid=s%3A2Bu8BkaP9JUfHu9rG59G16Ma0QZf6Gj1.WT549XqX37PN8n0OecqnMCq798eLjZC5IT7yiDCBHPM;
|
||||
```
|
||||
|
||||
Copy the value after `connect.sid` (without the `;` at the end) and pass it as a cookie in subsequent requests as the following:
|
||||
|
||||
```bash
|
||||
curl --location --request GET 'https://medusa-url.com/store/customers/me/orders' \
|
||||
--header 'Cookie: connect.sid={sid}'
|
||||
```
|
||||
|
||||
Where `{sid}` is the value of `connect.sid` that you copied.
|
||||
@@ -0,0 +1,6 @@
|
||||
/dist
|
||||
node_modules
|
||||
.DS_store
|
||||
.env*
|
||||
.env
|
||||
*.sql
|
||||
@@ -0,0 +1,3 @@
|
||||
# @medusajs/oas-cli
|
||||
|
||||
## 0.1.0
|
||||
@@ -0,0 +1,72 @@
|
||||
# medusa-oas-cli - 0.1.0 - experimental
|
||||
|
||||
A command-line tool for all OpenAPI Specifications (OAS) related tooling.
|
||||
|
||||
## Install
|
||||
|
||||
`yarn add --dev @medusajs/medusa-oas-cli`
|
||||
|
||||
Install in the global namespace is not yet supported.
|
||||
~~`npm install -g @medusajs/medusa-oas-cli`~~
|
||||
|
||||
## Configuration / First time setup
|
||||
|
||||
N/A
|
||||
|
||||
## How to use
|
||||
|
||||
```bash
|
||||
yarn medusa-oas <command>
|
||||
```
|
||||
|
||||
### Command - `oas`
|
||||
|
||||
This command will scan the `@medusajs/medusa` package in order to extract JSDoc OAS into a json file.
|
||||
|
||||
By default, the command will output the two files `admin.oas.json` and `store.oas.json` in the same directory that the
|
||||
command was run.
|
||||
|
||||
Invalid OAS with throw an error and will prevent the files from being outputted.
|
||||
|
||||
#### `--type <string>`
|
||||
|
||||
Specify which API OAS to create. Accepts `all`, `admin`, `store`.
|
||||
Defaults to `all`.
|
||||
|
||||
```bash
|
||||
yarn medusa-oas oas --type admin
|
||||
```
|
||||
|
||||
#### `--out-dir <path>`
|
||||
|
||||
Specify in which directory should the files be outputted. It accepts a relative or absolute path.
|
||||
If the directory doesn't exist, it will be created. Defaults to `./`.
|
||||
|
||||
```bash
|
||||
yarm medusa-oas oas --out-dir
|
||||
```
|
||||
|
||||
#### `--paths <paths...>`
|
||||
|
||||
Allows passing additional directory paths to crawl for JSDoc OAS and include in the generated OAS.
|
||||
It accepts multiple entries.
|
||||
|
||||
```bash
|
||||
yarn medusa-oas oas --paths ~/medusa-server/src
|
||||
```
|
||||
|
||||
#### `--dry-run`
|
||||
|
||||
Will package the OAS but will not output file. Useful for validating OAS.
|
||||
|
||||
```bash
|
||||
yarn medusa-oas oas --dry-run
|
||||
```
|
||||
|
||||
#### `--force`
|
||||
|
||||
Ignore OAS errors and attempt to output generated OAS files.
|
||||
|
||||
```bash
|
||||
yarn medusa-oas oas --force
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@medusajs/medusa-oas-cli",
|
||||
"version": "0.1.0",
|
||||
"description": "OAS CLI",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
"medusa-oas": "./dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/medusajs/medusa",
|
||||
"directory": "packages/oas/medusa-oas-cli"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"author": "Medusa",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "4.9.5"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "cross-env NODE_ENV=production yarn run build",
|
||||
"build": "tsc --build",
|
||||
"cli": "ts-node src/index.ts",
|
||||
"test": "jest --passWithNoTests",
|
||||
"test:unit": "jest --passWithNoTests"
|
||||
},
|
||||
"dependencies": {
|
||||
"@medusajs/medusa": "*",
|
||||
"@readme/openapi-parser": "^2.4.0",
|
||||
"commander": "^10.0.0",
|
||||
"openapi3-ts": "^3.1.2",
|
||||
"swagger-inline": "^6.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import * as path from "path"
|
||||
import { lstat, mkdir, writeFile } from "fs/promises"
|
||||
import swaggerInline from "swagger-inline"
|
||||
import OpenAPIParser from "@readme/openapi-parser"
|
||||
import { OpenAPIObject } from "openapi3-ts"
|
||||
import { Command, Option, OptionValues } from "commander"
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
// Medusa core package directory
|
||||
const medusaPackagePath = path.dirname(
|
||||
require.resolve("@medusajs/medusa/package.json")
|
||||
)
|
||||
|
||||
type ApiType = "store" | "admin"
|
||||
|
||||
/**
|
||||
* CLI Command declaration
|
||||
*/
|
||||
export const commandName = "oas"
|
||||
export const commandDescription =
|
||||
"Compile full OAS from swagger-inline compliant JSDoc."
|
||||
|
||||
export const commandOptions: Option[] = [
|
||||
new Option("-t, --type <type>", "API type to compile []")
|
||||
.choices(["all", "admin", "store"])
|
||||
.default("all"),
|
||||
new Option(
|
||||
"-o, --out-dir <outDir>",
|
||||
"Destination directory to output generated OAS files."
|
||||
).default(process.cwd()),
|
||||
new Option("-D, --dry-run", "Do not output files."),
|
||||
new Option(
|
||||
"-p, --paths <paths...>",
|
||||
"Additional paths to crawl for OAS JSDoc."
|
||||
),
|
||||
new Option("-F, --force", "Ignore OAS validation and output OAS files."),
|
||||
]
|
||||
|
||||
export function getCommand() {
|
||||
const command = new Command(commandName)
|
||||
command.description(commandDescription)
|
||||
for (const opt of commandOptions) {
|
||||
command.addOption(opt)
|
||||
}
|
||||
command.action(async (options) => await execute(options))
|
||||
return command
|
||||
}
|
||||
|
||||
/**
|
||||
* Main
|
||||
*/
|
||||
export async function execute(cliParams: OptionValues) {
|
||||
/**
|
||||
* Process CLI options
|
||||
*/
|
||||
const dryRun = !!cliParams.dryRun
|
||||
const force = !!cliParams.force
|
||||
|
||||
const apiTypesToExport =
|
||||
cliParams.type === "all" ? ["store", "admin"] : [cliParams.type]
|
||||
|
||||
const outDir = path.resolve(cliParams.outDir)
|
||||
|
||||
const additionalPaths = (cliParams.paths ?? []).map((additionalPath) =>
|
||||
path.resolve(additionalPath)
|
||||
)
|
||||
for (const additionalPath of additionalPaths) {
|
||||
if (!(await isDirectory(additionalPath))) {
|
||||
throw new Error(`--paths must be a directory - ${additionalPath}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Command execution
|
||||
*/
|
||||
if (!dryRun) {
|
||||
await mkdir(outDir, { recursive: true })
|
||||
}
|
||||
|
||||
for (const apiType of apiTypesToExport) {
|
||||
console.log(`🟣 Generating OAS - ${apiType}`)
|
||||
const oas = await getOASFromCodebase(apiType as ApiType, additionalPaths)
|
||||
await validateOAS(oas, apiType as ApiType, force)
|
||||
if (!dryRun) {
|
||||
await exportOASToJSON(oas, apiType as ApiType, outDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods
|
||||
*/
|
||||
async function getOASFromCodebase(
|
||||
apiType: ApiType,
|
||||
additionalPaths: string[] = []
|
||||
): Promise<OpenAPIObject> {
|
||||
const gen = await swaggerInline(
|
||||
[
|
||||
path.resolve(medusaPackagePath, "dist", "models"),
|
||||
path.resolve(medusaPackagePath, "dist", "types"),
|
||||
path.resolve(medusaPackagePath, "dist", "api/middlewares"),
|
||||
path.resolve(medusaPackagePath, "dist", `api/routes/${apiType}`),
|
||||
...additionalPaths,
|
||||
],
|
||||
{
|
||||
base: path.resolve(
|
||||
medusaPackagePath,
|
||||
"oas",
|
||||
`${apiType}-spec3-base.yaml`
|
||||
),
|
||||
format: ".json",
|
||||
}
|
||||
)
|
||||
|
||||
return await OpenAPIParser.parse(JSON.parse(gen))
|
||||
}
|
||||
|
||||
async function validateOAS(
|
||||
oas: OpenAPIObject,
|
||||
apiType: ApiType,
|
||||
force = false
|
||||
): Promise<void> {
|
||||
try {
|
||||
await OpenAPIParser.validate(JSON.parse(JSON.stringify(oas)))
|
||||
console.log(`🟢 Valid OAS - ${apiType}`)
|
||||
} catch (err) {
|
||||
console.error(`🔴 Invalid OAS - ${apiType}`, err)
|
||||
if (!force) {
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function exportOASToJSON(
|
||||
oas: OpenAPIObject,
|
||||
apiType: ApiType,
|
||||
targetDir: string
|
||||
): Promise<void> {
|
||||
const json = JSON.stringify(oas, null, 2)
|
||||
const filePath = path.resolve(targetDir, `${apiType}.oas.json`)
|
||||
await writeFile(filePath, json)
|
||||
console.log(`🔵 Exported OAS - ${apiType} - ${filePath}`)
|
||||
}
|
||||
|
||||
async function isDirectory(dirPath: string): Promise<boolean> {
|
||||
return (await lstat(path.resolve(dirPath))).isDirectory()
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#! /usr/bin/env node
|
||||
|
||||
import { Command } from "commander"
|
||||
import { getCommand as oasGetCommand } from "./command-oas"
|
||||
|
||||
const run = async () => {
|
||||
const program = new Command()
|
||||
|
||||
/**
|
||||
* Alias to command-oas.ts
|
||||
*/
|
||||
program.addCommand(oasGetCommand())
|
||||
|
||||
/**
|
||||
* Run CLI
|
||||
*/
|
||||
await program.parseAsync()
|
||||
}
|
||||
|
||||
void (async () => {
|
||||
await run()
|
||||
})()
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"es2019"
|
||||
],
|
||||
"target": "es2019",
|
||||
"outDir": "./dist",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"sourceMap": true,
|
||||
"noImplicitReturns": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitThis": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"downlevelIteration": true // to use ES5 specific tooling
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/tests/*"
|
||||
],
|
||||
"ts-node": {
|
||||
"transpileOnly": true
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user