Skip to content

feat(edge-apps): migrate to Vite dev server with dist-based deployment#690

Merged
nicomiguelino merged 15 commits intomasterfrom
migrate-to-vite-dev-server
Feb 17, 2026
Merged

feat(edge-apps): migrate to Vite dev server with dist-based deployment#690
nicomiguelino merged 15 commits intomasterfrom
migrate-to-vite-dev-server

Conversation

@nicomiguelino
Copy link
Contributor

@nicomiguelino nicomiguelino commented Feb 16, 2026

User description

Summary

This PR migrates the clock Edge App to use Vite's dev server with a dist-based deployment approach. The main change is that index.html is now built and output to the dist/ directory, which serves as the deployment source.

Key Changes

  • Vite dev server integration: Added custom Vite plugin (screenlyDevServer) that serves mock data during development
  • Live mock data reloading: Automatically watches and regenerates mock data when screenly.yml or mock-data.yml changes
  • Dist-based deployment: Build system now outputs index.html and all assets to dist/ directory
  • Source file references: Updated index.html to reference TypeScript source files directly (Vite handles transformation)
  • CLI dev command: Added edge-apps-scripts dev command to run Vite dev server
  • Manifest copying: Added plugin to automatically copy screenly.yml, screenly_qc.yml, and instance.yml to dist/

Motivation

This change establishes the foundation for automated screenshot testing (likely via Playwright) by ensuring Edge Apps build to a predictable dist/ directory structure. Having index.html in dist/ makes it easier to serve and test the built application programmatically.

What's Next

  • Apply these changes to other Edge Apps once validated
  • Set up Playwright or similar for automated screenshot testing

PR Type

Enhancement, Bug fix, Tests


Description

  • Add Vite dev server CLI command

  • Provide screenly.js mock middleware

  • Build from index.html into dist/

  • Fix menu-board assets bundling/tests


Diagram Walkthrough

flowchart LR
  cli["`edge-apps-scripts` CLI"] 
  vitecfg["Vite config (`vite.config.ts`)"]
  plugin["Dev server plugin (`vite-plugins/dev-server.ts`)"]
  apps["Edge apps (`index.html`, scripts)"]
  dist["`dist/` deployment output"]
  mock["Mock config (`screenly.yml`/`mock-data.yml`)"]
  assets["Bundled assets (menu-board)"]

  cli -- "adds `dev` command" --> apps
  vitecfg -- "input `index.html`" --> dist
  vitecfg -- "register plugins" --> plugin
  plugin -- "watches + serves `/screenly.js?version=1`" --> apps
  mock -- "drives generated `window.screenly`" --> plugin
  assets -- "import for bundling" --> apps
Loading

File Walkthrough

Relevant files
Enhancement
3 files
cli.ts
Add `dev` command and shared spawn helpers                             
+82/-62 
dev-server.ts
Add Vite middleware to serve mock `screenly.js`                   
+177/-0 
utils.ts
Import assets for bundling and add logo helper                     
+15/-1   
Configuration changes
17 files
vite.config.ts
Switch build input to `index.html` and add plugins             
+37/-3   
index.html
Load Vite module entrypoint instead of `dist` assets         
+1/-2     
index.html
Load Vite module entrypoint instead of `dist` assets         
+1/-2     
index.html
Load Vite module entrypoint instead of `dist` assets         
+1/-2     
index.html
Load Vite module entrypoint instead of `dist` assets         
+1/-2     
index.html
Load Vite module entrypoint instead of `dist` assets         
+1/-2     
index.html
Load Vite module entrypoint instead of `dist` assets         
+1/-2     
index.html
Load Vite module entrypoint instead of `dist` assets         
+1/-2     
package.json
Run Vite dev server and deploy from `dist/`                           
+4/-4     
package.json
Simplify dev script and deploy from `dist/`                           
+3/-4     
package.json
Use Vite dev server plus CORS proxy; deploy `dist/`           
+6/-6     
package.json
Simplify dev script and deploy from `dist/`                           
+3/-4     
screenly.yml
Clear default `logo_url` setting value                                     
+1/-1     
screenly_qc.yml
Clear default `logo_url` setting value                                     
+1/-1     
package.json
Simplify dev script and deploy from `dist/`                           
+3/-4     
package.json
Simplify dev script and deploy from `dist/`                           
+3/-4     
package.json
Simplify dev script and deploy from `dist/`                           
+3/-4     
Tests
1 files
main.test.ts
Refactor asset URL tests and add logo coverage                     
+35/-22 
Bug fix
1 files
main.ts
Use computed default logo URL from utils                                 
+2/-1     
Dependencies
1 files
package.json
Add `yaml` dependency and format `vite-plugins/`                 
+4/-3     

nicomiguelino and others added 2 commits February 14, 2026 22:14
- Add Vite dev server plugin with live mock data reloading
- Update build system to output to dist/ directory for deployment
- Modify index.html to reference TypeScript source files directly
- Add edge-apps-scripts dev command for running Vite dev server
- Create plugin to copy screenly.yml, screenly_qc.yml, and instance.yml to dist/
- Watch screenly.yml and mock-data.yml for changes during development
- Update deploy script to use --path=dist/ flag

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@github-actions
Copy link

github-actions bot commented Feb 16, 2026

PR Reviewer Guide 🔍

(Review updated until commit 286608d)

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Default Handling

Validate that getSettingWithDefault('logo_url', getDefaultLogoUrl()) actually falls back when the setting exists but is an empty string. With the manifest now defaulting logo_url to an empty string, the runtime/mock may provide logo_url: "", which could prevent the intended default logo from being used.

  'rgba(255, 255, 255, 0.95)',
)
const backgroundImage = getSettingWithDefault<string>(
  'background_image',
  getDefaultBackgroundImage(),
)
const logoUrl = getSettingWithDefault<string>(
  'logo_url',
  getDefaultLogoUrl(),
)
const currency = getSettingWithDefault<string>('currency', '$')
Path Matching

The watcher change handler and middleware use strict string comparisons (file === manifestPath and req.url === '/screenly.js?version=1'). This can be brittle across platforms (path separators/case) and URL variations (additional query params, different ordering). Consider normalizing paths and parsing the request URL.

// Watch for changes to screenly.yml and mock-data.yml using Vite's watcher
const manifestPath = path.resolve(rootDir, 'screenly.yml')
const mockDataPath = path.resolve(rootDir, 'mock-data.yml')

const handleConfigFileChange = (file: string) => {
  if (file === manifestPath || file === mockDataPath) {
    config = generateMockData(rootDir, config)
  }
}

server.watcher.add([manifestPath, mockDataPath])
server.watcher.on('add', handleConfigFileChange)
server.watcher.on('change', handleConfigFileChange)
server.watcher.on('unlink', handleConfigFileChange)

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()
})
Windows Support

getVitePaths() resolves the Vite binary to node_modules/.bin/vite, which may not exist on Windows (often vite.cmd). Even with shell enabled, spawning a non-existent file can fail. Consider resolving platform-specific bin names or invoking via bunx/npx-style execution if applicable.

@github-actions
Copy link

github-actions bot commented Feb 16, 2026

PR Code Suggestions ✨

Latest suggestions up to 286608d
Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Propagate child exit status correctly

Ensure the parent process exits with the same exit code as the spawned process;
currently a non-zero Vite exit can be reported as success. Also avoid registering
repeated signal/exit listeners inside the signal handler, which can miss fast exits
and cause multiple listener accumulation.

edge-apps/edge-apps-library/scripts/cli.ts [51-84]

 function setupSignalHandlers(child: ChildProcess): void {
-  const handleSignal = (signal: string) => {
-    child.kill(signal as NodeJS.Signals)
-    child.on('exit', () => process.exit(0))
+  const signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM']
+
+  const handlers = new Map<NodeJS.Signals, () => void>()
+  for (const signal of signals) {
+    const handler = () => {
+      if (!child.killed) child.kill(signal)
+    }
+    handlers.set(signal, handler)
+    process.once(signal, handler)
   }
-  process.on('SIGINT', () => handleSignal('SIGINT'))
-  process.on('SIGTERM', () => handleSignal('SIGTERM'))
+
+  child.once('exit', (code, signal) => {
+    for (const [sig, handler] of handlers) {
+      process.removeListener(sig, handler)
+    }
+    process.exit(code ?? (signal ? 1 : 0))
+  })
 }
 
 function spawnWithSignalHandling(
   command: string,
   args: string[],
   errorMessage: string,
 ): void {
   const child = spawn(command, args, {
     stdio: 'inherit',
     cwd: process.cwd(),
     shell: process.platform === 'win32',
     env: {
       ...process.env,
       NODE_PATH: getNodePath(),
     },
   })
 
   child.on('error', (err) => {
     console.error(errorMessage, err)
     process.exit(1)
   })
 
   setupSignalHandlers(child)
 }
Suggestion importance[1-10]: 7

__

Why: The current setupSignalHandlers can cause the parent to exit with code 0 even when the spawned Vite process fails, and it also registers signal/exit listeners in a way that can accumulate. Exiting with the child's actual exit code and using once handlers makes the CLI behavior more correct and predictable.

Medium
Normalize mock settings value types

Coerce mock-data.yml settings values to strings, since YAML parsing can produce
numbers/booleans and downstream code typically assumes string settings. Also guard
cors_proxy_url to only accept strings to avoid runtime type issues in generated
screenly.js.

edge-apps/edge-apps-library/vite-plugins/dev-server.ts [117-132]

 if (mockData && typeof mockData === 'object') {
   // Override metadata if present
-  if (mockData.metadata) {
+  if (mockData.metadata && typeof mockData.metadata === 'object') {
     Object.assign(screenlyConfig.metadata, mockData.metadata)
   }
 
-  // Override settings if present
-  if (mockData.settings) {
-    Object.assign(screenlyConfig.settings, mockData.settings)
+  // Override settings if present (coerce all values to strings)
+  if (mockData.settings && typeof mockData.settings === 'object') {
+    for (const [key, value] of Object.entries(
+      mockData.settings as Record<string, unknown>,
+    )) {
+      screenlyConfig.settings[key] = value == null ? '' : String(value)
+    }
   }
 
   // Override cors_proxy_url if present
-  if (mockData.cors_proxy_url) {
-    screenlyConfig.cors_proxy_url = mockData.cors_proxy_url as string
+  if (typeof mockData.cors_proxy_url === 'string') {
+    screenlyConfig.cors_proxy_url = mockData.cors_proxy_url
   }
 }
Suggestion importance[1-10]: 7

__

Why: mock-data.yml values can be parsed as booleans/numbers, but screenlyConfig.settings is a Record<string, string> and downstream code likely expects strings. Coercing settings to strings and validating cors_proxy_url prevents subtle runtime/type inconsistencies in generated screenly.js.

Medium
Make watcher and URL matching robust

Normalize watched file paths before comparing, since watcher events can emit
different path formats (especially on Windows), preventing reloads. Also parse
req.url to match pathname/query robustly, rather than relying on an exact raw string
match.

edge-apps/edge-apps-library/vite-plugins/dev-server.ts [154-174]

+const normalizePath = (p: string) => path.normalize(p)
+
 const handleConfigFileChange = (file: string) => {
-  if (file === manifestPath || file === mockDataPath) {
+  const normalized = normalizePath(file)
+  if (
+    normalized === normalizePath(manifestPath) ||
+    normalized === normalizePath(mockDataPath)
+  ) {
     config = generateMockData(rootDir, config)
   }
 }
 
 server.watcher.add([manifestPath, mockDataPath])
 server.watcher.on('add', handleConfigFileChange)
 server.watcher.on('change', handleConfigFileChange)
 server.watcher.on('unlink', handleConfigFileChange)
 
 server.middlewares.use((req, res, next) => {
-  if (req.url === '/screenly.js?version=1') {
+  const url = new URL(req.url ?? '/', 'http://localhost')
+  if (url.pathname === '/screenly.js' && url.searchParams.get('version') === '1') {
     const screenlyJsContent = generateScreenlyObject(config)
 
     res.setHeader('Content-Type', 'application/javascript')
     res.end(screenlyJsContent)
     return
   }
   next()
 })
Suggestion importance[1-10]: 6

__

Why: Comparing raw watcher paths (file === manifestPath) can fail depending on OS/path formatting, and matching req.url by exact string is brittle. Normalizing paths and parsing req.url improves cross-platform reliability of config reloads and middleware routing.

Low

Previous suggestions

Suggestions up to commit 8873844
CategorySuggestion                                                                                                                                    Impact
Possible issue
Guard against missing config

Avoid crashing the dev server when screenly.yml is missing/malformed or when
manifest.settings is absent. Fall back to defaults and only iterate settings when
it’s a plain object.

edge-apps/edge-apps-library/vite-plugins/dev-server.ts [66-82]

-const manifest = YAML.parse(fs.readFileSync(manifestPath, 'utf8'))
-const screenlyConfig: BaseScreenlyMockData = structuredClone(
-  defaultScreenlyConfig,
-)
+let manifest: unknown = {}
+if (fs.existsSync(manifestPath)) {
+  try {
+    manifest = YAML.parse(fs.readFileSync(manifestPath, 'utf8'))
+  } catch (e) {
+    console.warn('Failed to parse screenly.yml, using defaults:', e)
+  }
+} else {
+  console.warn('screenly.yml not found, using defaults')
+}
 
-// 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
+const screenlyConfig: BaseScreenlyMockData = structuredClone(defaultScreenlyConfig)
+
+// Merge settings from manifest (if present)
+const settings =
+  manifest && typeof manifest === 'object'
+    ? (manifest as Record<string, unknown>).settings
+    : undefined
+
+if (settings && typeof settings === 'object') {
+  for (const [key, value] of Object.entries(settings) as [
+    string,
+    ScreenlyManifestField,
+  ][]) {
+    if (value.type === 'string' || value.type === 'secret') {
+      const defaultValue = value?.default_value ?? ''
+      screenlyConfig.settings[key] = defaultValue
+    }
   }
 }
Suggestion importance[1-10]: 7

__

Why: As written, generateMockData() will throw if screenly.yml is missing/malformed or if manifest.settings is absent, which can crash the dev server. The suggestion correctly adds safe parsing and guards before iterating settings, improving robustness without changing intended defaults.

Medium
Fix dev server process spawning

Make the Vite binary resolution work reliably on Windows by using vite.cmd and avoid
relying on shell execution. Also propagate the child process exit code so failures
don’t leave the parent hanging or incorrectly exiting with 0.

edge-apps/edge-apps-library/scripts/cli.ts [74-109]

 // Start Vite dev server
-const viteBin = path.resolve(libraryRoot, 'node_modules', '.bin', 'vite')
+const viteBinName = process.platform === 'win32' ? 'vite.cmd' : 'vite'
+const viteBin = path.resolve(libraryRoot, 'node_modules', '.bin', viteBinName)
 const configPath = path.resolve(libraryRoot, 'vite.config.ts')
 const viteArgs = ['--config', configPath, ...args]
 ...
 const child = spawn(viteBin, viteArgs, {
   stdio: 'inherit',
   cwd: callerDir,
-  shell: process.platform === 'win32',
+  shell: false,
   env: {
     ...process.env,
     NODE_PATH: nodePath,
   },
 })
+
+child.on('exit', (code) => {
+  process.exit(code ?? 1)
+})
 ...
-const handleSignal = (signal: string) => {
-  child.kill(signal as NodeJS.Signals)
-  child.on('exit', () => process.exit(0))
+const handleSignal = (signal: NodeJS.Signals) => {
+  child.kill(signal)
 }
-process.on('SIGINT', () => handleSignal('SIGINT'))
-process.on('SIGTERM', () => handleSignal('SIGTERM'))
+process.once('SIGINT', () => handleSignal('SIGINT'))
+process.once('SIGTERM', () => handleSignal('SIGTERM'))
Suggestion importance[1-10]: 7

__

Why: Using vite.cmd on Windows and shell: false makes the spawn more reliable across platforms, and propagating the child's exit code fixes cases where failures could incorrectly exit with 0. This directly improves correctness of the dev command process behavior.

Medium
Use Vite watcher for files

fs.watch can throw if the file doesn’t exist and won’t start watching mock-data.yml
if it gets created later. Use Vite’s server.watcher (chokidar) so add/change/unlink
are handled and watchers are cleaned up with the dev server lifecycle.

edge-apps/edge-apps-library/vite-plugins/dev-server.ts [118-132]

-// 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)
-})
+const mockDataPath = path.resolve(rootDir, 'mock-data.yml')
 
-// 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...')
+const refresh = (file: string) => {
+  if (file === manifestPath || file === mockDataPath) {
+    console.log(`${path.basename(file)} changed, regenerating mock data...`)
     config = generateMockData(rootDir)
-  })
+  }
 }
 
+server.watcher.add([manifestPath, mockDataPath])
+server.watcher.on('add', refresh)
+server.watcher.on('change', refresh)
+server.watcher.on('unlink', refresh)
+
Suggestion importance[1-10]: 6

__

Why: Replacing fs.watch with server.watcher is appropriate in a Vite plugin and avoids issues like watching non-existent files and missing newly-created mock-data.yml. This is a solid lifecycle/maintainability improvement with moderate impact.

Low

@nicomiguelino nicomiguelino changed the title feat(clock): migrate to Vite dev server with dist-based deployment feat(edge-apps): migrate to Vite dev server with dist-based deployment Feb 16, 2026
Copy link
Contributor Author

@nicomiguelino nicomiguelino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall Assessment

This is a solid architectural improvement that modernizes the build system. The changes are well-structured and set up a good foundation for screenshot testing. I've left several inline comments with suggestions for improvements, primarily around error handling and HMR integration.

Main areas to address:

  • File watching reliability and browser reload
  • Error handling for YAML parsing
  • Cleanup of file watchers

Once these are addressed, this will be ready to merge!

Copy link
Contributor Author

@nicomiguelino nicomiguelino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File: vite-plugins/dev-server.ts (Line 119-122)

Issue: File watching doesn't trigger browser reload

When YAML files change, the config regenerates but the browser doesn't automatically reload to reflect the changes.

Suggestion:

fs.watch(manifestPath, () => {
  console.log('screenly.yml changed, regenerating mock data...')
  config = generateMockData(rootDir)
  server.ws.send({ type: 'full-reload', path: '*' })
})

This will trigger a full page reload when the manifest changes.

Copy link
Contributor Author

@nicomiguelino nicomiguelino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File: vite-plugins/dev-server.ts (Line 119-130)

Issue: fs.watch() can fire multiple events and lacks error handling

The current implementation has a few issues:

  • Multiple events can fire for a single file change (causing duplicate regeneration)
  • No error handling if watch fails
  • Watch handles are never cleaned up

Suggestion:

const watchers: FSWatcher[] = []

// Debounce to prevent multiple rapid regenerations
const debouncedRegenerate = debounce(() => {
  config = generateMockData(rootDir)
  server.ws.send({ type: 'full-reload' })
}, 100)

try {
  const watcher = fs.watch(manifestPath, (eventType) => {
    if (eventType === 'change') {
      console.log('screenly.yml changed, regenerating mock data...')
      debouncedRegenerate()
    }
  })
  watcher.on('error', (err) => console.error('Watch error:', err))
  watchers.push(watcher)
} catch (err) {
  console.warn('Could not watch screenly.yml:', err)
}

// Add cleanup in plugin lifecycle
return {
  name: 'screenly-dev-server',
  configureServer(server) { /* ... */ },
  buildEnd() {
    watchers.forEach(w => w.close())
  }
}

Copy link
Contributor Author

@nicomiguelino nicomiguelino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File: vite-plugins/dev-server.ts (Line 62-79)

Issue: Missing error handling for YAML parsing

If screenly.yml is malformed, the dev server will crash without a helpful error message.

Suggestion:

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

  let manifest
  try {
    manifest = YAML.parse(fs.readFileSync(manifestPath, 'utf8'))
  } catch (err) {
    console.error(`Failed to parse screenly.yml: ${err}`)
    return defaultScreenlyConfig // Fallback to defaults
  }

  // Add validation
  if (\!manifest?.settings || typeof manifest.settings \!== 'object') {
    console.warn('Invalid screenly.yml: missing or invalid settings')
    return defaultScreenlyConfig
  }

  const screenlyConfig: BaseScreenlyMockData = structuredClone(
    defaultScreenlyConfig,
  )

  // ... rest of function
}

Copy link
Contributor Author

@nicomiguelino nicomiguelino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File: vite-plugins/dev-server.ts (Line 82-99)

Issue: mock-data.yml parsing also needs error handling

Same issue as with screenly.yml - malformed YAML will crash the server.

Suggestion:

// Override with mock-data.yml if it exists
if (fs.existsSync(mockDataPath)) {
  try {
    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
    }
  } catch (err) {
    console.error(`Failed to parse mock-data.yml: ${err}`)
    // Continue with defaults from screenly.yml
  }
}

Copy link
Contributor Author

@nicomiguelino nicomiguelino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File: vite-plugins/dev-server.ts (Line 134-142)

Nitpick: Add cache control header

Adding a cache control header ensures the browser doesn't cache the generated screenly.js file during development.

Suggestion:

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

    res.setHeader('Content-Type', 'application/javascript')
    res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
    res.end(screenlyJsContent)
    return
  }
  next()
})

Copy link
Contributor Author

@nicomiguelino nicomiguelino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File: scripts/cli.ts (Line 107-111)

Issue: Empty catch block swallows errors

The empty catch block makes debugging failures difficult.

Suggestion:

  } catch (err) {
    console.error('Dev server failed:', err)
    process.exit(1)
  }

Copy link
Contributor Author

@nicomiguelino nicomiguelino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File: vite.config.ts (Line 14-20)

Nitpick: Add error handling for file copy operations

If copying manifest files fails, it should log a warning but not crash the build.

Suggestion:

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

Copy link
Contributor Author

@nicomiguelino nicomiguelino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File: clock/package.json (Line 8)

Question: Is the predev hook still needed?

The new dev server automatically generates mock data from screenly.yml and mock-data.yml. The predev hook runs screenly edge-app run --generate-mock-data, which might be redundant now.

Questions:

  • Does generate-mock-data create additional files that the dev server needs?
  • Can this hook be removed to simplify the workflow?

If it's no longer needed, consider removing it for clarity.

nicomiguelino and others added 4 commits February 16, 2026 14:08
- Add server.fs.allow for app and library directories
- Remove verbose console logs from file watchers
- Add vite-plugins/ to prettier format script
- Update index.html to reference TypeScript source files
- Simplify dev scripts to use edge-apps-scripts dev
- Add --path=dist/ to deploy scripts
- Preserve CORS proxy for cap-alerting and grafana apps
- Add getNodePath() helper for NODE_PATH setup
- Add setupSignalHandlers() for process cleanup
- Add spawnWithSignalHandling() to combine spawn logic
- Add getVitePaths() for Vite binary and config paths
- Simplify command functions by reusing helpers
- Import pizza.png and screenly_food.svg assets in utils.ts
- Update manifest default values for logo_url to empty string
- Add getDefaultLogoUrl() function with hardware-specific logic
- Update tests to check bundled asset paths instead of relative paths
- Add testAssetUrl helper to eliminate test duplication

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates multiple Edge Apps to use a shared Vite dev server workflow and dist-based deployment, including a custom dev-server plugin to serve screenly.js mock data and copying manifests into dist/ for deploys.

Changes:

  • Update Edge App dev/deploy scripts to run Vite dev server and deploy from dist/.
  • Switch app HTML entrypoints to Vite module loading (/src/main.ts) instead of dist/js/main.js + dist/css/style.css.
  • Add shared Vite config/plugin support in edge-apps-library (mock screenly.js, copy manifests into dist/).

Reviewed changes

Copilot reviewed 23 out of 24 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
edge-apps/weather/package.json Use edge-apps-scripts dev and deploy from dist/.
edge-apps/weather/index.html Switch to Vite module entry (/src/main.ts), remove direct dist/ asset links.
edge-apps/simple-timer/package.json Use edge-apps-scripts dev and deploy from dist/.
edge-apps/simple-timer/index.html Switch to Vite module entry, remove direct dist/ asset links.
edge-apps/qr-code/package.json Use edge-apps-scripts dev and deploy from dist/.
edge-apps/qr-code/index.html Switch to Vite module entry, remove direct dist/ asset links.
edge-apps/menu-board/src/utils.ts Use bundled asset imports for default background/logo (and add default logo helper).
edge-apps/menu-board/src/main.ts Use getDefaultLogoUrl() as the app-side default for logo_url.
edge-apps/menu-board/src/main.test.ts Refactor tests with helper coverage for background + logo defaults.
edge-apps/menu-board/screenly_qc.yml Clear logo_url manifest default to defer to app-side default logic.
edge-apps/menu-board/screenly.yml Clear logo_url manifest default to defer to app-side default logic.
edge-apps/menu-board/package.json Use edge-apps-scripts dev and deploy from dist/.
edge-apps/menu-board/index.html Switch to Vite module entry, remove direct dist/ asset links.
edge-apps/grafana/package.json Run Vite dev server alongside the CORS proxy; deploy from dist/.
edge-apps/grafana/index.html Switch to Vite module entry, remove direct dist/ asset links.
edge-apps/edge-apps-library/vite.config.ts Build from index.html, add dev plugin, allow fs access for shared library, copy manifests to dist/.
edge-apps/edge-apps-library/vite-plugins/dev-server.ts New Vite middleware to generate and serve mock screenly.js from YAML.
edge-apps/edge-apps-library/scripts/cli.ts Add edge-apps-scripts dev command to start Vite dev server.
edge-apps/edge-apps-library/package.json Add yaml dev dependency and include vite-plugins/ in formatting.
edge-apps/edge-apps-library/bun.lock Lockfile updates for the new yaml dependency.
edge-apps/clock/package.json Use edge-apps-scripts dev and deploy from dist/.
edge-apps/clock/index.html Switch to Vite module entry, remove direct dist/ asset links.
edge-apps/cap-alerting/package.json Run Vite dev server alongside CORS proxy; deploy from dist/.
edge-apps/cap-alerting/index.html Switch to Vite module entry, remove direct dist/ asset links.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

nicomiguelino and others added 2 commits February 17, 2026 06:27
- Add error handling for YAML parsing to prevent dev server crashes
- Accept previousConfig parameter to fallback on parse errors
- Switch from fs.watch to Vite's server.watcher for robust file watching
- Watch both screenly.yml and mock-data.yml regardless of existence
- Handle add, change, and unlink events for both config files

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@github-actions
Copy link

Persistent review updated to latest commit 286608d

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 24 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

nicomiguelino and others added 7 commits February 17, 2026 10:31
- Send full-reload message to connected clients via WebSocket
- Automatically reload browser when screenly.yml or mock-data.yml changes
- Eliminates need for manual refresh during development

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Switch from blueprint to edge-apps-library CORS proxy reference
- Standardizes with grafana and other migrated apps
- No functional changes to dev mode behavior

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Wrap copyFileSync in try/catch with descriptive error message
- Re-throw to preserve build failure behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove hardware-specific branching from getDefaultBackgroundImage and getDefaultLogoUrl
- Assets are inlined as data URIs by Vite, working on all hardware types
- Remove unused getHardware/Hardware imports from utils.ts
- Simplify tests by removing hardware-specific mock setup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add dev and deploy commands to edge-apps-library recommended scripts
- Add Development Server section to available commands
- Fix entry point reference from src/main.ts to index.html
- Update menu-board logo_url default to reflect empty manifest value

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@nicomiguelino nicomiguelino merged commit 328bc56 into master Feb 17, 2026
12 checks passed
@nicomiguelino nicomiguelino deleted the migrate-to-vite-dev-server branch February 17, 2026 21:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants