chore: finish monorepo setup

Signed-off-by: Fernando Fernández <ferferga@hotmail.com>
This commit is contained in:
Fernando Fernández 2024-10-14 02:09:53 +02:00
parent 4c4cdbc9db
commit cb89d94228
No known key found for this signature in database
GPG Key ID: 82FD36644F1F4D3B
29 changed files with 822 additions and 678 deletions

View File

@ -22,7 +22,7 @@ env:
jobs:
cf-pages:
name: CloudFlare Pages 📃
name: Cloudflare Pages 📃
runs-on: ubuntu-latest
environment:
name: ${{ inputs.branch == 'master' && 'Production' || 'Preview' }}

View File

@ -29,7 +29,7 @@ env:
jobs:
cf_pages_msg:
name: CloudFlare Pages deployment 📃🚀
name: Cloudflare Pages deployment 📃🚀
runs-on: ubuntu-latest
steps:

View File

@ -36,10 +36,9 @@ jobs:
command:
- lint
- check:types
- analyze:cycles
defaults:
run:
working-directory: frontend
include:
- workspace: frontend
command: analyze:cycles
steps:
- name: Checkout ⬇️
@ -59,7 +58,7 @@ jobs:
run: npm ci --no-audit
- name: Run ${{ matrix.command }} ⚙️
run: npm run ${{ matrix.command }}
run: npm run ${{ matrix.command }} ${{ matrix.workspace && format('-w {0}', matrix.workspace) || '' }}
commits_checks:
name: Commit linting 💬✅

View File

