From fbf8f35f4717c7d6585edbdf555665aa81838d46 Mon Sep 17 00:00:00 2001
From: ddaoxuan
Date: Wed, 6 Nov 2024 21:48:12 +0100
Subject: [PATCH] chore: sync scripts
---
.../app/api/feed/sync/route.ts | 26 +-
.../components/demo-mode-alert.tsx | 2 +-
.../shopify-algolia/lib/algolia/client.ts | 164 +++----
.../shopify-algolia/lib/shopify/client.ts | 52 +-
.../shopify-algolia/lib/shopify/normalize.ts | 14 +-
.../shopify/queries/collection.storefront.ts | 8 +-
.../shopify/types/storefront.generated.d.ts | 6 +-
starters/shopify-algolia/lib/shopify/utils.ts | 7 +
starters/shopify-algolia/package.json | 9 +-
starters/shopify-algolia/scripts/sync.ts | 122 +++++
starters/shopify-algolia/tsup.config.ts | 13 +
starters/shopify-algolia/types/index.ts | 5 +
.../shopify-algolia/utils/enrich-product.ts | 124 +++--
starters/shopify-algolia/utils/opt-in.ts | 6 +-
starters/shopify-algolia/yarn.lock | 463 +++++++++++++++++-
.../app/api/feed/sync/route.ts | 16 +-
.../components/demo-mode-alert.tsx | 2 +-
.../shopify-meilisearch/lib/shopify/client.ts | 13 +-
.../lib/shopify/normalize.ts | 5 +-
.../shopify/types/storefront.generated.d.ts | 6 +-
.../shopify-meilisearch/lib/shopify/utils.ts | 7 +
starters/shopify-meilisearch/package.json | 3 +-
starters/shopify-meilisearch/scripts/sync.ts | 114 ++---
starters/shopify-meilisearch/types/index.ts | 5 +
.../utils/enrich-product.ts | 122 +++--
starters/shopify-meilisearch/utils/opt-in.ts | 8 +-
starters/shopify-meilisearch/yarn.lock | 47 ++
27 files changed, 1068 insertions(+), 301 deletions(-)
create mode 100644 starters/shopify-algolia/lib/shopify/utils.ts
create mode 100644 starters/shopify-algolia/scripts/sync.ts
create mode 100644 starters/shopify-algolia/tsup.config.ts
create mode 100644 starters/shopify-meilisearch/lib/shopify/utils.ts
diff --git a/starters/shopify-algolia/app/api/feed/sync/route.ts b/starters/shopify-algolia/app/api/feed/sync/route.ts
index de13f5a3..96b13a50 100644
--- a/starters/shopify-algolia/app/api/feed/sync/route.ts
+++ b/starters/shopify-algolia/app/api/feed/sync/route.ts
@@ -1,9 +1,12 @@
-import type { PlatformProduct } from "lib/shopify/types"
import { env } from "env.mjs"
import { compareHmac } from "utils/compare-hmac"
-import { enrichProduct } from "utils/enrich-product"
+import { ProductEnrichmentBuilder } from "utils/enrich-product"
import { deleteCategories, deleteProducts, updateCategories, updateProducts } from "lib/algolia"
import { getCollection, getHierarchicalCollections, getProduct } from "lib/shopify"
+import { makeShopifyId } from "lib/shopify/utils"
+import { HIERARCHICAL_SEPARATOR } from "constants/index"
+import { isOptIn } from "utils/opt-in"
+import { getAllProductReviews } from "lib/reviews"
type SupportedTopic = "products/update" | "products/delete" | "products/create" | "collections/update" | "collections/delete" | "collections/create"
@@ -55,7 +58,7 @@ async function handleCollectionTopics(topic: SupportedTopic, { id }: RecordFiltering, searching, and adding to cart is disabled.
diff --git a/starters/shopify-algolia/lib/algolia/client.ts b/starters/shopify-algolia/lib/algolia/client.ts
index 2bcbcaf8..51c99b53 100644
--- a/starters/shopify-algolia/lib/algolia/client.ts
+++ b/starters/shopify-algolia/lib/algolia/client.ts
@@ -1,4 +1,5 @@
import {
+ type BatchProps,
algoliasearch,
type BrowseProps,
type DeleteObjectsOptions,
@@ -7,139 +8,110 @@ import {
type SearchMethodParams,
type SearchResponse,
type SearchSingleIndexProps,
-} from "algoliasearch";
+} from "algoliasearch"
-import { env } from "env.mjs";
+import { env } from "env.mjs"
-import { FilterBuilder } from "./filter-builder";
+import { FilterBuilder } from "./filter-builder"
const algoliaClient = (args: { applicationId: string; apiKey: string }) => {
- return algoliasearch(args.applicationId, args.apiKey);
-};
+ return algoliasearch(args.applicationId, args.apiKey)
+}
export const algolia = (args: { applicationId: string; apiKey: string }) => {
- const client = algoliaClient(args);
- const recommendationClient = client.initRecommend();
+ const client = algoliaClient(args)
+ const recommendationClient = client.initRecommend()
return {
- search: async >(
- args: SearchSingleIndexProps
- ) => search(args, client),
- getAllResults: async >(args: BrowseProps) =>
- getAllResults(client, args),
- update: async (args: PartialUpdateObjectsOptions) =>
- updateObjects(args, client),
+ search: async >(args: SearchSingleIndexProps) => search(args, client),
+ getAllResults: async >(args: BrowseProps) => getAllResults(client, args),
+ update: async (args: PartialUpdateObjectsOptions) => updateObjects(args, client),
+ batchUpdate: async (args: BatchProps) => batchUpdate(args, client),
delete: async (args: DeleteObjectsOptions) => deleteObjects(args, client),
- create: async (args: PartialUpdateObjectsOptions) =>
- createObjects(args, client),
- multiSearch: async >(
- args: SearchMethodParams
- ) => multiSearch(args, client),
- getRecommendations: async (args: GetRecommendationsParams) =>
- getRecommendations(recommendationClient, args),
+ create: async (args: PartialUpdateObjectsOptions) => createObjects(args, client),
+ multiSearch: async >(args: SearchMethodParams) => multiSearch(args, client),
+ getRecommendations: async (args: GetRecommendationsParams) => getRecommendations(recommendationClient, args),
filterBuilder: () => new FilterBuilder(),
mapIndexToSort,
- };
-};
+ }
+}
-const search = async >(
- args: SearchSingleIndexProps,
- client: ReturnType
-) => {
- return client.searchSingleIndex(args);
-};
+const search = async >(args: SearchSingleIndexProps, client: ReturnType) => {
+ return client.searchSingleIndex(args)
+}
// agregator as temp fix for now
-const getAllResults = async >(
- client: ReturnType,
- args: BrowseProps
-) => {
- const allHits: T[] = [];
- let totalPages: number;
- let currentPage = 0;
+const getAllResults = async >(client: ReturnType, args: BrowseProps) => {
+ const allHits: T[] = []
+ let totalPages: number
+ let currentPage = 0
do {
- const { hits, nbPages } = await client.browseObjects({
+ const { hits, nbPages } = await client.browse({
...args,
browseParams: {
...args.browseParams,
hitsPerPage: 1000,
+ page: currentPage,
},
- aggregator: () => null,
- });
- allHits.push(...hits);
- totalPages = nbPages || 0;
- currentPage++;
- } while (currentPage < totalPages);
-
- return { hits: allHits, totalPages };
-};
-
-const updateObjects = async (
- args: PartialUpdateObjectsOptions,
- client: ReturnType
-) => {
- return client.partialUpdateObjects(args);
-};
-
-const deleteObjects = async (
- args: DeleteObjectsOptions,
- client: ReturnType
-) => {
- return client.deleteObjects(args);
-};
-
-const createObjects = async (
- args: PartialUpdateObjectsOptions,
- client: ReturnType
-) => {
+ })
+ allHits.push(...hits)
+ totalPages = nbPages || 0
+ currentPage++
+ } while (currentPage < totalPages)
+
+ return { hits: allHits, totalPages }
+}
+
+const batchUpdate = async (args: BatchProps, client: ReturnType) => {
+ return client.batch(args)
+}
+
+const updateObjects = async (args: PartialUpdateObjectsOptions, client: ReturnType) => {
+ return client.partialUpdateObjects(args)
+}
+
+const deleteObjects = async (args: DeleteObjectsOptions, client: ReturnType) => {
+ return client.deleteObjects(args)
+}
+
+const createObjects = async (args: PartialUpdateObjectsOptions, client: ReturnType) => {
return client.partialUpdateObjects({
...args,
createIfNotExists: true,
- });
-};
-
-const multiSearch = async >(
- args: SearchMethodParams,
- client: ReturnType
-) => {
- return client.search(args) as Promise<{ results: SearchResponse[] }>;
-};
-
-const getRecommendations = async (
- client: ReturnType["initRecommend"]>,
- args: GetRecommendationsParams
-) => {
- return client.getRecommendations(args);
-};
-
-export type SortType =
- | "minPrice:desc"
- | "minPrice:asc"
- | "avgRating:desc"
- | "updatedAtTimestamp:asc"
- | "updatedAtTimestamp:desc";
+ })
+}
+
+const multiSearch = async >(args: SearchMethodParams, client: ReturnType) => {
+ return client.search(args) as Promise<{ results: SearchResponse[] }>
+}
+
+const getRecommendations = async (client: ReturnType["initRecommend"]>, args: GetRecommendationsParams) => {
+ return client.getRecommendations(args)
+}
+
+export type SortType = "minPrice:desc" | "minPrice:asc" | "avgRating:desc" | "updatedAtTimestamp:asc" | "updatedAtTimestamp:desc"
const mapIndexToSort = (index: string, sortOption: SortType) => {
switch (sortOption) {
case "minPrice:desc":
- return `${index}_price_desc`;
+ return `${index}_price_desc`
case "minPrice:asc":
- return `${index}_price_asc`;
+ return `${index}_price_asc`
case "avgRating:desc":
- return `${index}_rating_desc`;
+ return `${index}_rating_desc`
case "updatedAtTimestamp:asc":
- return `${index}_updated_asc`;
+ return `${index}_updated_asc`
case "updatedAtTimestamp:desc":
- return `${index}_updated_desc`;
+ return `${index}_updated_desc`
default:
- return index;
+ return index
}
-};
+}
export const searchClient: ReturnType = algolia({
applicationId: env.ALGOLIA_APP_ID || "",
// Make sure write api key never leaks to the client
apiKey: env.ALGOLIA_WRITE_API_KEY || "",
-});
+})
diff --git a/starters/shopify-algolia/lib/shopify/client.ts b/starters/shopify-algolia/lib/shopify/client.ts
index 1a1028cf..f1da0488 100644
--- a/starters/shopify-algolia/lib/shopify/client.ts
+++ b/starters/shopify-algolia/lib/shopify/client.ts
@@ -13,7 +13,7 @@ import { getMenuQuery, type MenuQuery } from "./queries/menu.storefront"
import { getPageQuery, getPagesQuery } from "./queries/page.storefront"
import { getLatestProductFeedQuery } from "./queries/product-feed.admin"
import { getAdminProductQuery, getProductStatusQuery } from "./queries/product.admin"
-import { getProductQuery, getProductsByHandleQuery } from "./queries/product.storefront"
+import { getProductQuery, getProductsByHandleQuery, getProductsQuery } from "./queries/product.storefront"
import type {
LatestProductFeedsQuery,
@@ -57,6 +57,7 @@ import {
} from "./types"
import { env } from "env.mjs"
+import { cleanShopifyId, makeShopifyId } from "./utils"
interface CreateShopifyClientProps {
storeDomain: string
@@ -105,6 +106,8 @@ export function createShopifyClient({ storefrontAccessToken, adminAccessToken, s
updateUser: async (accessToken: string, input: Omit) => updateUser(client!, accessToken, input),
createUserAccessToken: async (input: Pick) => createUserAccessToken(client!, input),
getHierarchicalCollections: async (handle: string, depth?: number) => getHierarchicalCollections(client!, handle, depth),
+ getAllProducts: async () => getAllProducts(client!),
+ getAllCollections: async () => getAllCollections(client!),
}
}
@@ -116,7 +119,7 @@ async function getMenu(client: StorefrontApiClient, handle: string = "main-menu"
return { items: mappedItems || [] }
}
-async function getHierarchicalCollections(client: StorefrontApiClient, handle: string, depth = 3): Promise {
+async function getHierarchicalCollections(client: StorefrontApiClient, handle: string, depth = 3) {
const query = getMenuQuery(depth)
const response = await client.request(query, { variables: { handle } })
const mappedItems = response.data?.menu.items.filter((item) => item.resource?.__typename === "Collection")
@@ -127,7 +130,7 @@ async function getHierarchicalCollections(client: StorefrontApiClient, handle: s
}
async function getProduct(client: StorefrontApiClient, id: string): Promise {
- const response = await client.request(getProductQuery, { variables: { id } })
+ const response = await client.request(getProductQuery, { variables: { id: makeShopifyId(id, "Product") } })
const product = response.data?.product
return normalizeProduct(product)
@@ -176,7 +179,7 @@ async function getAllPages(client: StorefrontApiClient): Promise {
- const status = await client.request(getProductStatusQuery, { variables: { id } })
+ const status = await client.request(getProductStatusQuery, { variables: { id: makeShopifyId(id, "Product") } })
return status.data?.product
}
@@ -224,7 +227,7 @@ async function getCollection(client: StorefrontApiClient, handle: string): Promi
}
async function getCollectionById(client: StorefrontApiClient, id: string): Promise {
- const collection = await client.request(getCollectionByIdQuery, { variables: { id } })
+ const collection = await client.request(getCollectionByIdQuery, { variables: { id: makeShopifyId(id, "Collection") } })
return normalizeCollection(collection.data?.collection)
}
@@ -255,7 +258,7 @@ async function updateUser(client: StorefrontApiClient, customerAccessToken: stri
async function getAdminProduct(client: AdminApiClient, id: string) {
const response = await client.request(getAdminProductQuery, {
- variables: { id: id.startsWith("gid://shopify/Product/") ? id : `gid://shopify/Product/${id}` },
+ variables: { id: makeShopifyId(id, "Product") },
})
if (!response.data?.product) return null
@@ -266,6 +269,43 @@ async function getAdminProduct(client: AdminApiClient, id: string) {
return normalizeProduct({ ...response.data?.product, variants })
}
+async function getAllProducts(client: StorefrontApiClient, limit: number = 250): Promise {
+ const products: PlatformProduct[] = []
+ let hasNextPage = true
+ let cursor: string | null = null
+
+ while (hasNextPage) {
+ const response = await client.request(getProductsQuery, {
+ variables: { numProducts: limit, cursor },
+ })
+
+ const fetchedProducts = response.data?.products?.edges || []
+ products.push(...fetchedProducts.map((edge) => normalizeProduct(edge.node)))
+
+ hasNextPage = response.data?.products?.pageInfo?.hasNextPage || false
+ cursor = hasNextPage ? response.data?.products?.pageInfo?.endCursor : null
+ }
+
+ return products.map((product) => ({ ...product, id: cleanShopifyId(product.id, "Product") }))
+}
+
+async function getAllCollections(client: StorefrontApiClient, limit?: number) {
+ const collections: PlatformCollection[] = []
+ let hasNextPage = true
+ let cursor: string | null = null
+
+ while (hasNextPage) {
+ const response = await client.request(getCollectionsQuery, { variables: { first: limit, after: cursor } })
+ const fetchedCollections = response.data?.collections?.edges || []
+ collections.push(...fetchedCollections.map((edge) => normalizeCollection(edge.node)))
+
+ hasNextPage = response.data?.collections?.pageInfo?.hasNextPage || false
+ cursor = hasNextPage ? response?.data?.collections?.pageInfo?.endCursor : null
+ }
+
+ return collections.map((collection) => ({ ...collection, id: cleanShopifyId(collection.id, "Collection") }))
+}
+
export const storefrontClient = createShopifyClient({
storeDomain: env.SHOPIFY_STORE_DOMAIN || "",
storefrontAccessToken: env.SHOPIFY_STOREFRONT_ACCESS_TOKEN || "",
diff --git a/starters/shopify-algolia/lib/shopify/normalize.ts b/starters/shopify-algolia/lib/shopify/normalize.ts
index 7142b2aa..dce8fa3b 100644
--- a/starters/shopify-algolia/lib/shopify/normalize.ts
+++ b/starters/shopify-algolia/lib/shopify/normalize.ts
@@ -1,12 +1,13 @@
import { SingleCartQuery, SingleCollectionQuery, SingleProductQuery } from "./types/storefront.generated"
import type { PlatformCart, PlatformCartItem, PlatformCollection, PlatformProduct } from "./types"
+import { cleanShopifyId } from "./utils"
export function normalizeProduct(product: SingleProductQuery["product"]): PlatformProduct | null {
if (!product) return null
const { id, handle, title, description, vendor, descriptionHtml, options, priceRange, variants, featuredImage, images, tags, updatedAt, createdAt, collections, seo } = product
return {
- id,
+ id: cleanShopifyId(id, "Product"),
handle,
title,
description,
@@ -46,5 +47,14 @@ export function normalizeCollection(collection: SingleCollectionQuery["collectio
if (!collection) return null
const { id, handle, title, descriptionHtml, seo, image, updatedAt, description } = collection
- return { id, handle, title, descriptionHtml, seo, image, updatedAt, description }
+ return {
+ id: cleanShopifyId(id, "Collection"),
+ handle,
+ title,
+ descriptionHtml,
+ seo,
+ image,
+ updatedAt,
+ description,
+ }
}
diff --git a/starters/shopify-algolia/lib/shopify/queries/collection.storefront.ts b/starters/shopify-algolia/lib/shopify/queries/collection.storefront.ts
index 40a78a78..144aba2a 100644
--- a/starters/shopify-algolia/lib/shopify/queries/collection.storefront.ts
+++ b/starters/shopify-algolia/lib/shopify/queries/collection.storefront.ts
@@ -19,13 +19,17 @@ export const getCollectionQuery = `#graphql
`
export const getCollectionsQuery = `#graphql
- query Collections($limit: Int = 250) {
- collections(first: $limit, sortKey: TITLE) {
+ query Collections($first: Int = 250, $after: String) {
+ collections(first: $first, after: $after, sortKey: TITLE) {
edges {
node {
...singleCollection
}
}
+ pageInfo {
+ hasNextPage
+ endCursor
+ }
}
}
${collectionFragment}
diff --git a/starters/shopify-algolia/lib/shopify/types/storefront.generated.d.ts b/starters/shopify-algolia/lib/shopify/types/storefront.generated.d.ts
index 8c2c6d95..94739202 100644
--- a/starters/shopify-algolia/lib/shopify/types/storefront.generated.d.ts
+++ b/starters/shopify-algolia/lib/shopify/types/storefront.generated.d.ts
@@ -544,7 +544,8 @@ export type SingleCollectionQuery = {
}
export type CollectionsQueryVariables = StorefrontTypes.Exact<{
- limit?: StorefrontTypes.InputMaybe
+ first?: StorefrontTypes.InputMaybe
+ after?: StorefrontTypes.InputMaybe
}>
export type CollectionsQuery = {
@@ -555,6 +556,7 @@ export type CollectionsQuery = {
seo: Pick
}
}>
+ pageInfo: Pick
}
}
@@ -716,7 +718,7 @@ interface GeneratedQueryTypes {
return: SingleCollectionQuery
variables: SingleCollectionQueryVariables
}
- "#graphql\n query Collections($limit: Int = 250) {\n collections(first: $limit, sortKey: TITLE) {\n edges {\n node {\n ...singleCollection\n }\n }\n }\n }\n #graphql\n fragment singleCollection on Collection {\n handle\n image {\n ...singleImage\n }\n title\n descriptionHtml\n id\n description\n seo {\n ...seo\n }\n updatedAt\n }\n #graphql\n fragment seo on SEO {\n description\n title\n }\n\n #graphql\n fragment singleImage on Image {\n url\n altText\n width\n height\n }\n\n\n": {
+ "#graphql\n query Collections($first: Int = 250, $after: String) {\n collections(first: $first, after: $after, sortKey: TITLE) {\n edges {\n node {\n ...singleCollection\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n #graphql\n fragment singleCollection on Collection {\n handle\n image {\n ...singleImage\n }\n title\n descriptionHtml\n id\n description\n seo {\n ...seo\n }\n updatedAt\n }\n #graphql\n fragment seo on SEO {\n description\n title\n }\n\n #graphql\n fragment singleImage on Image {\n url\n altText\n width\n height\n }\n\n\n": {
return: CollectionsQuery
variables: CollectionsQueryVariables
}
diff --git a/starters/shopify-algolia/lib/shopify/utils.ts b/starters/shopify-algolia/lib/shopify/utils.ts
new file mode 100644
index 00000000..3349ec83
--- /dev/null
+++ b/starters/shopify-algolia/lib/shopify/utils.ts
@@ -0,0 +1,7 @@
+export function makeShopifyId(id: string, type: "Product" | "Collection") {
+ return id.startsWith("gid://shopify/") ? id : `gid://shopify/${type}/${id}`
+}
+
+export function cleanShopifyId(id: string, type: "Product" | "Collection") {
+ return id.replace(`gid://shopify/${type}/`, "")
+}
diff --git a/starters/shopify-algolia/package.json b/starters/shopify-algolia/package.json
index 16cbe7ca..c6314f9e 100644
--- a/starters/shopify-algolia/package.json
+++ b/starters/shopify-algolia/package.json
@@ -18,7 +18,9 @@
"postinstall": "npx patch-package -y",
"generate-bloom-filter": "ts-node redirects/generate-bloom-filter.ts",
"codegen": "graphql-codegen && graphql-codegen -p admin",
- "coupling-graph": "npx madge --extensions js,jsx,ts,tsx,css,md,mdx ./ --exclude '.next|tailwind.config.js|reset.d.ts|prettier.config.js|postcss.config.js|playwright.config.ts|next.config.js|next-env.d.ts|instrumentation.ts|e2e/|README.md|.storybook/|.eslintrc.js' --image graph.svg"
+ "coupling-graph": "npx madge --extensions js,jsx,ts,tsx,css,md,mdx ./ --exclude '.next|tailwind.config.js|reset.d.ts|prettier.config.js|postcss.config.js|playwright.config.ts|next.config.js|next-env.d.ts|instrumentation.ts|e2e/|README.md|.storybook/|.eslintrc.js' --image graph.svg",
+ "sync": "yarn build:scripts && node scripts/dist/sync.mjs",
+ "build:scripts": "rimraf scripts/dist && tsup scripts/ -d scripts/dist"
},
"dependencies": {
"@ai-sdk/openai": "^0.0.71",
@@ -90,6 +92,7 @@
"@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.3.1",
"@vercel/style-guide": "^5.0.0",
+ "dotenv": "^16.4.5",
"eslint": "8.54.0",
"eslint-config-next": "14.0.3",
"eslint-config-prettier": "^9.0.0",
@@ -100,14 +103,18 @@
"eslint-plugin-tailwindcss": "^3.13.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
+ "lodash.isequal": "^4.5.0",
+ "lodash.omit": "^4.5.0",
"patch-package": "^8.0.0",
"prettier": "3.2.5",
"prettier-plugin-tailwindcss": "^0.5.12",
+ "rimraf": "^6.0.1",
"server-only": "^0.0.1",
"tailwind-merge": "^2.2.2",
"tailwindcss": "^3.4.1",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
+ "tsup": "^8.3.5",
"typescript": "5.4.3",
"zod": "^3.22.4"
}
diff --git a/starters/shopify-algolia/scripts/sync.ts b/starters/shopify-algolia/scripts/sync.ts
new file mode 100644
index 00000000..5d1bc5c1
--- /dev/null
+++ b/starters/shopify-algolia/scripts/sync.ts
@@ -0,0 +1,122 @@
+import isEqual from "lodash.isequal"
+import omit from "lodash.omit"
+
+import { HIERARCHICAL_SEPARATOR } from "../constants/index"
+import { storefrontClient } from "../lib/shopify/client"
+import type { PlatformProduct } from "../lib/shopify/types"
+import { searchClient } from "../lib/algolia/client"
+import { env } from "../env.mjs"
+import { isOptIn } from "utils/opt-in"
+import { reviewsClient } from "lib/reviews/client"
+import { ProductEnrichmentBuilder, buildCategoryMap } from "utils/enrich-product"
+
+async function sync() {
+ console.log("🚀 Starting sync process...")
+
+ try {
+ console.log("📦 Fetching products from Shopify...")
+ const allProducts = await storefrontClient.getAllProducts()
+ console.log(`✓ Found ${allProducts.length} products`)
+
+ console.log("📑 Fetching categories from Shopify...")
+ const allCategories = await storefrontClient.getAllCollections()
+ console.log(`✓ Found ${allCategories.length} categories`)
+
+ if (!allProducts.length || !allCategories.length) {
+ console.warn("⚠️ No products or categories found, aborting sync")
+ return
+ }
+
+ console.log("🌳 Fetching hierarchical collections...")
+ const hierarchicalCategories = await storefrontClient.getHierarchicalCollections(env.SHOPIFY_HIERARCHICAL_NAV_HANDLE!)
+ console.log(`✓ Found ${hierarchicalCategories.items.length} hierarchical categories`)
+
+ console.log("🔄 Enriching products with hierarchical data...")
+ const categoryMap = buildCategoryMap(hierarchicalCategories.items)
+ const reviews = isOptIn("reviews") ? await reviewsClient.getAllProductReviews() : []
+
+ const enrichedProducts = allProducts.map((product) =>
+ new ProductEnrichmentBuilder(product).withHierarchicalCategories(hierarchicalCategories.items, HIERARCHICAL_SEPARATOR).withReviews(reviews).build()
+ )
+
+ console.log("📥 Fetching current Algolia indices...")
+ const { hits: allIndexProducts } = await searchClient.getAllResults({
+ indexName: env.ALGOLIA_PRODUCTS_INDEX,
+ browseParams: {},
+ })
+
+ console.log(`✓ Found ${allIndexProducts.length} products in Algolia`)
+
+ const { hits: allIndexCategories } = await searchClient.getAllResults({
+ indexName: env.ALGOLIA_CATEGORIES_INDEX,
+ })
+
+ console.log(`✓ Found ${allIndexCategories.length} categories in Algolia`)
+
+ console.log("🔍 Calculating differences...")
+ const deltaProducts = calculateProductDelta(enrichedProducts, allIndexProducts)
+ const deltaCategories = calculateCategoryDelta(allCategories, allIndexCategories)
+
+ await updateAlgoliaDocuments(env.ALGOLIA_PRODUCTS_INDEX, deltaProducts, "products")
+ await updateAlgoliaDocuments(env.ALGOLIA_CATEGORIES_INDEX, deltaCategories, "categories")
+
+ if (deltaProducts.length === 0 && deltaCategories.length === 0) {
+ console.log("✨ Nothing to sync, looks like you're all set!")
+ return
+ }
+
+ console.log("🎉 Sync completed successfully!")
+ } catch (error) {
+ console.error("❌ Error during sync:", error instanceof Error ? error.message : error)
+ throw error
+ }
+}
+
+async function updateAlgoliaDocuments>(indexName: string, documents: T[], entityName: string) {
+ if (documents.length === 0) return
+
+ console.log(`📤 Updating ${documents.length} ${entityName} in Algolia...`)
+ await searchClient.batchUpdate({
+ indexName,
+ batchWriteParams: {
+ requests: documents.map((doc) => ({
+ action: doc.objectID ? "partialUpdateObjectNoCreate" : "addObject",
+ body: doc,
+ })),
+ },
+ })
+}
+
+function calculateProductDelta(enrichedProducts: PlatformProduct[], allIndexProducts: any[]) {
+ const allIndexProductsMap = new Map(allIndexProducts.map((product) => [product.id, product]))
+
+ return enrichedProducts.reduce>((acc, product) => {
+ const existingProduct = allIndexProductsMap.get(product.id)
+ if (!existingProduct || !isEqual(omit(product, ["objectID"]), omit(existingProduct, ["objectID"]))) {
+ acc.push(existingProduct ? { ...product, objectID: existingProduct.objectID } : product)
+ }
+ return acc
+ }, [])
+}
+
+function calculateCategoryDelta(categories: PlatformProduct["collections"], allIndexCategories: any[]) {
+ const allIndexCategoriesMap = new Map(allIndexCategories.map((category) => [category.id, category]))
+
+ return categories.reduce>((acc, category) => {
+ const existingCategory = allIndexCategoriesMap.get(category.id)
+ if (!existingCategory || !isEqual(omit(category, ["objectID"]), omit(existingCategory, ["objectID"]))) {
+ acc.push(existingCategory ? { ...category, objectID: existingCategory.objectID } : category)
+ }
+ return acc
+ }, [])
+}
+
+sync()
+ .then(() => {
+ console.log("👋 Sync process finished")
+ process.exit(0)
+ })
+ .catch((error) => {
+ console.error("💥 Sync process failed:", error)
+ process.exit(1)
+ })
diff --git a/starters/shopify-algolia/tsup.config.ts b/starters/shopify-algolia/tsup.config.ts
new file mode 100644
index 00000000..abdf4772
--- /dev/null
+++ b/starters/shopify-algolia/tsup.config.ts
@@ -0,0 +1,13 @@
+import { defineConfig } from "tsup"
+import dotenv from "dotenv"
+
+dotenv.config({ path: ".env.local" })
+
+const envVariables = Object.fromEntries(Object.entries(process.env).map(([key, value]) => [`process.env.${key}`, JSON.stringify(value)]))
+
+export default defineConfig({
+ format: ["esm"],
+ clean: true,
+ target: "node16",
+ define: envVariables,
+})
diff --git a/starters/shopify-algolia/types/index.ts b/starters/shopify-algolia/types/index.ts
index 54f38f5d..032ccca2 100644
--- a/starters/shopify-algolia/types/index.ts
+++ b/starters/shopify-algolia/types/index.ts
@@ -7,4 +7,9 @@ export type CommerceProduct = PlatformProduct & {
avgRating?: number
totalReviews?: number
reviewsSummary?: string
+ hierarchicalCategories?: {
+ lvl0?: string[]
+ lvl1?: string[]
+ lvl2?: string[]
+ }
}
diff --git a/starters/shopify-algolia/utils/enrich-product.ts b/starters/shopify-algolia/utils/enrich-product.ts
index 18cefe35..fe8c0405 100644
--- a/starters/shopify-algolia/utils/enrich-product.ts
+++ b/starters/shopify-algolia/utils/enrich-product.ts
@@ -1,61 +1,125 @@
import { generateImageCaption } from "lib/replicate"
+import { Review } from "lib/reviews/types"
import type { PlatformImage, PlatformMenu, PlatformProduct } from "lib/shopify/types"
+import { CommerceProduct } from "types"
+import { isOptIn } from "./opt-in"
/*
* Enrich product by attaching hierarchical categories to it
* Takes in all tags, and tries to find hierarchy against it
- * Currently just shopify focused
*/
-export const enrichProduct = async (product: PlatformProduct, collections: PlatformMenu["items"]) => {
- const categoryMap = buildCategoryMap(collections)
- const images = await generateProductAltTags(product)
- const hierarchicalCategories = generateHierarchicalCategories(product.tags, categoryMap)
+export class ProductEnrichmentBuilder {
+ private product: CommerceProduct
- return {
- ...product,
- images: images.filter(Boolean),
- hierarchicalCategories,
+ constructor(baseProduct: PlatformProduct) {
+ this.product = baseProduct
+ }
+
+ withHierarchicalCategories(collections: PlatformMenu["items"], separator: string): this {
+ const categoryMap = buildCategoryMap(collections)
+
+ if (!categoryMap.size) {
+ return this
+ }
+
+ this.product = {
+ ...this.product,
+ hierarchicalCategories: generateHierarchicalCategories(this.product.tags, categoryMap, separator),
+ }
+ return this
+ }
+
+ withReviews(allReviews: Review[]): this {
+ if (!isOptIn("reviews")) {
+ return this
+ }
+
+ const productReviews = allReviews.filter((review) => review.product_handle === this.product.handle)
+
+ if (productReviews.length) {
+ const avgRating = productReviews.reduce((acc, review) => acc + review.rating, 0) / productReviews.length || 0
+
+ this.product = {
+ ...this.product,
+ avgRating,
+ totalReviews: productReviews.length,
+ }
+ }
+ return this
+ }
+
+ async withAltTags(): Promise {
+ if (!isOptIn("altTags")) {
+ return this
+ }
+
+ try {
+ const images = await generateProductAltTags(this.product)
+ this.product = {
+ ...this.product,
+ images: images.filter(Boolean),
+ }
+ } catch (e) {
+ console.error(e)
+ }
+ return this
+ }
+
+ build(): CommerceProduct {
+ return this.product
}
}
-function buildCategoryMap(categories: PlatformMenu["items"]) {
+async function generateProductAltTags(product: PlatformProduct): Promise<(PlatformImage | undefined)[]> {
+ try {
+ const altTagAwareImages = await Promise.all(product.images?.slice(0, 1).map(mapper).filter(Boolean))
+ return [...altTagAwareImages, ...product.images?.slice(1)?.filter(Boolean)] || []
+ } catch (e) {
+ console.error(e)
+ return product.images // graceful exit
+ }
+}
+
+async function mapper(image: PlatformImage) {
+ const output = await generateImageCaption(image.url)
+ return { ...image, altText: output?.replace("Caption:", "") || "" }
+}
+
+export function buildCategoryMap(items: PlatformMenu["items"]): Map {
const categoryMap = new Map()
- function traverse(items: PlatformMenu["items"], path: string[]) {
+ const traverse = (items: PlatformMenu["items"], path: string[]) => {
for (const item of items) {
- const newPath = [...path, item.resource!.handle]
- categoryMap.set(item.resource!.handle, newPath)
+ const newPath = [...path, item.resource?.handle || ""]
+ categoryMap.set(item.resource?.handle || "", newPath)
if (item.items && item.items.length > 0) {
traverse(item.items, newPath)
}
}
}
- traverse(categories, [])
-
+ traverse(items, [])
return categoryMap
}
-function generateHierarchicalCategories(tags: PlatformProduct["tags"], categoryMap: Map) {
- const hierarchicalCategories: Record<"lvl0" | "lvl1" | "lvl2", string[]> = { lvl0: [], lvl1: [], lvl2: [] }
-
- if (!tags.length || !categoryMap.size) return hierarchicalCategories
+export function generateHierarchicalCategories(collections: PlatformProduct["tags"], categoryMap: Map, separator: string = " > ") {
+ const hierarchicalCategories: { lvl0: string[]; lvl1: string[]; lvl2: string[] } = { lvl0: [], lvl1: [], lvl2: [] }
- tags.forEach((tag) => {
+ collections.forEach((tag) => {
const path = categoryMap.get(tag)
if (path) {
if (path.length > 0 && !hierarchicalCategories.lvl0.includes(path[0])) {
hierarchicalCategories.lvl0.push(path[0])
}
if (path.length > 1) {
- const lvl1Path = path.slice(0, 2).join(" > ")
+ const lvl1Path = path.slice(0, 2).join(separator)
if (!hierarchicalCategories.lvl1.includes(lvl1Path)) {
hierarchicalCategories.lvl1.push(lvl1Path)
}
}
if (path.length > 2) {
- const lvl2Path = path.slice(0, 3).join(" > ")
+ const lvl2Path = path.slice(0, 3).join(separator)
if (!hierarchicalCategories.lvl2.includes(lvl2Path)) {
hierarchicalCategories.lvl2.push(lvl2Path)
}
@@ -65,19 +129,3 @@ function generateHierarchicalCategories(tags: PlatformProduct["tags"], categoryM
return hierarchicalCategories
}
-
-async function generateProductAltTags(product: PlatformProduct): Promise<(PlatformImage | undefined)[]> {
- try {
- const altTagAwareImages = await Promise.all(product?.images?.slice(0, 1).map(mapper).filter(Boolean))
- return [...altTagAwareImages, ...product?.images?.slice(1)?.filter(Boolean)] || []
- } catch (e) {
- console.error(e)
- return product.images // graceful exit
- }
-
- async function mapper(image: PlatformImage) {
- const output = await generateImageCaption(image.url)
-
- return { ...image, altText: output?.replace("Caption:", "") || "" }
- }
-}
diff --git a/starters/shopify-algolia/utils/opt-in.ts b/starters/shopify-algolia/utils/opt-in.ts
index 417cd122..bd6ae0b4 100644
--- a/starters/shopify-algolia/utils/opt-in.ts
+++ b/starters/shopify-algolia/utils/opt-in.ts
@@ -1,6 +1,6 @@
import { env } from "env.mjs"
-type Feature = "reviews" | "ai-reviews"
+type Feature = "reviews" | "ai-reviews" | "altTags"
const features: Record> = {
reviews: {
@@ -11,6 +11,10 @@ const features: Record> = {
message: "No keys provided for ai reviews summary feautre, to opt-in set envrioment variables: OpenAI API, JUDGE_API_TOKEN ",
predicate: !!env.OPENAI_API_KEY,
},
+ altTags: {
+ message: "No keys provided for alt tags feature, to opt-in set environment variables: REPLICATE_API_KEY",
+ predicate: !!env.REPLICATE_API_KEY,
+ },
}
const optInNotification = ({ message, source }: { message: string; source?: string }) => {
diff --git a/starters/shopify-algolia/yarn.lock b/starters/shopify-algolia/yarn.lock
index 6c51ebfe..7bba8e49 100644
--- a/starters/shopify-algolia/yarn.lock
+++ b/starters/shopify-algolia/yarn.lock
@@ -1329,6 +1329,126 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
+"@esbuild/aix-ppc64@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz#b57697945b50e99007b4c2521507dc613d4a648c"
+ integrity sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==
+
+"@esbuild/android-arm64@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz#1add7e0af67acefd556e407f8497e81fddad79c0"
+ integrity sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==
+
+"@esbuild/android-arm@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.24.0.tgz#ab7263045fa8e090833a8e3c393b60d59a789810"
+ integrity sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==
+
+"@esbuild/android-x64@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.24.0.tgz#e8f8b196cfdfdd5aeaebbdb0110983460440e705"
+ integrity sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==
+
+"@esbuild/darwin-arm64@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz#2d0d9414f2acbffd2d86e98253914fca603a53dd"
+ integrity sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==
+
+"@esbuild/darwin-x64@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz#33087aab31a1eb64c89daf3d2cf8ce1775656107"
+ integrity sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==
+
+"@esbuild/freebsd-arm64@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz#bb76e5ea9e97fa3c753472f19421075d3a33e8a7"
+ integrity sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==
+
+"@esbuild/freebsd-x64@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz#e0e2ce9249fdf6ee29e5dc3d420c7007fa579b93"
+ integrity sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==
+
+"@esbuild/linux-arm64@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz#d1b2aa58085f73ecf45533c07c82d81235388e75"
+ integrity sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==
+
+"@esbuild/linux-arm@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz#8e4915df8ea3e12b690a057e77a47b1d5935ef6d"
+ integrity sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==
+
+"@esbuild/linux-ia32@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz#8200b1110666c39ab316572324b7af63d82013fb"
+ integrity sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==
+
+"@esbuild/linux-loong64@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz#6ff0c99cf647504df321d0640f0d32e557da745c"
+ integrity sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==
+
+"@esbuild/linux-mips64el@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz#3f720ccd4d59bfeb4c2ce276a46b77ad380fa1f3"
+ integrity sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==
+
+"@esbuild/linux-ppc64@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz#9d6b188b15c25afd2e213474bf5f31e42e3aa09e"
+ integrity sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==
+
+"@esbuild/linux-riscv64@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz#f989fdc9752dfda286c9cd87c46248e4dfecbc25"
+ integrity sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==
+
+"@esbuild/linux-s390x@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz#29ebf87e4132ea659c1489fce63cd8509d1c7319"
+ integrity sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==
+
+"@esbuild/linux-x64@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz#4af48c5c0479569b1f359ffbce22d15f261c0cef"
+ integrity sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==
+
+"@esbuild/netbsd-x64@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz#1ae73d23cc044a0ebd4f198334416fb26c31366c"
+ integrity sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==
+
+"@esbuild/openbsd-arm64@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz#5d904a4f5158c89859fd902c427f96d6a9e632e2"
+ integrity sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==
+
+"@esbuild/openbsd-x64@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz#4c8aa88c49187c601bae2971e71c6dc5e0ad1cdf"
+ integrity sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==
+
+"@esbuild/sunos-x64@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz#8ddc35a0ea38575fa44eda30a5ee01ae2fa54dd4"
+ integrity sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==
+
+"@esbuild/win32-arm64@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz#6e79c8543f282c4539db684a207ae0e174a9007b"
+ integrity sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==
+
+"@esbuild/win32-ia32@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz#057af345da256b7192d18b676a02e95d0fa39103"
+ integrity sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==
+
+"@esbuild/win32-x64@0.24.0":
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz#168ab1c7e1c318b922637fad8f339d48b01e1244"
+ integrity sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==
+
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
version "4.4.1"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56"
@@ -3025,6 +3145,96 @@
resolved "https://registry.yarnpkg.com/@repeaterjs/repeater/-/repeater-3.0.6.tgz#be23df0143ceec3c69f8b6c2517971a5578fdaa2"
integrity sha512-Javneu5lsuhwNCryN+pXH93VPQ8g0dBX7wItHFgYiwQmzE1sVdg5tWHiOgHywzL2W21XQopa7IwIEnNbmeUJYA==
+"@rollup/rollup-android-arm-eabi@4.24.4":
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.4.tgz#c460b54c50d42f27f8254c435a4f3b3e01910bc8"
+ integrity sha512-jfUJrFct/hTA0XDM5p/htWKoNNTbDLY0KRwEt6pyOA6k2fmk0WVwl65PdUdJZgzGEHWx+49LilkcSaumQRyNQw==
+
+"@rollup/rollup-android-arm64@4.24.4":
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.4.tgz#96e01f3a04675d8d5973ab8d3fd6bc3be21fa5e1"
+ integrity sha512-j4nrEO6nHU1nZUuCfRKoCcvh7PIywQPUCBa2UsootTHvTHIoIu2BzueInGJhhvQO/2FTRdNYpf63xsgEqH9IhA==
+
+"@rollup/rollup-darwin-arm64@4.24.4":
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.4.tgz#9b2ec23b17b47cbb2f771b81f86ede3ac6730bce"
+ integrity sha512-GmU/QgGtBTeraKyldC7cDVVvAJEOr3dFLKneez/n7BvX57UdhOqDsVwzU7UOnYA7AAOt+Xb26lk79PldDHgMIQ==
+
+"@rollup/rollup-darwin-x64@4.24.4":
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.4.tgz#f30e4ee6929e048190cf10e0daa8e8ae035b6e46"
+ integrity sha512-N6oDBiZCBKlwYcsEPXGDE4g9RoxZLK6vT98M8111cW7VsVJFpNEqvJeIPfsCzbf0XEakPslh72X0gnlMi4Ddgg==
+
+"@rollup/rollup-freebsd-arm64@4.24.4":
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.4.tgz#c54b2373ec5bcf71f08c4519c7ae80a0b6c8e03b"
+ integrity sha512-py5oNShCCjCyjWXCZNrRGRpjWsF0ic8f4ieBNra5buQz0O/U6mMXCpC1LvrHuhJsNPgRt36tSYMidGzZiJF6mw==
+
+"@rollup/rollup-freebsd-x64@4.24.4":
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.4.tgz#3bc53aa29d5a34c28ba8e00def76aa612368458e"
+ integrity sha512-L7VVVW9FCnTTp4i7KrmHeDsDvjB4++KOBENYtNYAiYl96jeBThFfhP6HVxL74v4SiZEVDH/1ILscR5U9S4ms4g==
+
+"@rollup/rollup-linux-arm-gnueabihf@4.24.4":
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.4.tgz#c85aedd1710c9e267ee86b6d1ce355ecf7d9e8d9"
+ integrity sha512-10ICosOwYChROdQoQo589N5idQIisxjaFE/PAnX2i0Zr84mY0k9zul1ArH0rnJ/fpgiqfu13TFZR5A5YJLOYZA==
+
+"@rollup/rollup-linux-arm-musleabihf@4.24.4":
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.4.tgz#e77313408bf13995aecde281aec0cceb08747e42"
+ integrity sha512-ySAfWs69LYC7QhRDZNKqNhz2UKN8LDfbKSMAEtoEI0jitwfAG2iZwVqGACJT+kfYvvz3/JgsLlcBP+WWoKCLcw==
+
+"@rollup/rollup-linux-arm64-gnu@4.24.4":
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.4.tgz#633f632397b3662108cfaa1abca2a80b85f51102"
+ integrity sha512-uHYJ0HNOI6pGEeZ/5mgm5arNVTI0nLlmrbdph+pGXpC9tFHFDQmDMOEqkmUObRfosJqpU8RliYoGz06qSdtcjg==
+
+"@rollup/rollup-linux-arm64-musl@4.24.4":
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.4.tgz#63edd72b29c4cced93e16113a68e1be9fef88907"
+ integrity sha512-38yiWLemQf7aLHDgTg85fh3hW9stJ0Muk7+s6tIkSUOMmi4Xbv5pH/5Bofnsb6spIwD5FJiR+jg71f0CH5OzoA==
+
+"@rollup/rollup-linux-powerpc64le-gnu@4.24.4":
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.4.tgz#a9418a4173df80848c0d47df0426a0bf183c4e75"
+ integrity sha512-q73XUPnkwt9ZNF2xRS4fvneSuaHw2BXuV5rI4cw0fWYVIWIBeDZX7c7FWhFQPNTnE24172K30I+dViWRVD9TwA==
+
+"@rollup/rollup-linux-riscv64-gnu@4.24.4":
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.4.tgz#bc9c195db036a27e5e3339b02f51526b4ce1e988"
+ integrity sha512-Aie/TbmQi6UXokJqDZdmTJuZBCU3QBDA8oTKRGtd4ABi/nHgXICulfg1KI6n9/koDsiDbvHAiQO3YAUNa/7BCw==
+
+"@rollup/rollup-linux-s390x-gnu@4.24.4":
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.4.tgz#1651fdf8144ae89326c01da5d52c60be63e71a82"
+ integrity sha512-P8MPErVO/y8ohWSP9JY7lLQ8+YMHfTI4bAdtCi3pC2hTeqFJco2jYspzOzTUB8hwUWIIu1xwOrJE11nP+0JFAQ==
+
+"@rollup/rollup-linux-x64-gnu@4.24.4":
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.4.tgz#e473de5e4acb95fcf930a35cbb7d3e8080e57a6f"
+ integrity sha512-K03TljaaoPK5FOyNMZAAEmhlyO49LaE4qCsr0lYHUKyb6QacTNF9pnfPpXnFlFD3TXuFbFbz7tJ51FujUXkXYA==
+
+"@rollup/rollup-linux-x64-musl@4.24.4":
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.4.tgz#0af12dd2578c29af4037f0c834b4321429dd5b01"
+ integrity sha512-VJYl4xSl/wqG2D5xTYncVWW+26ICV4wubwN9Gs5NrqhJtayikwCXzPL8GDsLnaLU3WwhQ8W02IinYSFJfyo34Q==
+
+"@rollup/rollup-win32-arm64-msvc@4.24.4":
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.4.tgz#e48e78cdd45313b977c1390f4bfde7ab79be8871"
+ integrity sha512-ku2GvtPwQfCqoPFIJCqZ8o7bJcj+Y54cZSr43hHca6jLwAiCbZdBUOrqE6y29QFajNAzzpIOwsckaTFmN6/8TA==
+
+"@rollup/rollup-win32-ia32-msvc@4.24.4":
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.4.tgz#a3fc8536d243fe161c796acb93eba43c250f311c"
+ integrity sha512-V3nCe+eTt/W6UYNr/wGvO1fLpHUrnlirlypZfKCT1fG6hWfqhPgQV/K/mRBXBpxc0eKLIF18pIOFVPh0mqHjlg==
+
+"@rollup/rollup-win32-x64-msvc@4.24.4":
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.4.tgz#e2a9d1fd56524103a6cc8a54404d9d3ebc73c454"
+ integrity sha512-LTw1Dfd0mBIEqUVCxbvTE/LLo+9ZxVC9k99v1v4ahg9Aak6FpqOfNu5kRkeTAn0wphoC4JU7No1/rL+bBCEwhg==
+
"@rtsao/scc@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8"
@@ -3359,6 +3569,11 @@
resolved "https://registry.yarnpkg.com/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz#dcef10a69d357fe9d43ac4ff2eca6b85dbf466af"
integrity sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==
+"@types/estree@1.0.6":
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
+ integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
+
"@types/graceful-fs@^4.1.3":
version "4.1.9"
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4"
@@ -4558,6 +4773,13 @@ builtin-modules@^3.3.0:
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6"
integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==
+bundle-require@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/bundle-require/-/bundle-require-5.0.0.tgz#071521bdea6534495cf23e92a83f889f91729e93"
+ integrity sha512-GuziW3fSSmopcx4KRymQEJVbZUfqlCqcq7dvs6TYwKRZiegK/2buMxQTPs6MGlNv50wms1699qYO54R8XfRX4w==
+ dependencies:
+ load-tsconfig "^0.2.3"
+
busboy@1.6.0, busboy@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
@@ -4565,6 +4787,11 @@ busboy@1.6.0, busboy@^1.6.0:
dependencies:
streamsearch "^1.1.0"
+cac@^6.7.14:
+ version "6.7.14"
+ resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959"
+ integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==
+
call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
@@ -4698,6 +4925,13 @@ chokidar@^3.5.3:
optionalDependencies:
fsevents "~2.3.2"
+chokidar@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.1.tgz#4a6dff66798fb0f72a94f616abbd7e1a19f31d41"
+ integrity sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==
+ dependencies:
+ readdirp "^4.0.1"
+
chownr@^1.1.1:
version "1.1.4"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
@@ -4865,6 +5099,11 @@ confusing-browser-globals@^1.0.11:
resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81"
integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==
+consola@^3.2.3:
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/consola/-/consola-3.2.3.tgz#0741857aa88cfa0d6fd53f1cff0375136e98502f"
+ integrity sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==
+
constant-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-3.0.4.tgz#3b84a9aeaf4cf31ec45e6bf5de91bdfb0589faf1"
@@ -5041,7 +5280,7 @@ debounce@^1.2.0, debounce@^1.2.1:
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5"
integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==
-debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5:
+debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.3.7:
version "4.3.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
@@ -5267,7 +5506,7 @@ dotenv@16.0.3:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07"
integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==
-dotenv@^16.0.0:
+dotenv@^16.0.0, dotenv@^16.4.5:
version "16.4.5"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
@@ -5512,6 +5751,36 @@ es-vary@^0.0.8:
resolved "https://registry.yarnpkg.com/es-vary/-/es-vary-0.0.8.tgz#4dc62235dda14e51e16b6690e564a9e5d40e3df0"
integrity sha512-fiERjQiCHrXUAToNRT/sh7MtXnfei9n7cF9oVQRUEp9L5BGXsTKSPaXq8L+4v0c/ezfvuTWd/f0JSl5IBRUvSg==
+esbuild@^0.24.0:
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.24.0.tgz#f2d470596885fcb2e91c21eb3da3b3c89c0b55e7"
+ integrity sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==
+ optionalDependencies:
+ "@esbuild/aix-ppc64" "0.24.0"
+ "@esbuild/android-arm" "0.24.0"
+ "@esbuild/android-arm64" "0.24.0"
+ "@esbuild/android-x64" "0.24.0"
+ "@esbuild/darwin-arm64" "0.24.0"
+ "@esbuild/darwin-x64" "0.24.0"
+ "@esbuild/freebsd-arm64" "0.24.0"
+ "@esbuild/freebsd-x64" "0.24.0"
+ "@esbuild/linux-arm" "0.24.0"
+ "@esbuild/linux-arm64" "0.24.0"
+ "@esbuild/linux-ia32" "0.24.0"
+ "@esbuild/linux-loong64" "0.24.0"
+ "@esbuild/linux-mips64el" "0.24.0"
+ "@esbuild/linux-ppc64" "0.24.0"
+ "@esbuild/linux-riscv64" "0.24.0"
+ "@esbuild/linux-s390x" "0.24.0"
+ "@esbuild/linux-x64" "0.24.0"
+ "@esbuild/netbsd-x64" "0.24.0"
+ "@esbuild/openbsd-arm64" "0.24.0"
+ "@esbuild/openbsd-x64" "0.24.0"
+ "@esbuild/sunos-x64" "0.24.0"
+ "@esbuild/win32-arm64" "0.24.0"
+ "@esbuild/win32-ia32" "0.24.0"
+ "@esbuild/win32-x64" "0.24.0"
+
escalade@^3.1.1, escalade@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
@@ -6085,6 +6354,11 @@ fbjs@^3.0.0:
setimmediate "^1.0.5"
ua-parser-js "^1.0.35"
+fdir@^6.4.2:
+ version "6.4.2"
+ resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.2.tgz#ddaa7ce1831b161bc3657bb99cb36e1622702689"
+ integrity sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==
+
figures@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
@@ -6339,6 +6613,18 @@ glob@^10.3.10:
package-json-from-dist "^1.0.0"
path-scurry "^1.11.1"
+glob@^11.0.0:
+ version "11.0.0"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.0.tgz#6031df0d7b65eaa1ccb9b29b5ced16cea658e77e"
+ integrity sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==
+ dependencies:
+ foreground-child "^3.1.0"
+ jackspeak "^4.0.1"
+ minimatch "^10.0.0"
+ minipass "^7.1.2"
+ package-json-from-dist "^1.0.0"
+ path-scurry "^2.0.0"
+
glob@^7.1.1, glob@^7.1.3, glob@^7.1.4:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
@@ -7084,6 +7370,13 @@ jackspeak@^3.1.2:
optionalDependencies:
"@pkgjs/parseargs" "^0.11.0"
+jackspeak@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.0.2.tgz#11f9468a3730c6ff6f56823a820d7e3be9bef015"
+ integrity sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==
+ dependencies:
+ "@isaacs/cliui" "^8.0.2"
+
jake@^10.8.5:
version "10.9.2"
resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f"
@@ -7491,6 +7784,11 @@ jose@^5.0.0:
resolved "https://registry.yarnpkg.com/jose/-/jose-5.9.6.tgz#77f1f901d88ebdc405e57cce08d2a91f47521883"
integrity sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==
+joycon@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03"
+ integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==
+
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -7690,7 +7988,7 @@ lilconfig@^2.1.0:
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==
-lilconfig@^3.0.0:
+lilconfig@^3.0.0, lilconfig@^3.1.1:
version "3.1.2"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.2.tgz#e4a7c3cb549e3a606c8dcc32e5ae1005e62c05cb"
integrity sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==
@@ -7714,6 +8012,11 @@ listr2@^4.0.5:
through "^2.3.8"
wrap-ansi "^7.0.0"
+load-tsconfig@^0.2.3:
+ version "0.2.5"
+ resolved "https://registry.yarnpkg.com/load-tsconfig/-/load-tsconfig-0.2.5.tgz#453b8cd8961bfb912dea77eb6c168fe8cca3d3a1"
+ integrity sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==
+
locate-path@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
@@ -7743,6 +8046,11 @@ lodash.indexof@^4.0.5:
resolved "https://registry.yarnpkg.com/lodash.indexof/-/lodash.indexof-4.0.5.tgz#53714adc2cddd6ed87638f893aa9b6c24e31ef3c"
integrity sha512-t9wLWMQsawdVmf6/IcAgVGqAJkNzYVcn4BHYZKTPW//l7N5Oq7Bq138BaVk19agcsPZePcidSgTTw4NqS1nUAw==
+lodash.isequal@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
+ integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
+
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@@ -7753,6 +8061,11 @@ lodash.merge@^4.6.2:
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+lodash.omit@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60"
+ integrity sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==
+
lodash.sortby@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
@@ -7812,6 +8125,11 @@ lru-cache@^10.2.0:
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
+lru-cache@^11.0.0:
+ version "11.0.2"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.0.2.tgz#fbd8e7cf8211f5e7e5d91905c415a3f55755ca39"
+ integrity sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==
+
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@@ -7910,6 +8228,13 @@ minimatch@9.0.3:
dependencies:
brace-expansion "^2.0.1"
+minimatch@^10.0.0:
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b"
+ integrity sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==
+ dependencies:
+ brace-expansion "^2.0.1"
+
minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
@@ -8436,12 +8761,20 @@ path-scurry@^1.11.1:
lru-cache "^10.2.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
+path-scurry@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580"
+ integrity sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==
+ dependencies:
+ lru-cache "^11.0.0"
+ minipass "^7.1.2"
+
path-type@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
-picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.0:
+picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.0, picocolors@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
@@ -8451,6 +8784,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+picomatch@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab"
+ integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==
+
pify@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
@@ -8516,6 +8854,13 @@ postcss-load-config@^4.0.1:
lilconfig "^3.0.0"
yaml "^2.3.4"
+postcss-load-config@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-6.0.1.tgz#6fd7dcd8ae89badcf1b2d644489cbabf83aa8096"
+ integrity sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==
+ dependencies:
+ lilconfig "^3.1.1"
+
postcss-nested@^6.0.1:
version "6.2.0"
resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.2.0.tgz#4c2d22ab5f20b9cb61e2c5c5915950784d068131"
@@ -8821,6 +9166,11 @@ readable-stream@^3.1.1, readable-stream@^3.4.0:
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
+readdirp@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.0.2.tgz#388fccb8b75665da3abffe2d8f8ed59fe74c230a"
+ integrity sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==
+
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
@@ -9065,6 +9415,41 @@ rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
+rimraf@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-6.0.1.tgz#ffb8ad8844dd60332ab15f52bc104bc3ed71ea4e"
+ integrity sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==
+ dependencies:
+ glob "^11.0.0"
+ package-json-from-dist "^1.0.0"
+
+rollup@^4.24.0:
+ version "4.24.4"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.24.4.tgz#fdc76918de02213c95447c9ffff5e35dddb1d058"
+ integrity sha512-vGorVWIsWfX3xbcyAS+I047kFKapHYivmkaT63Smj77XwvLSJos6M1xGqZnBPFQFBRZDOcG1QnYEIxAvTr/HjA==
+ dependencies:
+ "@types/estree" "1.0.6"
+ optionalDependencies:
+ "@rollup/rollup-android-arm-eabi" "4.24.4"
+ "@rollup/rollup-android-arm64" "4.24.4"
+ "@rollup/rollup-darwin-arm64" "4.24.4"
+ "@rollup/rollup-darwin-x64" "4.24.4"
+ "@rollup/rollup-freebsd-arm64" "4.24.4"
+ "@rollup/rollup-freebsd-x64" "4.24.4"
+ "@rollup/rollup-linux-arm-gnueabihf" "4.24.4"
+ "@rollup/rollup-linux-arm-musleabihf" "4.24.4"
+ "@rollup/rollup-linux-arm64-gnu" "4.24.4"
+ "@rollup/rollup-linux-arm64-musl" "4.24.4"
+ "@rollup/rollup-linux-powerpc64le-gnu" "4.24.4"
+ "@rollup/rollup-linux-riscv64-gnu" "4.24.4"
+ "@rollup/rollup-linux-s390x-gnu" "4.24.4"
+ "@rollup/rollup-linux-x64-gnu" "4.24.4"
+ "@rollup/rollup-linux-x64-musl" "4.24.4"
+ "@rollup/rollup-win32-arm64-msvc" "4.24.4"
+ "@rollup/rollup-win32-ia32-msvc" "4.24.4"
+ "@rollup/rollup-win32-x64-msvc" "4.24.4"
+ fsevents "~2.3.2"
+
run-async@^2.4.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
@@ -9377,6 +9762,13 @@ source-map-support@0.5.13:
buffer-from "^1.0.0"
source-map "^0.6.0"
+source-map@0.8.0-beta.0:
+ version "0.8.0-beta.0"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11"
+ integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==
+ dependencies:
+ whatwg-url "^7.0.0"
+
source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
@@ -9632,7 +10024,7 @@ styled-jsx@5.1.1:
dependencies:
client-only "0.0.1"
-sucrase@^3.32.0:
+sucrase@^3.32.0, sucrase@^3.35.0:
version "3.35.0"
resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263"
integrity sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==
@@ -9829,6 +10221,19 @@ through@^2.3.6, through@^2.3.8:
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==
+tinyexec@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.1.tgz#0ab0daf93b43e2c211212396bdb836b468c97c98"
+ integrity sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==
+
+tinyglobby@^0.2.9:
+ version "0.2.10"
+ resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.10.tgz#e712cf2dc9b95a1f5c5bbd159720e15833977a0f"
+ integrity sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==
+ dependencies:
+ fdir "^6.4.2"
+ picomatch "^4.0.2"
+
title-case@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/title-case/-/title-case-3.0.3.tgz#bc689b46f02e411f1d1e1d081f7c3deca0489982"
@@ -9870,6 +10275,13 @@ tough-cookie@^4.1.2:
universalify "^0.2.0"
url-parse "^1.5.3"
+tr46@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
+ integrity sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==
+ dependencies:
+ punycode "^2.1.0"
+
tr46@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9"
@@ -9882,6 +10294,11 @@ tr46@~0.0.3:
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
+tree-kill@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
+ integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
+
ts-api-utils@^1.0.1, ts-api-utils@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.0.tgz#709c6f2076e511a81557f3d07a0cbd566ae8195c"
@@ -9961,6 +10378,28 @@ tslib@~2.6.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"
integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==
+tsup@^8.3.5:
+ version "8.3.5"
+ resolved "https://registry.yarnpkg.com/tsup/-/tsup-8.3.5.tgz#d55344e4756e924bf6f442e54e7d324b4471eee0"
+ integrity sha512-Tunf6r6m6tnZsG9GYWndg0z8dEV7fD733VBFzFJ5Vcm1FtlXB8xBD/rtrBi2a3YKEV7hHtxiZtW5EAVADoe1pA==
+ dependencies:
+ bundle-require "^5.0.0"
+ cac "^6.7.14"
+ chokidar "^4.0.1"
+ consola "^3.2.3"
+ debug "^4.3.7"
+ esbuild "^0.24.0"
+ joycon "^3.1.1"
+ picocolors "^1.1.1"
+ postcss-load-config "^6.0.1"
+ resolve-from "^5.0.0"
+ rollup "^4.24.0"
+ source-map "0.8.0-beta.0"
+ sucrase "^3.35.0"
+ tinyexec "^0.3.1"
+ tinyglobby "^0.2.9"
+ tree-kill "^1.2.2"
+
tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
@@ -10267,6 +10706,11 @@ webidl-conversions@^3.0.0:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
+webidl-conversions@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
+ integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
+
webidl-conversions@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
@@ -10319,6 +10763,15 @@ whatwg-url@^5.0.0:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
+whatwg-url@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"
+ integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==
+ dependencies:
+ lodash.sortby "^4.7.0"
+ tr46 "^1.0.1"
+ webidl-conversions "^4.0.2"
+
which-boxed-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
diff --git a/starters/shopify-meilisearch/app/api/feed/sync/route.ts b/starters/shopify-meilisearch/app/api/feed/sync/route.ts
index 038de440..5df727de 100644
--- a/starters/shopify-meilisearch/app/api/feed/sync/route.ts
+++ b/starters/shopify-meilisearch/app/api/feed/sync/route.ts
@@ -1,9 +1,13 @@
import type { PlatformProduct } from "lib/shopify/types"
import { env } from "env.mjs"
import { compareHmac } from "utils/compare-hmac"
-import { enrichProduct } from "utils/enrich-product"
+import { ProductEnrichmentBuilder } from "utils/enrich-product"
import { deleteCategories, deleteProducts, updateCategories, updateProducts } from "lib/meilisearch"
import { getCollection, getHierarchicalCollections, getProduct } from "lib/shopify"
+import { makeShopifyId } from "lib/shopify/utils"
+import { HIERARCHICAL_SEPARATOR } from "constants/index"
+import { isOptIn } from "utils/opt-in"
+import { getAllProductReviews } from "lib/reviews"
type SupportedTopic = "products/update" | "products/delete" | "products/create" | "collections/update" | "collections/delete" | "collections/create"
@@ -56,7 +60,6 @@ async function handleCollectionTopics(topic: SupportedTopic, { id }: RecordFiltering, searching, and adding to cart is disabled.
diff --git a/starters/shopify-meilisearch/lib/shopify/client.ts b/starters/shopify-meilisearch/lib/shopify/client.ts
index e549e135..f1da0488 100644
--- a/starters/shopify-meilisearch/lib/shopify/client.ts
+++ b/starters/shopify-meilisearch/lib/shopify/client.ts
@@ -57,6 +57,7 @@ import {
} from "./types"
import { env } from "env.mjs"
+import { cleanShopifyId, makeShopifyId } from "./utils"
interface CreateShopifyClientProps {
storeDomain: string
@@ -129,7 +130,7 @@ async function getHierarchicalCollections(client: StorefrontApiClient, handle: s
}
async function getProduct(client: StorefrontApiClient, id: string): Promise {
- const response = await client.request(getProductQuery, { variables: { id } })
+ const response = await client.request(getProductQuery, { variables: { id: makeShopifyId(id, "Product") } })
const product = response.data?.product
return normalizeProduct(product)
@@ -178,7 +179,7 @@ async function getAllPages(client: StorefrontApiClient): Promise {
- const status = await client.request(getProductStatusQuery, { variables: { id } })
+ const status = await client.request(getProductStatusQuery, { variables: { id: makeShopifyId(id, "Product") } })
return status.data?.product
}
@@ -226,7 +227,7 @@ async function getCollection(client: StorefrontApiClient, handle: string): Promi
}
async function getCollectionById(client: StorefrontApiClient, id: string): Promise {
- const collection = await client.request(getCollectionByIdQuery, { variables: { id } })
+ const collection = await client.request(getCollectionByIdQuery, { variables: { id: makeShopifyId(id, "Collection") } })
return normalizeCollection(collection.data?.collection)
}
@@ -257,7 +258,7 @@ async function updateUser(client: StorefrontApiClient, customerAccessToken: stri
async function getAdminProduct(client: AdminApiClient, id: string) {
const response = await client.request(getAdminProductQuery, {
- variables: { id: id.startsWith("gid://shopify/Product/") ? id : `gid://shopify/Product/${id}` },
+ variables: { id: makeShopifyId(id, "Product") },
})
if (!response.data?.product) return null
@@ -285,7 +286,7 @@ async function getAllProducts(client: StorefrontApiClient, limit: number = 250):
cursor = hasNextPage ? response.data?.products?.pageInfo?.endCursor : null
}
- return products
+ return products.map((product) => ({ ...product, id: cleanShopifyId(product.id, "Product") }))
}
async function getAllCollections(client: StorefrontApiClient, limit?: number) {
@@ -302,7 +303,7 @@ async function getAllCollections(client: StorefrontApiClient, limit?: number) {
cursor = hasNextPage ? response?.data?.collections?.pageInfo?.endCursor : null
}
- return collections
+ return collections.map((collection) => ({ ...collection, id: cleanShopifyId(collection.id, "Collection") }))
}
export const storefrontClient = createShopifyClient({
diff --git a/starters/shopify-meilisearch/lib/shopify/normalize.ts b/starters/shopify-meilisearch/lib/shopify/normalize.ts
index 7142b2aa..b2f58335 100644
--- a/starters/shopify-meilisearch/lib/shopify/normalize.ts
+++ b/starters/shopify-meilisearch/lib/shopify/normalize.ts
@@ -1,12 +1,13 @@
import { SingleCartQuery, SingleCollectionQuery, SingleProductQuery } from "./types/storefront.generated"
import type { PlatformCart, PlatformCartItem, PlatformCollection, PlatformProduct } from "./types"
+import { cleanShopifyId } from "./utils"
export function normalizeProduct(product: SingleProductQuery["product"]): PlatformProduct | null {
if (!product) return null
const { id, handle, title, description, vendor, descriptionHtml, options, priceRange, variants, featuredImage, images, tags, updatedAt, createdAt, collections, seo } = product
return {
- id,
+ id: cleanShopifyId(id, "Product"),
handle,
title,
description,
@@ -46,5 +47,5 @@ export function normalizeCollection(collection: SingleCollectionQuery["collectio
if (!collection) return null
const { id, handle, title, descriptionHtml, seo, image, updatedAt, description } = collection
- return { id, handle, title, descriptionHtml, seo, image, updatedAt, description }
+ return { id: cleanShopifyId(id, "Collection"), handle, title, descriptionHtml, seo, image, updatedAt, description }
}
diff --git a/starters/shopify-meilisearch/lib/shopify/types/storefront.generated.d.ts b/starters/shopify-meilisearch/lib/shopify/types/storefront.generated.d.ts
index 8c2c6d95..94739202 100644
--- a/starters/shopify-meilisearch/lib/shopify/types/storefront.generated.d.ts
+++ b/starters/shopify-meilisearch/lib/shopify/types/storefront.generated.d.ts
@@ -544,7 +544,8 @@ export type SingleCollectionQuery = {
}
export type CollectionsQueryVariables = StorefrontTypes.Exact<{
- limit?: StorefrontTypes.InputMaybe
+ first?: StorefrontTypes.InputMaybe
+ after?: StorefrontTypes.InputMaybe
}>
export type CollectionsQuery = {
@@ -555,6 +556,7 @@ export type CollectionsQuery = {
seo: Pick
}
}>
+ pageInfo: Pick
}
}
@@ -716,7 +718,7 @@ interface GeneratedQueryTypes {
return: SingleCollectionQuery
variables: SingleCollectionQueryVariables
}
- "#graphql\n query Collections($limit: Int = 250) {\n collections(first: $limit, sortKey: TITLE) {\n edges {\n node {\n ...singleCollection\n }\n }\n }\n }\n #graphql\n fragment singleCollection on Collection {\n handle\n image {\n ...singleImage\n }\n title\n descriptionHtml\n id\n description\n seo {\n ...seo\n }\n updatedAt\n }\n #graphql\n fragment seo on SEO {\n description\n title\n }\n\n #graphql\n fragment singleImage on Image {\n url\n altText\n width\n height\n }\n\n\n": {
+ "#graphql\n query Collections($first: Int = 250, $after: String) {\n collections(first: $first, after: $after, sortKey: TITLE) {\n edges {\n node {\n ...singleCollection\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n #graphql\n fragment singleCollection on Collection {\n handle\n image {\n ...singleImage\n }\n title\n descriptionHtml\n id\n description\n seo {\n ...seo\n }\n updatedAt\n }\n #graphql\n fragment seo on SEO {\n description\n title\n }\n\n #graphql\n fragment singleImage on Image {\n url\n altText\n width\n height\n }\n\n\n": {
return: CollectionsQuery
variables: CollectionsQueryVariables
}
diff --git a/starters/shopify-meilisearch/lib/shopify/utils.ts b/starters/shopify-meilisearch/lib/shopify/utils.ts
new file mode 100644
index 00000000..3349ec83
--- /dev/null
+++ b/starters/shopify-meilisearch/lib/shopify/utils.ts
@@ -0,0 +1,7 @@
+export function makeShopifyId(id: string, type: "Product" | "Collection") {
+ return id.startsWith("gid://shopify/") ? id : `gid://shopify/${type}/${id}`
+}
+
+export function cleanShopifyId(id: string, type: "Product" | "Collection") {
+ return id.replace(`gid://shopify/${type}/`, "")
+}
diff --git a/starters/shopify-meilisearch/package.json b/starters/shopify-meilisearch/package.json
index e0e29086..83691b02 100644
--- a/starters/shopify-meilisearch/package.json
+++ b/starters/shopify-meilisearch/package.json
@@ -20,7 +20,7 @@
"codegen": "graphql-codegen && graphql-codegen -p admin",
"coupling-graph": "npx madge --extensions js,jsx,ts,tsx,css,md,mdx ./ --exclude '.next|tailwind.config.js|reset.d.ts|prettier.config.js|postcss.config.js|playwright.config.ts|next.config.js|next-env.d.ts|instrumentation.ts|e2e/|README.md|eslintrc.js' --image graph.svg",
"sync": "yarn build:scripts && node scripts/dist/sync.mjs",
- "build:scripts": "tsup scripts/ -d scripts/dist"
+ "build:scripts": "rimraf scripts/dist && tsup scripts/ -d scripts/dist"
},
"dependencies": {
"@ai-sdk/openai": "^0.0.16",
@@ -109,6 +109,7 @@
"patch-package": "^8.0.0",
"prettier": "3.2.5",
"prettier-plugin-tailwindcss": "^0.5.12",
+ "rimraf": "^6.0.1",
"server-only": "^0.0.1",
"tailwind-merge": "^2.2.2",
"tailwindcss": "^3.4.1",
diff --git a/starters/shopify-meilisearch/scripts/sync.ts b/starters/shopify-meilisearch/scripts/sync.ts
index 4daf9eea..b864fab4 100644
--- a/starters/shopify-meilisearch/scripts/sync.ts
+++ b/starters/shopify-meilisearch/scripts/sync.ts
@@ -2,9 +2,13 @@ import isEqual from "lodash.isequal"
import { HIERARCHICAL_SEPARATOR } from "../constants/index"
import { storefrontClient } from "../lib/shopify/client"
-import type { PlatformMenu, PlatformProduct } from "../lib/shopify/types"
+import type { PlatformProduct } from "../lib/shopify/types"
import { searchClient } from "../lib/meilisearch/client"
import { env } from "../env.mjs"
+import { isOptIn } from "utils/opt-in"
+import { reviewsClient } from "lib/reviews/client"
+import { CommerceProduct } from "types"
+import { ProductEnrichmentBuilder } from "utils/enrich-product"
async function sync() {
console.log("🚀 Starting sync process...")
@@ -28,7 +32,11 @@ async function sync() {
console.log(`✓ Found ${hierarchicalCategories.items.length} hierarchical categories`)
console.log("🔄 Enriching products with hierarchical data...")
- const enrichedProducts = await enrichProducts(allProducts, hierarchicalCategories.items, HIERARCHICAL_SEPARATOR)
+ const reviews = isOptIn("reviews") ? await reviewsClient.getAllProductReviews() : []
+
+ const enrichedProducts = allProducts.map((product) =>
+ new ProductEnrichmentBuilder(product).withHierarchicalCategories(hierarchicalCategories.items, HIERARCHICAL_SEPARATOR).withReviews(reviews).build()
+ )
console.log("📥 Fetching current Meilisearch indices...")
const { results: allIndexProducts } = await searchClient.getDocuments({
@@ -37,43 +45,29 @@ async function sync() {
limit: 10000,
},
})
+
+ console.log(`✓ Found ${allIndexProducts.length} products in Meilisearch`)
+
const { results: allIndexCategories } = await searchClient.getDocuments({
indexName: env.MEILISEARCH_CATEGORIES_INDEX,
options: {
limit: 10000,
},
})
+ console.log(`✓ Found ${allIndexCategories.length} categories in Meilisearch`)
console.log("🔍 Calculating differences...")
- const deltaProducts = enrichedProducts.filter((product) => {
- return !isEqual(allIndexProducts.find((p) => p.id === product.id))
- })
+ const deltaProducts = calculateProductDelta(enrichedProducts, allIndexProducts)
+ const deltaCategories = calculateCategoryDelta(allCategories, allIndexCategories)
- const deltaCategories = allCategories.filter((category) => !isEqual(allIndexCategories.find((c) => c.id === category.id)))
+ await updateMeilisearchDocuments(env.MEILISEARCH_PRODUCTS_INDEX, deltaProducts, "products")
+ await updateMeilisearchDocuments(env.MEILISEARCH_CATEGORIES_INDEX, deltaCategories, "categories")
if (deltaProducts.length === 0 && deltaCategories.length === 0) {
console.log("✨ Nothing to sync, looks like you're all set!")
return
}
- if (deltaProducts.length > 0) {
- console.log(`📤 Updating ${deltaProducts.length} products in Meilisearch...`)
- await searchClient.updateDocuments({
- indexName: env.MEILISEARCH_PRODUCTS_INDEX,
- documents: deltaProducts,
- })
- console.log("✓ Products updated successfully")
- }
-
- if (deltaCategories.length > 0) {
- console.log(`📤 Updating ${deltaCategories.length} categories in Meilisearch...`)
- await searchClient.updateDocuments({
- indexName: env.MEILISEARCH_CATEGORIES_INDEX,
- documents: deltaCategories,
- })
- console.log("✓ Categories updated successfully")
- }
-
console.log("🎉 Sync completed successfully!")
} catch (error) {
console.error("❌ Error during sync:", error instanceof Error ? error.message : error)
@@ -81,61 +75,39 @@ async function sync() {
}
}
-async function enrichProducts(products: PlatformProduct[], categories: PlatformMenu["items"], separator: string) {
- if (!categories.length) {
- return products
- }
- const categoryMap = await buildCategoryMap(categories)
+async function updateMeilisearchDocuments>(indexName: string, documents: T[], entityName: string) {
+ if (documents.length === 0) return
- return products.map((product) => ({
- ...product,
- hierarchicalCategories: generateHierarchicalCategories(product.collections, categoryMap, separator),
- }))
+ console.log(`📤 Updating ${documents.length} ${entityName} in Meilisearch...`)
+ await searchClient.updateDocuments({
+ indexName,
+ options: { primaryKey: "id" },
+ documents,
+ })
}
-async function buildCategoryMap(categories: PlatformMenu["items"]) {
- const categoryMap = new Map()
+function calculateProductDelta(enrichedProducts: CommerceProduct[], allIndexProducts: any[]) {
+ const allIndexProductsMap = new Map(allIndexProducts.map((product) => [product.id, product]))
- function traverse(items: PlatformMenu["items"], path: string[]) {
- for (const item of items) {
- const newPath = [...path, item.resource?.handle || ""]
- categoryMap.set(item.resource?.handle || "", newPath)
- if (item.items && item.items.length > 0) {
- traverse(item.items, newPath)
- }
+ return enrichedProducts.reduce>((acc, product) => {
+ const existingProduct = allIndexProductsMap.get(product.id)
+ if (!existingProduct || !isEqual(product, existingProduct)) {
+ acc.push(product)
}
- }
-
- traverse(categories, [])
-
- return categoryMap
+ return acc
+ }, [])
}
-function generateHierarchicalCategories(collections: PlatformProduct["collections"], categoryMap: Map, separator: string = " > ") {
- const hierarchicalCategories: { lvl0: string[]; lvl1: string[]; lvl2: string[] } = { lvl0: [], lvl1: [], lvl2: [] }
-
- collections.forEach(({ handle }) => {
- const path = categoryMap.get(handle)
- if (path) {
- if (path.length > 0 && !hierarchicalCategories.lvl0.includes(path[0])) {
- hierarchicalCategories.lvl0.push(path[0])
- }
- if (path.length > 1) {
- const lvl1Path = path.slice(0, 2).join(separator)
- if (!hierarchicalCategories.lvl1.includes(lvl1Path)) {
- hierarchicalCategories.lvl1.push(lvl1Path)
- }
- }
- if (path.length > 2) {
- const lvl2Path = path.slice(0, 3).join(separator)
- if (!hierarchicalCategories.lvl2.includes(lvl2Path)) {
- hierarchicalCategories.lvl2.push(lvl2Path)
- }
- }
- }
- })
+function calculateCategoryDelta(categories: PlatformProduct["collections"], allIndexCategories: any[]) {
+ const allIndexCategoriesMap = new Map(allIndexCategories.map((category) => [category.id, category]))
- return hierarchicalCategories
+ return categories.reduce>((acc, category) => {
+ const existingCategory = allIndexCategoriesMap.get(category.id)
+ if (!existingCategory || !isEqual(category, existingCategory)) {
+ acc.push(category)
+ }
+ return acc
+ }, [])
}
sync()
diff --git a/starters/shopify-meilisearch/types/index.ts b/starters/shopify-meilisearch/types/index.ts
index 490232a2..4e71e983 100644
--- a/starters/shopify-meilisearch/types/index.ts
+++ b/starters/shopify-meilisearch/types/index.ts
@@ -7,4 +7,9 @@ export type CommerceProduct = PlatformProduct & {
avgRating?: number
totalReviews?: number
reviewsSummary?: string
+ hierarchicalCategories?: {
+ lvl0?: string[]
+ lvl1?: string[]
+ lvl2?: string[]
+ }
}
diff --git a/starters/shopify-meilisearch/utils/enrich-product.ts b/starters/shopify-meilisearch/utils/enrich-product.ts
index 18cefe35..14f83661 100644
--- a/starters/shopify-meilisearch/utils/enrich-product.ts
+++ b/starters/shopify-meilisearch/utils/enrich-product.ts
@@ -1,46 +1,110 @@
import { generateImageCaption } from "lib/replicate"
+import { Review } from "lib/reviews/types"
import type { PlatformImage, PlatformMenu, PlatformProduct } from "lib/shopify/types"
+import { CommerceProduct } from "types"
+import { isOptIn } from "./opt-in"
/*
* Enrich product by attaching hierarchical categories to it
* Takes in all tags, and tries to find hierarchy against it
- * Currently just shopify focused
*/
-export const enrichProduct = async (product: PlatformProduct, collections: PlatformMenu["items"]) => {
- const categoryMap = buildCategoryMap(collections)
- const images = await generateProductAltTags(product)
- const hierarchicalCategories = generateHierarchicalCategories(product.tags, categoryMap)
+export class ProductEnrichmentBuilder {
+ private product: CommerceProduct
- return {
- ...product,
- images: images.filter(Boolean),
- hierarchicalCategories,
+ constructor(baseProduct: PlatformProduct) {
+ this.product = baseProduct
+ }
+
+ withHierarchicalCategories(collections: PlatformMenu["items"], separator: string): this {
+ const categoryMap = buildCategoryMap(collections)
+
+ if (!categoryMap.size) {
+ return this
+ }
+
+ this.product = {
+ ...this.product,
+ hierarchicalCategories: generateHierarchicalCategories(this.product.tags, categoryMap, separator),
+ }
+ return this
+ }
+
+ withReviews(allReviews: Review[]): this {
+ if (!isOptIn("reviews")) {
+ return this
+ }
+
+ const productReviews = allReviews.filter((review) => review.product_handle === this.product.handle)
+
+ if (productReviews.length) {
+ const avgRating = productReviews.reduce((acc, review) => acc + review.rating, 0) / productReviews.length || 0
+
+ this.product = {
+ ...this.product,
+ avgRating,
+ totalReviews: productReviews.length,
+ }
+ }
+ return this
+ }
+
+ async withAltTags(): Promise {
+ if (!isOptIn("altTags")) {
+ return this
+ }
+
+ try {
+ const images = await generateProductAltTags(this.product)
+ this.product = {
+ ...this.product,
+ images: images.filter(Boolean),
+ }
+ } catch (e) {
+ console.error(e)
+ }
+ return this
+ }
+
+ build(): CommerceProduct {
+ return this.product
}
}
-function buildCategoryMap(categories: PlatformMenu["items"]) {
+async function generateProductAltTags(product: PlatformProduct): Promise<(PlatformImage | undefined)[]> {
+ try {
+ const altTagAwareImages = await Promise.all(product.images?.slice(0, 1).map(mapper).filter(Boolean))
+ return [...altTagAwareImages, ...product.images?.slice(1)?.filter(Boolean)] || []
+ } catch (e) {
+ console.error(e)
+ return product.images // graceful exit
+ }
+}
+
+async function mapper(image: PlatformImage) {
+ const output = await generateImageCaption(image.url)
+ return { ...image, altText: output?.replace("Caption:", "") || "" }
+}
+
+export function buildCategoryMap(items: PlatformMenu["items"]): Map {
const categoryMap = new Map()
- function traverse(items: PlatformMenu["items"], path: string[]) {
+ const traverse = (items: PlatformMenu["items"], path: string[]) => {
for (const item of items) {
- const newPath = [...path, item.resource!.handle]
- categoryMap.set(item.resource!.handle, newPath)
+ const newPath = [...path, item.resource?.handle || ""]
+ categoryMap.set(item.resource?.handle || "", newPath)
if (item.items && item.items.length > 0) {
traverse(item.items, newPath)
}
}
}
- traverse(categories, [])
-
+ traverse(items, [])
return categoryMap
}
-function generateHierarchicalCategories(tags: PlatformProduct["tags"], categoryMap: Map) {
- const hierarchicalCategories: Record<"lvl0" | "lvl1" | "lvl2", string[]> = { lvl0: [], lvl1: [], lvl2: [] }
-
- if (!tags.length || !categoryMap.size) return hierarchicalCategories
+export function generateHierarchicalCategories(tags: CommerceProduct["tags"], categoryMap: Map, separator: string = " > ") {
+ const hierarchicalCategories: { lvl0: string[]; lvl1: string[]; lvl2: string[] } = { lvl0: [], lvl1: [], lvl2: [] }
tags.forEach((tag) => {
const path = categoryMap.get(tag)
@@ -49,13 +113,13 @@ function generateHierarchicalCategories(tags: PlatformProduct["tags"], categoryM
hierarchicalCategories.lvl0.push(path[0])
}
if (path.length > 1) {
- const lvl1Path = path.slice(0, 2).join(" > ")
+ const lvl1Path = path.slice(0, 2).join(separator)
if (!hierarchicalCategories.lvl1.includes(lvl1Path)) {
hierarchicalCategories.lvl1.push(lvl1Path)
}
}
if (path.length > 2) {
- const lvl2Path = path.slice(0, 3).join(" > ")
+ const lvl2Path = path.slice(0, 3).join(separator)
if (!hierarchicalCategories.lvl2.includes(lvl2Path)) {
hierarchicalCategories.lvl2.push(lvl2Path)
}
@@ -65,19 +129,3 @@ function generateHierarchicalCategories(tags: PlatformProduct["tags"], categoryM
return hierarchicalCategories
}
-
-async function generateProductAltTags(product: PlatformProduct): Promise<(PlatformImage | undefined)[]> {
- try {
- const altTagAwareImages = await Promise.all(product?.images?.slice(0, 1).map(mapper).filter(Boolean))
- return [...altTagAwareImages, ...product?.images?.slice(1)?.filter(Boolean)] || []
- } catch (e) {
- console.error(e)
- return product.images // graceful exit
- }
-
- async function mapper(image: PlatformImage) {
- const output = await generateImageCaption(image.url)
-
- return { ...image, altText: output?.replace("Caption:", "") || "" }
- }
-}
diff --git a/starters/shopify-meilisearch/utils/opt-in.ts b/starters/shopify-meilisearch/utils/opt-in.ts
index 417cd122..137624da 100644
--- a/starters/shopify-meilisearch/utils/opt-in.ts
+++ b/starters/shopify-meilisearch/utils/opt-in.ts
@@ -1,6 +1,6 @@
import { env } from "env.mjs"
-type Feature = "reviews" | "ai-reviews"
+type Feature = "reviews" | "ai-reviews" | "altTags"
const features: Record> = {
reviews: {
@@ -11,6 +11,10 @@ const features: Record> = {
message: "No keys provided for ai reviews summary feautre, to opt-in set envrioment variables: OpenAI API, JUDGE_API_TOKEN ",
predicate: !!env.OPENAI_API_KEY,
},
+ altTags: {
+ message: "No keys provided for alt tags feature, to opt-in set environment variables: REPLICATE_API_KEY",
+ predicate: !!env.REPLICATE_API_KEY,
+ },
}
const optInNotification = ({ message, source }: { message: string; source?: string }) => {
@@ -21,7 +25,7 @@ const optInNotification = ({ message, source }: { message: string; source?: stri
}
export const isOptIn = (feature: Feature) => {
- return features[feature].predicate
+ return !!features[feature].predicate
}
export const notifyOptIn = ({ feature, source }: { feature: Feature; source?: string }) => {
diff --git a/starters/shopify-meilisearch/yarn.lock b/starters/shopify-meilisearch/yarn.lock
index 3d33eee4..1032d110 100644
--- a/starters/shopify-meilisearch/yarn.lock
+++ b/starters/shopify-meilisearch/yarn.lock
@@ -6615,6 +6615,18 @@ glob@^10.3.10:
package-json-from-dist "^1.0.0"
path-scurry "^1.11.1"
+glob@^11.0.0:
+ version "11.0.0"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.0.tgz#6031df0d7b65eaa1ccb9b29b5ced16cea658e77e"
+ integrity sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==
+ dependencies:
+ foreground-child "^3.1.0"
+ jackspeak "^4.0.1"
+ minimatch "^10.0.0"
+ minipass "^7.1.2"
+ package-json-from-dist "^1.0.0"
+ path-scurry "^2.0.0"
+
glob@^7.1.1, glob@^7.1.3, glob@^7.1.4:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
@@ -7365,6 +7377,13 @@ jackspeak@^3.1.2:
optionalDependencies:
"@pkgjs/parseargs" "^0.11.0"
+jackspeak@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.0.2.tgz#11f9468a3730c6ff6f56823a820d7e3be9bef015"
+ integrity sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==
+ dependencies:
+ "@isaacs/cliui" "^8.0.2"
+
jake@^10.8.5:
version "10.9.2"
resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f"
@@ -8113,6 +8132,11 @@ lru-cache@^10.2.0:
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
+lru-cache@^11.0.0:
+ version "11.0.2"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.0.2.tgz#fbd8e7cf8211f5e7e5d91905c415a3f55755ca39"
+ integrity sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==
+
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@@ -8218,6 +8242,13 @@ minimatch@9.0.3:
dependencies:
brace-expansion "^2.0.1"
+minimatch@^10.0.0:
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b"
+ integrity sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==
+ dependencies:
+ brace-expansion "^2.0.1"
+
minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
@@ -8754,6 +8785,14 @@ path-scurry@^1.11.1:
lru-cache "^10.2.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
+path-scurry@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580"
+ integrity sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==
+ dependencies:
+ lru-cache "^11.0.0"
+ minipass "^7.1.2"
+
path-type@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
@@ -9400,6 +9439,14 @@ rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
+rimraf@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-6.0.1.tgz#ffb8ad8844dd60332ab15f52bc104bc3ed71ea4e"
+ integrity sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==
+ dependencies:
+ glob "^11.0.0"
+ package-json-from-dist "^1.0.0"
+
rollup@^4.24.0:
version "4.24.4"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.24.4.tgz#fdc76918de02213c95447c9ffff5e35dddb1d058"