Skip to content
Closed
4 changes: 1 addition & 3 deletions packages/nuedom/src/compiler/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const RE_FN = /(script|h_fn|fn):\s*(['"`])([^\2]*?)\2/g

export function compileJS(js) {
return js.replace(RE_FN, function(_, key, __, expr) {
return key == 'script' ? `${key}: function() { ${expr.trim().replaceAll('\\n', '\n')} \n\t\t}`
return key == 'script' ? `${key}: function() { ${expr.trim().replace(/\\r/g, '').replace(/\\n/g, '\n')} \n\t\t}`
: `${key}: ${compileFn(expr, key[0] == 'h')}`
})
}
Expand All @@ -48,5 +48,3 @@ export function compileFn(str, is_event) {
}
return '_=>' + (is_simple ? str : `(${str})`)
}


9 changes: 7 additions & 2 deletions packages/nuekit/src/cmd/build.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

import { mkdir, rmdir, writeFile, unlink } from 'node:fs/promises'
import { join, sep } from 'node:path'
import path, { join } from 'node:path'
import { tmpdir } from 'os'

import { generateSitemap, generateFeed } from '../render/feed'
Expand Down Expand Up @@ -55,7 +55,12 @@ export async function buildAll(subset, args) {
await createSystemFiles(dist, init)

// build files
subset = subset.filter(el => !el.is_yaml && !el.dir.startsWith(`@shared${sep}data`))
const SHARED_DATA_DIR = `@shared/data`;

subset = subset.filter(el => {
const dir = el.dir ? path.posix.normalize(el.dir) : undefined
return !el.is_yaml && !dir?.startsWith(SHARED_DATA_DIR);
});

await Promise.all(subset.map(async asset => {
try {
Expand Down
23 changes: 19 additions & 4 deletions packages/nuekit/src/cmd/serve.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

import { extname, join } from 'node:path'
import path, { extname, join } from 'node:path'

import { generateSitemap, generateFeed } from '../render/feed'
import { createServer, broadcast } from '../tools/server'
Expand Down Expand Up @@ -59,6 +59,20 @@ export async function serve(site, { silent }) {
// server requests
export async function onServe(url, assets, opts={}) {
const { params={}, conf={} } = opts

// Redirect to trailing slash if it's a directory
if (!url.endsWith('/') && !extname(url)) {
const basePath = url.slice(1);
const htmlPath = basePath ? `${basePath}/index.html` : 'index.html';
const mdPath = basePath ? `${basePath}/index.md` : 'index.md';

const indexAsset = assets.find(asset => asset.path === htmlPath || asset.path === mdPath);

if (indexAsset) {
return { redirect: url + '/' };
}
}

const asset = findAssetByURL(url, assets)
const ext = extname(url)

Expand Down Expand Up @@ -104,9 +118,10 @@ export async function onServe(url, assets, opts={}) {

const sysfiles = getSystemFiles()

export function findAssetByURL(url, assets=[]) {
export function findAssetByURL(url, assets = []) {
const targetPath = url.endsWith('.html.js') ? url.slice(1, -3) : url
return [...sysfiles, ...assets].find(asset => {
return url.endsWith('.html.js') ? asset.path == url.slice(1, -3)
: asset.url == url
const assetPath = asset.path ? path.posix.normalize(asset.path) : undefined
return assetPath === targetPath || asset.url === url
})
}
12 changes: 6 additions & 6 deletions packages/nuekit/src/deps.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@

import { join, normalize, dirname, extname, basename, sep } from 'node:path'
import { posix, join, dirname, extname, basename } from 'node:path'

// app, lib, server are @shared, but not auto-included
const AUTO_INCLUDED = ['data', 'design', 'ui'].map(dir => join('@shared', dir))
const AUTO_INCLUDED = ['data', 'design', 'ui'].map(dir => posix.join('@shared', dir))

const ASSET_TYPES = ['.html', '.js', '.ts', '.yaml', '.css']

Expand Down Expand Up @@ -40,12 +40,12 @@ function isDep(page_path, asset_path, all_paths) {
if (dir == '.') return true

// shared dir -> auto-included
if (AUTO_INCLUDED.some(dir => asset_path.startsWith(dir + sep))) return true
if (AUTO_INCLUDED.some(dir => asset_path.startsWith(dir + '/'))) return true

// SPA: entire app tree
if (basename(page_path) == 'index.html') {
const dir = dirname(page_path)
return dir == '.' ? !all_paths.some(el => extname(el) == '.md') : asset_path.startsWith(dir + sep)
return dir == '.' ? !all_paths.some(el => extname(el) == '.md') : asset_path.startsWith(dir + '/')
}

// index.md -> home dir
Expand All @@ -58,14 +58,14 @@ function isDep(page_path, asset_path, all_paths) {

// check if asset is in ui of any parent directory
return page_dirs.some(pageDir => {
const ui_dir = pageDir ? join(pageDir, 'ui') : 'ui'
const ui_dir = pageDir ? posix.join(pageDir, 'ui') : 'ui'
return asset_dir == ui_dir || asset_dir == pageDir
})
}


// parseDirs('a/b/c') --> ['a', 'a/b', 'a/b/c']
export function parseDirs(dir) {
const els = normalize(dir).split(sep)
const els = dir.split('/')
return els.map((el, i) => els.slice(0, i + 1).join('/'))
}
18 changes: 9 additions & 9 deletions packages/nuekit/src/file.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@

import { parse, sep, join } from 'node:path'
import { posix, parse } from 'node:path'
import { lstat } from 'node:fs/promises'

export async function createFile(root, path) {
try {
const rootpath = join(root, path)
const rootpath = posix.join(root, path)
const stat = await lstat(rootpath)
const info = getFileInfo(path)
const file = Bun.file(rootpath)
Expand All @@ -24,19 +24,19 @@ export async function createFile(root, path) {
}

async function copy(dist) {
const to = join(dist, path)
const to = posix.join(dist, path)
await Bun.write(to, file)
return to
}

async function write(dist, content, ext) {
const toname = ext ? info.base.replace(info.ext, ext) : info.base
const to = join(dist, info.dir, toname)
const to = posix.join(dist, info.dir, toname)
await Bun.write(to, content)
return to
}

return { ...info, rootpath, mtime, text, copy, write, flush }
return { ...info, rootpath: posix.normalize(rootpath), mtime, text, copy, write, flush }

} catch (error) {
console.warn(`Warning: Error reading ${path}: ${error.message}`)
Expand All @@ -45,17 +45,17 @@ export async function createFile(root, path) {
}

export function getFileInfo(path) {
const info = parse(path)
const info = parse(posix.normalize(path))
delete info.root

const { ext, dir } = info
const type = info.ext.slice(1)
const url = getURL(info)
const slug = getSlug(info)

if (dir.includes(sep)) info.basedir = dir.split(sep)[0]
if (dir.includes('/')) info.basedir = dir.split('/')[0]

return { ...info, path, type, url, slug, [`is_${type}`]: true }
return { ...info, path: posix.normalize(path), type, url, slug, [`is_${type}`]: true }
}

export function getURL(file) {
Expand All @@ -67,7 +67,7 @@ export function getURL(file) {
}

if (ext == '.ts') ext = '.js'
const els = dir.split(sep)
const els = dir ? dir.split('/') : []
els.push(name + ext)

return `/${ els.join('/') }`.replace('//', '/')
Expand Down
11 changes: 7 additions & 4 deletions packages/nuekit/src/site.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

import { parse, sep } from 'node:path'
import { posix, parse } from 'node:path'
import { fswalk } from './tools/fswalk'
import { createAsset } from './asset'
import { createFile } from './file'
Expand Down Expand Up @@ -71,7 +71,12 @@ export function sortAssets(items) {


export async function mergeSharedData(assets, data={}) {
const shared = assets.filter(a => a.dir?.startsWith(`@shared${sep}data`))
const SHARED_DATA_DIR = '@shared/data';

const shared = assets.filter(a => {
const dir = a.dir ? posix.normalize(a.dir) : null
return dir?.startsWith(SHARED_DATA_DIR);
});
const statics = shared.filter(f => f.is_json || f.is_yaml)

const dataset = await Promise.all(statics.map(f => f.parse()))
Expand All @@ -88,5 +93,3 @@ export async function mergeSharedData(assets, data={}) {

return data
}


4 changes: 2 additions & 2 deletions packages/nuekit/src/tools/fswalk.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

import { readdir, stat } from 'node:fs/promises'
import { parse, join, relative } from 'node:path'
import { parse, join, relative, sep } from 'node:path'

export function matches(path, patterns) {
return patterns.some(pattern => path.includes(pattern))
Expand All @@ -24,7 +24,7 @@ async function walkDirectory(dir, root, opts) {

for (const entry of entries) {
const fullPath = join(dir, entry.name)
const relativePath = relative(root, fullPath)
let relativePath = relative(root, fullPath).split(sep).join('/')

if (isSkipped(relativePath) || matches(relativePath, ignore)) continue

Expand Down
14 changes: 7 additions & 7 deletions packages/nuekit/src/tools/fswatch.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

import { promises as fs, watch } from 'node:fs'
import { join, extname } from 'node:path'
import { join, extname, posix } from 'node:path'
import { fswalk, matches } from './fswalk'

// Main fswatch function
Expand All @@ -9,10 +9,11 @@ export function fswatch(root, opts = {}) {
// const shouldProcess = createDeduplicator()

// Start watching
const watcher = watch(root, { recursive: true }, async function(event, path) {
const watcher = watch(root, { recursive: true }, async function (event, raw_path) {
const { onupdate, onremove } = watcher
if (!path) return

if (!raw_path) return
const path = posix.normalize(raw_path)

// Skip editor backup files
if (isEditorBackup(path)) return

Expand All @@ -31,18 +32,17 @@ export function fswatch(root, opts = {}) {
const paths = await fswalk(fullPath, ignore)

for (const subPath of paths) {
// Ensure the full path is also POSIX normalized
await onupdate(join(path, subPath))
}
}

if (onupdate && extname(path)) {
await onupdate(path)
}

} catch (error) {
if (error.errno == -2 && onremove) {
await onremove(path)

} else if (error.errno != -2) {
console.error('fswatch error:', error)
}
Expand All @@ -66,4 +66,4 @@ export function createDeduplicator() {
lastTime = now
return true
}
}
}
6 changes: 5 additions & 1 deletion packages/nuekit/src/tools/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export function createServer({ port=4000, handler }, callback) {

// res = { content, type, status } || HTML <string>
if (res) {
// Handle redirects from onServe
if (res.redirect) {
return Response.redirect(res.redirect, 301)
}

return new Response(res.content || res, {
headers: { 'Content-Type': res.type || 'text/html; charset=utf-8' },
status: res.status || 200
Expand Down Expand Up @@ -61,4 +66,3 @@ export function broadcast(data) {
try { ws.send(JSON.stringify(data)) } catch(e) {}
})
}

1 change: 0 additions & 1 deletion packages/nuekit/test/cmd/create.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import { create, unzip, getLocalZip, fetchZip } from '../../src/cmd/create'
import { rm, readdir } from 'node:fs/promises'

Expand Down
11 changes: 6 additions & 5 deletions packages/nuekit/test/fswatch.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { test, expect } from 'bun:test'
import { promises as fs } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'

import {
Expand Down Expand Up @@ -35,7 +36,7 @@ test.skip('deduplicator blocks rapid events', async () => {
})

test('watches single file changes', async () => {
const tmpDir = await fs.mkdtemp('/tmp/fswatch-test-')
const tmpDir = await fs.mkdtemp(join(tmpdir(), 'fswatch-test-'))
const testFile = join(tmpDir, 'test.txt')

const changes = []
Expand All @@ -55,7 +56,7 @@ test('watches single file changes', async () => {
})

test('watches directory creation and processes files', async () => {
const tmpDir = await fs.mkdtemp('/tmp/fswatch-test-')
const tmpDir = await fs.mkdtemp(join(tmpdir(), 'fswatch-test-'))
const newDir = join(tmpDir, 'newdir')

const changes = []
Expand All @@ -78,7 +79,7 @@ test('watches directory creation and processes files', async () => {
})

test('ignores files matching patterns', async () => {
const tmpDir = await fs.mkdtemp('/tmp/fswatch-test-')
const tmpDir = await fs.mkdtemp(join(tmpdir(), 'fswatch-test-'))

const changes = []
const watcher = fswatch(tmpDir, { ignore: ['*.log', '.hidden*'] })
Expand All @@ -100,7 +101,7 @@ test('ignores files matching patterns', async () => {
})

test('handles file removal', async () => {
const tmpDir = await fs.mkdtemp('/tmp/fswatch-test-')
const tmpDir = await fs.mkdtemp(join(tmpdir(), 'fswatch-test-'))
const testFile = join(tmpDir, 'test.txt')

const removed = []
Expand All @@ -122,7 +123,7 @@ test('handles file removal', async () => {
})

test('ignores files without extensions', async () => {
const tmpDir = await fs.mkdtemp('/tmp/fswatch-test-')
const tmpDir = await fs.mkdtemp(join(tmpdir(), 'fswatch-test-'))

const changes = []
const watcher = fswatch(tmpDir)
Expand Down
Loading