@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.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { run } from '../scripts/cli.js'
4
+
5
+ run()
@@ -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. 'bun add -d @playwright/test') " +
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.0",
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.ts"
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": "bun run build",
59
- "prebuild": "bun run type-check",
60
- "build": "bun build:types && bun run copy:assets",
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": "bun run scripts/copy-assets.ts",
63
- "test": "bun test --parallel",
64
- "test:unit": "bun test --parallel",
65
- "test:coverage": "bun test --coverage --parallel",
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
- "bun-types": "^1.3.10",
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 bun
2
- /**
3
- * CLI command dispatcher for edge-apps-scripts
4
- */
1
+ #!/usr/bin/env node
5
2
 
6
- import { execSync, execFileSync, spawn, type ChildProcess } from 'child_process'
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
- * Helper: Setup signal handlers for spawned processes
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: string[]) {
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: string[]) {
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: string[]) {
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: string[]) {
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: string[]) {
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: string): Promise<void> {
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: string[]) {
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 as keyof typeof commands].handler
266
+ const handler = commands[cmd].handler
284
267
  await handler(cmdArgs)
285
268
  }
286
269
 
@@ -1,7 +1,4 @@
1
- #!/usr/bin/env bun
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
- // Listen on a specific host via the HOST environment variable
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
- const server = http.createServer((req, res) => {
146
- handleProxyRequest(req, res)
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
- // Start the server
150
- server.listen(port, host, () => {
151
- console.log(
152
- `Running CORS Proxy (Node.js built-ins) on http://${host}:${port}`,
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: string): string {
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: string): string[] {
32
- const results: string[] = []
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: string[]) {
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: Record<string, unknown>
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: Record<string, string> = {
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
- bun install
107
+ npm install
111
108
 
112
109
  3. Start the dev server:
113
- bun run dev
110
+ npm run dev
114
111
 
115
112
  4. Deploy when ready:
116
- bun run deploy
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": ["bun-types", "vite/client"]
17
+ "types": ["vite/client"]
18
18
  },
19
19
  "include": ["src/**/*"],
20
20
  "exclude": ["node_modules", "dist", "**/*.test.ts"]
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * Edge Apps Scripts CLI
4
- * Wrapper around build tools for Edge Apps
5
- */
6
-
7
- import { run } from '../scripts/cli'
8
-
9
- run()