@screenly/edge-apps 0.1.0 → 1.0.0-rc.1
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/bin/edge-apps-scripts.js +5 -0
- package/configs/playwright.js +1 -1
- package/eslint.config.ts +7 -0
- package/package.json +16 -12
- package/scripts/{cli.ts → cli.js} +23 -40
- package/scripts/{copy-assets.ts → copy-assets.js} +1 -7
- package/scripts/{cors-proxy-server.ts → cors-proxy-server.js} +18 -30
- package/scripts/{create.ts → create.js} +10 -13
- package/tsconfig.json +1 -1
- package/bin/edge-apps-scripts.ts +0 -9
package/configs/playwright.js
CHANGED
|
@@ -18,7 +18,7 @@ try {
|
|
|
18
18
|
} catch {
|
|
19
19
|
throw new Error(
|
|
20
20
|
"Failed to resolve '@playwright/test' from the application. " +
|
|
21
|
-
"Please install it in your app (e.g. '
|
|
21
|
+
"Please install it in your app (e.g. 'npm install -D @playwright/test') " +
|
|
22
22
|
'and try again.',
|
|
23
23
|
)
|
|
24
24
|
}
|
package/eslint.config.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import eslint from '@eslint/js'
|
|
2
2
|
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
3
|
+
import globals from 'globals'
|
|
3
4
|
import tseslint from 'typescript-eslint'
|
|
4
5
|
|
|
5
6
|
export default defineConfig(
|
|
@@ -34,6 +35,12 @@ export default defineConfig(
|
|
|
34
35
|
}],
|
|
35
36
|
},
|
|
36
37
|
},
|
|
38
|
+
{
|
|
39
|
+
files: ['scripts/**/*.js', 'bin/**/*.js'],
|
|
40
|
+
languageOptions: {
|
|
41
|
+
globals: globals.node,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
37
44
|
{
|
|
38
45
|
files: ['**/*.test.ts', '**/*.spec.ts'],
|
|
39
46
|
rules: {
|
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@screenly/edge-apps",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.0.0-rc.1",
|
|
4
4
|
"description": "A TypeScript library for interfacing with Screenly Edge Apps API",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20.6.0"
|
|
8
|
+
},
|
|
6
9
|
"main": "./dist/index.js",
|
|
7
10
|
"module": "./dist/index.js",
|
|
8
11
|
"types": "./dist/index.d.ts",
|
|
9
12
|
"bin": {
|
|
10
|
-
"edge-apps-scripts": "./bin/edge-apps-scripts.
|
|
13
|
+
"edge-apps-scripts": "./bin/edge-apps-scripts.js"
|
|
11
14
|
},
|
|
12
15
|
"exports": {
|
|
13
16
|
".": {
|
|
@@ -55,14 +58,14 @@
|
|
|
55
58
|
"src/assets"
|
|
56
59
|
],
|
|
57
60
|
"scripts": {
|
|
58
|
-
"prepublishOnly": "
|
|
59
|
-
"prebuild": "
|
|
60
|
-
"build": "
|
|
61
|
+
"prepublishOnly": "npm run build",
|
|
62
|
+
"prebuild": "npm run type-check",
|
|
63
|
+
"build": "npm run build:types && npm run copy:assets",
|
|
61
64
|
"build:types": "tsc",
|
|
62
|
-
"copy:assets": "
|
|
63
|
-
"test": "
|
|
64
|
-
"test:unit": "
|
|
65
|
-
"test:coverage": "
|
|
65
|
+
"copy:assets": "node scripts/copy-assets.js",
|
|
66
|
+
"test": "vitest run",
|
|
67
|
+
"test:unit": "vitest run",
|
|
68
|
+
"test:coverage": "vitest run --coverage",
|
|
66
69
|
"lint": "eslint . --fix",
|
|
67
70
|
"format": "prettier --write src/ scripts/ vite-plugins/ README.md index.html",
|
|
68
71
|
"format:check": "prettier --check src/ scripts/ vite-plugins/ README.md index.html",
|
|
@@ -105,12 +108,13 @@
|
|
|
105
108
|
}
|
|
106
109
|
},
|
|
107
110
|
"devDependencies": {
|
|
108
|
-
"@types/bun": "^1.3.10",
|
|
109
111
|
"@types/jsdom": "^28.0.0",
|
|
110
112
|
"@types/node": "^25.4.0",
|
|
111
113
|
"@types/sharp": "^0.32.0",
|
|
112
|
-
"
|
|
114
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
115
|
+
"globals": "^13.24.0",
|
|
113
116
|
"jsdom": "^28.1.0",
|
|
114
|
-
"prettier": "^3.8.1"
|
|
117
|
+
"prettier": "^3.8.1",
|
|
118
|
+
"vitest": "^3.2.4"
|
|
115
119
|
}
|
|
116
120
|
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
2
|
-
/**
|
|
3
|
-
* CLI command dispatcher for edge-apps-scripts
|
|
4
|
-
*/
|
|
1
|
+
#!/usr/bin/env node
|
|
5
2
|
|
|
6
|
-
import { execSync, execFileSync, spawn
|
|
3
|
+
import { execSync, execFileSync, spawn } from 'child_process'
|
|
7
4
|
import fs from 'fs'
|
|
8
5
|
import path from 'path'
|
|
9
6
|
import { fileURLToPath } from 'url'
|
|
10
|
-
import { createCommand } from './create'
|
|
7
|
+
import { createCommand } from './create.js'
|
|
8
|
+
import { start as startCorsProxy } from './cors-proxy-server.js'
|
|
11
9
|
|
|
12
10
|
const __filename = fileURLToPath(import.meta.url)
|
|
13
11
|
const __dirname = path.dirname(__filename)
|
|
@@ -38,6 +36,10 @@ const commands = {
|
|
|
38
36
|
description: 'Capture screenshots at all supported resolutions',
|
|
39
37
|
handler: screenshotsCommand,
|
|
40
38
|
},
|
|
39
|
+
'cors-proxy': {
|
|
40
|
+
description: 'Start the CORS proxy server for local development',
|
|
41
|
+
handler: startCorsProxy,
|
|
42
|
+
},
|
|
41
43
|
create: {
|
|
42
44
|
description:
|
|
43
45
|
'Initialize a scaffolded Edge App (replaces template placeholders)',
|
|
@@ -45,20 +47,14 @@ const commands = {
|
|
|
45
47
|
},
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
* Helper: Resolve a binary from the app's node_modules first, falling back to the library's
|
|
50
|
-
*/
|
|
51
|
-
function resolveBin(name: string): string {
|
|
50
|
+
function resolveBin(name) {
|
|
52
51
|
const appBin = path.resolve(process.cwd(), 'node_modules', '.bin', name)
|
|
53
52
|
return fs.existsSync(appBin)
|
|
54
53
|
? appBin
|
|
55
54
|
: path.resolve(libraryRoot, 'node_modules', '.bin', name)
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
* Helper: Get NODE_PATH with library's node_modules included
|
|
60
|
-
*/
|
|
61
|
-
function getNodePath(): string {
|
|
57
|
+
function getNodePath() {
|
|
62
58
|
const libraryNodeModules = path.resolve(libraryRoot, 'node_modules')
|
|
63
59
|
const existingNodePath = process.env.NODE_PATH || ''
|
|
64
60
|
return existingNodePath
|
|
@@ -66,26 +62,16 @@ function getNodePath(): string {
|
|
|
66
62
|
: libraryNodeModules
|
|
67
63
|
}
|
|
68
64
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
function setupSignalHandlers(child: ChildProcess): void {
|
|
73
|
-
const handleSignal = (signal: string) => {
|
|
74
|
-
child.kill(signal as NodeJS.Signals)
|
|
65
|
+
function setupSignalHandlers(child) {
|
|
66
|
+
const handleSignal = (signal) => {
|
|
67
|
+
child.kill(signal)
|
|
75
68
|
child.on('exit', () => process.exit(0))
|
|
76
69
|
}
|
|
77
70
|
process.on('SIGINT', () => handleSignal('SIGINT'))
|
|
78
71
|
process.on('SIGTERM', () => handleSignal('SIGTERM'))
|
|
79
72
|
}
|
|
80
73
|
|
|
81
|
-
|
|
82
|
-
* Helper: Spawn a process with common options and signal handling
|
|
83
|
-
*/
|
|
84
|
-
function spawnWithSignalHandling(
|
|
85
|
-
command: string,
|
|
86
|
-
args: string[],
|
|
87
|
-
errorMessage: string,
|
|
88
|
-
): void {
|
|
74
|
+
function spawnWithSignalHandling(command, args, errorMessage) {
|
|
89
75
|
const child = spawn(command, args, {
|
|
90
76
|
stdio: 'inherit',
|
|
91
77
|
cwd: process.cwd(),
|
|
@@ -104,17 +90,14 @@ function spawnWithSignalHandling(
|
|
|
104
90
|
setupSignalHandlers(child)
|
|
105
91
|
}
|
|
106
92
|
|
|
107
|
-
|
|
108
|
-
* Helper: Get Vite binary and config paths
|
|
109
|
-
*/
|
|
110
|
-
function getVitePaths(): { viteBin: string; configPath: string } {
|
|
93
|
+
function getVitePaths() {
|
|
111
94
|
return {
|
|
112
95
|
viteBin: resolveBin('vite'),
|
|
113
96
|
configPath: path.resolve(libraryRoot, 'vite.config.ts'),
|
|
114
97
|
}
|
|
115
98
|
}
|
|
116
99
|
|
|
117
|
-
async function lintCommand(args
|
|
100
|
+
async function lintCommand(args) {
|
|
118
101
|
try {
|
|
119
102
|
const eslintBin = resolveBin('eslint')
|
|
120
103
|
|
|
@@ -137,7 +120,7 @@ async function lintCommand(args: string[]) {
|
|
|
137
120
|
}
|
|
138
121
|
}
|
|
139
122
|
|
|
140
|
-
async function devCommand(args
|
|
123
|
+
async function devCommand(args) {
|
|
141
124
|
try {
|
|
142
125
|
const { viteBin, configPath } = getVitePaths()
|
|
143
126
|
const viteArgs = ['--config', configPath, ...args]
|
|
@@ -148,7 +131,7 @@ async function devCommand(args: string[]) {
|
|
|
148
131
|
}
|
|
149
132
|
}
|
|
150
133
|
|
|
151
|
-
async function buildCommand(args
|
|
134
|
+
async function buildCommand(args) {
|
|
152
135
|
try {
|
|
153
136
|
const { viteBin, configPath } = getVitePaths()
|
|
154
137
|
const sourcemaps = args.includes('--sourcemaps')
|
|
@@ -174,7 +157,7 @@ async function buildCommand(args: string[]) {
|
|
|
174
157
|
}
|
|
175
158
|
}
|
|
176
159
|
|
|
177
|
-
async function buildDevCommand(args
|
|
160
|
+
async function buildDevCommand(args) {
|
|
178
161
|
try {
|
|
179
162
|
const { viteBin, configPath } = getVitePaths()
|
|
180
163
|
const viteArgs = [
|
|
@@ -192,7 +175,7 @@ async function buildDevCommand(args: string[]) {
|
|
|
192
175
|
}
|
|
193
176
|
}
|
|
194
177
|
|
|
195
|
-
async function typeCheckCommand(args
|
|
178
|
+
async function typeCheckCommand(args) {
|
|
196
179
|
try {
|
|
197
180
|
const tscBin = resolveBin('tsc')
|
|
198
181
|
|
|
@@ -212,7 +195,7 @@ async function typeCheckCommand(args: string[]) {
|
|
|
212
195
|
}
|
|
213
196
|
}
|
|
214
197
|
|
|
215
|
-
async function convertPngsToWebP(screenshotsDir
|
|
198
|
+
async function convertPngsToWebP(screenshotsDir) {
|
|
216
199
|
const { default: sharp } = await import('sharp')
|
|
217
200
|
const pngFiles = fs
|
|
218
201
|
.readdirSync(screenshotsDir)
|
|
@@ -226,7 +209,7 @@ async function convertPngsToWebP(screenshotsDir: string): Promise<void> {
|
|
|
226
209
|
}
|
|
227
210
|
}
|
|
228
211
|
|
|
229
|
-
async function screenshotsCommand(_args
|
|
212
|
+
async function screenshotsCommand(_args) {
|
|
230
213
|
try {
|
|
231
214
|
const playwrightBin = resolveBin('playwright')
|
|
232
215
|
const playwrightConfig = path.resolve(
|
|
@@ -280,7 +263,7 @@ export async function run() {
|
|
|
280
263
|
process.exit(1)
|
|
281
264
|
}
|
|
282
265
|
|
|
283
|
-
const handler = commands[cmd
|
|
266
|
+
const handler = commands[cmd].handler
|
|
284
267
|
await handler(cmdArgs)
|
|
285
268
|
}
|
|
286
269
|
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
2
|
-
/**
|
|
3
|
-
* Copy assets from src to dist after TypeScript compilation
|
|
4
|
-
*/
|
|
1
|
+
#!/usr/bin/env node
|
|
5
2
|
|
|
6
3
|
import { cpSync, existsSync, mkdirSync } from 'fs'
|
|
7
4
|
import { dirname } from 'path'
|
|
@@ -15,10 +12,7 @@ const srcAssets = `${libraryRoot}/src/assets`
|
|
|
15
12
|
const distAssets = `${libraryRoot}/dist/assets`
|
|
16
13
|
|
|
17
14
|
if (existsSync(srcAssets)) {
|
|
18
|
-
// Ensure dist directory exists
|
|
19
15
|
mkdirSync(dirname(distAssets), { recursive: true })
|
|
20
|
-
|
|
21
|
-
// Copy assets
|
|
22
16
|
cpSync(srcAssets, distAssets, { recursive: true })
|
|
23
17
|
console.log('✓ Copied assets to dist/')
|
|
24
18
|
} else {
|
|
@@ -2,17 +2,7 @@ import http from 'http'
|
|
|
2
2
|
import https from 'https'
|
|
3
3
|
import { URL } from 'url'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
const host = process.env.HOST || '0.0.0.0'
|
|
7
|
-
// Listen on a specific port via the PORT environment variable
|
|
8
|
-
const port = process.env.PORT || 8080
|
|
9
|
-
|
|
10
|
-
// This script is for development purposes only and should never be deployed to production.
|
|
11
|
-
// Disabling TLS certificate validation is a critical security risk.
|
|
12
|
-
// Only use this in local development environments.
|
|
13
|
-
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
|
14
|
-
|
|
15
|
-
function setCorsHeaders(res: http.ServerResponse) {
|
|
5
|
+
function setCorsHeaders(res) {
|
|
16
6
|
res.setHeader('Access-Control-Allow-Origin', '*')
|
|
17
7
|
res.setHeader(
|
|
18
8
|
'Access-Control-Allow-Methods',
|
|
@@ -22,12 +12,7 @@ function setCorsHeaders(res: http.ServerResponse) {
|
|
|
22
12
|
res.setHeader('Access-Control-Max-Age', '86400')
|
|
23
13
|
}
|
|
24
14
|
|
|
25
|
-
function makeProxyRequest(
|
|
26
|
-
httpModule: typeof http | typeof https,
|
|
27
|
-
parsedUrl: URL,
|
|
28
|
-
req: http.IncomingMessage,
|
|
29
|
-
res: http.ServerResponse,
|
|
30
|
-
) {
|
|
15
|
+
function makeProxyRequest(httpModule, parsedUrl, req, res) {
|
|
31
16
|
const options = {
|
|
32
17
|
hostname: parsedUrl.hostname,
|
|
33
18
|
port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
|
|
@@ -98,10 +83,7 @@ function makeProxyRequest(
|
|
|
98
83
|
}
|
|
99
84
|
}
|
|
100
85
|
|
|
101
|
-
function handleProxyRequest(
|
|
102
|
-
req: http.IncomingMessage,
|
|
103
|
-
res: http.ServerResponse,
|
|
104
|
-
) {
|
|
86
|
+
function handleProxyRequest(req, res) {
|
|
105
87
|
setCorsHeaders(res)
|
|
106
88
|
|
|
107
89
|
if (req.method === 'OPTIONS') {
|
|
@@ -142,13 +124,19 @@ function handleProxyRequest(
|
|
|
142
124
|
}
|
|
143
125
|
}
|
|
144
126
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
127
|
+
export function start() {
|
|
128
|
+
const host = process.env.HOST || '0.0.0.0'
|
|
129
|
+
const port = process.env.PORT || 8080
|
|
148
130
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
)
|
|
154
|
-
|
|
131
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
|
132
|
+
|
|
133
|
+
const server = http.createServer((req, res) => {
|
|
134
|
+
handleProxyRequest(req, res)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
server.listen(port, host, () => {
|
|
138
|
+
console.log(
|
|
139
|
+
`Running CORS Proxy (Node.js built-ins) on http://${host}:${port}`,
|
|
140
|
+
)
|
|
141
|
+
})
|
|
142
|
+
}
|
|
@@ -21,15 +21,15 @@ const TEXT_EXTENSIONS = new Set([
|
|
|
21
21
|
|
|
22
22
|
const SKIP_DIRS = new Set(['node_modules', 'dist', '.git'])
|
|
23
23
|
|
|
24
|
-
function toTitleCase(kebab
|
|
24
|
+
function toTitleCase(kebab) {
|
|
25
25
|
return kebab
|
|
26
26
|
.split('-')
|
|
27
27
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
28
28
|
.join(' ')
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function walkTextFiles(dir
|
|
32
|
-
const results
|
|
31
|
+
function walkTextFiles(dir) {
|
|
32
|
+
const results = []
|
|
33
33
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
34
34
|
const fullPath = path.join(dir, entry.name)
|
|
35
35
|
if (entry.isDirectory()) {
|
|
@@ -45,10 +45,7 @@ function walkTextFiles(dir: string): string[] {
|
|
|
45
45
|
return results
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function replaceInFile(
|
|
49
|
-
filePath: string,
|
|
50
|
-
replacements: Record<string, string>,
|
|
51
|
-
): void {
|
|
48
|
+
function replaceInFile(filePath, replacements) {
|
|
52
49
|
const original = fs.readFileSync(filePath, 'utf-8')
|
|
53
50
|
const updated = Object.entries(replacements).reduce(
|
|
54
51
|
(src, [placeholder, value]) => src.replaceAll(placeholder, value),
|
|
@@ -57,11 +54,11 @@ function replaceInFile(
|
|
|
57
54
|
if (updated !== original) fs.writeFileSync(filePath, updated, 'utf-8')
|
|
58
55
|
}
|
|
59
56
|
|
|
60
|
-
export async function createCommand(_args
|
|
57
|
+
export async function createCommand(_args) {
|
|
61
58
|
const projectRoot = process.cwd()
|
|
62
59
|
const pkgPath = path.join(projectRoot, 'package.json')
|
|
63
60
|
|
|
64
|
-
let pkg
|
|
61
|
+
let pkg
|
|
65
62
|
try {
|
|
66
63
|
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
|
|
67
64
|
} catch (error) {
|
|
@@ -84,7 +81,7 @@ export async function createCommand(_args: string[]) {
|
|
|
84
81
|
const appTitle = toTitleCase(appName)
|
|
85
82
|
const appDescription = `${appTitle} - Screenly Edge App`
|
|
86
83
|
|
|
87
|
-
const replacements
|
|
84
|
+
const replacements = {
|
|
88
85
|
'{{APP_NAME}}': appName,
|
|
89
86
|
'{{APP_TITLE}}': appTitle,
|
|
90
87
|
'{{APP_DESCRIPTION}}': appDescription,
|
|
@@ -107,12 +104,12 @@ Next steps:
|
|
|
107
104
|
1. Add an id field to screenly.yml and screenly_qc.yml.
|
|
108
105
|
|
|
109
106
|
2. Install dependencies:
|
|
110
|
-
|
|
107
|
+
npm install
|
|
111
108
|
|
|
112
109
|
3. Start the dev server:
|
|
113
|
-
|
|
110
|
+
npm run dev
|
|
114
111
|
|
|
115
112
|
4. Deploy when ready:
|
|
116
|
-
|
|
113
|
+
npm run deploy
|
|
117
114
|
`)
|
|
118
115
|
}
|
package/tsconfig.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"forceConsistentCasingInFileNames": true,
|
|
15
15
|
"resolveJsonModule": true,
|
|
16
16
|
"allowSyntheticDefaultImports": true,
|
|
17
|
-
"types": ["
|
|
17
|
+
"types": ["vite/client"]
|
|
18
18
|
},
|
|
19
19
|
"include": ["src/**/*"],
|
|
20
20
|
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|