@ -28,6 +28,9 @@
"javascript",
"typescript"
],
"eslint.options": {
"flags": ["unstable_ts_config"]
},
"eslint.format.enable": true,
"eslint.useFlatConfig": true,
"eslint.workingDirectories": [

View File

@ -1,403 +0,0 @@
import jsdoc from 'eslint-plugin-jsdoc';
import unicorn from 'eslint-plugin-unicorn';
import eslintImportX from 'eslint-plugin-import-x';
import fileProgress from 'eslint-plugin-file-progress';
import promise from 'eslint-plugin-promise';
import js from '@eslint/js';
import globals from 'globals';
import vueScopedCSS from 'eslint-plugin-vue-scoped-css';
import css from 'eslint-plugin-css';
import vue from 'eslint-plugin-vue';
import i18n from '@intlify/eslint-plugin-vue-i18n';
import gitignore from 'eslint-config-flat-gitignore';
import stylistic from '@stylistic/eslint-plugin';
import sonarjs from 'eslint-plugin-sonarjs';
import tseslint from 'typescript-eslint';
import jsonc from 'eslint-plugin-jsonc';
import regexp from 'eslint-plugin-regexp';
import { configs as dependConfigs } from 'eslint-plugin-depend';
import unocss from '@unocss/eslint-config/flat';
const tsFiles = ['*.ts', '**/*.ts'];
const vueFiles = ['*.vue', '**/*.vue'];
const vueAndTsFiles = [...vueFiles, ...tsFiles];
const CI_environment = process.env.CI ? 0 : 1;
const jsoncRecommended = jsonc.configs['flat/recommended-with-json'];
const eqeqeqConfig = ['error', 'always', { null: 'ignore' }];
/**
* Util functions
*/
const flatArrayOfObjects = obj => Object.assign({}, ...obj);
export default tseslint.config(
/** Global settings */
{ ...js.configs.recommended,
name: '(eslint) Extended config from plugin'
},
{
...unicorn.configs['flat/recommended'],
name: '(unicorn) Extended config from plugin'
},
{
...dependConfigs['flat/recommended'],
name: '(depend) Extended config from plugin'
},
{
...unocss,
name: '(unocss) Extended config from plugin'
},
{
...stylistic.configs.customize({
quotes: 'single',
semi: true,
commaDangle: 'never',
braceStyle: '1tbs',
arrowParens: false,
blockSpacing: true
}),
name: '(@stylistic) Extended config from plugin'
},
{
name: 'Common settings',
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
globals: {
...globals.browser
}
},
rules: {
'no-empty': ['error', { allowEmptyCatch: true }],
'no-extend-native': 'error',
'curly': ['error', 'all'],
'prefer-arrow-callback': 'error',
'multiline-comment-style': 'error',
'unicode-bom': ['error', 'never'],
'eqeqeq': eqeqeqConfig,
'@stylistic/quotes': ['error', 'single', { avoidEscape: true }],
'@stylistic/linebreak-style': ['error', 'unix'],
'unicorn/import-style': 'off',
'unicorn/filename-case': 'off',
'unicorn/consistent-function-scoping': 'off',
'unicorn/prevent-abbreviations': 'off',
'unicorn/no-await-expression-member': 'off',
/**
* See https://github.com/jellyfin/jellyfin-vue/pull/2361
*/
'unicorn/explicit-length-check': 'off',
'@stylistic/padding-line-between-statements': [
'error',
// Always require blank lines after import, except between imports
{ blankLine: 'always', prev: 'import', next: '*' },
{ blankLine: 'never', prev: 'import', next: 'import' },
// Always require blank lines before and after every sequence of variable declarations and export
{
blankLine: 'always',
prev: '*',
next: ['const', 'let', 'var', 'export']
},
{
blankLine: 'always',
prev: ['const', 'let', 'var', 'export'],
next: '*'
},
{
blankLine: 'any',
prev: ['const', 'let', 'var', 'export'],
next: ['const', 'let', 'var', 'export']
},
{
blankLine: 'any',
prev: ['block-like'],
next: '*'
},
// Always require blank lines before and after class declaration, if, do/while, switch, try
{
blankLine: 'always',
prev: '*',
next: ['if', 'class', 'for', 'do', 'while', 'switch', 'try']
},
{
blankLine: 'always',
prev: ['if', 'class', 'for', 'do', 'while', 'switch', 'try'],
next: '*'
},
// Always require blank lines before return statements
{ blankLine: 'always', prev: '*', next: 'return' }
]
}
},
/** Common TypeScript rules */
{
name: '(TypeScript & Vue) - Parser config',
files: vueAndTsFiles,
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
warnOnUnsupportedTypeScriptVersion: false,
extraFileExtensions: ['.vue']
}
}
},
{
...flatArrayOfObjects(tseslint.configs.strictTypeChecked),
name: '(typescript-eslint) Extended config from plugin (strict type checking)',
files: vueAndTsFiles
},
{
...flatArrayOfObjects(tseslint.configs.stylisticTypeChecked),
name: '(typescript-eslint) Extended config from plugin (stylistic type checking)',
files: vueAndTsFiles
},
{
...tseslint.configs.eslintRecommended,
files: vueAndTsFiles,
name: '(typescript-eslint) Extended config from plugin (ESLint rules with type checking)'
},
{
...regexp.configs['flat/recommended'],
name: '(regexp) Extended config from plugin',
files: vueAndTsFiles
},
{
...promise.configs['flat/recommended'],
name: '(promise) Extended config from plugin',
files: vueAndTsFiles
},
{
name: '(promise) Custom config',
files: vueAndTsFiles,
rules: {
'promise/prefer-await-to-callbacks': 'error',
'promise/prefer-await-to-then': 'error'
}
},
{
name: '(import) Custom config',
// TODO: Remove .js after: https://github.com/eslint/eslint/pull/18134
files: [...vueAndTsFiles, '**/*.js'],
plugins: {
'import-x': eslintImportX
},
rules: {
'import-x/no-extraneous-dependencies': 'error',
'import-x/order': 'error',
'import-x/no-cycle': 'error',
'import-x/no-nodejs-modules': 'error',
'import-x/no-duplicates': ['error', { 'prefer-inline': true, 'considerQueryString': true }],
// From the recommended preset
'import-x/named': 'error',
'import-x/export': 'error'
}
},
{
files: vueAndTsFiles,
name: '(JSDoc) Custom config',
plugins: {
jsdoc
},
rules: {
'jsdoc/require-hyphen-before-param-description': 'error',
'jsdoc/require-description': 'error',
'jsdoc/no-types': 'error',
'jsdoc/require-jsdoc': 'error',
'jsdoc/informative-docs': 'error'
}
},
{
name: '(TypeScript & Vue) Custom config',
files: vueAndTsFiles,
rules: {
'@typescript-eslint/no-redundant-type-constituents': 'off',
'@typescript-eslint/restrict-template-expressions': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-dynamic-delete': 'off',
'@typescript-eslint/no-import-type-side-effects': 'error',
'@typescript-eslint/consistent-type-exports': 'error',
'@typescript-eslint/explicit-member-accessibility': 'error',
'@typescript-eslint/consistent-type-imports': ['error', {
prefer: 'type-imports',
fixStyle: 'inline-type-imports'
}],
'@typescript-eslint/no-confusing-void-expression': ['error', { ignoreArrowShorthand: true }],
'@typescript-eslint/no-empty-object-type': ['error', { allowInterfaces: 'with-single-extends' }],
'vue/return-in-computed-property': 'off'
}
},
{
...sonarjs.configs.recommended,
name: '(sonarcloud) Extended config from plugin',
files: vueAndTsFiles
},
/** SFC rules */
...vue.configs['flat/recommended'].map((config) => {
/**
* Specified above, unnecessary to overwrite
*/
delete config.languageOptions?.globals;
/**
* DEPRECATED: See https://eslint.vuejs.org/rules/component-tags-order.html#vue-component-tags-order
* TODO: Remove when it's removed from the recommended rules
*/
delete config.rules?.['vue/component-tags-order'];
config.name = `(${config.name}) - Extended config from plugin`;
return config;
}),
{
...flatArrayOfObjects(vueScopedCSS.configs['flat/recommended']),
name: '(Vue - Scoped CSS) Extended config from plugin'
},
{
...css.configs['flat/recommended'],
name: '(Vue - CSS) Extended config from plugin',
files: [...vueFiles]
},
{
name: '(Vue) Custom config',
files: vueFiles,
languageOptions: {
parserOptions: {
parser: tseslint.parser
}
},
rules: {
'vue/component-name-in-template-casing': [
'error',
'PascalCase',
{
registeredComponentsOnly: false
}
],
'vue/define-macros-order': ['error', {
order: ['defineOptions', 'defineProps', 'defineEmits', 'defineSlots']
}],
'vue/html-closing-bracket-newline': ['error', { multiline: 'never' }],
'vue/block-order': ['error', {
order: ['template', 'script:not([setup])', 'script[setup]']
}],
'vue/multi-word-component-names': 'off',
'vue/require-default-prop': 'off',
'vue/eqeqeq': eqeqeqConfig
}
},
{
/* First index is just the plugin definition */
...jsoncRecommended.at(0),
...jsoncRecommended.at(1),
name: '(JSON) Custom config',
rules: {
...jsoncRecommended.at(1).rules,
...jsoncRecommended.at(2).rules,
'jsonc/auto': 'error',
'@stylistic/quotes': ['error', 'double'],
'@stylistic/semi': 'off',
'@stylistic/quote-props': 'off'
}
},
{
/* First index is just the plugin definition */
...i18n.configs['flat/recommended'].at(0),
/* Last contains the rule definitions */
...i18n.configs['flat/recommended'].at(-1),
name: '(@intlify/vue-i18n) Extended config (plugin & settings)',
settings: {
'vue-i18n': {
localeDir: 'locales/en.json',
messageSyntaxVersion: '^9.0.0'
}
},
files: vueAndTsFiles
},
{
name: '(@intlify/vue-i18n) Custom config',
files: vueAndTsFiles,
rules: {
'@intlify/vue-i18n/no-unused-keys': ['error', {
extensions: ['.ts', '.vue', '.json'],
enableFix: true
}],
'@intlify/vue-i18n/no-raw-text': ['error', {
ignorePattern: '^[-#:()&.]+$'
}],
'@intlify/vue-i18n/no-duplicate-keys-in-locale': 'error',
'@intlify/vue-i18n/no-dynamic-keys': 'error',
'@intlify/vue-i18n/key-format-style': 'error'
}
},
/**
* See the following:
* - https://en.wikipedia.org/wiki/History_of_sentence_spacing#French_and_English_spacing
* - https://docs.weblate.org/en/weblate-4.14.1/user/checks.html#check-punctuation-spacing
*/
{
name: '(@intlify/vue-i18n - fr) Config exceptions for linguistic rules',
files: ['locales/fr.json'],
rules: {
'no-irregular-whitespace': 'off'
}
},
/** Settings for all the files that run in development */
{
name: 'Environment config - Node.js and development-related files',
files: ['*.config.*', 'scripts/**/*.ts'],
languageOptions: {
globals: {
...globals.node,
...globals.nodeBuiltin
}
},
rules: {
'import-x/no-extraneous-dependencies': [
'error',
{
devDependencies: true
}
],
'import-x/no-nodejs-modules': 'off'
}
},
/** Settings for WebWorkers (the pattern matches any file that ends in .worker.ts) */
{
name: 'Environment config - WebWorkers',
files: ['**/*.worker.ts'],
languageOptions: {
globals: {
...globals.worker
}
}
},
{
...stylistic.configs['disable-legacy'],
name: 'Environment config - Disable legacy rules'
},
/**
* Extra files to include and ignores that should override all the others
*/
{
name: 'Environment config - Ignored files',
ignores: [
'**/.git',
'types/global/routes.d.ts',
'types/global/components.d.ts',
...gitignore().ignores
]
},
/** File progress plugin */
{
name: '(eslint) Linting progress report',
settings: {
progress: {
successMessage: 'Linting done!'
}
},
plugins: {
'file-progress': fileProgress
},
rules: {
'file-progress/activate': CI_environment
}
}
);

11
frontend/eslint.config.ts Normal file
View File

@ -0,0 +1,11 @@
import type { Linter } from 'eslint';
import { getBaseConfig, getTSVueConfig, getNodeFiles, unocss, getWorkerFiles } from '@jellyfin-vue/configs/eslint';
// TODO: Add missing rules for i18n and json
export default [
...getBaseConfig('@jellyfin-vue/frontend'),
...getTSVueConfig(true, import.meta.dirname),
...unocss,
...getNodeFiles(),
...getWorkerFiles()
] satisfies Linter.Config[];

View File

@ -8,8 +8,8 @@
"scripts": {
"analyze:bundle": "vite build --mode analyze:bundle",
"analyze:cycles": "vite build --mode analyze:cycles",
"lint": "eslint . --max-warnings=0 --cache --cache-location ../node_modules/.cache/eslint",
"lint:fix": "eslint . --fix --max-warnings=0 --cache --cache-location ../node_modules/.cache/eslint",
"lint": "eslint . --max-warnings=0 --flag unstable_ts_config",
"lint:fix": "eslint . --fix --max-warnings=0 --flag unstable_ts_config",
"lint:inspect": "eslint-config-inspector",
"build": "vite build",
"check": "npm run lint && npm run check:types",
@ -47,45 +47,22 @@
"vuetify": "3.7.1"
},
"devDependencies": {
"@eslint/config-inspector": "0.5.4",
"@eslint/js": "9.10.0",
"@iconify/json": "2.2.246",
"@intlify/eslint-plugin-vue-i18n": "3.0.0",
"@intlify/unplugin-vue-i18n": "4.0.0",
"@jellyfin-vue/configs": "*",
"@jellyfin-vue/vite-plugins": "*",
"@rollup/plugin-virtual": "3.0.2",
"@stylistic/eslint-plugin": "2.8.0",
"@types/dompurify": "3.0.5",
"@types/node": "22.5.4",
"@types/sortablejs": "1.15.8",
"@types/uuid": "10.0.0",
"@unocss/eslint-config": "0.62.3",
"@vitejs/plugin-vue": "5.1.3",
"browserslist": "4.23.3",
"eslint-config-flat-gitignore": "0.3.0",
"eslint-plugin-css": "0.11.0",
"eslint-plugin-depend": "0.11.0",
"eslint-plugin-file-progress": "1.5.0",
"eslint-plugin-import-x": "4.2.1",
"eslint-plugin-jsdoc": "50.2.2",
"eslint-plugin-jsonc": "2.16.0",
"eslint-plugin-promise": "7.1.0",
"eslint-plugin-regexp": "2.6.0",
"eslint-plugin-sonarjs": "1.0.4",
"eslint-plugin-unicorn": "55.0.0",
"eslint-plugin-vue": "9.28.0",
"eslint-plugin-vue-scoped-css": "2.8.1",
"globals": "15.9.0",
"lightningcss": "1.26.0",
"typescript-eslint": "8.5.0",
"unocss": "0.62.3",
"unplugin-icons": "0.19.3",
"unplugin-vue-components": "0.27.4",
"unplugin-vue-router": "0.10.8",
"vite": "5.4.6",
"vite-plugin-vue-devtools": "7.4.4",
"vue-eslint-parser": "9.4.3",
"vue-tsc": "2.1.6"
"vite-plugin-vue-devtools": "7.4.4"
}
}

View File

