primo-extract
Version:
Extract all sources and templates from a Primo environment
212 lines (186 loc) • 6.57 kB
JavaScript
import sourceMap from 'source-map'
import fs from 'fs'
import path from 'path'
import mkdirp from 'mkdirp'
import axios from 'axios'
import os from 'os'
import glob from 'glob'
import { html as beautifyHtml } from 'js-beautify'
import { js as beautifyJS } from 'js-beautify'
// Primo May 2023 Release
// const Parts = [
// "app.js.map",
// "vendor.js.map",
// "account_chunk.js.map",
// "almaViewer_chunk.js.map",
// "fullView_chunk.js.map",
// "favorites_chunk.js.map",
// "collectionDiscovery_chunk.js.map",
// "atoz_chunk.js.map",
// "account_chunk_web_pack_generated.js.map",
// "almaViewer_chunk_web_pack_generated.js.map",
// "angular.js.map",
// "atoz_chunk_web_pack_generated.js.map",
// "bootstrap_bundle.js.map",
// "bundle.js.map",
// "collectionDiscovery_chunk_web_pack_generated.js.map",
// "favorites_chunk_web_pack_generated.js.map",
// "fullView_chunk_web_pack_generated.js.map"];
// Primo February 2024 Release
const Parts = ["bundle.js.map"];
async function extract(uri, outDir, primoType = 'discovery' ) {
outDir = path.resolve(outDir.replace(/^\~/, os.homedir()));
const mapsDir = `${outDir}/tmp/maps`;
const filepaths = await downloadMaps(mapsDir, uri, primoType);
await dumpSource(filepaths, outDir);
await copyFiles(outDir);
}
async function downloadMaps(mapsDir, uri, primoType = 'discovery') {
console.log(mapsDir);
mkdirp.sync(mapsDir);
let filepaths = []
console.log(`Primo type = ${primoType}`);
await Promise.all(Parts.map(async (part) => {
console.log(`Fetching part ${part}`);
try {
const file = await getMap(`${uri}/${primoType}/lib/${part}`);
if (file.status == 200) {
const filename = path.parse(file.request.path).base;
const filepath = `${mapsDir}${path.sep}${filename}`;
filepaths.push(filepath);
console.log(`Writing ${filename} to ${filepath}`);
fs.writeFileSync(filepath, JSON.stringify(file.data));
}
} catch (e) {
console.log(`\tError (don't panic, yet) fetching part ${part}`);
}
}));
return filepaths;
}
async function getMap(uri) {
return axios.get(uri, {
validateStatus: () => true,
});
}
async function dumpSource(filepaths, outDir) {
console.log('\tParsing maps');
for (const filename of filepaths) {
const subDir = path.basename(filename, '.js.map');
const map = await readMapFile(filename)
await mapConsumerToSource(map, outDir, subDir);
}
}
async function readMapFile(filename) {
return new Promise((resolve, reject) => {
fs.readFile(filename, (err, data) => {
if (err) {
console.log(`\n\n\n${filename}\n\n\n`);
reject(err);
} else {
let map = JSON.parse(data);
resolve(map);
}
})
});
}
async function writeSourceFile(sourceWritePath, source) {
return new Promise((resolve, reject) => {
mkdirp.sync(path.dirname(sourceWritePath));
fs.writeFile(sourceWritePath, source, (err) => {
if (err) {
console.log(`\n\n\n${path.dirname(sourceWritePath)}\n\n\n`);
reject(err)
};
resolve(true)
});
})
}
async function mapConsumerToSource(map, outDir, subDir) {
let consumer = new sourceMap.SourceMapConsumer(map);
if (consumer) {
try {
const c = await consumer;
for (const sourceFile of c.sources) {
const source = c.sourceContentFor(sourceFile);
const fileExt = path.extname(sourceFile);
let beautifiedSource = "";
//console.log(sourceFile, fileExt);
switch (fileExt) {
case "ts":
beautifiedSource = beautifyJS(source)
break;
case "js":
beautifiedSource = beautifyJS(source)
break;
case "html":
beautifiedSource = beautifyHtml(source)
break;
default:
beautifiedSource = source;
}
const sourceWritePath = `${outDir}${path.sep}tmp${path.sep}source${path.sep}${subDir}/${sourceFile.replace(/^webpack:\/+/, '')}`;
await writeSourceFile(sourceWritePath, beautifiedSource);
if (/templates.js/.test(sourceWritePath)) {
dumpTemplates(sourceWritePath, outDir);
}
}
} catch (e) {
console.log(e)
}
} else {
console.log('nothing to extract');
}
}
function dumpTemplates(templatePath, outDir) {
let $templateCache = {
put: function (k, v) {
let s = {}
s[k] = v;
t.push(s);
}
}; //$templateCache replacement
/* Angular shim */
let angular = {
module: function (moduleName) {
return {
run: function (templateArray) {
templateArray[1]($templateCache);
}
}
}
}
global.angular = angular;
global.$templateCache = $templateCache;
global.t = [];
require(templatePath);
console.log(`\tFound ${t.length} templates`);
console.log("\t\tExtracting Templates");
t.forEach(function (d) {
Object.keys(d).forEach(function (k) {
let sourceWritePath = `${outDir}${path.sep}source${path.sep}html${path.sep}${k}`;
mkdirp.sync(path.dirname(sourceWritePath));
fs.writeFileSync(sourceWritePath, beautifyHtml(d[k]));
});
});
}
async function copyFiles(outDir) {
const files = await glob(`${outDir}/tmp/**/webapp/components/**`);
console.log(`\tCopying source files(${files.length})`);
files.forEach((f) => {
try {
let copyFile = `${outDir}${path.sep}source${path.sep}www${
path.sep
}components${f.split(`webapp${path.sep}components`).pop()}`.replace(
"/",
path.sep
);
mkdirp.sync(path.dirname(copyFile));
if (fs.existsSync(f) && fs.lstatSync(f).isFile()) {
fs.copyFileSync(f, copyFile);
}
} catch (e) {
console.log(e.message);
}
});
}
export default extract;