Use typescript for the project

This commit is contained in:
Artur Gurgul 2025-02-24 18:46:31 +01:00
parent 0f1d41a991
commit 83fd79f674
24 changed files with 2882 additions and 115 deletions

View file

@ -1,15 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
We are using Node.js <span id="node-version"></span>,
Chromium <span id="chrome-version"></span>,
and Electron <span id="electron-version"></span>.
</body>
</html>

View file

@ -1,34 +0,0 @@
import { app, BrowserWindow } from 'electron'
// import pkg from 'electron';
// console.log("package ", pkg)
// const { app, BrowserWindow } = pkg;
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600
})
//win.loadFile('index.html')
win.loadFile('/Users/agurgul/projs/home/docs/artur.gurgul.pro/.build/index.html')
}
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
// REPL
// { app } = await import('electron')
export function run() {
console.log("running")
}

30
src/index.ts Normal file
View file

@ -0,0 +1,30 @@
import { Command } from 'commander'
import chalk from 'chalk'
//import fs from 'fs-extra'
import path from 'path'
import { newProject, buildProject, appProject } from './project.js'
import { serve } from './serve.js'
const program = new Command()
program
.command('init')
.description('Initialize project in the current directory with the default theme')
.action(newProject)
program
.command('build')
.description('Build the webpage')
.action(buildProject)
program
.command('serve')
.description('Run the website locally')
.action(serve)
program
.command('app')
.description('Run notes as the the app')
.action(appProject)
program.parse(process.argv)

55
src/markdown.ts Normal file
View file

@ -0,0 +1,55 @@
import hljs from 'highlight.js'
import { marked } from 'marked'
import { markedHighlight } from 'marked-highlight'
import matter from 'gray-matter'
import fs from 'fs'
import path from 'path'
marked.use(markedHighlight({
langPrefix: 'hljs language-',
highlight: function(code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
}
}))
export function parseMD(file: string) {
const fileContents = fs.readFileSync(path.join("./", file), 'utf8')
const { data: metadata, content: markdownContent } = matter(fileContents)
const htmlContent = marked(markdownContent)
return {
meta: metadata,
content: htmlContent
}
}
const renderer = new marked.Renderer()
renderer.paragraph = (text) => {
return text.text
}
export function parseMarkdown(obj: any) {
for (let key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
if (Array.isArray(obj[key])) {
for (let i = 0; i < obj[key].length; i++) {
if (typeof obj[key][i] === 'object' && obj[key][i] !== null) {
parseMarkdown(obj[key][i]);
}
else if (typeof obj[key][i] === 'string') {
obj[key][i] = marked(obj[key][i], { renderer });
}
}
} else {
parseMarkdown(obj[key]);
}
} else if (typeof obj[key] === 'string') {
obj[key] = marked(obj[key], { renderer });
}
}
}

72
src/project.ts Normal file
View file

@ -0,0 +1,72 @@
import { fileURLToPath } from 'url'
import path from 'path'
import fs from 'fs'
import { cp } from './utils.js'
// Get the directory of the current file
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
// Path relative to the script file's directory
const DEFAULT_PROJECT_PATH = path.join(__dirname, 'empty')
export function newProject() {
console.log("Initialize a new project")
console.log(DEFAULT_PROJECT_PATH)
cp(DEFAULT_PROJECT_PATH, ".")
}
import { readConfig } from './site.js'
import { build } from './site.js'
export function buildProject() {
console.log("building")
let config = {
... readConfig(),
buildDir: './.build',
ignore: [".build", ".sajt"]
}
config.remote.port = 22
config.remote.privateKey = fs.readFileSync(path.resolve(process.env.HOME ?? "", '.ssh/id_rsa'))
build(config)
//loadTemplate()
//parseMD()
//buildProject(config)
}
//import { run } from './src/desktop/main.js'
import * as proc from 'child_process'
//import { app, BrowserWindow } from 'electron'
import * as electron from 'electron'
export function appProject() {
//run()
//const child = proc.spawn(electron, ["."])
// console.log(electron)
// console.log(electron.default)
// const child = proc.spawn(electron.default, [".build"])
// https://www.matthewslipper.com/2019/09/22/everything-you-wanted-electron-child-process.html
// exec('node start', (error, stdout, stderr) => {
// if (error) {
// console.error(`error: ${error.message}`)
// return;
// }
// if (stderr) {
// console.error(`stderr: ${stderr}`);
// return
// }
// console.log(`stdout:\n${stdout}`)
// })
}

17
src/serve.ts Normal file
View file

@ -0,0 +1,17 @@
import express from 'express'
import { setWatcher } from './watch.js'
const app = express()
const PORT = process.env.PORT || 3000
export function serve() {
app.use(express.static('./.build'))
setWatcher(url => {
console.log(url)
})
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`)
})
}

157
src/site.ts Executable file
View file

@ -0,0 +1,157 @@
import fs from 'fs'
import pug from 'pug'
import path from 'path'
import yaml from 'js-yaml'
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}`)
}
}
export function readConfig(): any {
const __dirname = process.cwd()
const configPath = path.join(__dirname, '.sajt/config.yaml')
const fileContents = fs.readFileSync(configPath, 'utf8')
return yaml.load(fileContents)
}
function compile(template: string, content: any, output: string) {
if (template == null) {
console.error("Template is not defined")
return
}
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 })
// sites needs to include data from header
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) {
//console.log(site.md.meta.path)
// TODO: data can set default path
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))
}
}
//console.log(readMetadata())
// sajt
// Not to upload now
//uploadDirectory(serverConfig, buildFolder)
}

36
src/ssh.ts Normal file
View file

@ -0,0 +1,36 @@
/*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}`)
}
}
}
*/

53
src/utils.ts Normal file
View file

@ -0,0 +1,53 @@
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) {
const items = fs.readdirSync(directory)
items.forEach(item => {
if(excludes.includes(item)) {
return
}
const itemPath = path.join(directory, item)
const stat = fs.statSync(itemPath)
if (stat.isDirectory()) {
readDirectory(itemPath)
} else if (path.extname(item) === extension) {
results.push(itemPath)
}
})
}
readDirectory(directory)
return results
}
// copyDirectory
export function cp(source: string, destination: string) {
fs.mkdirSync(destination, { recursive: true })
const items = fs.readdirSync(source)
items.forEach(item => {
const sourceItemPath = path.join(source, item)
const destinationItemPath = path.join(destination, item)
const stat = fs.statSync(sourceItemPath)
if (stat.isDirectory()) {
cp(sourceItemPath, destinationItemPath)
} else {
fs.copyFileSync(sourceItemPath, destinationItemPath)
}
})
}
export function pathToArray(filePath: string) {
// Normalize the file path to handle different OS path separators
const normalizedPath = path.normalize(filePath)
// Split the path into an array of directories
return normalizedPath.split(path.sep)
}

35
src/watch.ts Normal file
View file

@ -0,0 +1,35 @@
import fs from 'fs'
import path from 'path'
export function setWatcher(callback: (url: string) => void) {
// Directory to watch
const directoryPath = './';
// Ensure the directory exists
if (!fs.existsSync(directoryPath)) {
fs.mkdirSync(directoryPath, { recursive: true });
}
console.log(`Watching for changes in: ${directoryPath}`);
// Watch the directory for changes
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
fs.stat(fullPath, (err, stats) => {
if (err) {
if (err.code === 'ENOENT') {
console.log(`File ${filename} was deleted.`);
} else {
console.error(`Error checking file status: ${err.message}`);
}
} else {
if (stats.isFile()) {
console.log(`File ${filename} exists with size: ${stats.size} bytes`);
}
}
});
} else {
console.log('Filename not provided, but change detected.');
}
});
}