build: update to node 22 and pnpm (#3616)

This commit brings the project to support node 22 which became LTS and
fixes broken builds with typescript 5.7+ until vue-tsc is updated and
replaces npm with pnpm.

- Update tsconfig for node 22
- Pin typescript to 5.6.x to not break vue-tsc
- Replace npm with pnpm (corepack recommended)
- Update Makefile and main workflow for pnpm
- Migrate to eslint 9 flat config
- Fix broken imports
- Exclude non-TS vue files for vue-tsc
This commit is contained in:
kloon15 2024-12-09 12:27:18 +01:00 committed by GitHub
parent 065959451d
commit d51a343820
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 5551 additions and 8084 deletions

View File

@ -3,20 +3,25 @@ name: main
on: on:
push: push:
branches: branches:
- 'master' - "master"
tags: tags:
- 'v*' - "v*"
pull_request: pull_request:
jobs: jobs:
# linters # linters
lint-frontend: lint-frontend:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
package_json_file: "frontend/package.json"
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: '18' node-version: "22.x"
cache: "pnpm"
cache-dependency-path: "frontend/pnpm-lock.yaml"
- run: make lint-frontend - run: make lint-frontend
lint-backend: lint-backend:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -32,14 +37,19 @@ jobs:
steps: steps:
- run: echo "done" - run: echo "done"
# tests # tests
test-frontend: test-frontend:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
package_json_file: "frontend/package.json"
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: '18' node-version: "22.x"
cache: "pnpm"
cache-dependency-path: "frontend/pnpm-lock.yaml"
- run: make test-frontend - run: make test-frontend
test-backend: test-backend:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -55,7 +65,7 @@ jobs:
steps: steps:
- run: echo "done" - run: echo "done"
# release # release
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [lint, test] needs: [lint, test]
@ -67,9 +77,14 @@ jobs:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: 1.23.0 go-version: 1.23.0
- uses: pnpm/action-setup@v4
with:
package_json_file: "frontend/package.json"
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: '18' node-version: "22.x"
cache: "pnpm"
cache-dependency-path: "frontend/pnpm-lock.yaml"
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx - name: Set up Docker Buildx

View File

@ -10,7 +10,7 @@ build: | build-frontend build-backend ## Build binary
.PHONY: build-frontend .PHONY: build-frontend
build-frontend: ## Build frontend build-frontend: ## Build frontend
$Q cd frontend && npm ci && npm run build $Q cd frontend && pnpm install --frozen-lockfile && pnpm run build
.PHONY: build-backend .PHONY: build-backend
build-backend: ## Build backend build-backend: ## Build backend
@ -21,6 +21,7 @@ test: | test-frontend test-backend ## Run all tests
.PHONY: test-frontend .PHONY: test-frontend
test-frontend: ## Run frontend tests test-frontend: ## Run frontend tests
$Q cd frontend && pnpm install --frozen-lockfile && pnpm run typecheck
.PHONY: test-backend .PHONY: test-backend
test-backend: ## Run backend tests test-backend: ## Run backend tests
@ -31,7 +32,7 @@ lint: lint-frontend lint-backend ## Run all linters
.PHONY: lint-frontend .PHONY: lint-frontend
lint-frontend: ## Run frontend linters lint-frontend: ## Run frontend linters
$Q cd frontend && npm ci && npm run lint $Q cd frontend && pnpm install --frozen-lockfile && pnpm run lint
.PHONY: lint-backend .PHONY: lint-backend
lint-backend: | $(golangci-lint) ## Run backend linters lint-backend: | $(golangci-lint) ## Run backend linters
@ -65,4 +66,4 @@ help: ## Show this help
@awk 'BEGIN {FS = ":.*?## "} { \ @awk 'BEGIN {FS = ":.*?## "} { \
if (/^[a-zA-Z_-]+:.*?##.*$$/) {printf " ${YELLOW}%-20s${GREEN}%s${RESET}\n", $$1, $$2} \ if (/^[a-zA-Z_-]+:.*?##.*$$/) {printf " ${YELLOW}%-20s${GREEN}%s${RESET}\n", $$1, $$2} \
else if (/^## .*$$/) {printf " ${CYAN}%s${RESET}\n", substr($$1,4)} \ else if (/^## .*$$/) {printf " ${CYAN}%s${RESET}\n", substr($$1,4)} \
}' $(MAKEFILE_LIST) }' $(MAKEFILE_LIST)

View File

@ -1,27 +0,0 @@
{
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/eslint-config-typescript",
"@vue/eslint-config-prettier"
],
"rules": {
"vue/multi-word-component-names": "off",
"vue/no-mutating-props": [
"error",
{
"shallowOnly": true
}
]
// no-undef is already included in
// @vue/eslint-config-typescript
},
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
}
}

39
frontend/eslint.config.js Normal file
View File

@ -0,0 +1,39 @@
import pluginVue from "eslint-plugin-vue";
import vueTsEslintConfig from "@vue/eslint-config-typescript";
import prettierConfig from "@vue/eslint-config-prettier";
export default [
{
name: "app/files-to-lint",
files: ["**/*.{ts,mts,tsx,vue}"],
},
{
name: "app/files-to-ignore",
ignores: ["**/dist/**", "**/dist-ssr/**", "**/coverage/**"],
},
...pluginVue.configs["flat/essential"],
...vueTsEslintConfig(),
prettierConfig,
{
rules: {
"no-var": "warn",
"prefer-const": "warn",
// Note: you must disable the base rule as it can report incorrect errors
"no-unused-expressions": "off",
"@typescript-eslint/no-unused-expressions": "warn",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/ban-ts-comment": "warn",
"vue/block-lang": "warn",
"vue/multi-word-component-names": "off",
"vue/no-mutating-props": [
"error",
{
shallowOnly: true,
},
],
},
},
];

View File

@ -1,10 +0,0 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

File diff suppressed because it is too large Load Diff

View File

@ -4,32 +4,33 @@
"private": true, "private": true,
"type": "module", "type": "module",
"engines": { "engines": {
"npm": ">=7.0.0", "node": ">=22.0.0",
"node": ">=18.0.0" "pnpm": ">=9.0.0"
}, },
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
"build": "npm run typecheck && vite build", "build": "pnpm run typecheck && vite build",
"clean": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitkeep' -exec rm -r {} +", "clean": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitkeep' -exec rm -r {} +",
"typecheck": "vue-tsc -p ./tsconfig.json --noEmit", "typecheck": "vue-tsc -p ./tsconfig.tsc.json --noEmit",
"lint": "npm run typecheck && eslint --ext .vue,.ts src/", "lint": "eslint src/",
"lint:fix": "eslint --ext .vue,.ts --fix src/", "lint:fix": "eslint --fix src/",
"format": "prettier --write .", "format": "prettier --write .",
"test": "playwright test" "test": "playwright test"
}, },
"dependencies": { "dependencies": {
"@chenfengyuan/vue-number-input": "^2.0.1", "@chenfengyuan/vue-number-input": "^2.0.1",
"@vueuse/core": "^10.9.0", "@vueuse/core": "^12.0.0",
"@vueuse/integrations": "^10.9.0", "@vueuse/integrations": "^12.0.0",
"ace-builds": "^1.32.9", "ace-builds": "^1.32.9",
"core-js": "^3.36.1", "core-js": "^3.36.1",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"epubjs": "^0.3.93",
"filesize": "^10.1.1", "filesize": "^10.1.1",
"js-base64": "^3.7.7", "js-base64": "^3.7.7",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"marked": "^15.0.3",
"material-icons": "^1.13.12", "material-icons": "^1.13.12",
"marked": "^14.1.0",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"pretty-bytes": "^6.1.1", "pretty-bytes": "^6.1.1",
@ -41,33 +42,41 @@
"videojs-mobile-ui": "^1.1.1", "videojs-mobile-ui": "^1.1.1",
"vue": "^3.4.21", "vue": "^3.4.21",
"vue-final-modal": "^4.5.4", "vue-final-modal": "^4.5.4",
"vue-i18n": "^9.14.2", "vue-i18n": "^10.0.5",
"vue-lazyload": "^3.0.0", "vue-lazyload": "^3.0.0",
"vue-reader": "^1.2.14", "vue-reader": "^1.2.14",
"vue-router": "^4.3.0", "vue-router": "^4.3.0",
"vue-toastification": "^2.0.0-rc.5" "vue-toastification": "^2.0.0-rc.5"
}, },
"devDependencies": { "devDependencies": {
"@intlify/unplugin-vue-i18n": "^4.0.0", "@intlify/unplugin-vue-i18n": "^6.0.0",
"@playwright/test": "^1.42.1", "@playwright/test": "^1.42.1",
"@tsconfig/node22": "^22.0.0",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/node": "^20.12.2", "@types/node": "^22.10.1",
"@typescript-eslint/eslint-plugin": "^7.4.0", "@typescript-eslint/eslint-plugin": "^8.17.0",
"@vitejs/plugin-legacy": "^5.3.2", "@vitejs/plugin-legacy": "^6.0.0",
"@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue": "^5.0.4",
"@vue/eslint-config-prettier": "^9.0.0", "@vue/eslint-config-prettier": "^10.1.0",
"@vue/eslint-config-typescript": "^13.0.0", "@vue/eslint-config-typescript": "^14.1.4",
"@vue/tsconfig": "^0.7.0",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"concurrently": "^8.2.2", "concurrently": "^9.1.0",
"eslint": "^8.57.0", "eslint": "^9.16.0",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "^9.24.0", "eslint-plugin-vue": "^9.24.0",
"jsdom": "^24.0.0", "jsdom": "^25.0.1",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"terser": "^5.30.0", "terser": "^5.30.0",
"vite": "^5.4.6", "vite": "^6.0.2",
"vite-plugin-compression2": "^1.0.0", "vite-plugin-compression2": "^1.0.0",
"vue-tsc": "^2.0.7" "vue-tsc": "^2.0.7"
} },
"pnpm": {
"overrides": {
"typescript": "~5.6.3"
}
},
"packageManager": "pnpm@9.14.4+sha512.c8180b3fbe4e4bca02c94234717896b5529740a6cbadf19fa78254270403ea2f27d4e1d46a08a0f56c89b63dc8ebfd3ee53326da720273794e6200fcf0d184ab"
} }

