generated from adobe/aem-boilerplate
-
Notifications
You must be signed in to change notification settings - Fork 191
Centralized DA importer hosted within milo #4255
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
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
cbe0ddf
WP
mokimo fc3da7f
WP
mokimo 9a5b67c
wp
mokimo 28fca2f
Fix a few small issues
mokimo f54520c
Set max length for finishing logs
mokimo 284a749
Remove unused env var
mokimo 6b209ee
Fix linting issues
mokimo 4b400e6
add org
mokimo ca8691c
Add PR feedback
mokimo cf89e84
Add yaml to trigger bacom imports
mokimo fdccc47
Always log the first 500 successfully imported live paths
mokimo 0554e65
Add EOF (thx Rares)
mokimo 614dc9c
Dont error if there are no logs
mokimo c95a49d
Remove double slack notification
mokimo 76298c2
Add Rares feedback
mokimo 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export default [ | ||
// { | ||
// "path": "/africa/fragments/products/cards/headless-cms", | ||
// "route": "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,15 @@ | ||
export const AEM_ORIGIN = 'https://admin.hlx.page'; | ||
|
||
export const SUPPORTED_FILES = { | ||
html: 'text/html', | ||
jpeg: 'image/jpeg', | ||
json: 'application/json', | ||
jpg: 'image/jpeg', | ||
png: 'image/png', | ||
gif: 'image/gif', | ||
mp4: 'video/mp4', | ||
pdf: 'application/pdf', | ||
svg: 'image/svg+xml', | ||
}; | ||
|
||
export const DA_ORIGIN = 'https://admin.da.live'; |
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,168 @@ | ||
import { unified } from 'unified'; | ||
import remarkParse from 'remark-parse'; | ||
import remarkGridTable from '@adobe/remark-gridtables'; | ||
import { toHast as mdast2hast, defaultHandlers } from 'mdast-util-to-hast'; | ||
import { raw } from 'hast-util-raw'; | ||
import { mdast2hastGridTablesHandler } from '@adobe/mdast-util-gridtables'; | ||
import { toHtml } from 'hast-util-to-html'; | ||
|
||
import { JSDOM } from 'jsdom'; | ||
|
||
function toBlockCSSClassNames(text) { | ||
if (!text) return []; | ||
const names = []; | ||
const idx = text.lastIndexOf('('); | ||
if (idx >= 0) { | ||
names.push(text.substring(0, idx)); | ||
names.push(...text.substring(idx + 1).split(',')); | ||
} else { | ||
names.push(text); | ||
} | ||
|
||
return names | ||
.map((name) => | ||
name | ||
.toLowerCase() | ||
.replace(/[^0-9a-z]+/g, '-') | ||
.replace(/^-+/, '') | ||
.replace(/-+$/, '') | ||
) | ||
.filter((name) => !!name); | ||
} | ||
|
||
function convertBlocks(dom) { | ||
const tables = dom.window.document.querySelectorAll('body > table'); | ||
|
||
tables.forEach((table) => { | ||
const rows = [ | ||
...table.querySelectorAll(':scope > tbody > tr, :scope > thead > tr'), | ||
]; | ||
const nameRow = rows.shift(); | ||
const divs = rows.map((row) => { | ||
const cols = row.querySelectorAll(':scope > td, :scope > th'); | ||
// eslint-disable-next-line no-shadow | ||
const divs = [...cols].map((col) => { | ||
const { innerHTML } = col; | ||
const div = dom.window.document.createElement('div'); | ||
div.innerHTML = innerHTML; | ||
mokimo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return div; | ||
}); | ||
const div = dom.window.document.createElement('div'); | ||
div.append(...divs); | ||
return div; | ||
}); | ||
|
||
const div = dom.window.document.createElement('div'); | ||
div.className = toBlockCSSClassNames(nameRow.textContent).join(' '); | ||
div.append(...divs); | ||
table.parentElement.replaceChild(div, table); | ||
}); | ||
} | ||
|
||
function makePictures(dom) { | ||
const imgs = dom.window.document.querySelectorAll('img'); | ||
imgs.forEach((img) => { | ||
const clone = img.cloneNode(true); | ||
clone.setAttribute('loading', 'lazy'); | ||
// clone.src = `${clone.src}?optimize=medium`; | ||
|
||
let pic = dom.window.document.createElement('picture'); | ||
|
||
const srcMobile = dom.window.document.createElement('source'); | ||
srcMobile.srcset = clone.src; | ||
|
||
const srcTablet = dom.window.document.createElement('source'); | ||
srcTablet.srcset = clone.src; | ||
srcTablet.media = '(min-width: 600px)'; | ||
mokimo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
pic.append(srcMobile, srcTablet, clone); | ||
|
||
const hrefAttr = img.getAttribute('href'); | ||
if (hrefAttr) { | ||
const a = dom.window.document.createElement('a'); | ||
a.href = hrefAttr; | ||
const titleAttr = img.getAttribute('title'); | ||
if (titleAttr) { | ||
a.title = titleAttr; | ||
} | ||
a.append(pic); | ||
pic = a; | ||
} | ||
|
||
// Determine what to replace | ||
const imgParent = img.parentElement; | ||
const imgGrandparent = imgParent.parentElement; | ||
if (imgParent.nodeName === 'P' && imgGrandparent?.childElementCount === 1) { | ||
imgGrandparent.replaceChild(pic, imgParent); | ||
} else { | ||
imgParent.replaceChild(pic, img); | ||
} | ||
}); | ||
} | ||
|
||
function makeSections(dom) { | ||
const children = dom.window.document.body.querySelectorAll(':scope > *'); | ||
|
||
const section = dom.window.document.createElement('div'); | ||
const sections = [...children].reduce( | ||
(acc, child) => { | ||
if (child.nodeName === 'HR') { | ||
child.remove(); | ||
acc.push(dom.window.document.createElement('div')); | ||
} else { | ||
acc[acc.length - 1].append(child); | ||
} | ||
return acc; | ||
}, | ||
[section] | ||
); | ||
|
||
dom.window.document.body.append(...sections); | ||
} | ||
|
||
// Generic docs have table blocks and HRs, but not ProseMirror decorations | ||
export function docDomToAemHtml(dom) { | ||
convertBlocks(dom); | ||
makePictures(dom); | ||
makeSections(dom); | ||
|
||
return dom.window.document.body.innerHTML; | ||
} | ||
|
||
function makeHast(mdast) { | ||
const handlers = { | ||
...defaultHandlers, | ||
gridTable: mdast2hastGridTablesHandler(), | ||
}; | ||
const hast = mdast2hast(mdast, { handlers, allowDangerousHtml: true }); | ||
return raw(hast); | ||
} | ||
|
||
function removeImageSizeHash(dom) { | ||
const imgs = dom.window.document.querySelectorAll('[src*="#width"]'); | ||
imgs.forEach((img) => { | ||
img.setAttribute('src', img.src.split('#width')[0]); | ||
mokimo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}); | ||
} | ||
|
||
export function mdToDocDom(md) { | ||
// convert linebreaks | ||
const converted = md.replace(/(\r\n|\n|\r)/gm, '\n'); | ||
|
||
// convert to mdast | ||
const mdast = unified() | ||
.use(remarkParse) | ||
.use(remarkGridTable) | ||
.parse(converted); | ||
|
||
const hast = makeHast(mdast); | ||
|
||
let htmlText = toHtml(hast); | ||
htmlText = htmlText.replaceAll('.hlx.page', '.hlx.live'); | ||
htmlText = htmlText.replaceAll('.aem.page', '.aem.live'); | ||
|
||
const dom = new JSDOM(htmlText); | ||
removeImageSizeHash(dom); | ||
|
||
return dom; | ||
} |
mokimo marked this conversation as resolved.
Show resolved
Hide resolved
|
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,107 @@ | ||
import { DA_ORIGIN } from './constants.js'; | ||
|
||
let token; | ||
export async function getImsToken() { | ||
console.log('Fetching IMS token'); | ||
const params = new URLSearchParams(); | ||
params.append('client_id', process.env.ROLLING_IMPORT_CLIENT_ID); | ||
params.append('client_secret', process.env.ROLLING_IMPORT_CLIENT_SECRET); | ||
params.append('code', process.env.ROLLING_IMPORT_CODE); | ||
params.append('grant_type', process.env.ROLLING_IMPORT_GRANT_TYPE); | ||
|
||
const response = await fetch(process.env.ROLLING_IMPORT_IMS_URL, { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
}, | ||
body: params, | ||
}); | ||
|
||
if (!response.ok) { | ||
throw new Error('Failed to retrieve IMS token'); | ||
} | ||
|
||
const data = await response.json(); | ||
token = data.access_token; | ||
mokimo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
console.log('Fetched IMS token'); | ||
} | ||
|
||
export const daFetch = async (url, opts = {}) => { | ||
opts.headers ||= {}; | ||
opts.headers.Authorization = `Bearer ${token}`; | ||
const resp = await fetch(url, opts); | ||
if (!resp.ok) throw new Error('DA import failed'); | ||
return resp; | ||
}; | ||
|
||
export function replaceHtml(text, fromOrg, fromRepo) { | ||
let inner = text; | ||
if (fromOrg && fromRepo) { | ||
const fromOrigin = `https://main--${fromRepo}--${fromOrg}.aem.live`; | ||
inner = text | ||
.replaceAll('./media', `${fromOrigin}/media`) | ||
.replaceAll('href="/', `href="${fromOrigin}/`); | ||
} | ||
|
||
return ` | ||
<body> | ||
<header></header> | ||
<main>${inner}</main> | ||
<footer></footer> | ||
</body> | ||
`; | ||
} | ||
|
||
export async function saveToDa(text, url) { | ||
const daPath = `/${url.org}/${url.repo}${url.pathname}`; | ||
const daHref = `https://da.live/edit#${daPath}`; | ||
const { org, repo } = url; | ||
|
||
const body = replaceHtml(text, org, repo); | ||
|
||
const blob = new Blob([body], { type: 'text/html' }); | ||
const formData = new FormData(); | ||
formData.append('data', blob); | ||
const opts = { method: 'PUT', body: formData }; | ||
try { | ||
const daResp = await daFetch(`${DA_ORIGIN}/source${daPath}.html`, opts); | ||
return { daHref, daStatus: daResp.status, daResp, ok: daResp.ok }; | ||
} catch (e) { | ||
console.log(`Couldn't save ${url.daUrl} `); | ||
throw e; | ||
} | ||
} | ||
|
||
function getBlob(url, content) { | ||
const body = | ||
url.type === 'json' | ||
? content | ||
: replaceHtml(content, url.fromOrg, url.fromRepo); | ||
|
||
const type = url.type === 'json' ? 'application/json' : 'text/html'; | ||
|
||
return new Blob([body], { type }); | ||
} | ||
|
||
export async function saveAllToDa(url, content) { | ||
const { toOrg, toRepo, destPath, editPath, type } = url; | ||
|
||
const route = type === 'json' ? '/sheet' : '/edit'; | ||
url.daHref = `https://da.live${route}#/${toOrg}/${toRepo}${editPath}`; | ||
|
||
const blob = getBlob(url, content); | ||
const body = new FormData(); | ||
body.append('data', blob); | ||
const opts = { method: 'PUT', body }; | ||
|
||
try { | ||
const resp = await daFetch( | ||
`${DA_ORIGIN}/source/${toOrg}/${toRepo}${destPath}`, | ||
opts | ||
); | ||
return resp.status; | ||
} catch { | ||
console.log(`Couldn't save ${destPath}`); | ||
return 500; | ||
} | ||
} |
Oops, something went wrong.
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.