@stackbit/sdk
Version:
278 lines • 12 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileBrowser = exports.getFileBrowserFromOptions = exports.GitHubFileBrowserAdapter = exports.FileSystemFileBrowserAdapter = exports.EXCLUDED_LIST_FILES = void 0;
const path_1 = __importDefault(require("path"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const lodash_1 = __importDefault(require("lodash"));
const micromatch_1 = __importDefault(require("micromatch"));
const rest_1 = require("@octokit/rest");
const utils_1 = require("@stackbit/utils");
const consts_1 = require("../consts");
exports.EXCLUDED_LIST_FILES = ['**/node_modules/**', '**/.git/**', '.idea/**'];
class FileSystemFileBrowserAdapter {
constructor({ dirPath }) {
this.dirPath = dirPath;
}
async listFiles({ includePattern, excludePattern }) {
const readDirResult = await (0, utils_1.readDirRecursively)(this.dirPath, {
includeDirs: true,
includeStats: true,
filter: (filePath) => {
const isIncluded = !includePattern || micromatch_1.default.isMatch(filePath, includePattern);
excludePattern = excludePattern || exports.EXCLUDED_LIST_FILES;
const isExcluded = micromatch_1.default.isMatch(filePath, excludePattern);
return isIncluded && !isExcluded;
}
});
// TODO: order files alphabetically so both Git and FileSystem adapters will result same analyze results
return readDirResult.map((fileResult) => ({
filePath: fileResult.filePath,
isFile: fileResult.stats.isFile(),
isDirectory: fileResult.stats.isDirectory()
}));
}
async readFile(filePath) {
const absPath = path_1.default.join(this.dirPath, filePath);
return fs_extra_1.default.readFile(absPath, 'utf8');
}
}
exports.FileSystemFileBrowserAdapter = FileSystemFileBrowserAdapter;
class GitHubFileBrowserAdapter {
constructor(options) {
if ('repoUrl' in options) {
const parsedRepoUrl = this.parseGitHubUrl(options.repoUrl);
if (!parsedRepoUrl) {
throw new Error(`repository URL '${options.repoUrl}' cannot be parsed, please use standard github URL`);
}
this.owner = parsedRepoUrl.owner;
this.repo = parsedRepoUrl.repo;
}
else {
this.owner = options.owner;
this.repo = options.repo;
}
this.branch = options.branch;
this.octokit = new rest_1.Octokit({
auth: options.auth
});
}
async listFiles(listFilesOptions) {
// const branchResponse = await this.octokit.repos.getBranch({
// owner: this.owner,
// repo: this.repo,
// branch: this.branch
// });
// const treeSha = branchResponse.data.commit.commit.tree.sha;
const branchResponse = await this.octokit.repos.listBranches({
owner: this.owner,
repo: this.repo
});
const branch = lodash_1.default.find(branchResponse.data, { name: this.branch });
if (!branch) {
throw new Error(`branch ${this.branch} not found`);
}
const treeSha = branch.commit.sha;
const treeResponse = await this.octokit.git.getTree({
owner: this.owner,
repo: this.repo,
tree_sha: treeSha,
recursive: 'true'
});
let tree;
if (!treeResponse.data.truncated) {
const { includePattern, excludePattern } = listFilesOptions;
tree = treeResponse.data.tree;
tree = tree.filter((node) => {
if (!node.path || !(node.type === 'blob' || node.type === 'tree')) {
return false;
}
const isIncluded = !includePattern || micromatch_1.default.isMatch(node.path, includePattern);
const isExcluded = micromatch_1.default.isMatch(node.path, excludePattern || exports.EXCLUDED_LIST_FILES);
return isIncluded && !isExcluded;
});
}
else {
tree = await this.listFilesRecursively(treeResponse.data.sha, listFilesOptions, '');
}
// TODO: order files alphabetically so both Git and FileSystem adapters will result same analyze results
return tree.map((node) => ({
filePath: node.path,
isFile: node.type === 'blob',
isDirectory: node.type === 'tree'
}));
}
async listFilesRecursively(treeSha, listFilesOptions, parentPath) {
const treeResponse = await this.octokit.git.getTree({
owner: this.owner,
repo: this.repo,
tree_sha: treeSha
});
const { includePattern, excludePattern } = listFilesOptions;
const { blob: files, tree: folders } = lodash_1.default.groupBy(treeResponse.data.tree, 'type');
const filter = (fullPath) => {
const isIncluded = !includePattern || micromatch_1.default.isMatch(fullPath, includePattern);
const isExcluded = micromatch_1.default.isMatch(fullPath, excludePattern || exports.EXCLUDED_LIST_FILES);
return isIncluded && !isExcluded;
};
const filteredFolders = (folders || []).reduce((accum, treeNode) => {
if (!treeNode.path || !treeNode.sha) {
return accum;
}
const fullPath = (parentPath ? parentPath + '/' : '') + treeNode.path;
if (!filter(fullPath)) {
return accum;
}
return accum.concat(Object.assign(treeNode, { path: fullPath }));
}, []);
const filteredFiles = (files || []).reduce((accum, fileNode) => {
if (!fileNode.path) {
return accum;
}
const fullPath = (parentPath ? parentPath + '/' : '') + fileNode.path;
if (!filter(fullPath)) {
return accum;
}
return accum.concat(Object.assign(fileNode, { path: fullPath }));
}, []);
const folderResults = await (0, utils_1.reducePromise)(filteredFolders, async (accum, treeNode) => {
const results = await this.listFilesRecursively(treeNode.sha, listFilesOptions, treeNode.path);
return accum.concat(treeNode, results);
}, []);
return folderResults.concat(filteredFiles);
}
async readFile(filePath) {
const contentResponse = await this.octokit.repos.getContent({
owner: this.owner,
repo: this.repo,
path: filePath
});
if ('content' in contentResponse.data) {
const base64Content = contentResponse.data.content;
return Buffer.from(base64Content, 'base64').toString();
}
return '';
}
parseGitHubUrl(repoUrl) {
const match = repoUrl.match(/github\.com[/:](.+?)\/(.+?)(\.git)?$/);
if (!match) {
return null;
}
const owner = match[1];
const repo = match[2];
return { owner, repo };
}
}
exports.GitHubFileBrowserAdapter = GitHubFileBrowserAdapter;
function getFileBrowserFromOptions(options) {
if ('fileBrowser' in options) {
return options.fileBrowser;
}
if (!('fileBrowserAdapter' in options)) {
throw new Error('either fileBrowser or fileBrowserAdapter must be provided to SSG matcher');
}
return new FileBrowser({ fileBrowserAdapter: options.fileBrowserAdapter });
}
exports.getFileBrowserFromOptions = getFileBrowserFromOptions;
class FileBrowser {
constructor({ fileBrowserAdapter }) {
this.files = [];
this.fileBrowserAdapter = fileBrowserAdapter;
this.filePaths = [];
this.filePathMap = {};
this.directoryPathsMap = {};
this.filePathsByFileName = {};
this.fileTree = {};
this.fileData = {};
}
async listFiles({ includePattern, excludePattern } = {}) {
if (this.files.length > 0) {
return;
}
this.files = await this.fileBrowserAdapter.listFiles({ includePattern, excludePattern });
// create maps to find files by names or paths quickly
lodash_1.default.forEach(this.files, (fileReadResult) => {
const filePath = fileReadResult.filePath;
const filePathArr = filePath.split(path_1.default.sep);
if (fileReadResult.isFile) {
lodash_1.default.set(this.fileTree, filePathArr, true);
const pathObject = path_1.default.parse(filePath);
const fileName = pathObject.base;
if (!(fileName in this.filePathsByFileName)) {
this.filePathsByFileName[fileName] = [];
}
this.filePathsByFileName[fileName].push(filePath);
this.filePaths.push(filePath);
this.filePathMap[filePath] = true;
}
else if (fileReadResult.isDirectory) {
if (!lodash_1.default.has(this.fileTree, filePathArr)) {
lodash_1.default.set(this.fileTree, filePathArr, {});
}
this.directoryPathsMap[filePath] = true;
}
});
}
filePathExists(filePath) {
return lodash_1.default.has(this.filePathMap, filePath);
}
fileNameExists(fileName) {
return lodash_1.default.has(this.filePathsByFileName, fileName);
}
getFilePathsForFileName(fileName) {
return lodash_1.default.get(this.filePathsByFileName, fileName, []);
}
directoryPathExists(dirPath) {
return lodash_1.default.has(this.directoryPathsMap, dirPath);
}
findFiles(pattern) {
return (0, micromatch_1.default)(this.filePaths, pattern);
}
readFilesRecursively(dirPath, { filter, includeDirs }) {
const reduceTreeNode = (treeNode, parentPath) => {
return lodash_1.default.reduce(treeNode, (result, value, name) => {
const filePath = path_1.default.join(parentPath, name);
const isFile = value === true;
if (filter && !filter({ filePath, isFile: isFile, isDirectory: !isFile })) {
return result;
}
if (value !== true) {
const childFilePaths = reduceTreeNode(value, filePath);
result = includeDirs ? result.concat(filePath, childFilePaths) : result.concat(childFilePaths);
}
else {
result = result.concat(filePath);
}
return result;
}, []);
};
const treeNode = dirPath === '' ? this.fileTree : lodash_1.default.get(this.fileTree, dirPath.split(path_1.default.sep));
return reduceTreeNode(treeNode, '');
}
async getFileData(filePath) {
if (!this.filePathExists(filePath)) {
return null;
}
if (!(filePath in this.fileData)) {
const extension = path_1.default.extname(filePath).substring(1);
const data = await this.fileBrowserAdapter.readFile(filePath);
if ([...consts_1.DATA_FILE_EXTENSIONS, ...consts_1.MARKDOWN_FILE_EXTENSIONS].includes(extension)) {
try {
this.fileData[filePath] = (0, utils_1.parseDataByFilePath)(data, filePath);
}
catch (error) {
console.warn(`error parsing file: ${filePath}`);
this.fileData[filePath] = null;
}
}
else {
this.fileData[filePath] = data;
}
}
return this.fileData[filePath];
}
}
exports.FileBrowser = FileBrowser;
//# sourceMappingURL=file-browser.js.map