@ciridae/configs 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @ciridae/configs
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add `@ciridae/configs/vite` with `versionManifest`, which writes `version.json` for the update prompt, and `sentrySourceMaps`, which uploads production source maps to Sentry when its credentials are set.
8
+
9
+ ## 0.3.0
10
+
11
+ ### Minor Changes
12
+
13
+ - Keep one ESLint 10 preset with regexp correctness checks and remove copied code-shape, compiler-advisory, and Tailwind lint policy.
14
+
3
15
  ## 0.2.0
4
16
 
5
17
  ### Minor Changes
package/README.md CHANGED
@@ -23,3 +23,20 @@ export { default } from "@ciridae/configs/prettier" with { type: "json" }
23
23
  "extends": "@ciridae/configs/tsconfig/frontend-app.json"
24
24
  }
25
25
  ```
26
+
27
+ ## Vite
28
+
29
+ ```ts
30
+ import { sentrySourceMaps, versionManifest } from "@ciridae/configs/vite"
31
+
32
+ export default defineConfig(({ mode }) => ({
33
+ plugins: [
34
+ versionManifest({ buildSha: process.env.VITE_BUILD_SHA ?? "development" }),
35
+ sentrySourceMaps({ mode }),
36
+ ],
37
+ }))
38
+ ```
39
+
40
+ `versionManifest` writes `version.json` beside the bundle so an open tab can
41
+ tell a newer build is deployed. `sentrySourceMaps` uploads production source
42
+ maps when `SENTRY_ORG`, `SENTRY_PROJECT`, and `SENTRY_AUTH_TOKEN` are set.
package/eslint.js CHANGED
@@ -1,26 +1,49 @@
1
- import { fixupPluginRules } from "@eslint/compat";
2
1
  import eslintReact from "@eslint-react/eslint-plugin";
3
2
  import js from "@eslint/js";
4
3
  import { createNodeResolver, importX } from "eslint-plugin-import-x";
5
4
  import globals from "globals";
6
5
  import reactHooks from "eslint-plugin-react-hooks";
7
6
  import reactRefresh from "eslint-plugin-react-refresh";
8
- import tailwindcss from "eslint-plugin-tailwindcss";
7
+ import regexpPlugin from "eslint-plugin-regexp";
9
8
  import tseslint from "typescript-eslint";
10
9
 
11
- const typeAssertionBan = [
12
- {
13
- selector:
14
- "TSAsExpression:not([typeAnnotation.type='TSTypeReference'][typeAnnotation.typeName.name='const'])",
15
- message:
16
- "Type assertions are banned. Prefer narrowing, parser/schema validation, or an eslint-disable with justification.",
17
- },
18
- {
19
- selector: "TSTypeAssertion",
20
- message:
21
- "Type assertions are banned. Prefer narrowing, parser/schema validation, or an eslint-disable with justification.",
22
- },
23
- ];
10
+ const reactCompilerAdvisoryRules = {
11
+ "react-hooks/config": "off",
12
+ "react-hooks/gating": "off",
13
+ "react-hooks/globals": "off",
14
+ "react-hooks/immutability": "off",
15
+ "react-hooks/incompatible-library": "off",
16
+ "react-hooks/preserve-manual-memoization": "off",
17
+ "react-hooks/purity": "off",
18
+ "react-hooks/refs": "off",
19
+ "react-hooks/set-state-in-effect": "off",
20
+ "react-hooks/static-components": "off",
21
+ "react-hooks/unsupported-syntax": "off",
22
+ "react-hooks/use-memo": "off",
23
+ };
24
+
25
+ const recommendedRegexpRules = Object.fromEntries(
26
+ Object.entries(regexpPlugin.configs["flat/recommended"].rules)
27
+ .filter(
28
+ ([ruleName, setting]) =>
29
+ ruleName.startsWith("regexp/") && setting !== "off" && setting !== 0,
30
+ )
31
+ .map(([ruleName, setting]) => [
32
+ ruleName,
33
+ Array.isArray(setting) ? ["error", ...setting.slice(1)] : "error",
34
+ ]),
35
+ );
36
+
37
+ const regexpRules = {
38
+ ...recommendedRegexpRules,
39
+ "no-empty-character-class": "off",
40
+ "no-invalid-regexp": "off",
41
+ "no-misleading-character-class": "off",
42
+ "no-useless-backreference": "off",
43
+ "prefer-regex-literals": "error",
44
+ "regexp/no-super-linear-move": "error",
45
+ "regexp/prefer-escape-replacement-dollar-char": "error",
46
+ };
24
47
 
25
48
  export function reactAppConfig({ ignores = ["dist"], rules = {} } = {}) {
26
49
  return tseslint.config(
@@ -35,9 +58,12 @@ export function reactAppConfig({ ignores = ["dist"], rules = {} } = {}) {
35
58
  plugins: {
36
59
  "react-hooks": reactHooks,
37
60
  "react-refresh": reactRefresh,
61
+ regexp: regexpPlugin,
38
62
  },
39
63
  rules: {
40
64
  ...reactHooks.configs.recommended.rules,
65
+ ...reactCompilerAdvisoryRules,
66
+ ...regexpRules,
41
67
  "react-refresh/only-export-components": [
42
68
  "warn",
43
69
  { allowConstantExport: true },
@@ -57,9 +83,6 @@ function createStrictReactAppConfig(
57
83
  {
58
84
  ignores = ["dist"],
59
85
  tsconfigRootDir,
60
- maxLines = 300,
61
- maxLinesPerFunction = 80,
62
- tailwindCssFiles,
63
86
  rules = {},
64
87
  settings = {},
65
88
  testFiles = ["**/*.test.{ts,tsx}", "**/test/**/*.{ts,tsx}"],
@@ -71,7 +94,7 @@ function createStrictReactAppConfig(
71
94
  "import-x": importX,
72
95
  "react-hooks": reactHooks,
73
96
  "react-refresh": reactRefresh,
74
- tailwindcss: fixupPluginRules(tailwindcss),
97
+ regexp: regexpPlugin,
75
98
  };
76
99
 
77
100
  return tseslint.config(
@@ -93,6 +116,8 @@ function createStrictReactAppConfig(
93
116
  plugins,
94
117
  rules: {
95
118
  ...plugins["react-hooks"].configs.recommended.rules,
119
+ ...reactCompilerAdvisoryRules,
120
+ ...regexpRules,
96
121
  "@typescript-eslint/no-explicit-any": "error",
97
122
  "@typescript-eslint/no-non-null-assertion": "error",
98
123
  "@typescript-eslint/no-unnecessary-type-assertion": "error",
@@ -103,25 +128,7 @@ function createStrictReactAppConfig(
103
128
  "@typescript-eslint/no-unsafe-member-access": "error",
104
129
  "@typescript-eslint/no-unsafe-return": "error",
105
130
  "@typescript-eslint/switch-exhaustiveness-check": "error",
106
- complexity: ["error", 12],
107
- "import-x/no-cycle": "error",
108
131
  "import-x/no-self-import": "error",
109
- "max-depth": ["error", 3],
110
- "max-lines": [
111
- "error",
112
- { max: maxLines, skipBlankLines: true, skipComments: true },
113
- ],
114
- "max-lines-per-function": [
115
- "error",
116
- {
117
- max: maxLinesPerFunction,
118
- skipBlankLines: true,
119
- skipComments: true,
120
- },
121
- ],
122
- "max-nested-callbacks": ["error", 3],
123
- "max-params": ["error", 4],
124
- "no-restricted-syntax": ["error", ...typeAssertionBan],
125
132
  "@eslint-react/no-nested-component-definitions": "error",
126
133
  "@eslint-react/no-unstable-context-value": "error",
127
134
  "react-refresh/only-export-components": [
@@ -138,14 +145,6 @@ function createStrictReactAppConfig(
138
145
  "import-x/resolver-next": [
139
146
  createNodeResolver({ extensions: [".js", ".jsx", ".ts", ".tsx"] }),
140
147
  ],
141
- ...(tailwindCssFiles
142
- ? {
143
- tailwindcss: {
144
- config: {},
145
- cssFiles: tailwindCssFiles,
146
- },
147
- }
148
- : {}),
149
148
  ...settings,
150
149
  },
151
150
  },
@@ -159,10 +158,6 @@ function createStrictReactAppConfig(
159
158
  "@typescript-eslint/no-unsafe-return": "off",
160
159
  "@typescript-eslint/no-explicit-any": "off",
161
160
  "@typescript-eslint/no-non-null-assertion": "off",
162
- "no-restricted-syntax": "off",
163
- "max-lines-per-function": "off",
164
- "max-lines": "off",
165
- "max-nested-callbacks": "off",
166
161
  },
167
162
  },
168
163
  ...overrides,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ciridae/configs",
3
- "version": "0.2.0",
4
- "description": "Shared ESLint, Prettier, and TypeScript configurations for Ciridae projects.",
3
+ "version": "0.4.0",
4
+ "description": "Shared ESLint, Prettier, TypeScript, and Vite configurations for Ciridae projects.",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": ">=22.18"
@@ -16,6 +16,8 @@
16
16
  "CHANGELOG.md",
17
17
  "eslint.js",
18
18
  "prettier.json",
19
+ "vite.js",
20
+ "vite.d.ts",
19
21
  "tsconfig"
20
22
  ],
21
23
  "exports": {
@@ -23,23 +25,40 @@
23
25
  "./eslint": "./eslint.js",
24
26
  "./prettier": "./prettier.json",
25
27
  "./tsconfig/frontend-app.json": "./tsconfig/frontend-app.json",
26
- "./tsconfig/frontend-node.json": "./tsconfig/frontend-node.json"
28
+ "./tsconfig/frontend-node.json": "./tsconfig/frontend-node.json",
29
+ "./vite": {
30
+ "types": "./vite.d.ts",
31
+ "default": "./vite.js"
32
+ }
27
33
  },
28
34
  "publishConfig": {
29
35
  "access": "public"
30
36
  },
31
37
  "dependencies": {
32
- "@eslint/compat": "^2.1.0",
33
38
  "@eslint-react/eslint-plugin": "^5.14.6",
34
39
  "@eslint/js": "^10.0.1",
35
40
  "eslint-plugin-import-x": "^4.17.1",
36
41
  "eslint-plugin-react-hooks": "^7.1.1",
37
42
  "eslint-plugin-react-refresh": "^0.5.3",
38
- "eslint-plugin-tailwindcss": "4.0.5",
43
+ "eslint-plugin-regexp": "^3.1.1",
39
44
  "globals": "^17.7.0",
40
45
  "typescript-eslint": "^8.63.0"
41
46
  },
42
47
  "peerDependencies": {
43
- "eslint": "^10.7.0"
48
+ "eslint": "^10.7.0",
49
+ "vite": ">=8",
50
+ "@sentry/vite-plugin": ">=5"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "vite": {
54
+ "optional": true
55
+ },
56
+ "@sentry/vite-plugin": {
57
+ "optional": true
58
+ }
59
+ },
60
+ "devDependencies": {
61
+ "@sentry/vite-plugin": "^5.2.1",
62
+ "vite": "catalog:frontend8"
44
63
  }
45
64
  }
package/vite.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type { Plugin, PluginOption } from "vite";
2
+
3
+ export function versionManifest(options: {
4
+ buildSha: string;
5
+ deployedBuildSha?: string;
6
+ }): Plugin[];
7
+
8
+ export function sentrySourceMaps(options: { mode: string }): PluginOption;
package/vite.js ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Writes `version.json` beside the bundle so an open tab can tell the deployment moved on
3
+ * without it. In development it serves the same file, and a `deployedBuildSha` other than
4
+ * `buildSha` exercises the update prompt.
5
+ *
6
+ * @param {{ buildSha: string, deployedBuildSha?: string }} options
7
+ * @returns {import("vite").Plugin[]}
8
+ */
9
+ export function versionManifest({ buildSha, deployedBuildSha = buildSha }) {
10
+ return [
11
+ {
12
+ name: "ciridae-version-manifest",
13
+ apply: "build",
14
+ generateBundle() {
15
+ this.emitFile({
16
+ type: "asset",
17
+ fileName: "version.json",
18
+ source: `${JSON.stringify({ buildSha })}\n`,
19
+ });
20
+ },
21
+ },
22
+ {
23
+ name: "ciridae-dev-version-manifest",
24
+ apply: "serve",
25
+ configureServer(server) {
26
+ server.middlewares.use("/version.json", (_request, response) => {
27
+ response.setHeader("Content-Type", "application/json");
28
+ response.end(`${JSON.stringify({ buildSha: deployedBuildSha })}\n`);
29
+ });
30
+ },
31
+ },
32
+ ];
33
+ }
34
+
35
+ /**
36
+ * Uploads production source maps to Sentry, then deletes them from the output, when the build
37
+ * has `SENTRY_ORG`, `SENTRY_PROJECT`, and `SENTRY_AUTH_TOKEN`. Otherwise it adds nothing.
38
+ *
39
+ * @param {{ mode: string }} options
40
+ * @returns {import("vite").PluginOption}
41
+ */
42
+ export function sentrySourceMaps({ mode }) {
43
+ const {
44
+ SENTRY_ORG: org,
45
+ SENTRY_PROJECT: project,
46
+ SENTRY_AUTH_TOKEN: authToken,
47
+ } = process.env;
48
+ if (mode !== "production" || !org || !project || !authToken) return [];
49
+ return [
50
+ {
51
+ name: "ciridae-sentry-source-maps",
52
+ config: () => ({ build: { sourcemap: "hidden" } }),
53
+ },
54
+ import("@sentry/vite-plugin").then(({ sentryVitePlugin }) =>
55
+ sentryVitePlugin({
56
+ org,
57
+ project,
58
+ authToken,
59
+ sourcemaps: { filesToDeleteAfterUpload: ["dist/**/*.js.map"] },
60
+ }),
61
+ ),
62
+ ];
63
+ }