Add ESLint, and run it with the tests in CI
A variable declared inside a try block but read after it shipped and broke collection checks for a day; tests can't reach that path and node --check only parses. ESLint flags it as no-undef. Wired into npm run lint / check and into CI before the image build, so a failure stops the build. Three findings fixed on the way: an unread argument, and two values assigned then immediately replaced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
517574720e
commit
ac88a247ae
8 changed files with 1008 additions and 6 deletions
|
|
@ -16,6 +16,13 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Lint and test
|
||||
# In the runner's own Docker, so this needs no extra actions. A failure
|
||||
# here stops the image being built, rather than deploying a broken one.
|
||||
run: |
|
||||
docker run --rm -v "$PWD":/src -w /src node:22-bookworm-slim \
|
||||
sh -c "npm ci --no-audit --no-fund && npm run lint && npm test"
|
||||
|
||||
- name: Log in to the Forgejo container registry
|
||||
# REGISTRY_TOKEN = a Forgejo access token with write:package scope, added
|
||||
# as a repo Actions secret. The auto GITHUB_TOKEN can't push packages.
|
||||
|
|
|
|||
|
|
@ -55,7 +55,9 @@ A self-hosted web app for organizing your 3D-printing files (STL, 3MF, OBJ, FBX,
|
|||
```
|
||||
4. Open http://localhost:3000
|
||||
|
||||
Run the unit tests with `npm test`.
|
||||
Run the unit tests with `npm test`, ESLint with `npm run lint`, or both with `npm run check`. CI runs both before
|
||||
building the image, so a lint error stops a broken image being pushed. The lint rules are deliberately thin — their
|
||||
job is the class of mistake tests can't catch, chiefly a name that doesn't exist until the line finally runs.
|
||||
|
||||
### Where your data lives
|
||||
|
||||
|
|
|
|||
45
eslint.config.js
Normal file
45
eslint.config.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// Lint rules, kept to the mistakes tests can't catch — chiefly a name that
|
||||
// doesn't exist, which only shows up when that line finally runs.
|
||||
const js = require('@eslint/js');
|
||||
const globals = require('globals');
|
||||
|
||||
module.exports = [
|
||||
{ ignores: ['node_modules/**', 'uploads/**', 'thumbnails/**', 'glb/**', 'extracted/**', 'models/**', 'data/**', 'backups/**', 'tools/venv/**'] },
|
||||
|
||||
// The server: Node, CommonJS.
|
||||
{
|
||||
files: ['**/*.js'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2023,
|
||||
sourceType: 'commonjs',
|
||||
globals: { ...globals.node },
|
||||
},
|
||||
rules: {
|
||||
...js.configs.recommended.rules,
|
||||
'no-unused-vars': ['error', { argsIgnorePattern: '^_|^next$', caughtErrors: 'none' }],
|
||||
},
|
||||
},
|
||||
|
||||
// The browser: plain scripts sharing globals across files, plus one module.
|
||||
{
|
||||
files: ['public/**/*.js'],
|
||||
languageOptions: {
|
||||
sourceType: 'script',
|
||||
globals: {
|
||||
...globals.browser,
|
||||
ModelViewer: 'readonly', // viewer.js hangs it on window
|
||||
PRINTER_MODELS: 'readonly', // printer-art.js does the same
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['public/viewer.js'],
|
||||
languageOptions: { sourceType: 'module' },
|
||||
},
|
||||
|
||||
// Tests.
|
||||
{
|
||||
files: ['test/**/*.js'],
|
||||
languageOptions: { globals: { ...globals.node } },
|
||||
},
|
||||
];
|
||||
|
|
@ -52,7 +52,7 @@ async function fetchHtml(url) {
|
|||
}
|
||||
|
||||
// Extract Open Graph / title metadata from raw HTML.
|
||||
function parseMetadata(html, url) {
|
||||
function parseMetadata(html, _url) { // _url: kept for the call shape, not read
|
||||
const titleTag = (html.match(/<title[^>]*>([^<]*)<\/title>/i) || [])[1];
|
||||
return {
|
||||
title: metaContent(html, 'property', 'og:title') || (titleTag ? decodeEntities(titleTag).trim() : null),
|
||||
|
|
|
|||
943
package-lock.json
generated
943
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,12 +1,14 @@
|
|||
{
|
||||
"name": "printhive",
|
||||
"version": "1.0.0",
|
||||
"description": "Printhive — a web app for organizing 3D-printing files (STL, 3MF, OBJ, ...) with URL import",
|
||||
"description": "Printhive \u2014 a web app for organizing 3D-printing files (STL, 3MF, OBJ, ...) with URL import",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js",
|
||||
"test": "node --test test/*.test.js"
|
||||
"test": "node --test test/*.test.js",
|
||||
"lint": "eslint .",
|
||||
"check": "npm run lint && npm test"
|
||||
},
|
||||
"keywords": [
|
||||
"stl",
|
||||
|
|
@ -35,6 +37,9 @@
|
|||
"tar": "^7.5.22"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"eslint": "^10.11.0",
|
||||
"globals": "^17.12.0",
|
||||
"nodemon": "^3.0.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2908,7 +2908,7 @@ function printerStatusHtml(snap, model, p = {}) {
|
|||
async function pollPrinters() {
|
||||
clearTimeout(printersPoll);
|
||||
if (selectedScope !== 'printers' || !document.getElementById('printerCards')) return;
|
||||
let live = {};
|
||||
let live;
|
||||
try {
|
||||
live = await getJson('/api/printers/live');
|
||||
} catch {
|
||||
|
|
|
|||
|
|
@ -1028,7 +1028,7 @@ async function importFromUrl(url, { projects = [], withFiles = true } = {}) {
|
|||
let filesError = null; // why no files could be downloaded, shown after the import
|
||||
let filesErrorReason = null; // makerworld_token: the app offers to update the token
|
||||
let license = meta.license || null;
|
||||
let commercial = null;
|
||||
let commercial;
|
||||
if (detected.site === 'makerworld' && html) {
|
||||
if (withFiles) {
|
||||
files = await downloadMakerworldFiles(html).catch((err) => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue