8000 [Release] Stage to Main by milo-pr-merge[bot] · Pull Request #4278 · adobecom/milo · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

[Release] Stage to Main #4278

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 16 commits into from
Jun 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/import/LOCAL_DEBUG_ENTRIES.js
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [eslint] reported by reviewdog 🐶
File ignored by default. Use a negated ignore pattern (like "--ignore-pattern '!<relative/path/to/filename>'") to override.

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"
// },
]
15 changes: 15 additions & 0 deletions .github/workflows/import/constants.js
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [eslint] reported by reviewdog 🐶
File ignored by default. Use a negated ignore pattern (like "--ignore-pattern '!<relative/path/to/filename>'") to override.

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';
168 changes: 168 additions & 0 deletions .github/workflows/import/converters.js
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [eslint] reported by reviewdog 🐶
File ignored by default. Use a negated ignore pattern (like "--ignore-pattern '!<relative/path/to/filename>'") to override.

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;
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)';

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]);
});
}

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;
}
107 changes: 107 additions & 0 deletions .github/workflows/import/daFetch.js
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [eslint] reported by reviewdog 🐶
File ignored by default. Use a negated ignore pattern (like "--ignore-pattern '!<relative/path/to/filename>'") to override.

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;
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;
}
}
Loading
Loading
0