From fe4439560c62235f4c2e2b72004b63ae383f7f81 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Wed, 26 Jul 2023 16:45:23 -0400
Subject: [PATCH 01/56] Add preview command support
---
fileMapper.js | 3 +-
lib/constants.js | 1 +
lib/preview.js | 216 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 219 insertions(+), 1 deletion(-)
create mode 100644 lib/preview.js
diff --git a/fileMapper.js b/fileMapper.js
index 2427c5c..44dfa20 100644
--- a/fileMapper.js
+++ b/fileMapper.js
@@ -87,7 +87,7 @@ function isAllowedExtension(filepath) {
* @param {Mode} mode
*/
function useApiBuffer(mode) {
- return mode === Mode.draft;
+ return mode === Mode.draft || mode === Mode.preview;
}
/**
@@ -103,6 +103,7 @@ function getFileMapperQueryValues({ mode, options = {} }) {
buffer: useApiBuffer(mode),
environmentId: options.staging ? 2 : 1,
version: options.assetVersion,
+ previewSession: options.previewSession
},
};
}
diff --git a/lib/constants.js b/lib/constants.js
index 328d33c..31b9c68 100644
--- a/lib/constants.js
+++ b/lib/constants.js
@@ -34,6 +34,7 @@ const FUNCTIONS_EXTENSION = 'functions';
const Mode = {
draft: 'draft',
publish: 'publish',
+ preview: 'preview'
};
const DEFAULT_MODE = Mode.publish;
diff --git a/lib/preview.js b/lib/preview.js
new file mode 100644
index 0000000..d0363ec
--- /dev/null
+++ b/lib/preview.js
@@ -0,0 +1,216 @@
+const path = require('path');
+const chokidar = require('chokidar');
+const { default: PQueue } = require('p-queue');
+
+const { logger } = require('../logger');
+const debounce = require('debounce');
+const {
+ ApiErrorContext,
+ logApiErrorInstance,
+ logApiUploadErrorInstance,
+ logErrorInstance,
+} = require('../errorHandlers');
+const { uploadFolder } = require('./uploadFolder');
+const { shouldIgnoreFile, ignoreFile } = require('../ignoreRules');
+const { getFileMapperQueryValues } = require('../fileMapper');
+const { upload, deleteFile } = require('../api/fileMapper');
+const escapeRegExp = require('./escapeRegExp');
+const { convertToUnixPath, isAllowedExtension, getCwd } = require('../path');
+const { triggerNotify } = require('./notify');
+
+const queue = new PQueue({
+ concurrency: 10,
+});
+
+const generatePreviewUrl = (accountId, sessionToken) => {
+ return `https://app.hubspot.com/${accountId}/preview/${sessionToken}`;
+}
+
+const _notifyPreviewUrl = (accountId, sessionToken) => {
+ if (queue.size > 0) return;
+ logger.log(`To preview, visit: ${generatePreviewUrl(accountId, sessionToken)}`);
+};
+const notifyPreviewUrl = debounce(_notifyPreviewUrl, 1000);
+
+async function uploadFile(accountId, sessionToken, file, dest) {
+ logger.debug(`Attempting to upload file "${src}" to "${dest}"`);
+ const fileMapperArgs = buildFileMapperArgs(sessionToken);
+
+ return queue.add(() => {
+ return upload(accountId, src, dest, fileMapperArgs)
+ .then(() => {
+ logger.log(`Uploaded file ${src} to ${dest}`);
+ notifyPreviewUrl(accountId, sessionToken);
+ })
+ .catch(() => {
+ const uploadFailureMessage = `Uploading file ${src} to ${dest} failed`;
+ logger.debug(uploadFailureMessage);
+ logger.debug(`Retrying to upload file "${src}" to "${dest}"`);
+ return upload(accountId, file, dest, fileMapperArgs).catch(error => {
+ logger.error(uploadFailureMessage);
+ logApiUploadErrorInstance(
+ error,
+ new ApiErrorContext({
+ accountId,
+ request: dest,
+ payload: src,
+ })
+ );
+ });
+ });
+ });
+}
+
+async function deleteRemoteFile(accountId, sessionToken, remoteFilePath) {
+ logger.debug(`Attempting to delete file "${remoteFilePath}"`);
+ const fileMapperArgs = buildFileMapperArgs(sessionToken);
+
+ return queue.add(() => {
+ return deleteFile(accountId, remoteFilePath, fileMapperArgs)
+ .then(() => {
+ logger.log(`Deleted file ${remoteFilePath}`);
+ notifyPreviewUrl(accountId, sessionToken);
+ })
+ .catch(error => {
+ logger.error(`Deleting file ${remoteFilePath} failed`);
+ logApiErrorInstance(
+ error,
+ new ApiErrorContext({
+ accountId,
+ request: remoteFilePath,
+ })
+ );
+ });
+ });
+}
+
+const getDesignManagerPath = (src, dest, file) => {
+ const regex = new RegExp(`^${escapeRegExp(src)}`);
+ const relativePath = file.replace(regex, '');
+ return convertToUnixPath(path.join(dest, relativePath));
+};
+
+const buildFileMapperArgs = (sessionToken) => {
+ return getFileMapperQueryValues({
+ mode: 'preview',
+ previewSession: sessionToken
+ });
+}
+
+const buildDeleteFileFromPreviewBufferCallback = (sessionInfo, type) => {
+ const { accountId, src, dest, notify } = sessionInfo;
+
+ return (filePath) => {
+ if (shouldIgnoreFile(filePath)) {
+ logger.debug(`Skipping ${filePath} due to an ignore rule`);
+ return;
+ }
+
+ const remotePath = getDesignManagerPath(src, dest, filePath);
+ logger.debug(`Attempting to delete ${type} "${remotePath}"`, type, remotePath);
+ queue.add(() => {
+ const deletePromise = deleteRemoteFile(accountId, filePath, remotePath)
+ .then(() => {
+ logger.log(`Deleted ${type} "${remotePath}"`);
+ })
+ .catch(error => {
+ logger.error(`Deleting ${type} "${remotePath}" failed`);
+ logApiErrorInstance(
+ error,
+ new ApiErrorContext({
+ accountId,
+ request: remotePath,
+ })
+ );
+ });
+ triggerNotify(notify, 'Removed', filePath, deletePromise);
+ return deletePromise;
+ });
+ }
+}
+
+const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
+ const { accountId, src, sessionToken, notify } = sessionInfo;
+
+ return async (filePath) => {
+ if (!isAllowedExtension(filePath)) {
+ logger.debug(`Skipping ${filePath} due to unsupported extension`);
+ return;
+ }
+ if (shouldIgnoreFile(filePath)) {
+ logger.debug(`Skipping ${filePath} due to an ignore rule`);
+ return;
+ }
+ const destPath = getDesignManagerPath(sessionInfo, filePath);
+ const fileMapperArgs = buildFileMapperArgs(sessionToken)
+ const uploadPromise = uploadFile(accountId, filePath, destPath, {
+ src,
+ fileMapperArgs,
+ commandOptions,
+ });
+ triggerNotify(notify, notifyMessage, filePath, uploadPromise);
+ }
+}
+
+const initialPreviewBufferUpload = (sessionInfo, filePaths) => {
+ const { accountId, sessionToken, src, dest } = sessionInfo
+
+ const fileMapperArgs = buildFileMapperArgs(sessionToken);
+ // Use uploadFolder so that failures of initial upload are retried
+ uploadFolder(accountId, src, dest, fileMapperArgs, {}, filePaths)
+ .then(() => {
+ logger.success(
+ `Completed uploading files in ${src} to ${dest} in ${accountId}`
+ );
+ })
+ .catch(error => {
+ logger.error(
+ `Initial uploading of folder "${src}" to "${dest} in account ${accountId} failed`
+ );
+ logErrorInstance(error, {
+ accountId,
+ });
+ });
+}
+
+const preview = (
+ accountId,
+ src,
+ dest,
+ { notify, filePaths }
+) => {
+ const sessionToken = Date.now();
+ if (notify) {
+ ignoreFile(notify);
+ }
+ const sessionInfo = { accountId, sessionToken, src, dest, notify };
+ initialPreviewBufferUpload(sessionInfo, filePaths);
+
+ const watcher = chokidar.watch(src, {
+ ignoreInitial: true, // makes initial addition of files not trigger the watcher
+ ignored: file => shouldIgnoreFile(file),
+ });
+
+
+ const addFileCallback = buildUploadFileToPreviewBufferCallback(sessionInfo, 'Added');
+ const changeFileCallback = buildUploadFileToPreviewBufferCallback(sessionInfo, 'Change');
+ const deleteFileCallback = buildDeleteFileFromPreviewBufferCallback(sessionInfo, 'file');
+ const deleteFolderCallback = buildDeleteFileFromPreviewBufferCallback(sessionInfo, 'folder');
+
+ watcher.on('ready', () => {
+ logger.log(
+ `This is the message that displays when previewing begins.`
+ );
+ notifyPreviewUrl(accountId, sessionToken);
+ });
+ watcher.on('add', addFileCallback);
+ watcher.on('change', changeFileCallback);
+ watcher.on('unlink', deleteFileCallback);
+ watcher.on('unlinkDir', deleteFolderCallback);
+
+ return watcher;
+}
+
+module.exports = {
+ preview,
+};
From de74c6ae4113365f0603d2489069f72336c6ca2c Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Mon, 25 Sep 2023 13:29:14 -0400
Subject: [PATCH 02/56] Saving work
---
lib/preview.js | 41 +++++++++++++++++++++--------------------
1 file changed, 21 insertions(+), 20 deletions(-)
diff --git a/lib/preview.js b/lib/preview.js
index d0363ec..62882dc 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -22,9 +22,6 @@ const queue = new PQueue({
concurrency: 10,
});
-const generatePreviewUrl = (accountId, sessionToken) => {
- return `https://app.hubspot.com/${accountId}/preview/${sessionToken}`;
-}
const _notifyPreviewUrl = (accountId, sessionToken) => {
if (queue.size > 0) return;
@@ -91,7 +88,7 @@ const getDesignManagerPath = (src, dest, file) => {
};
const buildFileMapperArgs = (sessionToken) => {
- return getFileMapperQueryValues({
+ return getFileMapperQueryValues({
mode: 'preview',
previewSession: sessionToken
});
@@ -152,12 +149,12 @@ const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
}
}
-const initialPreviewBufferUpload = (sessionInfo, filePaths) => {
+const initialPreviewBufferUpload = async (sessionInfo, filePaths) => {
const { accountId, sessionToken, src, dest } = sessionInfo
const fileMapperArgs = buildFileMapperArgs(sessionToken);
// Use uploadFolder so that failures of initial upload are retried
- uploadFolder(accountId, src, dest, fileMapperArgs, {}, filePaths)
+ return uploadFolder(accountId, src, dest, fileMapperArgs, {}, filePaths)
.then(() => {
logger.success(
`Completed uploading files in ${src} to ${dest} in ${accountId}`
@@ -173,19 +170,7 @@ const initialPreviewBufferUpload = (sessionInfo, filePaths) => {
});
}
-const preview = (
- accountId,
- src,
- dest,
- { notify, filePaths }
-) => {
- const sessionToken = Date.now();
- if (notify) {
- ignoreFile(notify);
- }
- const sessionInfo = { accountId, sessionToken, src, dest, notify };
- initialPreviewBufferUpload(sessionInfo, filePaths);
-
+const startPreviewWatchingService = () => {
const watcher = chokidar.watch(src, {
ignoreInitial: true, // makes initial addition of files not trigger the watcher
ignored: file => shouldIgnoreFile(file),
@@ -201,7 +186,6 @@ const preview = (
logger.log(
`This is the message that displays when previewing begins.`
);
- notifyPreviewUrl(accountId, sessionToken);
});
watcher.on('add', addFileCallback);
watcher.on('change', changeFileCallback);
@@ -211,6 +195,23 @@ const preview = (
return watcher;
}
+const preview = async (
+ accountId,
+ src,
+ dest,
+ { notify, filePaths }
+) => {
+ const sessionToken = Date.now();
+ if (notify) {
+ ignoreFile(notify);
+ }
+ const sessionInfo = { accountId, sessionToken, src, dest, notify };
+ await initialPreviewBufferUpload(sessionInfo, filePaths);
+ startPreviewWatchingService();
+ startLocalHttpService();
+
+}
+
module.exports = {
preview,
};
From d7136a429c9db9c7f8d3073c25bd04406d0f682b Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Mon, 2 Oct 2023 14:10:45 -0400
Subject: [PATCH 03/56] Move work in from cloudlfare worker, start
implementation of proxy, fetching web domains, modularize routes a bit
---
api/domains.js | 20 +++++++++
api/preview.js | 20 +++++++++
lib/preview.js | 81 ++++++++++++++++++++-----------------
lib/preview/createRoutes.js | 15 +++++++
lib/preview/previewUtils.js | 15 +++++++
lib/preview/routes/index.js | 61 ++++++++++++++++++++++++++++
lib/preview/routes/proxy.js | 33 +++++++++++++++
7 files changed, 209 insertions(+), 36 deletions(-)
create mode 100644 api/domains.js
create mode 100644 api/preview.js
create mode 100644 lib/preview/createRoutes.js
create mode 100644 lib/preview/previewUtils.js
create mode 100644 lib/preview/routes/index.js
create mode 100644 lib/preview/routes/proxy.js
diff --git a/api/domains.js b/api/domains.js
new file mode 100644
index 0000000..47571bd
--- /dev/null
+++ b/api/domains.js
@@ -0,0 +1,20 @@
+const http = require('../http')
+
+const DOMAINS_API_PATH = `/cms/v3/domains`;
+
+async function fetchDomains(accountId) {
+ try {
+ const result = await http.get(accountId, {
+ uri: DOMAINS_API_PATH,
+ json: true
+ });
+
+ return result.results
+ } catch (err) {
+ throw err;
+ }
+}
+
+module.exports = {
+ fetchDomains,
+}
diff --git a/api/preview.js b/api/preview.js
new file mode 100644
index 0000000..47571bd
--- /dev/null
+++ b/api/preview.js
@@ -0,0 +1,20 @@
+const http = require('../http')
+
+const DOMAINS_API_PATH = `/cms/v3/domains`;
+
+async function fetchDomains(accountId) {
+ try {
+ const result = await http.get(accountId, {
+ uri: DOMAINS_API_PATH,
+ json: true
+ });
+
+ return result.results
+ } catch (err) {
+ throw err;
+ }
+}
+
+module.exports = {
+ fetchDomains,
+}
diff --git a/lib/preview.js b/lib/preview.js
index 62882dc..7cfc710 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -1,5 +1,7 @@
+const http = require('http');
const path = require('path');
const chokidar = require('chokidar');
+const express = require('express');
const { default: PQueue } = require('p-queue');
const { logger } = require('../logger');
@@ -17,17 +19,9 @@ const { upload, deleteFile } = require('../api/fileMapper');
const escapeRegExp = require('./escapeRegExp');
const { convertToUnixPath, isAllowedExtension, getCwd } = require('../path');
const { triggerNotify } = require('./notify');
-
-const queue = new PQueue({
- concurrency: 10,
-});
-
-
-const _notifyPreviewUrl = (accountId, sessionToken) => {
- if (queue.size > 0) return;
- logger.log(`To preview, visit: ${generatePreviewUrl(accountId, sessionToken)}`);
-};
-const notifyPreviewUrl = debounce(_notifyPreviewUrl, 1000);
+const { getAccountConfig } = require('./config');
+const { createPreviewServerRoutes } = require('./preview/createRoutes');
+const { getPortalDomains } = require('./preview/previewUtils');
async function uploadFile(accountId, sessionToken, file, dest) {
logger.debug(`Attempting to upload file "${src}" to "${dest}"`);
@@ -89,8 +83,8 @@ const getDesignManagerPath = (src, dest, file) => {
const buildFileMapperArgs = (sessionToken) => {
return getFileMapperQueryValues({
- mode: 'preview',
- previewSession: sessionToken
+ mode: 'publish',
+ //previewSession: sessionToken
});
}
@@ -150,27 +144,17 @@ const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
}
const initialPreviewBufferUpload = async (sessionInfo, filePaths) => {
- const { accountId, sessionToken, src, dest } = sessionInfo
-
+ const { portalId, sessionToken, src, dest } = sessionInfo
const fileMapperArgs = buildFileMapperArgs(sessionToken);
// Use uploadFolder so that failures of initial upload are retried
- return uploadFolder(accountId, src, dest, fileMapperArgs, {}, filePaths)
- .then(() => {
- logger.success(
- `Completed uploading files in ${src} to ${dest} in ${accountId}`
- );
- })
- .catch(error => {
- logger.error(
- `Initial uploading of folder "${src}" to "${dest} in account ${accountId} failed`
- );
- logErrorInstance(error, {
- accountId,
- });
- });
+ console.log(`Uploading ${portalId} ${src} ${dest} ${fileMapperArgs} ${filePaths}`)
+ const result = await uploadFolder(portalId, src, dest, fileMapperArgs, {}, filePaths);
+ console.log(result);
}
-const startPreviewWatchingService = () => {
+const startPreviewWatchingService = (sessionInfo) => {
+ const { src } = sessionInfo;
+
const watcher = chokidar.watch(src, {
ignoreInitial: true, // makes initial addition of files not trigger the watcher
ignored: file => shouldIgnoreFile(file),
@@ -195,20 +179,45 @@ const startPreviewWatchingService = () => {
return watcher;
}
+const startLocalHttpService = async (sessionInfo) => {
+ const expressServer = express();
+ //expressServer.use(bodyParser.json());
+ expressServer.use('/', await createPreviewServerRoutes(sessionInfo));
+
+ const httpServer = http.createServer(expressServer);
+ httpServer.listen(3000);
+ console.log('Now listening on port 3000');
+ return httpServer;
+}
+
const preview = async (
accountId,
src,
dest,
- { notify, filePaths }
+ { notify, filePaths, skipInitial }
) => {
- const sessionToken = Date.now();
+ const accountConfig = getAccountConfig(accountId);
+ const domains = await getPortalDomains(accountId);
+ const sessionInfo = {
+ src,
+ dest,
+ portalName: accountConfig.name,
+ portalId: accountId,
+ env: accountConfig.env,
+ personalAccessKey: accountConfig.personalAccessKey,
+ sessionToken: Date.now(),
+ domains
+ }
+
if (notify) {
ignoreFile(notify);
}
- const sessionInfo = { accountId, sessionToken, src, dest, notify };
- await initialPreviewBufferUpload(sessionInfo, filePaths);
- startPreviewWatchingService();
- startLocalHttpService();
+
+ if (!skipInitial) {
+ await initialPreviewBufferUpload(sessionInfo, filePaths);
+ }
+ startPreviewWatchingService(sessionInfo);
+ startLocalHttpService(sessionInfo);
}
diff --git a/lib/preview/createRoutes.js b/lib/preview/createRoutes.js
new file mode 100644
index 0000000..d648fc5
--- /dev/null
+++ b/lib/preview/createRoutes.js
@@ -0,0 +1,15 @@
+const { Router } = require('express');
+
+const { buildIndexRouteHandler } = require('./routes/index.js');
+const { buildProxyRouteHandler } = require('./routes/proxy.js');
+
+const createPreviewServerRoutes = async (sessionInfo) => {
+ const previewServerRouter = Router();
+ previewServerRouter.get('/', buildIndexRouteHandler(sessionInfo))
+ previewServerRouter.get('/proxy', buildProxyRouteHandler(sessionInfo))
+ return previewServerRouter;
+}
+
+module.exports = {
+ createPreviewServerRoutes,
+}
diff --git a/lib/preview/previewUtils.js b/lib/preview/previewUtils.js
new file mode 100644
index 0000000..aa5da7d
--- /dev/null
+++ b/lib/preview/previewUtils.js
@@ -0,0 +1,15 @@
+const { fetchDomains } = require('../../api/domains');
+
+const getPortalDomains = async (portalId) => {
+ try {
+ const result = await fetchDomains(portalId);
+ return result;
+ } catch (error) {
+ console.log("There was a problem fetching domains for your portal. You may be missing a scope necessary for this feature.")
+ return [];
+ }
+}
+
+module.exports = {
+ getPortalDomains,
+}
diff --git a/lib/preview/routes/index.js b/lib/preview/routes/index.js
new file mode 100644
index 0000000..c3154ca
--- /dev/null
+++ b/lib/preview/routes/index.js
@@ -0,0 +1,61 @@
+const buildIndexRouteHandler = (sessionInfo) => {
+ return async (req, res) => {
+ const responseHTML = buildIndexHtml(sessionInfo);
+ res.status(200).set({ 'Content-Type': 'text/html' }).end(responseHTML);
+ }
+}
+
+const buildIndexHtml = (sessionInfo) => {
+ const { domains } = sessionInfo;
+ return `
+
+
+
+
+ Hello World!
+ This markup was generated by the Cloudflare Worker.
+ This will allow us to inject scripts and UI into the resultant preview render after its fetched from the API if we want!
+ Here's an example to show the JS embed:
0
+ ${getSiteList(domains)}
+
+
+ `;
+}
+
+const getSiteList = (domains) => {
+ return ''
+ + domains.reduce((x,y) => x += `- ${y.domain}
`, '')
+ + '
';
+}
+
+const getEmbeddedJS = () => {
+ return `
+ function nzeros(n){
+ var building ='0';
+ var i = 0;
+ while (i < n-1){
+ building+='0';
+ i+=1
+ }
+ return building;
+ }
+ var z = document.querySelector('#test');
+ var goingRight = true;
+ function makeStep() {
+ if (goingRight) {
+ z.innerHTML+='0'
+ if (z.innerHTML.length === 10) goingRight=false;
+ } else {
+ z.innerHTML=nzeros(z.innerHTML.length-1)
+ if (z.innerHTML.length === 1) goingRight=true;
+ }
+ }
+ var y = setInterval(makeStep, 100)
+ `
+}
+
+module.exports = {
+ buildIndexRouteHandler
+}
diff --git a/lib/preview/routes/proxy.js b/lib/preview/routes/proxy.js
new file mode 100644
index 0000000..aec12e8
--- /dev/null
+++ b/lib/preview/routes/proxy.js
@@ -0,0 +1,33 @@
+const { get } = require('../../../http');
+
+const buildProxyRouteHandler = (sessionInfo) => {
+ const { portalId } = sessionInfo;
+
+ return async (req, res) => {
+ const { page } = req.query;
+ const result = await fetch(page);
+ const responseHTML = buildProxyHtml(sessionInfo, page)
+ res.status(200).set({ 'Content-Type': 'text/html' }).end(responseHTML);
+ }
+}
+
+const buildProxyHtml = (sessionInfo, page) => {
+ // If we want to embed a script or something into the proxy
+ // we can wrap it here or parse the returned HTML into the render
+ // but that's maybe trickier...
+ return `
+
+
+
+
+ ${page}
+
+ `;
+}
+
+
+
+
+module.exports = {
+ buildProxyRouteHandler
+}
From f3fb919757abf607062b15bfe5691386c4c1e00c Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Tue, 3 Oct 2023 13:20:35 -0400
Subject: [PATCH 04/56] Proxying works mostly
---
api/preview.js | 163 ++++++++++++++++++++++++++++++++++--
lib/preview/proxyPage.js | 93 ++++++++++++++++++++
lib/preview/routes/index.js | 34 +-------
lib/preview/routes/proxy.js | 62 ++++++++++++--
4 files changed, 306 insertions(+), 46 deletions(-)
create mode 100644 lib/preview/proxyPage.js
diff --git a/api/preview.js b/api/preview.js
index 47571bd..7a4394d 100644
--- a/api/preview.js
+++ b/api/preview.js
@@ -1,20 +1,169 @@
-const http = require('../http')
+const { get } = require('../http')
+const http = require('http');
+const https = require('https');
+const { getAccountId } = require('../lib/config');
-const DOMAINS_API_PATH = `/cms/v3/domains`;
+const CONTENT_API_PATH = `/content/api/v4/contents`;
-async function fetchDomains(accountId) {
+async function fetchPreviewInfo(accountId, contentId) {
try {
- const result = await http.get(accountId, {
- uri: DOMAINS_API_PATH,
+ const content = await get(accountId, {
+ uri: `${CONTENT_API_PATH}/${contentId}`,
+ query: { portalId: accountId },
json: true
});
- return result.results
+ return {
+ previewDomain: content.previewDomain ?? content.resolvedDomain,
+ previewKey: content.previewKey,
+ };
} catch (err) {
throw err;
}
}
+const requestContentPreview = async (url, portalId) => {
+ const urlObject = new URL(url);
+
+ const proxiedQueryParams = {};
+
+ // Convert searchParams to object param format hsGet/request expects
+ for (const queryKey of urlObject.searchParams.keys()) {
+ const values = urlObject.searchParams.getAll(queryKey);
+
+ if (values.length > 1) {
+ proxiedQueryParams[queryKey] = values;
+ } else {
+ proxiedQueryParams[queryKey] = values[0];
+ }
+ }
+
+ const accountId = getAccountId(portalId);
+
+ const response = await get(accountId, {
+ baseUrl: urlObject.origin,
+ uri: urlObject.pathname,
+ query: proxiedQueryParams,
+ json: false,
+ resolveWithFullResponse: true,
+ });
+
+ return response;
+}
+
+const requestPage = (url, redirects = 0, originalUrl=undefined) => {
+ if (redirects > 5) {
+ throw new Error(
+ `Hit too many redirects to HEAD requests for ${originalUrl}`
+ );
+ }
+
+ return new Promise((resolve, reject) => {
+ const protocolAPI = url.startsWith('https://') ? https : http;
+ protocolAPI
+ .request(
+ url,
+ {
+ method: 'HEAD',
+ headers: {
+ // Keep this user agent, so we don't get 403s from the request
+ ['User-Agent']: 'cms-dev-server',
+ },
+ },
+ async res => {
+ // Use a lib to better follow various redirects?
+ if (
+ res.statusCode >= 300 &&
+ res.statusCode < 400 &&
+ res.headers.location
+ ) {
+ console.log(`Redirecting to ${res.headers.location} (from ${url})`);
+ return resolve(
+ requestPage(
+ res.headers.location,
+ redirects + 1,
+ originalUrl ?? url
+ )
+ );
+ }
+
+ return resolve(res);
+ }
+ )
+ .on('error', err => {
+ console.error(err);
+ reject(err);
+ })
+ .end();
+ });
+}
+
+const fetchContentMetadata = async (
+ url,
+ portalId
+) => {
+ let res;
+ if (url.includes('hs_preview')) {
+ console.log('hellox')
+ res = await requestContentPreview(url, portalId);
+ } else {
+ console.log('helloy')
+ res = await requestPage(url);
+ console.log(res.headers)
+ console.log('past page req')
+ }
+
+ const portalIdHeader = res.headers['x-hs-hub-id'];
+ const contentId = res.headers['x-hs-content-id'];
+ console.log(`${portalIdHeader} ${contentId}`)
+ if (Array.isArray(portalIdHeader) || Array.isArray(contentId)) {
+ throw new Error(
+ `There were multiple hub ID or content ID headers in the HEAD request to ${url}`
+ );
+ } else {
+ const portalIdFromResponse = parseInt(portalIdHeader, 10);
+ console.log(`${portalIdFromResponse}`)
+ const pageAccount = await getAccountId(portalIdFromResponse);
+ console.log(`${portalIdFromResponse} ${pageAccount}`)
+ if (!pageAccount) {
+ throw new Error(
+ `No CLI auth for portal ${portalIdFromResponse} found, please run \`hs auth\``
+ );
+ }
+
+ if (res.statusCode !== 200) {
+ throw new Error(
+ `${res.statusCode} ${
+ res.statusMessage
+ } - Unable to obtain HEAD information from ${url}. ${
+ res.statusCode === 429 && res.headers['retry-after']
+ ? `Retry after ${res.headers['retry-after']} seconds.`
+ : ''
+ }`
+ );
+ } else if (!portalId || !contentId) {
+ throw new Error(
+ `Missing hub ID or content ID headers on HEAD request to ${url}`
+ );
+ } else if (isNaN(portalId)) {
+ throw new Error(
+ `Hub ID from the HEAD request is not a number: '${portalId}' (${url})`
+ );
+ }
+
+ // TODO, the hubs-hublets/hublets/find/ API is internal auth only.🤔...
+ // Prior implementation of fetchHubletForPortalId that would work execept for an internal auth issue:
+ // https://git.hubteam.com/HubSpot/cms-js-platform/blob/ef6e8029df6c09fe30d65a80073606f41ca324a6/cms-dev-server/src/proxyPage/fetchHubletForPortalId.ts
+ //
+ // const hublet = await fetchHubletForPortalId(portalId);
+
+ const hublet = 'na1';
+
+ return { portalId, contentId, hublet };
+ }
+}
+
module.exports = {
- fetchDomains,
+ fetchPreviewInfo,
+ fetchContentMetadata
}
diff --git a/lib/preview/proxyPage.js b/lib/preview/proxyPage.js
new file mode 100644
index 0000000..62f2da8
--- /dev/null
+++ b/lib/preview/proxyPage.js
@@ -0,0 +1,93 @@
+const http = require('../../http');
+const { fetchPreviewInfo } = require('../../api/preview');
+
+const proxyPage = async (
+ res,
+ portalId,
+ hublet,
+ contentId,
+ urlToProxy,
+) => {
+ try {
+ console.log('test1')
+ const previewInfo = await fetchPreviewInfo(portalId, contentId);
+ console.log('test2')
+
+ const { previewKey } = previewInfo;
+ console.log(previewKey)
+ console.log(urlToProxy)
+ const pageHtml = await makeProxyPreviewRequest(
+ portalId,
+ urlToProxy,
+ previewKey,
+ contentId
+ )
+ console.log('test3')
+
+ res.status(200).set({ 'Content-Type': 'text/html' }).end(pageHtml);
+ } catch (error) {
+ // TODO change error.stack to error.message before we publish
+ response
+ .status(500)
+ .end(
+ `Failed proxy render of page id = ${contentId} hub id = ${portalId}\n\n${error.stack}`
+ );
+ return;
+ }
+}
+
+const makeProxyPreviewRequest = async (
+ portalId,
+ urlToProxy,
+ previewKey,
+ contentId
+) => {
+ const url = new URL(urlToProxy);
+ console.log(`Proxying request to ${url.href}`, {
+ previewKey,
+ contentId
+ });
+
+ let proxiedQueryParams = {};
+
+ // Convert searchParams to object param format hsGet/request expects
+ for (const queryKey of url.searchParams.keys()) {
+ const values = url.searchParams.getAll(queryKey);
+
+ if (values.length > 1) {
+ proxiedQueryParams[queryKey] = values;
+ } else {
+ proxiedQueryParams[queryKey] = values[0];
+ }
+ }
+
+ proxiedQueryParams = {
+ ...proxiedQueryParams,
+ hs_preview: `${previewKey}-${contentId}`,
+ };
+
+ const result = http.get(portalId, {
+ baseUrl: url.origin,
+ uri: url.pathname,
+ query: proxiedQueryParams,
+ json: false,
+ useQuerystring: true,
+ });
+
+ return result.catch(error => {
+ if (
+ error.statusCode >= 400 &&
+ error.statusCode < 500 &&
+ !!error.response.body
+ ) {
+ return error.response.body;
+ } else {
+ throw error;
+ }
+ });
+
+}
+
+module.exports = {
+ proxyPage
+}
diff --git a/lib/preview/routes/index.js b/lib/preview/routes/index.js
index c3154ca..f8e5bcd 100644
--- a/lib/preview/routes/index.js
+++ b/lib/preview/routes/index.js
@@ -14,48 +14,18 @@ const buildIndexHtml = (sessionInfo) => {
Hello World!
This markup was generated by the Cloudflare Worker.
- This will allow us to inject scripts and UI into the resultant preview render after its fetched from the API if we want!
- Here's an example to show the JS embed:
0
+ Domains
${getSiteList(domains)}
-
`;
}
const getSiteList = (domains) => {
return '';
}
-const getEmbeddedJS = () => {
- return `
- function nzeros(n){
- var building ='0';
- var i = 0;
- while (i < n-1){
- building+='0';
- i+=1
- }
- return building;
- }
- var z = document.querySelector('#test');
- var goingRight = true;
- function makeStep() {
- if (goingRight) {
- z.innerHTML+='0'
- if (z.innerHTML.length === 10) goingRight=false;
- } else {
- z.innerHTML=nzeros(z.innerHTML.length-1)
- if (z.innerHTML.length === 1) goingRight=true;
- }
- }
- var y = setInterval(makeStep, 100)
- `
-}
-
module.exports = {
buildIndexRouteHandler
}
diff --git a/lib/preview/routes/proxy.js b/lib/preview/routes/proxy.js
index aec12e8..8fca3dc 100644
--- a/lib/preview/routes/proxy.js
+++ b/lib/preview/routes/proxy.js
@@ -1,17 +1,51 @@
const { get } = require('../../../http');
+const { fetchContentMetadata } = require('../../../api/preview');
+const { proxyPage } = require('../proxyPage');
const buildProxyRouteHandler = (sessionInfo) => {
- const { portalId } = sessionInfo;
return async (req, res) => {
- const { page } = req.query;
- const result = await fetch(page);
- const responseHTML = buildProxyHtml(sessionInfo, page)
- res.status(200).set({ 'Content-Type': 'text/html' }).end(responseHTML);
+ if (!req.query.page) {
+ res.status(200).set({ 'Content-Type': 'text/html' }).end(buildProxyIndex());
+ return;
+ }
+ // parse proxy page URL from query param
+ let proxyPageUrl;
+ try {
+ proxyPageUrl = new URL(req.query.page);
+ } catch (e) {
+ const message =
+ 'Please provide a valid page query parameter, e.g., http://localhost:3000/proxy?page=https://yourdomain.com/path';
+ console.warn(message);
+ return res.status(400).send(message);
+ }
+ // validate proxy page URL query params
+ if (proxyPageUrl.searchParams.has('hs_preview')) {
+ const message = `Can't make a proxy request, you cannot proxy URLs that include internal query params like hs_preview`;
+ console.warn(message);
+ return res.status(400).send(message);
+ }
+
+ try {
+ console.log('hello')
+ const { portalId, contentId, hublet } = await fetchContentMetadata(
+ proxyPageUrl.href,
+ sessionInfo.portalId
+ )
+ console.log(`world ${portalId} ${contentId} ${hublet}`)
+ await proxyPage(
+ res,
+ portalId,
+ hublet,
+ contentId,
+ proxyPageUrl.href
+ );
+ } catch (e) {
+ }
}
}
-const buildProxyHtml = (sessionInfo, page) => {
+const buildProxyIndex = () => {
// If we want to embed a script or something into the proxy
// we can wrap it here or parse the returned HTML into the render
// but that's maybe trickier...
@@ -20,7 +54,21 @@ const buildProxyHtml = (sessionInfo, page) => {
- ${page}
+
+
Local Proxy
+
+
`;
}
From 4fdace42e1dfb364c5dcab7293413d73812d65e2 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Tue, 3 Oct 2023 13:21:31 -0400
Subject: [PATCH 05/56] Remove logs
---
api/preview.js | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/api/preview.js b/api/preview.js
index 7a4394d..77957e1 100644
--- a/api/preview.js
+++ b/api/preview.js
@@ -104,27 +104,22 @@ const fetchContentMetadata = async (
) => {
let res;
if (url.includes('hs_preview')) {
- console.log('hellox')
res = await requestContentPreview(url, portalId);
} else {
- console.log('helloy')
res = await requestPage(url);
- console.log(res.headers)
- console.log('past page req')
}
const portalIdHeader = res.headers['x-hs-hub-id'];
const contentId = res.headers['x-hs-content-id'];
- console.log(`${portalIdHeader} ${contentId}`)
+
if (Array.isArray(portalIdHeader) || Array.isArray(contentId)) {
throw new Error(
`There were multiple hub ID or content ID headers in the HEAD request to ${url}`
);
} else {
const portalIdFromResponse = parseInt(portalIdHeader, 10);
- console.log(`${portalIdFromResponse}`)
const pageAccount = await getAccountId(portalIdFromResponse);
- console.log(`${portalIdFromResponse} ${pageAccount}`)
+
if (!pageAccount) {
throw new Error(
`No CLI auth for portal ${portalIdFromResponse} found, please run \`hs auth\``
From 0aae19b02ac8dc5b3562f76e3590c153970f11a1 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Thu, 5 Oct 2023 10:26:22 -0400
Subject: [PATCH 06/56] module and template route handlers
---
lib/preview/createRoutes.js | 8 +++++--
lib/preview/previewUtils.js | 13 +++++++++++
lib/preview/proxyPage.js | 6 +----
lib/preview/routes/index.js | 42 ++++++++++++++++++++++++++++++----
lib/preview/routes/module.js | 13 +++++++++++
lib/preview/routes/template.js | 13 +++++++++++
6 files changed, 84 insertions(+), 11 deletions(-)
create mode 100644 lib/preview/routes/module.js
create mode 100644 lib/preview/routes/template.js
diff --git a/lib/preview/createRoutes.js b/lib/preview/createRoutes.js
index d648fc5..bb6a83a 100644
--- a/lib/preview/createRoutes.js
+++ b/lib/preview/createRoutes.js
@@ -2,11 +2,15 @@ const { Router } = require('express');
const { buildIndexRouteHandler } = require('./routes/index.js');
const { buildProxyRouteHandler } = require('./routes/proxy.js');
+const { buildModuleRouteHandler } = require('./routes/module.js');
+const { buildTemplateRouteHandler } = require('./routes/template.js');
const createPreviewServerRoutes = async (sessionInfo) => {
const previewServerRouter = Router();
- previewServerRouter.get('/', buildIndexRouteHandler(sessionInfo))
- previewServerRouter.get('/proxy', buildProxyRouteHandler(sessionInfo))
+ previewServerRouter.get('/', buildIndexRouteHandler(sessionInfo));
+ previewServerRouter.get('/proxy', buildProxyRouteHandler(sessionInfo));
+ previewServerRouter.get('/module/:name', buildModuleRouteHandler(sessionInfo));
+ previewServerRouter.get('/template/:name', buildTemplateRouteHandler(sessionInfo));
return previewServerRouter;
}
diff --git a/lib/preview/previewUtils.js b/lib/preview/previewUtils.js
index aa5da7d..62b57a1 100644
--- a/lib/preview/previewUtils.js
+++ b/lib/preview/previewUtils.js
@@ -1,4 +1,5 @@
const { fetchDomains } = require('../../api/domains');
+const { getDirectoryContentsByPath } = require('../../api/fileMapper');
const getPortalDomains = async (portalId) => {
try {
@@ -10,6 +11,18 @@ const getPortalDomains = async (portalId) => {
}
}
+const getModules = async (sessionInfo) => {
+ const modulesPath = `${sessionInfo.dest}/modules`;
+ return getDirectoryContentsByPath(sessionInfo.portalId, modulesPath);
+}
+
+const getTemplates = async (sessionInfo) => {
+ const templatesPath = `${sessionInfo.dest}/templates`;
+ return getDirectoryContentsByPath(sessionInfo.portalId, templatesPath);
+}
+
module.exports = {
getPortalDomains,
+ getModules,
+ getTemplates,
}
diff --git a/lib/preview/proxyPage.js b/lib/preview/proxyPage.js
index 62f2da8..e38d3b7 100644
--- a/lib/preview/proxyPage.js
+++ b/lib/preview/proxyPage.js
@@ -9,20 +9,16 @@ const proxyPage = async (
urlToProxy,
) => {
try {
- console.log('test1')
const previewInfo = await fetchPreviewInfo(portalId, contentId);
- console.log('test2')
const { previewKey } = previewInfo;
- console.log(previewKey)
- console.log(urlToProxy)
+
const pageHtml = await makeProxyPreviewRequest(
portalId,
urlToProxy,
previewKey,
contentId
)
- console.log('test3')
res.status(200).set({ 'Content-Type': 'text/html' }).end(pageHtml);
} catch (error) {
diff --git a/lib/preview/routes/index.js b/lib/preview/routes/index.js
index f8e5bcd..bf38f73 100644
--- a/lib/preview/routes/index.js
+++ b/lib/preview/routes/index.js
@@ -1,12 +1,17 @@
+const { getModules, getTemplates } = require('../previewUtils');
+
const buildIndexRouteHandler = (sessionInfo) => {
return async (req, res) => {
- const responseHTML = buildIndexHtml(sessionInfo);
+ const responseHTML = await buildIndexHtml(sessionInfo);
res.status(200).set({ 'Content-Type': 'text/html' }).end(responseHTML);
}
}
-const buildIndexHtml = (sessionInfo) => {
+const buildIndexHtml = async (sessionInfo) => {
const { domains } = sessionInfo;
+ const modules = (await getModules(sessionInfo)).children;
+ const templates = (await getTemplates(sessionInfo)).children;
+
return `
@@ -14,15 +19,44 @@ const buildIndexHtml = (sessionInfo) => {
Hello World!
This markup was generated by the Cloudflare Worker.
- Domains
+ Domains
${getSiteList(domains)}
+ Modules
+ ${getModuleList(modules)}
+ Templates
+ ${getTemplateList(templates)}
`;
}
+
const getSiteList = (domains) => {
+ return listify(
+ domains,
+ domainObj => `proxy?page=http://${domainObj.domain}`,
+ domainObj => domainObj.domain
+ );
+}
+
+const getModuleList = (modules) => {
+ return listify(
+ modules.map(module => module.split('.')[0]),
+ moduleName => `module/${moduleName}`,
+ moduleName => moduleName
+ );
+}
+
+const getTemplateList = (templates) => {
+ return listify(
+ templates,
+ templateName => `template/${templateName}`,
+ templateName => templateName
+ )
+}
+
+const listify = (objects, hrefPathBuilder, labelBuilder) => {
return '';
}
diff --git a/lib/preview/routes/module.js b/lib/preview/routes/module.js
new file mode 100644
index 0000000..a6b73a7
--- /dev/null
+++ b/lib/preview/routes/module.js
@@ -0,0 +1,13 @@
+
+
+const buildModuleRouteHandler = (sessionInfo) => {
+ return async (req, res) => {
+ const { name } = req.params;
+
+ }
+}
+
+
+module.exports = {
+ buildModuleRouteHandler
+}
diff --git a/lib/preview/routes/template.js b/lib/preview/routes/template.js
new file mode 100644
index 0000000..1d6f34a
--- /dev/null
+++ b/lib/preview/routes/template.js
@@ -0,0 +1,13 @@
+
+
+const buildTemplateRouteHandler = (sessionInfo) => {
+ return async (req, res) => {
+ const { name } = req.params;
+
+ }
+}
+
+
+module.exports = {
+ buildTemplateRouteHandler
+}
From dd8970e7410f95263f5b29278245319f36f30d3f Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Wed, 11 Oct 2023 16:30:07 -0400
Subject: [PATCH 07/56] Flesh out module and template previews, add a few error
responses
---
api/designManager.js | 14 +++++++++
lib/preview.js | 3 +-
lib/preview/previewUtils.js | 23 +++++++++++++--
lib/preview/routes/index.js | 17 ++++++-----
lib/preview/routes/module.js | 54 +++++++++++++++++++++++++++++++++-
lib/preview/routes/proxy.js | 6 ++--
lib/preview/routes/template.js | 54 ++++++++++++++++++++++++++++++++++
7 files changed, 157 insertions(+), 14 deletions(-)
diff --git a/api/designManager.js b/api/designManager.js
index dceadc4..75a7432 100644
--- a/api/designManager.js
+++ b/api/designManager.js
@@ -43,9 +43,23 @@ async function fetchRawAssetByPath(accountId, path) {
});
}
+async function fetchModulesByPath(accountId, path) {
+ return http.get(accountId, {
+ uri: `${DESIGN_MANAGER_API_PATH}/modules/by-path/${path}?portalId=${accountId}`
+ })
+}
+
+async function fetchTemplatesByPath(accountId, path) {
+ return http.get(accountId, {
+ uri: `${DESIGN_MANAGER_API_PATH}/templates/by-path/${path}?portalId=${accountId}`
+ })
+}
+
module.exports = {
fetchBuiltinMapping,
fetchMenus,
fetchRawAssetByPath,
fetchThemes,
+ fetchModulesByPath,
+ fetchTemplatesByPath
};
diff --git a/lib/preview.js b/lib/preview.js
index 7cfc710..5e011bb 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -205,8 +205,9 @@ const preview = async (
portalId: accountId,
env: accountConfig.env,
personalAccessKey: accountConfig.personalAccessKey,
+ hublet: 'na1', // we find this when getting content metadata during page preview, can we get that ahead of time?
sessionToken: Date.now(),
- domains
+ domains,
}
if (notify) {
diff --git a/lib/preview/previewUtils.js b/lib/preview/previewUtils.js
index 62b57a1..d5c6a65 100644
--- a/lib/preview/previewUtils.js
+++ b/lib/preview/previewUtils.js
@@ -13,16 +13,35 @@ const getPortalDomains = async (portalId) => {
const getModules = async (sessionInfo) => {
const modulesPath = `${sessionInfo.dest}/modules`;
- return getDirectoryContentsByPath(sessionInfo.portalId, modulesPath);
+ return getDirectoryContentsByPath(sessionInfo.portalId, modulesPath)
+ .then(response => response.children)
+ .catch(err => null);
}
const getTemplates = async (sessionInfo) => {
const templatesPath = `${sessionInfo.dest}/templates`;
- return getDirectoryContentsByPath(sessionInfo.portalId, templatesPath);
+ return getDirectoryContentsByPath(sessionInfo.portalId, templatesPath)
+ .then(response => response.children)
+ .catch(err => null);
}
+const getPreviewUrl = (sessionInfo, queryParams) => {
+ const { portalId, env, hublet } = sessionInfo;
+
+ return `http://${portalId}.hubspotpreview${
+ env === 'qa' ? 'qa' : ''
+ }-${hublet}.com/_hcms/preview/template/multi?${stringifyQuery(queryParams)}`;
+}
+
+const stringifyQuery = (query) => {
+ return Object.keys(query)
+ .sort()
+ .map(key => `${key}=${query[key]}`)
+ .join('&');
+}
module.exports = {
getPortalDomains,
getModules,
getTemplates,
+ getPreviewUrl
}
diff --git a/lib/preview/routes/index.js b/lib/preview/routes/index.js
index bf38f73..bc15bf5 100644
--- a/lib/preview/routes/index.js
+++ b/lib/preview/routes/index.js
@@ -8,27 +8,28 @@ const buildIndexRouteHandler = (sessionInfo) => {
}
const buildIndexHtml = async (sessionInfo) => {
- const { domains } = sessionInfo;
- const modules = (await getModules(sessionInfo)).children;
- const templates = (await getTemplates(sessionInfo)).children;
+ const { domains, dest } = sessionInfo;
+ const modules = await getModules(sessionInfo);
+ const templates = await getTemplates(sessionInfo);
return `
- Hello World!
- This markup was generated by the Cloudflare Worker.
Domains
- ${getSiteList(domains)}
+ ${ domains ? getSiteList(domains) : p('No domains found') }
Modules
- ${getModuleList(modules)}
+ ${ modules ? getModuleList(modules) : p(`No modules found in ${dest}/modules`) }
Templates
- ${getTemplateList(templates)}
+ ${ templates ? getTemplateList(templates) : p(`No modules found in ${dest}/templates`) }
`;
}
+const p = (text) => {
+ return `${text}
`;
+}
const getSiteList = (domains) => {
return listify(
diff --git a/lib/preview/routes/module.js b/lib/preview/routes/module.js
index a6b73a7..fb9164b 100644
--- a/lib/preview/routes/module.js
+++ b/lib/preview/routes/module.js
@@ -1,12 +1,64 @@
+const http = require('http')
+const { fetchModulesByPath, fetchModuleBuffer } = require('../../../api/designManager');
+const { getPreviewUrl } = require('../previewUtils');
const buildModuleRouteHandler = (sessionInfo) => {
+ const { portalId } = sessionInfo;
+
return async (req, res) => {
const { name } = req.params;
-
+ if (!name) {
+ res.status(200).set({ 'Content-Type': 'text/html' }).end(buildModuleIndex());
+ return;
+ }
+ const customWidgetInfo = await fetchModulesByPath(portalId, `${sessionInfo.dest}/modules/${name}.module`);
+ if (!('moduleId' in customWidgetInfo && 'previewKey' in customWidgetInfo)) {
+ res.status(200).set({ 'Content-Type': 'text/html' }).end(buildErrorIndex());
+ return;
+ }
+ const params = {
+ module_id: customWidgetInfo.moduleId,
+ //is_buffered: true,
+ //portalId: portalId,
+ //language: 'en',
+ hs_preview_key: customWidgetInfo.previewKey,
+ //hsPreviewerApp: 'module',
+ //updated:
+ }
+ const previewUrl = getPreviewUrl(sessionInfo, params);
+ console.log(previewUrl)
+ res.redirect(previewUrl);
}
}
+const buildErrorIndex = () => {
+ return `
+
+
+
+
+
+
Error
+
Failed to fetch module data.
+
+
+ `;
+}
+
+const buildModuleIndex = () => {
+ return `
+
+
+
+
+
+
Module Index
+
Incomplete!
+
+
+ `;
+}
module.exports = {
buildModuleRouteHandler
diff --git a/lib/preview/routes/proxy.js b/lib/preview/routes/proxy.js
index 8fca3dc..3cc5087 100644
--- a/lib/preview/routes/proxy.js
+++ b/lib/preview/routes/proxy.js
@@ -9,6 +9,7 @@ const buildProxyRouteHandler = (sessionInfo) => {
res.status(200).set({ 'Content-Type': 'text/html' }).end(buildProxyIndex());
return;
}
+
// parse proxy page URL from query param
let proxyPageUrl;
try {
@@ -27,12 +28,11 @@ const buildProxyRouteHandler = (sessionInfo) => {
}
try {
- console.log('hello')
const { portalId, contentId, hublet } = await fetchContentMetadata(
proxyPageUrl.href,
sessionInfo.portalId
)
- console.log(`world ${portalId} ${contentId} ${hublet}`)
+
await proxyPage(
res,
portalId,
@@ -41,6 +41,8 @@ const buildProxyRouteHandler = (sessionInfo) => {
proxyPageUrl.href
);
} catch (e) {
+ const message = `Failed to fetch proxy page for ${proxyPageUrl.href}`;
+ return res.status(400).send(message);
}
}
}
diff --git a/lib/preview/routes/template.js b/lib/preview/routes/template.js
index 1d6f34a..0a27f2c 100644
--- a/lib/preview/routes/template.js
+++ b/lib/preview/routes/template.js
@@ -1,12 +1,66 @@
+const http = require('http')
+const { fetchTemplatesByPath } = require('../../../api/designManager');
+const { getPreviewUrl } = require('../previewUtils');
const buildTemplateRouteHandler = (sessionInfo) => {
+ const { portalId } = sessionInfo;
+
return async (req, res) => {
const { name } = req.params;
+ if (!name) {
+ res.status(200).set({ 'Content-Type': 'text/html' }).end(buildTemplateIndex());
+ return;
+ }
+ const calculatedPath = `${sessionInfo.dest}/templates/${name}`;
+ const templateInfo = await fetchTemplatesByPath(portalId, calculatedPath);
+ if (!('previewKey' in templateInfo)) {
+ res.status(502).set({ 'Content-Type': 'text/html' }).end(buildErrorIndex());
+ return;
+ }
+ const params = {
+ template_file_path: calculatedPath,
+ //is_buffered: true,
+ //portalId: portalId,
+ //language: 'en',
+ hs_preview_key: templateInfo.previewKey,
+ //hsPreviewerApp: 'module',
+ //updated
+ }
+ const previewUrl = getPreviewUrl(sessionInfo, params);
+ console.log(previewUrl)
+ res.redirect(previewUrl);
}
}
+const buildErrorIndex = () => {
+ return `
+
+
+
+
+
+
Error
+
Failed to fetch template data.
+
+
+ `;
+}
+
+const buildTemplateIndex = () => {
+ return `
+
+
+
+
+
+
Template
+
Provide a template path in the request
+
+
+ `;
+}
module.exports = {
buildTemplateRouteHandler
From fbd47017b53c6071eddb230ab68846ac0c30ffc3 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Thu, 12 Oct 2023 11:38:49 -0400
Subject: [PATCH 08/56] Remove filemapper preview changes for later
---
fileMapper.js | 3 +--
lib/constants.js | 1 -
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/fileMapper.js b/fileMapper.js
index 44dfa20..2427c5c 100644
--- a/fileMapper.js
+++ b/fileMapper.js
@@ -87,7 +87,7 @@ function isAllowedExtension(filepath) {
* @param {Mode} mode
*/
function useApiBuffer(mode) {
- return mode === Mode.draft || mode === Mode.preview;
+ return mode === Mode.draft;
}
/**
@@ -103,7 +103,6 @@ function getFileMapperQueryValues({ mode, options = {} }) {
buffer: useApiBuffer(mode),
environmentId: options.staging ? 2 : 1,
version: options.assetVersion,
- previewSession: options.previewSession
},
};
}
diff --git a/lib/constants.js b/lib/constants.js
index 31b9c68..328d33c 100644
--- a/lib/constants.js
+++ b/lib/constants.js
@@ -34,7 +34,6 @@ const FUNCTIONS_EXTENSION = 'functions';
const Mode = {
draft: 'draft',
publish: 'publish',
- preview: 'preview'
};
const DEFAULT_MODE = Mode.publish;
From b1fa76f42811e26037fe5fa42754479d0bede219 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Thu, 12 Oct 2023 14:03:36 -0400
Subject: [PATCH 09/56] Clean up preview a little bit
---
lib/preview.js | 92 +++++++++++++++++++++++---------------------------
1 file changed, 42 insertions(+), 50 deletions(-)
diff --git a/lib/preview.js b/lib/preview.js
index 5e011bb..c5f2d62 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -2,22 +2,18 @@ const http = require('http');
const path = require('path');
const chokidar = require('chokidar');
const express = require('express');
-const { default: PQueue } = require('p-queue');
-
const { logger } = require('../logger');
-const debounce = require('debounce');
const {
ApiErrorContext,
logApiErrorInstance,
logApiUploadErrorInstance,
- logErrorInstance,
} = require('../errorHandlers');
const { uploadFolder } = require('./uploadFolder');
const { shouldIgnoreFile, ignoreFile } = require('../ignoreRules');
const { getFileMapperQueryValues } = require('../fileMapper');
const { upload, deleteFile } = require('../api/fileMapper');
const escapeRegExp = require('./escapeRegExp');
-const { convertToUnixPath, isAllowedExtension, getCwd } = require('../path');
+const { convertToUnixPath, isAllowedExtension } = require('../path');
const { triggerNotify } = require('./notify');
const { getAccountConfig } = require('./config');
const { createPreviewServerRoutes } = require('./preview/createRoutes');
@@ -27,28 +23,28 @@ async function uploadFile(accountId, sessionToken, file, dest) {
logger.debug(`Attempting to upload file "${src}" to "${dest}"`);
const fileMapperArgs = buildFileMapperArgs(sessionToken);
- return queue.add(() => {
- return upload(accountId, src, dest, fileMapperArgs)
- .then(() => {
- logger.log(`Uploaded file ${src} to ${dest}`);
- notifyPreviewUrl(accountId, sessionToken);
- })
- .catch(() => {
- const uploadFailureMessage = `Uploading file ${src} to ${dest} failed`;
- logger.debug(uploadFailureMessage);
- logger.debug(`Retrying to upload file "${src}" to "${dest}"`);
- return upload(accountId, file, dest, fileMapperArgs).catch(error => {
- logger.error(uploadFailureMessage);
- logApiUploadErrorInstance(
- error,
- new ApiErrorContext({
- accountId,
- request: dest,
- payload: src,
- })
- );
- });
- });
+ return queue.add(async () => {
+ try {
+ await upload(accountId, src, dest, fileMapperArgs);
+ logger.log(`Uploaded file ${src} to ${dest}`);
+ } catch {
+ const uploadFailureMessage = `Uploading file ${src} to ${dest} failed`;
+ logger.debug(uploadFailureMessage);
+ logger.debug(`Retrying to upload file "${src}" to "${dest}"`);
+ try {
+ return await upload(accountId, file, dest, fileMapperArgs);
+ } catch (error) {
+ logger.error(uploadFailureMessage);
+ logApiUploadErrorInstance(
+ error,
+ new ApiErrorContext({
+ accountId,
+ request: dest,
+ payload: src,
+ })
+ );
+ }
+ }
});
}
@@ -56,22 +52,20 @@ async function deleteRemoteFile(accountId, sessionToken, remoteFilePath) {
logger.debug(`Attempting to delete file "${remoteFilePath}"`);
const fileMapperArgs = buildFileMapperArgs(sessionToken);
- return queue.add(() => {
- return deleteFile(accountId, remoteFilePath, fileMapperArgs)
- .then(() => {
- logger.log(`Deleted file ${remoteFilePath}`);
- notifyPreviewUrl(accountId, sessionToken);
- })
- .catch(error => {
- logger.error(`Deleting file ${remoteFilePath} failed`);
- logApiErrorInstance(
- error,
- new ApiErrorContext({
- accountId,
- request: remoteFilePath,
- })
- );
- });
+ return queue.add(async () => {
+ try {
+ await deleteFile(accountId, remoteFilePath, fileMapperArgs);
+ logger.log(`Deleted file ${remoteFilePath}`);
+ } catch (error) {
+ logger.error(`Deleting file ${remoteFilePath} failed`);
+ logApiErrorInstance(
+ error,
+ new ApiErrorContext({
+ accountId,
+ request: remoteFilePath,
+ })
+ );
+ }
});
}
@@ -146,10 +140,7 @@ const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
const initialPreviewBufferUpload = async (sessionInfo, filePaths) => {
const { portalId, sessionToken, src, dest } = sessionInfo
const fileMapperArgs = buildFileMapperArgs(sessionToken);
- // Use uploadFolder so that failures of initial upload are retried
- console.log(`Uploading ${portalId} ${src} ${dest} ${fileMapperArgs} ${filePaths}`)
- const result = await uploadFolder(portalId, src, dest, fileMapperArgs, {}, filePaths);
- console.log(result);
+ return uploadFolder(portalId, src, dest, fileMapperArgs, {}, filePaths);
}
const startPreviewWatchingService = (sessionInfo) => {
@@ -168,7 +159,7 @@ const startPreviewWatchingService = (sessionInfo) => {
watcher.on('ready', () => {
logger.log(
- `This is the message that displays when previewing begins.`
+ `This is the message that displays when previewing begins! ・°˖✧◝(⁰▿⁰)◜✧˖°.`
);
});
watcher.on('add', addFileCallback);
@@ -205,7 +196,9 @@ const preview = async (
portalId: accountId,
env: accountConfig.env,
personalAccessKey: accountConfig.personalAccessKey,
- hublet: 'na1', // we find this when getting content metadata during page preview, can we get that ahead of time?
+ // we find hublet later in the content metadata fetch
+ // can we get that ahead of time? hardcoding it for now
+ hublet: 'na1',
sessionToken: Date.now(),
domains,
}
@@ -219,7 +212,6 @@ const preview = async (
}
startPreviewWatchingService(sessionInfo);
startLocalHttpService(sessionInfo);
-
}
module.exports = {
From 2626400a3158284b6e77e27403e19ebcd75becb2 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Fri, 13 Oct 2023 11:10:00 -0400
Subject: [PATCH 10/56] Fixes logic around watch stuff
---
lib/preview.js | 78 ++++++++++++++++++++++----------------------------
1 file changed, 35 insertions(+), 43 deletions(-)
diff --git a/lib/preview.js b/lib/preview.js
index c5f2d62..bbd62b9 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -19,54 +19,50 @@ const { getAccountConfig } = require('./config');
const { createPreviewServerRoutes } = require('./preview/createRoutes');
const { getPortalDomains } = require('./preview/previewUtils');
-async function uploadFile(accountId, sessionToken, file, dest) {
+async function uploadFile(accountId, sessionToken, src, dest) {
logger.debug(`Attempting to upload file "${src}" to "${dest}"`);
const fileMapperArgs = buildFileMapperArgs(sessionToken);
- return queue.add(async () => {
+ try {
+ await upload(accountId, src, dest, fileMapperArgs);
+ logger.log(`Uploaded file ${src} to ${dest}`);
+ } catch {
+ const uploadFailureMessage = `Uploading file ${src} to ${dest} failed`;
+ logger.debug(uploadFailureMessage);
+ logger.debug(`Retrying to upload file "${src}" to "${dest}"`);
try {
await upload(accountId, src, dest, fileMapperArgs);
- logger.log(`Uploaded file ${src} to ${dest}`);
- } catch {
- const uploadFailureMessage = `Uploading file ${src} to ${dest} failed`;
- logger.debug(uploadFailureMessage);
- logger.debug(`Retrying to upload file "${src}" to "${dest}"`);
- try {
- return await upload(accountId, file, dest, fileMapperArgs);
- } catch (error) {
- logger.error(uploadFailureMessage);
- logApiUploadErrorInstance(
- error,
- new ApiErrorContext({
- accountId,
- request: dest,
- payload: src,
- })
- );
- }
- }
- });
-}
-
-async function deleteRemoteFile(accountId, sessionToken, remoteFilePath) {
- logger.debug(`Attempting to delete file "${remoteFilePath}"`);
- const fileMapperArgs = buildFileMapperArgs(sessionToken);
-
- return queue.add(async () => {
- try {
- await deleteFile(accountId, remoteFilePath, fileMapperArgs);
- logger.log(`Deleted file ${remoteFilePath}`);
} catch (error) {
- logger.error(`Deleting file ${remoteFilePath} failed`);
- logApiErrorInstance(
+ logger.error(uploadFailureMessage);
+ logApiUploadErrorInstance(
error,
new ApiErrorContext({
accountId,
- request: remoteFilePath,
+ request: dest,
+ payload: src,
})
);
}
- });
+ }
+};
+
+async function deleteRemoteFile(accountId, sessionToken, remoteFilePath) {
+ logger.debug(`Attempting to delete file "${remoteFilePath}"`);
+ const fileMapperArgs = buildFileMapperArgs(sessionToken);
+
+ try {
+ await deleteFile(accountId, remoteFilePath, fileMapperArgs);
+ logger.log(`Deleted file ${remoteFilePath}`);
+ } catch (error) {
+ logger.error(`Deleting file ${remoteFilePath} failed`);
+ logApiErrorInstance(
+ error,
+ new ApiErrorContext({
+ accountId,
+ request: remoteFilePath,
+ })
+ );
+ }
}
const getDesignManagerPath = (src, dest, file) => {
@@ -115,7 +111,7 @@ const buildDeleteFileFromPreviewBufferCallback = (sessionInfo, type) => {
}
const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
- const { accountId, src, sessionToken, notify } = sessionInfo;
+ const { portalId, src, dest, sessionToken, notify } = sessionInfo;
return async (filePath) => {
if (!isAllowedExtension(filePath)) {
@@ -126,13 +122,9 @@ const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
logger.debug(`Skipping ${filePath} due to an ignore rule`);
return;
}
- const destPath = getDesignManagerPath(sessionInfo, filePath);
+ const destPath = getDesignManagerPath(src, dest, filePath);
const fileMapperArgs = buildFileMapperArgs(sessionToken)
- const uploadPromise = uploadFile(accountId, filePath, destPath, {
- src,
- fileMapperArgs,
- commandOptions,
- });
+ const uploadPromise = uploadFile(portalId, sessionToken, filePath, destPath);
triggerNotify(notify, notifyMessage, filePath, uploadPromise);
}
}
From 4c404b47dfd1d575da8d79bd653150e80818daeb Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Mon, 16 Oct 2023 13:15:52 -0400
Subject: [PATCH 11/56] Make initial upload off by default
---
lib/preview.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/preview.js b/lib/preview.js
index bbd62b9..bbd2c85 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -177,7 +177,7 @@ const preview = async (
accountId,
src,
dest,
- { notify, filePaths, skipInitial }
+ { notify, filePaths, initialUpload }
) => {
const accountConfig = getAccountConfig(accountId);
const domains = await getPortalDomains(accountId);
@@ -199,7 +199,7 @@ const preview = async (
ignoreFile(notify);
}
- if (!skipInitial) {
+ if (initialUpload) {
await initialPreviewBufferUpload(sessionInfo, filePaths);
}
startPreviewWatchingService(sessionInfo);
From dd2108d3677d9629beea3f47eace19438b42d29a Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Mon, 16 Oct 2023 15:38:43 -0400
Subject: [PATCH 12/56] Add express
---
package.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/package.json b/package.json
index 228697f..e0153a9 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
"chokidar": "^3.0.1",
"content-disposition": "^0.5.3",
"debounce": "^1.2.0",
+ "express": "^4.18.2",
"extract-zip": "^1.6.7",
"findup-sync": "^3.0.0",
"fs-extra": "^8.1.0",
From 2eae3949d0013af7a1cba6f47d4ef0929ab488db Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Mon, 16 Oct 2023 15:44:32 -0400
Subject: [PATCH 13/56] Add express dep
---
package.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/package.json b/package.json
index 228697f..e0153a9 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
"chokidar": "^3.0.1",
"content-disposition": "^0.5.3",
"debounce": "^1.2.0",
+ "express": "^4.18.2",
"extract-zip": "^1.6.7",
"findup-sync": "^3.0.0",
"fs-extra": "^8.1.0",
From d06710be04672065d6ac71089cf8c97b871290b9 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Tue, 17 Oct 2023 14:09:30 -0400
Subject: [PATCH 14/56] Add auto refreshing
---
lib/preview.js | 47 +++++++++++++++++--------------------
lib/preview/createRoutes.js | 2 ++
lib/preview/previewUtils.js | 31 +++++++++++++++++++++++-
lib/preview/proxyPage.js | 7 ++++--
lib/preview/routes/proxy.js | 2 +-
5 files changed, 59 insertions(+), 30 deletions(-)
diff --git a/lib/preview.js b/lib/preview.js
index bbd2c85..7efc10e 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -18,6 +18,7 @@ const { triggerNotify } = require('./notify');
const { getAccountConfig } = require('./config');
const { createPreviewServerRoutes } = require('./preview/createRoutes');
const { getPortalDomains } = require('./preview/previewUtils');
+const { markRemoteFsDirty } = require('./preview/routes/meta');
async function uploadFile(accountId, sessionToken, src, dest) {
logger.debug(`Attempting to upload file "${src}" to "${dest}"`);
@@ -26,12 +27,14 @@ async function uploadFile(accountId, sessionToken, src, dest) {
try {
await upload(accountId, src, dest, fileMapperArgs);
logger.log(`Uploaded file ${src} to ${dest}`);
+ markRemoteFsDirty();
} catch {
const uploadFailureMessage = `Uploading file ${src} to ${dest} failed`;
logger.debug(uploadFailureMessage);
logger.debug(`Retrying to upload file "${src}" to "${dest}"`);
try {
await upload(accountId, src, dest, fileMapperArgs);
+ markRemoteFsDirty();
} catch (error) {
logger.error(uploadFailureMessage);
logApiUploadErrorInstance(
@@ -53,15 +56,23 @@ async function deleteRemoteFile(accountId, sessionToken, remoteFilePath) {
try {
await deleteFile(accountId, remoteFilePath, fileMapperArgs);
logger.log(`Deleted file ${remoteFilePath}`);
+ markRemoteFsDirty();
} catch (error) {
logger.error(`Deleting file ${remoteFilePath} failed`);
- logApiErrorInstance(
- error,
- new ApiErrorContext({
- accountId,
- request: remoteFilePath,
- })
- );
+ logger.debug(`Retrying deletion of file ${remoteFilePath}`)
+ try {
+ await deleteFile(acccountId, remoteFilePath, fileMapperArgs);
+ markRemoteFsDirty();
+ } catch (error) {
+ logger.error(`Deleting file ${remoteFilePath} failed`);
+ logApiErrorInstance(
+ error,
+ new ApiErrorContext({
+ accountId,
+ request: remoteFilePath,
+ })
+ );
+ }
}
}
@@ -88,28 +99,12 @@ const buildDeleteFileFromPreviewBufferCallback = (sessionInfo, type) => {
}
const remotePath = getDesignManagerPath(src, dest, filePath);
- logger.debug(`Attempting to delete ${type} "${remotePath}"`, type, remotePath);
- queue.add(() => {
- const deletePromise = deleteRemoteFile(accountId, filePath, remotePath)
- .then(() => {
- logger.log(`Deleted ${type} "${remotePath}"`);
- })
- .catch(error => {
- logger.error(`Deleting ${type} "${remotePath}" failed`);
- logApiErrorInstance(
- error,
- new ApiErrorContext({
- accountId,
- request: remotePath,
- })
- );
- });
- triggerNotify(notify, 'Removed', filePath, deletePromise);
- return deletePromise;
- });
+ const deletePromise = deleteRemoteFile(accountId, filePath, remotePath)
+ triggerNotify(notify, 'Removed', filePath, deletePromise);
}
}
+
const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
const { portalId, src, dest, sessionToken, notify } = sessionInfo;
diff --git a/lib/preview/createRoutes.js b/lib/preview/createRoutes.js
index bb6a83a..13a4df6 100644
--- a/lib/preview/createRoutes.js
+++ b/lib/preview/createRoutes.js
@@ -4,6 +4,7 @@ const { buildIndexRouteHandler } = require('./routes/index.js');
const { buildProxyRouteHandler } = require('./routes/proxy.js');
const { buildModuleRouteHandler } = require('./routes/module.js');
const { buildTemplateRouteHandler } = require('./routes/template.js');
+const { buildMetaRouteHandler } = require('./routes/meta.js');
const createPreviewServerRoutes = async (sessionInfo) => {
const previewServerRouter = Router();
@@ -11,6 +12,7 @@ const createPreviewServerRoutes = async (sessionInfo) => {
previewServerRouter.get('/proxy', buildProxyRouteHandler(sessionInfo));
previewServerRouter.get('/module/:name', buildModuleRouteHandler(sessionInfo));
previewServerRouter.get('/template/:name', buildTemplateRouteHandler(sessionInfo));
+ previewServerRouter.get('/meta', buildMetaRouteHandler(sessionInfo));
return previewServerRouter;
}
diff --git a/lib/preview/previewUtils.js b/lib/preview/previewUtils.js
index d5c6a65..6fd2709 100644
--- a/lib/preview/previewUtils.js
+++ b/lib/preview/previewUtils.js
@@ -39,9 +39,38 @@ const stringifyQuery = (query) => {
.map(key => `${key}=${query[key]}`)
.join('&');
}
+
+const insertAtEndOfBody = (html, script) => {
+ const insertAt = (baseStr, index, insertStr) => {
+ return `${baseStr.slice(0, index)}${insertStr}${baseStr.slice(index)}`;
+ }
+ const endOfBodyIndex = html.lastIndexOf("");
+ return insertAt(html, endOfBodyIndex, script);
+}
+
+const addRefreshScript = (html) => {
+ const refreshScript = `
+
+ `;
+ return insertAtEndOfBody(html, refreshScript);
+}
+
module.exports = {
getPortalDomains,
getModules,
getTemplates,
- getPreviewUrl
+ getPreviewUrl,
+ addRefreshScript
}
diff --git a/lib/preview/proxyPage.js b/lib/preview/proxyPage.js
index e38d3b7..b64566a 100644
--- a/lib/preview/proxyPage.js
+++ b/lib/preview/proxyPage.js
@@ -1,5 +1,6 @@
const http = require('../../http');
const { fetchPreviewInfo } = require('../../api/preview');
+const { addRefreshScript } = require('./previewUtils');
const proxyPage = async (
res,
@@ -11,6 +12,7 @@ const proxyPage = async (
try {
const previewInfo = await fetchPreviewInfo(portalId, contentId);
+ console.log('Successfully fetched previewInfo')
const { previewKey } = previewInfo;
const pageHtml = await makeProxyPreviewRequest(
@@ -19,8 +21,9 @@ const proxyPage = async (
previewKey,
contentId
)
-
- res.status(200).set({ 'Content-Type': 'text/html' }).end(pageHtml);
+ console.log('Successfully fetched pageHtml')
+ const wrappedHtml = addRefreshScript(pageHtml);
+ res.status(200).set({ 'Content-Type': 'text/html' }).end(wrappedHtml);
} catch (error) {
// TODO change error.stack to error.message before we publish
response
diff --git a/lib/preview/routes/proxy.js b/lib/preview/routes/proxy.js
index 3cc5087..7a6e253 100644
--- a/lib/preview/routes/proxy.js
+++ b/lib/preview/routes/proxy.js
@@ -32,7 +32,7 @@ const buildProxyRouteHandler = (sessionInfo) => {
proxyPageUrl.href,
sessionInfo.portalId
)
-
+ console.log('Successfully fetched content metadata')
await proxyPage(
res,
portalId,
From 4e20f17309a7de7d69760ccbe5eae4ca0c4b4309 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Tue, 17 Oct 2023 14:09:36 -0400
Subject: [PATCH 15/56] Add auto refreshing
---
lib/preview/routes/meta.js | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
create mode 100644 lib/preview/routes/meta.js
diff --git a/lib/preview/routes/meta.js b/lib/preview/routes/meta.js
new file mode 100644
index 0000000..804828a
--- /dev/null
+++ b/lib/preview/routes/meta.js
@@ -0,0 +1,22 @@
+const hsServerState = {
+ REMOTE_FS_IS_DIRTY: false
+}
+
+const buildMetaRouteHandler = (sessionInfo) => {
+ return async (req, res) => {
+ console.log(hsServerState)
+ res.json(hsServerState);
+ hsServerState["REMOTE_FS_IS_DIRTY"] = false;
+ }
+}
+
+const markRemoteFsDirty = () => {
+ hsServerState["REMOTE_FS_IS_DIRTY"] = true;
+}
+
+
+
+module.exports = {
+ buildMetaRouteHandler,
+ markRemoteFsDirty,
+}
From a3bd4d9d2fe72ff07f8879c35ee30f21c451848f Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Tue, 17 Oct 2023 14:19:47 -0400
Subject: [PATCH 16/56] Remove logs
---
lib/preview/proxyPage.js | 5 ++---
lib/preview/routes/proxy.js | 2 +-
2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/lib/preview/proxyPage.js b/lib/preview/proxyPage.js
index b64566a..4560ccb 100644
--- a/lib/preview/proxyPage.js
+++ b/lib/preview/proxyPage.js
@@ -1,7 +1,7 @@
const http = require('../../http');
const { fetchPreviewInfo } = require('../../api/preview');
const { addRefreshScript } = require('./previewUtils');
-
+``
const proxyPage = async (
res,
portalId,
@@ -12,7 +12,6 @@ const proxyPage = async (
try {
const previewInfo = await fetchPreviewInfo(portalId, contentId);
- console.log('Successfully fetched previewInfo')
const { previewKey } = previewInfo;
const pageHtml = await makeProxyPreviewRequest(
@@ -21,7 +20,7 @@ const proxyPage = async (
previewKey,
contentId
)
- console.log('Successfully fetched pageHtml')
+
const wrappedHtml = addRefreshScript(pageHtml);
res.status(200).set({ 'Content-Type': 'text/html' }).end(wrappedHtml);
} catch (error) {
diff --git a/lib/preview/routes/proxy.js b/lib/preview/routes/proxy.js
index 7a6e253..3cc5087 100644
--- a/lib/preview/routes/proxy.js
+++ b/lib/preview/routes/proxy.js
@@ -32,7 +32,7 @@ const buildProxyRouteHandler = (sessionInfo) => {
proxyPageUrl.href,
sessionInfo.portalId
)
- console.log('Successfully fetched content metadata')
+
await proxyPage(
res,
portalId,
From c7ec29b4874c183605d836cf034e4641b96b8751 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Tue, 17 Oct 2023 14:20:15 -0400
Subject: [PATCH 17/56] Whitespace
---
lib/preview/routes/meta.js | 2 --
1 file changed, 2 deletions(-)
diff --git a/lib/preview/routes/meta.js b/lib/preview/routes/meta.js
index 804828a..ce55e57 100644
--- a/lib/preview/routes/meta.js
+++ b/lib/preview/routes/meta.js
@@ -14,8 +14,6 @@ const markRemoteFsDirty = () => {
hsServerState["REMOTE_FS_IS_DIRTY"] = true;
}
-
-
module.exports = {
buildMetaRouteHandler,
markRemoteFsDirty,
From e05c847aef631a7592713fd313a710f0e3c01468 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Tue, 17 Oct 2023 14:22:31 -0400
Subject: [PATCH 18/56] Whitespace, remove logs
---
lib/preview.js | 1 -
lib/preview/previewUtils.js | 1 -
lib/preview/proxyPage.js | 6 +++---
lib/preview/routes/meta.js | 1 -
4 files changed, 3 insertions(+), 6 deletions(-)
diff --git a/lib/preview.js b/lib/preview.js
index 7efc10e..a726c10 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -104,7 +104,6 @@ const buildDeleteFileFromPreviewBufferCallback = (sessionInfo, type) => {
}
}
-
const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
const { portalId, src, dest, sessionToken, notify } = sessionInfo;
diff --git a/lib/preview/previewUtils.js b/lib/preview/previewUtils.js
index 6fd2709..2801be0 100644
--- a/lib/preview/previewUtils.js
+++ b/lib/preview/previewUtils.js
@@ -55,7 +55,6 @@ const addRefreshScript = (html) => {
const res = fetch('/meta')
.then(async (res) => {
const hsServerState = await res.json();
- console.log(hsServerState)
if (hsServerState["REMOTE_FS_IS_DIRTY"]) {
location.reload();
}
diff --git a/lib/preview/proxyPage.js b/lib/preview/proxyPage.js
index 4560ccb..bd78025 100644
--- a/lib/preview/proxyPage.js
+++ b/lib/preview/proxyPage.js
@@ -1,7 +1,7 @@
const http = require('../../http');
const { fetchPreviewInfo } = require('../../api/preview');
const { addRefreshScript } = require('./previewUtils');
-``
+
const proxyPage = async (
res,
portalId,
@@ -21,8 +21,8 @@ const proxyPage = async (
contentId
)
- const wrappedHtml = addRefreshScript(pageHtml);
- res.status(200).set({ 'Content-Type': 'text/html' }).end(wrappedHtml);
+ const embeddedHtml = addRefreshScript(pageHtml);
+ res.status(200).set({ 'Content-Type': 'text/html' }).end(embeddedHtml);
} catch (error) {
// TODO change error.stack to error.message before we publish
response
diff --git a/lib/preview/routes/meta.js b/lib/preview/routes/meta.js
index ce55e57..75a026b 100644
--- a/lib/preview/routes/meta.js
+++ b/lib/preview/routes/meta.js
@@ -4,7 +4,6 @@ const hsServerState = {
const buildMetaRouteHandler = (sessionInfo) => {
return async (req, res) => {
- console.log(hsServerState)
res.json(hsServerState);
hsServerState["REMOTE_FS_IS_DIRTY"] = false;
}
From 2d6676c2636a2fb34019ccae5614075454e883f1 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Thu, 19 Oct 2023 14:06:47 -0400
Subject: [PATCH 19/56] use nodefetchcommonjs, add routes
---
lib/preview/createRoutes.js | 13 ++++++++++++-
package.json | 1 +
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/lib/preview/createRoutes.js b/lib/preview/createRoutes.js
index 13a4df6..3078d1a 100644
--- a/lib/preview/createRoutes.js
+++ b/lib/preview/createRoutes.js
@@ -6,13 +6,24 @@ const { buildModuleRouteHandler } = require('./routes/module.js');
const { buildTemplateRouteHandler } = require('./routes/template.js');
const { buildMetaRouteHandler } = require('./routes/meta.js');
+const { proxyResourceRedirectHandler } = require('./routes/proxyResourceRedirect.js');
+
const createPreviewServerRoutes = async (sessionInfo) => {
const previewServerRouter = Router();
- previewServerRouter.get('/', buildIndexRouteHandler(sessionInfo));
previewServerRouter.get('/proxy', buildProxyRouteHandler(sessionInfo));
previewServerRouter.get('/module/:name', buildModuleRouteHandler(sessionInfo));
previewServerRouter.get('/template/:name', buildTemplateRouteHandler(sessionInfo));
previewServerRouter.get('/meta', buildMetaRouteHandler(sessionInfo));
+
+ previewServerRouter.get('/*', proxyResourceRedirectHandler);
+ previewServerRouter.post('/*', proxyResourceRedirectHandler);
+ previewServerRouter.delete('/*', proxyResourceRedirectHandler);
+ previewServerRouter.head('/*', proxyResourceRedirectHandler);
+ previewServerRouter.put('/*', proxyResourceRedirectHandler);
+ previewServerRouter.options('/*', proxyResourceRedirectHandler);
+
+ previewServerRouter.get('/', buildIndexRouteHandler(sessionInfo));
+
return previewServerRouter;
}
diff --git a/package.json b/package.json
index e0153a9..f8cc9e8 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"jest": "^26.6.3",
"js-yaml": "^4.1.0",
"moment": "^2.24.0",
+ "node-fetch-commonjs": "^3.3.2",
"p-queue": "^6.0.2",
"prettier": "^1.19.1",
"request": "^2.87.0",
From 872e25b2c1e330c19a19efa2c69b0ba28b57fa69 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Thu, 19 Oct 2023 14:07:07 -0400
Subject: [PATCH 20/56] proxyResourceRedirect.js
---
lib/preview/routes/proxyResourceRedirect.js | 49 +++++++++++++++++++++
1 file changed, 49 insertions(+)
create mode 100644 lib/preview/routes/proxyResourceRedirect.js
diff --git a/lib/preview/routes/proxyResourceRedirect.js b/lib/preview/routes/proxyResourceRedirect.js
new file mode 100644
index 0000000..fbf6b1f
--- /dev/null
+++ b/lib/preview/routes/proxyResourceRedirect.js
@@ -0,0 +1,49 @@
+const fetch = require('node-fetch-commonjs');
+
+const HCMS_PATH = '/_hcms/';
+const HS_FS_PATH = '/hs-fs/'
+const HUB_FS_PATH = '/hubfs/';
+
+const isInternalCMSRoute = (req) =>
+ req.path.startsWith(HCMS_PATH) ||
+ req.path.startsWith(HS_FS_PATH) ||
+ req.path.startsWith(HUB_FS_PATH);
+
+const proxyResourceRedirectHandler = (req, res, next) => {
+ const isAHtmlRequest = req.accepts().includes('text/html');
+
+ // proxy when referer's path is /proxy and has page query param
+ let proxyPageUrl;
+ let refererUrl;
+ try {
+ refererUrl = new URL(req.headers.referer);
+ proxyPageUrl = new URL(
+ new URL(req.headers.referer).searchParams.get('page')
+ );
+ } catch (e) {
+ next();
+ return;
+ }
+ if (!refererUrl.pathname.startsWith('/proxy')) {
+ next();
+ return;
+ }
+
+ // handle anchor links unless internal CMS route
+ if (isAHtmlRequest && !isInternalCMSRoute(req)) {
+ const encodedPageUrl = encodeURIComponent(
+ `${proxyPageUrl.origin}${req.url}`
+ );
+ res.redirect(`/proxy?page=${encodedPageUrl}`);
+ return;
+ }
+
+ const urlToProxy = `https://${proxyPageUrl.host}${req.url}`;
+ fetch(urlToProxy)
+ .then(response => response.body.pipe(res)) // https://github.com/node-fetch/node-fetch#bodybody
+ .catch(err => console.log(`crying on ${err}`));
+}
+
+module.exports = {
+ proxyResourceRedirectHandler,
+}
From caaf52598a9c7854bc591107135b5545822d085e Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Thu, 19 Oct 2023 14:09:00 -0400
Subject: [PATCH 21/56] Error log
---
lib/preview/routes/proxyResourceRedirect.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/preview/routes/proxyResourceRedirect.js b/lib/preview/routes/proxyResourceRedirect.js
index fbf6b1f..74fd42f 100644
--- a/lib/preview/routes/proxyResourceRedirect.js
+++ b/lib/preview/routes/proxyResourceRedirect.js
@@ -41,7 +41,7 @@ const proxyResourceRedirectHandler = (req, res, next) => {
const urlToProxy = `https://${proxyPageUrl.host}${req.url}`;
fetch(urlToProxy)
.then(response => response.body.pipe(res)) // https://github.com/node-fetch/node-fetch#bodybody
- .catch(err => console.log(`crying on ${err}`));
+ .catch(err => console.error(err));
}
module.exports = {
From 4818e70ff41248af0efc6e24ea5a808de9782ead Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Tue, 24 Oct 2023 14:05:30 -0400
Subject: [PATCH 22/56] @preview folder support, module/template
proxying/auto-refreshing
---
lib/preview.js | 6 ++++--
lib/preview/createRoutes.js | 4 ++--
lib/preview/previewUtils.js | 2 +-
lib/preview/routes/module.js | 28 ++++++++++++++++------------
lib/preview/routes/template.js | 29 ++++++++++++++++-------------
5 files changed, 39 insertions(+), 30 deletions(-)
diff --git a/lib/preview.js b/lib/preview.js
index a726c10..0daf2e4 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -2,6 +2,7 @@ const http = require('http');
const path = require('path');
const chokidar = require('chokidar');
const express = require('express');
+const { v4: uuidv4 } = require('uuid');
const { logger } = require('../logger');
const {
ApiErrorContext,
@@ -175,9 +176,10 @@ const preview = async (
) => {
const accountConfig = getAccountConfig(accountId);
const domains = await getPortalDomains(accountId);
+ const sessionToken = uuidv4();
const sessionInfo = {
src,
- dest,
+ dest: `@preview/${sessionToken}/${dest}`,
portalName: accountConfig.name,
portalId: accountId,
env: accountConfig.env,
@@ -185,7 +187,7 @@ const preview = async (
// we find hublet later in the content metadata fetch
// can we get that ahead of time? hardcoding it for now
hublet: 'na1',
- sessionToken: Date.now(),
+ sessionToken,
domains,
}
diff --git a/lib/preview/createRoutes.js b/lib/preview/createRoutes.js
index 3078d1a..136f6cf 100644
--- a/lib/preview/createRoutes.js
+++ b/lib/preview/createRoutes.js
@@ -11,8 +11,8 @@ const { proxyResourceRedirectHandler } = require('./routes/proxyResourceRedirect
const createPreviewServerRoutes = async (sessionInfo) => {
const previewServerRouter = Router();
previewServerRouter.get('/proxy', buildProxyRouteHandler(sessionInfo));
- previewServerRouter.get('/module/:name', buildModuleRouteHandler(sessionInfo));
- previewServerRouter.get('/template/:name', buildTemplateRouteHandler(sessionInfo));
+ previewServerRouter.get('/module/:modulePath', buildModuleRouteHandler(sessionInfo));
+ previewServerRouter.get('/template/:templatePath', buildTemplateRouteHandler(sessionInfo));
previewServerRouter.get('/meta', buildMetaRouteHandler(sessionInfo));
previewServerRouter.get('/*', proxyResourceRedirectHandler);
diff --git a/lib/preview/previewUtils.js b/lib/preview/previewUtils.js
index 2801be0..33aa4b4 100644
--- a/lib/preview/previewUtils.js
+++ b/lib/preview/previewUtils.js
@@ -52,7 +52,7 @@ const addRefreshScript = (html) => {
const refreshScript = `
`;
return insertAtEndOfBody(html, refreshScript);
@@ -78,14 +78,14 @@ const getSubDomainFromValidLocalDomain = hostname => {
};
// From(ish) https://git.hubteam.com/HubSpot/cloudflare-workers/blob/master/worker-lib/src/Constants.ts
-const HCMS_PATH = '/_hcms/';
-const HS_FS_PATH = '/hs-fs/';
-const HUB_FS_PATH = '/hubfs/';
+const internalRoutes = {
+ HCMS: '/_hcms/',
+ HS_FS: '/hs-fs/',
+ HUB_FS: '/hubfs/'
+}
const isInternalCMSRoute = (req) =>
- req.path.startsWith(HCMS_PATH) ||
- req.path.startsWith(HS_FS_PATH) ||
- req.path.startsWith(HUB_FS_PATH);
+ Object.values(internalRoutes).some((route => req.path.startsWith(route)));
const silenceConsoleWhile = async (act, ...args) => {
const tmpConsole = console;
@@ -95,14 +95,32 @@ const silenceConsoleWhile = async (act, ...args) => {
return result;
}
+const memoize = (func, cacheBustCallback) => {
+ const cache = {};
+ return async (...args) => {
+ const stringArgs = args.toString();
+ const storedResult = cache[stringArgs];
+ if (storedResult && (cacheBustCallback ? !cacheBustCallback() : true)) {
+ //console.log(`Cache hit ${func}`)
+ return storedResult;
+ }
+ //console.log(`Cache miss ${func}`)
+ const res = await func(...args);
+ cache[stringArgs] = res;
+ return res;
+ }
+}
+
+const lieAboutDest = (previewDest) => previewDest.split('/').slice(2).join('/');
+
module.exports = {
isInternalCMSRoute,
VALID_PROXY_DOMAIN_SUFFIXES,
getSubDomainFromValidLocalDomain,
getPortalDomains,
- getModules,
- getTemplates,
getPreviewUrl,
addRefreshScript,
- silenceConsoleWhile
+ silenceConsoleWhile,
+ memoize,
+ lieAboutDest
}
diff --git a/lib/preview/proxyPage.js b/lib/preview/proxyPage.js
index bd78025..05cf054 100644
--- a/lib/preview/proxyPage.js
+++ b/lib/preview/proxyPage.js
@@ -1,6 +1,8 @@
const http = require('../../http');
const { fetchPreviewInfo } = require('../../api/preview');
-const { addRefreshScript } = require('./previewUtils');
+const { addRefreshScript, memoize } = require('./previewUtils');
+
+const cachedFetchPreviewInfo = memoize(fetchPreviewInfo);
const proxyPage = async (
res,
@@ -10,7 +12,7 @@ const proxyPage = async (
urlToProxy,
) => {
try {
- const previewInfo = await fetchPreviewInfo(portalId, contentId);
+ const previewInfo = await cachedFetchPreviewInfo(portalId, contentId);
const { previewKey } = previewInfo;
@@ -41,10 +43,6 @@ const makeProxyPreviewRequest = async (
contentId
) => {
const url = new URL(urlToProxy);
- console.log(`Proxying request to ${url.href}`, {
- previewKey,
- contentId
- });
let proxiedQueryParams = {};
diff --git a/lib/preview/routes/index.js b/lib/preview/routes/index.js
index b6fc950..7ba4453 100644
--- a/lib/preview/routes/index.js
+++ b/lib/preview/routes/index.js
@@ -1,4 +1,9 @@
-const { getModules, getTemplates } = require('../previewUtils');
+const { lieAboutDest } = require('../previewUtils');
+const {
+ fetchTemplatesPathStartsWith,
+ fetchModulesPathStartsWith
+} = require('../../../api/designManager');
+const { parse: pathParse } = require('path');
const buildIndexRouteHandler = (sessionInfo) => {
return async (req, res) => {
@@ -8,59 +13,113 @@ const buildIndexRouteHandler = (sessionInfo) => {
}
const buildIndexHtml = async (sessionInfo) => {
- const { domains, dest } = sessionInfo;
- const modules = await getModules(sessionInfo);
- const templates = await getTemplates(sessionInfo);
-
+ const { domains, dest, PORT } = sessionInfo;
+ const fakeDest = lieAboutDest(dest);
+ const modulesHtml = await getModulesForDisplayToUser(sessionInfo);
+ const templatesHtml = await getTemplatesForDisplayToUser(sessionInfo);
return `
-
-
-
- Domains
- ${ domains ? getSiteList(domains) : p('No domains found') }
- Modules
- ${ modules ? getModuleList(modules) : p(`No modules found in ${dest}/modules`) }
- Templates
- ${ templates ? getTemplateList(templates) : p(`No modules found in ${dest}/templates`) }
-
+
+
+
+
+
+
Domains
+ ${ domains ? getSiteList(domains, PORT) : '
No domains found
' }
+
+
+
+
Modules
+ ${ modulesHtml ? modulesHtml : `
No modules found in ${fakeDest}
` }
+
+
+
Templates
+ ${ templatesHtml ? templatesHtml : `
No templates found in ${fakeDest}
` }
+
+
+
+
`;
}
-const p = (text) => {
- return `${text}
`;
-}
-
-const getSiteList = (domains) => {
+const getSiteList = (domains, PORT) => {
return listify(
domains,
- domainObj => `http://${domainObj.domain}.hslocal.net:3000`,
+ domainObj => `http://${domainObj.domain}.hslocal.net:${PORT}`,
domainObj => domainObj.domain
);
}
-const getModuleList = (modules) => {
- return listify(
- modules.map(module => module.split('.')[0]),
- moduleName => `http://hslocal.net:3000/module/${moduleName}`,
- moduleName => moduleName
- );
-}
-
-const getTemplateList = (templates) => {
- return listify(
- templates,
- templateName => `http://hslocal.net:3000/template/${templateName}`,
- templateName => templateName
- )
-}
-
const listify = (objects, hrefBuilder, labelBuilder) => {
return '';
}
+const getModulesForDisplayToUser = async (sessionInfo) => {
+ const { portalId, dest } = sessionInfo;
+ try {
+ const res = await fetchModulesPathStartsWith(portalId, dest);
+ const modulePaths = res
+ .objects
+ .map(moduleObj => moduleObj.path);
+ const filesGroupedByFolder = groupByFolder(modulePaths)
+ const htmlToRender = renderFilesGroupedByFolder(filesGroupedByFolder, 'module')
+ return htmlToRender;
+ } catch (err) {
+ console.log(`Failed to fetch modules for index page: ${err}`);
+ return undefined;
+ }
+}
+
+const getTemplatesForDisplayToUser = async (sessionInfo) => {
+ const { portalId, dest } = sessionInfo;
+ try {
+ const res = await fetchTemplatesPathStartsWith(portalId, dest);
+ const templatePaths = res
+ .objects
+ .filter(templateObj => templateObj.filename.split('.')[1] === 'html')
+ .map(templateObj => templateObj.path);
+ const filesGroupedByFolder = groupByFolder(templatePaths)
+ const htmlToRender = renderFilesGroupedByFolder(filesGroupedByFolder, 'template')
+ return htmlToRender;
+ } catch (err) {
+ console.log(`Failed to fetch templates for index page: ${err}`);
+ return undefined;
+ }
+}
+
+const groupByFolder = (paths) => {
+ return paths.reduce((acc, cur) => {
+ const { dir, base } = pathParse(cur);
+ if (Object.keys(acc).includes(dir)) {
+ acc[dir].push(base);
+ } else {
+ acc[dir] = [base];
+ }
+ return acc;
+ }, {})
+}
+
+const renderFilesGroupedByFolder = (filesGroupedByFolder, endpoint) => {
+ const folders = Object.keys(filesGroupedByFolder);
+ return folders.reduce((outer_acc, folder) => {
+ const fakeDest = lieAboutDest(folder);
+ const folderDisplay = ''
+ + fakeDest
+ + ''
+ + filesGroupedByFolder[folder].reduce((acc, cur) => {
+ const newListItem = `- ${cur}
`
+ acc += newListItem;
+ return acc;
+ }, '')
+ + '
';
+ outer_acc += folderDisplay;
+ return outer_acc;
+ }, '');
+}
+
+
module.exports = {
buildIndexRouteHandler
}
diff --git a/lib/preview/routes/meta.js b/lib/preview/routes/meta.js
index 75a026b..7041d21 100644
--- a/lib/preview/routes/meta.js
+++ b/lib/preview/routes/meta.js
@@ -13,7 +13,12 @@ const markRemoteFsDirty = () => {
hsServerState["REMOTE_FS_IS_DIRTY"] = true;
}
+const remoteFsIsDirty = () => {
+ return hsServerState["REMOTE_FS_IS_DIRTY"];
+}
+
module.exports = {
buildMetaRouteHandler,
markRemoteFsDirty,
+ remoteFsIsDirty
}
diff --git a/lib/preview/routes/module.js b/lib/preview/routes/module.js
index 5883b7d..e3fb30a 100644
--- a/lib/preview/routes/module.js
+++ b/lib/preview/routes/module.js
@@ -1,24 +1,37 @@
const http = require('../../../http')
-const { fetchModulesByPath, fetchModuleBuffer } = require('../../../api/designManager');
-const { addRefreshScript, getPreviewUrl } = require('../previewUtils');
+const { fetchModulesByPath } = require('../../../api/designManager');
+const { addRefreshScript, getPreviewUrl, memoize } = require('../previewUtils');
+
+const cachedFetchModulesByPath = memoize(fetchModulesByPath);
const buildModuleRouteHandler = (sessionInfo) => {
- const { portalId } = sessionInfo;
+ const { portalId, sessionToken } = sessionInfo;
return async (req, res) => {
+ console.log('Fetching module preview...')
+
const { modulePath } = req.params;
+
if (!modulePath) {
res.status(200).set({ 'Content-Type': 'text/html' }).end(buildModuleIndex());
return;
}
- const customWidgetInfo = await fetchModulesByPath(portalId, `${sessionInfo.dest}/modules/${modulePath}.module`);
- if (!('moduleId' in customWidgetInfo && 'previewKey' in customWidgetInfo)) {
+
+ const calculatedPath = `@preview/${sessionToken}/${modulePath}.module`;
+ let customWidgetInfo;
+ try {
+ customWidgetInfo = await cachedFetchModulesByPath(portalId, calculatedPath);
+ } catch (err) {
+ console.log(`Failed to fetch module preview for ${calculatedPath}`)
+ }
+ if (!customWidgetInfo || !('moduleId' in customWidgetInfo && 'previewKey' in customWidgetInfo)) {
res.status(200).set({ 'Content-Type': 'text/html' }).end(buildErrorIndex());
return;
}
const params = {
module_id: customWidgetInfo.moduleId,
hs_preview_key: customWidgetInfo.previewKey,
+ updated: Date.now(),
...req.query
}
const previewUrl = new URL(getPreviewUrl(sessionInfo, params)) ;
diff --git a/lib/preview/routes/proxyPageRouteHandler.js b/lib/preview/routes/proxyPageRouteHandler.js
index a42ea34..2978fc5 100644
--- a/lib/preview/routes/proxyPageRouteHandler.js
+++ b/lib/preview/routes/proxyPageRouteHandler.js
@@ -3,11 +3,10 @@ const { proxyPage } = require('../proxyPage.js');
const {
getSubDomainFromValidLocalDomain,
VALID_PROXY_DOMAIN_SUFFIXES,
+ memoize,
} = require('../previewUtils.js');
-const fullOriginalUrlFromRequest = (request) => {
- return `${request.protocol}://${request.get('host')}${request.originalUrl}`;
-}
+const cachedFetchContentMetadata = memoize(fetchContentMetadata);
const buildProxyPageRouteHandler = (sessionInfo) => {
return async (request, response, next) => {
@@ -15,7 +14,6 @@ const buildProxyPageRouteHandler = (sessionInfo) => {
return response.sendStatus(404);
}
- const fullOriginalUrl = fullOriginalUrlFromRequest(request);
const domainToProxy = getSubDomainFromValidLocalDomain(request.hostname);
if (!domainToProxy) {
@@ -29,9 +27,10 @@ const buildProxyPageRouteHandler = (sessionInfo) => {
}
}
const urlToProxy = `http://${domainToProxy}${request.originalUrl}`;
+ console.log(`Fetching preview for ${urlToProxy}`);
try {
- const { portalId, contentId, hublet } = await fetchContentMetadata(
+ const { portalId, contentId, hublet } = await cachedFetchContentMetadata(
urlToProxy,
sessionInfo.portalId
);
@@ -42,7 +41,6 @@ const buildProxyPageRouteHandler = (sessionInfo) => {
hublet,
contentId,
urlToProxy,
- fullOriginalUrl
);
} catch (e) {
next(e);
diff --git a/lib/preview/routes/template.js b/lib/preview/routes/template.js
index 6868344..4d28520 100644
--- a/lib/preview/routes/template.js
+++ b/lib/preview/routes/template.js
@@ -1,21 +1,30 @@
const http = require('../../../http');
const { fetchTemplatesByPath } = require('../../../api/designManager');
-const { getPreviewUrl } = require('../previewUtils');
-const { addRefreshScript } = require('../previewUtils');
+const { getPreviewUrl, addRefreshScript, memoize } = require('../previewUtils');
+const cachedFetchTemplatesByPath = memoize(fetchTemplatesByPath);
const buildTemplateRouteHandler = (sessionInfo) => {
- const { portalId } = sessionInfo;
+ const { portalId, sessionToken } = sessionInfo;
return async (req, res) => {
+ console.log(`Fetching template preview...`);
+
const { templatePath } = req.params;
+ console.log(templatePath)
if (!templatePath) {
res.status(200).set({ 'Content-Type': 'text/html' }).end(buildTemplateIndex());
return;
}
- const calculatedPath = `${sessionInfo.dest}/templates/${templatePath}`;
- const templateInfo = await fetchTemplatesByPath(portalId, calculatedPath);
- if (!('previewKey' in templateInfo)) {
+
+ const calculatedPath = `@preview/${sessionToken}/${templatePath}`;
+ let templateInfo;
+ try {
+ templateInfo = await cachedFetchTemplatesByPath(portalId, calculatedPath);
+ } catch (err) {
+ console.log(`Failed to fetch template info for ${calculatedPath}`);
+ }
+ if (!templateInfo || !('previewKey' in templateInfo)) {
res.status(502).set({ 'Content-Type': 'text/html' }).end(buildErrorIndex());
return;
}
diff --git a/lib/preview/shadowDevServer.js b/lib/preview/shadowDevServer.js
index 7f11e79..b72136a 100644
--- a/lib/preview/shadowDevServer.js
+++ b/lib/preview/shadowDevServer.js
@@ -5,7 +5,7 @@ const cors = require("cors");
const SHADOW_PORT = 1442;
-const startShadowDevService = async (sessionInfo) => {
+const startShadowDevServer = async (sessionInfo) => {
const portIsTaken = await new Promise((res, rej) => {
console.log(`Starting proxy link server on port ${SHADOW_PORT}`);
const testNetServer = net.createServer();
@@ -42,23 +42,24 @@ const startShadowDevService = async (sessionInfo) => {
shadowDevServer.use(
'/',
- await createShadowDevServerRoutes()
+ await createShadowDevServerRoutes(sessionInfo)
);
}
-const createShadowDevServerRoutes = async () => {
+const createShadowDevServerRoutes = async (sessionInfo) => {
const shadowDevServerRoutes = Router();
shadowDevServerRoutes.get(
'/check-if-local-dev-server',
cors(),
- shadowDevServerCheckHandler(),
+ shadowDevServerCheckHandler(sessionInfo),
);
return shadowDevServerRoutes;
}
-const shadowDevServerCheckHandler = () => async (req, res) => {
+const shadowDevServerCheckHandler = (sessionInfo) => async (req, res) => {
+ const { PORT } = sessionInfo;
const { query } = req;
if (query) {
@@ -71,7 +72,7 @@ const shadowDevServerCheckHandler = () => async (req, res) => {
let localProxyUrl;
hasJSBuildingBlocks = false;
- localProxyUrl = `http://${hostName}.hslocal.net:3000${pathName}`;
+ localProxyUrl = `http://${hostName}.hslocal.net:${PORT}${pathName}`;
res
.status(200)
@@ -81,5 +82,5 @@ const shadowDevServerCheckHandler = () => async (req, res) => {
}
module.exports = {
- startShadowDevService
+ startShadowDevServer
}
From 4a6b0453c61df9883abe91c9aebdc7cddb7c9a96 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Mon, 6 Nov 2023 10:48:44 -0500
Subject: [PATCH 27/56] Cleanup some semicolons, preview tracking
---
lib/preview.js | 8 ++---
lib/preview/previewUtils.js | 36 +++++++++++++++++--
lib/preview/routes/index.js | 11 ++++--
lib/preview/routes/module.js | 1 +
lib/preview/routes/proxyPageRouteHandler.js | 1 +
.../routes/proxyPathPageRouteHandler.js.js | 2 +-
6 files changed, 48 insertions(+), 11 deletions(-)
diff --git a/lib/preview.js b/lib/preview.js
index 2a3f86d..5ee6fc3 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -101,7 +101,7 @@ const buildDeleteFileFromPreviewBufferCallback = (sessionInfo, type) => {
}
const remotePath = getDesignManagerPath(src, dest, filePath);
- const deletePromise = deleteRemoteFile(accountId, filePath, remotePath)
+ const deletePromise = deleteRemoteFile(accountId, filePath, remotePath);
triggerNotify(notify, 'Removed', filePath, deletePromise);
}
}
@@ -119,14 +119,14 @@ const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
return;
}
const destPath = getDesignManagerPath(src, dest, filePath);
- const fileMapperArgs = buildFileMapperArgs(sessionToken)
+ const fileMapperArgs = buildFileMapperArgs(sessionToken);
const uploadPromise = uploadFile(portalId, sessionToken, filePath, destPath);
triggerNotify(notify, notifyMessage, filePath, uploadPromise);
}
}
const initialPreviewBufferUpload = async (sessionInfo, filePaths) => {
- const { portalId, sessionToken, src, dest } = sessionInfo
+ const { portalId, sessionToken, src, dest } = sessionInfo;
const fileMapperArgs = buildFileMapperArgs(sessionToken);
return uploadFolder(portalId, src, dest, fileMapperArgs, {}, filePaths);
}
@@ -180,7 +180,7 @@ const preview = async (
src,
dest,
{ notify, filePaths, skipUpload, noSsl, port }
-) => {`x`
+) => {
const accountConfig = getAccountConfig(accountId);
const domains = await getPortalDomains(accountId);
const sessionToken = 'test' // uuidv4();
diff --git a/lib/preview/previewUtils.js b/lib/preview/previewUtils.js
index c69a647..9e7b2f0 100644
--- a/lib/preview/previewUtils.js
+++ b/lib/preview/previewUtils.js
@@ -1,5 +1,7 @@
const { fetchDomains } = require('../../api/domains');
-const { getDirectoryContentsByPath } = require('../../api/fileMapper');
+const { getAccountId, isTrackingAllowed } = require('../config');
+const { platform, release } = require('os');
+const { trackUsage } = require('../../api/fileMapper');
const VALID_PROXY_DOMAIN_SUFFIXES = ['localhost', 'hslocal.net'];
@@ -111,7 +113,34 @@ const memoize = (func, cacheBustCallback) => {
}
}
-const lieAboutDest = (previewDest) => previewDest.split('/').slice(2).join('/');
+const hidePreviewInDest = (previewDest) => previewDest.split('/').slice(2).join('/');
+
+const trackPreviewEvent = async (action) => {
+ if (!isTrackingAllowed()) {
+ return;
+ }
+ const accountId = getAccountId();
+
+ trackUsage(
+ 'cms-cli-usage',
+ 'INTERACTION',
+ {
+ ...options,
+ ...{
+ applicationName: 'hubspot.preview',
+ os: `${platform()} ${release()}`,
+ authType: getAuthType(accountId),
+ },
+ action,
+ },
+ accountId
+ ).then(
+ (val) => console.log(`Succesfully tracked ${val}`),
+ (err) => {
+ console.error(`trackUsage failed: ${err}`);
+ }
+ );
+}
module.exports = {
isInternalCMSRoute,
@@ -122,5 +151,6 @@ module.exports = {
addRefreshScript,
silenceConsoleWhile,
memoize,
- lieAboutDest
+ hidePreviewInDest,
+ trackPreviewEvent
}
diff --git a/lib/preview/routes/index.js b/lib/preview/routes/index.js
index 7ba4453..1885705 100644
--- a/lib/preview/routes/index.js
+++ b/lib/preview/routes/index.js
@@ -1,4 +1,7 @@
-const { lieAboutDest } = require('../previewUtils');
+const {
+ hidePreviewInDest,
+ trackPreviewEvent,
+} = require('../previewUtils');
const {
fetchTemplatesPathStartsWith,
fetchModulesPathStartsWith
@@ -7,6 +10,8 @@ const { parse: pathParse } = require('path');
const buildIndexRouteHandler = (sessionInfo) => {
return async (req, res) => {
+ trackPreviewEvent('view-index-route');
+
const responseHTML = await buildIndexHtml(sessionInfo);
res.status(200).set({ 'Content-Type': 'text/html' }).end(responseHTML);
}
@@ -14,7 +19,7 @@ const buildIndexRouteHandler = (sessionInfo) => {
const buildIndexHtml = async (sessionInfo) => {
const { domains, dest, PORT } = sessionInfo;
- const fakeDest = lieAboutDest(dest);
+ const fakeDest = hidePreviewInDest(dest);
const modulesHtml = await getModulesForDisplayToUser(sessionInfo);
const templatesHtml = await getTemplatesForDisplayToUser(sessionInfo);
return `
@@ -104,7 +109,7 @@ const groupByFolder = (paths) => {
const renderFilesGroupedByFolder = (filesGroupedByFolder, endpoint) => {
const folders = Object.keys(filesGroupedByFolder);
return folders.reduce((outer_acc, folder) => {
- const fakeDest = lieAboutDest(folder);
+ const fakeDest = hidePreviewInDest(folder);
const folderDisplay = ''
+ fakeDest
+ ''
diff --git a/lib/preview/routes/module.js b/lib/preview/routes/module.js
index e3fb30a..e6217ee 100644
--- a/lib/preview/routes/module.js
+++ b/lib/preview/routes/module.js
@@ -9,6 +9,7 @@ const buildModuleRouteHandler = (sessionInfo) => {
return async (req, res) => {
console.log('Fetching module preview...')
+ trackPreviewEvent('view-module-route');
const { modulePath } = req.params;
diff --git a/lib/preview/routes/proxyPageRouteHandler.js b/lib/preview/routes/proxyPageRouteHandler.js
index 2978fc5..643818d 100644
--- a/lib/preview/routes/proxyPageRouteHandler.js
+++ b/lib/preview/routes/proxyPageRouteHandler.js
@@ -26,6 +26,7 @@ const buildProxyPageRouteHandler = (sessionInfo) => {
return response.status(400).send(message);
}
}
+ trackPreviewEvent('proxy-page-route')
const urlToProxy = `http://${domainToProxy}${request.originalUrl}`;
console.log(`Fetching preview for ${urlToProxy}`);
diff --git a/lib/preview/routes/proxyPathPageRouteHandler.js.js b/lib/preview/routes/proxyPathPageRouteHandler.js.js
index a7e8083..2216dad 100644
--- a/lib/preview/routes/proxyPathPageRouteHandler.js.js
+++ b/lib/preview/routes/proxyPathPageRouteHandler.js.js
@@ -25,7 +25,7 @@ const buildProxyRouteHandler = (sessionInfo) => {
console.warn(message);
return res.status(400).send(message);
}
-
+ trackPreviewEvent('proxy-path-route');
try {
const { portalId, contentId, hublet } = await fetchContentMetadata(
proxyPageUrl.href,
From 5cb6ae13768111ed129bdf8c8caa0638d85a1a03 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Mon, 6 Nov 2023 11:39:57 -0500
Subject: [PATCH 28/56] Add getauthtype
---
lib/preview/previewUtils.js | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/lib/preview/previewUtils.js b/lib/preview/previewUtils.js
index 9e7b2f0..e57da6a 100644
--- a/lib/preview/previewUtils.js
+++ b/lib/preview/previewUtils.js
@@ -1,5 +1,5 @@
const { fetchDomains } = require('../../api/domains');
-const { getAccountId, isTrackingAllowed } = require('../config');
+const { getAccountId, isTrackingAllowed, getAccountConfig } = require('../config');
const { platform, release } = require('os');
const { trackUsage } = require('../../api/fileMapper');
@@ -125,7 +125,6 @@ const trackPreviewEvent = async (action) => {
'cms-cli-usage',
'INTERACTION',
{
- ...options,
...{
applicationName: 'hubspot.preview',
os: `${platform()} ${release()}`,
@@ -134,14 +133,27 @@ const trackPreviewEvent = async (action) => {
action,
},
accountId
- ).then(
- (val) => console.log(`Succesfully tracked ${val}`),
+ ).catch(
(err) => {
console.error(`trackUsage failed: ${err}`);
}
);
}
+const getAuthType = (accountId) => {
+ let authType = 'unknown';
+
+ if (accountId) {
+ const accountConfig = getAccountConfig(accountId);
+ authType =
+ accountConfig && accountConfig.authType
+ ? accountConfig.authType
+ : 'apikey';
+ }
+
+ return authType;
+};
+
module.exports = {
isInternalCMSRoute,
VALID_PROXY_DOMAIN_SUFFIXES,
From 57bd6c38d645e8db58d0370c5cf2e646c7243308 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Mon, 6 Nov 2023 16:48:37 -0500
Subject: [PATCH 29/56] Add all changes for now...
---
api/designManager.js | 18 ++--
api/domains.js | 8 +-
api/preview.js | 21 ++---
lib/preview.js | 84 ++++++++++++-------
lib/preview/previewUtils.js | 12 ++-
lib/preview/routes/module.js | 8 +-
lib/preview/routes/proxyPageRouteHandler.js | 2 +-
.../routes/proxyPathPageRouteHandler.js.js | 1 +
lib/preview/routes/template.js | 4 +-
package.json | 3 +-
10 files changed, 92 insertions(+), 69 deletions(-)
diff --git a/api/designManager.js b/api/designManager.js
index 6b0d200..92904a2 100644
--- a/api/designManager.js
+++ b/api/designManager.js
@@ -45,26 +45,26 @@ async function fetchRawAssetByPath(accountId, path) {
async function fetchModulesByPath(accountId, path) {
return http.get(accountId, {
- uri: `${DESIGN_MANAGER_API_PATH}/modules/by-path/${path}?portalId=${accountId}`
- })
+ uri: `${DESIGN_MANAGER_API_PATH}/modules/by-path/${path}?portalId=${accountId}`,
+ });
}
async function fetchModulesPathStartsWith(accountId, path) {
return http.get(accountId, {
- uri: `${DESIGN_MANAGER_API_PATH}/modules?portalId=${accountId}&path__startswith=${path}`
- })
+ uri: `${DESIGN_MANAGER_API_PATH}/modules?portalId=${accountId}&path__startswith=${path}`,
+ });
}
async function fetchTemplatesByPath(accountId, path) {
return http.get(accountId, {
- uri: `${DESIGN_MANAGER_API_PATH}/templates/by-path/${path}?portalId=${accountId}`
- })
+ uri: `${DESIGN_MANAGER_API_PATH}/templates/by-path/${path}?portalId=${accountId}`,
+ });
}
async function fetchTemplatesPathStartsWith(accountId, path) {
return http.get(accountId, {
- uri: `${DESIGN_MANAGER_API_PATH}/templates?portalId=${accountId}&path__startswith=${path}`
- })
+ uri: `${DESIGN_MANAGER_API_PATH}/templates?portalId=${accountId}&path__startswith=${path}`,
+ });
}
module.exports = {
@@ -75,5 +75,5 @@ module.exports = {
fetchModulesByPath,
fetchModulesPathStartsWith,
fetchTemplatesByPath,
- fetchTemplatesPathStartsWith
+ fetchTemplatesPathStartsWith,
};
diff --git a/api/domains.js b/api/domains.js
index 47571bd..fd1f97a 100644
--- a/api/domains.js
+++ b/api/domains.js
@@ -1,4 +1,4 @@
-const http = require('../http')
+const http = require('../http');
const DOMAINS_API_PATH = `/cms/v3/domains`;
@@ -6,10 +6,10 @@ async function fetchDomains(accountId) {
try {
const result = await http.get(accountId, {
uri: DOMAINS_API_PATH,
- json: true
+ json: true,
});
- return result.results
+ return result.results;
} catch (err) {
throw err;
}
@@ -17,4 +17,4 @@ async function fetchDomains(accountId) {
module.exports = {
fetchDomains,
-}
+};
diff --git a/api/preview.js b/api/preview.js
index 77957e1..f73f279 100644
--- a/api/preview.js
+++ b/api/preview.js
@@ -1,4 +1,4 @@
-const { get } = require('../http')
+const { get } = require('../http');
const http = require('http');
const https = require('https');
const { getAccountId } = require('../lib/config');
@@ -10,7 +10,7 @@ async function fetchPreviewInfo(accountId, contentId) {
const content = await get(accountId, {
uri: `${CONTENT_API_PATH}/${contentId}`,
query: { portalId: accountId },
- json: true
+ json: true,
});
return {
@@ -49,9 +49,9 @@ const requestContentPreview = async (url, portalId) => {
});
return response;
-}
+};
-const requestPage = (url, redirects = 0, originalUrl=undefined) => {
+const requestPage = (url, redirects = 0, originalUrl = undefined) => {
if (redirects > 5) {
throw new Error(
`Hit too many redirects to HEAD requests for ${originalUrl}`
@@ -96,12 +96,9 @@ const requestPage = (url, redirects = 0, originalUrl=undefined) => {
})
.end();
});
-}
+};
-const fetchContentMetadata = async (
- url,
- portalId
-) => {
+const fetchContentMetadata = async (url, portalId) => {
let res;
if (url.includes('hs_preview')) {
res = await requestContentPreview(url, portalId);
@@ -156,9 +153,9 @@ const fetchContentMetadata = async (
return { portalId, contentId, hublet };
}
-}
+};
module.exports = {
fetchPreviewInfo,
- fetchContentMetadata
-}
+ fetchContentMetadata,
+};
diff --git a/lib/preview.js b/lib/preview.js
index 5ee6fc3..b46d781 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -20,8 +20,10 @@ const { getAccountConfig } = require('./config');
const { createPreviewServerRoutes } = require('./preview/createRoutes');
const { getPortalDomains } = require('./preview/previewUtils');
const { markRemoteFsDirty } = require('./preview/routes/meta');
-const { startShadowDevServer } = require('./preview/shadowDevServer')
-const { createHttpsRedirectingServer } = require('./preview/httpsRedirectingServer');
+const { startShadowDevServer } = require('./preview/shadowDevServer');
+const {
+ createHttpsRedirectingServer,
+} = require('./preview/httpsRedirectingServer');
async function uploadFile(accountId, sessionToken, src, dest) {
logger.debug(`Attempting to upload file "${src}" to "${dest}"`);
@@ -50,7 +52,7 @@ async function uploadFile(accountId, sessionToken, src, dest) {
);
}
}
-};
+}
async function deleteRemoteFile(accountId, sessionToken, remoteFilePath) {
logger.debug(`Attempting to delete file "${remoteFilePath}"`);
@@ -62,7 +64,7 @@ async function deleteRemoteFile(accountId, sessionToken, remoteFilePath) {
markRemoteFsDirty();
} catch (error) {
logger.error(`Deleting file ${remoteFilePath} failed`);
- logger.debug(`Retrying deletion of file ${remoteFilePath}`)
+ logger.debug(`Retrying deletion of file ${remoteFilePath}`);
try {
await deleteFile(acccountId, remoteFilePath, fileMapperArgs);
markRemoteFsDirty();
@@ -85,16 +87,16 @@ const getDesignManagerPath = (src, dest, file) => {
return convertToUnixPath(path.join(dest, relativePath));
};
-const buildFileMapperArgs = (sessionToken) => {
+const buildFileMapperArgs = sessionToken => {
return getFileMapperQueryValues({
mode: 'publish',
});
-}
+};
const buildDeleteFileFromPreviewBufferCallback = (sessionInfo, type) => {
const { accountId, src, dest, notify } = sessionInfo;
- return (filePath) => {
+ return filePath => {
if (shouldIgnoreFile(filePath)) {
logger.debug(`Skipping ${filePath} due to an ignore rule`);
return;
@@ -103,13 +105,13 @@ const buildDeleteFileFromPreviewBufferCallback = (sessionInfo, type) => {
const remotePath = getDesignManagerPath(src, dest, filePath);
const deletePromise = deleteRemoteFile(accountId, filePath, remotePath);
triggerNotify(notify, 'Removed', filePath, deletePromise);
- }
-}
+ };
+};
const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
const { portalId, src, dest, sessionToken, notify } = sessionInfo;
- return async (filePath) => {
+ return async filePath => {
if (!isAllowedExtension(filePath)) {
logger.debug(`Skipping ${filePath} due to unsupported extension`);
return;
@@ -120,19 +122,23 @@ const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
}
const destPath = getDesignManagerPath(src, dest, filePath);
const fileMapperArgs = buildFileMapperArgs(sessionToken);
- const uploadPromise = uploadFile(portalId, sessionToken, filePath, destPath);
+ const uploadPromise = uploadFile(
+ portalId,
+ sessionToken,
+ filePath,
+ destPath
+ );
triggerNotify(notify, notifyMessage, filePath, uploadPromise);
- }
-}
+ };
+};
const initialPreviewBufferUpload = async (sessionInfo, filePaths) => {
const { portalId, sessionToken, src, dest } = sessionInfo;
const fileMapperArgs = buildFileMapperArgs(sessionToken);
return uploadFolder(portalId, src, dest, fileMapperArgs, {}, filePaths);
-}
-
+};
-const startPreviewWatcher = async (sessionInfo) => {
+const startPreviewWatcher = async sessionInfo => {
const { src } = sessionInfo;
let watcherIsReady = false;
@@ -141,10 +147,22 @@ const startPreviewWatcher = async (sessionInfo) => {
ignored: file => shouldIgnoreFile(file),
});
- const addFileCallback = buildUploadFileToPreviewBufferCallback(sessionInfo, 'Added');
- const changeFileCallback = buildUploadFileToPreviewBufferCallback(sessionInfo, 'Change');
- const deleteFileCallback = buildDeleteFileFromPreviewBufferCallback(sessionInfo, 'file');
- const deleteFolderCallback = buildDeleteFileFromPreviewBufferCallback(sessionInfo, 'folder');
+ const addFileCallback = buildUploadFileToPreviewBufferCallback(
+ sessionInfo,
+ 'Added'
+ );
+ const changeFileCallback = buildUploadFileToPreviewBufferCallback(
+ sessionInfo,
+ 'Change'
+ );
+ const deleteFileCallback = buildDeleteFileFromPreviewBufferCallback(
+ sessionInfo,
+ 'file'
+ );
+ const deleteFolderCallback = buildDeleteFileFromPreviewBufferCallback(
+ sessionInfo,
+ 'folder'
+ );
watcher.on('ready', () => {
console.log('Local file watching service has started!');
@@ -156,24 +174,24 @@ const startPreviewWatcher = async (sessionInfo) => {
watcher.on('unlinkDir', deleteFolderCallback);
function sleep(ms) {
- return new Promise((resolve) => {
+ return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
while (!watcherIsReady) {
- await sleep(1)
+ await sleep(1);
// do nothing... surely there's a better way to do this...
}
return watcher;
-}
+};
-const createLocalHttpServer = async (sessionInfo) => {
+const createLocalHttpServer = async sessionInfo => {
const expressServer = express();
//expressServer.use(bodyParser.json());
expressServer.use('/', await createPreviewServerRoutes(sessionInfo));
return expressServer;
-}
+};
const preview = async (
accountId,
@@ -183,7 +201,7 @@ const preview = async (
) => {
const accountConfig = getAccountConfig(accountId);
const domains = await getPortalDomains(accountId);
- const sessionToken = 'test' // uuidv4();
+ const sessionToken = 'test'; // uuidv4();
const PORT = port || 3000;
const protocol = noSsl ? 'http' : 'https';
@@ -201,7 +219,7 @@ const preview = async (
domains,
PORT,
protocol,
- }
+ };
if (notify) {
ignoreFile(notify);
@@ -214,15 +232,21 @@ const preview = async (
const previewWatcher = await startPreviewWatcher(sessionInfo);
if (!noSsl) {
- const { server, innerHTTPServer, innerHTTPSServer } = await createHttpsRedirectingServer(expressServer, domains);
+ const {
+ server,
+ innerHTTPServer,
+ innerHTTPSServer,
+ } = await createHttpsRedirectingServer(expressServer, domains);
server.listen(PORT);
} else {
const httpServer = http.createServer(expressServer);
httpServer.listen(PORT);
}
startShadowDevServer(sessionInfo);
- console.log(`HubSpot preview local dev server hosting at ${protocol}://hslocal.net:${PORT}, portalId=${accountId}`)
-}
+ console.log(
+ `HubSpot preview local dev server hosting at ${protocol}://hslocal.net:${PORT}, portalId=${accountId}`
+ );
+};
module.exports = {
preview,
diff --git a/lib/preview/previewUtils.js b/lib/preview/previewUtils.js
index e57da6a..c6a7eb5 100644
--- a/lib/preview/previewUtils.js
+++ b/lib/preview/previewUtils.js
@@ -122,20 +122,18 @@ const trackPreviewEvent = async (action) => {
const accountId = getAccountId();
trackUsage(
- 'cms-cli-usage',
+ 'cli-interaction',
'INTERACTION',
{
- ...{
- applicationName: 'hubspot.preview',
- os: `${platform()} ${release()}`,
- authType: getAuthType(accountId),
- },
+ applicationName: 'hubspot.preview',
+ os: `${platform()} ${release()}`,
+ authType: getAuthType(accountId),
action,
},
accountId
).catch(
(err) => {
- console.error(`trackUsage failed: ${err}`);
+ console.error(`trackUsage failed: ${JSON.stringify(err, null, 2)}`);
}
);
}
diff --git a/lib/preview/routes/module.js b/lib/preview/routes/module.js
index e6217ee..91c580c 100644
--- a/lib/preview/routes/module.js
+++ b/lib/preview/routes/module.js
@@ -1,6 +1,11 @@
const http = require('../../../http')
const { fetchModulesByPath } = require('../../../api/designManager');
-const { addRefreshScript, getPreviewUrl, memoize } = require('../previewUtils');
+const {
+ addRefreshScript,
+ getPreviewUrl,
+ memoize,
+ trackPreviewEvent
+} = require('../previewUtils');
const cachedFetchModulesByPath = memoize(fetchModulesByPath);
@@ -8,7 +13,6 @@ const buildModuleRouteHandler = (sessionInfo) => {
const { portalId, sessionToken } = sessionInfo;
return async (req, res) => {
- console.log('Fetching module preview...')
trackPreviewEvent('view-module-route');
const { modulePath } = req.params;
diff --git a/lib/preview/routes/proxyPageRouteHandler.js b/lib/preview/routes/proxyPageRouteHandler.js
index 643818d..e438c97 100644
--- a/lib/preview/routes/proxyPageRouteHandler.js
+++ b/lib/preview/routes/proxyPageRouteHandler.js
@@ -4,6 +4,7 @@ const {
getSubDomainFromValidLocalDomain,
VALID_PROXY_DOMAIN_SUFFIXES,
memoize,
+ trackPreviewEvent
} = require('../previewUtils.js');
const cachedFetchContentMetadata = memoize(fetchContentMetadata);
@@ -28,7 +29,6 @@ const buildProxyPageRouteHandler = (sessionInfo) => {
}
trackPreviewEvent('proxy-page-route')
const urlToProxy = `http://${domainToProxy}${request.originalUrl}`;
- console.log(`Fetching preview for ${urlToProxy}`);
try {
const { portalId, contentId, hublet } = await cachedFetchContentMetadata(
diff --git a/lib/preview/routes/proxyPathPageRouteHandler.js.js b/lib/preview/routes/proxyPathPageRouteHandler.js.js
index 2216dad..ea131ea 100644
--- a/lib/preview/routes/proxyPathPageRouteHandler.js.js
+++ b/lib/preview/routes/proxyPathPageRouteHandler.js.js
@@ -1,5 +1,6 @@
const { fetchContentMetadata } = require('../../../api/preview');
const { proxyPage } = require('../proxyPage');
+const { trackPreviewEvent } = require('../previewUtils');
const buildProxyRouteHandler = (sessionInfo) => {
diff --git a/lib/preview/routes/template.js b/lib/preview/routes/template.js
index 4d28520..9910d2b 100644
--- a/lib/preview/routes/template.js
+++ b/lib/preview/routes/template.js
@@ -8,10 +8,8 @@ const buildTemplateRouteHandler = (sessionInfo) => {
const { portalId, sessionToken } = sessionInfo;
return async (req, res) => {
- console.log(`Fetching template preview...`);
-
const { templatePath } = req.params;
- console.log(templatePath)
+
if (!templatePath) {
res.status(200).set({ 'Content-Type': 'text/html' }).end(buildTemplateIndex());
return;
diff --git a/package.json b/package.json
index 66aed4e..457dfee 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,7 @@
"scripts": {
"check-main": "branch=$(git rev-parse --abbrev-ref HEAD) && [ $branch = main ] || (echo 'Error: New release can only be published on main branch' && exit 1)",
"lint": "eslint . && prettier --list-different packages/**/*.{js,json}",
- "prettier:write": "prettier --write packages/**/*.{js,json}",
+ "prettier:write": "prettier --write **/*.{js,json}",
"pub": "npm publish --tag latest",
"push": "git push --atomic origin main v$npm_package_version",
"release:major": "yarn check-main && yarn version --major && yarn pub && yarn push",
@@ -34,6 +34,7 @@
"js-yaml": "^4.1.0",
"mkcert-cli": "^1.5.0",
"moment": "^2.24.0",
+ "net": "^1.0.2",
"node-fetch-commonjs": "^3.3.2",
"p-queue": "^6.0.2",
"prettier": "^1.19.1",
From 18b4cd53e9762c12be83603bdf4a10af27aa7d31 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Mon, 6 Nov 2023 17:46:59 -0500
Subject: [PATCH 30/56] Undo prettier
---
api/designManager.js | 8 +++---
api/domains.js | 8 +++---
api/preview.js | 21 +++++++++-------
lib/preview.js | 58 ++++++++++++++++++--------------------------
package.json | 1 -
5 files changed, 43 insertions(+), 53 deletions(-)
diff --git a/api/designManager.js b/api/designManager.js
index 92904a2..f1ccc5d 100644
--- a/api/designManager.js
+++ b/api/designManager.js
@@ -45,8 +45,8 @@ async function fetchRawAssetByPath(accountId, path) {
async function fetchModulesByPath(accountId, path) {
return http.get(accountId, {
- uri: `${DESIGN_MANAGER_API_PATH}/modules/by-path/${path}?portalId=${accountId}`,
- });
+ uri: `${DESIGN_MANAGER_API_PATH}/modules/by-path/${path}?portalId=${accountId}`
+ })
}
async function fetchModulesPathStartsWith(accountId, path) {
@@ -57,8 +57,8 @@ async function fetchModulesPathStartsWith(accountId, path) {
async function fetchTemplatesByPath(accountId, path) {
return http.get(accountId, {
- uri: `${DESIGN_MANAGER_API_PATH}/templates/by-path/${path}?portalId=${accountId}`,
- });
+ uri: `${DESIGN_MANAGER_API_PATH}/templates/by-path/${path}?portalId=${accountId}`
+ })
}
async function fetchTemplatesPathStartsWith(accountId, path) {
diff --git a/api/domains.js b/api/domains.js
index fd1f97a..47571bd 100644
--- a/api/domains.js
+++ b/api/domains.js
@@ -1,4 +1,4 @@
-const http = require('../http');
+const http = require('../http')
const DOMAINS_API_PATH = `/cms/v3/domains`;
@@ -6,10 +6,10 @@ async function fetchDomains(accountId) {
try {
const result = await http.get(accountId, {
uri: DOMAINS_API_PATH,
- json: true,
+ json: true
});
- return result.results;
+ return result.results
} catch (err) {
throw err;
}
@@ -17,4 +17,4 @@ async function fetchDomains(accountId) {
module.exports = {
fetchDomains,
-};
+}
diff --git a/api/preview.js b/api/preview.js
index f73f279..77957e1 100644
--- a/api/preview.js
+++ b/api/preview.js
@@ -1,4 +1,4 @@
-const { get } = require('../http');
+const { get } = require('../http')
const http = require('http');
const https = require('https');
const { getAccountId } = require('../lib/config');
@@ -10,7 +10,7 @@ async function fetchPreviewInfo(accountId, contentId) {
const content = await get(accountId, {
uri: `${CONTENT_API_PATH}/${contentId}`,
query: { portalId: accountId },
- json: true,
+ json: true
});
return {
@@ -49,9 +49,9 @@ const requestContentPreview = async (url, portalId) => {
});
return response;
-};
+}
-const requestPage = (url, redirects = 0, originalUrl = undefined) => {
+const requestPage = (url, redirects = 0, originalUrl=undefined) => {
if (redirects > 5) {
throw new Error(
`Hit too many redirects to HEAD requests for ${originalUrl}`
@@ -96,9 +96,12 @@ const requestPage = (url, redirects = 0, originalUrl = undefined) => {
})
.end();
});
-};
+}
-const fetchContentMetadata = async (url, portalId) => {
+const fetchContentMetadata = async (
+ url,
+ portalId
+) => {
let res;
if (url.includes('hs_preview')) {
res = await requestContentPreview(url, portalId);
@@ -153,9 +156,9 @@ const fetchContentMetadata = async (url, portalId) => {
return { portalId, contentId, hublet };
}
-};
+}
module.exports = {
fetchPreviewInfo,
- fetchContentMetadata,
-};
+ fetchContentMetadata
+}
diff --git a/lib/preview.js b/lib/preview.js
index b46d781..838fcdb 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -52,7 +52,7 @@ async function uploadFile(accountId, sessionToken, src, dest) {
);
}
}
-}
+};
async function deleteRemoteFile(accountId, sessionToken, remoteFilePath) {
logger.debug(`Attempting to delete file "${remoteFilePath}"`);
@@ -64,7 +64,7 @@ async function deleteRemoteFile(accountId, sessionToken, remoteFilePath) {
markRemoteFsDirty();
} catch (error) {
logger.error(`Deleting file ${remoteFilePath} failed`);
- logger.debug(`Retrying deletion of file ${remoteFilePath}`);
+ logger.debug(`Retrying deletion of file ${remoteFilePath}`)
try {
await deleteFile(acccountId, remoteFilePath, fileMapperArgs);
markRemoteFsDirty();
@@ -87,31 +87,31 @@ const getDesignManagerPath = (src, dest, file) => {
return convertToUnixPath(path.join(dest, relativePath));
};
-const buildFileMapperArgs = sessionToken => {
+const buildFileMapperArgs = (sessionToken) => {
return getFileMapperQueryValues({
mode: 'publish',
});
-};
+}
const buildDeleteFileFromPreviewBufferCallback = (sessionInfo, type) => {
const { accountId, src, dest, notify } = sessionInfo;
- return filePath => {
+ return (filePath) => {
if (shouldIgnoreFile(filePath)) {
logger.debug(`Skipping ${filePath} due to an ignore rule`);
return;
}
const remotePath = getDesignManagerPath(src, dest, filePath);
- const deletePromise = deleteRemoteFile(accountId, filePath, remotePath);
+ const deletePromise = deleteRemoteFile(accountId, filePath, remotePath)
triggerNotify(notify, 'Removed', filePath, deletePromise);
- };
-};
+ }
+}
const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
const { portalId, src, dest, sessionToken, notify } = sessionInfo;
- return async filePath => {
+ return async (filePath) => {
if (!isAllowedExtension(filePath)) {
logger.debug(`Skipping ${filePath} due to unsupported extension`);
return;
@@ -121,7 +121,7 @@ const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
return;
}
const destPath = getDesignManagerPath(src, dest, filePath);
- const fileMapperArgs = buildFileMapperArgs(sessionToken);
+ const fileMapperArgs = buildFileMapperArgs(sessionToken)
const uploadPromise = uploadFile(
portalId,
sessionToken,
@@ -129,16 +129,16 @@ const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
destPath
);
triggerNotify(notify, notifyMessage, filePath, uploadPromise);
- };
-};
+ }
+}
const initialPreviewBufferUpload = async (sessionInfo, filePaths) => {
- const { portalId, sessionToken, src, dest } = sessionInfo;
+ const { portalId, sessionToken, src, dest } = sessionInfo
const fileMapperArgs = buildFileMapperArgs(sessionToken);
return uploadFolder(portalId, src, dest, fileMapperArgs, {}, filePaths);
-};
+}
-const startPreviewWatcher = async sessionInfo => {
+const startPreviewWatcher = async (sessionInfo) => {
const { src } = sessionInfo;
let watcherIsReady = false;
@@ -147,22 +147,10 @@ const startPreviewWatcher = async sessionInfo => {
ignored: file => shouldIgnoreFile(file),
});
- const addFileCallback = buildUploadFileToPreviewBufferCallback(
- sessionInfo,
- 'Added'
- );
- const changeFileCallback = buildUploadFileToPreviewBufferCallback(
- sessionInfo,
- 'Change'
- );
- const deleteFileCallback = buildDeleteFileFromPreviewBufferCallback(
- sessionInfo,
- 'file'
- );
- const deleteFolderCallback = buildDeleteFileFromPreviewBufferCallback(
- sessionInfo,
- 'folder'
- );
+ const addFileCallback = buildUploadFileToPreviewBufferCallback(sessionInfo, 'Added');
+ const changeFileCallback = buildUploadFileToPreviewBufferCallback(sessionInfo, 'Change');
+ const deleteFileCallback = buildDeleteFileFromPreviewBufferCallback(sessionInfo, 'file');
+ const deleteFolderCallback = buildDeleteFileFromPreviewBufferCallback(sessionInfo, 'folder');
watcher.on('ready', () => {
console.log('Local file watching service has started!');
@@ -183,7 +171,7 @@ const startPreviewWatcher = async sessionInfo => {
// do nothing... surely there's a better way to do this...
}
return watcher;
-};
+}
const createLocalHttpServer = async sessionInfo => {
const expressServer = express();
@@ -191,7 +179,7 @@ const createLocalHttpServer = async sessionInfo => {
expressServer.use('/', await createPreviewServerRoutes(sessionInfo));
return expressServer;
-};
+}
const preview = async (
accountId,
@@ -201,7 +189,7 @@ const preview = async (
) => {
const accountConfig = getAccountConfig(accountId);
const domains = await getPortalDomains(accountId);
- const sessionToken = 'test'; // uuidv4();
+ const sessionToken = uuidv4();
const PORT = port || 3000;
const protocol = noSsl ? 'http' : 'https';
@@ -219,7 +207,7 @@ const preview = async (
domains,
PORT,
protocol,
- };
+ }
if (notify) {
ignoreFile(notify);
diff --git a/package.json b/package.json
index 457dfee..536e280 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,6 @@
"js-yaml": "^4.1.0",
"mkcert-cli": "^1.5.0",
"moment": "^2.24.0",
- "net": "^1.0.2",
"node-fetch-commonjs": "^3.3.2",
"p-queue": "^6.0.2",
"prettier": "^1.19.1",
From 3fddbe0fa75ce6dd256eead0c9da085f46c09e7b Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Mon, 6 Nov 2023 17:52:14 -0500
Subject: [PATCH 31/56] More remove
---
lib/preview.js | 19 +++++--------------
lib/preview/routes/meta.js | 5 -----
2 files changed, 5 insertions(+), 19 deletions(-)
diff --git a/lib/preview.js b/lib/preview.js
index 838fcdb..8f184dd 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -87,12 +87,6 @@ const getDesignManagerPath = (src, dest, file) => {
return convertToUnixPath(path.join(dest, relativePath));
};
-const buildFileMapperArgs = (sessionToken) => {
- return getFileMapperQueryValues({
- mode: 'publish',
- });
-}
-
const buildDeleteFileFromPreviewBufferCallback = (sessionInfo, type) => {
const { accountId, src, dest, notify } = sessionInfo;
@@ -121,13 +115,7 @@ const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
return;
}
const destPath = getDesignManagerPath(src, dest, filePath);
- const fileMapperArgs = buildFileMapperArgs(sessionToken)
- const uploadPromise = uploadFile(
- portalId,
- sessionToken,
- filePath,
- destPath
- );
+ const uploadPromise = uploadFile(portalId, sessionToken, filePath, destPath);
triggerNotify(notify, notifyMessage, filePath, uploadPromise);
}
}
@@ -135,7 +123,9 @@ const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
const initialPreviewBufferUpload = async (sessionInfo, filePaths) => {
const { portalId, sessionToken, src, dest } = sessionInfo
const fileMapperArgs = buildFileMapperArgs(sessionToken);
- return uploadFolder(portalId, src, dest, fileMapperArgs, {}, filePaths);
+ return uploadFolder(portalId, src, dest, getFileMapperQueryValues({
+ mode: 'publish',
+ }), {}, filePaths);
}
const startPreviewWatcher = async (sessionInfo) => {
@@ -147,6 +137,7 @@ const startPreviewWatcher = async (sessionInfo) => {
ignored: file => shouldIgnoreFile(file),
});
+
const addFileCallback = buildUploadFileToPreviewBufferCallback(sessionInfo, 'Added');
const changeFileCallback = buildUploadFileToPreviewBufferCallback(sessionInfo, 'Change');
const deleteFileCallback = buildDeleteFileFromPreviewBufferCallback(sessionInfo, 'file');
diff --git a/lib/preview/routes/meta.js b/lib/preview/routes/meta.js
index 7041d21..75a026b 100644
--- a/lib/preview/routes/meta.js
+++ b/lib/preview/routes/meta.js
@@ -13,12 +13,7 @@ const markRemoteFsDirty = () => {
hsServerState["REMOTE_FS_IS_DIRTY"] = true;
}
-const remoteFsIsDirty = () => {
- return hsServerState["REMOTE_FS_IS_DIRTY"];
-}
-
module.exports = {
buildMetaRouteHandler,
markRemoteFsDirty,
- remoteFsIsDirty
}
From bc206ad1a89cabd0772823573d26081ec746571c Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Tue, 7 Nov 2023 10:09:06 -0500
Subject: [PATCH 32/56] Fix filemapperarg mess I made
---
lib/preview.js | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/lib/preview.js b/lib/preview.js
index 8f184dd..a34e3b4 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -25,9 +25,12 @@ const {
createHttpsRedirectingServer,
} = require('./preview/httpsRedirectingServer');
+const fileMapperArgs = getFileMapperQueryValues({
+ mode: 'publish',
+});
+
async function uploadFile(accountId, sessionToken, src, dest) {
logger.debug(`Attempting to upload file "${src}" to "${dest}"`);
- const fileMapperArgs = buildFileMapperArgs(sessionToken);
try {
await upload(accountId, src, dest, fileMapperArgs);
@@ -56,7 +59,6 @@ async function uploadFile(accountId, sessionToken, src, dest) {
async function deleteRemoteFile(accountId, sessionToken, remoteFilePath) {
logger.debug(`Attempting to delete file "${remoteFilePath}"`);
- const fileMapperArgs = buildFileMapperArgs(sessionToken);
try {
await deleteFile(accountId, remoteFilePath, fileMapperArgs);
@@ -122,10 +124,8 @@ const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
const initialPreviewBufferUpload = async (sessionInfo, filePaths) => {
const { portalId, sessionToken, src, dest } = sessionInfo
- const fileMapperArgs = buildFileMapperArgs(sessionToken);
- return uploadFolder(portalId, src, dest, getFileMapperQueryValues({
- mode: 'publish',
- }), {}, filePaths);
+
+ return uploadFolder(portalId, src, dest, fileMapperArgs, {}, filePaths);
}
const startPreviewWatcher = async (sessionInfo) => {
From 7af2be9e5180e73e52c2ef2aef6465dba2a2ffd1 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Tue, 7 Nov 2023 10:15:38 -0500
Subject: [PATCH 33/56] Remove unused args
---
lib/preview.js | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/lib/preview.js b/lib/preview.js
index a34e3b4..eacd234 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -29,7 +29,7 @@ const fileMapperArgs = getFileMapperQueryValues({
mode: 'publish',
});
-async function uploadFile(accountId, sessionToken, src, dest) {
+async function uploadFile(accountId, src, dest) {
logger.debug(`Attempting to upload file "${src}" to "${dest}"`);
try {
@@ -57,7 +57,7 @@ async function uploadFile(accountId, sessionToken, src, dest) {
}
};
-async function deleteRemoteFile(accountId, sessionToken, remoteFilePath) {
+async function deleteRemoteFile(accountId, remoteFilePath) {
logger.debug(`Attempting to delete file "${remoteFilePath}"`);
try {
@@ -99,13 +99,13 @@ const buildDeleteFileFromPreviewBufferCallback = (sessionInfo, type) => {
}
const remotePath = getDesignManagerPath(src, dest, filePath);
- const deletePromise = deleteRemoteFile(accountId, filePath, remotePath)
+ const deletePromise = deleteRemoteFile(accountId, remotePath)
triggerNotify(notify, 'Removed', filePath, deletePromise);
}
}
const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
- const { portalId, src, dest, sessionToken, notify } = sessionInfo;
+ const { portalId, src, dest, notify } = sessionInfo;
return async (filePath) => {
if (!isAllowedExtension(filePath)) {
@@ -117,13 +117,13 @@ const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
return;
}
const destPath = getDesignManagerPath(src, dest, filePath);
- const uploadPromise = uploadFile(portalId, sessionToken, filePath, destPath);
+ const uploadPromise = uploadFile(portalId, filePath, destPath);
triggerNotify(notify, notifyMessage, filePath, uploadPromise);
}
}
const initialPreviewBufferUpload = async (sessionInfo, filePaths) => {
- const { portalId, sessionToken, src, dest } = sessionInfo
+ const { portalId, src, dest } = sessionInfo
return uploadFolder(portalId, src, dest, fileMapperArgs, {}, filePaths);
}
From d0d181b8327a33ff51ad1f82c83e463be2efdf5b Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Tue, 7 Nov 2023 10:15:58 -0500
Subject: [PATCH 34/56] Prettier
---
api/designManager.js | 8 +++----
api/domains.js | 8 +++----
api/preview.js | 21 ++++++++----------
lib/preview.js | 51 +++++++++++++++++++++++++++-----------------
4 files changed, 48 insertions(+), 40 deletions(-)
diff --git a/api/designManager.js b/api/designManager.js
index f1ccc5d..92904a2 100644
--- a/api/designManager.js
+++ b/api/designManager.js
@@ -45,8 +45,8 @@ async function fetchRawAssetByPath(accountId, path) {
async function fetchModulesByPath(accountId, path) {
return http.get(accountId, {
- uri: `${DESIGN_MANAGER_API_PATH}/modules/by-path/${path}?portalId=${accountId}`
- })
+ uri: `${DESIGN_MANAGER_API_PATH}/modules/by-path/${path}?portalId=${accountId}`,
+ });
}
async function fetchModulesPathStartsWith(accountId, path) {
@@ -57,8 +57,8 @@ async function fetchModulesPathStartsWith(accountId, path) {
async function fetchTemplatesByPath(accountId, path) {
return http.get(accountId, {
- uri: `${DESIGN_MANAGER_API_PATH}/templates/by-path/${path}?portalId=${accountId}`
- })
+ uri: `${DESIGN_MANAGER_API_PATH}/templates/by-path/${path}?portalId=${accountId}`,
+ });
}
async function fetchTemplatesPathStartsWith(accountId, path) {
diff --git a/api/domains.js b/api/domains.js
index 47571bd..fd1f97a 100644
--- a/api/domains.js
+++ b/api/domains.js
@@ -1,4 +1,4 @@
-const http = require('../http')
+const http = require('../http');
const DOMAINS_API_PATH = `/cms/v3/domains`;
@@ -6,10 +6,10 @@ async function fetchDomains(accountId) {
try {
const result = await http.get(accountId, {
uri: DOMAINS_API_PATH,
- json: true
+ json: true,
});
- return result.results
+ return result.results;
} catch (err) {
throw err;
}
@@ -17,4 +17,4 @@ async function fetchDomains(accountId) {
module.exports = {
fetchDomains,
-}
+};
diff --git a/api/preview.js b/api/preview.js
index 77957e1..f73f279 100644
--- a/api/preview.js
+++ b/api/preview.js
@@ -1,4 +1,4 @@
-const { get } = require('../http')
+const { get } = require('../http');
const http = require('http');
const https = require('https');
const { getAccountId } = require('../lib/config');
@@ -10,7 +10,7 @@ async function fetchPreviewInfo(accountId, contentId) {
const content = await get(accountId, {
uri: `${CONTENT_API_PATH}/${contentId}`,
query: { portalId: accountId },
- json: true
+ json: true,
});
return {
@@ -49,9 +49,9 @@ const requestContentPreview = async (url, portalId) => {
});
return response;
-}
+};
-const requestPage = (url, redirects = 0, originalUrl=undefined) => {
+const requestPage = (url, redirects = 0, originalUrl = undefined) => {
if (redirects > 5) {
throw new Error(
`Hit too many redirects to HEAD requests for ${originalUrl}`
@@ -96,12 +96,9 @@ const requestPage = (url, redirects = 0, originalUrl=undefined) => {
})
.end();
});
-}
+};
-const fetchContentMetadata = async (
- url,
- portalId
-) => {
+const fetchContentMetadata = async (url, portalId) => {
let res;
if (url.includes('hs_preview')) {
res = await requestContentPreview(url, portalId);
@@ -156,9 +153,9 @@ const fetchContentMetadata = async (
return { portalId, contentId, hublet };
}
-}
+};
module.exports = {
fetchPreviewInfo,
- fetchContentMetadata
-}
+ fetchContentMetadata,
+};
diff --git a/lib/preview.js b/lib/preview.js
index eacd234..aa8f886 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -55,7 +55,7 @@ async function uploadFile(accountId, src, dest) {
);
}
}
-};
+}
async function deleteRemoteFile(accountId, remoteFilePath) {
logger.debug(`Attempting to delete file "${remoteFilePath}"`);
@@ -66,7 +66,7 @@ async function deleteRemoteFile(accountId, remoteFilePath) {
markRemoteFsDirty();
} catch (error) {
logger.error(`Deleting file ${remoteFilePath} failed`);
- logger.debug(`Retrying deletion of file ${remoteFilePath}`)
+ logger.debug(`Retrying deletion of file ${remoteFilePath}`);
try {
await deleteFile(acccountId, remoteFilePath, fileMapperArgs);
markRemoteFsDirty();
@@ -92,22 +92,22 @@ const getDesignManagerPath = (src, dest, file) => {
const buildDeleteFileFromPreviewBufferCallback = (sessionInfo, type) => {
const { accountId, src, dest, notify } = sessionInfo;
- return (filePath) => {
+ return filePath => {
if (shouldIgnoreFile(filePath)) {
logger.debug(`Skipping ${filePath} due to an ignore rule`);
return;
}
const remotePath = getDesignManagerPath(src, dest, filePath);
- const deletePromise = deleteRemoteFile(accountId, remotePath)
+ const deletePromise = deleteRemoteFile(accountId, remotePath);
triggerNotify(notify, 'Removed', filePath, deletePromise);
- }
-}
+ };
+};
const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
const { portalId, src, dest, notify } = sessionInfo;
- return async (filePath) => {
+ return async filePath => {
if (!isAllowedExtension(filePath)) {
logger.debug(`Skipping ${filePath} due to unsupported extension`);
return;
@@ -119,16 +119,16 @@ const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => {
const destPath = getDesignManagerPath(src, dest, filePath);
const uploadPromise = uploadFile(portalId, filePath, destPath);
triggerNotify(notify, notifyMessage, filePath, uploadPromise);
- }
-}
+ };
+};
const initialPreviewBufferUpload = async (sessionInfo, filePaths) => {
- const { portalId, src, dest } = sessionInfo
+ const { portalId, src, dest } = sessionInfo;
return uploadFolder(portalId, src, dest, fileMapperArgs, {}, filePaths);
-}
+};
-const startPreviewWatcher = async (sessionInfo) => {
+const startPreviewWatcher = async sessionInfo => {
const { src } = sessionInfo;
let watcherIsReady = false;
@@ -137,11 +137,22 @@ const startPreviewWatcher = async (sessionInfo) => {
ignored: file => shouldIgnoreFile(file),
});
-
- const addFileCallback = buildUploadFileToPreviewBufferCallback(sessionInfo, 'Added');
- const changeFileCallback = buildUploadFileToPreviewBufferCallback(sessionInfo, 'Change');
- const deleteFileCallback = buildDeleteFileFromPreviewBufferCallback(sessionInfo, 'file');
- const deleteFolderCallback = buildDeleteFileFromPreviewBufferCallback(sessionInfo, 'folder');
+ const addFileCallback = buildUploadFileToPreviewBufferCallback(
+ sessionInfo,
+ 'Added'
+ );
+ const changeFileCallback = buildUploadFileToPreviewBufferCallback(
+ sessionInfo,
+ 'Change'
+ );
+ const deleteFileCallback = buildDeleteFileFromPreviewBufferCallback(
+ sessionInfo,
+ 'file'
+ );
+ const deleteFolderCallback = buildDeleteFileFromPreviewBufferCallback(
+ sessionInfo,
+ 'folder'
+ );
watcher.on('ready', () => {
console.log('Local file watching service has started!');
@@ -162,7 +173,7 @@ const startPreviewWatcher = async (sessionInfo) => {
// do nothing... surely there's a better way to do this...
}
return watcher;
-}
+};
const createLocalHttpServer = async sessionInfo => {
const expressServer = express();
@@ -170,7 +181,7 @@ const createLocalHttpServer = async sessionInfo => {
expressServer.use('/', await createPreviewServerRoutes(sessionInfo));
return expressServer;
-}
+};
const preview = async (
accountId,
@@ -198,7 +209,7 @@ const preview = async (
domains,
PORT,
protocol,
- }
+ };
if (notify) {
ignoreFile(notify);
From 5f91643d3b15b95122de6b2483f73ff413e12f23 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Tue, 7 Nov 2023 17:13:55 -0500
Subject: [PATCH 35/56] Add check for gate
---
lib/preview.js | 13 ++++++++++---
lib/preview/previewUtils.js | 19 ++++++++++++++++++-
2 files changed, 28 insertions(+), 4 deletions(-)
diff --git a/lib/preview.js b/lib/preview.js
index aa8f886..bb6f539 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -18,7 +18,10 @@ const { convertToUnixPath, isAllowedExtension } = require('../path');
const { triggerNotify } = require('./notify');
const { getAccountConfig } = require('./config');
const { createPreviewServerRoutes } = require('./preview/createRoutes');
-const { getPortalDomains } = require('./preview/previewUtils');
+const {
+ getPortalDomains,
+ isUngatedForPreview
+} = require('./preview/previewUtils');
const { markRemoteFsDirty } = require('./preview/routes/meta');
const { startShadowDevServer } = require('./preview/shadowDevServer');
const {
@@ -68,7 +71,7 @@ async function deleteRemoteFile(accountId, remoteFilePath) {
logger.error(`Deleting file ${remoteFilePath} failed`);
logger.debug(`Retrying deletion of file ${remoteFilePath}`);
try {
- await deleteFile(acccountId, remoteFilePath, fileMapperArgs);
+ await deleteFile(accountId, remoteFilePath, fileMapperArgs);
markRemoteFsDirty();
} catch (error) {
logger.error(`Deleting file ${remoteFilePath} failed`);
@@ -210,7 +213,11 @@ const preview = async (
PORT,
protocol,
};
-
+ const ungated = await isUngatedForPreview(sessionInfo);
+ if (!ungated) {
+ console.log(`Portal ${accountId} is missing a required gate for this feature.`);
+ //process.exit(); Uncomment when this check actually works
+ }
if (notify) {
ignoreFile(notify);
}
diff --git a/lib/preview/previewUtils.js b/lib/preview/previewUtils.js
index c6a7eb5..6d54471 100644
--- a/lib/preview/previewUtils.js
+++ b/lib/preview/previewUtils.js
@@ -2,8 +2,10 @@ const { fetchDomains } = require('../../api/domains');
const { getAccountId, isTrackingAllowed, getAccountConfig } = require('../config');
const { platform, release } = require('os');
const { trackUsage } = require('../../api/fileMapper');
+const unAuth = require('../../api/localDevAuth/unauthenticated');
const VALID_PROXY_DOMAIN_SUFFIXES = ['localhost', 'hslocal.net'];
+const HS_PREVIEW_GATE = "Cms:LocalHublPreviews";
const getPortalDomains = async (portalId) => {
try {
@@ -152,6 +154,20 @@ const getAuthType = (accountId) => {
return authType;
};
+const isUngatedForPreview = async (sessionInfo) => {
+ const { portalId, env, personalAccessKey } = sessionInfo;
+
+ const { enabledFeatures = {} } = await unAuth.fetchAccessToken(
+ personalAccessKey,
+ env,
+ portalId
+ );
+
+ return (Object.keys(enabledFeatures).includes(HS_PREVIEW_GATE)
+ && enabledFeatures[HS_PREVIEW_GATE] === true)
+}
+
+
module.exports = {
isInternalCMSRoute,
VALID_PROXY_DOMAIN_SUFFIXES,
@@ -162,5 +178,6 @@ module.exports = {
silenceConsoleWhile,
memoize,
hidePreviewInDest,
- trackPreviewEvent
+ trackPreviewEvent,
+ isUngatedForPreview,
}
From cd01cda5620e95e606c84d9ada567cac0f421db4 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Tue, 7 Nov 2023 17:26:43 -0500
Subject: [PATCH 36/56] External gate name casing is different
---
lib/preview/previewUtils.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/preview/previewUtils.js b/lib/preview/previewUtils.js
index 6d54471..50b78d6 100644
--- a/lib/preview/previewUtils.js
+++ b/lib/preview/previewUtils.js
@@ -5,7 +5,7 @@ const { trackUsage } = require('../../api/fileMapper');
const unAuth = require('../../api/localDevAuth/unauthenticated');
const VALID_PROXY_DOMAIN_SUFFIXES = ['localhost', 'hslocal.net'];
-const HS_PREVIEW_GATE = "Cms:LocalHublPreviews";
+const HS_PREVIEW_GATE = "cms:localHublPreviews";
const getPortalDomains = async (portalId) => {
try {
From ce57effcb00449dd77fc948045c5278ac211dec6 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Wed, 8 Nov 2023 13:26:43 -0500
Subject: [PATCH 37/56] Uncomment now that BE change merged
---
lib/preview.js | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/lib/preview.js b/lib/preview.js
index bb6f539..acdfa0f 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -20,7 +20,7 @@ const { getAccountConfig } = require('./config');
const { createPreviewServerRoutes } = require('./preview/createRoutes');
const {
getPortalDomains,
- isUngatedForPreview
+ isUngatedForPreview,
} = require('./preview/previewUtils');
const { markRemoteFsDirty } = require('./preview/routes/meta');
const { startShadowDevServer } = require('./preview/shadowDevServer');
@@ -215,8 +215,10 @@ const preview = async (
};
const ungated = await isUngatedForPreview(sessionInfo);
if (!ungated) {
- console.log(`Portal ${accountId} is missing a required gate for this feature.`);
- //process.exit(); Uncomment when this check actually works
+ console.log(
+ `Portal ${accountId} is missing a required gate for this feature.`
+ );
+ process.exit();
}
if (notify) {
ignoreFile(notify);
From 11210ad509ea3425c65f65362ba079d02d202060 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Wed, 15 Nov 2023 17:59:01 -0500
Subject: [PATCH 38/56] remove random token gen while testing
---
lib/preview.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/preview.js b/lib/preview.js
index aa8f886..aa68760 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -197,7 +197,7 @@ const preview = async (
const sessionInfo = {
src,
- dest: `@preview/${sessionToken}/${dest}`,
+ dest: `@preview/test/${dest}`,
portalName: accountConfig.name,
portalId: accountId,
env: accountConfig.env,
From f5c3b1366c9107f315be468db8c5815a73739f1c Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Wed, 15 Nov 2023 18:12:32 -0500
Subject: [PATCH 39/56] Static uuid
---
lib/preview.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/preview.js b/lib/preview.js
index aa68760..ea0c606 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -197,7 +197,7 @@ const preview = async (
const sessionInfo = {
src,
- dest: `@preview/test/${dest}`,
+ dest: `@preview/96cd331a-189d-41f2-8a4c-a12485402eff/${dest}`,
portalName: accountConfig.name,
portalId: accountId,
env: accountConfig.env,
From 1515aab917ad4e18adfb0678092dc305b11ef74b Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Thu, 16 Nov 2023 12:19:54 -0500
Subject: [PATCH 40/56] Fix bad sessionToken staticing, fix .hubl.html
---
lib/preview.js | 4 ++--
lib/preview/routes/index.js | 2 +-
lib/preview/routes/template.js | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/lib/preview.js b/lib/preview.js
index ea0c606..8e42e29 100644
--- a/lib/preview.js
+++ b/lib/preview.js
@@ -191,13 +191,13 @@ const preview = async (
) => {
const accountConfig = getAccountConfig(accountId);
const domains = await getPortalDomains(accountId);
- const sessionToken = uuidv4();
+ const sessionToken = '96cd331a-189d-41f2-8a4c-a12485402eff';
const PORT = port || 3000;
const protocol = noSsl ? 'http' : 'https';
const sessionInfo = {
src,
- dest: `@preview/96cd331a-189d-41f2-8a4c-a12485402eff/${dest}`,
+ dest: `@preview/${sessionToken}/${dest}`,
portalName: accountConfig.name,
portalId: accountId,
env: accountConfig.env,
diff --git a/lib/preview/routes/index.js b/lib/preview/routes/index.js
index 1885705..f5fab11 100644
--- a/lib/preview/routes/index.js
+++ b/lib/preview/routes/index.js
@@ -83,7 +83,7 @@ const getTemplatesForDisplayToUser = async (sessionInfo) => {
const res = await fetchTemplatesPathStartsWith(portalId, dest);
const templatePaths = res
.objects
- .filter(templateObj => templateObj.filename.split('.')[1] === 'html')
+ .filter(templateObj => templateObj.filename.endsWith('html'))
.map(templateObj => templateObj.path);
const filesGroupedByFolder = groupByFolder(templatePaths)
const htmlToRender = renderFilesGroupedByFolder(filesGroupedByFolder, 'template')
diff --git a/lib/preview/routes/template.js b/lib/preview/routes/template.js
index 9910d2b..271160b 100644
--- a/lib/preview/routes/template.js
+++ b/lib/preview/routes/template.js
@@ -20,7 +20,7 @@ const buildTemplateRouteHandler = (sessionInfo) => {
try {
templateInfo = await cachedFetchTemplatesByPath(portalId, calculatedPath);
} catch (err) {
- console.log(`Failed to fetch template info for ${calculatedPath}`);
+ console.log(`Failed to fetch template info for ${calculatedPath}: ${err}`);
}
if (!templateInfo || !('previewKey' in templateInfo)) {
res.status(502).set({ 'Content-Type': 'text/html' }).end(buildErrorIndex());
From c3c586577c795aaceb997bae8d5916f86244c232 Mon Sep 17 00:00:00 2001
From: Jessica Sines
Date: Wed, 3 Jan 2024 23:28:29 -0500
Subject: [PATCH 41/56] Swap index routes to new endpoints
---
api/designManager.js | 12 ++++++------
lib/preview/routes/index.js | 16 ++++++++--------
lib/preview/routes/proxyPageRouteHandler.js | 2 +-
3 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/api/designManager.js b/api/designManager.js
index 92904a2..68c2597 100644
--- a/api/designManager.js
+++ b/api/designManager.js
@@ -49,9 +49,9 @@ async function fetchModulesByPath(accountId, path) {
});
}
-async function fetchModulesPathStartsWith(accountId, path) {
+async function fetchPreviewModules(accountId, token) {
return http.get(accountId, {
- uri: `${DESIGN_MANAGER_API_PATH}/modules?portalId=${accountId}&path__startswith=${path}`,
+ uri: `${DESIGN_MANAGER_API_PATH}/modules/local-preview?portalId=${accountId}&previewToken=${token}`,
});
}
@@ -61,9 +61,9 @@ async function fetchTemplatesByPath(accountId, path) {
});
}
-async function fetchTemplatesPathStartsWith(accountId, path) {
+async function fetchPreviewTemplates(accountId, token) {
return http.get(accountId, {
- uri: `${DESIGN_MANAGER_API_PATH}/templates?portalId=${accountId}&path__startswith=${path}`,
+ uri: `${DESIGN_MANAGER_API_PATH}/templates/local-preview?portalId=${accountId}&previewToken=${token}`,
});
}
@@ -73,7 +73,7 @@ module.exports = {
fetchRawAssetByPath,
fetchThemes,
fetchModulesByPath,
- fetchModulesPathStartsWith,
+ fetchPreviewModules,
fetchTemplatesByPath,
- fetchTemplatesPathStartsWith,
+ fetchPreviewTemplates,
};
diff --git a/lib/preview/routes/index.js b/lib/preview/routes/index.js
index f5fab11..0e9f67a 100644
--- a/lib/preview/routes/index.js
+++ b/lib/preview/routes/index.js
@@ -3,8 +3,8 @@ const {
trackPreviewEvent,
} = require('../previewUtils');
const {
- fetchTemplatesPathStartsWith,
- fetchModulesPathStartsWith
+ fetchPreviewTemplates,
+ fetchPreviewModules
} = require('../../../api/designManager');
const { parse: pathParse } = require('path');
@@ -35,11 +35,11 @@ const buildIndexHtml = async (sessionInfo) => {
Modules
- ${ modulesHtml ? modulesHtml : `
No modules found in ${fakeDest}
` }
+ ${ modulesHtml ? modulesHtml : `
No modules found in ${dest}
` }
Templates
- ${ templatesHtml ? templatesHtml : `
No templates found in ${fakeDest}
` }
+ ${ templatesHtml ? templatesHtml : `
No templates found in ${dest}
` }