@ -1,5 +1,5 @@
{
"extends": "@jellyfin-vue/configs/tsconfig.json",
"extends": "@jellyfin-vue/configs/typescript/base.json",
"compilerOptions": {
"types": [
"axios",

View File

@ -1,15 +1 @@
import { defineConfig, presetUno } from 'unocss';
export default defineConfig({
presets: [
presetUno({
prefix: 'uno-',
preflight: false
})
],
theme: {
colors: {
background: 'rgb(var(--j-color-background))'
}
}
});
export { defaultConfig as default } from '@jellyfin-vue/configs/unocss';

334
package-lock.json generated
View File

@ -1,10 +1,9 @@
{
"name": "@jellyfin-vue",
"name": "jellyfin-vue",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@jellyfin-vue",
"license": "GPL-3.0-only",
"workspaces": [
"frontend",
@ -12,8 +11,12 @@
"packages/*"
],
"devDependencies": {
"eslint": "9.10.0",
"typescript": "5.6.2"
"@eslint/config-inspector": "0.5.4",
"@types/node": "22.5.4",
"eslint": "9.12.0",
"jiti": "2.3.3",
"typescript": "5.6.2",
"vue-tsc": "2.1.6"
},
"engines": {
"node": ">=20.8.1 <21.0.0",
@ -52,46 +55,23 @@
"vuetify": "3.7.1"
},
"devDependencies": {
"@eslint/config-inspector": "0.5.4",
"@eslint/js": "9.10.0",
"@iconify/json": "2.2.246",
"@intlify/eslint-plugin-vue-i18n": "3.0.0",
"@intlify/unplugin-vue-i18n": "4.0.0",
"@jellyfin-vue/configs": "*",
"@jellyfin-vue/vite-plugins": "*",
"@rollup/plugin-virtual": "3.0.2",
"@stylistic/eslint-plugin": "2.8.0",
"@types/dompurify": "3.0.5",
"@types/node": "22.5.4",
"@types/sortablejs": "1.15.8",
"@types/uuid": "10.0.0",
"@unocss/eslint-config": "0.62.3",
"@vitejs/plugin-vue": "5.1.3",
"browserslist": "4.23.3",
"eslint-config-flat-gitignore": "0.3.0",
"eslint-plugin-css": "0.11.0",
"eslint-plugin-depend": "0.11.0",
"eslint-plugin-file-progress": "1.5.0",
"eslint-plugin-import-x": "4.2.1",
"eslint-plugin-jsdoc": "50.2.2",
"eslint-plugin-jsonc": "2.16.0",
"eslint-plugin-promise": "7.1.0",
"eslint-plugin-regexp": "2.6.0",
"eslint-plugin-sonarjs": "1.0.4",
"eslint-plugin-unicorn": "55.0.0",
"eslint-plugin-vue": "9.28.0",
"eslint-plugin-vue-scoped-css": "2.8.1",
"globals": "15.9.0",
"lightningcss": "1.26.0",
"typescript-eslint": "8.5.0",
"unocss": "0.62.3",
"unplugin-icons": "0.19.3",
"unplugin-vue-components": "0.27.4",
"unplugin-vue-router": "0.10.8",
"vite": "5.4.6",
"vite-plugin-vue-devtools": "7.4.4",
"vue-eslint-parser": "9.4.3",
"vue-tsc": "2.1.6"
"vite-plugin-vue-devtools": "7.4.4"
}
},
"node_modules/@ampproject/remapping": {
@ -729,9 +709,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz",
"integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz",
"integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -1345,6 +1325,16 @@
"eslint": "^8.50.0 || ^9.0.0"
}
},
"node_modules/@eslint/core": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz",
"integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/eslintrc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
@ -1407,9 +1397,9 @@
}
},
"node_modules/@eslint/js": {
"version": "9.10.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz",
"integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==",
"version": "9.12.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.12.0.tgz",
"integrity": "sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==",
"dev": true,
"license": "MIT",
"engines": {
@ -1427,9 +1417,9 @@
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz",
"integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==",
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz",
"integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@ -1445,6 +1435,30 @@
"integrity": "sha512-p0fta2L9CMeTbC7rNYeKF0bcu+tUGb4NH5D/pHC0RUNW1I+rrBDXLoGYKaH4yNfxxMrsZ4k1fchgkCI4F9zIFQ==",
"license": "OFL-1.1"
},
"node_modules/@humanfs/core": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz",
"integrity": "sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18.18.0"
}
},
"node_modules/@humanfs/node": {
"version": "0.16.5",
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.5.tgz",
"integrity": "sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@humanfs/core": "^0.19.0",
"@humanwhocodes/retry": "^0.3.0"
},
"engines": {
"node": ">=18.18.0"
}
},
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@ -1460,9 +1474,9 @@
}
},
"node_modules/@humanwhocodes/retry": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz",
"integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==",
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
"integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@ -1673,9 +1687,9 @@
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"license": "MIT",
"engines": {
@ -2371,6 +2385,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.5.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
@ -4472,29 +4493,32 @@
}
},
"node_modules/eslint": {
"version": "9.10.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz",
"integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==",
"version": "9.12.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.12.0.tgz",
"integrity": "sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.11.0",
"@eslint/config-array": "^0.18.0",
"@eslint/core": "^0.6.0",
"@eslint/eslintrc": "^3.1.0",
"@eslint/js": "9.10.0",
"@eslint/plugin-kit": "^0.1.0",
"@eslint/js": "9.12.0",
"@eslint/plugin-kit": "^0.2.0",
"@humanfs/node": "^0.16.5",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.3.0",
"@nodelib/fs.walk": "^1.2.8",
"@humanwhocodes/retry": "^0.3.1",
"@types/estree": "^1.0.6",
"@types/json-schema": "^7.0.15",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"debug": "^4.3.2",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^8.0.2",
"eslint-visitor-keys": "^4.0.0",
"espree": "^10.1.0",
"eslint-scope": "^8.1.0",
"eslint-visitor-keys": "^4.1.0",
"espree": "^10.2.0",
"esquery": "^1.5.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@ -4504,13 +4528,11 @@
"ignore": "^5.2.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"is-path-inside": "^3.0.3",
"json-stable-stringify-without-jsonify": "^1.0.1",
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
"optionator": "^0.9.3",
"strip-ansi": "^6.0.1",
"text-table": "^0.2.0"
},
"bin": {
@ -4920,9 +4942,9 @@
}
},
"node_modules/eslint-scope": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz",
"integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==",
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz",
"integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@ -4937,9 +4959,9 @@
}
},
"node_modules/eslint-visitor-keys": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
"integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz",
"integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@ -4964,43 +4986,12 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/eslint/node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"node_modules/eslint/node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "2.0.5",
"run-parallel": "^1.1.9"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/eslint/node_modules/@nodelib/fs.stat": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/eslint/node_modules/@nodelib/fs.walk": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.scandir": "2.1.5",
"fastq": "^1.6.0"
},
"engines": {
"node": ">= 8"
}
"license": "MIT"
},
"node_modules/eslint/node_modules/brace-expansion": {
"version": "1.1.11",
@ -5128,15 +5119,15 @@
}
},
"node_modules/espree": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz",
"integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==",
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz",
"integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"acorn": "^8.12.0",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^4.0.0"
"eslint-visitor-keys": "^4.1.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -5795,6 +5786,16 @@
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/importx/node_modules/jiti": {
"version": "2.0.0-beta.2",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.0.0-beta.2.tgz",
"integrity": "sha512-c+PHQZakiQuMKbnhvrjZUvrK6E/AfmTOf4P+E3Y4FNVHcNMX9e/XrnbEvO+m4wS6ZjsvhHh/POQTlfy8uXFc0A==",
"dev": true,
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@ -5984,16 +5985,6 @@
"node": ">=0.12.0"
}
},
"node_modules/is-path-inside": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
@ -6069,9 +6060,9 @@
}
},
"node_modules/jiti": {
"version": "2.0.0-beta.2",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.0.0-beta.2.tgz",
"integrity": "sha512-c+PHQZakiQuMKbnhvrjZUvrK6E/AfmTOf4P+E3Y4FNVHcNMX9e/XrnbEvO+m4wS6ZjsvhHh/POQTlfy8uXFc0A==",
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.3.3.tgz",
"integrity": "sha512-EX4oNDwcXSivPrw2qKH2LB5PoFxEvgtv2JgwW0bU858HoLQ+kutSvjLMUqBd0PeJYEinLWhoI9Ol0eYMqj/wNQ==",
"dev": true,
"license": "MIT",
"bin": {
@ -7062,9 +7053,9 @@
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
"integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"dev": true,
"license": "BlueOak-1.0.0"
},
@ -7122,13 +7113,13 @@
}
},
"node_modules/parse5": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
"integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.0.tgz",
"integrity": "sha512-ZkDsAOcxsUMZ4Lz5fVciOehNcJ+Gb8gTzcA4yl3wnc273BAybYWrQ+Ks/OjCjSEpjvQkDSeZbybK9qj2VHHdGA==",
"dev": true,
"license": "MIT",
"dependencies": {
"entities": "^4.4.0"
"entities": "^4.5.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
@ -8095,9 +8086,9 @@
"license": "MIT"
},
"node_modules/string-width/node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"license": "MIT",
"engines": {
@ -9761,9 +9752,9 @@
}
},
"node_modules/wrap-ansi/node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"license": "MIT",
"engines": {
@ -9968,74 +9959,39 @@
},
"packages/configs": {
"name": "@jellyfin-vue/configs",
"version": "0.0.1"
"version": "0.0.1",
"devDependencies": {
"@eslint/js": "9.12.0",
"@intlify/eslint-plugin-vue-i18n": "3.0.0",
"@stylistic/eslint-plugin": "2.8.0",
"@unocss/eslint-config": "0.62.3",
"eslint-config-flat-gitignore": "0.3.0",
"eslint-plugin-css": "0.11.0",
"eslint-plugin-depend": "0.11.0",
"eslint-plugin-file-progress": "1.5.0",
"eslint-plugin-import-x": "4.2.1",
"eslint-plugin-jsdoc": "50.2.2",
"eslint-plugin-jsonc": "2.16.0",
"eslint-plugin-promise": "7.1.0",
"eslint-plugin-regexp": "2.6.0",
"eslint-plugin-sonarjs": "1.0.4",
"eslint-plugin-unicorn": "55.0.0",
"eslint-plugin-vue": "9.28.0",
"eslint-plugin-vue-scoped-css": "2.8.1",
"find-up-simple": "1.0.0",
"globals": "15.9.0",
"typescript-eslint": "8.5.0",
"unocss": "0.62.3",
"vue-eslint-parser": "9.4.3"
}
},
"packages/vite-plugins": {
"name": "@jellyfin-vue/vite-plugins",
"version": "0.0.1",
"devDependencies": {
"@jellyfin-vue/configs": "*",
"rollup-plugin-visualizer": "5.12.0",
"vite": "5.4.3"
}
},
"packages/vite-plugins/node_modules/vite": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.3.tgz",
"integrity": "sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
"rollup": "^4.20.0"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^18.0.0 || >=20.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^18.0.0 || >=20.0.0",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
}
"vite": "5.4.6"
}
},
"packaging/tauri": {

View File

@ -1,5 +1,4 @@
{
"name": "@jellyfin-vue",
"private": true,
"type": "module",
"homepage": "https://jellyfin.org/",
@ -22,8 +21,17 @@
"npm": ">=10.1.0",
"yarn": "Yarn is not supported. Please use NPM."
},
"scripts": {
"lint": "npm run lint --workspaces",
"lint:fix": "npm run lint:fix --workspaces",
"check:types": "npm run check:types --workspaces"
},
"devDependencies": {
"eslint": "9.10.0",
"typescript": "5.6.2"
"@eslint/config-inspector": "0.5.4",
"@types/node": "22.5.4",
"eslint": "9.12.0",
"jiti": "2.3.3",
"typescript": "5.6.2",
"vue-tsc": "2.1.6"
}
}

View File

@ -0,0 +1,8 @@
import type { Linter } from 'eslint';
import { getBaseConfig } from './eslint/rules/base';
import { getTSVueConfig } from './eslint/rules/typescript-vue';
export default [
...getBaseConfig('@jellyfin-vue/configs'),
...getTSVueConfig(false, import.meta.dirname)
] satisfies Linter.Config[];

View File

@ -0,0 +1,6 @@
export { getBaseConfig } from './rules/base';
export { unocss } from './rules/unocss';
export { getTSVueConfig } from './rules/typescript-vue';
export { json } from './rules/json';
export { getNodeFiles, getWorkerFiles } from './rules/env';
export { i18n } from './rules/i18n';

View File

@ -0,0 +1,147 @@
import type { Linter } from 'eslint';
/*
* import { join } from 'node:path';
* import { findUpSync } from 'find-up-simple';
*/
import unicorn from 'eslint-plugin-unicorn';
// @ts-expect-error - No types available
import js from '@eslint/js';
import stylistic from '@stylistic/eslint-plugin';
import { configs as dependConfigs } from 'eslint-plugin-depend';
import gitignore from 'eslint-config-flat-gitignore';
// @ts-expect-error - No types available
import fileProgress from 'eslint-plugin-file-progress';
import { eqeqeqConfig } from '../shared';
const CI_environment = process.env.CI ? 0 : 1;
/**
* Gets ESLint's minimal configuration for the monorepo
*
* @param packageName - The name of the current package
*/
export function getBaseConfig(packageName: string): Linter.Config[] {
return [
/*
* TODO: Wait for https://github.com/eslint/eslint/issues/19015
* {
* cache: true,
* cacheLocation: join(findUpSync('node_modules', { type: 'directory' }) ?? '', '.cache/eslint', packageName.replace('/', '_')),
* },
*/
{ ...js.configs.recommended,
name: '(@jellyfin-vue/configs/eslint/base - eslint) Extended config from plugin'
},
{
...unicorn.configs['flat/recommended'],
name: '(@jellyfin-vue/configs/eslint/base - unicorn) Extended config from plugin'
},
{
...dependConfigs['flat/recommended'],
name: '(@jellyfin-vue/configs/eslint/base - depend) Extended config from plugin'
},
{
...stylistic.configs.customize({
quotes: 'single',
semi: true,
commaDangle: 'never',
braceStyle: '1tbs',
arrowParens: false,
blockSpacing: true
}),
name: '(@jellyfin-vue/configs/eslint/base - @stylistic) Extended config from plugin'
},
{
name: '(@jellyfin-vue/configs/eslint/base) Common settings',
rules: {
'no-empty': ['error', { allowEmptyCatch: true }],
'no-extend-native': 'error',
'curly': ['error', 'all'],
'prefer-arrow-callback': 'error',
'multiline-comment-style': 'error',
'unicode-bom': ['error', 'never'],
'eqeqeq': eqeqeqConfig,
'@stylistic/quotes': ['error', 'single', { avoidEscape: true }],
'@stylistic/linebreak-style': ['error', 'unix'],
'unicorn/import-style': 'off',
'unicorn/filename-case': 'off',
'unicorn/consistent-function-scoping': 'off',
'unicorn/prevent-abbreviations': 'off',
'unicorn/no-await-expression-member': 'off',
/**
* See https://github.com/jellyfin/jellyfin-vue/pull/2361
*/
'unicorn/explicit-length-check': 'off',
'@stylistic/padding-line-between-statements': [
'error',
// Always require blank lines after import, except between imports
{ blankLine: 'always', prev: 'import', next: '*' },
{ blankLine: 'never', prev: 'import', next: 'import' },
// Always require blank lines before and after every sequence of variable declarations and export
{
blankLine: 'always',
prev: '*',
next: ['const', 'let', 'var', 'export']
},
{
blankLine: 'always',
prev: ['const', 'let', 'var', 'export'],
next: '*'
},
{
blankLine: 'any',
prev: ['const', 'let', 'var', 'export'],
next: ['const', 'let', 'var', 'export']
},
{
blankLine: 'any',
prev: ['block-like'],
next: '*'
},
// Always require blank lines before and after class declaration, if, do/while, switch, try
{
blankLine: 'always',
prev: '*',
next: ['if', 'class', 'for', 'do', 'while', 'switch', 'try']
},
{
blankLine: 'always',
prev: ['if', 'class', 'for', 'do', 'while', 'switch', 'try'],
next: '*'
},
// Always require blank lines before return statements
{ blankLine: 'always', prev: '*', next: 'return' }
]
}
},
{
...stylistic.configs['disable-legacy'],
name: '(@jellyfin-vue/configs/eslint/base) Disable stylistic legacy rules'
},
/**
* Extra files to include and ignores that should override all the others
*/
{
name: '(@jellyfin-vue/configs/eslint/base) Ignored files',
ignores: [
'**/.git',
...gitignore().ignores
]
},
/** File progress plugin */
{
name: '(@jellyfin-vue/configs/eslint/base) Linting progress report',
settings: {
progress: {
successMessage: 'Linting done!'
}
},
plugins: {
'file-progress': fileProgress
},
rules: {
'file-progress/activate': CI_environment
}
}
];
};

View File

@ -0,0 +1,45 @@
import type { Linter } from 'eslint';
import globals from 'globals';
/**
* Gets the ESLint config from Node.js and development related files
* @param files - Defaults to `*config.*` and files under `scripts` folder
*/
export function getNodeFiles(files = ['*.config.*', 'scripts/**/*.ts']): Linter.Config[] {
return [{
name: '(@jellyfin-vue/configs/eslint/env) Node.js and development-related files',
files,
languageOptions: {
globals: {
...globals.node,
...globals.nodeBuiltin
}
},
rules: {
'import-x/no-extraneous-dependencies': [
'error',
{
devDependencies: true
}
],
'import-x/no-nodejs-modules': 'off'
}
}
];
}
/**
* Gets the rules config for WebWorker files
* @param files - Defaults to `*.worker.ts` files
*/
export function getWorkerFiles(files = ['**/*.worker.ts']): Linter.Config[] {
return [{
name: '(@jellyfin-vue/configs/eslint/env) WebWorkers',
files,
languageOptions: {
globals: {
...globals.worker
}
}
}];
}

View File

@ -0,0 +1,48 @@
import type { Linter } from 'eslint';
import i18nPlugin from '@intlify/eslint-plugin-vue-i18n';
import { vueAndTsFiles } from '../shared';
export const i18n = [
{
/* First index is just the plugin definition */
...i18nPlugin.configs['flat/recommended'].at(0),
/* Last contains the rule definitions */
...i18nPlugin.configs['flat/recommended'].at(-1),
name: '(@intlify/vue-i18n) Extended config (plugin & settings)',
settings: {
'vue-i18n': {
localeDir: 'locales/en.json',
messageSyntaxVersion: '^9.0.0'
}
},
files: vueAndTsFiles
},
{
name: '(@intlify/vue-i18n) Custom config',
files: vueAndTsFiles,
rules: {
'@intlify/vue-i18n/no-unused-keys': ['error', {
extensions: ['.ts', '.vue', '.json'],
enableFix: true
}],
'@intlify/vue-i18n/no-raw-text': ['error', {
ignorePattern: '^[-#:()&.]+$'
}],
'@intlify/vue-i18n/no-duplicate-keys-in-locale': 'error',
'@intlify/vue-i18n/no-dynamic-keys': 'error',
'@intlify/vue-i18n/key-format-style': 'error'
}
},
/**
* See the following:
* - https://en.wikipedia.org/wiki/History_of_sentence_spacing#French_and_English_spacing
* - https://docs.weblate.org/en/weblate-4.14.1/user/checks.html#check-punctuation-spacing
*/
{
name: '(@intlify/vue-i18n - fr) Config exceptions for linguistic rules',
files: ['locales/fr.json'],
rules: {
'no-irregular-whitespace': 'off'
}
}
] satisfies Linter.Config[];

View File

@ -0,0 +1,21 @@
import type { Linter } from 'eslint';
import jsonc from 'eslint-plugin-jsonc';
const jsoncRecommended = jsonc.configs['flat/recommended-with-json'];
export const json = [
{
/* First index is just the plugin definition */
...jsoncRecommended.at(0),
...jsoncRecommended.at(1),
name: '(@jellyfin-vue/configs/eslint/json) - Custom config',
rules: {
...jsoncRecommended.at(1).rules,
...jsoncRecommended.at(2).rules,
'jsonc/auto': 'error',
'@stylistic/quotes': ['error', 'double'],
'@stylistic/semi': 'off',
'@stylistic/quote-props': 'off'
}
}
] satisfies Linter.Config[];

View File

@ -0,0 +1,231 @@
import type { Linter } from 'eslint';
import tseslint from 'typescript-eslint';
import sonarjs from 'eslint-plugin-sonarjs';
import regexp from 'eslint-plugin-regexp';
import jsdoc from 'eslint-plugin-jsdoc';
import eslintImportX from 'eslint-plugin-import-x';
// @ts-expect-error - No types available
import vueScopedCSS from 'eslint-plugin-vue-scoped-css';
import css from 'eslint-plugin-css';
// @ts-expect-error - No types available
import vue from 'eslint-plugin-vue';
// @ts-expect-error - No types available
import promise from 'eslint-plugin-promise';
import globals from 'globals';
import vueParser from 'vue-eslint-parser';
import { eqeqeqConfig, vueAndTsFiles, vueFiles, tsFiles } from '../shared';
/**
* Util functions
*/
const flatArrayOfObjects = (obj: unknown[]) => Object.assign({}, ...obj);
/** Common TypeScript and Vue rules */
const common = [
{
...flatArrayOfObjects(tseslint.configs.strictTypeChecked),
name: '(@jellyfin-vue/configs/eslint/typescript-vue - typescript-eslint) Extended config from plugin (strict type checking)'
},
{
...flatArrayOfObjects(tseslint.configs.stylisticTypeChecked),
name: '(@jellyfin-vue/configs/eslint/typescript-vue - typescript-eslint) Extended config from plugin (stylistic type checking)'
},
{
...tseslint.configs.eslintRecommended,
name: '(@jellyfin-vue/configs/eslint/typescript-vue - typescript-eslint) Extended config from plugin (ESLint rules with type checking)'
},
{
...regexp.configs['flat/recommended'],
name: '(@jellyfin-vue/configs/eslint/typescript-vue - regexp) Extended config from plugin'
},
{
...promise.configs['flat/recommended'],
name: '(@jellyfin-vue/configs/eslint/typescript-vue - promise) Extended config from plugin'
},
{
name: '(@jellyfin-vue/configs/eslint/typescript-vue - promise) Custom config',
rules: {
'promise/prefer-await-to-callbacks': 'error',
'promise/prefer-await-to-then': 'error'
}
},
{
name: '(@jellyfin-vue/configs/eslint/typescript-vue - import) Custom config',
plugins: {
'import-x': eslintImportX
},
rules: {
'import-x/no-extraneous-dependencies': 'error',
'import-x/order': 'error',
'import-x/no-cycle': 'error',
'import-x/no-nodejs-modules': 'error',
'import-x/no-duplicates': ['error', { 'prefer-inline': true, 'considerQueryString': true }],
// From the recommended preset
'import-x/named': 'error',
'import-x/export': 'error'
}
},
{
name: '(@jellyfin-vue/configs/eslint/typescript-vue - JSDoc) Custom config',
plugins: {
jsdoc
},
rules: {
'jsdoc/require-hyphen-before-param-description': 'error',
'jsdoc/require-description': 'error',
'jsdoc/no-types': 'error',
'jsdoc/require-jsdoc': 'error',
'jsdoc/informative-docs': 'error'
}
},
{
name: '(@jellyfin-vue/configs/eslint/typescript-vue - TypeScript & Vue) Custom config',
rules: {
'@typescript-eslint/no-redundant-type-constituents': 'off',
'@typescript-eslint/restrict-template-expressions': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-dynamic-delete': 'off',
'@typescript-eslint/no-import-type-side-effects': 'error',
'@typescript-eslint/consistent-type-exports': 'error',
'@typescript-eslint/explicit-member-accessibility': 'error',
'@typescript-eslint/consistent-type-imports': ['error', {
prefer: 'type-imports',
fixStyle: 'inline-type-imports'
}],
'@typescript-eslint/no-confusing-void-expression': ['error', { ignoreArrowShorthand: true }],
'@typescript-eslint/no-empty-object-type': ['error', { allowInterfaces: 'with-single-extends' }],
'vue/return-in-computed-property': 'off'
}
},
{
...sonarjs.configs.recommended,
name: '(@jellyfin-vue/configs/eslint/typescript-vue - sonarcloud) Extended config from plugin'
}
] satisfies Linter.Config[];
/** Vue SFC only rules */
const vue_config = [
...vue.configs['flat/recommended'].map((config) => {
/**
* Specified above, unnecessary to overwrite
*/
delete config.languageOptions?.globals;
/**
* DEPRECATED: See https://eslint.vuejs.org/rules/component-tags-order.html#vue-component-tags-order
* TODO: Remove when it's removed from the recommended rules
*/
delete config.rules?.['vue/component-tags-order'];
config.name = `(@jellyfin-vue/configs/eslint/typescript-vue - ${config.name}) - Extended config from plugin`;
return config;
}),
{
...flatArrayOfObjects(vueScopedCSS.configs['flat/recommended']),
name: '(@jellyfin-vue/configs/eslint/typescript-vue - Vue Scoped CSS) Extended config from plugin',
files: vueFiles
},
{
...css.configs['flat/recommended'],
name: '(@jellyfin-vue/configs/eslint/typescript-vue - Vue CSS) Extended config from plugin',
files: vueFiles
},
{
name: '(Vue) Custom config',
files: vueFiles,
rules: {
'vue/component-name-in-template-casing': [
'error',
'PascalCase',
{
registeredComponentsOnly: false
}
],
'vue/define-macros-order': ['error', {
order: ['defineOptions', 'defineProps', 'defineEmits', 'defineSlots']
}],
'vue/html-closing-bracket-newline': ['error', { multiline: 'never' }],
'vue/block-order': ['error', {
order: ['template', 'script:not([setup])', 'script[setup]']
}],
'vue/multi-word-component-names': 'off',
'vue/require-default-prop': 'off',
'vue/eqeqeq': eqeqeqConfig,
'vue/block-lang': ['error',
{
script: {
lang: 'ts'
}
}
]
}
}
] satisfies Linter.Config[];
/**
* Gets the base configuration for TypeScript files only or both Vue and TypeScript
*
* @param enableVue - Whether to apply the base config for Vue files
* @returns
*/
export function getTSVueConfig(enableVue = true, tsconfigRootDir = import.meta.dirname): Linter.Config[] {
const result = [
...(enableVue ? vue_config : []),
...common.map(conf => ({
...conf, files: enableVue ? vueAndTsFiles : tsFiles
}))] satisfies Linter.Config[];
const langOptions = {
ecmaVersion: 2024,
sourceType: 'module',
globals: {
...globals.browser
}
};
const sharedParserOptions = {
projectService: true,
tsconfigRootDir,
warnOnUnsupportedTypeScriptVersion: false
};
// Extracted from https://github.com/vuejs/eslint-config-typescript
const base_vue_parser = {
name: '(@jellyfin-vue/configs/eslint/typescript-vue - Vue) Extra parser configuration for typed linting',
files: vueFiles,
languageOptions: {
...langOptions,
parser: vueParser,
parserOptions: {
parser: tseslint.parser,
...sharedParserOptions
}
}
};
const base_ts_parser = {
name: enableVue
? '(@jellyfin-vue/configs/eslint/typescript-vue - TypeScript & Vue) Parser extra config'
: '(@jellyfin-vue/configs/eslint/typescript-vue - TypeScript) Parser extra config',
files: enableVue ? vueAndTsFiles : tsFiles,
languageOptions: {
...langOptions,
parserOptions: {
...sharedParserOptions,
...(enableVue
? {
extraFileExtensions: ['.vue']
}
: {})
}
}
};
result.push(base_ts_parser);
if (enableVue) {
result.push(base_vue_parser);
}
return result;
}

