feat: gatsby source medusa (#924)
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"presets": [["babel-preset-gatsby-package"]],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"test": [],
|
||||||
|
"presets": [
|
||||||
|
["babel-preset-gatsby-package", { "browser": true, "esm": true }]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
node_modules
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
*.js
|
||||||
|
*.js.map
|
||||||
|
*.d.ts
|
||||||
|
!/types/*.d.ts
|
||||||
|
|
||||||
|
!jest.config.js
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
src
|
||||||
|
.prettierrc
|
||||||
|
.env
|
||||||
|
.babelrc.js
|
||||||
|
.eslintrc
|
||||||
|
.gitignore
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 Medusa
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="https://www.medusa-commerce.com">
|
||||||
|
<img alt="Medusa" src="https://user-images.githubusercontent.com/7554214/129161578-19b83dc8-fac5-4520-bd48-53cba676edd2.png" width="100" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<h1 align="center">
|
||||||
|
gatsby-source-medusa
|
||||||
|
</h1>
|
||||||
|
<p align="center">
|
||||||
|
Medusa is an open-source headless commerce engine that enables developers to create amazing digital commerce experiences. This is a Gatsby source plugin for building websites using Medusa as a data source.
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/medusajs/medusa/blob/master/LICENSE">
|
||||||
|
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="Medusa is released under the MIT license." />
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md">
|
||||||
|
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" alt="PRs welcome!" />
|
||||||
|
</a>
|
||||||
|
<a href="https://discord.gg/xpCwq3Kfn8">
|
||||||
|
<img src="https://img.shields.io/badge/chat-on%20discord-7289DA.svg" alt="Discord Chat" />
|
||||||
|
</a>
|
||||||
|
<a href="https://twitter.com/intent/follow?screen_name=medusajs">
|
||||||
|
<img src="https://img.shields.io/twitter/follow/medusajs.svg?label=Follow%20@medusajs" alt="Follow @medusajs" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Note
|
||||||
|
|
||||||
|
This plugin is still under development. Please report any issues or suggestions on the GitHub issues page.
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
This takes you through the minimal steps to see your Medusa data in your Gatsby site's GraphiQL explorer.
|
||||||
|
|
||||||
|
### 1. Installation
|
||||||
|
|
||||||
|
Install the source plugin to your Gatsby project using your favorite package manager.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm install gatsby-source-medusa
|
||||||
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn add gatsby-source-medusa
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Configuration
|
||||||
|
|
||||||
|
Add the plugin to your `gatsby-config.js`:
|
||||||
|
|
||||||
|
```js:title=gatsby-config.js
|
||||||
|
require("dotenv").config()
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
resolve: "gatsby-source-medusa",
|
||||||
|
options: {
|
||||||
|
storeUrl: process.env.MEDUSA_URL,
|
||||||
|
authToken: process.env.MEDUSA_AUTH_TOKEN //This is optional
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The plugin accepts two options `storeUrl` and `authToken`. The `storeUrl` option is required and should point to the server where your Medusa instance is hosted (this could be `localhost:9000` in development). The `authToken` option is optional, and if you add it the plugin will also source orders from your store.
|
||||||
|
|
||||||
|
## You should now be ready to begin querying your data
|
||||||
|
|
||||||
|
You should now be able to view your stores `MedusaProducts`, `MedusaRegions`, `MedusaCollections`, and `MedusaOrders` (if enabled) in your Gatsby site's GraphiQL explorer.
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`helper functions should return return a new node from processNode 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"children": Array [],
|
||||||
|
"description": "A test product",
|
||||||
|
"id": "prod_test_1234",
|
||||||
|
"internal": Object {
|
||||||
|
"content": "{\\"id\\":\\"prod_test_1234\\",\\"title\\":\\"Test Shirt\\",\\"description\\":\\"A test product\\",\\"unit_price\\":2500}",
|
||||||
|
"contentDigest": "digest_string",
|
||||||
|
"type": "MedusaProduct",
|
||||||
|
},
|
||||||
|
"parent": null,
|
||||||
|
"title": "Test Shirt",
|
||||||
|
"unit_price": 2500,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
const { processNode } = require("../src/process-node.ts");
|
||||||
|
|
||||||
|
describe("helper functions", () => {
|
||||||
|
it("should return return a new node from processNode", () => {
|
||||||
|
const fieldName = "product";
|
||||||
|
const node = {
|
||||||
|
id: "prod_test_1234",
|
||||||
|
title: "Test Shirt",
|
||||||
|
description: "A test product",
|
||||||
|
unit_price: 2500,
|
||||||
|
};
|
||||||
|
|
||||||
|
const createContentDigest = jest.fn(() => "digest_string");
|
||||||
|
const processNodeResult = processNode(node, fieldName, createContentDigest);
|
||||||
|
|
||||||
|
expect(createContentDigest).toBeCalled();
|
||||||
|
|
||||||
|
expect(processNodeResult).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "gatsby-source-medusa",
|
||||||
|
"version": "0.0.44",
|
||||||
|
"description": "Gatsby source plugin for building websites using Medusa Commerce as a data source",
|
||||||
|
"scripts": {
|
||||||
|
"test": "jest --watchAll",
|
||||||
|
"watch": "tsc-watch --outDir .",
|
||||||
|
"build": "tsc --outDir ."
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"gatsby",
|
||||||
|
"gatsby-plugin",
|
||||||
|
"gatsby-source",
|
||||||
|
"gatsby-source-medusa",
|
||||||
|
"medusa",
|
||||||
|
"medusa-commerce"
|
||||||
|
],
|
||||||
|
"author": "Kasper Kristensen <kasper@medusa-commerce.com> (https://www.medusa-commerce.com/)",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/medusajs/medusa.git",
|
||||||
|
"directory": "packages/gatsby-source-medusa"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/medusajs/medusa/issues"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^0.24.0",
|
||||||
|
"babel-preset-gatsby-package": "^2.0.0",
|
||||||
|
"gatsby-core-utils": "^3.3.0",
|
||||||
|
"gatsby-plugin-image": "^2.3.0",
|
||||||
|
"gatsby-source-filesystem": "^4.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/jest": "^27.0.1",
|
||||||
|
"babel-plugin-polyfill-corejs2": "^0.3.0",
|
||||||
|
"gatsby": "^4.1.0",
|
||||||
|
"jest": "^27.0.6",
|
||||||
|
"tsc-watch": "^4.5.0",
|
||||||
|
"typescript": "^4.5.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"gatsby": "^4.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import axios, { AxiosPromise, AxiosRequestConfig } from "axios"
|
||||||
|
import { Reporter } from "gatsby-cli/lib/reporter/reporter"
|
||||||
|
|
||||||
|
function medusaRequest(
|
||||||
|
storeURL: string,
|
||||||
|
path = "",
|
||||||
|
headers = {}
|
||||||
|
): AxiosPromise {
|
||||||
|
const options: AxiosRequestConfig = {
|
||||||
|
method: "GET",
|
||||||
|
withCredentials: true,
|
||||||
|
url: path,
|
||||||
|
headers: headers,
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = axios.create({ baseURL: storeURL })
|
||||||
|
|
||||||
|
return client(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createClient = (
|
||||||
|
options: MedusaPluginOptions,
|
||||||
|
reporter: Reporter
|
||||||
|
): any => {
|
||||||
|
const { storeUrl, authToken } = options
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} date used fetch products updated since the specified date
|
||||||
|
* @return {Promise<any[]>}
|
||||||
|
*/
|
||||||
|
async function products(date?: string): Promise<any[]> {
|
||||||
|
let products: any[] = []
|
||||||
|
let offset = 0
|
||||||
|
let count = 1
|
||||||
|
do {
|
||||||
|
await medusaRequest(storeUrl, `/store/products?offset=${offset}`)
|
||||||
|
.then(({ data }) => {
|
||||||
|
products = [...products, ...data.products]
|
||||||
|
count = data.count
|
||||||
|
offset = data.products.length
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reporter.error(
|
||||||
|
`"The following error status was produced while attempting to fetch products: ${error}`
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
} while (products.length < count)
|
||||||
|
|
||||||
|
return products
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} date used fetch regions updated since the specified date
|
||||||
|
* @return {Promise<any[]>}
|
||||||
|
*/
|
||||||
|
async function regions(date?: string): Promise<any[]> {
|
||||||
|
const regions = await medusaRequest(storeUrl, `/store/regions`)
|
||||||
|
.then(({ data }) => {
|
||||||
|
return data.regions
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn(`
|
||||||
|
"The following error status was produced while attempting to fetch regions: ${error}
|
||||||
|
`)
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
return regions
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} date used fetch regions updated since the specified date
|
||||||
|
* @return {Promise<any[]>}
|
||||||
|
*/
|
||||||
|
async function orders(date?: string): Promise<any[]> {
|
||||||
|
const orders = await medusaRequest(storeUrl, `/admin/orders`, {
|
||||||
|
Authorization: `Bearer ${authToken}`,
|
||||||
|
})
|
||||||
|
.then(({ data }) => {
|
||||||
|
return data.orders
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn(`
|
||||||
|
The following error status was produced while attempting to fetch orders: ${error}. \n
|
||||||
|
Make sure that the auth token you provided is valid.
|
||||||
|
`)
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
return orders
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} date used fetch regions updated since the specified date
|
||||||
|
* @return {Promise<any[]>}
|
||||||
|
*/
|
||||||
|
async function collections(date?: string): Promise<any[]> {
|
||||||
|
let collections: any[] = []
|
||||||
|
let offset = 0
|
||||||
|
let count = 1
|
||||||
|
do {
|
||||||
|
await medusaRequest(storeUrl, `/store/collections?offset=${offset}`)
|
||||||
|
.then(({ data }) => {
|
||||||
|
collections = [...collections, ...data.collections]
|
||||||
|
count = data.count
|
||||||
|
offset = data.collections.length
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reporter.error(
|
||||||
|
`"The following error status was produced while attempting to fetch products: ${error}`
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
} while (collections.length < count)
|
||||||
|
|
||||||
|
return collections
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
products,
|
||||||
|
collections,
|
||||||
|
regions,
|
||||||
|
orders,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
import {
|
||||||
|
CreateResolversArgs,
|
||||||
|
GatsbyCache,
|
||||||
|
Node,
|
||||||
|
PluginOptionsSchemaArgs,
|
||||||
|
Reporter,
|
||||||
|
SourceNodesArgs,
|
||||||
|
Store,
|
||||||
|
} from "gatsby"
|
||||||
|
import { createRemoteFileNode } from "gatsby-source-filesystem"
|
||||||
|
import { makeSourceFromOperation } from "./make-source-from-operation"
|
||||||
|
import { createOperations } from "./operations"
|
||||||
|
|
||||||
|
export function pluginOptionsSchema({ Joi }: PluginOptionsSchemaArgs): any {
|
||||||
|
return Joi.object({
|
||||||
|
storeUrl: Joi.string().required(),
|
||||||
|
apiKey: Joi.string().optional(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sourceAllNodes(
|
||||||
|
gatsbyApi: SourceNodesArgs,
|
||||||
|
pluginOptions: MedusaPluginOptions
|
||||||
|
): Promise<void> {
|
||||||
|
const {
|
||||||
|
createProductsOperation,
|
||||||
|
createRegionsOperation,
|
||||||
|
createOrdersOperation,
|
||||||
|
createCollectionsOperation,
|
||||||
|
} = createOperations(pluginOptions, gatsbyApi)
|
||||||
|
|
||||||
|
const operations = [
|
||||||
|
createProductsOperation,
|
||||||
|
createRegionsOperation,
|
||||||
|
createCollectionsOperation,
|
||||||
|
]
|
||||||
|
|
||||||
|
// if auth token is provided then source orders
|
||||||
|
if (pluginOptions.apiKey) {
|
||||||
|
operations.push(createOrdersOperation)
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceFromOperation = makeSourceFromOperation(gatsbyApi)
|
||||||
|
|
||||||
|
for (const op of operations) {
|
||||||
|
await sourceFromOperation(op)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const medusaNodeTypes = [
|
||||||
|
"MedusaRegions",
|
||||||
|
"MedusaProducts",
|
||||||
|
"MedusaOrders",
|
||||||
|
"MedusaCollections",
|
||||||
|
]
|
||||||
|
|
||||||
|
async function sourceUpdatedNodes(
|
||||||
|
gatsbyApi: SourceNodesArgs,
|
||||||
|
pluginOptions: MedusaPluginOptions
|
||||||
|
): Promise<void> {
|
||||||
|
const {
|
||||||
|
incrementalProductsOperation,
|
||||||
|
incrementalRegionsOperation,
|
||||||
|
incrementalOrdersOperation,
|
||||||
|
incrementalCollectionsOperation,
|
||||||
|
} = createOperations(pluginOptions, gatsbyApi)
|
||||||
|
|
||||||
|
const lastBuildTime = new Date(
|
||||||
|
gatsbyApi.store.getState().status.plugins?.[`gatsby-source-medusa`]?.[
|
||||||
|
`lastBuildTime`
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const nodeType of medusaNodeTypes) {
|
||||||
|
gatsbyApi
|
||||||
|
.getNodesByType(nodeType)
|
||||||
|
.forEach((node) => gatsbyApi.actions.touchNode(node))
|
||||||
|
}
|
||||||
|
|
||||||
|
const operations = [
|
||||||
|
incrementalProductsOperation(lastBuildTime),
|
||||||
|
incrementalRegionsOperation(lastBuildTime),
|
||||||
|
incrementalCollectionsOperation(lastBuildTime),
|
||||||
|
]
|
||||||
|
|
||||||
|
if (pluginOptions.apiKey) {
|
||||||
|
operations.push(incrementalOrdersOperation(lastBuildTime))
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceFromOperation = makeSourceFromOperation(gatsbyApi)
|
||||||
|
|
||||||
|
for (const op of operations) {
|
||||||
|
await sourceFromOperation(op)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sourceNodes(
|
||||||
|
gatsbyApi: SourceNodesArgs,
|
||||||
|
pluginOptions: MedusaPluginOptions
|
||||||
|
): Promise<void> {
|
||||||
|
const pluginStatus =
|
||||||
|
gatsbyApi.store.getState().status.plugins?.[`gatsby-source-medusa`]
|
||||||
|
|
||||||
|
const lastBuildTime = pluginStatus?.[`lastBuildTime`]
|
||||||
|
|
||||||
|
if (lastBuildTime !== undefined) {
|
||||||
|
gatsbyApi.reporter.info(
|
||||||
|
`Cache is warm, but incremental builds are currently not supported. Running a clean build.`
|
||||||
|
)
|
||||||
|
await sourceAllNodes(gatsbyApi, pluginOptions)
|
||||||
|
} else {
|
||||||
|
gatsbyApi.reporter.info(`Cache is cold, running a clean build.`)
|
||||||
|
await sourceAllNodes(gatsbyApi, pluginOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
gatsbyApi.reporter.info(`Finished sourcing nodes, caching last build time`)
|
||||||
|
gatsbyApi.actions.setPluginStatus(
|
||||||
|
pluginStatus !== undefined
|
||||||
|
? {
|
||||||
|
...pluginStatus,
|
||||||
|
[`lastBuildTime`]: Date.now(),
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
[`lastBuildTime`]: Date.now(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createResolvers({ createResolvers }: CreateResolversArgs): any {
|
||||||
|
const resolvers = {
|
||||||
|
MedusaProducts: {
|
||||||
|
images: {
|
||||||
|
type: ["MedusaImages"],
|
||||||
|
resolve: async (
|
||||||
|
source: any,
|
||||||
|
_args: any,
|
||||||
|
context: any,
|
||||||
|
_info: any
|
||||||
|
): Promise<any> => {
|
||||||
|
const { entries } = await context.nodeModel.findAll({
|
||||||
|
query: {
|
||||||
|
filter: { parent: { id: { eq: source.id } } },
|
||||||
|
},
|
||||||
|
type: "MedusaImages",
|
||||||
|
})
|
||||||
|
return entries
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
createResolvers(resolvers)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createSchemaCustomization({
|
||||||
|
actions: { createTypes },
|
||||||
|
}: {
|
||||||
|
actions: { createTypes: any }
|
||||||
|
schema: any
|
||||||
|
}): Promise<void> {
|
||||||
|
createTypes(`
|
||||||
|
type MedusaProducts implements Node {
|
||||||
|
thumbnail: File @link(from: "fields.localThumbnail")
|
||||||
|
}
|
||||||
|
|
||||||
|
type MedusaImages implements Node {
|
||||||
|
image: File @link(from: "fields.localImage")
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function onCreateNode({
|
||||||
|
actions: { createNode, createNodeField },
|
||||||
|
cache,
|
||||||
|
createNodeId,
|
||||||
|
node,
|
||||||
|
store,
|
||||||
|
reporter,
|
||||||
|
}: {
|
||||||
|
actions: { createNode: any; createNodeField: any }
|
||||||
|
cache: GatsbyCache
|
||||||
|
createNodeId: any
|
||||||
|
node: Node
|
||||||
|
store: Store
|
||||||
|
reporter: Reporter
|
||||||
|
}): Promise<void> {
|
||||||
|
if (node.internal.type === `MedusaProducts`) {
|
||||||
|
if (node.thumbnail !== null) {
|
||||||
|
const thumbnailNode: Node | null = await createRemoteFileNode({
|
||||||
|
url: `${node.thumbnail}`,
|
||||||
|
parentNodeId: node.id,
|
||||||
|
createNode,
|
||||||
|
createNodeId,
|
||||||
|
cache,
|
||||||
|
store,
|
||||||
|
reporter,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (thumbnailNode) {
|
||||||
|
createNodeField({
|
||||||
|
node,
|
||||||
|
name: `localThumbnail`,
|
||||||
|
value: thumbnailNode.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.internal.type === `MedusaImages`) {
|
||||||
|
const imageNode: Node | null = await createRemoteFileNode({
|
||||||
|
url: `${node.url}`,
|
||||||
|
parentNodeId: node.id,
|
||||||
|
createNode,
|
||||||
|
createNodeId,
|
||||||
|
cache,
|
||||||
|
store,
|
||||||
|
reporter,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (imageNode) {
|
||||||
|
createNodeField({
|
||||||
|
node,
|
||||||
|
name: `localImage`,
|
||||||
|
value: imageNode.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { SourceNodesArgs } from "gatsby"
|
||||||
|
import { processNode } from "./process-node"
|
||||||
|
|
||||||
|
export function makeSourceFromOperation(gatsbyApi: SourceNodesArgs) {
|
||||||
|
return async function sourceFromOperation(
|
||||||
|
op: IMedusaOperation
|
||||||
|
): Promise<void> {
|
||||||
|
const { reporter, actions } = gatsbyApi
|
||||||
|
|
||||||
|
reporter.info(`Initiating operation query ${op.name}`)
|
||||||
|
const nodes = await op.execute()
|
||||||
|
|
||||||
|
nodes.map((rawNode) => {
|
||||||
|
const nodeArr = processNode(
|
||||||
|
rawNode,
|
||||||
|
op.name,
|
||||||
|
gatsbyApi.createContentDigest
|
||||||
|
)
|
||||||
|
|
||||||
|
nodeArr.forEach((node) => {
|
||||||
|
actions.createNode(node)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { SourceNodesArgs } from "gatsby"
|
||||||
|
import { createClient } from "./client"
|
||||||
|
|
||||||
|
export function createOperations(
|
||||||
|
options: MedusaPluginOptions,
|
||||||
|
{ reporter }: SourceNodesArgs
|
||||||
|
): IOperations {
|
||||||
|
const client = createClient(options, reporter)
|
||||||
|
|
||||||
|
function createOperation(
|
||||||
|
name: "products" | "collections" | "regions" | "orders",
|
||||||
|
queryString?: string
|
||||||
|
): IMedusaOperation {
|
||||||
|
return {
|
||||||
|
execute: (): Promise<any[]> => client[name](queryString),
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
createProductsOperation: createOperation("products"),
|
||||||
|
createCollectionsOperation: createOperation("collections"),
|
||||||
|
createRegionsOperation: createOperation("regions"),
|
||||||
|
createOrdersOperation: createOperation("orders"),
|
||||||
|
incrementalProductsOperation: (date: Date): any =>
|
||||||
|
createOperation("products", date.toISOString()),
|
||||||
|
incrementalCollectionsOperation: (date: Date): any =>
|
||||||
|
createOperation("collections", date.toISOString()),
|
||||||
|
incrementalRegionsOperation: (date: Date): any =>
|
||||||
|
createOperation("regions", date.toISOString()),
|
||||||
|
incrementalOrdersOperation: (date: Date): any =>
|
||||||
|
createOperation("orders", date.toISOString()),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { capitalize } from "./utils/capitalize"
|
||||||
|
|
||||||
|
export const processNode = (
|
||||||
|
node: any,
|
||||||
|
fieldName: string,
|
||||||
|
createContentDigest: (this: void, input: string | object) => string
|
||||||
|
): any[] => {
|
||||||
|
const nodeId: string = node.id
|
||||||
|
const nodeContent = JSON.stringify(node)
|
||||||
|
const nodeContentDigest = createContentDigest(nodeContent)
|
||||||
|
|
||||||
|
let images = []
|
||||||
|
|
||||||
|
if (fieldName === "products") {
|
||||||
|
if (node.images?.length) {
|
||||||
|
images = node.images.map((image: any) => {
|
||||||
|
const nodeImageContentDigest = createContentDigest(image.id)
|
||||||
|
const nodeImageContent = JSON.stringify(image)
|
||||||
|
|
||||||
|
const imageData = Object.assign({}, image, {
|
||||||
|
id: image.id,
|
||||||
|
parent: nodeId,
|
||||||
|
children: [],
|
||||||
|
internal: {
|
||||||
|
type: "MedusaImages",
|
||||||
|
content: nodeImageContent,
|
||||||
|
contentDigest: nodeImageContentDigest,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return imageData
|
||||||
|
})
|
||||||
|
}
|
||||||
|
delete node.images
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeData = Object.assign({}, node, {
|
||||||
|
id: nodeId,
|
||||||
|
parent: null,
|
||||||
|
children: [],
|
||||||
|
internal: {
|
||||||
|
type: `Medusa${capitalize(fieldName)}`,
|
||||||
|
content: nodeContent,
|
||||||
|
contentDigest: nodeContentDigest,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return [nodeData, ...images]
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function capitalize(s: string): string {
|
||||||
|
return s[0].toUpperCase() + s.slice(1)
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export const formatUri = (uri: string): string => {
|
||||||
|
let url
|
||||||
|
|
||||||
|
try {
|
||||||
|
url = new URL(uri)
|
||||||
|
} catch (_) {
|
||||||
|
const formatted = /[\w||\d].*/.exec(uri)?.[0]
|
||||||
|
return `https://${formatted}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.href
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"include": ["src/**/*.ts", "types"],
|
||||||
|
"exclude": ["node_modules"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"removeComments": false,
|
||||||
|
"preserveConstEnums": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"target": "es2017",
|
||||||
|
"declaration": true,
|
||||||
|
"lib": ["es2017", "dom", "esnext.asynciterable"]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
interface MedusaPluginOptions {
|
||||||
|
storeUrl: string
|
||||||
|
apiKey: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MedusaProductImage {
|
||||||
|
url: string
|
||||||
|
metadata: Record<string, unknown> | null
|
||||||
|
id: string
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
deleted_at: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IMedusaOperation {
|
||||||
|
execute: () => Promise<any[]>
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IOperations {
|
||||||
|
createProductsOperation: IMedusaOperation
|
||||||
|
createCollectionsOperation: IMedusaOperation
|
||||||
|
createRegionsOperation: IMedusaOperation
|
||||||
|
createOrdersOperation: IMedusaOperation
|
||||||
|
incrementalProductsOperation: (date: Date) => IMedusaOperation
|
||||||
|
incrementalCollectionsOperation: (date: Date) => IMedusaOperation
|
||||||
|
incrementalRegionsOperation: (date: Date) => IMedusaOperation
|
||||||
|
incrementalOrdersOperation: (date: Date) => IMedusaOperation
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user