motoko
Version:
Compile and run Motoko smart contracts in Node.js or the browser.
165 lines • 6.11 kB
JavaScript
;
// Derived from: https://github.com/dfinity/motoko-playground/blob/main/src/workers/file.ts
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.fetchPackage = fetchPackage;
exports.installPackages = installPackages;
exports.validatePackage = validatePackage;
const parse_github_url_1 = __importDefault(require("parse-github-url"));
const cross_fetch_1 = __importDefault(require("cross-fetch"));
const sanitize_filename_1 = __importDefault(require("sanitize-filename"));
function parseGithubPackageInfo(path) {
if (!path) {
return;
}
if (typeof path === 'object') {
return path;
}
let result;
try {
result = (0, parse_github_url_1.default)(path);
if (!result) {
return;
}
}
catch (err) {
// console.warn(err);
return;
}
const { name, filepath, branch, owner } = result;
return {
name,
repo: `https://github.com/${owner}/${name}.git`,
version: branch,
dir: filepath,
branch,
// homepage: ,
};
}
async function fetchPackageFiles(info) {
const prefix = 'https://github.com/';
const suffix = '.git';
if (!info.repo.startsWith(prefix) || !info.repo.endsWith(suffix)) {
return;
}
const repoPart = info.repo.slice(prefix.length, -suffix.length);
const possiblyCDN = !((info.branch &&
info.branch.length % 2 === 0 &&
/^[A-F0-9]+$/i.test(info.branch)) ||
info.branch === 'master' ||
info.branch === 'main');
if (possiblyCDN) {
try {
const result = await fetchFromService(info, 'CDN', `https://data.jsdelivr.com/v1/package/gh/${repoPart}@${info.branch}/flat`, `https://cdn.jsdelivr.net/gh/${repoPart}@${info.branch}`, 'files', 'name');
if (result) {
return result;
}
}
catch (err) {
console.error('[CDN]', err);
}
}
return await fetchFromService(info, 'GitHub', `https://api.github.com/repos/${repoPart}/git/trees/${info.branch}?recursive=1`, `https://raw.githubusercontent.com/${repoPart}/${info.branch}/`, 'tree', 'path', (file) => file.type === 'blob');
}
async function fetchFromService(info, serviceName, metaUrl, baseUrl, resultProperty, pathProperty, condition) {
const response = await (0, cross_fetch_1.default)(metaUrl);
if (!response.ok) {
throw Error(response.statusText ||
`Could not fetch from ${serviceName}: ${info.repo}`);
}
const json = await response.json();
if (!json.hasOwnProperty(resultProperty)) {
throw new Error(`Unexpected response from ${serviceName}`);
}
// Remove leading and trailing '/' from directory
let directory = info.dir
? info.dir.replace(/^\//, '').replace(/\/$/, '')
: '';
const files = {};
await Promise.all(json[resultProperty]
.filter((file) => {
return ((!directory ||
file[pathProperty].startsWith(file[pathProperty].startsWith('/')
? `/${directory}`
: directory)) &&
(!condition || condition(file)) &&
/\.mo$/.test(file[pathProperty]));
})
.map(async (file) => {
const response = await (0, cross_fetch_1.default)(`${baseUrl}${file[pathProperty]}`);
if (!response.ok) {
throw Error(response.statusText);
}
const content = await response.text();
let path = file[pathProperty];
if (path.startsWith('/')) {
path = path.slice(1);
}
if (directory) {
// Remove directory prefix
path = path.slice(directory.length + 1);
}
files[path] = {
content,
};
}));
return files;
}
async function fetchPackage(name, info) {
if (typeof info === 'string') {
info = parseGithubPackageInfo(info);
}
const files = await fetchPackageFiles(info);
if (!files) {
return;
}
return {
name,
version: info.version,
files,
};
}
async function installPackages(mo, packages) {
await Promise.all(Object.entries(packages).map(async ([name, info]) => {
const pkg = await fetchPackage(name, info);
mo.loadPackage(pkg);
return pkg;
}));
}
function validatePackage(pkg) {
function showValue(value) {
const string = JSON.stringify(value);
return string.length > 50 ? string.substring(0, 50) + '...' : string;
}
function getPackageDisplayName() {
return `(${pkg.name} / ${pkg.version})`;
}
if (typeof pkg !== 'object' || Array.isArray(pkg)) {
throw new Error(`Unexpected package: ${showValue(pkg)}`);
}
if (typeof pkg.name !== 'string' || (0, sanitize_filename_1.default)(pkg.name) !== pkg.name) {
throw new Error(`Invalid package name ${getPackageDisplayName()}`);
}
if (typeof pkg.version !== 'string' ||
(0, sanitize_filename_1.default)(pkg.version) !== pkg.version) {
throw new Error(`Invalid package version ${getPackageDisplayName()}`);
}
if (typeof pkg.files !== 'object' || Array.isArray(pkg.files)) {
throw new Error(`Invalid package files: ${showValue(pkg.files)}`);
}
Object.entries(pkg.files).forEach(([path, file]) => {
if (typeof path !== 'string' ||
path.split('/').some((p) => (0, sanitize_filename_1.default)(p) !== p)) {
throw new Error(`Invalid file path ${getPackageDisplayName()} [${path}]`);
}
if (typeof file !== 'object' || Array.isArray(file)) {
throw new Error(`Invalid file ${getPackageDisplayName()} [${path}]: ${showValue(file)}`);
}
if (typeof file.content !== 'string') {
throw new Error(`Invalid file content ${getPackageDisplayName()} [${path}]: ${showValue(file.content)}`);
}
});
}
//# sourceMappingURL=package.js.map