feat: Ajout de la CI

This commit is contained in:
Simon 2021-11-17 22:45:32 +01:00
parent 504549cd1b
commit fb4a449b11
9 changed files with 4956 additions and 8 deletions

107
.drone.yml Normal file
View File

@ -0,0 +1,107 @@
---
# drone encrypt weko/lestoitsduval $REGISTRY_PASSWORD
kind: secret
name: REGISTRY_PASSWORD
data: RrJX1Ir1Fqp83P43po3iQh4RE+SULN060LDe/PkEYw+mxPLrdDREEoQNKfh8RvAMjvocNojY9OuxXaZSpC72GOpi8qzslnHAbHhQfCX/
---
# drone encrypt weko/lestoitsduval $REGISTRY_USER
kind: secret
name: REGISTRY_USER
data: fyPAM/EECUhDI5XGhj3RbQWgzbGija2DAwepuAWmnEx1+A==
---
# drone encrypt weko/lestoitsduval "{\"auths\":{\"https://registry.weko.io\":{\"auth\":\"$(echo -n "$REGISTRY_USER:$REGISTRY_PASSWORD" | base64)\",\"email\":\"$REGISTRY_USER\"}}}"
kind: secret
name: REGISTRY_CONFIG
data: NsJ77R/8TEmyCM7h6OXkOeGhKAdctofukKezjfjIMGaBv5aGYN4CKYCIBlLsBWfFvMb+0SIDqrExyLw4i5dFWfDepwdNacJXNRmjIEkxuaJmvGxV8BZpcof28LjhB4psKrKjmTFpGioS3kG1MRfnljD/AlwckUd6S4KYNfGgJW0hQccxbamW9M0tqagPddayGEDghwxUfzqQk937rmOV7ngI+mon9f4DCWVA
---
# drone encrypt weko/lestoitsduval $DIRECTUS_URL
kind: secret
name: DIRECTUS_URL
data: pPaOgtpSRFhEZ6zq61OoN+gYxZ6nzg+FlmFYEhUq/GLoFXTwA3i+Yci7ZrwJUmhOpLtG
---
# drone encrypt weko/lestoitsduval $DIRECTUS_EMAIL
kind: secret
name: DIRECTUS_EMAIL
data: Egj2gQqz40mbxx9tFc7bfTOiUc36Dch+2Y4RjVEt9qpoL1y5d5KEpSE=
---
# drone encrypt weko/lestoitsduval $DIRECTUS_PASSWORD
kind: secret
name: DIRECTUS_PASSWORD
data: TC0fUdQ7NvL7Qri7SuqTK6JC9zbX0xVSyndHl/zbFlecARlc7Yzru65OO1fvf1VQxSejIVlPcyorZRiHepUeTnt0nEXTgDmO
---
kind: pipeline
type: docker
name: default
platform:
os: linux
arch: arm64
steps:
- name: install submodule
image: drone/git
commands:
- git config --global http.sslverify false
- git submodule update --init
- name: install npm
image: node:current-alpine
volumes:
- name: hugo-theme-directus-import_node_modules
path: /drone/src/themes/hugo-theme-directus-import/node_modules
- name: hugo-theme-lowtech_node_modules
path: /drone/src/themes/hugo-theme-lowtech/node_modules
environment:
DIRECTUS_URL:
from_secret: DIRECTUS_URL
DIRECTUS_EMAIL:
from_secret: DIRECTUS_EMAIL
DIRECTUS_PASSWORD:
from_secret: DIRECTUS_PASSWORD
commands:
- (cd themes/hugo-theme-lowtech && npm i)
- (cd themes/hugo-theme-directus-import && npm i)
- node themes/hugo-theme-directus-import/import.js
- name: build website
image: jakejarvis/hugo-extended
commands:
- hugo --minify --environment production
- name: typo
image: node:current-alpine
volumes:
- name: hugo-theme-lowtech_node_modules
path: /drone/src/themes/hugo-theme-lowtech/node_modules
commands:
- node themes/hugo-theme-lowtech/scripts/typo
- name: push docker image on registry
image: plugins/docker
settings:
username:
from_secret: REGISTRY_USER
password:
from_secret: REGISTRY_PASSWORD
repo: registry.weko.io/lestoitsduval
registry: registry.weko.io
tags:
- latest
image_pull_secrets:
- REGISTRY_CONFIG
volumes:
- name: hugo-theme-directus-import_node_modules
host:
path: /tmp/drone/cache/weko/lestoitsduval/themes/hugo-theme-directus-import
- name: hugo-theme-lowtech_node_modules
host:
path: /tmp/drone/cache/weko/lestoitsduval/themes/hugo-theme-lowtech

View File

