Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions edge-apps/clock/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Clock - Screenly Edge App</title>
<script src="screenly.js?version=1"></script>
<link rel="stylesheet" href="dist/css/style.css" />
</head>
<body>
<auto-scaler
Expand Down Expand Up @@ -38,6 +37,6 @@
</main>
</div>
</auto-scaler>
<script src="dist/js/main.js"></script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
7 changes: 3 additions & 4 deletions edge-apps/clock/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
"scripts": {
"prebuild": "bun run type-check",
"generate-mock-data": "screenly edge-app run --generate-mock-data",
"predev": "bun run generate-mock-data && edge-apps-scripts build",
"dev": "run-p build:dev edge-app-server",
"edge-app-server": "screenly edge-app run",
"predev": "bun run generate-mock-data",
"dev": "edge-apps-scripts dev",
"build": "edge-apps-scripts build",
"build:dev": "edge-apps-scripts build:dev",
"build:prod": "edge-apps-scripts build",
Expand All @@ -16,7 +15,7 @@
"lint": "edge-apps-scripts lint --fix",
"format": "prettier --write src/ README.md index.html",
"format:check": "prettier --check src/ README.md index.html",
"deploy": "bun run build && screenly edge-app deploy",
"deploy": "bun run build && screenly edge-app deploy --path=dist/",
"type-check": "edge-apps-scripts type-check",
"prepare": "cd ../edge-apps-library && bun install && bun run build"
},
Expand Down
3 changes: 3 additions & 0 deletions edge-apps/edge-apps-library/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion edge-apps/edge-apps-library/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"prettier": "^3.8.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.54.0",
"vite": "^7.3.1"
"vite": "^7.3.1",
"yaml": "^2.8.2"
}
}
49 changes: 49 additions & 0 deletions edge-apps/edge-apps-library/scripts/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ const commands = {
description: 'Run ESLint with shared configuration',
handler: lintCommand,
},
dev: {
description: 'Start Vite development server',
handler: devCommand,
},
build: {
description: 'Build application for production',
handler: buildCommand,
Expand Down Expand Up @@ -63,6 +67,51 @@ async function lintCommand(args: string[]) {
}
}

async function devCommand(args: string[]) {
try {
const callerDir = process.cwd()

// Start Vite dev server
const viteBin = path.resolve(libraryRoot, 'node_modules', '.bin', 'vite')
const configPath = path.resolve(libraryRoot, 'vite.config.ts')
const viteArgs = ['--config', configPath, ...args]

// Set NODE_PATH to include library's node_modules so plugins can resolve dependencies
const libraryNodeModules = path.resolve(libraryRoot, 'node_modules')
const existingNodePath = process.env.NODE_PATH || ''
const nodePath = existingNodePath
? `${libraryNodeModules}${path.delimiter}${existingNodePath}`
: libraryNodeModules

// Use spawn instead of execSync to allow dev server to run without blocking
const child = spawn(viteBin, viteArgs, {
stdio: 'inherit',
cwd: callerDir,
shell: process.platform === 'win32',
env: {
...process.env,
NODE_PATH: nodePath,
},
})

// Attach an error handler
child.on('error', (err) => {
console.error('Failed to start dev server:', err)
process.exit(1)
})

// Handle parent process termination to clean up child process
const handleSignal = (signal: string) => {
child.kill(signal as NodeJS.Signals)
child.on('exit', () => process.exit(0))
}
process.on('SIGINT', () => handleSignal('SIGINT'))
process.on('SIGTERM', () => handleSignal('SIGTERM'))
} catch {
process.exit(1)
}
}

async function buildCommand(args: string[]) {
try {
const callerDir = process.cwd()
Expand Down
146 changes: 146 additions & 0 deletions edge-apps/edge-apps-library/vite-plugins/dev-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import type { ViteDevServer, Plugin } from 'vite'
import YAML from 'yaml'
import fs from 'fs'
import path from 'path'

type ScreenlyManifestField = {
type: string
default_value?: string
title: string
optional: boolean
is_global?: boolean
help_text: string | Record<string, unknown>
}

type BaseScreenlyMockData = {
metadata: {
coordinates: [number, number]
hostname: string
screen_name: string
hardware: string
location: string
screenly_version: string
tags: string[]
}
settings: Record<string, string>
cors_proxy_url: string
}

const defaultScreenlyConfig: BaseScreenlyMockData = {
metadata: {
coordinates: [37.3861, -122.0839] as [number, number],
hostname: 'dev-hostname',
screen_name: 'Development Server',
hardware: 'x86',
location: 'Development Environment',
screenly_version: 'development-server',
tags: ['Development'],
},
settings: {
enable_analytics: 'true',
tag_manager_id: '',
theme: 'light',
screenly_color_accent: '#972EFF',
screenly_color_light: '#ADAFBE',
screenly_color_dark: '#454BD2',
},
cors_proxy_url: 'http://127.0.0.1:8080',
}

function generateScreenlyObject(config: BaseScreenlyMockData) {
return `
// Generated screenly.js for development mode
window.screenly = {
signalReadyForRendering: () => {},
metadata: ${JSON.stringify(config.metadata, null, 2)},
settings: ${JSON.stringify(config.settings, null, 2)},
cors_proxy_url: ${JSON.stringify(config.cors_proxy_url)}
}
`
}

function generateMockData(rootDir: string): BaseScreenlyMockData {
const manifestPath = path.resolve(rootDir, 'screenly.yml')
const mockDataPath = path.resolve(rootDir, 'mock-data.yml')

const manifest = YAML.parse(fs.readFileSync(manifestPath, 'utf8'))
const screenlyConfig: BaseScreenlyMockData = structuredClone(
defaultScreenlyConfig,
)

// Merge settings from manifest
for (const [key, value] of Object.entries(manifest.settings) as [
string,
ScreenlyManifestField,
][]) {
if (value.type === 'string' || value.type === 'secret') {
const manifestField: ScreenlyManifestField = value
const defaultValue = manifestField?.default_value ?? ''
screenlyConfig.settings[key] = defaultValue
}
}

// Override with mock-data.yml if it exists
if (fs.existsSync(mockDataPath)) {
const mockData = YAML.parse(fs.readFileSync(mockDataPath, 'utf8'))

// Override metadata if present
if (mockData.metadata) {
Object.assign(screenlyConfig.metadata, mockData.metadata)
}

// Override settings if present
if (mockData.settings) {
Object.assign(screenlyConfig.settings, mockData.settings)
}

// Override cors_proxy_url if present
if (mockData.cors_proxy_url) {
screenlyConfig.cors_proxy_url = mockData.cors_proxy_url
}
}

return screenlyConfig
}

export function screenlyDevServer(): Plugin {
let config: BaseScreenlyMockData
let rootDir: string

return {
name: 'screenly-dev-server',
configureServer(server: ViteDevServer) {
rootDir = server.config.root

// Generate initial mock data
config = generateMockData(rootDir)

// Watch for changes to screenly.yml
const manifestPath = path.resolve(rootDir, 'screenly.yml')
fs.watch(manifestPath, () => {
console.log('screenly.yml changed, regenerating mock data...')
config = generateMockData(rootDir)
})

// Watch for changes to mock-data.yml if it exists
const mockDataPath = path.resolve(rootDir, 'mock-data.yml')
if (fs.existsSync(mockDataPath)) {
fs.watch(mockDataPath, () => {
console.log('mock-data.yml changed, regenerating mock data...')
config = generateMockData(rootDir)
})
}

server.middlewares.use((req, res, next) => {
if (req.url === '/screenly.js?version=1') {
const screenlyJsContent = generateScreenlyObject(config)

res.setHeader('Content-Type', 'application/javascript')
res.end(screenlyJsContent)
return
}
next()
})
},
}
}
27 changes: 24 additions & 3 deletions edge-apps/edge-apps-library/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
import { defineConfig } from 'vite'
import { defineConfig, Plugin } from 'vite'
import tailwindcss from '@tailwindcss/vite'
import { copyFileSync, existsSync } from 'fs'
import { resolve } from 'path'
import { screenlyDevServer } from './vite-plugins/dev-server'

function copyScreenlyFiles(): Plugin {
return {
name: 'copy-screenly-files',
closeBundle() {
const filesToCopy = ['screenly.yml', 'screenly_qc.yml', 'instance.yml']

for (const file of filesToCopy) {
const srcPath = resolve(process.cwd(), file)
if (existsSync(srcPath)) {
const destPath = resolve(process.cwd(), 'dist', file)
copyFileSync(srcPath, destPath)
console.log(`Copied ${file} to dist/`)
}
}
},
}
}

export default defineConfig({
base: '',
Expand All @@ -8,7 +29,7 @@ export default defineConfig({
assetsInlineLimit: 7000000,
minify: true,
rollupOptions: {
input: 'src/main.ts',
input: 'index.html',
output: {
dir: 'dist',
entryFileNames: 'js/[name].js',
Expand All @@ -23,5 +44,5 @@ export default defineConfig({
},
},
},
plugins: [tailwindcss()],
plugins: [tailwindcss(), screenlyDevServer(), copyScreenlyFiles()],
})