mirror of
https://github.com/certimate-go/certimate.git
synced 2026-09-03 06:23:54 +08:00
chore: improve ui build scripts
This commit is contained in:
@@ -7,13 +7,7 @@ import reactHooksPlugin from "eslint-plugin-react-hooks";
|
||||
import reactRefreshPlugin from "eslint-plugin-react-refresh";
|
||||
import typescriptPlugin from "typescript-eslint";
|
||||
|
||||
import noRootRelativeUrlRule from "./eslint-rules/no-root-relative-url.mjs";
|
||||
|
||||
const certimatePlugin = {
|
||||
rules: {
|
||||
"no-root-relative-url": noRootRelativeUrlRule,
|
||||
},
|
||||
};
|
||||
import noRootRelativeUrlRule from "./scripts/eslint/rules/no-root-relative-url.mjs";
|
||||
|
||||
/**
|
||||
* @type {import("eslint").Linter.Config[]}
|
||||
@@ -145,17 +139,6 @@ export default defineConfig(
|
||||
},
|
||||
},
|
||||
|
||||
// Certimate
|
||||
{
|
||||
name: "certimate",
|
||||
plugins: {
|
||||
certimate: certimatePlugin,
|
||||
},
|
||||
rules: {
|
||||
"certimate/no-root-relative-url": "error",
|
||||
},
|
||||
},
|
||||
|
||||
// TailwindCSS
|
||||
{
|
||||
name: "tailwindcss",
|
||||
@@ -174,5 +157,20 @@ export default defineConfig(
|
||||
entryPoint: "src/global.css",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Certimate
|
||||
{
|
||||
name: "certimate",
|
||||
plugins: {
|
||||
certimate: {
|
||||
rules: {
|
||||
"no-root-relative-url": noRootRelativeUrlRule,
|
||||
},
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"certimate/no-root-relative-url": "error",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const TARGET_ATTRIBUTES = new Set(["href", "src"]);
|
||||
const targetAttrs = ["href", "src"];
|
||||
|
||||
const getStaticPrefix = (node) => {
|
||||
if (!node) {
|
||||
@@ -21,27 +21,21 @@ const getStaticPrefix = (node) => {
|
||||
export default {
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
description: "Disallow root-relative URLs in JSX href and src attributes",
|
||||
},
|
||||
messages: {
|
||||
useBasePath: "Root-relative {{attribute}} bypasses the application base path. Wrap the URL with withBasePath().",
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
JSXAttribute(node) {
|
||||
const attribute = node.name.type === "JSXIdentifier" ? node.name.name : "";
|
||||
if (!TARGET_ATTRIBUTES.has(attribute)) {
|
||||
if (!targetAttrs.includes(attribute)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const value = node.value?.type === "JSXExpressionContainer" ? getStaticPrefix(node.value.expression) : getStaticPrefix(node.value);
|
||||
if (value?.startsWith("/") && !value.startsWith("//")) {
|
||||
if (!!value && value.startsWith("/") && !value.startsWith("//")) {
|
||||
context.report({
|
||||
node: node.value ?? node,
|
||||
messageId: "useBasePath",
|
||||
message: "Root-relative {{attribute}} bypasses the application base path. Wrap the URL with `withBasePath()` utility function.",
|
||||
data: { attribute },
|
||||
});
|
||||
}
|
||||
40
ui/scripts/vite/plugins/preserve-files-plugin.ts
Normal file
40
ui/scripts/vite/plugins/preserve-files-plugin.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import path from "node:path";
|
||||
|
||||
import fs from "fs-extra";
|
||||
import { type Plugin } from "vite";
|
||||
|
||||
interface Options {
|
||||
files: string[];
|
||||
}
|
||||
|
||||
export default function (options: Options): Plugin {
|
||||
const tmpdir = path.resolve("./", "node_modules/.vite/certimate-temp");
|
||||
|
||||
return {
|
||||
name: "preserve-files",
|
||||
apply: "build",
|
||||
buildStart() {
|
||||
// 在构建开始时将要保留的文件或目录移动到临时位置
|
||||
options.files.forEach((file) => {
|
||||
const src = path.resolve("./", file);
|
||||
const dist = path.resolve(tmpdir, file);
|
||||
if (fs.existsSync(src)) {
|
||||
fs.moveSync(src, dist, { overwrite: true });
|
||||
}
|
||||
});
|
||||
},
|
||||
closeBundle() {
|
||||
// 在构建完成后将临时位置的文件或目录移回原来的位置
|
||||
options.files.forEach((file) => {
|
||||
const src = path.resolve("./", file);
|
||||
const dist = path.resolve(tmpdir, file);
|
||||
if (fs.existsSync(dist)) {
|
||||
fs.moveSync(dist, src, { overwrite: true });
|
||||
}
|
||||
});
|
||||
|
||||
// 清理临时目录
|
||||
fs.emptyDirSync(tmpdir);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import AppLocale from "@/components/AppLocale";
|
||||
import AppTheme from "@/components/AppTheme";
|
||||
import AppVersion from "@/components/AppVersion";
|
||||
import { useAntdForm, useBrowserTheme } from "@/hooks";
|
||||
|
||||
import { authWithPassword } from "@/repository/admin";
|
||||
import { unwrapErrMsg } from "@/utils/error";
|
||||
import { withBasePath } from "@/utils/url";
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import PocketBase from "pocketbase";
|
||||
|
||||
import { APP_BASE_PATH } from "@/utils/url";
|
||||
import { getBasePath } from "@/utils/url";
|
||||
|
||||
let pb: PocketBase;
|
||||
export const getPocketBase = () => {
|
||||
if (pb) return pb;
|
||||
pb = new PocketBase(APP_BASE_PATH);
|
||||
pb = new PocketBase(getBasePath());
|
||||
pb.afterSend = (res, data) => {
|
||||
if ((res.status === 401 || res.status === 403) && pb.authStore?.isValid) {
|
||||
pb.authStore.clear();
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
const appBaseUrl = new URL(import.meta.env.BASE_URL, document.baseURI);
|
||||
const urlSchemePattern = /^[a-z][a-z\d+.-]*:/i;
|
||||
const baseUrl = new URL(import.meta.env.BASE_URL, document.baseURI);
|
||||
|
||||
// PocketBase 会把自身的 /api 路径拼接到这里,因此基础路径必须以斜杠结尾。
|
||||
export const APP_BASE_PATH = appBaseUrl.pathname;
|
||||
export const getBasePath = () => {
|
||||
return baseUrl.pathname;
|
||||
};
|
||||
|
||||
export const withBasePath = (path: string) => {
|
||||
// 完整 URL 和协议相对 URL 不属于应用内资源,保持调用方传入的地址不变。
|
||||
if (urlSchemePattern.test(path) || path.startsWith("//")) {
|
||||
if (/^[a-z][a-z\d+.-]*:/i.test(path) || path.startsWith("//")) {
|
||||
return path;
|
||||
}
|
||||
|
||||
const url = new URL(path.replace(/^\/+/, ""), appBaseUrl);
|
||||
const url = new URL(path.replace(/^\/+/, ""), baseUrl);
|
||||
return `${url.pathname}${url.search}${url.hash}`;
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"noEmit": true
|
||||
},
|
||||
"include": [
|
||||
"vite.config.ts"
|
||||
"vite.config.ts",
|
||||
"scripts"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -4,34 +4,9 @@ import tailwindcssPlugin from "@tailwindcss/vite";
|
||||
import legacyPlugin from "@vitejs/plugin-legacy";
|
||||
import reactPlugin from "@vitejs/plugin-react";
|
||||
import fs from "fs-extra";
|
||||
import { type Plugin, defineConfig } from "vite";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
const preserveFilesPlugin = (filesToPreserve: string[]): Plugin => {
|
||||
return {
|
||||
name: "preserve-files",
|
||||
apply: "build",
|
||||
buildStart() {
|
||||
// 在构建开始时将要保留的文件或目录移动到临时位置
|
||||
filesToPreserve.forEach((file) => {
|
||||
const srcPath = path.resolve(__dirname, file);
|
||||
const tempPath = path.resolve(__dirname, `node_modules/.tmp/build/${file}`);
|
||||
if (fs.existsSync(srcPath)) {
|
||||
fs.moveSync(srcPath, tempPath, { overwrite: true });
|
||||
}
|
||||
});
|
||||
},
|
||||
closeBundle() {
|
||||
// 在构建完成后将临时位置的文件或目录移回原来的位置
|
||||
filesToPreserve.forEach((file) => {
|
||||
const srcPath = path.resolve(__dirname, file);
|
||||
const tempPath = path.resolve(__dirname, `node_modules/.tmp/build/${file}`);
|
||||
if (fs.existsSync(tempPath)) {
|
||||
fs.moveSync(tempPath, srcPath, { overwrite: true });
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
import preserveFilesPlugin from "./scripts/vite/plugins/preserve-files-plugin";
|
||||
|
||||
export default defineConfig(({ command }) => {
|
||||
let appVersion = undefined;
|
||||
@@ -45,14 +20,13 @@ export default defineConfig(({ command }) => {
|
||||
}
|
||||
console.info("[certimate] AppVersion is " + appVersion);
|
||||
} else {
|
||||
throw new Error("AppVersion not found in '/internal/app/app.go'");
|
||||
throw new Error("`AppVersion` not found in '/internal/app/app.go'");
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error("Could not read app version: " + (err as Error).message);
|
||||
}
|
||||
|
||||
return {
|
||||
// 使用相对基础路径,让同一份构建产物可以部署在根路径或任意反向代理子路径下。
|
||||
base: "./",
|
||||
define: {
|
||||
__APP_VERSION__: JSON.stringify(appVersion),
|
||||
@@ -78,8 +52,10 @@ export default defineConfig(({ command }) => {
|
||||
renderLegacyChunks: false,
|
||||
renderModernChunks: true,
|
||||
}),
|
||||
tailwindcssPlugin(),
|
||||
preserveFilesPlugin(["dist/.gitkeep"]),
|
||||
tailwindcssPlugin({}),
|
||||
preserveFilesPlugin({
|
||||
files: ["dist/.gitkeep"],
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
|
||||
Reference in New Issue
Block a user