@ -10,11 +10,13 @@ services:
ltdv-prod:
container_name: ltdv-prod
build: .
image: registry.weko.io/lestoitsduval
restart: always
labels:
traefik.enable: "true"
traefik.http.routers.ltdv-prod.rule: "Host(`${URL}`)"
traefik.http.routers.ltdv-prod.entrypoints: "web"
- "traefik.enable=true"
- "traefik.http.routers.ltdv-prod.rule=Host(`${URL}`)"
- "traefik.http.routers.ltdv-prod.entrypoints=web"
- "com.centurylinklabs.watchtower.enable=true"
volumes:
- ltdv-log:/var/log/nginx
- ltdv-stats:/usr/share/nginx/html/stats
@ -35,5 +37,4 @@ services:
networks:
default:
external:
name: traefik
name: traefik

View File

@ -2,8 +2,7 @@ version: "3.8"
networks:
default:
external:
name: traefik
name: traefik
services:
ltdv-staging:

View File

@ -0,0 +1 @@
node_modules

View File

@ -0,0 +1,23 @@
const DirectusHugoDriver = require('.');
const driverOptions = {
url: process.env.DIRECTUS_URL,
email: process.env.DIRECTUS_EMAIL,
password: process.env.DIRECTUS_PASSWORD,
buildDrafts: false,
collections: {
'lestoitsduval_actualites': {
pathBuilder: (path, article, urlslug) => {
if (article.slug) {
return `${path}/actualites/${article.slug}`
}
return `${path}/actualites/${article.date}-${urlslug(article.title, { remove: /\./g })}`;
}
}
},
content: {
path: './content'
}
}
const driver = new DirectusHugoDriver(driverOptions);
driver.import()

View File

