feat: Create library

This commit is contained in:
Simon 2022-01-27 12:41:28 +01:00
commit 4fe23e688f
5 changed files with 317 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

91
README.md Normal file
View File

@ -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();
```

94
index.js Normal file
View File

@ -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)
}
}
}
}

104
package-lock.json generated Normal file
View File

@ -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"
}
}
}
}

27
package.json Normal file
View File

@ -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"
}
}