@oxiul/configs 0.0.0-stage → 0.3.2

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/README.md CHANGED
@@ -1,3 +1,16 @@
1
- # Temporary Holding Version
1
+ # Configs
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ ## Lint config
4
+
5
+ A shared config for oxlint
6
+
7
+ ## Format config
8
+
9
+ A shared config for oxfmt
10
+
11
+ ## Development
12
+
13
+ ### Prerequisites
14
+
15
+ - Node.js (from `engines.node` field in `package.json`)
16
+ - Bun (from `packageManager` field in `package.json`)
@@ -0,0 +1,11 @@
1
+ //#region src/common.ts
2
+ const baseIgnorePatterns = [
3
+ ".history/",
4
+ "bun.lock",
5
+ "**/*.gen.ts"
6
+ ];
7
+ const arraify = (input) => input === void 0 ? [] : Array.isArray(input) ? input : [input];
8
+ const moduleName = "oxiul";
9
+ const packageName = "@oxiul/configs";
10
+ //#endregion
11
+ export { packageName as i, baseIgnorePatterns as n, moduleName as r, arraify as t };
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,37 @@
1
+ import { i as packageName } from "../common-B9SE4GL9.mjs";
2
+ import { exec } from "node:child_process";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { promisify } from "node:util";
6
+ //#region src/hook/install.ts
7
+ const promisifiedExec = promisify(exec);
8
+ const { stdout: rawGitRepositoryRoot } = await promisifiedExec("git rev-parse --show-toplevel");
9
+ const gitRepositoryRoot = rawGitRepositoryRoot.trim();
10
+ const { stdout: rawHooksDirectory } = await promisifiedExec("git rev-parse --path-format=absolute --git-path hooks");
11
+ const hooksDirectory = rawHooksDirectory.trim();
12
+ const packageJson = await fs.readFile(path.join(gitRepositoryRoot, "package.json"), "utf8");
13
+ const { name } = JSON.parse(packageJson);
14
+ const HOOK_CONTENT = ["#!/bin/bash", `node ${name === "@oxiul/configs" ? "." : `./node_modules/${packageName}`}/dist/hook/pre-commit.mjs`].join("\n");
15
+ await fs.mkdir(hooksDirectory, { recursive: true });
16
+ const preCommitPath = path.join(hooksDirectory, "pre-commit");
17
+ let oldHook = "";
18
+ if (await fs.stat(preCommitPath).then(() => true).catch(() => false)) {
19
+ if ((await fs.lstat(preCommitPath)).isSymbolicLink()) {
20
+ console.log("Detected an existing git pre-commit hook");
21
+ await fs.unlink(preCommitPath);
22
+ } else {
23
+ oldHook = await fs.readFile(preCommitPath, "utf8");
24
+ if (oldHook !== HOOK_CONTENT) {
25
+ console.log(`Found hooks path: "${hooksDirectory}"`);
26
+ console.log("Detected an existing git pre-commit hook");
27
+ await fs.writeFile(`${preCommitPath}.old`, oldHook);
28
+ }
29
+ }
30
+ }
31
+ if (oldHook !== HOOK_CONTENT) {
32
+ console.log(`Found hooks path: "${hooksDirectory}"`);
33
+ await fs.writeFile(preCommitPath, HOOK_CONTENT, { mode: 493 });
34
+ console.log("Hook saved");
35
+ }
36
+ //#endregion
37
+ export {};
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,57 @@
1
+ import { r as moduleName } from "../common-B9SE4GL9.mjs";
2
+ import { t as configSchema } from "../schema-DM8wm8HH.mjs";
3
+ import { z } from "zod";
4
+ import { spawn } from "node:child_process";
5
+ import { cosmiconfig } from "cosmiconfig";
6
+ import { Listr } from "listr2";
7
+ //#region src/hook/pre-commit.ts
8
+ const failedOutputs = /* @__PURE__ */ new Map();
9
+ const run = (command, task) => new Promise((resolve, reject) => {
10
+ const child = spawn(command, {
11
+ env: {
12
+ ...process.env,
13
+ FORCE_COLOR: "1"
14
+ },
15
+ shell: true,
16
+ stdio: [
17
+ "ignore",
18
+ "pipe",
19
+ "pipe"
20
+ ]
21
+ });
22
+ const window = task.stdout();
23
+ let output = "";
24
+ const onData = (chunk) => {
25
+ output += chunk;
26
+ window.write(chunk);
27
+ };
28
+ child.stdout.on("data", onData);
29
+ child.stderr.on("data", onData);
30
+ child.on("error", reject);
31
+ child.on("close", (code, signal) => {
32
+ if (code === 0) return resolve();
33
+ failedOutputs.set(command, output);
34
+ reject(/* @__PURE__ */ new Error(signal ? `killed by ${signal}` : `exited with code ${code}`));
35
+ });
36
+ });
37
+ const rawConfig = await cosmiconfig(moduleName).search();
38
+ const parsedConfig = z.safeParse(configSchema, rawConfig?.config);
39
+ if (!parsedConfig.success) throw new Error(`Validation error: ${parsedConfig.error.message}`);
40
+ try {
41
+ const config = parsedConfig.data;
42
+ if (config.preCommitCommands && config.preCommitCommands.length !== 0) await new Listr(config.preCommitCommands.map((command) => ({
43
+ rendererOptions: {
44
+ outputBar: 5,
45
+ persistentOutput: false
46
+ },
47
+ task: (_ctx, task) => run(command, task),
48
+ title: command
49
+ })), { concurrent: false }).run();
50
+ else console.log(`No commands in pre-commit list found`);
51
+ } catch {
52
+ for (const [command, output] of failedOutputs) console.error(`\n✖ Output of "${command}"\n${output}`);
53
+ console.error(`${failedOutputs.size} tasks failed: ${[...failedOutputs.keys()].join(", ")}`);
54
+ process.exit(1);
55
+ }
56
+ //#endregion
57
+ export {};
@@ -0,0 +1,152 @@
1
+ import { z } from "zod";
2
+ import { OxlintConfig } from "oxlint";
3
+ import { OxfmtConfig } from "oxfmt";
4
+ import { KnipConfig } from "knip";
5
+ //#region src/oxlint.d.ts
6
+ export declare const disabledRules: OxlintConfig["rules"];
7
+ export declare const overridenRules: OxlintConfig["rules"];
8
+ type MergableConfig$1 = Omit<OxlintConfig, "plugins"> & {
9
+ plugins?: Partial<Record<NonNullable<OxlintConfig["plugins"]>[number], boolean>>;
10
+ };
11
+ type TailwindConfig = {
12
+ entryPoint: string;
13
+ };
14
+ type VitestConfig = {
15
+ files?: string[];
16
+ };
17
+ type TanstackRouterConfig = {
18
+ routes: string[];
19
+ };
20
+ type PackageJsonConfig = boolean;
21
+ type PlaywrightConfig = {
22
+ files: string[];
23
+ assertFunctionNames?: string[];
24
+ };
25
+ type ServerSideConfig = {
26
+ files: string[];
27
+ allowConsole?: string[];
28
+ allowProcess?: string[];
29
+ };
30
+ type RestrictedSyntaxRule = {
31
+ selector: string;
32
+ message: string;
33
+ };
34
+ type RestrictedSyntaxOverride = {
35
+ files: string[];
36
+ rules: RestrictedSyntaxRule[];
37
+ };
38
+ type RestrictedSyntaxConfig = {
39
+ banBarrelImport?: boolean;
40
+ banReactMemo?: boolean;
41
+ overrides?: RestrictedSyntaxOverride[];
42
+ rulesOverrides?: RestrictedSyntaxRule[];
43
+ previousRules?: RestrictedSyntaxRule[];
44
+ previousOverrides?: RestrictedSyntaxOverride[];
45
+ };
46
+ type RestrictedConfig = {
47
+ globals?: {
48
+ name: string;
49
+ message: string;
50
+ }[];
51
+ properties?: {
52
+ object: string;
53
+ property: string;
54
+ message: string;
55
+ }[];
56
+ syntaxOverrides?: RestrictedSyntaxConfig["overrides"];
57
+ syntaxRulesOverrides?: RestrictedSyntaxConfig["rulesOverrides"];
58
+ };
59
+ type ReactConfig = {
60
+ allowedStrings?: string[];
61
+ additionalHooks?: string[];
62
+ banMemo?: boolean;
63
+ };
64
+ type I18nConfig = {
65
+ ignore?: string[] | true;
66
+ };
67
+ type ImportConfig = {
68
+ allowDefault?: string[];
69
+ banBarrel?: boolean;
70
+ };
71
+ type ConfigOptions$2 = {
72
+ jsPlugins?: Exclude<OxlintConfig["jsPlugins"], null>;
73
+ plugins?: MergableConfig$1["plugins"];
74
+ tailwind?: TailwindConfig;
75
+ vitest?: VitestConfig;
76
+ tanstackRouter?: TanstackRouterConfig;
77
+ packageJson?: PackageJsonConfig;
78
+ playwright?: PlaywrightConfig;
79
+ serverSide?: ServerSideConfig;
80
+ restricted?: RestrictedConfig;
81
+ react?: ReactConfig;
82
+ i18n?: I18nConfig;
83
+ import?: ImportConfig;
84
+ } & Pick<OxlintConfig, "env" | "rules" | "options" | "ignorePatterns" | "categories" | "settings" | "overrides" | "globals"> & ReactConfig;
85
+ declare const getConfig$2: ({ tailwind, tanstackRouter, vitest, packageJson, playwright, serverSide, restricted, react, i18n, import: importOptions, ...config }?: ConfigOptions$2) => Promise<OxlintConfig>;
86
+ //#endregion
87
+ //#region src/oxfmt.d.ts
88
+ type ConfigOptions$1 = OxfmtConfig & {
89
+ tailwind?: {
90
+ stylesheet: string;
91
+ functions?: string[];
92
+ };
93
+ imports?: Exclude<OxfmtConfig["sortImports"], boolean>;
94
+ };
95
+ declare const getConfig: ({ ignorePatterns, tailwind, imports, sortImports, ...rest }?: ConfigOptions$1) => Promise<{
96
+ readonly arrowParens?: import("oxfmt").ArrowParensConfig;
97
+ readonly associations?: import("oxfmt").OxfmtAssociationConfig[];
98
+ astro: import("oxfmt").AstroUserConfig;
99
+ readonly bracketSameLine?: boolean;
100
+ readonly bracketSpacing?: boolean;
101
+ readonly embeddedLanguageFormatting?: import("oxfmt").EmbeddedLanguageFormattingConfig;
102
+ readonly endOfLine?: import("oxfmt").EndOfLineConfig;
103
+ readonly experimentalOperatorPosition?: import("oxfmt").OperatorPositionConfig;
104
+ readonly htmlWhitespaceSensitivity?: import("oxfmt").HtmlWhitespaceSensitivityConfig;
105
+ readonly insertFinalNewline?: boolean;
106
+ readonly jsdoc?: import("oxfmt").JsdocUserConfig;
107
+ readonly jsxSingleQuote?: boolean;
108
+ readonly objectWrap?: import("oxfmt").ObjectWrapConfig;
109
+ readonly overrides?: import("oxfmt").OxfmtOverrideConfig[];
110
+ printWidth: number;
111
+ readonly proseWrap?: import("oxfmt").ProseWrapConfig;
112
+ readonly quoteProps?: import("oxfmt").QuotePropsConfig;
113
+ readonly semi?: boolean;
114
+ readonly singleAttributePerLine?: boolean;
115
+ readonly singleQuote?: boolean;
116
+ sortPackageJson: boolean | import("oxfmt").SortPackageJsonConfig | {
117
+ readonly sortScripts: true;
118
+ };
119
+ sortTailwindcss: import("oxfmt").SortTailwindcssUserConfig;
120
+ readonly svelte?: import("oxfmt").SvelteUserConfig;
121
+ readonly tabWidth?: number;
122
+ readonly trailingComma?: import("oxfmt").TrailingCommaConfig;
123
+ useTabs: boolean;
124
+ readonly vueIndentScriptAndStyle?: boolean;
125
+ readonly ignorePatterns: string[];
126
+ readonly sortImports: import("oxfmt").SortImportsUserConfig;
127
+ }>;
128
+ //#endregion
129
+ //#region src/knip.d.ts
130
+ type KnipConfig$1 = Exclude<KnipConfig, Function>;
131
+ type MergableConfig = Omit<KnipConfig$1, "include"> & {
132
+ include?: Partial<Record<NonNullable<KnipConfig$1["include"]>[number], boolean>>;
133
+ };
134
+ type BaseOptions = {
135
+ vitest?: boolean;
136
+ reactNative?: boolean;
137
+ opencode?: boolean;
138
+ eslint?: boolean;
139
+ workspaces?: boolean;
140
+ };
141
+ type ConfigOptions = {
142
+ options?: BaseOptions;
143
+ } & MergableConfig;
144
+ declare const getConfig$1: ({ options, ...overrides }?: ConfigOptions) => Promise<KnipConfig$1>;
145
+ //#endregion
146
+ //#region src/config/schema.d.ts
147
+ export declare const configSchema: z.ZodObject<{
148
+ preCommitCommands: z.ZodOptional<z.ZodArray<z.ZodString>>;
149
+ }, z.core.$strip>;
150
+ type Config = z.infer<typeof configSchema>;
151
+ //#endregion
152
+ export { type Config, getConfig as getFormatConfig, getConfig$1 as getKnipConfig, getConfig$2 as getLintConfig };
package/dist/index.mjs ADDED
@@ -0,0 +1,472 @@
1
+ import { n as baseIgnorePatterns, t as arraify } from "./common-B9SE4GL9.mjs";
2
+ import { t as configSchema } from "./schema-DM8wm8HH.mjs";
3
+ import { isNonNullish, mapValues, omitBy } from "remeda";
4
+ //#region src/oxlint.ts
5
+ const disabledRules = {
6
+ "eslint/capitalized-comments": "off",
7
+ "eslint/default-case": "off",
8
+ "eslint/id-length": "off",
9
+ "eslint/max-lines": "off",
10
+ "eslint/max-lines-per-function": "off",
11
+ "eslint/max-params": "off",
12
+ "eslint/max-statements": "off",
13
+ "eslint/no-await-in-loop": "off",
14
+ "eslint/no-continue": "off",
15
+ "eslint/no-duplicate-imports": "off",
16
+ "eslint/no-inline-comments": "off",
17
+ "eslint/no-nested-ternary": "off",
18
+ "eslint/no-ternary": "off",
19
+ "eslint/no-undefined": "off",
20
+ "eslint/no-warning-comments": "off",
21
+ "eslint/one-var": "off",
22
+ "eslint/require-await": "off",
23
+ "import/exports-last": "off",
24
+ "import/group-exports": "off",
25
+ "import/max-dependencies": "off",
26
+ "import/no-named-export": "off",
27
+ "import/no-namespace": "off",
28
+ "import/no-unassigned-import": "off",
29
+ "import/prefer-default-export": "off",
30
+ "import/unambiguous": "off",
31
+ "oxc/no-accumulating-spread": "off",
32
+ "oxc/no-async-await": "off",
33
+ "oxc/no-map-spread": "off",
34
+ "oxc/no-optional-chaining": "off",
35
+ "oxc/no-rest-spread-properties": "off",
36
+ "promise/avoid-new": "off",
37
+ "promise/prefer-await-to-callbacks": "off",
38
+ "promise/prefer-await-to-then": "off",
39
+ "react-perf/jsx-no-jsx-as-prop": "off",
40
+ "react-perf/jsx-no-new-array-as-prop": "off",
41
+ "react-perf/jsx-no-new-function-as-prop": "off",
42
+ "react-perf/jsx-no-new-object-as-prop": "off",
43
+ "react/display-name": "off",
44
+ "react/forbid-component-props": "off",
45
+ "react/jsx-filename-extension": "off",
46
+ "react/jsx-handler-names": "off",
47
+ "react/jsx-max-depth": "off",
48
+ "react/jsx-props-no-spreading": "off",
49
+ "react/no-multi-comp": "off",
50
+ "react/only-export-components": "off",
51
+ "react/react-in-jsx-scope": "off",
52
+ "react/rule-suppression": "off",
53
+ "typescript/consistent-return": "off",
54
+ "typescript/explicit-function-return-type": "off",
55
+ "typescript/explicit-module-boundary-types": "off",
56
+ "typescript/no-confusing-void-expression": "off",
57
+ "typescript/no-dynamic-delete": "off",
58
+ "typescript/no-invalid-void-type": "off",
59
+ "typescript/no-unnecessary-type-parameters": "off",
60
+ "typescript/no-unsafe-type-assertion": "off",
61
+ "typescript/prefer-nullish-coalescing": "off",
62
+ "typescript/prefer-readonly-parameter-types": "off",
63
+ "typescript/promise-function-async": "off",
64
+ "unicorn/no-array-callback-reference": "off",
65
+ "unicorn/no-array-reduce": "off",
66
+ "unicorn/no-nested-ternary": "off",
67
+ "unicorn/no-null": "off",
68
+ "unicorn/no-useless-undefined": "off",
69
+ "unicorn/number-literal-case": "off",
70
+ "unicorn/prefer-global-this": "off",
71
+ "unicorn/prefer-spread": "off",
72
+ "vitest/max-expects": "off",
73
+ "vitest/no-conditional-in-test": "off",
74
+ "vitest/no-hooks": "off",
75
+ "vitest/no-importing-vitest-globals": "off",
76
+ "vitest/no-standalone-expect": "off",
77
+ "vitest/prefer-called-once": "off",
78
+ "vitest/prefer-expect-assertions": "off",
79
+ "vitest/prefer-importing-vitest-globals": "off",
80
+ "vitest/prefer-lowercase-title": "off",
81
+ "vitest/prefer-to-be": "off",
82
+ "vitest/prefer-to-be-falsy": "off",
83
+ "vitest/prefer-to-be-truthy": "off",
84
+ "vitest/require-hook": "off",
85
+ "vitest/require-test-timeout": "off"
86
+ };
87
+ const disabledTailwindRules = {
88
+ "better-tailwindcss/enforce-consistent-class-order": "off",
89
+ "better-tailwindcss/enforce-consistent-line-wrapping": "off"
90
+ };
91
+ const overridenRules = {
92
+ "eslint/no-magic-numbers": ["error", { ignore: [
93
+ 0,
94
+ 1,
95
+ -1
96
+ ] }],
97
+ "eslint/no-void": ["error", { allowAsStatement: true }],
98
+ "eslint/sort-imports": ["error", { ignoreDeclarationSort: true }],
99
+ "react/function-component-definition": ["error", {
100
+ namedComponents: "arrow-function",
101
+ unnamedComponents: "arrow-function"
102
+ }],
103
+ "react/jsx-no-useless-fragment": ["error", { allowExpressions: true }],
104
+ "typescript/consistent-type-definitions": ["error", "type"],
105
+ "typescript/no-misused-promises": ["error", { checksVoidReturn: {
106
+ arguments: false,
107
+ attributes: false
108
+ } }],
109
+ "typescript/no-unnecessary-condition": ["error", { allowConstantLoopConditions: true }],
110
+ "typescript/restrict-template-expressions": ["error", { allowNumber: true }],
111
+ "typescript/strict-boolean-expressions": ["error", {
112
+ allowNullableBoolean: true,
113
+ allowNullableNumber: true,
114
+ allowNullableString: true,
115
+ allowNumber: true,
116
+ allowString: true
117
+ }],
118
+ "typescript/switch-exhaustiveness-check": ["error", { considerDefaultExhaustiveForUnions: true }],
119
+ "unicorn/explicit-length-check": ["error", { "non-zero": "not-equal" }],
120
+ "unicorn/max-nested-calls": ["error", { max: 5 }],
121
+ "unicorn/switch-case-braces": ["error", "avoid"],
122
+ "vitest/consistent-test-it": ["error", { fn: "test" }]
123
+ };
124
+ const mergeConfigs$1 = (...configs) => configs.reduce(({ categories, env, globals, ignorePatterns, jsPlugins, options, overrides, plugins, rules, settings, ...rest }, { categories: configCategories, env: configEnv, globals: configGlobals, ignorePatterns: configIgnorePatterns, jsPlugins: configJsPlugins, options: configOptions, overrides: configOverrides, plugins: configPlugins, rules: configRules, settings: configSettings, ...configRest }) => ({
125
+ categories: {
126
+ ...categories,
127
+ ...configCategories
128
+ },
129
+ env: {
130
+ ...env,
131
+ ...configEnv
132
+ },
133
+ globals: {
134
+ ...globals,
135
+ ...configGlobals
136
+ },
137
+ ignorePatterns: [...ignorePatterns ?? [], ...configIgnorePatterns ?? []],
138
+ jsPlugins: [...jsPlugins ?? [], ...configJsPlugins ?? []],
139
+ options: {
140
+ ...options,
141
+ ...configOptions
142
+ },
143
+ overrides: [...overrides ?? [], ...configOverrides ?? []],
144
+ plugins: {
145
+ ...plugins,
146
+ ...configPlugins
147
+ },
148
+ rules: {
149
+ ...rules,
150
+ ...configRules
151
+ },
152
+ settings: {
153
+ ...settings,
154
+ ...configSettings
155
+ },
156
+ ...rest,
157
+ ...configRest
158
+ }), {});
159
+ const getTailwindConfig = async (config) => {
160
+ if (!config) return {};
161
+ const tailwindPlugin = await import("eslint-plugin-better-tailwindcss");
162
+ return {
163
+ jsPlugins: [{
164
+ name: "better-tailwindcss",
165
+ specifier: "eslint-plugin-better-tailwindcss"
166
+ }],
167
+ rules: {
168
+ ...Object.fromEntries(Object.keys(tailwindPlugin.default.rules).map((key) => [`better-tailwindcss/${key}`, "error"])),
169
+ ...disabledTailwindRules
170
+ },
171
+ settings: { "better-tailwindcss": { entryPoint: config.entryPoint } }
172
+ };
173
+ };
174
+ const getVitestConfig = (config) => {
175
+ if (!config) return {};
176
+ return {
177
+ overrides: config.files ? [{
178
+ files: config.files,
179
+ rules: { "react/rules-of-hooks": "off" }
180
+ }] : [],
181
+ plugins: { vitest: true }
182
+ };
183
+ };
184
+ const getTanstackRouterConfig = async (config) => {
185
+ if (!config) return {};
186
+ const plugin = await import("@tanstack/eslint-plugin-router");
187
+ return {
188
+ jsPlugins: [{
189
+ name: "@tanstack/router",
190
+ specifier: "@tanstack/eslint-plugin-router"
191
+ }],
192
+ overrides: [{
193
+ files: config.routes,
194
+ rules: { "no-use-before-define": "off" }
195
+ }],
196
+ rules: plugin.default.configs["flat/recommended"][0].rules
197
+ };
198
+ };
199
+ const getPackageJsonConfig = async (config) => {
200
+ if (!config) return {};
201
+ return {
202
+ jsPlugins: [{
203
+ name: "package-json",
204
+ specifier: "eslint-plugin-package-json"
205
+ }],
206
+ rules: (await import("eslint-plugin-package-json")).configs.recommended.rules
207
+ };
208
+ };
209
+ const getPlaywrightConfig = async (config) => {
210
+ if (!config) return {};
211
+ const plugin = await import("eslint-plugin-playwright");
212
+ return {
213
+ jsPlugins: [{
214
+ name: "playwright",
215
+ specifier: "eslint-plugin-playwright"
216
+ }],
217
+ overrides: [{
218
+ files: config.files,
219
+ rules: {
220
+ ...plugin.default.configs["flat/recommended"].rules,
221
+ ...config.assertFunctionNames ? { "playwright/expect-expect": ["error", { assertFunctionNames: config.assertFunctionNames }] } : {},
222
+ "react/rules-of-hooks": "off",
223
+ "vitest/consistent-test-filename": "off",
224
+ "vitest/prefer-each": "off",
225
+ "vitest/valid-expect": "off"
226
+ }
227
+ }]
228
+ };
229
+ };
230
+ const getServerSideConfig = (config) => {
231
+ if (!config) return {};
232
+ return { overrides: [
233
+ {
234
+ files: config.files,
235
+ rules: { "import/no-nodejs-modules": "off" }
236
+ },
237
+ config.allowConsole ? {
238
+ files: config.allowConsole,
239
+ rules: { "no-console": "off" }
240
+ } : null,
241
+ config.allowProcess ? {
242
+ files: config.allowProcess,
243
+ rules: { "node/no-process-env": "off" }
244
+ } : null
245
+ ].filter(isNonNullish) };
246
+ };
247
+ const getRestrictedSyntaxConfig = (config) => {
248
+ if (!config) return {};
249
+ const overrides = config.overrides ?? [];
250
+ const rawRules = [config.banBarrelImport ? {
251
+ message: "Do not use barrel export, prefer named export",
252
+ selector: "ExportAllDeclaration"
253
+ } : null, config.banReactMemo ? {
254
+ message: "No need to use `React.memo`, we have a react compiler turned on",
255
+ selector: "MemberExpression[object.name='React'][property.name='memo']"
256
+ } : null].filter(isNonNullish);
257
+ return {
258
+ overrides: overrides.map(({ files, rules }) => ({
259
+ files,
260
+ rules: { "eslint-js/no-restricted-syntax": ["error", [
261
+ ...rawRules,
262
+ ...rules,
263
+ ...config.rulesOverrides ?? []
264
+ ]] }
265
+ })),
266
+ rules: { "eslint-js/no-restricted-syntax": [
267
+ "error",
268
+ ...rawRules,
269
+ ...config.rulesOverrides ?? []
270
+ ] }
271
+ };
272
+ };
273
+ const getRestrictedConfig = (config) => {
274
+ if (!config) return {};
275
+ return { rules: {
276
+ "eslint/no-restricted-globals": config.globals ? ["error", ...config.globals] : "error",
277
+ "eslint/no-restricted-properties": config.properties ? ["error", ...config.properties] : "error"
278
+ } };
279
+ };
280
+ const getReactConfig = (config) => {
281
+ if (!config) return {};
282
+ return { rules: {
283
+ "react/exhaustive-deps": config.additionalHooks ? ["error", { additionalHooks: `(${config.additionalHooks.join(",")})` }] : "error",
284
+ "react/jsx-no-literals": ["error", {
285
+ allowedStrings: config.allowedStrings,
286
+ ignoreProps: true,
287
+ noAttributeStrings: true,
288
+ noStrings: true
289
+ }]
290
+ } };
291
+ };
292
+ const getI18nConfig = (config) => {
293
+ if (!config) return {};
294
+ return {
295
+ overrides: Array.isArray(config.ignore) ? [{
296
+ files: config.ignore,
297
+ rules: { "react/jsx-no-literals": "off" }
298
+ }] : [],
299
+ rules: config.ignore === true ? { "react/jsx-no-literals": "off" } : {}
300
+ };
301
+ };
302
+ const getImportConfig = (config) => {
303
+ if (!config) return {};
304
+ return { overrides: [config.allowDefault ? {
305
+ files: config.allowDefault,
306
+ rules: { "import/no-default-export": "off" }
307
+ } : null].filter(isNonNullish) };
308
+ };
309
+ const getBaseConfig$1 = () => ({
310
+ categories: {
311
+ correctness: "error",
312
+ nursery: "error",
313
+ pedantic: "error",
314
+ perf: "error",
315
+ restriction: "error",
316
+ style: "error",
317
+ suspicious: "error"
318
+ },
319
+ env: {
320
+ browser: true,
321
+ es2026: true
322
+ },
323
+ ignorePatterns: baseIgnorePatterns,
324
+ jsPlugins: [{
325
+ name: "eslint-js",
326
+ specifier: "oxlint-plugin-eslint"
327
+ }],
328
+ options: {
329
+ denyWarnings: true,
330
+ reportUnusedDisableDirectives: "error",
331
+ typeAware: true
332
+ },
333
+ overrides: [{
334
+ files: ["**/*.config.{j,t}s"],
335
+ rules: {
336
+ "import/no-default-export": "off",
337
+ "import/no-nodejs-modules": "off",
338
+ "node/no-top-level-await": "off"
339
+ }
340
+ }, {
341
+ files: ["**/*.d.ts"],
342
+ rules: { "typescript/triple-slash-reference": "off" }
343
+ }],
344
+ plugins: {
345
+ eslint: true,
346
+ import: true,
347
+ "jsx-a11y": true,
348
+ node: true,
349
+ oxc: true,
350
+ promise: true,
351
+ react: true,
352
+ "react-perf": true,
353
+ typescript: true,
354
+ unicorn: true
355
+ },
356
+ rules: {
357
+ ...disabledRules,
358
+ ...overridenRules
359
+ }
360
+ });
361
+ const getConfig$2 = async ({ tailwind, tanstackRouter, vitest, packageJson = true, playwright, serverSide, restricted, react, i18n, import: importOptions, ...config } = {}) => {
362
+ const baseConfig = getBaseConfig$1();
363
+ const tailwindConfig = await getTailwindConfig(tailwind);
364
+ const tanstackRouterConfig = await getTanstackRouterConfig(tanstackRouter);
365
+ const vitestConfig = getVitestConfig(vitest);
366
+ const packageJsonConfig = await getPackageJsonConfig(packageJson);
367
+ const playwrightConfig = await getPlaywrightConfig(playwright);
368
+ const serverSideConfig = getServerSideConfig(serverSide);
369
+ const reactConfig = getReactConfig(react);
370
+ const i18nConfig = getI18nConfig(i18n);
371
+ const importConfig = getImportConfig(importOptions);
372
+ const restrictedConfig = getRestrictedConfig(restricted);
373
+ const rawMergedConfig = mergeConfigs$1(baseConfig, vitestConfig, tanstackRouterConfig, packageJsonConfig, playwrightConfig, tailwindConfig, serverSideConfig, restrictedConfig, reactConfig, i18nConfig, importConfig, config);
374
+ const restrictedSyntaxConfig = getRestrictedSyntaxConfig({
375
+ banBarrelImport: importOptions?.banBarrel,
376
+ banReactMemo: react?.banMemo,
377
+ overrides: restricted?.syntaxOverrides,
378
+ previousOverrides: rawMergedConfig.overrides?.map((override) => override.rules?.["eslint-js/no-restricted-syntax"] === void 0 ? void 0 : {
379
+ files: override.files,
380
+ rules: override.rules["eslint-js/no-restricted-syntax"].slice(1)
381
+ }).filter(isNonNullish),
382
+ previousRules: (rawMergedConfig.rules?.["eslint-js/no-restricted-syntax"])?.slice(1),
383
+ rulesOverrides: restricted?.syntaxRulesOverrides
384
+ });
385
+ const { plugins: pluginsRecord = {}, ...mergedConfig } = mergeConfigs$1(rawMergedConfig, restrictedSyntaxConfig);
386
+ return {
387
+ ...mergedConfig,
388
+ plugins: Object.entries(pluginsRecord).filter(([, value]) => value).map(([key]) => key)
389
+ };
390
+ };
391
+ //#endregion
392
+ //#region src/oxfmt.ts
393
+ const getConfig = async ({ ignorePatterns = [], tailwind, imports, sortImports, ...rest } = {}) => ({
394
+ astro: true,
395
+ ignorePatterns: [...baseIgnorePatterns, ...ignorePatterns],
396
+ printWidth: 80,
397
+ sortImports: sortImports ?? (imports ? {
398
+ newlinesBetween: false,
399
+ order: "asc",
400
+ ...imports
401
+ } : true),
402
+ sortPackageJson: { sortScripts: true },
403
+ sortTailwindcss: tailwind ? {
404
+ functions: tailwind.functions,
405
+ stylesheet: tailwind.stylesheet
406
+ } : void 0,
407
+ useTabs: true,
408
+ ...rest
409
+ });
410
+ //#endregion
411
+ //#region src/knip.ts
412
+ const mergeConfigs = (...configs) => configs.reduce(({ include, ignore, ignoreDependencies, entry, ignoreIssues, workspaces = {}, ...rest }, { include: configInclude, ignore: configIgnore, ignoreDependencies: configIgnoreDependencies, entry: configEntry, ignoreIssues: configIgnoreIssues, workspaces: configWorkspaces = {}, ...configRest }) => ({
413
+ entry: [...arraify(entry), ...arraify(configEntry)],
414
+ ignore: [...arraify(ignore), ...arraify(configIgnore)],
415
+ ignoreDependencies: [...arraify(ignoreDependencies), ...arraify(configIgnoreDependencies)],
416
+ ignoreIssues: {
417
+ ...ignoreIssues,
418
+ ...configIgnoreIssues
419
+ },
420
+ include: {
421
+ ...include,
422
+ ...configInclude
423
+ },
424
+ workspaces: {
425
+ ...mapValues(workspaces, (workspace, key) => ({
426
+ ...workspace,
427
+ ...configWorkspaces[key]
428
+ })),
429
+ ...omitBy(configWorkspaces, (_value, key) => Boolean(workspaces[key]))
430
+ },
431
+ ...rest,
432
+ ...configRest
433
+ }), {});
434
+ const getBaseConfig = ({ opencode = false, reactNative = false, eslint = true, vitest = true, workspaces = false } = {}) => ({
435
+ entry: workspaces ? [] : ["**/*.config.ts"],
436
+ ignore: [process.env.CI ? void 0 : ".history/**", opencode ? ".opencode/**" : void 0].filter(isNonNullish),
437
+ ignoreDependencies: [
438
+ reactNative ? "expo-doctor" : void 0,
439
+ vitest ? "@vitest/coverage-v8" : void 0,
440
+ eslint ? "oxlint-plugin-eslint" : void 0
441
+ ].filter(isNonNullish),
442
+ ignoreIssues: { "**/*.gen.ts": ["types"] },
443
+ include: {
444
+ catalog: true,
445
+ catalogReferences: true,
446
+ cycles: true,
447
+ dependencies: true,
448
+ duplicates: true,
449
+ enumMembers: true,
450
+ exports: true,
451
+ files: true,
452
+ namespaceMembers: true,
453
+ nsExports: true,
454
+ nsTypes: true,
455
+ types: true,
456
+ unlisted: true,
457
+ unresolved: true
458
+ },
459
+ treatConfigHintsAsErrors: true,
460
+ treatTagHintsAsErrors: true,
461
+ workspaces: workspaces ? { ".": { entry: ["**/*.config.ts"] } } : {}
462
+ });
463
+ const getConfig$1 = async ({ options, ...overrides } = {}) => {
464
+ const baseConfig = getBaseConfig(options);
465
+ const { include: includeRecord = {}, ...mergedConfig } = mergeConfigs(baseConfig, overrides);
466
+ return {
467
+ ...mergedConfig,
468
+ include: Object.entries(includeRecord).filter(([, value]) => value).map(([key]) => key)
469
+ };
470
+ };
471
+ //#endregion
472
+ export { configSchema, disabledRules, getConfig as getFormatConfig, getConfig$1 as getKnipConfig, getConfig$2 as getLintConfig, overridenRules };
@@ -0,0 +1,5 @@
1
+ import { z } from "zod";
2
+ //#region src/config/schema.ts
3
+ const configSchema = z.object({ preCommitCommands: z.string().array() }).partial();
4
+ //#endregion
5
+ export { configSchema as t };
package/package.json CHANGED
@@ -1,6 +1,67 @@
1
1
  {
2
- "name": "@oxiul/configs",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@oxiul/configs",
3
+ "version": "0.3.2",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/oxiul/configs.git"
8
+ },
9
+ "files": [
10
+ "dist/**",
11
+ "scripts/postinstall.mjs"
12
+ ],
13
+ "type": "module",
14
+ "sideEffects": false,
15
+ "main": "./dist/index.mjs",
16
+ "module": "./dist/index.mjs",
17
+ "exports": {
18
+ "types": "./dist/index.d.mts",
19
+ "default": "./dist/index.mjs"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "scripts": {
25
+ "build": "tsdown",
26
+ "changeset": "changeset add",
27
+ "deps": "knip",
28
+ "format": "oxfmt --write",
29
+ "format:verify": "oxfmt --check",
30
+ "lint": "oxlint",
31
+ "lint:fix": "oxlint --fix",
32
+ "postinstall": "node scripts/postinstall.mjs",
33
+ "typecheck": "bun check"
34
+ },
35
+ "dependencies": {
36
+ "cosmiconfig": "^10.0.1",
37
+ "listr2": "^11.4.0",
38
+ "oxlint-plugin-eslint": "^1.88.0",
39
+ "zod": "^4.6.7"
40
+ },
41
+ "devDependencies": {
42
+ "@arethetypeswrong/core": "^0.18.5",
43
+ "@changesets/changelog-github": "^1.0.1",
44
+ "@changesets/cli": "^3.0.3",
45
+ "@tanstack/eslint-plugin-router": "^1.162.0",
46
+ "@types/bun": "1.4.3",
47
+ "eslint-plugin-better-tailwindcss": "^4.9.1",
48
+ "eslint-plugin-package-json": "^1.10.1",
49
+ "eslint-plugin-playwright": "^2.12.1",
50
+ "knip": "^6.42.0",
51
+ "oxfmt": "^0.73.0",
52
+ "oxlint": "^1.88.0",
53
+ "oxlint-tsgolint": "^7.0.2004",
54
+ "publint": "^0.3.25",
55
+ "remeda": "^2.52.0",
56
+ "tsdown": "^0.23.1"
57
+ },
58
+ "peerDependencies": {
59
+ "knip": "^6.42.0",
60
+ "oxfmt": "^0.73.0",
61
+ "oxlint": "^1.88.0"
62
+ },
63
+ "engines": {
64
+ "node": "26"
65
+ },
66
+ "packageManager": "bun@1.4.3"
67
+ }
@@ -0,0 +1,39 @@
1
+ import { execFileSync, spawnSync } from "node:child_process";
2
+ import { readFileSync } from "node:fs";
3
+ import path from "node:path";
4
+
5
+ if (process.env.CI) {
6
+ process.exit(0);
7
+ }
8
+
9
+ const readName = (dir) => {
10
+ try {
11
+ // oxlint-disable-next-line typescript/no-unsafe-argument
12
+ const fileContent = readFileSync(path.join(dir, "package.json"), "utf8");
13
+ // oxlint-disable-next-line typescript/no-unsafe-return typescript/no-unsafe-member-access
14
+ return JSON.parse(fileContent).name;
15
+ } catch {
16
+ return undefined;
17
+ }
18
+ };
19
+
20
+ // oxlint-disable-next-line typescript/no-unsafe-assignment
21
+ const ownName = readName(process.cwd());
22
+
23
+ let gitRoot = "";
24
+ try {
25
+ gitRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
26
+ encoding: "utf8",
27
+ stdio: ["ignore", "pipe", "ignore"],
28
+ }).trim();
29
+ } catch {
30
+ process.exit(0);
31
+ }
32
+
33
+ const [cmd, ...args] =
34
+ readName(gitRoot) === ownName
35
+ ? ["bun", "src/hook/install.ts"]
36
+ : ["node", "dist/hook/install.mjs"];
37
+
38
+ const result = spawnSync(cmd, args, { stdio: "inherit" });
39
+ process.exit(result.status ?? 1);