feat: Create library
This commit is contained in:
commit
4fe23e688f
|
@ -0,0 +1 @@
|
|||
node_modules
|
|
@ -0,0 +1,91 @@
|
|||
# Directus To Markdown
|
||||
|
||||
This library export [Directus](https://directus.io) items collections to markdown files with assets.
|
||||
|
||||
I used it to export article from Directus to Hugo website.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Directus
|
||||
|
||||
This library export data from an Directus so you should specify an url and token.
|
||||
|
||||
With environment variables:
|
||||
|
||||
```
|
||||
export DIRECTUS_URL=https://your.directus.url
|
||||
export DIRECTUS_TOKEN=your-token
|
||||
```
|
||||
|
||||
or on configuration parameters :
|
||||
|
||||
```js
|
||||
const config = {
|
||||
url: 'https://your.directus.url',
|
||||
token: 'your-token',
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Collection Name
|
||||
|
||||
The key of collections object should be the name of Directus Collection :
|
||||
|
||||
```js
|
||||
const config = {
|
||||
collections: {
|
||||
news: { ... },
|
||||
pages: { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Content key
|
||||
|
||||
_default: content_
|
||||
|
||||
You can modify the field of the content :
|
||||
|
||||
```js
|
||||
const config = {
|
||||
contentKey: 'body',
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### readManyOption
|
||||
|
||||
`readManyOption` match https://docs.directus.io/reference/sdk/#read-multiple-items
|
||||
|
||||
### Export to specific path
|
||||
|
||||
For each collection you should an `pathBuilder`.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
import DirectusToMarkdown from '@resilien/directus-to-markdown'
|
||||
import urlslug from 'url-slug'
|
||||
|
||||
const config = {
|
||||
url: 'https://your.directus.url',
|
||||
token: 'your-token',
|
||||
contentKey: 'body',
|
||||
collections: {
|
||||
news: {
|
||||
readManyOption: {
|
||||
fields: ['title', 'slug', 'date', 'image', 'image_credit', 'draft', 'body'],
|
||||
filter: { draft: { _eq: 'false' } }
|
||||
},
|
||||
pathBuilder: (article) => {
|
||||
if (article.slug) {
|
||||
return `./content/news/${article.slug}`
|
||||
}
|
||||
return `./content/news/${article.date}-${urlslug(article.title, { remove: /\./g })}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new DirectusToMarkdown(config).export();
|
||||
```
|
|
@ -0,0 +1,94 @@
|
|||
import { Directus } from '@directus/sdk'
|
||||
import yaml from 'js-yaml'
|
||||
import fs from 'fs'
|
||||
import Axios from 'axios'
|
||||
|
||||
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) {
|
||||
const front = { ...item } // copie item
|
||||
delete front[this.contentKey]
|
||||
return `---\r\n${yaml.dump(front).trim()}\r\n---\r\n\r\n`
|
||||
}
|
||||
|
||||
async _writeIndex(item, itemPath) {
|
||||
const frontMatter = this._formatFrontMatter(item)
|
||||
const content = item[this.contentKey] ? item[this.contentKey].toString() : ''
|
||||
const itemContent = `${frontMatter}${content}`
|
||||
const indexName = 'index' // TODO: index or _index ?
|
||||
fs.writeFileSync(`${itemPath}/${indexName}.md`, itemContent)
|
||||
}
|
||||
|
||||
async _writeFile(response, path) {
|
||||
const writer = fs.createWriteStream(path)
|
||||
|
||||
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 readManyOption = collection.readManyOption
|
||||
const items = (await this.directus.items(collectionName).readMany(readManyOption)).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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
"name": "@resilien/directus-to-markdown",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@resilien/directus-to-markdown",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@directus/sdk": "^9.5.0",
|
||||
"js-yaml": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@directus/sdk": {
|
||||
"version": "9.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@directus/sdk/-/sdk-9.5.0.tgz",
|
||||
"integrity": "sha512-zrQmE8Wde5ITKhTpeYgJgb+QhFc8ySq/mFPfLq1+vO/xzvx5mg7v1OER1tMs7Byq3X/78euZCN4rxrvVRWQGNA==",
|
||||
"dependencies": {
|
||||
"axios": "^0.24.0"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.24.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
|
||||
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.4"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.14.7",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
|
||||
"integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@directus/sdk": {
|
||||
"version": "9.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@directus/sdk/-/sdk-9.5.0.tgz",
|
||||
"integrity": "sha512-zrQmE8Wde5ITKhTpeYgJgb+QhFc8ySq/mFPfLq1+vO/xzvx5mg7v1OER1tMs7Byq3X/78euZCN4rxrvVRWQGNA==",
|
||||
"requires": {
|
||||
"axios": "^0.24.0"
|
||||
}
|
||||
},
|
||||
"argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.24.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
|
||||
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.4"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.14.7",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
|
||||
"integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ=="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"requires": {
|
||||
"argparse": "^2.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "@resilien/directus-to-markdown",
|
||||
"version": "0.1.0",
|
||||
"description": "Export Directus items to markdown files with assets",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.weko.io/resilien/directus-to-markdown.git"
|
||||
},
|
||||
"keywords": [
|
||||
"directus",
|
||||
"export",
|
||||
"markdown",
|
||||
"assets",
|
||||
"hugo"
|
||||
],
|
||||
"author": "RésiLien",
|
||||
"license": "ISC",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@directus/sdk": "^9.5.0",
|
||||
"js-yaml": "^4.1.0"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue