Skip to content

Conversation

@nicomiguelino
Copy link
Contributor

@nicomiguelino nicomiguelino commented Feb 3, 2026

User description

Move all build configurations from clock and weather apps to edge-apps-library. All config files removed from apps - edge-apps-scripts now handles everything.

Changes:

  • Create shared Vite plugins (screenly-mock, copy-manifest, component-resolve, css-alias)
  • Add centralized configs (vite, eslint, postcss, tailwind, tsconfig)
  • Update edge-apps-scripts to use library configs and create temp tsconfig on-the-fly
  • Remove all local config files from clock and weather apps
  • Update clock/weather package.json to use edge-apps-scripts for build and lint
  • Add autoprefixer, postcss, @tailwindcss/postcss, and yaml to library dependencies

PR Type

Enhancement


Description

  • Centralize Vite config under configs/

  • Add shared Vite plugins (mock, manifest, aliases)

  • Update edge-apps-scripts build and typecheck

  • Remove clock/weather local config files


Diagram Walkthrough

flowchart LR
  apps["Clock & Weather apps"]
  cli["`edge-apps-library/scripts/cli.ts`"]
  viteCfg["`configs/vite.config.with-plugins.ts`"]
  plugins["`src/vite-plugins/*`"]
  tsCfg["`configs/tsconfig.vite-app.json`"]
  eslintBase["`configs/eslint.config.base.ts`"]

  apps -- "use `edge-apps-scripts`" --> cli
  cli -- "Vite builds via" --> viteCfg
  viteCfg -- "loads" --> plugins
  cli -- "temp project extends" --> tsCfg
  apps -- "lint uses" --> eslintBase
Loading

File Walkthrough

Relevant files
Configuration changes
12 files
vite.config.ts
Remove app-local Vite config and plugins                                 
+0/-208 
eslint.config.base.ts
Add shared base ESLint flat config                                             
[link]   
eslint.config.ts
Remove app-local ESLint configuration                                       
+0/-15   
vite.config.ts
Remove app-local Vite config and plugins                                 
+0/-212 
tailwind.config.js
Remove app-local Tailwind configuration                                   
+0/-12   
tailwind.config.base.js
Add default Tailwind `content` globs                                         
+4/-0     
postcss.config.js
Remove app-local PostCSS configuration                                     
+0/-7     
tailwind.config.js
Remove app-local Tailwind config extension file                   
+0/-36   
package.json
Switch build/lint scripts to `edge-apps-scripts`                 
+2/-2     
tsconfig.vite-app.json
Bump TS target/lib and remove app paths                                   
+3/-9     
package.json
Switch build/lint scripts to `edge-apps-scripts`                 
+2/-2     
tsconfig.json
Remove app-local TypeScript configuration                               
+0/-26   
Enhancement
7 files
vite.config.with-plugins.ts
Add centralized Vite config with shared plugins                   
+58/-0   
cli.ts
Use shared Vite config and temp tsconfig                                 
+49/-13 
component-resolve.ts
Add plugin resolving library `.js` to `.ts`                           
+63/-0   
copy-manifest.ts
Add plugin copying `screenly.yml` manifests                           
+27/-0   
css-alias.ts
Add plugin resolving styles alias in CSS                                 
+18/-0   
index.ts
Export shared Vite plugins barrel                                               
+4/-0     
screenly-mock.ts
Add dev middleware generating mocked `screenly.js`             
+76/-0   
Formatting
1 files
postcss.config.js
Minor formatting cleanup in PostCSS config                             
+0/-1     
Dependencies
1 files
package.json
Export `./vite-plugins` and add build dependencies             
+10/-1   

…brary

Move all build configurations from clock and weather apps to edge-apps-library.
All config files removed from apps - edge-apps-scripts now handles everything.

Changes:
- Create shared Vite plugins (screenly-mock, copy-manifest, component-resolve, css-alias)
- Add centralized configs (vite, eslint, postcss, tailwind, tsconfig)
- Update edge-apps-scripts to use library configs and create temp tsconfig on-the-fly
- Remove all local config files from clock and weather apps
- Update clock/weather package.json to use edge-apps-scripts for build and lint
- Add autoprefixer, postcss, @tailwindcss/postcss, and yaml to library dependencies
@github-actions
Copy link

github-actions bot commented Feb 3, 2026

PR Reviewer Guide 🔍

