v1 convert to Agility

This commit is contained in:
Joel Varty 2021-01-15 17:37:36 -05:00
parent 742ac5786e
commit 2dd8d59ae7
42 changed files with 1521 additions and 167 deletions

View File

@ -0,0 +1,23 @@
import pageTemplates from "components/agility-pageTemplates"
const AgilityPage = (props:any) => {
if (!props || !props.pageTemplateName) {
console.error(`Page object or template was not found.`)
return null
}
let AgilityPageTemplate = pageTemplates(props.pageTemplateName)
if (! AgilityPageTemplate) {
console.error(`${props.pageTemplateName} not found.`)
return null
}
return (
<AgilityPageTemplate {...props} />
)
}
export default AgilityPage

View File

@ -0,0 +1,35 @@
import React, { Component } from 'react';
import moduleComponents from "components/agility-modules"
function ContentZone({ name, page, dynamicPageItem }) {
function RenderModules() {
if (!page) return null
let modules = page.zones[name];
const modulesToRender = modules.map(m => {
const AgilityModule = moduleComponents(m.moduleName)
if (AgilityModule) {
return <AgilityModule key={m.item.contentID} page={page} dynamicPageItem={dynamicPageItem} {...m.item} />
} else {
console.error(`React Component for ${m.moduleName} was not found in the Agility Modules list.`)
}
})
return modulesToRender;
}
return (
<div>
<RenderModules />
</div>
)
}
export default ContentZone

View File

@ -0,0 +1,75 @@
import React, { Component, useState } from 'react';
import Link from 'next/link';
import {expandLinkedList} from "@agility/utils"
const GlobalFooter = (props) => {
const { globalFooterProps } = props;
return (
<div>FOOTER</div>
)
}
GlobalFooter.getCustomInitialProps = async function ({agility, languageCode, channelName}) {
const api = agility;
let contentItem = null;
//hack
return {}
try {
//get the global footer
let contentItemList = await api.getContentList({
referenceName: "globalfooter",
languageCode: languageCode
});
if (contentItemList?.length > 0) {
contentItem = contentItemList[0];
//resolve the links...
contentItem = await expandLinkedList({ agility, contentItem, languageCode,
fieldName: "column2Links",
sortIDField: "column2SortIDs"
})
contentItem = await expandLinkedList({ agility, contentItem, languageCode,
fieldName: "column3Links",
sortIDField: "column3SortIDs"
})
contentItem = await expandLinkedList({ agility, contentItem, languageCode,
fieldName: "column4Links",
sortIDField: "column4SortIDs"
})
}
} catch (error) {
if (console) console.error("Could not load global footer item.", error);
}
//return a clean object...
return {
siteName: contentItem.fields.siteName,
siteDescription: contentItem.fields.siteDescription,
column2Title: contentItem.fields.column2Title,
column3Title: contentItem.fields.column3Title,
column4Title: contentItem.fields.column4Title,
facebookURL: contentItem.fields.facebookURL,
twitterURL: contentItem.fields.twitterURL,
youTubeURL: contentItem.fields.youTubeURL,
column2Links: contentItem.fields.column2Links.map(link => link.fields.link),
column3Links: contentItem.fields.column3Links.map(link => link.fields.link),
column4Links: contentItem.fields.column4Links.map(link => link.fields.link),
}
}
export default GlobalFooter

View File

@ -0,0 +1,73 @@
import React, { Component, useState } from 'react';
import Link from 'next/link';
const GlobalHeader = (props) => {
const { globalHeaderProps, sitemapNode, page } = props;
const globalHeaderItem = globalHeaderProps.contentItem;
let siteName = globalHeaderItem?.fields.siteName || "Agility Starter 2020"
let logo = globalHeaderItem?.fields.logo || nulll
return (
<div>HEADER</div>
)
}
GlobalHeader.getCustomInitialProps = async function (props) {
const api = props.agility;
const languageCode = props.languageCode;
const channelName = props.channelName;
let contentItem = null;
let links = [];
//hack
return {}
try {
//get the global header
let contentItemList = await api.getContentList({
referenceName: "globalheader",
languageCode: languageCode
});
if (contentItemList && contentItemList.length) {
contentItem = contentItemList[0];
}
} catch (error) {
if (console) console.error("Could not load global header item.", error);
}
try {
//get the nested sitemap
let sitemap = await api.getSitemapNested({
channelName: channelName,
languageCode: languageCode,
});
//grab the top level links that are visible on menu
links = sitemap
.filter(node => node.visible.menu)
.map(node => {
return {
text: node.menuText || node.title,
path: node.path
}
})
} catch (error) {
if (console) console.error("Could not load nested sitemap.", error);
}
return {
contentItem,
links
}
}
export default GlobalHeader

View File

@ -0,0 +1,62 @@
import PreviewBar from './PreviewBar'
import GlobalHeader from './GlobalHeader'
import GlobalFooter from './GlobalFooter'
import { useRouter } from 'next/router'
import Head from 'next/head'
import dynamic from 'next/dynamic'
import tw from "twin.macro"
const MainElem = tw.main`p-8`;
import AnimationRevealPage from "helpers/AnimationRevealPage"
import Error from 'next/error'
function Layout(props) {
const { page, sitemapNode, dynamicPageItem, notFound } = props
// If the page is not yet generated, this will be displayed
// initially until getStaticProps() finishes running
const router = useRouter()
if (router.isFallback) {
return <div>Loading page...</div>
}
if (notFound === true) {
return <Error statusCode="404" />
}
const AgilityPageTemplate = dynamic(() => import('components/agility-pageTemplates/' + props.pageTemplateName));
if (dynamicPageItem?.seo?.metaDescription) {
page.seo.metaDescription = dynamicPageItem.seo.metaDescription
}
return (
<>
<Head>
<title>{sitemapNode?.title} - Agility CMS Sample Blog</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<meta name="description" content={page.seo.metaDescription} />
<meta name="generator" content="Agility CMS" />
<meta name="agility_timestamp" content={new Date().toLocaleString()} />
{dynamicPageItem?.seo?.ogImage &&
<meta property="og:image" content={dynamicPageItem.seo.ogImage} />
}
<link rel="stylesheet" href="/prose.css" />
</Head>
<PreviewBar {...props} />
<MainElem>
{/* <AnimationRevealPage disabled> */}
<GlobalHeader {...props} />
<AgilityPageTemplate {...props} />
<GlobalFooter {...props} />
{/* </AnimationRevealPage> */}
</MainElem>
</>
)
}
export default Layout

