sfcc-dts
Version:
> High quality Salesforce Commerce Cloud type definitions. A dw-api-types "done right"
237 lines (236 loc) • 11.2 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const axios_1 = __importDefault(require("axios"));
const cheerio_1 = __importDefault(require("cheerio"));
const cli_progress_1 = __importDefault(require("cli-progress"));
const fs_1 = __importDefault(require("fs"));
const https_1 = __importDefault(require("https"));
const path_1 = __importDefault(require("path"));
const interfaceexcludes = [
"dw.io.XMLStreamWriter"
];
const progress = new cli_progress_1.default.SingleBar({}, cli_progress_1.default.Presets.rect);
const api = {};
const propKey = (item, key) => {
let count = 0;
while (item[key]) {
key += count++;
}
return key;
};
const arrayToObj = (array) => {
const initialValue = {};
for (let j = 0; j < array.length; j++) {
const element = array[j];
initialValue[propKey(initialValue, element.name)] = element;
}
return initialValue;
};
const collectClasses = (obj, mapping) => {
if (obj.fullClassName) {
if (!obj.fullClassName.startsWith('TopLevel.')) {
mapping[obj.fullClassName.split('.').pop()] = obj.fullClassName;
}
}
else {
Object.keys(obj).forEach((key) => collectClasses(obj[key], mapping));
}
return mapping;
};
const collectInterfaces = (obj, interfaces) => {
if (obj.hierarchy) {
obj.hierarchy.forEach((el) => {
if (!interfaces.includes(el.name) && !interfaceexcludes.includes(el.name)) {
interfaces.push(el.name);
}
});
}
else {
Object.keys(obj).forEach((key) => collectInterfaces(obj[key], interfaces));
}
return interfaces;
};
const sort = (obj) => {
if (obj.fullClassName)
return obj;
return Object.keys(obj).sort().reduce(function (sortedObj, key) {
sortedObj[key] = sort(obj[key]);
return sortedObj;
}, {});
};
const finish = (api) => {
const apiSorted = sort(api);
const apiMapping = collectClasses(apiSorted, {});
const apiInterfaces = collectInterfaces(apiSorted, []);
console.log('Writing files');
fs_1.default.writeFileSync(path_1.default.join(process.cwd(), './api/sfcc-api.json'), JSON.stringify({
api: apiSorted,
mapping: apiMapping,
interfaces: apiInterfaces
}, null, 2));
};
const mapDetail = ($, el) => {
let detailSignature = $(el).find('.detailSignature').text().trim();
let parsedPropertyText = /^(?:\n|\s)*(static)?(?:public[\s\t])?(?:\n|\s)*([^\s\t]+)\(([^\)]*)\)(?:\n|\t|\s|:)*([^\s\t]+)?/.exec(detailSignature);
if (!parsedPropertyText) {
console.log('cannot parse ' + detailSignature);
return;
}
let staticprefix = parsedPropertyText[1];
let params = parsedPropertyText[3];
let returnValue = parsedPropertyText[4];
let returnDesc = $(el).find(".parameters").filter((i, el) => $(el).find(".parameterTitle").text().indexOf("Returns:") >= 0).find('.parameterDesc').text().trim();
let paramsDoc = $(el).find(".parameters").filter((i, el) => $(el).find(".parameterTitle").text().indexOf("Parameters:") >= 0).find('.parameterDetail');
return {
name: $(el).find(".detailName").text().trim(),
args: params.split(",").filter(arg => arg && arg.trim().length).map((arg) => {
let argTouple = arg.split(":");
let argType = argTouple[1].trim().split('...');
let argName = argTouple[0].trim();
return {
name: argName,
description: paramsDoc.filter((i, el) => $(el).find(".parameterName").text().trim() == argName).find('.parameterDesc').text().trim(),
class: {
name: argType[0].trim(),
},
multiple: argType.length > 1
};
}),
static: staticprefix && staticprefix.indexOf('static') == 0,
class: {
name: returnValue,
description: returnDesc
},
description: $(el).find(".description").text().trim(),
deprecated: !!$(el).find(".dep").length
};
};
(async () => {
const baseUrl = "https://documentation.b2c.commercecloud.salesforce.com/DOC1/topic/com.demandware.dochelp/DWAPI/scriptapi/html/api/";
let instance = axios_1.default.create({
timeout: 120000,
httpsAgent: new https_1.default.Agent({ keepAlive: true })
});
let response = await instance.get(baseUrl + "classList.html");
let $ = cheerio_1.default.load(response.data);
let classLinks = $("a[href^=class_]")
.map((i, el) => $(el).attr("href"))
.toArray();
console.log(`Scraping ${classLinks.length} classes`);
progress.start(classLinks.length, 0);
// old style for loop to make one request at a time...
for (let j = 0; j < classLinks.length; j++) {
const classLink = classLinks[j];
try {
let classPage = await instance.get(baseUrl + classLink);
let $ = cheerio_1.default.load(classPage.data);
let deprecated = $('.classSumary .parameters .parameterTitle').filter((i, el) => $(el).text().indexOf('Deprecated') >= 0);
let $class = $("div[id^=class_]");
if ($class.length > 0 && deprecated.length === 0) {
let fullClassName = $class.eq(0).attr("id").split("_")[1];
let className = fullClassName.split('.').slice(-1)[0];
var packageName = $('.packageName').text().trim();
var classes = packageName.split('.').reduce((classesInPackage, packageName) => {
return classesInPackage[packageName] || (classesInPackage[packageName] = {});
}, api);
if (!(className in classes)) {
// console.log('Parsing ' + className);
let methods = arrayToObj($(".section")
.filter((i, el) => $(el).find(".header").text().indexOf("Method Detail") >= 0)
.find(".detailItem")
.map((i, el) => {
return mapDetail($, el);
})
.get());
classes[className] = {
fullClassName: fullClassName,
package: packageName,
description: $('.classSummaryDetail .description').html().trim(),
hierarchy: $(".hierarchy a[href]")
.map((i, el) => ({
name: $(el).text().trim(),
// link: $(el).attr("href"),
}))
.get(),
constants: arrayToObj($(".section")
.filter((i, el) => $(el).find(".header").text().trim() === "Constants")
.find(".summaryItem")
.map((i, el) => {
let description = $(el).find(".description").text();
let propertyText = $(el).text().replace(description, '').trim();
let parsedPropertyText = /^(\n|\s)*([^\s\t]+)(\n|\t|\s|:)*([^\s\t]+)(\n|\t|\s)*(=(\n|\t|\s)*(("[^"]+")|([^\s\t\n]+)))?/.exec(propertyText);
return {
name: parsedPropertyText[2].trim(),
value: parsedPropertyText[8] ? parsedPropertyText[8].trim() : null,
class: {
name: $(el).find("a[href] span").text().trim() ||
parsedPropertyText[4].trim(),
// link: $(el).find("a[href]").attr("href"),
},
description: description.trim(),
deprecated: !!$(el).find(".dep").length,
type: "constant"
};
})
.get()),
properties: arrayToObj($(".section")
.filter((i, el) => $(el).find(".header").text().trim() === "Properties")
.find(".summaryItem")
.map((i, el) => {
let propertyEL = $(el);
if (propertyEL.find('.description')) {
propertyEL = propertyEL.clone();
propertyEL.find('.description').remove();
}
let propertyText = propertyEL.text().trim();
let parsedPropertyText = /^(\n|\s)*(static)?(\n|\s)*([^\s\t]+)(\n|\t|\s|:)*([^\s\t]+)/.exec(propertyText);
let name = parsedPropertyText[4].trim();
let isStatic = !!parsedPropertyText[2];
let getter = methods[`get${name.charAt(0).toUpperCase()}${name.substring(1)}`];
if (getter && getter.static) {
// properties don't have the "static" modifier in docs, try to determine it from the getter
isStatic = true; // e.g. dw.system.System.instanceHostname
}
return {
name: name,
class: {
name: $(el).find("a[href] span").text().trim() ||
parsedPropertyText[6].trim(),
// link: $(el).find("a[href]").attr("href"),
},
static: isStatic,
readonly: propertyText.indexOf("Read Only") > 0,
description: $(el).find(".description").text().trim(),
deprecated: !!$(el).find(".dep").length,
type: "property"
};
})
.get()),
constructors: arrayToObj($(".section")
.filter((i, el) => $(el)
.find(".header")
.text()
.indexOf("Constructor Detail") >= 0
&& $(el).find('.summaryItem').eq(0).text().indexOf('This class does not have a constructor') < 0)
.find(".detailItem")
.map((i, el) => {
return mapDetail($, el);
})
.get()),
methods: methods
};
}
}
}
catch (e) {
console.error(`\nError fetching ${classLink}: ${e.message}`);
}
progress.increment();
}
})().then(() => {
progress.stop();
finish(api);
});