(Review updated until commit cee67e6)

Here are some key observations to aid the review process:

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

Build Output

The build config sets emptyOutDir to false and uses custom Rollup output naming for JS/CSS. This can leave stale artifacts between builds and potentially cause collisions or unexpected assets in dist/ (especially across app upgrades). Confirm this is intentional for all consuming apps and won’t break deploy packaging expectations.

build: {
  outDir: 'dist',
  emptyOutDir: false,
  rollupOptions: {
    output: {
      entryFileNames: 'js/[name].js',
      assetFileNames: (assetInfo) => {
        if (assetInfo.name?.endsWith('.css')) {
          return 'css/[name][extname]'
        }
        return '[name][extname]'
      },
    },
  },
Temp Config

typeCheckCommand writes a fixed .tsconfig.temp.json into the caller project, which can conflict with parallel runs or overwrite an existing file. Consider using a unique temp filename (or a temp directory), and ensure cleanup is robust even under concurrent executions. Also validate the generated paths/extends are correct across different working directories and OS path rules.

async function typeCheckCommand(args: string[]) {
  try {
    const callerDir = process.cwd()
    const fs = await import('fs')

    // Get path to tsc binary in the library's node_modules
    const tscBin = path.resolve(libraryRoot, 'node_modules', '.bin', 'tsc')

    // Check if the app has TypeScript source files to check
    const srcDir = path.resolve(callerDir, 'src')
    const hasSrc = fs.existsSync(srcDir)

    if (!hasSrc) {
      console.log('No src directory found, skipping type check')
      return
    }

    // Create a temporary tsconfig in the app directory that extends the library's config
    const tempTsConfig = path.resolve(callerDir, '.tsconfig.temp.json')
    const baseTsConfig = path.resolve(libraryRoot, 'configs/tsconfig.vite-app.json')

    // Create temp config that extends base and adjusts paths for this app
    const tempConfig = {
      extends: path.relative(callerDir, baseTsConfig),
      compilerOptions: {
        baseUrl: '.',
        paths: {
          '@screenly/edge-apps': [path.relative(callerDir, path.resolve(libraryRoot, 'src/index.ts'))],
          '@screenly/edge-apps/*': [path.relative(callerDir, path.resolve(libraryRoot, 'src')) + '/*'],
        },
      },
      include: ['src'],
    }

    fs.writeFileSync(tempTsConfig, JSON.stringify(tempConfig, null, 2))

    try {
      // Build tsc command
      const tscArgs = [
        '--noEmit',
        '--project',
        tempTsConfig,
        ...args,
      ]

      execSync(`"${tscBin}" ${tscArgs.map((arg) => `"${arg}"`).join(' ')}`, {
        stdio: 'inherit',
        cwd: callerDir,
      })
    } finally {
      // Clean up temp config
      if (fs.existsSync(tempTsConfig)) {
        fs.unlinkSync(tempTsConfig)
      }
    }
Path Fragility

The resolver relies on string checks like importer.includes('/edge-apps-library/') and has several heuristics for mapping .js imports to .ts/.tsx. This can be brittle on Windows path separators and may mis-resolve edge cases (absolute ids, unusual importers, or symlinked paths). Consider normalizing paths and tightening the conditions to avoid incorrect resolutions.

resolveId(id, importer) {
  // Handle exact match for @screenly/edge-apps/components
  if (id === '@screenly/edge-apps/components') {
    return path.resolve(libraryRoot, 'src/components/index.ts')
  }
  // Handle sub-path imports like @screenly/edge-apps/components/app-header/app-header
  if (id.startsWith('@screenly/edge-apps/components/')) {
    const subPath = id.replace('@screenly/edge-apps/components/', '')
    // Try with .ts extension
    const tsPath = path.resolve(libraryRoot, 'src/components', `${subPath}.ts`)
    if (fs.existsSync(tsPath)) {
      return tsPath
    }
    // Try without extension (for imports that specify .js)
    const jsPath = path.resolve(
      libraryRoot,
      'src/components',
      `${subPath.replace(/\.js$/, '')}.ts`,
    )
    if (fs.existsSync(jsPath)) {
      return jsPath
    }
  }

  // Handle .js imports from edge-apps-library (resolve to .ts)
  if (importer && id.endsWith('.js') && !id.startsWith('http')) {
    // Skip node_modules
    if (id.includes('node_modules') || id.startsWith('@')) {
      return null
    }

    // Check if importer is from edge-apps-library
    if (importer.includes('/edge-apps-library/')) {
      const tsPath = id.replace(/\.js$/, '.ts')
      const importerDir = path.dirname(importer)
      const fullPath = path.resolve(importerDir, tsPath)
      if (fs.existsSync(fullPath)) {
        return fullPath
      }
      // Also try with .tsx extension
      const tsxPath = id.replace(/\.js$/, '.tsx')
      const fullTsxPath = path.resolve(importerDir, tsxPath)
      if (fs.existsSync(fullTsxPath)) {
        return fullTsxPath
      }
    }
  }

@github-actions
Copy link

github-actions bot commented Feb 3, 2026

Persistent review updated to latest commit cee67e6

@github-actions
Copy link

github-actions bot commented Feb 3, 2026

PR Code Suggestions ✨

Latest suggestions up to cee67e6
Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix config-time plugin imports

Avoid importing from ../src/vite-plugins/index.js inside a Vite config because that
file doesn’t exist on disk (and the resolve plugins aren’t active while loading the
config). Import the plugin modules directly from their .ts sources so the config can
be evaluated reliably.

edge-apps/edge-apps-library/configs/vite.config.with-plugins.ts [5-10]

-import {
-  screenlyMockPlugin,
-  copyScreenlyManifestPlugin,
-  componentResolvePlugin,
-  cssAliasPlugin,
-} from '../src/vite-plugins/index.js'
+import { screenlyMockPlugin } from '../src/vite-plugins/screenly-mock.ts'
+import { copyScreenlyManifestPlugin } from '../src/vite-plugins/copy-manifest.ts'
+import { componentResolvePlugin } from '../src/vite-plugins/component-resolve.ts'
+import { cssAliasPlugin } from '../src/vite-plugins/css-alias.ts'
Suggestion importance[1-10]: 8

__

Why: Importing ../src/vite-plugins/index.js is likely to fail because the on-disk file is index.ts, and Vite/esbuild config loading generally won’t remap an explicit .js specifier to .ts. Switching to direct .ts imports makes the config reliably evaluable and prevents a hard build-time failure.

Medium
Respect configured build output directory

Don’t hardcode dist or assume the output directory exists; builds can override
build.outDir, and copyFileSync will throw if the destination directory is missing.
Capture the resolved outDir via configResolved and mkdirSync the destination
directory before copying.

edge-apps/edge-apps-library/src/vite-plugins/copy-manifest.ts [8-27]

 export function copyScreenlyManifestPlugin(): Plugin {
+  let outDir = 'dist'
+
   return {
     name: 'copy-screenly-manifest',
+    configResolved(config) {
+      outDir = config.build.outDir || 'dist'
+    },
     closeBundle() {
-      const manifestSrc = path.resolve(process.cwd(), 'screenly.yml')
-      const manifestDest = path.resolve(process.cwd(), 'dist', 'screenly.yml')
+      const cwd = process.cwd()
+      const distDir = path.resolve(cwd, outDir)
+      fs.mkdirSync(distDir, { recursive: true })
 
-      const qcSrc = path.resolve(process.cwd(), 'screenly_qc.yml')
-      const qcDest = path.resolve(process.cwd(), 'dist', 'screenly_qc.yml')
+      const manifestSrc = path.resolve(cwd, 'screenly.yml')
+      const manifestDest = path.resolve(distDir, 'screenly.yml')
+
+      const qcSrc = path.resolve(cwd, 'screenly_qc.yml')
+      const qcDest = path.resolve(distDir, 'screenly_qc.yml')
 
       if (fs.existsSync(manifestSrc)) {
         fs.copyFileSync(manifestSrc, manifestDest)
       }
 
       if (fs.existsSync(qcSrc)) {
         fs.copyFileSync(qcSrc, qcDest)
       }
     },
   }
 }
Suggestion importance[1-10]: 6

__

Why: Using a hardcoded dist in copyScreenlyManifestPlugin() can break if build.outDir is changed, and fs.copyFileSync can throw if the destination directory doesn’t exist. Capturing config.build.outDir via configResolved and ensuring the directory exists improves robustness without changing intended behavior.

Low
Make path detection cross-platform

The substring check importer.includes('/edge-apps-library/') will fail on Windows
paths (backslashes), breaking .js.ts/.tsx resolution. Normalize importer to POSIX
separators before checking so this plugin works cross-platform.

edge-apps/edge-apps-library/src/vite-plugins/component-resolve.ts [44-58]

 // Check if importer is from edge-apps-library
-if (importer.includes('/edge-apps-library/')) {
+const normalizedImporter = importer.replaceAll('\\', '/')
+if (normalizedImporter.includes('/edge-apps-library/')) {
   const tsPath = id.replace(/\.js$/, '.ts')
   const importerDir = path.dirname(importer)
   const fullPath = path.resolve(importerDir, tsPath)
   if (fs.existsSync(fullPath)) {
     return fullPath
   }
   // Also try with .tsx extension
   const tsxPath = id.replace(/\.js$/, '.tsx')
   const fullTsxPath = path.resolve(importerDir, tsxPath)
   if (fs.existsSync(fullTsxPath)) {
     return fullTsxPath
   }
 }
Suggestion importance[1-10]: 5

__

Why: The check importer.includes('/edge-apps-library/') can fail on Windows due to backslash separators, preventing .js.ts/.tsx remapping. Normalizing importer before the substring check is a reasonable portability fix with low risk.

Low

Previous suggestions

Suggestions up to commit cee67e6
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix config import resolution

This import points to ../src/vite-plugins/index.js, but the source file in this PR
is index.ts, so Node/Vite may fail to resolve it when bundling the config. Import
the TS entry (or use an extensionless path) so the config can be loaded reliably.

edge-apps/edge-apps-library/configs/vite.config.with-plugins.ts [5-10]

 import {
   screenlyMockPlugin,
   copyScreenlyManifestPlugin,
   componentResolvePlugin,
   cssAliasPlugin,
-} from '../src/vite-plugins/index.js'
+} from '../src/vite-plugins/index.ts'
Suggestion importance[1-10]: 9

__

Why: The config currently imports ../src/vite-plugins/index.js, but this PR adds src/vite-plugins/index.ts and no corresponding .js file, so Vite loading the config is likely to fail at runtime. Switching to index.ts (or another resolvable specifier) is a correctness fix that unblocks builds.

High
Use valid module specifiers

These re-exports reference .js files that do not exist in the source tree (.ts files
were added), which can break runtime/module resolution depending on how the library
is consumed. Re-export using extensionless specifiers (or .ts) so both TS tooling
and bundlers can resolve the modules consistently.

edge-apps/edge-apps-library/src/vite-plugins/index.ts [1-4]

-export { screenlyMockPlugin } from './screenly-mock.js'
-export { copyScreenlyManifestPlugin } from './copy-manifest.js'
-export { componentResolvePlugin } from './component-resolve.js'
-export { cssAliasPlugin } from './css-alias.js'
+export { screenlyMockPlugin } from './screenly-mock'
+export { copyScreenlyManifestPlugin } from './copy-manifest'
+export { componentResolvePlugin } from './component-resolve'
+export { cssAliasPlugin } from './css-alias'
Suggestion importance[1-10]: 9

__

Why: The re-exports point to ./*.js files that don’t exist in the PR (.ts files were added), which can break module resolution when importing @screenly/edge-apps/vite-plugins. Using extensionless (or .ts) specifiers fixes a likely runtime/import failure.

High
Create output directory before copying

fs.copyFileSync will throw if the destination directory (dist/) does not exist yet
(e.g., first build or custom output cleaning). Ensure the output directory exists
before copying to prevent build failures.

edge-apps/edge-apps-library/src/vite-plugins/copy-manifest.ts [11-25]

 closeBundle() {
-  const manifestSrc = path.resolve(process.cwd(), 'screenly.yml')
-  const manifestDest = path.resolve(process.cwd(), 'dist', 'screenly.yml')
+  const cwd = process.cwd()
+  const outDir = path.resolve(cwd, 'dist')
+  fs.mkdirSync(outDir, { recursive: true })
 
-  const qcSrc = path.resolve(process.cwd(), 'screenly_qc.yml')
-  const qcDest = path.resolve(process.cwd(), 'dist', 'screenly_qc.yml')
+  const manifestSrc = path.resolve(cwd, 'screenly.yml')
+  const manifestDest = path.resolve(outDir, 'screenly.yml')
+
+  const qcSrc = path.resolve(cwd, 'screenly_qc.yml')
+  const qcDest = path.resolve(outDir, 'screenly_qc.yml')
 
   if (fs.existsSync(manifestSrc)) {
     fs.copyFileSync(manifestSrc, manifestDest)
   }
 
   if (fs.existsSync(qcSrc)) {
     fs.copyFileSync(qcSrc, qcDest)
   }
 },
Suggestion importance[1-10]: 5

__

Why: Ensuring dist/ exists before calling fs.copyFileSync makes the copyScreenlyManifestPlugin more robust, even if the output directory is missing or changed. It’s a defensive improvement, though Vite typically creates the output directory during a normal build.

Low

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 centralizes build configurations from the clock and weather apps into the edge-apps-library, reducing duplication and establishing a shared configuration pattern. The changes move Vite plugins, ESLint, PostCSS, Tailwind, and TypeScript configs to a central location, with edge-apps-scripts managing builds and linting.

Changes:

  • Created four shared Vite plugins (screenly-mock, copy-manifest, component-resolve, css-alias) and exported them from edge-apps-library
  • Added centralized configuration files (vite.config.with-plugins.ts, eslint.config.base.ts, postcss.config.js, tailwind.config.base.js, tsconfig.vite-app.json)
  • Updated edge-apps-scripts build and type-check commands to use centralized configs, with dynamic tsconfig generation for type checking
  • Removed all local config files from clock and weather apps, updating their package.json to use edge-apps-scripts for build and lint

Reviewed changes

Copilot reviewed 20 out of 22 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
edge-apps/weather/vite.config.ts Removed - moved to centralized config
edge-apps/weather/tsconfig.json Removed - apps now use centralized tsconfig via CLI
edge-apps/weather/tailwind.config.js Removed - uses base tailwind config
edge-apps/weather/postcss.config.js Removed - PostCSS config now in centralized vite config
edge-apps/weather/package.json Updated build and lint scripts to use edge-apps-scripts
edge-apps/weather/eslint.config.ts Removed - uses library's eslint config via CLI
edge-apps/clock/vite.config.ts Removed - moved to centralized config
edge-apps/clock/tailwind.config.js Removed - uses base tailwind config
edge-apps/clock/package.json Updated build and lint scripts to use edge-apps-scripts
edge-apps/edge-apps-library/src/vite-plugins/* Created shared plugins extracted from app configs
edge-apps/edge-apps-library/configs/vite.config.with-plugins.ts New centralized Vite config with all plugins and PostCSS setup
edge-apps/edge-apps-library/configs/tsconfig.vite-app.json New centralized TypeScript config for Vite apps (upgraded to ES2022)
edge-apps/edge-apps-library/configs/eslint.config.base.ts New base ESLint config for apps
edge-apps/edge-apps-library/configs/tailwind.config.base.js Added default content paths to base config
edge-apps/edge-apps-library/scripts/cli.ts Updated build commands to use centralized vite config; added dynamic tsconfig generation for type checking
edge-apps/edge-apps-library/package.json Added PostCSS/Tailwind dependencies and vite-plugins export
Comments suppressed due to low confidence (1)

edge-apps/edge-apps-library/configs/tsconfig.vite-app.json:6

  • The TypeScript target and lib have been changed from ES2020 to ES2022. This change introduces newer JavaScript features (like top-level await, class static blocks, etc.) that may not be supported in all target browsers or runtime environments where the edge apps will run. Ensure that all target platforms for Screenly Edge Apps support ES2022 features, or configure Vite to transpile to a lower target during the build process. If backwards compatibility is important, consider keeping ES2020 or documenting the minimum supported environments.

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

nicomiguelino and others added 2 commits February 3, 2026 14:22
…ort.meta errors

Added explicit `format: 'iife'` to the Vite build configuration to ensure
compatibility with non-module script tags. Without this, Vite defaults to
ESM output which includes import.meta, causing "Cannot use 'import.meta'
outside a module" errors when scripts are loaded without type="module".

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@nicomiguelino nicomiguelino changed the title refactor(edge-apps): centralize clock/weather configs to edge-apps-library fix(edge-apps-library): set Vite output format to IIFE to prevent import.meta errors Feb 3, 2026
@nicomiguelino nicomiguelino merged commit 437491a into edge-app-framework Feb 3, 2026
4 checks passed
@nicomiguelino nicomiguelino deleted the nicomiguelino/edge-app-framework/patch-06 branch February 3, 2026 23:49
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.

1 participant