-
Notifications
You must be signed in to change notification settings - Fork 3
Add preview command support #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jsines
wants to merge
70
commits into
main
Choose a base branch
from
js/hs-preview
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
70 commits
Select commit
Hold shift + click to select a range
fe44395
Add preview command support
jsines de74c6a
Saving work
jsines d7136a4
Move work in from cloudlfare worker, start implementation of proxy, f…
jsines f3fb919
Proxying works mostly
jsines 4fdace4
Remove logs
jsines 0aae19b
module and template route handlers
jsines dd8970e
Flesh out module and template previews, add a few error responses
jsines fbd4701
Remove filemapper preview changes for later
jsines b1fa76f
Clean up preview a little bit
jsines 2626400
Fixes logic around watch stuff
jsines 4c404b4
Make initial upload off by default
jsines dd2108d
Add express
jsines 2eae394
Add express dep
jsines 5ad2ed7
Merge branch 'js/hs-preview' into js/hs-preview-auto-refresh
jsines d06710b
Add auto refreshing
jsines 4e20f17
Add auto refreshing
jsines a3bd4d9
Remove logs
jsines c7ec29b
Whitespace
jsines e05c847
Whitespace, remove logs
jsines 2d6676c
use nodefetchcommonjs, add routes
jsines 872e25b
proxyResourceRedirect.js
jsines caaf525
Error log
jsines 81e65a2
Merge pull request #29 from HubSpot/js/hs-preview-auto-refresh
jsines 4818e70
@preview folder support, module/template proxying/auto-refreshing
jsines 8ce1d7e
Merge pull request #30 from HubSpot/js/hs-preview-resource-fetching
jsines d2b7a87
Merge pull request #31 from HubSpot/js/hs-preview-asset-proxy
jsines f0651dd
Add hslocal support, shadow dev server, https support
jsines 2e0175c
Fix index linking, change hasJSBuildingBlocks to false, remove consol…
jsines 02a651a
Remove net dep, add req
jsines b43f515
Committing before I leave...
jsines 4a6b045
Cleanup some semicolons, preview tracking
jsines 5cb6ae1
Add getauthtype
jsines 57bd6c3
Add all changes for now...
jsines 18b4cd5
Undo prettier
jsines 3fddbe0
More remove
jsines bc206ad
Fix filemapperarg mess I made
jsines 7af2be9
Remove unused args
jsines d0d181b
Prettier
jsines 5f91643
Add check for gate
jsines cd01cda
External gate name casing is different
jsines ce57eff
Uncomment now that BE change merged
jsines 8d95d02
Merge pull request #32 from HubSpot/js/hs-preview-hslocal
jsines 5b3d5ce
Merge pull request #35 from HubSpot/js/hs-preview-improvements
jsines 38280c4
Merge pull request #36 from HubSpot/js/hs-preview-prettier
jsines 11210ad
remove random token gen while testing
jsines f5c3b13
Static uuid
jsines 1515aab
Fix bad sessionToken staticing, fix .hubl.html
jsines c3c5865
Swap index routes to new endpoints
jsines 6576623
Merge pull request #37 from HubSpot/js/hs-preview-gate
jsines ba84f0c
Merge pull request #41 from HubSpot/js/swap-index-routes
jsines 94e6337
Move to cos-rend endpoint
jsines 3a44f9c
Axe requestpage
jsines b32b15d
-
jsines 33c1f30
Fix paths
jsines 73166ab
Merge pull request #43 from HubSpot/js/swap-index-routes
jsines da5b43a
Swap render fetch
jsines 404e06a
Proxy page
jsines f77eeba
Merge pull request #46 from HubSpot/js/renderchange
jsines e54ae21
Merge branch 'main' into js/hs-preview
jsines 690484a
Changes from DPG review
jsines 09f13a2
Changes for pre-empathy test
jsines cb5f80a
Fix resource redirect
jsines 44abf0b
Update lib/preview.js
jsines 7c07088
Fixes
jsines 832ccae
Merge pull request #48 from HubSpot/js/hs-preview-preempathytest
jsines 876be35
Move authtype stuff out of config
jsines 0a64658
Merge main
jsines 8662e8a
Missed a var on this callback
jsines c9e3704
Revert request package version bump
jsines dcd2068
Remove caching for the moment
jsines File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| const http = require('../http'); | ||
|
|
||
| const DOMAINS_API_PATH = `/cms/v3/domains`; | ||
|
|
||
| async function fetchDomains(accountId) { | ||
| const result = await http.get(accountId, { | ||
| uri: DOMAINS_API_PATH, | ||
| json: true, | ||
| }); | ||
|
|
||
| return result.results; | ||
| } | ||
|
|
||
| module.exports = { | ||
| fetchDomains, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| const { request } = require('../http'); | ||
|
|
||
| async function fetchPreviewRender(url, sessionInfo) { | ||
| const { sessionToken } = sessionInfo; | ||
|
|
||
| const urlObject = new URL(url); | ||
|
|
||
| urlObject.searchParams.append('localPreviewToken', sessionToken); | ||
| urlObject.searchParams.append('hsCacheBuster', Date.now()); | ||
|
|
||
| return request(urlObject.href); | ||
| } | ||
|
|
||
| module.exports = { | ||
| fetchPreviewRender, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,257 @@ | ||
| 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, | ||
| logApiErrorInstance, | ||
| logApiUploadErrorInstance, | ||
| } = 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 } = require('../path'); | ||
| const { triggerNotify } = require('./notify'); | ||
| const { getAccountConfig } = require('./config'); | ||
| const { createPreviewServerRoutes } = require('./preview/createRoutes'); | ||
| const { | ||
| getPortalDomains, | ||
| isUngatedForPreview, | ||
| } = require('./preview/previewUtils'); | ||
| const { markRemoteFsDirty } = require('./preview/routes/meta'); | ||
| const { startSprocketMenuServer } = require('./preview/sprocketMenuServer'); | ||
| const { | ||
| createHttpsRedirectingServer, | ||
| } = require('./preview/httpsRedirectingServer'); | ||
|
|
||
| const fileMapperArgs = getFileMapperQueryValues({ | ||
| mode: 'publish', | ||
| }); | ||
|
|
||
| async function uploadFile(accountId, src, dest) { | ||
| logger.debug(`Attempting to upload file "${src}" to "${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( | ||
| error, | ||
| new ApiErrorContext({ | ||
| accountId, | ||
| request: dest, | ||
| payload: src, | ||
| }) | ||
| ); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| async function deleteRemoteFile(accountId, remoteFilePath) { | ||
| logger.debug(`Attempting to delete file "${remoteFilePath}"`); | ||
|
|
||
| try { | ||
| await deleteFile(accountId, remoteFilePath, fileMapperArgs); | ||
| logger.log(`Deleted file ${remoteFilePath}`); | ||
| markRemoteFsDirty(); | ||
| } catch (error) { | ||
| logger.error(`Deleting file ${remoteFilePath} failed`); | ||
| logger.debug(`Retrying deletion of file ${remoteFilePath}`); | ||
| try { | ||
| await deleteFile(accountId, remoteFilePath, fileMapperArgs); | ||
| markRemoteFsDirty(); | ||
| } 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 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); | ||
| const deletePromise = deleteRemoteFile(accountId, remotePath); | ||
| triggerNotify(notify, 'Removed', filePath, deletePromise); | ||
| }; | ||
| }; | ||
|
|
||
| const buildUploadFileToPreviewBufferCallback = (sessionInfo, notifyMessage) => { | ||
| const { accountId, src, dest, 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(src, dest, filePath); | ||
| const uploadPromise = uploadFile(accountId, filePath, destPath); | ||
| triggerNotify(notify, notifyMessage, filePath, uploadPromise); | ||
| }; | ||
| }; | ||
|
|
||
| const initialPreviewBufferUpload = async (sessionInfo, filePaths, uploadOptions) => { | ||
| const { accountId, src, dest } = sessionInfo; | ||
| const { onFinishCallback, ...rest } = uploadOptions; | ||
|
|
||
| const results = await uploadFolder(accountId, src, dest, fileMapperArgs, rest, filePaths); | ||
| onFinishCallback(results); | ||
| }; | ||
|
|
||
| const startPreviewWatcher = async sessionInfo => { | ||
| const { src } = sessionInfo; | ||
| let watcherIsReady = false; | ||
|
|
||
| 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', () => { | ||
| watcherIsReady = true; | ||
| }); | ||
| watcher.on('add', addFileCallback); | ||
| watcher.on('change', changeFileCallback); | ||
| watcher.on('error', error => | ||
| logger.error(`An error occurred while watching files: ${error}`) | ||
| ); | ||
|
|
||
| watcher.on('unlink', deleteFileCallback); | ||
| watcher.on('unlinkDir', deleteFolderCallback); | ||
|
|
||
| function sleep(ms) { | ||
| return new Promise(resolve => { | ||
| setTimeout(resolve, ms); | ||
| }); | ||
| } | ||
| while (!watcherIsReady) { | ||
| await sleep(1); | ||
| // do nothing... surely there's a better way to do this... | ||
| } | ||
| return watcher; | ||
| }; | ||
|
|
||
| const createLocalHttpServer = async sessionInfo => { | ||
| const expressServer = express(); | ||
| expressServer.use('/', await createPreviewServerRoutes(sessionInfo)); | ||
|
|
||
| return expressServer; | ||
| }; | ||
|
|
||
| const preview = async ( | ||
| accountId, | ||
| src, | ||
| dest, | ||
| { notify, filePaths, skipUpload, noSsl, port, uploadOptions } | ||
| ) => { | ||
| const accountConfig = getAccountConfig(accountId); | ||
| const domains = await getPortalDomains(accountId); | ||
| const sessionToken = uuidv4(); | ||
| const PORT = port || 3000; | ||
| const protocol = noSsl ? 'http' : 'https'; | ||
|
|
||
| const sessionInfo = { | ||
| src, | ||
| dest: `@preview/${sessionToken}/${dest}`, | ||
| fakeDest: dest, | ||
| portalName: accountConfig.name, | ||
| accountId, | ||
| env: accountConfig.env, | ||
| personalAccessKey: accountConfig.personalAccessKey, | ||
| // we find hublet later in the content metadata fetch | ||
| // can we get that ahead of time? hardcoding it for now | ||
| hublet: 'na1', | ||
| sessionToken, | ||
| domains, | ||
| PORT, | ||
| protocol, | ||
| }; | ||
| const ungated = await isUngatedForPreview(sessionInfo); | ||
| if (!ungated) { | ||
| logger.log( | ||
| `Portal ${accountId} is missing a required gate for this feature.` | ||
| ); | ||
| process.exit(); | ||
| } | ||
| if (notify) { | ||
| ignoreFile(notify); | ||
| } | ||
|
|
||
| if (!skipUpload) { | ||
| await initialPreviewBufferUpload(sessionInfo, filePaths, uploadOptions); | ||
| } | ||
| const expressServer = await createLocalHttpServer(sessionInfo); | ||
| const previewWatcher = await startPreviewWatcher(sessionInfo); | ||
|
|
||
| if (!noSsl) { | ||
| const { | ||
| server, | ||
| innerHTTPServer, | ||
| innerHTTPSServer, | ||
| } = await createHttpsRedirectingServer(expressServer, domains); | ||
| server.listen(PORT); | ||
| } else { | ||
| const httpServer = http.createServer(expressServer); | ||
| httpServer.listen(PORT); | ||
| } | ||
| startSprocketMenuServer(sessionInfo); | ||
| logger.log( | ||
| `Local dev server started at ${protocol}://hslocal.net:${PORT} for portal ${accountId}` | ||
| ); | ||
| }; | ||
|
|
||
| module.exports = { | ||
| preview, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| const { Router } = require('express'); | ||
| const { logger } = require('./../../logger'); | ||
|
|
||
| const { buildIndexRouteHandler } = require('./routes/index.js'); | ||
| const { buildModuleRouteHandler } = require('./routes/module.js'); | ||
| const { buildTemplateRouteHandler } = require('./routes/template.js'); | ||
| const { buildMetaRouteHandler } = require('./routes/meta.js'); | ||
|
|
||
| const { buildProxyRouteHandler } = require('./routes/proxyPathPageRouteHandler.js'); | ||
| const { buildProxyPageRouteHandler } = require('./routes/proxyPageRouteHandler.js'); | ||
| const { proxyPathPageResourceRedirect } = require('./routes/proxyPathPageResourceRedirect.js'); | ||
| const { proxyPageResourceRedirect } = require('./routes/proxyPageResourceRedirect.js'); | ||
|
|
||
| const createPreviewServerRoutes = async (sessionInfo) => { | ||
| const previewServerRouter = Router(); | ||
| previewServerRouter.get('/proxy', buildProxyRouteHandler(sessionInfo)); | ||
| previewServerRouter.get('/module/:modulePath(*)', buildModuleRouteHandler(sessionInfo)); | ||
| previewServerRouter.get('/template/:templatePath(*)', buildTemplateRouteHandler(sessionInfo)); | ||
| // fetches server metadata from the client (used by refresh script to check if fs has been changed) | ||
| previewServerRouter.get('/meta', buildMetaRouteHandler(sessionInfo)); | ||
| // handles resources on the proxied page, so a fetch from relative path gets proxied too | ||
| previewServerRouter.get('/*', proxyPathPageResourceRedirect) | ||
| previewServerRouter.get('/*', proxyPageResourceRedirect); | ||
| previewServerRouter.post('/*', proxyPageResourceRedirect); | ||
| previewServerRouter.delete('/*', proxyPageResourceRedirect); | ||
| previewServerRouter.head('/*', proxyPageResourceRedirect); | ||
| previewServerRouter.put('/*', proxyPageResourceRedirect); | ||
| previewServerRouter.options('/*', proxyPageResourceRedirect); | ||
| previewServerRouter.get('/*', buildProxyPageRouteHandler(sessionInfo)); | ||
| // index route | ||
| previewServerRouter.get('/', buildIndexRouteHandler(sessionInfo)); | ||
|
|
||
| return previewServerRouter; | ||
| } | ||
|
|
||
| module.exports = { | ||
| createPreviewServerRoutes, | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.