@ -0,0 +1,244 @@
const util = require('util');
const DirectusSDK = require('@directus/sdk-js');
const yaml = require('js-yaml');
const urlslug = require('url-slug');
const fs = require('fs');
const mime = require('mime-types');
const express = require('express')
const bodyParser = require('body-parser');
const { exec } = require("child_process");
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}$/;
class Driver {
constructor(config = {}, directusConfig = {}){
this.url = config.url || 'http://localhost:8055';
this.email = config.email || '';
this.password = config.password || '';
this.frontMatter = (config.frontMatter && config.frontMatter.toUpperCase()) || 'YAML'; // TODO: Add TOML and JSON frontmatter support
this.collection = config.collection
this.collections = config.collections
this.content = {}
this.content.path = (config.content && config.content.path) ? config.content.path : 'content';
this.content.home = (config.content && config.content.home) ? config.content.home.toLowerCase() : 'home';
this.content.index = (config.content && config.content.index) ? config.content.index.toLowerCase() : 'index';
this.content.map = (config.content && config.content.map) ? config.content.map : [];
this.directus = new DirectusSDK(this.url, directusConfig);
this.pathMethod = config.pathBuilder || this._pathBuilder;
this._auth = null;
this.buildDrafts = config.buildDrafts || false;
// Auto-rebuild server related
this.buildPort = config.buildPort || 8060;
this.buildHost = config.buildHost || 'http://localhost';
this.autoWebhook = config.autoWebhook || true;
}
_checkAuth(){
return new Promise(resolve => {
if (this.email && this.password && this._auth === null){
this._auth = this.directus.auth.login({ email: this.email, password: this.password })
.then(() => {
console.info('Login with user', this.email);
resolve()
})
} else {
resolve()
}
})
}
static emptyPromise(){
return new Promise(resolve => resolve())
}
getCollections(){
return this.directus.collections.read()
// Let not following .then() confuse you. It is an arrow function running an arrow filter.
// The Directus internal system fields have "meta.system == true" and those we discard here.
// .then(data => {
// console.log(util.inspect(data, false, null, true /* enable colors */))
// return data
// })
.then(data => data.data.filter(collection => !collection.meta.system))
}
/**
* Build path string where the articles will be stored exlcluding individual article slug
* @param {*} article
* @param {*} collection
* @returns {string}
*/
_pathBuilder(article, collection){
console.log('article', article);
// console.log('collection', collection);
if (this.collections && this.collections[collection.collection] && this.collections[collection.collection].pathBuilder) {
return this.collections[collection.collection].pathBuilder(this.content.path, article, urlslug)
}
if (this._isHome(article, collection)) {
return `${this.content.path}`;
}
if (this._isBranch(article, collection) || this._isPage(article, collection)){
return `${this.content.path}/${collection.collection}`;
}
// console.log(article.date)
const datePrefix = article.date_created ? `${article.date_created.split('T')[0]}_` : '';
const [year, mouth, day] = article.date.split('-')
// console.log(year)
// return `${this.content.path}/${collection.collection}/${article.id}_${datePrefix}${urlslug(article.title, { remove: /\./g })}`;
return `${this.content.path}/actualites/${year}/${mouth}/${article.date.replaceAll('-', '_')}-${urlslug(article.title, { remove: /\./g })}`;
}
_isHome(article, collection){
return (collection.meta.singleton === true && collection.collection === this.content.home)
}
_isBranch(article, collection){
return (collection.meta.singleton === false && article.title.toLowerCase() === this.content.index)
}
// eslint-disable-next-line class-methods-use-this
_isPage(article, collection){
return (collection.meta.singleton === true)
}
_formatFrontMatter(article, collection = {}){
let front = { ...article };
// Manipulate some variable property names
if (front.date_created){
front.date = front.date_created
delete front.date_created
}
if (front.date_updated){
front.lastmod = front.date_updated
delete front.date_updated
}
delete front.body
// Finally, transform from object to string
if (this.frontMatter === 'YAML'){
front = `---\r\n${yaml.safeDump(front).trim()}\r\n---\r\n`
}
return front
}
static _writeFileStream(path, readstream, passtrough = {}){
return new Promise((resolve, reject) => {
const fileWriter = fs.createWriteStream(path);
fileWriter.on('finish', () => {
resolve(passtrough)
});
fileWriter.on('error', error => {
console.log(error)
fileWriter.close();
reject(error);
});
readstream.pipe(fileWriter);
});
}
/**
*
* @param {object} article
* @param {object} collection
* @returns {promise}
*/
_importItem(originArticle, collection){
const article = { ...originArticle }
const itemPath = this.pathMethod(article, collection);
const indexName = this._isHome(article, collection) || this._isBranch(article, collection) || this._isPage(article, collection) ? '_index' : 'index'
// Only continue if this is a published article or we have explicitly set to import Drafts
// Archived items would be always discarded
if ((article.status === 'archived') || (article.draft === 'true' && this.buildDrafts === false)){
return Driver.emptyPromise();
}
// if ((article.status !== 'published') && (article.status === 'draft' && this.buildDrafts === false)){
// return Driver.emptyPromise();
// }
if (!fs.existsSync(itemPath)){
fs.mkdirSync(itemPath, { recursive: true });
}
const writePromises = []
for (const [key, value] of Object.entries(article)){
// TODO: instead of trying to pull asset and hope for the best,
// crosscheck things with the /fields and play by the rules
if (typeof value === 'string' && value.match(uuidregex)) {
// console.log([key, value]);
const downloadPromise = this.directus.axios.get(`assets/${value}?download`, { responseType: 'stream' })
.then(response => {
const disposition = response.headers['content-disposition'].match(/filename="(.*)"/);
const downloadName = disposition ? disposition[1] : `${value}.${mime.extension(response.headers['content-type'])}`;
const savePath = `${itemPath}/${downloadName}`;
return Driver._writeFileStream(savePath, response.data, { key, downloadName })
})
.then(passtrough => {
article[passtrough.key] = passtrough.downloadName
})
.catch(error => {
if (error.response && error.response.status && error.response.status !== 403) {
console.error(error.response.status, error.response.statusText)
}
});
writePromises.push(downloadPromise);
}
}
return Promise.all(writePromises).then(() => {
const frontMatter = this._formatFrontMatter(article, collection);
const itemContent = `${frontMatter}${article.body ? article.body.toString() : ''}`
fs.writeFileSync(`${itemPath}/${indexName}.md`, itemContent)
})
}
_importCollection(collection){
const col = this.directus.items(collection.collection).read() // TODO: IMPORTANT handle pagination when a lot of content in CMS
return col
.then(data => {
// normalize to always have an array of content.
const content = (collection.meta.singleton === true) ? [data.data] : data.data;
return content.map(article => this._importItem(article, collection))
})
}
import() {
console.info('Let\'s import from', this.url);
this._checkAuth()
.then(() => this.getCollections())
.then(collections => {
if (this.collections) {
return collections.filter(collection => Object.keys(this.collections).includes(collection.collection))
}
return collections
})
.then(collections => {
return collections.map(collection => this._importCollection(collection))
})
.then(collectionPromises => {
return Promise.all(collectionPromises)
})
.catch(error => {
console.log(error)
})
}
}
module.exports = Driver

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,41 @@
{
"name": "directus-hugo-driver",
"version": "0.0.0",
"description": "Translates content from Directus.io to gohugo.io",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pilskalns/directus-hugo-driver.git"
},
"keywords": [
"directus",
"hugo",
"npm",
"cms",
"headless",
"headless-cms",
"automation"
],
"author": "Andžs Pilskalns",
"license": "ISC",
"bugs": {
"url": "https://github.com/pilskalns/directus-hugo-driver/issues"
},
"homepage": "https://github.com/pilskalns/directus-hugo-driver#readme",
"dependencies": {
"@directus/sdk-js": "^9.0.0-rc.32",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"js-yaml": "^3.14.1",
"mime-types": "^2.1.28",
"url-slug": "^3.0.1"
},
"devDependencies": {
"eslint": "^7.18.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.22.1"
}
}

@ -1 +1 @@
Subproject commit 4675c3a10fc35c23c01186faa7f7ccc6dd7d2e72
Subproject commit 437ab76c51a94e646333f77162934b147b5163ac