Refactoring
This commit is contained in:
parent
1e3833f7d0
commit
aa563e60ea
37 changed files with 472 additions and 2392 deletions
|
@ -1,28 +1,32 @@
|
|||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import yaml from 'js-yaml'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
export default class ProjectConfig {
|
||||
public config: any = {}
|
||||
public config: any
|
||||
|
||||
private __filename: string
|
||||
private __dirname: string
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
public load() {
|
||||
this.config = {
|
||||
this.config = {
|
||||
... this.read(),
|
||||
buildDir: './.build',
|
||||
ignore: [".build", ".sajt"]
|
||||
}
|
||||
|
||||
this.config.remote.port = 22
|
||||
this.config.remote.privateKey = fs.readFileSync(path.resolve(process.env.HOME ?? "", '.ssh/id_rsa'))
|
||||
this.__filename = fileURLToPath(import.meta.url)
|
||||
this.__dirname = path.dirname(this.__filename)
|
||||
this.config.defaultProjectPath = path.join(this.__dirname, '..', 'empty')
|
||||
}
|
||||
|
||||
read(): any {
|
||||
const __dirname = process.cwd()
|
||||
const configPath = path.join(__dirname, '.sajt/config.yaml')
|
||||
if (!fs.existsSync(configPath)) {
|
||||
return {}
|
||||
}
|
||||
const fileContents = fs.readFileSync(configPath, 'utf8')
|
||||
return yaml.load(fileContents)
|
||||
}
|
|
@ -1,12 +1,8 @@
|
|||
import { Command } from 'commander'
|
||||
import chalk from 'chalk'
|
||||
//import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import Project from './project.js'
|
||||
import { serve } from './serve.js'
|
||||
|
||||
const program = new Command()
|
||||
|
||||
let project = new Project()
|
||||
|
||||
program
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import Parser , {File} from "./renderer.js"
|
||||
|
||||
import hljs from 'highlight.js'
|
||||
import { marked } from 'marked'
|
||||
import { markedHighlight } from 'marked-highlight'
|
||||
|
@ -13,15 +15,19 @@ marked.use(markedHighlight({
|
|||
}
|
||||
}))
|
||||
|
||||
export function parseMD(file: string) {
|
||||
const fileContents = fs.readFileSync(path.join("./", file), 'utf8')
|
||||
export default class Markdown implements Parser {
|
||||
name = "md"
|
||||
|
||||
loadAsHTML(filePath: string): File {
|
||||
const fileContents = fs.readFileSync(path.join("./", filePath), 'utf8')
|
||||
const { data: metadata, content: markdownContent } = matter(fileContents)
|
||||
const htmlContent = marked(markdownContent)
|
||||
const htmlContent = marked(markdownContent, {async: false})
|
||||
|
||||
return {
|
||||
meta: metadata,
|
||||
content: htmlContent
|
||||
data: metadata,
|
||||
html: htmlContent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const renderer = new marked.Renderer()
|
||||
|
|
50
src/page.ts
Normal file
50
src/page.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
import Renderer , {File} from "./renderer.js"
|
||||
import path from 'path'
|
||||
import utils from './utils.js'
|
||||
|
||||
export default class Page {
|
||||
file: File
|
||||
|
||||
/// /example/path
|
||||
dir: string[]
|
||||
|
||||
/// /example/path/file
|
||||
path: string
|
||||
|
||||
/// /example/path/file.html
|
||||
finalPath: string
|
||||
|
||||
/// file.html
|
||||
fileName: string
|
||||
|
||||
hidden: boolean
|
||||
title: string
|
||||
layout: string
|
||||
|
||||
constructor(filePath: string, renderer: Renderer) {
|
||||
this.file = renderer.loadAsHTML(filePath)
|
||||
|
||||
if (this.file.data?.path != null && this.file.data?.path != undefined) {
|
||||
this.path = path.join("/", this.file.data?.path)
|
||||
} else {
|
||||
const parsedPath = path.parse(filePath)
|
||||
const basePath = path.join("/", parsedPath.dir, parsedPath.name)
|
||||
this.path = basePath
|
||||
}
|
||||
|
||||
const parsedPath = path.parse(this.path)
|
||||
parsedPath.ext = '.html'
|
||||
parsedPath.base = `${parsedPath.name}${parsedPath.ext}`
|
||||
this.finalPath = path.format(parsedPath)
|
||||
|
||||
const dirArray = utils.pathToArray(this.finalPath)
|
||||
this.fileName = dirArray.pop() || ""
|
||||
dirArray.shift()
|
||||
this.dir = dirArray
|
||||
this.hidden = this.file.data?.hidden || false
|
||||
|
||||
// TODO: if tilte do not exists search in markdown for # title
|
||||
this.title = this.file.data.title
|
||||
this.layout = this.file.data.layout || "default"
|
||||
}
|
||||
}
|
|
@ -1,41 +1,83 @@
|
|||
import { fileURLToPath } from 'url'
|
||||
|
||||
import path from 'path'
|
||||
import { cp } from './utils.js'
|
||||
import { build } from './site.js'
|
||||
import ProjectConfig from './project-config.js'
|
||||
import ProjectConfig from './config.js'
|
||||
|
||||
|
||||
import fs from 'fs'
|
||||
import pug from 'pug'
|
||||
|
||||
import utils from "./utils.js"
|
||||
|
||||
import Markdown from './markdown.js'
|
||||
import Yaml from './yaml.js'
|
||||
import Page from './page.js'
|
||||
|
||||
export default class Project {
|
||||
public config = new ProjectConfig() // should be injected
|
||||
private __filename: string
|
||||
private __dirname: string
|
||||
private DEFAULT_PROJECT_PATH: string
|
||||
private config = new ProjectConfig()
|
||||
pages: Page[]
|
||||
|
||||
constructor() {
|
||||
// Get the directory of the current file
|
||||
this.__filename = fileURLToPath(import.meta.url)
|
||||
this.__dirname = path.dirname(this.__filename)
|
||||
|
||||
// Path relative to the script file's directory
|
||||
this.DEFAULT_PROJECT_PATH = path.join(this.__dirname, '..', 'empty')
|
||||
this.pages = this.loadPages()
|
||||
|
||||
console.log(this.pages.map(it=> {return {dir: it.dir, file: it.finalPath}}))
|
||||
}
|
||||
|
||||
new() {
|
||||
console.log("Initialize a new project")
|
||||
console.log(this.DEFAULT_PROJECT_PATH)
|
||||
cp(this.DEFAULT_PROJECT_PATH, ".")
|
||||
|
||||
console.log(this.config)
|
||||
this.config.load()
|
||||
}
|
||||
|
||||
existing() {
|
||||
this.config.load()
|
||||
cp(this.config.config.defaultProjectPath, ".")
|
||||
}
|
||||
|
||||
build() {
|
||||
this.config.load()
|
||||
build(this.config.config)
|
||||
|
||||
utils.removeDirectorySync(this.config.config.buildDir)
|
||||
utils.cp("./.sajt/static", path.join(this.config.config.buildDir, "static"))
|
||||
|
||||
for(const page of this.pages) {
|
||||
this.compile(page)
|
||||
}
|
||||
}
|
||||
|
||||
loadPages(): Page[] {
|
||||
const mdParser = new Markdown()
|
||||
const yamlParser = new Yaml()
|
||||
|
||||
let listMD = utils.getAllFilesWithExtension('.',".md", this.config.config.ignore)
|
||||
.map(path => new Page(path, mdParser))
|
||||
|
||||
let listYML = utils.getAllFilesWithExtension('.',".yml", this.config.config.ignore)
|
||||
.map(path => new Page(path, yamlParser))
|
||||
|
||||
return listMD.concat(listYML)
|
||||
}
|
||||
|
||||
compile(page: Page) {
|
||||
const output = path.join(this.config.config.buildDir, page.finalPath)
|
||||
|
||||
const compiledFunction = pug.compileFile(`.sajt/layouts/${page.layout}.pug`)
|
||||
|
||||
const data = {
|
||||
page,
|
||||
context: this.context(page)
|
||||
}
|
||||
|
||||
const dirname = path.dirname(output)
|
||||
if (!fs.existsSync(dirname)) {
|
||||
fs.mkdirSync(dirname, { recursive: true })
|
||||
}
|
||||
|
||||
const html = compiledFunction(data)
|
||||
fs.writeFileSync(output, html)
|
||||
console.log(`> ${output}`)
|
||||
}
|
||||
|
||||
context(page: Page): any {
|
||||
return {
|
||||
pages: this.pages.map(it => { return {
|
||||
title: it.title,
|
||||
isCurrent: page == it,
|
||||
url: it.finalPath,
|
||||
isDir: false
|
||||
}})
|
||||
}
|
||||
}
|
||||
}
|
10
src/renderer.ts
Normal file
10
src/renderer.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
export interface File {
|
||||
get html(): string
|
||||
get data(): { [key: string]: any }
|
||||
}
|
||||
|
||||
export default interface Renderer {
|
||||
get name(): string
|
||||
loadAsHTML(path: string): File
|
||||
}
|
|
@ -1,15 +1,11 @@
|
|||
import express from 'express'
|
||||
import { setWatcher } from './watch.js'
|
||||
//import { webdavMiddleware } from "./webdav.js"
|
||||
|
||||
const app = express()
|
||||
const PORT = process.env.PORT || 3000
|
||||
|
||||
export function serve() {
|
||||
app.use(express.static('./.build'))
|
||||
|
||||
//app.use(webdavMiddleware)
|
||||
|
||||
setWatcher(url => {
|
||||
console.log(url)
|
||||
})
|
||||
|
|
132
src/site.ts
132
src/site.ts
|
@ -1,132 +0,0 @@
|
|||
import fs from 'fs'
|
||||
import pug from 'pug'
|
||||
import path from 'path'
|
||||
|
||||
import { cp } from "./utils.js"
|
||||
import { parseMarkdown } from './markdown.js'
|
||||
import { getAllFilesWithExtension, pathToArray, parseYML } from './utils.js'
|
||||
import { parseMD } from './markdown.js'
|
||||
|
||||
function removeDirectorySync(directory: string) {
|
||||
try {
|
||||
fs.rmSync(directory, { recursive: true, force: true })
|
||||
console.log("Directory and its contents removed.")
|
||||
} catch (err: any) {
|
||||
console.error(`Error removing directory: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
function compile(template: string, content: any, output: string) {
|
||||
if (template == null) {
|
||||
console.error("Template is not defined, loading the default")
|
||||
template = "default"
|
||||
}
|
||||
const compiledFunction = pug.compileFile(`.sajt/layouts/${template}.pug`);
|
||||
const data = {
|
||||
...content,
|
||||
site: {posts: []}
|
||||
}
|
||||
|
||||
const dirname = path.dirname(output)
|
||||
if (!fs.existsSync(dirname)) {
|
||||
fs.mkdirSync(dirname, { recursive: true })
|
||||
}
|
||||
|
||||
const html = compiledFunction(data)
|
||||
fs.writeFileSync(output, html)
|
||||
console.log(`HTML has been rendered and saved to ${output}`);
|
||||
}
|
||||
|
||||
function compileData(template: string, content: object, output: string) {
|
||||
const compiledFunction = pug.compileFile(`.sajt/layouts/${template}.pug`)
|
||||
|
||||
const dirname = path.dirname(output)
|
||||
if (!fs.existsSync(dirname)) {
|
||||
fs.mkdirSync(dirname, { recursive: true })
|
||||
}
|
||||
|
||||
const html = compiledFunction(content)
|
||||
fs.writeFileSync(output, html)
|
||||
console.log(`HTML has been rendered and saved to ${output}`);
|
||||
}
|
||||
|
||||
function readMetadata(ignore: string[]) {
|
||||
let htmlExtension = "html"
|
||||
|
||||
let listMD = getAllFilesWithExtension('.',".md", ignore)
|
||||
.map(f => { return {
|
||||
pathMD: f,
|
||||
type: "md",
|
||||
data: {} as any,
|
||||
md: parseMD(f)
|
||||
} as any })
|
||||
|
||||
let listYML = getAllFilesWithExtension('.',".yml", ignore)
|
||||
.map(f => { return {
|
||||
pathMD: f,
|
||||
type: "yml",
|
||||
data: parseYML(f),
|
||||
md: {meta: {}}
|
||||
} as any })
|
||||
|
||||
let list = listMD.concat(listYML)
|
||||
|
||||
for(const site of list) {
|
||||
if (site.md.meta?.path != null && site.md.meta?.path != undefined) {
|
||||
site.path = path.join("/", site.md.meta.path)
|
||||
} else {
|
||||
const parsedPath = path.parse(site.pathMD)
|
||||
const basePath = path.join("/", parsedPath.dir, parsedPath.name)
|
||||
site.path = basePath
|
||||
}
|
||||
|
||||
// add proper extension
|
||||
const parsedPath = path.parse(site.path)
|
||||
parsedPath.ext = htmlExtension.startsWith('.') ? htmlExtension : `.${htmlExtension}`
|
||||
parsedPath.base = `${parsedPath.name}${parsedPath.ext}`
|
||||
site.path = path.format(parsedPath)
|
||||
|
||||
// add dirs metadata
|
||||
const dirArray = pathToArray(site.path)
|
||||
site.fileName = dirArray.pop()
|
||||
dirArray.shift()
|
||||
site.dir = dirArray
|
||||
site.meta = site.md.meta
|
||||
site.hidden = site.data?.hidden || false
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
export function build(config: any) {
|
||||
removeDirectorySync(config.buildDir)
|
||||
cp("./.sajt/static", path.join(config.buildDir, "static"))
|
||||
|
||||
let data = readMetadata(config.ignore)
|
||||
let pages = data.map(site => {
|
||||
return {
|
||||
title: site.meta.title,
|
||||
url: site.path
|
||||
}
|
||||
})
|
||||
|
||||
for(const site of data) {
|
||||
if (site.type == "md") {
|
||||
compile(site.meta.layout,
|
||||
{
|
||||
content: site.md.content,
|
||||
title: site.meta.title,
|
||||
hidden: false,
|
||||
pages
|
||||
},
|
||||
path.join(config.buildDir, site.path))
|
||||
} else if (site.type == "yml") {
|
||||
let data = {...site.data}
|
||||
delete data.layout
|
||||
parseMarkdown(data)
|
||||
compileData(site.data.layout,
|
||||
{data, pages, hidden: data.hidden},
|
||||
path.join(config.buildDir, site.path))
|
||||
}
|
||||
}
|
||||
}
|
36
src/ssh.ts
36
src/ssh.ts
|
@ -1,36 +0,0 @@
|
|||
/*const Client = require('ssh2-sftp-client')
|
||||
|
||||
async function uploadDirectory(serverConfig, localDirPath) {
|
||||
const sftp = new Client()
|
||||
await sftp.connect(serverConfig)
|
||||
try {
|
||||
await upload(sftp, config, localDirPath, serverConfig.path)
|
||||
} catch (err) {
|
||||
console.error(`Error: ${err.message}`)
|
||||
} finally {
|
||||
await sftp.end()
|
||||
console.log('Connection closed')
|
||||
}
|
||||
}
|
||||
|
||||
async function upload(sftp, config, localPath, remotePath) {
|
||||
|
||||
console.log('Connected to the server')
|
||||
|
||||
const files = fs.readdirSync(localPath)
|
||||
|
||||
for (const file of files) {
|
||||
const localFilePath = path.join(localPath, file)
|
||||
const remoteFilePath = `${remotePath}/${file}`
|
||||
|
||||
if (fs.statSync(localFilePath).isDirectory()) {
|
||||
await sftp.mkdir(remoteFilePath, true)
|
||||
await upload(sftp, config, localFilePath, remoteFilePath)
|
||||
} else {
|
||||
const fileContent = fs.readFileSync(localFilePath)
|
||||
await sftp.put(Buffer.from(fileContent), remoteFilePath)
|
||||
console.log(`File transferred successfully: ${localFilePath}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
28
src/utils.ts
28
src/utils.ts
|
@ -1,12 +1,7 @@
|
|||
import yaml from 'js-yaml'
|
||||
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export function parseYML(file: string) {
|
||||
const fileContents = fs.readFileSync(file, 'utf8')
|
||||
return yaml.load(fileContents)
|
||||
}
|
||||
|
||||
export function getAllFilesWithExtension(directory: string, extension: string, excludes: string[]): string[] {
|
||||
let results: string[] = []
|
||||
function readDirectory(directory: string) {
|
||||
|
@ -29,7 +24,6 @@ export function getAllFilesWithExtension(directory: string, extension: string, e
|
|||
return results
|
||||
}
|
||||
|
||||
// copyDirectory
|
||||
export function cp(source: string, destination: string) {
|
||||
fs.mkdirSync(destination, { recursive: true })
|
||||
const items = fs.readdirSync(source)
|
||||
|
@ -45,9 +39,23 @@ export function cp(source: string, destination: string) {
|
|||
})
|
||||
}
|
||||
|
||||
export function pathToArray(filePath: string) {
|
||||
// Normalize the file path to handle different OS path separators
|
||||
function removeDirectorySync(directory: string) {
|
||||
try {
|
||||
fs.rmSync(directory, { recursive: true, force: true })
|
||||
console.log("Directory and its contents removed.")
|
||||
} catch (err: any) {
|
||||
console.error(`Error removing directory: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
export function pathToArray(filePath: string):string[] {
|
||||
const normalizedPath = path.normalize(filePath)
|
||||
// Split the path into an array of directories
|
||||
return normalizedPath.split(path.sep)
|
||||
}
|
||||
|
||||
export default {
|
||||
pathToArray,
|
||||
cp,
|
||||
getAllFilesWithExtension,
|
||||
removeDirectorySync
|
||||
}
|
27
src/watch.ts
27
src/watch.ts
|
@ -2,34 +2,31 @@ import fs from 'fs'
|
|||
import path from 'path'
|
||||
|
||||
export function setWatcher(callback: (url: string) => void) {
|
||||
// Directory to watch
|
||||
const directoryPath = './';
|
||||
// Ensure the directory exists
|
||||
const directoryPath = './'
|
||||
if (!fs.existsSync(directoryPath)) {
|
||||
fs.mkdirSync(directoryPath, { recursive: true });
|
||||
fs.mkdirSync(directoryPath, { recursive: true })
|
||||
}
|
||||
console.log(`Watching for changes in: ${directoryPath}`);
|
||||
// Watch the directory for changes
|
||||
console.log(`Watching for changes in: ${directoryPath}`)
|
||||
|
||||
fs.watch(directoryPath, (eventType, filename) => {
|
||||
if (filename) {
|
||||
const fullPath = path.join(directoryPath, filename);
|
||||
console.log(`File ${filename} has been ${eventType}`);
|
||||
// Check if the file was added, changed, or deleted
|
||||
const fullPath = path.join(directoryPath, filename)
|
||||
console.log(`File ${filename} has been ${eventType}`)
|
||||
fs.stat(fullPath, (err, stats) => {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
console.log(`File ${filename} was deleted.`);
|
||||
console.log(`File ${filename} was deleted.`)
|
||||
} else {
|
||||
console.error(`Error checking file status: ${err.message}`);
|
||||
console.error(`Error checking file status: ${err.message}`)
|
||||
}
|
||||
} else {
|
||||
if (stats.isFile()) {
|
||||
console.log(`File ${filename} exists with size: ${stats.size} bytes`);
|
||||
console.log(`File ${filename} exists with size: ${stats.size} bytes`)
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
} else {
|
||||
console.log('Filename not provided, but change detected.');
|
||||
console.log('Filename not provided, but change detected.')
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
import { v2 as webdav } from 'webdav-server';
|
||||
|
||||
// 1. Create a user manager and add a user
|
||||
const userManager = new webdav.SimpleUserManager()
|
||||
const user = userManager.addUser('user', 'password', false)
|
||||
|
||||
// 2. Create a privilege manager
|
||||
const privilegeManager = new webdav.SimplePathPrivilegeManager()
|
||||
|
||||
// 3. Configure the WebDAV server
|
||||
const server = new webdav.WebDAVServer({
|
||||
// HTTP Digest authentication for better security
|
||||
httpAuthentication: new webdav.HTTPDigestAuthentication(userManager, 'default-realm'),
|
||||
privilegeManager
|
||||
})
|
||||
|
||||
// 4. Set up a physical file system folder (e.g., `./data`) as the root
|
||||
const publicFileSystem = new webdav.PhysicalFileSystem('./')
|
||||
|
||||
server.setFileSystem('/', publicFileSystem, (success) => {
|
||||
if (!success) {
|
||||
console.error('Failed to set file system')
|
||||
process.exit(1)
|
||||
}
|
||||
// Give the user all permissions on the root
|
||||
privilegeManager.setRights(user, '/', ['all'])
|
||||
});
|
||||
|
||||
export const webdavMiddleware = webdav.extensions.express("/", server)
|
||||
*/
|
20
src/yaml.ts
Normal file
20
src/yaml.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import yaml from 'js-yaml'
|
||||
import Renderer , {File} from "./renderer.js"
|
||||
import fs from 'fs'
|
||||
|
||||
export default class Yaml implements Renderer {
|
||||
name = "yml"
|
||||
|
||||
loadAsHTML(file: string): File {
|
||||
const fileContents = fs.readFileSync(file, 'utf8')
|
||||
|
||||
// let data = {...site.data}
|
||||
// delete data.layout
|
||||
// parseMarkdown(data)
|
||||
|
||||
return {
|
||||
data: yaml.load(fileContents) as { [key: string]: any },
|
||||
html: ""
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue