Skip to content

Commit 47184d2

Browse files
committed
style: apply Biome formatting and update documentation
Apply comprehensive Biome formatting pass across the codebase including import ordering, line breaks, and trailing commas. Update biome.json with additional ignore patterns for task files, data directory, and bundled static assets. Update CHANGELOG.md with recent go2rtc migration, Discord webhook support, and build system changes. Refresh CLAUDE.md documentation to reflect current architecture and features.
1 parent be81414 commit 47184d2

105 files changed

Lines changed: 2978 additions & 2525 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- go2rtc-based camera streaming with:
13+
- bundled platform binaries in `resources/bin/`
14+
- `scripts/download-go2rtc.cjs` postinstall download flow
15+
- `Go2rtcService` and `Go2rtcBinaryManager` for stream and process lifecycle management
16+
- bundled `video-rtc` frontend player for WebRTC, MSE, and MJPEG playback
1217
- Discord webhook notifications for the standalone WebUI with:
1318
- Global config keys in `config.json`: `DiscordSync`, `WebhookUrl`, `DiscordUpdateIntervalMinutes`
1419
- Multi-printer periodic status updates using a single shared timer
1520
- Event-driven notifications for print completion, printer cooled, and idle transitions
1621
- Status embeds using precise elapsed seconds and firmware ETA when available
1722
- Focused Discord notification service tests covering timer behavior, multi-context sends, and payload formatting
23+
- Backend bundling via `scripts/build-backend.ts` for pkg-compatible CommonJS output from the TypeScript source tree
24+
- Wrapped platform build entrypoints via `scripts/platform-build-wrapper.ts`
25+
- `docs:check` and `docs:check:debug` npm scripts backed by `scripts/check-fileoverview.go`
26+
- Packaged favicon asset for the standalone WebUI
1827

1928
### Changed
2029

30+
- Camera streaming migrated from the legacy proxy/RTSP stack to go2rtc-managed per-context streams
31+
- Frontend camera playback now uses the bundled `video-rtc` player instead of the previous streaming path
32+
- Backend build pipeline now bundles `src/index.ts` before pkg packaging, while leaving runtime packages external for compatibility
2133
- Legacy per-printer settings in `config.json` are now treated as stale keys only and stripped on save
2234
- Printer connection and backend selection now use per-printer `forceLegacyMode` instead of the removed global `ForceLegacyAPI`
2335
- Camera configuration resolution now uses only per-printer settings from saved printer details
36+
- Static asset copying now includes the packaged favicon and updated browser assets
37+
- Project documentation was refreshed to match the go2rtc migration, Discord webhook support, and current build tooling
2438

2539
### Removed
2640

41+
- Legacy camera streaming components: `CameraProxyService`, `RtspStreamService`, `PortAllocator`, and old stream type shims
2742
- Legacy global config ownership for `CustomCamera`, `CustomCameraUrl`, `CustomLeds`, `ForceLegacyAPI`, and `CameraProxyPort`
2843

2944
## [1.0.2] - 2026-01-31

CLAUDE.md

Lines changed: 155 additions & 127 deletions
Large diffs are not rendered by default.

biome.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
"!!**/out",
1616
"!!.dependencies",
1717
"!!coverage",
18-
"!!FlashForgeUI-Electron"
18+
"!!FlashForgeUI-Electron",
19+
"!!.pi/tasks-*.json",
20+
"!!data",
21+
"!!src/webui/static/gridstack-extra.min.css",
22+
"!!src/webui/static/lib/video-rtc.js"
1923
]
2024
},
2125
"formatter": {

data/config.json

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@
33
"WebUIPort": 3000,
44
"WebUIPassword": "changeme",
55
"WebUIPasswordRequired": false,
6-
"CustomCamera": true,
7-
"CustomCameraUrl": "",
8-
"CameraProxyPort": 8181,
6+
"DiscordSync": false,
7+
"WebhookUrl": "",
8+
"DiscordUpdateIntervalMinutes": 5,
99
"SpoolmanEnabled": false,
1010
"SpoolmanServerUrl": "",
1111
"SpoolmanUpdateMode": "weight",
12-
"CustomLeds": false,
13-
"ForceLegacyAPI": false,
1412
"DebugMode": false,
1513
"WebUITheme": {
1614
"primary": "#2260c3",
@@ -19,4 +17,4 @@
1917
"surface": "#1e1e1e",
2018
"text": "#e0e0e0"
2119
}
22-
}
20+
}

jest.config.js

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,15 @@ module.exports = {
66
preset: 'ts-jest/presets/default-esm',
77
testEnvironment: 'node',
88
roots: ['<rootDir>/src'],
9-
testMatch: [
10-
'**/?(*.)+(spec|test).ts'
11-
],
12-
testPathIgnorePatterns: [
13-
'<rootDir>/src/__tests__/setup.ts'
14-
],
9+
testMatch: ['**/?(*.)+(spec|test).ts'],
10+
testPathIgnorePatterns: ['<rootDir>/src/__tests__/setup.ts'],
1511
transform: {
16-
'^.+\\.ts$': ['ts-jest', {
17-
useESM: true,
18-
}],
12+
'^.+\\.ts$': [
13+
'ts-jest',
14+
{
15+
useESM: true,
16+
},
17+
],
1918
},
2019
collectCoverageFrom: [
2120
'src/**/*.ts',
@@ -26,11 +25,7 @@ module.exports = {
2625
'!src/__tests__/**',
2726
],
2827
coverageDirectory: 'coverage',
29-
coverageReporters: [
30-
'text',
31-
'lcov',
32-
'html'
33-
],
28+
coverageReporters: ['text', 'lcov', 'html'],
3429
moduleNameMapper: {
3530
'^@/(.*)$': '<rootDir>/src/$1',
3631
},

scripts/copy-webui-assets.js

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
/**
44
* Copy WebUI static assets from source to build output directory.
5-
*
5+
*
66
* This script copies HTML, CSS, and other static files from src/webui/static/
77
* to dist/webui/static/ as part of the webui build process.
88
*/
@@ -20,24 +20,24 @@ const filesToCopy = ['index.html', 'webui.css', 'gridstack-extra.min.css', 'favi
2020
const vendorLibraries = [
2121
{
2222
src: 'node_modules/gridstack/dist/gridstack-all.js',
23-
dest: 'gridstack-all.js'
23+
dest: 'gridstack-all.js',
2424
},
2525
{
2626
src: 'node_modules/gridstack/dist/gridstack.min.css',
27-
dest: 'gridstack.min.css'
27+
dest: 'gridstack.min.css',
2828
},
2929
{
3030
src: 'node_modules/lucide/dist/umd/lucide.min.js',
31-
dest: 'lucide.min.js'
32-
}
31+
dest: 'lucide.min.js',
32+
},
3333
];
3434

3535
// Local lib files to copy
3636
const libFiles = [
3737
{
3838
src: 'src/webui/static/lib/video-rtc.js',
39-
dest: 'lib/video-rtc.js'
40-
}
39+
dest: 'lib/video-rtc.js',
40+
},
4141
];
4242

4343
const GREEN = '\u001B[32m';
@@ -84,30 +84,42 @@ function appendVersion(assetPath, buildId) {
8484

8585
function rewriteHtmlAssetUrls(content, buildId) {
8686
const assetAttributePattern = /(\b(?:href|src)=["'])([^"']+\.(?:css|js|png))(?:\?[^"']*)?(["'])/g;
87-
const inlineModulePattern = /((?:import|export)\s+(?:[^'"]*?\s+from\s+)?["'])(\.{1,2}\/[^"']+\.js)(?:\?[^"']*)?(["'])/g;
87+
const inlineModulePattern =
88+
/((?:import|export)\s+(?:[^'"]*?\s+from\s+)?["'])(\.{1,2}\/[^"']+\.js)(?:\?[^"']*)?(["'])/g;
89+
90+
const withVersionedAttributes = content.replace(
91+
assetAttributePattern,
92+
(match, prefix, assetPath, suffix) => {
93+
if (!isLocalAssetPath(assetPath)) {
94+
return match;
95+
}
8896

89-
const withVersionedAttributes = content.replace(assetAttributePattern, (match, prefix, assetPath, suffix) => {
90-
if (!isLocalAssetPath(assetPath)) {
91-
return match;
97+
return `${prefix}${appendVersion(assetPath, buildId)}${suffix}`;
9298
}
99+
);
93100

94-
return `${prefix}${appendVersion(assetPath, buildId)}${suffix}`;
95-
});
96-
97-
return withVersionedAttributes.replace(inlineModulePattern, (match, prefix, assetPath, suffix) => {
98-
return `${prefix}${appendVersion(assetPath, buildId)}${suffix}`;
99-
});
101+
return withVersionedAttributes.replace(
102+
inlineModulePattern,
103+
(_match, prefix, assetPath, suffix) => {
104+
return `${prefix}${appendVersion(assetPath, buildId)}${suffix}`;
105+
}
106+
);
100107
}
101108

102109
function rewriteModuleImports(content, buildId) {
103-
const staticImportPattern = /((?:import|export)\s+(?:[^'"]*?\s+from\s+)?["'])(\.{1,2}\/[^"']+\.js)(?:\?[^"']*)?(["'])/g;
104-
const dynamicImportPattern = /(\bimport\s*\(\s*["'])(\.{1,2}\/[^"']+\.js)(?:\?[^"']*)?(["']\s*\))/g;
105-
106-
const withStaticImports = content.replace(staticImportPattern, (match, prefix, assetPath, suffix) => {
107-
return `${prefix}${appendVersion(assetPath, buildId)}${suffix}`;
108-
});
110+
const staticImportPattern =
111+
/((?:import|export)\s+(?:[^'"]*?\s+from\s+)?["'])(\.{1,2}\/[^"']+\.js)(?:\?[^"']*)?(["'])/g;
112+
const dynamicImportPattern =
113+
/(\bimport\s*\(\s*["'])(\.{1,2}\/[^"']+\.js)(?:\?[^"']*)?(["']\s*\))/g;
114+
115+
const withStaticImports = content.replace(
116+
staticImportPattern,
117+
(_match, prefix, assetPath, suffix) => {
118+
return `${prefix}${appendVersion(assetPath, buildId)}${suffix}`;
119+
}
120+
);
109121

110-
return withStaticImports.replace(dynamicImportPattern, (match, prefix, assetPath, suffix) => {
122+
return withStaticImports.replace(dynamicImportPattern, (_match, prefix, assetPath, suffix) => {
111123
return `${prefix}${appendVersion(assetPath, buildId)}${suffix}`;
112124
});
113125
}
@@ -168,25 +180,25 @@ function copyWebUIAssets() {
168180
// Ensure destination directory exists
169181
fs.mkdirSync(destDir, { recursive: true });
170182
logInfo(`created directory ${destDir}`);
171-
183+
172184
// Copy each file
173185
let copiedCount = 0;
174186
for (const fileName of filesToCopy) {
175187
const srcPath = path.join(srcDir, fileName);
176188
const destPath = path.join(destDir, fileName);
177-
189+
178190
// Check if source file exists
179191
if (!fs.existsSync(srcPath)) {
180192
logWarn(`source file missing ${srcPath}`);
181193
continue;
182194
}
183-
195+
184196
// Copy the file
185197
fs.copyFileSync(srcPath, destPath);
186198
logInfo(`copied ${fileName}`);
187199
copiedCount++;
188200
}
189-
201+
190202
logInfo(`webui asset copy complete ${copiedCount}/${filesToCopy.length}`);
191203

192204
// Copy vendor libraries
@@ -233,7 +245,6 @@ function copyWebUIAssets() {
233245

234246
logInfo(`lib file copy complete ${libCount}/${libFiles.length}`);
235247
applyBuildStamp(destDir);
236-
237248
} catch (error) {
238249
logError(`error copying WebUI assets: ${error.message}`);
239250
process.exit(1);

scripts/download-go2rtc.cjs

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -56,24 +56,32 @@ function httpsGetWithRedirects(url, maxRedirects = 5) {
5656
const protocol = currentUrl.startsWith('https') ? https : http;
5757

5858
protocol
59-
.get(currentUrl, { headers: { 'User-Agent': 'FlashForgeWebUI-Downloader' } }, (response) => {
60-
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
61-
if (redirectsLeft <= 0) {
62-
reject(new Error('Too many redirects'));
59+
.get(
60+
currentUrl,
61+
{ headers: { 'User-Agent': 'FlashForgeWebUI-Downloader' } },
62+
(response) => {
63+
if (
64+
response.statusCode >= 300 &&
65+
response.statusCode < 400 &&
66+
response.headers.location
67+
) {
68+
if (redirectsLeft <= 0) {
69+
reject(new Error('Too many redirects'));
70+
return;
71+
}
72+
73+
makeRequest(response.headers.location, redirectsLeft - 1);
6374
return;
6475
}
6576

66-
makeRequest(response.headers.location, redirectsLeft - 1);
67-
return;
68-
}
77+
if (response.statusCode !== 200) {
78+
reject(new Error(`HTTP ${response.statusCode} for ${currentUrl}`));
79+
return;
80+
}
6981

70-
if (response.statusCode !== 200) {
71-
reject(new Error(`HTTP ${response.statusCode} for ${currentUrl}`));
72-
return;
82+
resolve(response);
7383
}
74-
75-
resolve(response);
76-
})
84+
)
7785
.on('error', reject);
7886
};
7987

@@ -144,9 +152,7 @@ async function downloadPlatform(platformKey, config, resourcesDir) {
144152

145153
if (fs.existsSync(binaryPath)) {
146154
const stats = fs.statSync(binaryPath);
147-
console.log(
148-
` Already exists: ${binaryPath} (${(stats.size / 1024 / 1024).toFixed(1)} MB)`
149-
);
155+
console.log(` Already exists: ${binaryPath} (${(stats.size / 1024 / 1024).toFixed(1)} MB)`);
150156
return;
151157
}
152158

scripts/platform-build-wrapper.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ function parseArgs(): ParsedArgs {
9898

9999
if (!arg.startsWith('--') && !platform) {
100100
platform = arg;
101-
continue;
102101
}
103102
}
104103

@@ -155,7 +154,9 @@ async function main(): Promise<void> {
155154

156155
if (!platform) {
157156
const supported = Object.keys(PLATFORM_CONFIG).join('|');
158-
logError(`Missing or invalid platform argument. Usage: tsx scripts/platform-build-wrapper.ts --platform <${supported}>`);
157+
logError(
158+
`Missing or invalid platform argument. Usage: tsx scripts/platform-build-wrapper.ts --platform <${supported}>`
159+
);
159160
process.exit(1);
160161
return;
161162
}

src/__tests__/setup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Runs before each test file
44
*/
55

6-
import { jest, afterEach } from '@jest/globals';
6+
import { afterEach, jest } from '@jest/globals';
77

88
// Mock console methods to reduce noise in tests
99
global.console = {

0 commit comments

Comments
 (0)