View File

@ -0,0 +1,60 @@
import React from 'react';
const PreviewBar = ({ isPreview, isDevelopmentMode }) => {
if (isPreview && !isDevelopmentMode) {
return (
<div className="agility-preview-bar">
<img className="agility-preview-bar__logo" src="https://media.agilitycms.com/preview-bar/2018-11/agility-logo-preview-bar-1.png" alt="Powered by Agility CMS" />
<span className="agility-preview-bar__text">You are viewing the latest changes in <strong>Preview Mode</strong>.</span>
<div>
<button className="agility-preview-bar__btn agility-preview-bar__btn-share" title="Click to generate a share-able link" onClick={getPreviewLink}>Share</button>
<button className="agility-preview-bar__btn" title="Click to exit preview" onClick={exitPreview}>Exit Preview</button>
</div>
</div>
)
} else if(isDevelopmentMode) {
return (
<div className="agility-preview-bar">
<img className="agility-preview-bar__logo" src="https://media.agilitycms.com/preview-bar/2018-11/agility-logo-preview-bar-1.png" alt="Powered by Agility CMS" />
<span className="agility-preview-bar__text">You are viewing the latest changes in <strong>Development Mode</strong>.</span>
<div></div>
</div>
)
} else {
return null
}
}
const exitPreview = () => {
const exit = confirm("Would you like to exit Preview Mode?");
if (exit === true) {
window.location = `/api/exitPreview?slug=${window.location.pathname}`;
}
}
const getPreviewLink = () => {
const xhr = new XMLHttpRequest();
xhr.onload = function () {
// Process our return data
if (xhr.status >= 200 && xhr.status < 300) {
// What do when the request is successful
const previewKey = xhr.responseText;
const previewLink = `${window.location.pathname}?agilitypreviewkey=${escape(previewKey)}`;
prompt("To share this page in preview mode with others, copy the link below:", previewLink);
} else {
// What do when the request fails
alert('Could not generate Preview Link. This indicates a problem with the API route that generates a Preview Link.')
}
};
// Create and send a GET request
xhr.open('GET', '/api/generatePreviewKey');
xhr.send();
}
export default PreviewBar;

View File

@ -0,0 +1,35 @@
import React, { FC } from 'react'
import { ProductCard } from '@components/product'
import { Grid, Marquee, Hero } from '@components/ui'
interface Fields {
}
interface Props {
fields: Fields,
customData: any
}
const BestsellingProducts:FC<Props> = ({fields, customData}) => {
const bestSelling = customData.bestSelling
return (
<Marquee variant="secondary">
{bestSelling.slice(0, 12).map(({ node }:any) => (
<ProductCard
key={node.path}
product={node}
variant="slim"
imgWidth={320}
imgHeight={320}
imgLayout="fixed"
/>
))}
</Marquee>
)
}
export default BestsellingProducts

View File

@ -0,0 +1,31 @@
import { FC } from "react"
import { Grid, Marquee, Hero } from '@components/ui'
import { ProductCard } from '@components/product'
interface Fields {
}
interface Props {
fields: Fields,
customData: any
}
const FeaturedProducts:FC<Props> = ({fields, customData}) => {
const featured:any = customData.featured
return (
<Grid layout="B">
{featured.map(({ node }:any, i:number) => (
<ProductCard
key={node.path}
product={node}
imgWidth={i === 1 ? 1080 : 540}
imgHeight={i === 1 ? 1080 : 540}
/>
))}
</Grid>
)
}
export default FeaturedProducts

View File

@ -0,0 +1,29 @@
import React, { FC } from 'react'
import { Hero } from '@components/ui'
import * as AgilityTypes from "@agility/types"
interface Fields {
title:string,
description:string
cTA?:AgilityTypes.URLField
}
interface Props {
fields: Fields
}
const HeroModule:FC<Props> = ({fields}) => {
return (
<Hero
headline={fields.title}
description={fields.description}
linkText={fields.cTA?.text}
linkUrl={fields.cTA?.href}
/>
)
}
export default HeroModule

View File

@ -0,0 +1,23 @@
import React, { FC } from 'react'
import { Hero } from '@components/ui'
import * as AgilityTypes from "@agility/types"
import { GetProductResult } from '@framework/api/operations/get-product'
import { ProductView } from '@components/product'
interface Fields {
}
interface Props {
fields: Fields,
dynamicPageItem:any
}
const HeroModule:FC<Props> = ({fields, dynamicPageItem}) => {
return (
<ProductView product={dynamicPageItem} />
)
}
export default HeroModule

View File

@ -0,0 +1,8 @@
const ProductListing = () => {
return (
<section>ProductListing</section>
)
}
export default ProductListing

View File

@ -0,0 +1,22 @@
import React, {FC} from 'react';
import { Text, Container } from '@components/ui'
interface Fields {
textblob:string,
}
interface Props {
fields: Fields
}
const RichTextArea:FC<Props> = ({fields}) => {
return (
<Container>
<Text className="prose prose-sm sm:prose lg:prose-lg xl:prose-xl" html={fields.textblob} />
</Container>
);
}
export default RichTextArea

View File

