@screenly/edge-apps 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@screenly/edge-apps",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "A TypeScript library for interfacing with Screenly Edge Apps API",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -46,7 +46,10 @@
46
46
  "bin",
47
47
  "configs",
48
48
  "scripts",
49
+ "vite-plugins",
49
50
  "eslint.config.ts",
51
+ "tsconfig.json",
52
+ "vite.config.ts",
50
53
  "src/styles",
51
54
  "src/assets"
52
55
  ],
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "lib": ["ES2022", "DOM"],
6
+ "moduleResolution": "bundler",
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "outDir": "./dist",
10
+ "rootDir": "./src",
11
+ "strict": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "resolveJsonModule": true,
16
+ "allowSyntheticDefaultImports": true,
17
+ "types": ["bun-types"]
18
+ },
19
+ "include": ["src/**/*"],
20
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
21
+ }
@@ -0,0 +1,191 @@
1
+ import type { ViteDevServer, Plugin } from 'vite'
2
+ import YAML from 'yaml'
3
+ import fs from 'fs'
4
+ import path from 'path'
5
+
6
+ type ScreenlyManifestField = {
7
+ type: string
8
+ default_value?: string
9
+ title: string
10
+ optional: boolean
11
+ is_global?: boolean
12
+ help_text: string | Record<string, unknown>
13
+ }
14
+
15
+ type BaseScreenlyMockData = {
16
+ metadata: {
17
+ coordinates: [number, number]
18
+ hostname: string
19
+ screen_name: string
20
+ hardware: string
21
+ location: string
22
+ screenly_version: string
23
+ tags: string[]
24
+ }
25
+ settings: Record<string, string>
26
+ cors_proxy_url: string
27
+ }
28
+
29
+ const defaultScreenlyConfig: BaseScreenlyMockData = {
30
+ metadata: {
31
+ coordinates: [37.3861, -122.0839] as [number, number],
32
+ hostname: 'dev-hostname',
33
+ screen_name: 'Development Server',
34
+ hardware: 'x86',
35
+ location: 'Development Environment',
36
+ screenly_version: 'development-server',
37
+ tags: ['Development'],
38
+ },
39
+ settings: {
40
+ enable_analytics: 'true',
41
+ tag_manager_id: '',
42
+ theme: 'light',
43
+ screenly_color_accent: '#972EFF',
44
+ screenly_color_light: '#ADAFBE',
45
+ screenly_color_dark: '#454BD2',
46
+ },
47
+ cors_proxy_url: 'http://127.0.0.1:8080',
48
+ }
49
+
50
+ function generateScreenlyObject(config: BaseScreenlyMockData) {
51
+ return `
52
+ // Generated screenly.js for development mode
53
+ window.screenly = {
54
+ signalReadyForRendering: () => {},
55
+ metadata: ${JSON.stringify(config.metadata, null, 2)},
56
+ settings: ${JSON.stringify(config.settings, null, 2)},
57
+ cors_proxy_url: ${JSON.stringify(config.cors_proxy_url)}
58
+ }
59
+ `
60
+ }
61
+
62
+ function generateMockData(
63
+ rootDir: string,
64
+ previousConfig: BaseScreenlyMockData = defaultScreenlyConfig,
65
+ ): BaseScreenlyMockData {
66
+ const manifestPath = path.resolve(rootDir, 'screenly.yml')
67
+ const mockDataPath = path.resolve(rootDir, 'mock-data.yml')
68
+
69
+ let manifest: Record<string, unknown>
70
+ try {
71
+ if (!fs.existsSync(manifestPath)) {
72
+ console.warn(
73
+ `screenly.yml not found at ${manifestPath}, using previous config.`,
74
+ )
75
+ return previousConfig
76
+ }
77
+ manifest = YAML.parse(fs.readFileSync(manifestPath, 'utf8'))
78
+ } catch (error) {
79
+ const message = error instanceof Error ? error.message : 'Unknown error'
80
+ console.warn(
81
+ `Failed to parse screenly.yml: ${message}. Using previous config.`,
82
+ )
83
+ return previousConfig
84
+ }
85
+
86
+ const screenlyConfig: BaseScreenlyMockData = structuredClone(
87
+ defaultScreenlyConfig,
88
+ )
89
+
90
+ // Merge settings from manifest
91
+ if (manifest?.settings && typeof manifest.settings === 'object') {
92
+ for (const [key, value] of Object.entries(manifest.settings) as [
93
+ string,
94
+ ScreenlyManifestField,
95
+ ][]) {
96
+ if (value.type === 'string' || value.type === 'secret') {
97
+ const manifestField: ScreenlyManifestField = value
98
+ const defaultValue = manifestField?.default_value ?? ''
99
+ screenlyConfig.settings[key] = defaultValue
100
+ }
101
+ }
102
+ }
103
+
104
+ applyMockDataOverrides(mockDataPath, screenlyConfig)
105
+
106
+ return screenlyConfig
107
+ }
108
+
109
+ function applyMockDataOverrides(
110
+ mockDataPath: string,
111
+ screenlyConfig: BaseScreenlyMockData,
112
+ ): void {
113
+ if (!fs.existsSync(mockDataPath)) return
114
+
115
+ // Typed as unknown since YAML.parse() can return any value (string, array, null, etc.)
116
+ let parsedMockData: unknown
117
+ try {
118
+ parsedMockData = YAML.parse(fs.readFileSync(mockDataPath, 'utf8'))
119
+ } catch (error) {
120
+ const message = error instanceof Error ? error.message : 'Unknown error'
121
+ console.warn(
122
+ `Failed to parse mock-data.yml: ${message}. Keeping config without mock-data overrides.`,
123
+ )
124
+ return
125
+ }
126
+
127
+ // Narrow to a plain object after ruling out null, primitives, and arrays
128
+ if (
129
+ !parsedMockData ||
130
+ typeof parsedMockData !== 'object' ||
131
+ Array.isArray(parsedMockData)
132
+ )
133
+ return
134
+
135
+ const mockData = parsedMockData as Record<string, unknown>
136
+
137
+ const { metadata, settings, cors_proxy_url } = mockData
138
+
139
+ if (metadata && typeof metadata === 'object' && !Array.isArray(metadata)) {
140
+ Object.assign(screenlyConfig.metadata, metadata)
141
+ }
142
+ if (settings && typeof settings === 'object' && !Array.isArray(settings)) {
143
+ Object.assign(screenlyConfig.settings, settings)
144
+ }
145
+ if (typeof cors_proxy_url === 'string' && cors_proxy_url.length > 0) {
146
+ screenlyConfig.cors_proxy_url = cors_proxy_url
147
+ }
148
+ }
149
+
150
+ export function screenlyDevServer(): Plugin {
151
+ let config: BaseScreenlyMockData
152
+ let rootDir: string
153
+
154
+ return {
155
+ name: 'screenly-dev-server',
156
+ configureServer(server: ViteDevServer) {
157
+ rootDir = server.config.root
158
+
159
+ // Generate initial mock data
160
+ config = generateMockData(rootDir)
161
+
162
+ // Watch for changes to screenly.yml and mock-data.yml using Vite's watcher
163
+ const manifestPath = path.resolve(rootDir, 'screenly.yml')
164
+ const mockDataPath = path.resolve(rootDir, 'mock-data.yml')
165
+
166
+ const handleConfigFileChange = (file: string) => {
167
+ if (file === manifestPath || file === mockDataPath) {
168
+ config = generateMockData(rootDir, config)
169
+ // Notify connected clients to perform a full reload so they pick up the new mock config
170
+ server.ws.send({ type: 'full-reload', path: '*' })
171
+ }
172
+ }
173
+
174
+ server.watcher.add([manifestPath, mockDataPath])
175
+ server.watcher.on('add', handleConfigFileChange)
176
+ server.watcher.on('change', handleConfigFileChange)
177
+ server.watcher.on('unlink', handleConfigFileChange)
178
+
179
+ server.middlewares.use((req, res, next) => {
180
+ if (req.url === '/screenly.js?version=1') {
181
+ const screenlyJsContent = generateScreenlyObject(config)
182
+
183
+ res.setHeader('Content-Type', 'application/javascript')
184
+ res.end(screenlyJsContent)
185
+ return
186
+ }
187
+ next()
188
+ })
189
+ },
190
+ }
191
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,70 @@
1
+ import { defineConfig, Plugin } from 'vite'
2
+ import tailwindcss from '@tailwindcss/vite'
3
+ import { copyFileSync, existsSync } from 'fs'
4
+ import { resolve, dirname } from 'path'
5
+ import { fileURLToPath } from 'url'
6
+ import { screenlyDevServer } from './vite-plugins/dev-server'
7
+
8
+ const __filename = fileURLToPath(import.meta.url)
9
+ const __dirname = dirname(__filename)
10
+
11
+ function copyScreenlyFiles(): Plugin {
12
+ return {
13
+ name: 'copy-screenly-files',
14
+ closeBundle() {
15
+ const filesToCopy = ['screenly.yml', 'screenly_qc.yml', 'instance.yml']
16
+
17
+ for (const file of filesToCopy) {
18
+ const srcPath = resolve(process.cwd(), file)
19
+ if (existsSync(srcPath)) {
20
+ const destPath = resolve(process.cwd(), 'dist', file)
21
+ try {
22
+ copyFileSync(srcPath, destPath)
23
+ console.log(`Copied ${file} to dist/`)
24
+ } catch (error) {
25
+ const message =
26
+ error instanceof Error ? error.message : 'Unknown error'
27
+ throw new Error(
28
+ `Failed to copy "${file}" to dist/: ${message}`,
29
+ { cause: error },
30
+ )
31
+ }
32
+ }
33
+ }
34
+ },
35
+ }
36
+ }
37
+
38
+ export default defineConfig({
39
+ base: '',
40
+ server: {
41
+ fs: {
42
+ // Allow serving files from both the app directory and the library directory
43
+ allow: [
44
+ process.cwd(), // The edge app directory (e.g., edge-apps/clock)
45
+ resolve(__dirname), // The edge-apps-library directory
46
+ ],
47
+ },
48
+ },
49
+ build: {
50
+ cssCodeSplit: false,
51
+ assetsInlineLimit: 7000000,
52
+ minify: true,
53
+ rollupOptions: {
54
+ input: 'index.html',
55
+ output: {
56
+ dir: 'dist',
57
+ entryFileNames: 'js/[name].js',
58
+ format: 'iife',
59
+ assetFileNames: (assetInfo) => {
60
+ if (assetInfo.names?.[0]?.endsWith('.css')) {
61
+ return 'css/style.css'
62
+ }
63
+
64
+ return 'assets/[name]-[hash][extname]'
65
+ },
66
+ },
67
+ },
68
+ },
69
+ plugins: [tailwindcss(), screenlyDevServer(), copyScreenlyFiles()],
70
+ })