bug fixes

This commit is contained in:
Joel Varty
2021-06-18 13:31:02 -04:00
parent 52f2cccc11
commit a88b52b00a
13 changed files with 44 additions and 1059 deletions

View File

@@ -1,77 +0,0 @@
const agilityContentSync = require("@agility/content-sync");
const nextFileSystem = require("./next.file.sync");
const path = require("path")
const fs = require('fs-extra');
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, isIncremental }) => {
const rootPath = process.cwd()
let cachePath = `${rootPath}/.next/cache/agility/${isPreview ? "preview" : "live" }`;
//if we are in "incremental" mode, we need to use the tmp folder...
if (isIncremental) {
cachePath = `/tmp/agilitycache/${isPreview ? "preview" : "live"}`;
}
console.log(`AgilityCMS => Content cache path is ${cachePath}`);
const apiKey = isPreview
? agilityConfig.previewAPIKey
: agilityConfig.fetchAPIKey;
if (!agilityConfig.guid) {
console.log("AgilityCMS => No GUID was provided.");
return null;
}
return agilityContentSync.getSyncClient({
guid: agilityConfig.guid,
apiKey: apiKey,
isPreview: isPreview,
languages: [agilityConfig.languageCode],
channels: [agilityConfig.channelName],
store: {
interface: nextFileSystem,
options: {
rootPath: cachePath
},
},
});
};
const prepIncrementalMode = async () => {
const rootPath = process.cwd()
let cachePath = `${rootPath}/.next/cache/agility/`;
const tempPath = `/tmp/agilitycache/`;
const buildFilePath = path.join(tempPath, "build.log")
//check for the build file in here...
if (!fs.existsSync(buildFilePath)) {
console.log(`Copying Agility Content files to temp folder: ${tempPath}`)
//copy everything across from cachePath
await fs.copy(cachePath, tempPath)
} else {
console.log(`Agility tmp folder up to date: ${tempPath}`)
}
}
module.exports = {
agilityConfig,
getSyncClient,
prepIncrementalMode
};

View File

@@ -1,302 +0,0 @@
import fs from "fs"
import crypto from 'crypto'
import { asyncForEach } from "./utils"
import { ModuleWithInit } from "@agility/types"
//Agility API stuff
import { agilityConfig, getSyncClient, prepIncrementalMode } 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
})
}
//determine if we've already done a full build yet
const buildFilePath = `${process.cwd()}/.next/cache/agility/build.log`
const isBuildComplete = fs.existsSync(buildFilePath)
//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,
isIncremental: isBuildComplete
});
if (! agilitySyncClient) {
console.log("AgilityCMS => Sync client could not be accessed.")
return {
notFound: true
};
}
if (preview || isBuildComplete) {
//only do on-demand sync in next's preview mode or incremental build...
console.log(`AgilityCMS => Sync On-demand ${isPreview ? "Preview" : "Live"} Mode`)
await prepIncrementalMode()
await agilitySyncClient.runSync();
}
console.log(`AgilityCMS => 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(`AgilityCMS => 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 (preview:boolean|undefined) => {
console.log(`AgilityCMS => Fetching sitemap for getAgilityPaths...`);
//determine if we are in preview mode
const isPreview = isDevelopmentMode || preview;
//determine if we've already done a full build yet
const buildFilePath = `${process.cwd()}/.next/cache/agility/build.log`
const isBuildComplete = fs.existsSync(buildFilePath)
const agilitySyncClient = getSyncClient({
isPreview,
isDevelopmentMode,
isIncremental: isBuildComplete
});
if (! agilitySyncClient) {
console.log("AgilityCMS => Sync client could not be accessed.")
return [];
}
const sitemapFlat = await agilitySyncClient.store.getSitemap({
channelName,
languageCode
})
if (!sitemapFlat) {
console.warn("AgilityCMS => 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

@@ -1,107 +0,0 @@
const fs = require('fs')
require("dotenv").config({
path: `.env.local`,
})
const { getSyncClient } = require('./agility.config')
const runSync = async () => {
setBuildLog(false)
const agilitySyncClient = getSyncClient({ isPreview: true, isDevelopmentMode: true })
if (! agilitySyncClient) {
console.log("AgilityCMS => Sync client could not be accessed.")
return;
}
await agilitySyncClient.runSync();
}
const setBuildLog = (builtYN) => {
//clear out a file saying WE HAVE SYNC'D
const rootPath = process.cwd()
const filePath = `${rootPath}/.next/cache/agility/build.log`
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
if (builtYN) {
//write out the build log so we know that we are up to date
fs.writeFileSync(filePath, "BUILT");
} else {
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
}
}
const preBuild = async () => {
//clear the build log
setBuildLog(false)
//sync preview mode
let agilitySyncClient = getSyncClient({ isPreview: true, isDevelopmentMode: false })
if (! agilitySyncClient) {
console.log("AgilityCMS => Sync client could not be accessed.")
return;
}
await agilitySyncClient.runSync();
//sync production mode
agilitySyncClient = getSyncClient({ isPreview: false, isDevelopmentMode: false })
if (! agilitySyncClient) {
console.log("AgilityCMS => Sync client could not be accessed.")
return;
}
await agilitySyncClient.runSync();
}
const postBuild = async() => {
//mark the build log as BUILT
setBuildLog(true)
}
const clearSync = async () => {
setBuildLog(false)
const agilitySyncClient = getSyncClient({ isPreview: true, isDevelopmentMode: true })
if (! agilitySyncClient) {
console.log("AgilityCMS => 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()
} else if (process.argv[2] === "prebuild") {
//pre build actions
return preBuild()
} else if (process.argv[2] === "postbuild") {
//post build actions
return postBuild()
}
}
module.exports = {
clearSync,
runSync
}

View File

@@ -1,153 +0,0 @@
const fs = require('fs')
const os = require('os')
const path = require('path')
const { lockSync, unlockSync, checkSync, check } = require("proper-lockfile")
require("dotenv").config({
path: `.env.${process.env.NODE_ENV}`,
})
const sleep = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms));
}
const getFilePath = ({ options, itemType, languageCode, itemID }) => {
const fileName = `${itemID}.json`;
return path.join(options.rootPath, languageCode, itemType, fileName);
}
const saveItem = async ({ options, item, itemType, languageCode, itemID }) => {
let filePath = getFilePath({ options, itemType, languageCode, itemID });
let dirPath = path.dirname(filePath);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
let json = JSON.stringify(item);
fs.writeFileSync(filePath, json);
}
const deleteItem = async ({ options, itemType, languageCode, itemID }) => {
let filePath = getFilePath({ options, itemType, languageCode, itemID });
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
}
const mergeItemToList = async ({ options, item, languageCode, itemID, referenceName, definitionName }) => {
let contentList = await getItem({ options, itemType: "list", languageCode, itemID: referenceName });
if (contentList == null) {
//initialize the list
contentList = [item];
} else {
//replace the item...
const cIndex = contentList.findIndex((ci) => {
return ci.contentID === itemID;
});
if (item.properties.state === 3) {
//*** deleted item (remove from the list) ***
if (cIndex >= 0) {
//remove the item
contentList.splice(cIndex, 1);
}
} else {
//*** regular item (merge) ***
if (cIndex >= 0) {
//replace the existing item
contentList[cIndex] = item;
} else {
//and it to the end of the
contentList.push(item);
}
}
}
await saveItem({ options, item: contentList, itemType: "list", languageCode, itemID: referenceName });
}
const getItem = async ({ options, itemType, languageCode, itemID }) => {
let filePath = getFilePath({ options, itemType, languageCode, itemID });
if (!fs.existsSync(filePath)) return null;
let json = fs.readFileSync(filePath, 'utf8');
return JSON.parse(json);
}
const clearItems = async ({ options }) => {
fs.rmdirSync(options.rootPath, { recursive: true })
}
const waitOnLock = async (lockFile) => {
while (await check(lockFile)) {
await sleep(100)
}
}
const mutexLock = async () => {
const dir = os.tmpdir();
const lockFile = `${dir}/${"agility-sync"}.mutex`
if (! fs.existsSync(lockFile)) {
fs.writeFileSync(lockFile, "agility-sync");
}
//THE LOCK IS ALREADY HELD - WAIT UP!
await waitOnLock(lockFile)
try {
return lockSync(lockFile)
} catch (err) {
if (`${err}`.indexOf("Lock file is already being held") !== -1) {
//this error happens when 2 processes try to get a lock at the EXACT same time (very rare)
await sleep(100)
await waitOnLock(lockFile)
try {
return lockSync(lockFile)
} catch (e2) {
if (`${err}`.indexOf("Lock file is already being held") !== -1) {
//this error happens when 2 processes try to get a lock at the EXACT same time (very rare)
await sleep(100)
await waitOnLock(lockFile)
return lockSync(lockFile)
}
}
}
throw Error("The mutex lock could not be obtained.")
}
}
module.exports = {
saveItem,
deleteItem,
mergeItemToList,
getItem,
clearItems,
mutexLock
}

View File

@@ -1,24 +0,0 @@
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
}

View File

@@ -1,108 +0,0 @@
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
}