View File

@ -0,0 +1,9 @@
import type { Linter } from 'eslint';
import UnoPlugin from '@unocss/eslint-config/flat';
export const unocss = [
{
...UnoPlugin,
name: '(unocss) Extended config from plugin'
}
] satisfies Linter.Config[];

View File

@ -0,0 +1,7 @@
/**
* Shared variables between rules
*/
export const eqeqeqConfig = ['error', 'always', { null: 'ignore' }];
export const tsFiles = ['*.ts', '**/*.ts'];
export const vueFiles = ['*.vue', '**/*.vue'];
export const vueAndTsFiles = [...vueFiles, ...tsFiles];

View File

@ -1,9 +1,40 @@
{
"name": "@jellyfin-vue/configs",
"version": "0.0.1",
"type": "module",
"exports": {
"./tsconfig.json": "./tsconfig.json",
"./index.js": "./index.js"
"./typescript/base.json": "./typescript/base.json",
"./eslint": "./eslint/index.ts",
"./unocss": "./unocss.ts"
},
"type": "module"
"scripts": {
"lint": "eslint . --max-warnings=0 --flag unstable_ts_config",
"lint:fix": "eslint . --fix --max-warnings=0 --flag unstable_ts_config",
"lint:inspect": "eslint-config-inspector",
"check:types": "vue-tsc"
},
"devDependencies": {
"@eslint/js": "9.12.0",
"@intlify/eslint-plugin-vue-i18n": "3.0.0",
"@stylistic/eslint-plugin": "2.8.0",
"@unocss/eslint-config": "0.62.3",
"eslint-config-flat-gitignore": "0.3.0",
"eslint-plugin-css": "0.11.0",
"eslint-plugin-depend": "0.11.0",
"eslint-plugin-file-progress": "1.5.0",
"eslint-plugin-import-x": "4.2.1",
"eslint-plugin-jsdoc": "50.2.2",
"eslint-plugin-jsonc": "2.16.0",
"eslint-plugin-promise": "7.1.0",
"eslint-plugin-regexp": "2.6.0",
"eslint-plugin-sonarjs": "1.0.4",
"eslint-plugin-unicorn": "55.0.0",
"eslint-plugin-vue": "9.28.0",
"eslint-plugin-vue-scoped-css": "2.8.1",
"find-up-simple": "1.0.0",
"globals": "15.9.0",
"typescript-eslint": "8.5.0",
"unocss": "0.62.3",
"vue-eslint-parser": "9.4.3"
}
}

View File

@ -1,33 +1,10 @@
{
"extends": "./typescript/base.json",
"compilerOptions": {
"target": "ESNext",
"module": "Preserve",
"moduleResolution": "Bundler",
"esModuleInterop": true,
"resolveJsonModule": true,
"sourceMap": true,
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"isolatedModules": true,
"moduleDetection": "force",
"useDefineForClassFields": true,
"noImplicitThis": true,
"noImplicitAny": true,
"noErrorTruncation": true,
"experimentalDecorators": true,
"removeComments": true,
"verbatimModuleSyntax": true,
"incremental": true,
"tsBuildInfoFile": "../node_modules/.cache/tsconfig.tsbuildinfo",
"paths": {
"@/*": ["src/*"]
},
"baseUrl": "."
},
"vueCompilerOptions": {
"strictTemplates": true,
"htmlAttributes": ["aria-*", "data-*"],
"fallthroughAttributes": true
},
"exclude": ["node_modules"]
"include": [
"eslint/**/*.ts",
"*.ts"
]
}

View File

@ -0,0 +1,34 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "ESNext",
"module": "Preserve",
"moduleResolution": "Bundler",
"esModuleInterop": true,
"resolveJsonModule": true,
"sourceMap": true,
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"isolatedModules": true,
"moduleDetection": "force",
"useDefineForClassFields": true,
"noImplicitThis": true,
"noImplicitAny": true,
"noErrorTruncation": true,
"experimentalDecorators": true,
"removeComments": true,
"verbatimModuleSyntax": true,
"incremental": true,
"tsBuildInfoFile": "../../../node_modules/.cache/tsconfig.tsbuildinfo",
"paths": {
"@/*": ["./src/*"]
},
},
"vueCompilerOptions": {
"strictTemplates": true,
"htmlAttributes": ["aria-*", "data-*"],
"fallthroughAttributes": true
},
"exclude": ["node_modules"]
}

View File

@ -0,0 +1,15 @@
import { defineConfig, presetUno } from 'unocss';
export const defaultConfig = defineConfig({
presets: [
presetUno({
prefix: 'uno-',
preflight: false
})
],
theme: {
colors: {
background: 'rgb(var(--j-color-background))'
}
}
});

View File

@ -0,0 +1,8 @@
import type { Linter } from 'eslint';
import { getBaseConfig, getTSVueConfig, getNodeFiles } from '@jellyfin-vue/configs/eslint';
export default [
...getBaseConfig('@jellyfin-vue/vite-plugins'),
...getTSVueConfig(false, import.meta.dirname),
...getNodeFiles()
] satisfies Linter.Config[];

View File

@ -3,8 +3,15 @@
"version": "0.0.1",
"exports": "./index.ts",
"type": "module",
"scripts": {
"lint": "eslint . --max-warnings=0 --flag unstable_ts_config",
"lint:fix": "eslint . --fix --max-warnings=0 --flag unstable_ts_config",
"lint:inspect": "eslint-config-inspector",
"check:types": "vue-tsc"
},
"devDependencies": {
"@jellyfin-vue/configs": "*",
"rollup-plugin-visualizer": "5.12.0",
"vite": "5.4.3"
"vite": "5.4.6"
}
}

View File

@ -0,0 +1,9 @@
{
"extends": "@jellyfin-vue/configs/typescript/base.json",
"compilerOptions": {
"baseUrl": "."
},
"include": [
"*.ts"
]
}

View File

@ -12,6 +12,7 @@ ENV COMMIT_HASH=$COMMIT_HASH
COPY package.json package-lock.json .npmrc /app/
COPY frontend /app/frontend
COPY packages /app/packages
WORKDIR /app/frontend
# Build client

View File

@ -6,7 +6,10 @@
"start": "npm run gen-icon && tauri dev",
"gen-icon": "tauri icon ../../frontend/public/icon.svg",
"build": "npm run gen-icon && tauri build",
"clean": "git clean -fxd"
"clean": "git clean -fxd",
"lint": "true",
"lint:fix": "true",
"check:types": "true"
},
"dependencies": {
"@jellyfin-vue/frontend": "*"