v0.1.31 로컬 실시간 확인 스크립트 추가
This commit is contained in:
211
scripts/dev-watch.js
Normal file
211
scripts/dev-watch.js
Normal file
@@ -0,0 +1,211 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { spawn } = require('child_process')
|
||||
|
||||
const rootDir = path.resolve(__dirname, '..')
|
||||
const ignoredDirectories = new Set([
|
||||
'.git',
|
||||
'.cursor',
|
||||
'.vscode',
|
||||
'.docker',
|
||||
'docs',
|
||||
'local-ghost',
|
||||
'node_modules',
|
||||
'seed',
|
||||
'theme-export'
|
||||
])
|
||||
const ignoredFiles = new Set([
|
||||
'.DS_Store'
|
||||
])
|
||||
const syncExtensions = new Set([
|
||||
'.css',
|
||||
'.hbs',
|
||||
'.js',
|
||||
'.json',
|
||||
'.svg',
|
||||
'.yaml',
|
||||
'.yml'
|
||||
])
|
||||
const watchedRoots = [rootDir]
|
||||
|
||||
let syncTimer = null
|
||||
let syncProcess = null
|
||||
let scanTimer = null
|
||||
let previousSnapshot = new Map()
|
||||
|
||||
/**
|
||||
* @param {string} relativePath
|
||||
*/
|
||||
function shouldIgnorePath(relativePath) {
|
||||
if (!relativePath || relativePath === '.') {
|
||||
return false
|
||||
}
|
||||
|
||||
const segments = relativePath.split(path.sep)
|
||||
return segments.some((segment) => ignoredDirectories.has(segment) || ignoredFiles.has(segment))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} relativePath
|
||||
*/
|
||||
function shouldSyncFile(relativePath) {
|
||||
if (shouldIgnorePath(relativePath)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (relativePath === path.join('assets', 'styles', 'tailwind.css')) {
|
||||
return false
|
||||
}
|
||||
|
||||
const extension = path.extname(relativePath)
|
||||
return syncExtensions.has(extension)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} command
|
||||
* @param {string[]} args
|
||||
* @param {(code: number | null) => void} [onExit]
|
||||
*/
|
||||
function runCommand(command, args, onExit) {
|
||||
const child = spawn(command, args, {
|
||||
cwd: rootDir,
|
||||
stdio: 'inherit',
|
||||
shell: false
|
||||
})
|
||||
|
||||
child.on('exit', (code) => {
|
||||
if (onExit) {
|
||||
onExit(code)
|
||||
}
|
||||
})
|
||||
|
||||
return child
|
||||
}
|
||||
|
||||
function queueSync() {
|
||||
if (syncTimer) {
|
||||
clearTimeout(syncTimer)
|
||||
}
|
||||
|
||||
syncTimer = setTimeout(() => {
|
||||
if (syncProcess) {
|
||||
queueSync()
|
||||
return
|
||||
}
|
||||
|
||||
console.log('[dev:watch] theme sync')
|
||||
syncProcess = runCommand('npm', ['run', 'dev:sync'], (code) => {
|
||||
syncProcess = null
|
||||
|
||||
if (code !== 0) {
|
||||
console.error(`[dev:watch] sync failed with code ${code}`)
|
||||
}
|
||||
})
|
||||
}, 180)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} directoryPath
|
||||
* @param {Map<string, number>} snapshot
|
||||
*/
|
||||
function collectSnapshot(directoryPath, snapshot) {
|
||||
if (!fs.existsSync(directoryPath)) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const entry of fs.readdirSync(directoryPath, { withFileTypes: true })) {
|
||||
const entryPath = path.join(directoryPath, entry.name)
|
||||
const relativePath = path.relative(rootDir, entryPath)
|
||||
|
||||
if (shouldIgnorePath(relativePath)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
collectSnapshot(entryPath, snapshot)
|
||||
continue
|
||||
}
|
||||
|
||||
if (shouldSyncFile(relativePath)) {
|
||||
snapshot.set(relativePath, fs.statSync(entryPath).mtimeMs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildSnapshot() {
|
||||
const snapshot = new Map()
|
||||
|
||||
for (const watchedRoot of watchedRoots) {
|
||||
collectSnapshot(watchedRoot, snapshot)
|
||||
}
|
||||
|
||||
return snapshot
|
||||
}
|
||||
|
||||
function scanForChanges() {
|
||||
const nextSnapshot = buildSnapshot()
|
||||
|
||||
for (const [relativePath, modifiedTime] of nextSnapshot.entries()) {
|
||||
if (previousSnapshot.get(relativePath) !== modifiedTime) {
|
||||
console.log(`[dev:watch] change detected: ${relativePath}`)
|
||||
queueSync()
|
||||
previousSnapshot = nextSnapshot
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for (const relativePath of previousSnapshot.keys()) {
|
||||
if (!nextSnapshot.has(relativePath)) {
|
||||
console.log(`[dev:watch] change detected: ${relativePath}`)
|
||||
queueSync()
|
||||
previousSnapshot = nextSnapshot
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
previousSnapshot = nextSnapshot
|
||||
}
|
||||
|
||||
function startPolling() {
|
||||
previousSnapshot = buildSnapshot()
|
||||
scanTimer = setInterval(scanForChanges, 800)
|
||||
}
|
||||
|
||||
function shutdown() {
|
||||
if (syncTimer) {
|
||||
clearTimeout(syncTimer)
|
||||
}
|
||||
|
||||
if (tailwindProcess && !tailwindProcess.killed) {
|
||||
tailwindProcess.kill('SIGINT')
|
||||
}
|
||||
|
||||
if (syncProcess && !syncProcess.killed) {
|
||||
syncProcess.kill('SIGINT')
|
||||
}
|
||||
|
||||
if (scanTimer) {
|
||||
clearInterval(scanTimer)
|
||||
}
|
||||
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
console.log('[dev:watch] initial prepare')
|
||||
|
||||
runCommand('npm', ['run', 'dev:prepare'], (prepareCode) => {
|
||||
if (prepareCode !== 0) {
|
||||
process.exit(prepareCode || 1)
|
||||
}
|
||||
|
||||
console.log('[dev:watch] tailwind watch start')
|
||||
tailwindProcess = runCommand('npm', ['run', 'build:tailwind', '--', '--watch'])
|
||||
|
||||
console.log('[dev:watch] filesystem watch start')
|
||||
startPolling()
|
||||
})
|
||||
|
||||
let tailwindProcess = null
|
||||
|
||||
process.on('SIGINT', shutdown)
|
||||
process.on('SIGTERM', shutdown)
|
||||
Reference in New Issue
Block a user