@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 +4 -1
- package/tsconfig.json +21 -0
- package/vite-plugins/dev-server.ts +191 -0
- package/vite.config.ts +70 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@screenly/edge-apps",
|
|
3
|
-
"version": "0.0.
|
|
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
|
+
})
|