5386
frontend/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -50,7 +50,7 @@ import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout"; import { useLayoutStore } from "@/stores/layout";
import { commands } from "@/api"; import { commands } from "@/api";
import { throttle } from "lodash"; import { throttle } from "lodash-es";
import { theme } from "@/utils/constants"; import { theme } from "@/utils/constants";
export default { export default {

View File

@ -14,15 +14,15 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import throttle from "lodash/throttle"; import { throttle } from "lodash-es";
import UTIF from "utif"; import UTIF from "utif";
import { onBeforeUnmount, onMounted, ref, watch } from "vue"; import { onBeforeUnmount, onMounted, ref, watch } from "vue";
interface IProps { interface IProps {
src: string; src: string;
moveDisabledTime: number; moveDisabledTime?: number;
classList: any[]; classList?: any[];
zoomStep: number; zoomStep?: number;
} }
const props = withDefaults(defineProps<IProps>(), { const props = withDefaults(defineProps<IProps>(), {

View File

@ -120,7 +120,7 @@ const subLabel = (subUrl: string) => {
let url: URL; let url: URL;
try { try {
url = new URL(subUrl); url = new URL(subUrl);
} catch (_) { } catch {
// treat it as a relative url // treat it as a relative url
// we only need this for filename // we only need this for filename
url = new URL(subUrl, window.location.origin); url = new URL(subUrl, window.location.origin);

View File

@ -7,7 +7,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { SelectHTMLAttributes } from "vue"; import type { SelectHTMLAttributes } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const { t } = useI18n(); const { t } = useI18n();

View File

@ -4,7 +4,7 @@ import VueNumberInput from "@chenfengyuan/vue-number-input";
import VueLazyload from "vue-lazyload"; import VueLazyload from "vue-lazyload";
import { createVfm } from "vue-final-modal"; import { createVfm } from "vue-final-modal";
import Toast, { POSITION, useToast } from "vue-toastification"; import Toast, { POSITION, useToast } from "vue-toastification";
import { import type {
ToastOptions, ToastOptions,
PluginOptions, PluginOptions,
} from "vue-toastification/dist/types/types"; } from "vue-toastification/dist/types/types";

View File

@ -1,4 +1,5 @@
import { RouteLocation, createRouter, createWebHistory } from "vue-router"; import type { RouteLocation } from "vue-router";
import { createRouter, createWebHistory } from "vue-router";
import Login from "@/views/Login.vue"; import Login from "@/views/Login.vue";
import Layout from "@/views/Layout.vue"; import Layout from "@/views/Layout.vue";
import Files from "@/views/Files.vue"; import Files from "@/views/Files.vue";

View File

@ -1,6 +1,6 @@
import { createPinia as _createPinia } from "pinia"; import { createPinia as _createPinia } from "pinia";
import { markRaw } from "vue"; import { markRaw } from "vue";
import { Router } from "vue-router"; import type { Router } from "vue-router";
export default function createPinia(router: Router) { export default function createPinia(router: Router) {
const pinia = _createPinia(); const pinia = _createPinia();

View File

@ -1,7 +1,7 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { useFileStore } from "./file"; import { useFileStore } from "./file";
import { files as api } from "@/api"; import { files as api } from "@/api";
import throttle from "lodash/throttle"; import { throttle } from "lodash-es";
import buttons from "@/utils/buttons"; import buttons from "@/utils/buttons";
// TODO: make this into a user setting // TODO: make this into a user setting

View File

@ -1,6 +1,7 @@
import { useAuthStore } from "@/stores/auth"; import { useAuthStore } from "@/stores/auth";
import router from "@/router"; import router from "@/router";
import { JwtPayload, jwtDecode } from "jwt-decode"; import type { JwtPayload } from "jwt-decode";
import { jwtDecode } from "jwt-decode";
import { baseURL, noAuth } from "./constants"; import { baseURL, noAuth } from "./constants";
import { StatusError } from "@/api/utils"; import { StatusError } from "@/api/utils";

View File

@ -285,7 +285,7 @@ import { users, files as api } from "@/api";
import { enableExec } from "@/utils/constants"; import { enableExec } from "@/utils/constants";
import * as upload from "@/utils/upload"; import * as upload from "@/utils/upload";
import css from "@/utils/css"; import css from "@/utils/css";
import throttle from "lodash/throttle"; import { throttle } from "lodash-es";
import { Base64 } from "js-base64"; import { Base64 } from "js-base64";
import HeaderBar from "@/components/header/HeaderBar.vue"; import HeaderBar from "@/components/header/HeaderBar.vue";

View File

@ -168,7 +168,7 @@ import { files as api } from "@/api";
import { createURL } from "@/api/utils"; import { createURL } from "@/api/utils";
import { resizePreview } from "@/utils/constants"; import { resizePreview } from "@/utils/constants";
import url from "@/utils/url"; import url from "@/utils/url";
import throttle from "lodash/throttle"; import { throttle } from "lodash-es";
import HeaderBar from "@/components/header/HeaderBar.vue"; import HeaderBar from "@/components/header/HeaderBar.vue";
import Action from "@/components/header/Action.vue"; import Action from "@/components/header/Action.vue";
import ExtendedImage from "@/components/files/ExtendedImage.vue"; import ExtendedImage from "@/components/files/ExtendedImage.vue";

View File

@ -0,0 +1,13 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"types": ["vite/client", "@intlify/unplugin-vue-i18n/messages"],
"paths": {
"@/*": ["./src/*"]
}
}
}

View File

@ -1,24 +1,11 @@
{ {
"compilerOptions": { "files": [],
"baseUrl": ".", "references": [
"allowJs": true, {
"target": "ESNext", "path": "./tsconfig.node.json"
"useDefineForClassFields": true, },
"module": "ESNext", {
"moduleResolution": "Node10", "path": "./tsconfig.app.json"
"strict": true,
"sourceMap": true,
"noImplicitReturns": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"types": ["vite/client", "@intlify/unplugin-vue-i18n/messages"],
"paths": {
"@/*": ["./src/*"]
} }
}, ]
"include": ["src/**/*.ts", "src/**/*.vue"],
"exclude": ["node_modules", "dist"]
} }

View File

@ -0,0 +1,18 @@
{
"extends": "@tsconfig/node22/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}

View File

@ -0,0 +1,14 @@
{
"extends": "./tsconfig.app.json",
// vue-tsc wont shut up about error TS9005
// in non-TS vue files so exclude them
"exclude": [
"src/components/Shell.vue",
"src/components/prompts/Copy.vue",
"src/components/prompts/Delete.vue",
"src/components/prompts/FileList.vue",
"src/components/prompts/Rename.vue",
"src/components/prompts/Share.vue",
"src/components/prompts/UploadFiles.vue"
]
}