@ -0,0 +1,30 @@
import {FC} from "react"
import * as AgilityTypes from "@agility/types"
import RichTextArea from "./RichTextArea"
import BestsellingProducts from "./BestsellingProducts"
import ProductDetails from "./ProductDetails"
import FeaturedProducts from "./FeaturedProducts"
import ProductListing from "./ProductListing"
import Hero from "./Hero"
const allModules =[
{ name: "RichTextArea", module:RichTextArea },
{ name: "BestsellingProducts", module: BestsellingProducts },
{ name: "FeaturedProducts", module: FeaturedProducts},
{ name: "ProductListing", module: ProductListing},
{ name: "Hero", module: Hero},
{ name: "ProductDetails", module: ProductDetails }
]
/**
* Find the component for a module.
* @param moduleName
*/
const getModule = (moduleName:string):any | null => {
const obj = allModules.find(m => m.name.toLowerCase() === moduleName.toLowerCase())
if (!obj) return null
return obj.module
}
export default getModule

View File

@ -0,0 +1,14 @@
import React, { Component } from 'react';
import ContentZone from 'components/agility-global/ContentZone'
const MainTemplate = (props:any) => {
return (
<div className="one-column-template">
<ContentZone name='MainContentZone' {...props} />
</div>
);
}
export default MainTemplate;

View File

@ -0,0 +1,18 @@
import MainTemplate from "./MainTemplate"
interface TemplateObj {
name:string,
template:any
}
const allTemplates:[TemplateObj] =[
{ name: "MainTemplate", template:MainTemplate }
]
const getPageTemplate = (templateName:string):any => {
const obj = allTemplates.find(m => m.name.toLowerCase() === templateName.toLowerCase())
if (! obj) return null
return obj?.template
}
export default getPageTemplate

View File

@ -6,10 +6,13 @@ import Link from 'next/link'
interface Props {
className?: string
headline: string
description: string
description: string,
linkText?: string,
linkUrl?: string
}
const Hero: FC<Props> = ({ headline, description }) => {
const Hero: FC<Props> = ({ headline, description, linkText, linkUrl }) => {
return (
<div className="bg-black">
<Container>
@ -21,12 +24,14 @@ const Hero: FC<Props> = ({ headline, description }) => {
<p className="mt-5 text-xl leading-7 text-accent-2 text-white">
{description}
</p>
<Link href="/blog">
{ linkText && linkUrl &&
<Link href={linkUrl}>
<a className="text-white pt-3 font-bold hover:underline flex flex-row cursor-pointer w-max-content">
Read it here
{linkText}
<RightArrow width="20" heigh="20" className="ml-1" />
</a>
</Link>
}
</div>
</div>
</Container>

View File

@ -3,9 +3,9 @@ import s from './LoadingDots.module.css'
const LoadingDots: React.FC = () => {
return (
<span className={s.root}>
<span />
<span />
<span />
<span></span>
<span></span>
<span></span>
</span>
)
}

View File

@ -0,0 +1,51 @@
const agilityContentSync = require("@agility/content-sync");
const agilityFileSystem = require("@agility/content-sync/src/store-interface-filesystem");
const agilityConfig = {
guid: process.env.AGILITY_GUID, //Set your guid here
fetchAPIKey: process.env.AGILITY_API_FETCH_KEY, //Set your fetch apikey here
previewAPIKey: process.env.AGILITY_API_PREVIEW_KEY, //set your preview apikey
languageCode: "en-us", //the language for your website in Agility CMS
channelName: "website", //the name of your channel in Agility CMS
securityKey: process.env.AGILITY_SECURITY_KEY, //the website security key used to validate and generate preview keys
};
const getSyncClient = ({ isPreview, isDevelopmentMode }) => {
let cachePath = `node_modules/@agility/content-sync/cache/${
isPreview ? "preview" : "live"
}`;
if (!isDevelopmentMode) {
//we are in prod and need to use a different directory that Vercel understands
cachePath = `/tmp/agilitycache/${isPreview ? "preview" : "live"}`;
}
console.log(`Agility CMS => Content cache path is ${cachePath}`);
const apiKey = isPreview
? agilityConfig.previewAPIKey
: agilityConfig.fetchAPIKey;
if (!agilityConfig.guid) {
console.log("Agility CMS => No GUID was provided.");
return null;
}
return agilityContentSync.getSyncClient({
guid: agilityConfig.guid,
apiKey: apiKey,
isPreview: isPreview,
languages: [agilityConfig.languageCode],
channels: [agilityConfig.channelName],
store: {
interface: agilityFileSystem,
options: {
rootPath: cachePath,
},
},
});
};
module.exports = {
agilityConfig,
getSyncClient,
};

View File

@ -0,0 +1,299 @@
import crypto from 'crypto'
import { asyncForEach } from "./utils"
import { ModuleWithInit } from "@agility/types"
//Agility API stuff
import { agilityConfig, getSyncClient } from './agility.config'
import GlobalFooter from 'components/agility-global/GlobalFooter'
import GlobalHeader from 'components/agility-global/GlobalHeader'
import moduleInitializers from "framework/module-data"
const securityKey = agilityConfig.securityKey
const channelName = agilityConfig.channelName
const languageCode = agilityConfig.languageCode
const isDevelopmentMode = process.env.NODE_ENV === "development"
interface AgilityPageProps {
sitemapNode?: any,
page?: any,
dynamicPageItem?: any,
pageTemplateName?:string|null,
globalHeaderProps?:any,
globalFooterProps?:any,
languageCode?:string|null,
channelName?:string|null,
isPreview?:boolean,
isDevelopmentMode?:boolean,
notFound?:boolean
}
const getAgilityPageProps = async ({ params, preview, locale }:any):Promise<AgilityPageProps> => {
let path = '/';
if (params) {
//build path by iterating through slugs
path = '';
params.slug.map((slug: string) => {
path += '/' + slug
})
}
//TODO: use locale to determin LANGUAGECODE (pulled from config at this point...)
//determine if we are in preview mode
const isPreview:boolean = (preview || isDevelopmentMode);
const agilitySyncClient = getSyncClient({
isPreview: isPreview,
isDevelopmentMode
});
//always sync to get latest
if (! agilitySyncClient) {
console.log("Agility CMS => Sync client could not be accessed.")
return {
notFound: true
};
}
if (!isDevelopmentMode) {
console.log(`Agility CMS => Syncing ${isPreview ? "Preview" : "Live"} Mode`)
await agilitySyncClient.runSync();
}
console.log(`Agility CMS => Getting page props for '${path}'...`);
//get sitemap
const sitemap = await agilitySyncClient.store.getSitemap({ channelName, languageCode });
if (sitemap === null) {
console.warn("No sitemap found after sync.");
}
let pageInSitemap = sitemap[path];
let page: any = null;
let dynamicPageItem: any = null;
if (path === '/') {
let firstPagePathInSitemap = Object.keys(sitemap)[0];
pageInSitemap = sitemap[firstPagePathInSitemap];
}
if (pageInSitemap) {
//get the page
page = await agilitySyncClient.store.getPage({
pageID: pageInSitemap.pageID,
languageCode: languageCode
});
} else {
//Could not find page
console.warn('page [' + path + '] not found in sitemap.');
return handlePageNotFound();
}
if (!page) {
console.warn('page [' + path + '] not found in getpage method.');
}
//if there is a dynamic page content id on this page, grab it...
if (pageInSitemap.contentID > 0) {
dynamicPageItem = await agilitySyncClient.store.getContentItem({
contentID: pageInSitemap.contentID,
languageCode: languageCode
});
}
//resolve the page template
const pageTemplateName = page.templateName.replace(/[^0-9a-zA-Z]/g, '');
//resolve the modules per content zone
await asyncForEach(Object.keys(page.zones), async (zoneName: string) => {
let modules: { moduleName: string; item: any }[] = [];
//grab the modules for this content zone
const modulesForThisContentZone = page.zones[zoneName];
//loop through the zone's modules
await asyncForEach(modulesForThisContentZone, async (moduleItem: { module: string, item: any }) => {
//find the react component to use for the module
const moduleInitializer = moduleInitializers(moduleItem.module)
if (moduleInitializer) {
//resolve any additional data for the modules
//we have some additional data in the module we'll need, execute that method now, so it can be included in SSG
if (isDevelopmentMode) {
console.log(`Agility CMS => Fetching additional data via getCustomInitialProps for ${moduleItem.module}...`);
}
const moduleData = await moduleInitializer({
item: moduleItem.item,
agility: agilitySyncClient.store,
languageCode,
channelName,
pageInSitemap,
dynamicPageItem
});
//if we have additional module data, then add it to the module props using 'customData'
if (moduleData != null) {
moduleItem.item.customData = moduleData;
}
}
modules.push({
moduleName: moduleItem.module,
item: moduleItem.item,
})
})
//store as dictionary
page.zones[zoneName] = modules;
})
//resolve data for other shared components
const globalHeaderProps = await GlobalHeader.getCustomInitialProps({ agility: agilitySyncClient.store, languageCode: languageCode, channelName: channelName });
const globalFooterProps = await GlobalFooter.getCustomInitialProps({ agility: agilitySyncClient.store, languageCode: languageCode, channelName: channelName });
return {
sitemapNode: pageInSitemap,
page,
dynamicPageItem,
pageTemplateName,
globalHeaderProps,
globalFooterProps,
languageCode,
channelName,
isPreview,
isDevelopmentMode
}
}
const getAgilityPaths = async () => {
console.log(`Agility CMS => Fetching sitemap for getAgilityPaths...`);
//determine if we are in preview mode
const isPreview = isDevelopmentMode;
const agilitySyncClient = getSyncClient({
isPreview,
isDevelopmentMode
});
//always sync to get latest
if (! agilitySyncClient) {
console.log("Agility CMS => Sync client could not be accessed.")
return [];
}
if (!isDevelopmentMode) {
console.log(`Agility CMS => Syncing ${isPreview ? "Preview" : "Live"} Mode`)
await agilitySyncClient.runSync();
}
const sitemapFlat = await agilitySyncClient.store.getSitemap({
channelName,
languageCode
})
if (!sitemapFlat) {
console.warn("Agility CMS => No Site map found. Make sure your environment variables are setup correctly.")
return []
}
//returns an array of paths as a string (i.e. ['/home', '/posts'] )
const paths = Object.keys(sitemapFlat)
.filter(path => {
const sitemapNode = sitemapFlat[path]
return !sitemapNode.redirect
&& !sitemapNode.isFolder
})
return paths
}
const validatePreview = async({ agilityPreviewKey, slug }: any) => {
//Validate the preview key
if (!agilityPreviewKey) {
return {
error: true,
message: `Missing agilitypreviewkey.`
}
}
//sanitize incoming key (replace spaces with '+')
if (agilityPreviewKey.indexOf(` `) > -1) {
agilityPreviewKey = agilityPreviewKey.split(` `).join(`+`);
}
//compare the preview key being used
const correctPreviewKey = generatePreviewKey();
if (agilityPreviewKey !== correctPreviewKey) {
return {
error: true,
message: `Invalid agilitypreviewkey.`
//message: `Invalid agilitypreviewkey. Incoming key is=${agilityPreviewKey} compared to=${correctPreviewKey}...`
}
}
//return success
return {
error: false,
message: null
}
}
const generatePreviewKey =() => {
//the string we want to encode
const str = `-1_${securityKey}_Preview`;
//build our byte array
let data = [];
for (var i = 0; i < str.length; ++i) {
data.push(str.charCodeAt(i));
data.push(0);
}
//convert byte array to buffer
const strBuffer = Buffer.from(data);
//encode it!
const previewKey = crypto.createHash('sha512').update(strBuffer).digest('base64');
return previewKey;
}
function handlePageNotFound() {
return {
notFound: true
}
}
export {
getAgilityPageProps,
getAgilityPaths,
validatePreview,
generatePreviewKey
}

View File

@ -0,0 +1,51 @@
/*
THIS FILE IS ONLY EXECUTED LOCALLY
WHEN DOING A LOCAL SYNC ON DEMAND
IN DEVELOPMENT MODE
*/
require("dotenv").config({
path: `.env.local`,
})
const { getSyncClient } = require('./agility.config')
const runSync = async () => {
const agilitySyncClient = getSyncClient({ isPreview: true, isDevelopmentMode: true })
if (! agilitySyncClient) {
console.log("Agility CMS => Sync client could not be accessed.")
return;
}
await agilitySyncClient.runSync();
}
const clearSync = async () => {
const agilitySyncClient = getSyncClient({ isPreview: true, isDevelopmentMode: true })
if (! agilitySyncClient) {
console.log("Agility CMS => Sync client could not be accessed.")
return;
}
await agilitySyncClient.clearSync();
}
if (process.argv[2]) {
if (process.argv[2] === "clear") {
//clear everything
return clearSync();
} else if (process.argv[2] === "sync") {
//run the sync
return runSync()
}
}
module.exports = {
clearSync,
runSync
}

View File

@ -0,0 +1,24 @@
import { FC } from 'react'
export interface CustomInitPropsArg {
item:any,
agility:any,
languageCode:any,
channelName:any,
pageInSitemap:any,
dynamicPageItem?:any
}
export interface CustomInitProps<T> {
(props:CustomInitPropsArg): T;
}
export interface ModuleWithInit<TProps, TInit> extends FC<TProps> {
getCustomInitialProps:CustomInitProps<TInit>
}
export interface URLField {
href:string,
target:string,
text:string
}

108
framework/agility/utils.js Normal file
View File

@ -0,0 +1,108 @@
const renderHTML = (html) => {
if (!html) return { __html: "" };
return { __html: cleanHTML(html) };
}
const cleanHTML = (html) => {
if (!html) return ""
//fix '~' in links in HTML
return html.replace(/href="~\//gi, 'href="/')
}
const asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
const expandContentList = async ({ agility, contentItems, languageCode, depth }) => {
asyncForEach(contentItems, async (contentItem) => {
await expandContentItem({agility, contentItem, languageCode, depth})
})
}
const expandContentItem = async ({ agility, contentItem, languageCode, depth = 1 }) => {
if (!contentItem) return null;
const api = agility;
if (depth > 0) {
//make this work for the .fields or the .customFields property...
let fields = contentItem.fields;
if (!fields) fields = contentItem.customFields;
for (const fieldName in fields) {
const fieldValue = fields[fieldName];
if (fieldValue.contentid > 0) {
//single linked item
const childItem = await api.getContentItem({ contentID: fieldValue.contentid, languageCode, depth: depth - 1 });
if (childItem != null) fields[fieldName] = childItem;
} else if (fieldValue.sortids && fieldValue.sortids.split) {
//multi linked item
const sortIDAry = fieldValue.sortids.split(',');
const childItems = [];
for (const childItemID of sortIDAry) {
const childItem = await api.getContentItem({ contentID: childItemID, languageCode, depth: depth - 1 });
if (childItem != null) childItems.push(childItem);
}
fields[fieldName] = childItems;
}
}
}
return contentItem;
}
const expandLinkedList = async ({ agility, contentItem, languageCode, fieldName, sortIDField }) => {
if (!contentItem) return null;
let fieldObj = contentItem.fields[fieldName]
if (! fieldObj) throw Error(`The field ${fieldName} was not found on the content item.`)
const referenceName = fieldObj.referencename
if (! referenceName) throw Error(`A referencename property was not found on the ${fieldName} value.`)
//grab the content list...
let listItems = await agility.getContentList({referenceName, languageCode})
if (listItems?.length > 0) {
let sortIDs = contentItem.fields[sortIDField]
if (sortIDs?.length > 0 && sortIDs?.split) {
//if we have sort ids, assemble the values in the list based on those...
const sortIDAry = sortIDs.split(',');
const sortedItems = [];
for (const idStr of sortIDAry) {
const childContentID = parseInt(idStr)
const childItemIndex = listItems.findIndex(item => item.contentID === childContentID)
if (childItemIndex >= 0) {
sortedItems.push(listItems[childItemIndex]);
listItems.splice(childItemIndex, 1)
}
}
listItems = sortedItems.concat(listItems)
}
}
contentItem.fields[fieldName] = listItems;
return contentItem;
}
module.exports = {
renderHTML,
cleanHTML,
asyncForEach,
expandContentItem,
expandContentList,
expandLinkedList
}

View File

@ -35,9 +35,11 @@ async function getAllPages({
>('/v3/content/pages')
const pages = (data as RecursiveRequired<typeof data>) ?? []
return {
const retPages = {
pages: preview ? pages : pages.filter((p) => p.is_visible),
}
return retPages
}
export default getAllPages

View File

@ -0,0 +1,55 @@
import { getConfig } from '@framework/api'
import getAllProducts from '@framework/api/operations/get-all-products'
import rangeMap from '@lib/range-map'
const nonNullable = (v: any) => v
const BestsellingProductsInit = async function ({ item, agility, languageCode, channelName, pageInSitemap, dynamicPageItem }: any) {
//TODO: pass the locale and preview mode as props...
const locale = "en-US"
const preview = false
const config = getConfig({ locale })
// Get Best Selling Products
const { products: bestSellingProducts } = await getAllProducts({
variables: { field: 'bestSellingProducts', first: 6 },
config,
preview,
})
// Get Best Newest Products
const { products: newestProducts } = await getAllProducts({
variables: { field: 'newestProducts', first: 12 },
config,
preview,
})
// These are the products that are going to be displayed in the landing.
// We prefer to do the computation at buildtime/servertime
const { bestSelling } = (() => {
// Create a copy of products that we can mutate
const products = [...newestProducts]
// If the lists of featured and best selling products don't have enough
// products, then fill them with products from the products list, this
// is useful for new commerce sites that don't have a lot of products
return {
bestSelling: rangeMap(
6,
(i) => bestSellingProducts[i] ?? products.shift()
).filter(nonNullable),
}
})()
return {
bestSelling
}
}
export default BestsellingProductsInit

View File

@ -0,0 +1,34 @@
import { getConfig } from '@framework/api'
import getAllProducts from '@framework/api/operations/get-all-products'
import rangeMap from '@lib/range-map'
const nonNullable = (v: any) => v
const FeaturedProductsInit = async function ({ item, agility, languageCode, channelName, pageInSitemap, dynamicPageItem }: any) {
//TODO: pass the locale and preview mode as props...
const locale = "en-US"
const preview = false
const config = getConfig({ locale })
// Get Featured Products
const { products: featuredProducts } = await getAllProducts({
variables: { field: 'featuredProducts', first: 6 },
config,
preview,
})
return {
featured: featuredProducts
}
}
export default FeaturedProductsInit

View File

@ -0,0 +1,22 @@
import {FC} from "react"
import * as AgilityTypes from "@agility/types"
import BestsellingProductsData from "./BestsellingProductsData"
import FeaturedProductsData from "./FeaturedProductsData"
const allModules:any =[
{ name: "BestsellingProducts", init: BestsellingProductsData },
{ name: "FeaturedProducts", init: FeaturedProductsData}
]
/**
* Find the data method for a module.
* @param moduleName
*/
const getInitMethod = (moduleName:string):any => {
const obj = allModules.find((m: { name: string }) => m.name.toLowerCase() === moduleName.toLowerCase())
if (!obj) return null
return obj.init
}
export default getInitMethod

5
jsconfig.json Normal file
View File

@ -0,0 +1,5 @@
{
"compilerOptions": {
"baseUrl": "."
}
}

View File

@ -3,8 +3,9 @@ const bundleAnalyzer = require('@next/bundle-analyzer')({
})
module.exports = bundleAnalyzer({
images: {
domains: ['cdn11.bigcommerce.com'],
domains: ['cdn11.bigcommerce.com', 'cdn.aglty.io'],
},
i18n: {
locales: ['en-US', 'es'],

View File

@ -8,7 +8,9 @@
"analyze": "BUNDLE_ANALYZE=both yarn build",
"find:unused": "next-unused",
"generate": "graphql-codegen",
"generate:definitions": "node framework/bigcommerce/scripts/generate-definitions.js"
"generate:definitions": "node framework/bigcommerce/scripts/generate-definitions.js",
"cms-pull": "NODE_ENV=development && node framework/agility/agility.sync.js sync",
"cms-clear": "node framework/agility/agility.sync.js clear"
},
"prettier": {
"semi": false,
@ -44,6 +46,7 @@
]
},
"dependencies": {
"@agility/content-sync": "^0.1.23",
"@reach/portal": "^0.11.2",
"@tailwindcss/ui": "^0.6.2",
"@vercel/fetch": "^6.1.0",

92
pages/[...slug].tsx Normal file
View File

@ -0,0 +1,92 @@
import type {
GetStaticPathsContext,
GetStaticPropsContext,
InferGetStaticPropsType,
} from 'next'
import { Layout } from '@components/common'
import { missingLocaleInPages } from '@lib/usage-warns'
import { defaultPageProps } from '@lib/defaults'
import AgilityPage from "components/agility-global/AgilityPage"
import { getConfig } from '@framework/api'
import getProduct from '@framework/api/operations/get-product'
import { getAgilityPageProps, getAgilityPaths } from "framework/agility/agility.node";
import getAllProductPaths from '@framework/api/operations/get-all-product-paths'
export async function getStaticProps({ preview, params, locale }: GetStaticPropsContext<{ slug: string[] }>) {
let productCode: string | null = null
//check if this page is a product...
if (params?.slug.length === 2
&& params?.slug[0] === "product") {
productCode = params.slug[1]
params.slug[1] = "product-details"
}
const page = await getAgilityPageProps({ preview, params, locale });
if (productCode) {
const config = getConfig({ locale })
const { product } = await getProduct({
variables: { slug: productCode },
config,
preview,
})
if (product !== null) {
page.dynamicPageItem = product
} else {
throw new Error(`Product not found`)
}
}
const pages = await getAgilityPaths()
if (!page) {
// We throw to make sure this fails at build time as this is never expected to happen
throw new Error(`Page not found`)
}
return {
props: { ...defaultPageProps, pages, page },
revalidate: 60 * 60, // Every hour
}
}
export async function getStaticPaths({ locales }: GetStaticPathsContext) {
//get the paths configured in agility
let agilityPaths = await getAgilityPaths()
//remove product/product-details from the agility paths (special details page...)
agilityPaths = agilityPaths.filter(p => p !== "/product/product-details")
//get the product paths from the commerce api
const { products } = await getAllProductPaths()
const productPaths = products.map(p => `/product${p.node.path}`)
const paths = [...agilityPaths, ...productPaths]
return {
paths,
fallback: true,
}
}
export default function Pages({ page }: InferGetStaticPropsType<typeof getStaticProps>) {
return (
<AgilityPage {...page} />
)
}
Pages.Layout = Layout

View File

@ -1,152 +1,2 @@
import rangeMap from '@lib/range-map'
import { Layout } from '@components/common'
import { ProductCard } from '@components/product'
import { Grid, Marquee, Hero } from '@components/ui'
import HomeAllProductsGrid from '@components/common/HomeAllProductsGrid'
import type { GetStaticPropsContext, InferGetStaticPropsType } from 'next'
import { getConfig } from '@framework/api'
import getAllProducts from '@framework/api/operations/get-all-products'
import getSiteInfo from '@framework/api/operations/get-site-info'
import getAllPages from '@framework/api/operations/get-all-pages'
export async function getStaticProps({
preview,
locale,
}: GetStaticPropsContext) {
const config = getConfig({ locale })
// Get Featured Products
const { products: featuredProducts } = await getAllProducts({
variables: { field: 'featuredProducts', first: 6 },
config,
preview,
})
// Get Best Selling Products
const { products: bestSellingProducts } = await getAllProducts({
variables: { field: 'bestSellingProducts', first: 6 },
config,
preview,
})
// Get Best Newest Products
const { products: newestProducts } = await getAllProducts({
variables: { field: 'newestProducts', first: 12 },
config,
preview,
})
const { categories, brands } = await getSiteInfo({ config, preview })
const { pages } = await getAllPages({ config, preview })
// These are the products that are going to be displayed in the landing.
// We prefer to do the computation at buildtime/servertime
const { featured, bestSelling } = (() => {
// Create a copy of products that we can mutate
const products = [...newestProducts]
// If the lists of featured and best selling products don't have enough
// products, then fill them with products from the products list, this
// is useful for new commerce sites that don't have a lot of products
return {
featured: rangeMap(6, (i) => featuredProducts[i] ?? products.shift())
.filter(nonNullable)
.sort((a, b) => a.node.prices.price.value - b.node.prices.price.value)
.reverse(),
bestSelling: rangeMap(
6,
(i) => bestSellingProducts[i] ?? products.shift()
).filter(nonNullable),
}
})()
return {
props: {
featured,
bestSelling,
newestProducts,
categories,
brands,
pages,
},
revalidate: 14400,
}
}
const nonNullable = (v: any) => v
export default function Home({
featured,
bestSelling,
brands,
categories,
newestProducts,
}: InferGetStaticPropsType<typeof getStaticProps>) {
return (
<div>
<Grid>
{featured.slice(0, 3).map(({ node }, i) => (
<ProductCard
key={node.path}
product={node}
imgWidth={i === 0 ? 1080 : 540}
imgHeight={i === 0 ? 1080 : 540}
imgPriority
imgLoading="eager"
/>
))}
</Grid>
<Marquee variant="secondary">
{bestSelling.slice(3, 6).map(({ node }) => (
<ProductCard
key={node.path}
product={node}
variant="slim"
imgWidth={320}
imgHeight={320}
imgLayout="fixed"
/>
))}
</Marquee>
<Hero
headline="Release Details: The Yeezy BOOST 350 V2 Natural'"
description="
The Yeezy BOOST 350 V2 lineup continues to grow. We recently had the
Carbon iteration, and now release details have been locked in for
this Natural joint. Revealed by Yeezy Mafia earlier this year, the
shoe was originally called Abez, which translated to Tin in
Hebrew. Its now undergone a name change, and will be referred to as
Natural."
/>
<Grid layout="B">
{featured.slice(3, 6).map(({ node }, i) => (
<ProductCard
key={node.path}
product={node}
imgWidth={i === 1 ? 1080 : 540}
imgHeight={i === 1 ? 1080 : 540}
/>
))}
</Grid>
<Marquee>
{bestSelling.slice(0, 3).map(({ node }) => (
<ProductCard
key={node.path}
product={node}
variant="slim"
imgWidth={320}
imgHeight={320}
imgLayout="fixed"
/>
))}
</Marquee>
<HomeAllProductsGrid
categories={categories}
brands={brands}
newestProducts={newestProducts}
/>
</div>
)
}
Home.Layout = Layout
//this is just a pointer to the catch-all route and logic for all CMS driven pages (i.e. even rootpage is dynamic from the CMS)
export { default, getStaticProps } from './[...slug]';

152
pagesBAK/index.tsx Normal file
View File

@ -0,0 +1,152 @@
import rangeMap from '@lib/range-map'
import { Layout } from '@components/common'
import { ProductCard } from '@components/product'
import { Grid, Marquee, Hero } from '@components/ui'
import HomeAllProductsGrid from '@components/common/HomeAllProductsGrid'
import type { GetStaticPropsContext, InferGetStaticPropsType } from 'next'
import { getConfig } from '@framework/api'
import getAllProducts from '@framework/api/operations/get-all-products'
import getSiteInfo from '@framework/api/operations/get-site-info'
import getAllPages from '@framework/api/operations/get-all-pages'
export async function getStaticProps({
preview,
locale,
}: GetStaticPropsContext) {
const config = getConfig({ locale })
// Get Featured Products
const { products: featuredProducts } = await getAllProducts({
variables: { field: 'featuredProducts', first: 6 },
config,
preview,
})
// Get Best Selling Products
const { products: bestSellingProducts } = await getAllProducts({
variables: { field: 'bestSellingProducts', first: 6 },
config,
preview,
})
// Get Best Newest Products
const { products: newestProducts } = await getAllProducts({
variables: { field: 'newestProducts', first: 12 },
config,
preview,
})
const { categories, brands } = await getSiteInfo({ config, preview })
const { pages } = await getAllPages({ config, preview })
// These are the products that are going to be displayed in the landing.
// We prefer to do the computation at buildtime/servertime
const { featured, bestSelling } = (() => {
// Create a copy of products that we can mutate
const products = [...newestProducts]
// If the lists of featured and best selling products don't have enough
// products, then fill them with products from the products list, this
// is useful for new commerce sites that don't have a lot of products
return {
featured: rangeMap(6, (i) => featuredProducts[i] ?? products.shift())
.filter(nonNullable)
.sort((a, b) => a.node.prices.price.value - b.node.prices.price.value)
.reverse(),
bestSelling: rangeMap(
6,
(i) => bestSellingProducts[i] ?? products.shift()
).filter(nonNullable),
}
})()
return {
props: {
featured,
bestSelling,
newestProducts,
categories,
brands,
pages,
},
revalidate: 14400,
}
}
const nonNullable = (v: any) => v
export default function Home({
featured,
bestSelling,
brands,
categories,
newestProducts,
}: InferGetStaticPropsType<typeof getStaticProps>) {
return (
<div>
<Grid>
{featured.slice(0, 3).map(({ node }, i) => (
<ProductCard
key={node.path}
product={node}
imgWidth={i === 0 ? 1080 : 540}
imgHeight={i === 0 ? 1080 : 540}
imgPriority
imgLoading="eager"
/>
))}
</Grid>
<Marquee variant="secondary">
{bestSelling.slice(3, 6).map(({ node }) => (
<ProductCard
key={node.path}
product={node}
variant="slim"
imgWidth={320}
imgHeight={320}
imgLayout="fixed"
/>
))}
</Marquee>
<Hero
headline="Release Details: The Yeezy BOOST 350 V2 Natural'"
description="
The Yeezy BOOST 350 V2 lineup continues to grow. We recently had the
Carbon iteration, and now release details have been locked in for
this Natural joint. Revealed by Yeezy Mafia earlier this year, the
shoe was originally called Abez, which translated to Tin in
Hebrew. Its now undergone a name change, and will be referred to as
Natural."
/>
<Grid layout="B">
{featured.slice(3, 6).map(({ node }, i) => (
<ProductCard
key={node.path}
product={node}
imgWidth={i === 1 ? 1080 : 540}
imgHeight={i === 1 ? 1080 : 540}
/>
))}
</Grid>
<Marquee>
{bestSelling.slice(0, 3).map(({ node }) => (
<ProductCard
key={node.path}
product={node}
variant="slim"
imgWidth={320}
imgHeight={320}
imgLayout="fixed"
/>
))}
</Marquee>
<HomeAllProductsGrid
categories={categories}
brands={brands}
newestProducts={newestProducts}
/>
</div>
)
}
Home.Layout = Layout

View File

@ -23,9 +23,12 @@
"@commerce/*": ["framework/commerce/*"],
"@commerce": ["framework/commerce"],
"@framework/*": ["framework/bigcommerce/*"],
"@framework": ["framework/bigcommerce"]
"@framework": ["framework/bigcommerce"],
"@agility/*": ["framework/agility/*"],
"@agility": ["framework/agility"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.js", "pages/XXXpages.tsx.bak"],
"exclude": ["node_modules"]
}

View File

@ -2,6 +2,23 @@
# yarn lockfile v1
"@agility/content-fetch@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@agility/content-fetch/-/content-fetch-1.0.0.tgz#a945aa1abf21c093a6688494ab5a15d1282774d9"
integrity sha512-cG8XMT4OtlxbuEk7DW4jwe2MK5WuCvu4vClajjOD4DerkkINtWGfvt92XHlcFQ9Az4RmBnOR4/LQMnH3l7AjXg==
dependencies:
axios "^0.18.1"
axios-cache-adapter "^2.4.1"
"@agility/content-sync@^0.1.23":
version "0.1.23"
resolved "https://registry.yarnpkg.com/@agility/content-sync/-/content-sync-0.1.23.tgz#68c46a0330bbe2aba772d638669c3bf49f8892e5"
integrity sha512-LDw6+nPqI0Lgtt4fzkDcdHtLnvNiuSyhMca2X/I8QuSrtX2asJGbYH3e8YQEq59C2Ocvfn9xrGJyIVj48f5Ukw==
dependencies:
"@agility/content-fetch" "^1.0.0"
dotenv "^8.2.0"
proper-lockfile "^4.1.1"
"@ampproject/toolbox-core@2.7.4", "@ampproject/toolbox-core@^2.7.1-alpha.0":
version "2.7.4"
resolved "https://registry.yarnpkg.com/@ampproject/toolbox-core/-/toolbox-core-2.7.4.tgz#8355136f16301458ce942acf6c55952c9a415627"
@ -1766,6 +1783,22 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
axios-cache-adapter@^2.4.1:
version "2.5.0"
resolved "https://registry.yarnpkg.com/axios-cache-adapter/-/axios-cache-adapter-2.5.0.tgz#e68be61e8c1cd7f31e4e7cfcbfd0435404ae67ef"
integrity sha512-YcMPdMoqmSLoZx7A5YD/PdYGuX6/Y9M2tHBhaIXvXrPeGgNnbW7nb3+uArWlT53WGHLfclnu2voMmS7jGXVg6A==
dependencies:
cache-control-esm "1.0.0"
lodash "^4.17.11"
axios@^0.18.1:
version "0.18.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3"
integrity sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==
dependencies:
follow-redirects "1.5.10"
is-buffer "^2.0.2"
babel-plugin-dynamic-import-node@^2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3"
@ -2050,6 +2083,11 @@ bytes@3.1.0, bytes@^3.0.0:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
cache-control-esm@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/cache-control-esm/-/cache-control-esm-1.0.0.tgz#417647ecf1837a5e74155f55d5a4ae32a84e2581"
integrity sha512-Fa3UV4+eIk4EOih8FTV6EEsVKO0W5XWtNs6FC3InTfVz+EjurjPfDXY5wZDo/lxjDxg5RjNcurLyxEJBcEUx9g==
cacheable-request@^6.0.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
@ -2650,7 +2688,7 @@ debounce@^1.2.0:
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131"
integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==
debug@3.1.0:
debug@3.1.0, debug@=3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
@ -3445,6 +3483,13 @@ flatten@^1.0.2:
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==
follow-redirects@1.5.10:
version "1.5.10"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
dependencies:
debug "=3.1.0"
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
@ -3671,7 +3716,7 @@ got@^9.6.0:
to-readable-stream "^1.0.0"
url-parse-lax "^3.0.0"
graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4:
graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4:
version "4.2.4"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
@ -4042,6 +4087,11 @@ is-binary-path@~2.1.0:
dependencies:
binary-extensions "^2.0.0"
is-buffer@^2.0.2:
version "2.0.5"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191"
integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==
is-callable@^1.1.4, is-callable@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9"
@ -6085,6 +6135,15 @@ prop-types@15.7.2, prop-types@^15.6.2:
object-assign "^4.1.1"
react-is "^16.8.1"
proper-lockfile@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.1.tgz#284cf9db9e30a90e647afad69deb7cb06881262c"
integrity sha512-1w6rxXodisVpn7QYvLk706mzprPTAPCYAqxMvctmPN3ekuRk/kuGkGc82pangZiAt4R3lwSuUzheTTn0/Yb7Zg==
dependencies:
graceful-fs "^4.1.11"
retry "^0.12.0"
signal-exit "^3.0.2"
prr@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
@ -6484,7 +6543,7 @@ restore-cursor@^3.1.0:
onetime "^5.1.0"
signal-exit "^3.0.2"
retry@0.12.0:
retry@0.12.0, retry@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=