import { Directus } from '@directus/sdk' import yaml from 'js-yaml' import fs from 'fs' import Axios from 'axios' import path from 'path' export default class DirectusToMarkdown { constructor(config) { this.url = config.url || process.env.DIRECTUS_URL this.token = config.token || process.env.DIRECTUS_TOKEN this.contentKey = config.contentKey || 'content' this.collections = config.collections this.directus = new Directus(this.url, { auth: { staticToken: this.token }}); } _formatFrontMatter(item, deleteFields) { const front = { ...item } // copie item delete front[this.contentKey] for (const field of deleteFields) { delete front[field] } return `---\r\n${yaml.dump(front).trim()}\r\n---\r\n\r\n` } async _writeIndex(item, itemPath, deleteFields) { const frontMatter = this._formatFrontMatter(item, deleteFields) const content = item[this.contentKey] ? item[this.contentKey].toString() : '' const itemContent = `${frontMatter}${content}\r\n` const filePath = `${itemPath}/${item.path ? item.path : 'index.md'}` const fileDirname = path.dirname(filePath) if (!fs.existsSync(fileDirname)) { fs.mkdirSync(fileDirname, { recursive: true }); } fs.writeFileSync(filePath, itemContent) } async _writeFile(response, filePath) { const writer = fs.createWriteStream(filePath) response.data.pipe(writer) return new Promise((resolve, reject) => { writer.on('finish', resolve) writer.on('error', reject) }) } async _downloadAssets(item, itemPath) { const uuidregex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/; for (const [key, uuid] of Object.entries(item)){ // TODO: instead of trying to pull asset and hope for the best, // crosscheck things with the /fields and play by the rules if (typeof uuid === 'string' && uuid.match(uuidregex)) { const filename = await this._downloadAsset(uuid, itemPath) item[key] = filename // Update field with filename instead of uuid } } } async _downloadAsset(uuid, itemPath) { const response = await Axios({url: `${this.url}/assets/${uuid}?download&access_token=${this.token}`, method: 'GET', responseType: 'stream'}) const disposition = response.headers['content-disposition'].match(/filename="(.*)"/); const filename = disposition ? disposition[1] : `${value}.${mime.extension(response.headers['content-type'])}`; const savePath = `${itemPath}/${filename}`; await this._writeFile(response, savePath) return filename } async _downloadAssetsFromContent(item, itemPath) { const uuidregex = '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}' const regEx = new RegExp(`!\\[.*\\]\\(${this.url}/assets/(${uuidregex})\\)`, "ig") const uuids = [] let asset do { asset = regEx.exec(item[this.contentKey]) if (asset) uuids.push(asset[1]) } while (asset) for (const uuid of uuids) { const filename = await this._downloadAsset(uuid, itemPath) const url = `${this.url}/assets/${uuid}` item[this.contentKey] = item[this.contentKey].replace(url, filename) } } async export() { for (const collectionName in this.collections) { const collection = this.collections[collectionName] const readByQueryOption = collection.readByQueryOption const deleteFields = collection.deleteFields const items = (await this.directus.items(collectionName).readByQuery(readByQueryOption)).data for (const item of items) { const itemPath = collection.pathBuilder(item) if (!fs.existsSync(itemPath)) { fs.mkdirSync(itemPath, { recursive: true }); } await this._downloadAssets(item, itemPath) await this._downloadAssetsFromContent(item, itemPath) await this._writeIndex(item, itemPath, deleteFields) } } } }