@jterrazz/codestyle 1.0.0 → 1.1.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/README.md CHANGED
@@ -1,125 +1,75 @@
1
- _Hey there - I'm Jean-Baptiste, just another developer doing weird things with code. All my projects live on [jterrazz.com](https://jterrazz.com) - complete with backstories and lessons learned. Feel free to poke around - you might just find something useful!_
2
-
3
1
  # @jterrazz/codestyle
4
2
 
5
- Unified lint/format configs to standardize TypeScript codebases with Oxlint + Oxfmt.
6
-
7
- ## Installation
3
+ Fast, opinionated linting and formatting for TypeScript. Powered by Oxlint (2-3x faster than ESLint) and Oxfmt.
8
4
 
9
- Install the package using npm:
5
+ ## Quick Start
10
6
 
11
7
  ```bash
12
8
  npm install @jterrazz/codestyle --save-dev
13
9
  ```
14
10
 
15
- ## Usage
16
-
17
- ### Oxlint Configuration
18
-
19
- Choose the configuration that matches your environment:
20
-
21
- **For Node.js projects:**
11
+ Create `.oxlintrc.json`:
22
12
 
23
13
  ```json
24
- // oxlint.json
25
14
  {
26
15
  "extends": ["@jterrazz/codestyle/oxlint/node"]
27
16
  }
28
17
  ```
29
18
 
30
- **For Expo/React Native projects:**
31
-
32
- ```json
33
- // oxlint.json
34
- {
35
- "extends": ["@jterrazz/codestyle/oxlint/expo"]
36
- }
37
- ```
38
-
39
- **For Next.js projects:**
40
-
41
- ```json
42
- // oxlint.json
43
- {
44
- "extends": ["@jterrazz/codestyle/oxlint/nextjs"]
45
- }
46
- ```
47
-
48
- ### Oxfmt Configuration
19
+ Run:
49
20
 
50
- ```json
51
- // oxfmt.json
52
- {
53
- "extends": ["@jterrazz/codestyle/oxfmt"]
54
- }
21
+ ```bash
22
+ npx codestyle # Check everything
23
+ npx codestyle --fix # Fix everything
55
24
  ```
56
25
 
57
- ## Features
58
-
59
- ### Environment-Specific Configurations
60
-
61
- **Node.js Configuration:**
26
+ ## Configurations
62
27
 
63
- - Requires explicit file extensions (`.js`) for imports
64
- - Optimized for Node.js patterns
28
+ | Config | Use Case |
29
+ | ----------------------------------- | --------------------------------- |
30
+ | `@jterrazz/codestyle/oxlint/node` | Node.js (requires .js extensions) |
31
+ | `@jterrazz/codestyle/oxlint/expo` | Expo / React Native |
32
+ | `@jterrazz/codestyle/oxlint/nextjs` | Next.js |
65
33
 
66
- **Expo/React Native Configuration:**
34
+ ## What's Included
67
35
 
68
- - No file extensions in imports (auto-fixed)
69
- - React plugin enabled
36
+ - TypeScript strict mode with `type` imports
37
+ - Import sorting and grouping (perfectionist)
38
+ - Named exports enforcement (no default exports)
39
+ - Performance warnings (spread in loops, etc.)
40
+ - Style consistency (curly braces, comments, naming)
70
41
 
71
- **Next.js Configuration:**
72
-
73
- - No file extensions in imports (Turbopack compatibility, auto-fixed)
74
- - React and Next.js plugins enabled
75
-
76
- ### Shared Features
77
-
78
- - **TypeScript**: Strict type checking with consistent type imports
79
- - **Import Sorting**: Automated import organization with architectural grouping (via perfectionist)
80
- - **Code Quality**: Perfectionist plugin for consistent code style
81
- - **Performance**: Oxlint is 50-100x faster than ESLint, Oxfmt is 10x faster than Prettier, tsgo is 10x faster than tsc
82
-
83
- ## CLI Tools
84
-
85
- This package includes a CLI tool for running quality checks:
42
+ ## CLI
86
43
 
87
44
  ```bash
88
- # Run all quality checks (tsgo, Oxlint, Oxfmt) in parallel
89
- npx codestyle
90
-
91
- # Automatically fix all fixable issues (Oxlint --fix, Oxfmt format)
92
- npx codestyle --fix
93
-
94
- # Run individual checks
95
- npx codestyle --type # TypeScript type checking only
96
- npx codestyle --lint # Linting only
97
- npx codestyle --format # Format checking only
45
+ npx codestyle # Run all checks (type + lint + format)
46
+ npx codestyle --fix # Auto-fix all issues
98
47
 
99
- # Combine flags
100
- npx codestyle --lint --fix # Fix lint issues only
101
- npx codestyle --format --fix # Format files only
48
+ npx codestyle --type # TypeScript only
49
+ npx codestyle --lint # Lint only
50
+ npx codestyle --format # Format only
102
51
  ```
103
52
 
104
- ## Scripts
53
+ ## Architecture Enforcement (Optional)
105
54
 
106
- Add these scripts to your `package.json` for common development tasks:
55
+ Enforce hexagonal architecture boundaries:
107
56
 
108
57
  ```json
109
58
  {
110
- "scripts": {
111
- "codestyle": "codestyle",
112
- "codestyle:fix": "codestyle --fix"
113
- }
59
+ "extends": [
60
+ "@jterrazz/codestyle/oxlint/node",
61
+ "@jterrazz/codestyle/oxlint/architectures/hexagonal"
62
+ ]
114
63
  }
115
64
  ```
116
65
 
117
- ## Configuration
66
+ Rules enforced:
118
67
 
119
- The configurations are fully modular and include:
68
+ - `domain/` cannot import from other layers
69
+ - `application/` cannot import infrastructure
70
+ - `presentation/ui/` cannot import navigation
71
+ - `features/` cannot import other features
120
72
 
121
- - **Base configuration**: Common rules for TypeScript, import sorting, code quality
122
- - **Environment-specific**: Tailored rules for Node.js, Expo/React Native, and Next.js
123
- - **Custom plugins**: JS plugins for import extension enforcement
73
+ ---
124
74
 
125
- Happy coding!
75
+ By [Jean-Baptiste Terrazzoni](https://jterrazz.com)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jterrazz/codestyle",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "author": "Jean-Baptiste Terrazzoni <contact@jterrazz.com>",
5
5
  "bin": {
6
6
  "codestyle": "./src/codestyle.sh"
@@ -15,16 +15,15 @@
15
15
  "./oxlint/node": "./src/oxlint/node.json",
16
16
  "./oxlint/expo": "./src/oxlint/expo.json",
17
17
  "./oxlint/nextjs": "./src/oxlint/nextjs.json",
18
+ "./oxlint/architectures/hexagonal": "./src/oxlint/architectures/hexagonal.json",
18
19
  "./oxfmt": "./src/oxfmt/index.json"
19
20
  },
20
21
  "publishConfig": {
21
22
  "registry": "https://registry.npmjs.org/"
22
23
  },
23
24
  "scripts": {
24
- "lint": "oxlint --ignore-pattern '**/fixtures/**'",
25
- "lint:fix": "oxlint --fix --ignore-pattern '**/fixtures/**'",
26
- "format": "oxfmt",
27
- "format:check": "oxfmt --check",
25
+ "lint": "oxlint --ignore-pattern '**/fixtures/**' && oxfmt --check",
26
+ "lint:fix": "oxlint --fix --ignore-pattern '**/fixtures/**' && oxfmt",
28
27
  "test": "vitest --run",
29
28
  "test:watch": "vitest",
30
29
  "build": "# no build script"
package/src/codestyle.sh CHANGED
@@ -63,25 +63,29 @@ type_pid=""
63
63
  code_pid=""
64
64
  style_pid=""
65
65
 
66
+ # Find the node_modules/.bin directory relative to this script
67
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
68
+ BIN_DIR="$SCRIPT_DIR/../node_modules/.bin"
69
+
66
70
  if [ "$RUN_TYPE" = true ]; then
67
- tsgo --noEmit "${EXTRA_ARGS[@]}" > "$tmp_dir/type.log" 2>&1 &
71
+ "$BIN_DIR/tsgo" --noEmit "${EXTRA_ARGS[@]}" > "$tmp_dir/type.log" 2>&1 &
68
72
  type_pid=$!
69
73
  fi
70
74
 
71
75
  if [ "$RUN_LINT" = true ]; then
72
76
  if [ "$FIX_MODE" = true ]; then
73
- oxlint --fix "${EXTRA_ARGS[@]:-.}" > "$tmp_dir/code.log" 2>&1 &
77
+ "$BIN_DIR/oxlint" --fix "${EXTRA_ARGS[@]:-.}" > "$tmp_dir/code.log" 2>&1 &
74
78
  else
75
- oxlint "${EXTRA_ARGS[@]:-.}" > "$tmp_dir/code.log" 2>&1 &
79
+ "$BIN_DIR/oxlint" "${EXTRA_ARGS[@]:-.}" > "$tmp_dir/code.log" 2>&1 &
76
80
  fi
77
81
  code_pid=$!
78
82
  fi
79
83
 
80
84
  if [ "$RUN_FORMAT" = true ]; then
81
85
  if [ "$FIX_MODE" = true ]; then
82
- oxfmt "${EXTRA_ARGS[@]:-.}" > "$tmp_dir/style.log" 2>&1 &
86
+ "$BIN_DIR/oxfmt" "${EXTRA_ARGS[@]:-.}" > "$tmp_dir/style.log" 2>&1 &
83
87
  else
84
- oxfmt --check "${EXTRA_ARGS[@]:-.}" > "$tmp_dir/style.log" 2>&1 &
88
+ "$BIN_DIR/oxfmt" --check "${EXTRA_ARGS[@]:-.}" > "$tmp_dir/style.log" 2>&1 &
85
89
  fi
86
90
  style_pid=$!
87
91
  fi
@@ -0,0 +1,65 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
3
+ "jsPlugins": ["../plugins/architecture-boundaries.js"],
4
+ "rules": {
5
+ // Hexagonal Architecture (Ports & Adapters)
6
+ //
7
+ // Dependency rule: outer layers depend on inner layers, never the reverse
8
+ // All layers CAN import domain (it's the core)
9
+ //
10
+ // ┌─────────────────────────────────────────┐
11
+ // │ infrastructure / presentation (outer) │
12
+ // │ ┌─────────────────────────────────┐ │
13
+ // │ │ application (use-cases, ports) │ │
14
+ // │ │ ┌─────────────────────────┐ │ │
15
+ // │ │ │ domain (core business) │ │ │
16
+ // │ │ └─────────────────────────┘ │ │
17
+ // │ └─────────────────────────────────┘ │
18
+ // └─────────────────────────────────────────┘
19
+
20
+ "architecture-boundaries/architecture-boundaries": [
21
+ "error",
22
+ {
23
+ "rules": [
24
+ {
25
+ "from": "/domain/",
26
+ "disallow": [
27
+ "/application/",
28
+ "/infrastructure/",
29
+ "/presentation/",
30
+ "/di/",
31
+ "/config/",
32
+ "/generated/"
33
+ ],
34
+ "message": "Domain layer must be pure - cannot import from other layers"
35
+ },
36
+ {
37
+ "from": "/application/use-cases/",
38
+ "disallow": ["/infrastructure/", "/presentation/", "/di/"],
39
+ "message": "Use cases can only depend on domain and ports"
40
+ },
41
+ {
42
+ "from": "/application/ports/",
43
+ "disallow": ["/infrastructure/", "/presentation/", "/di/"],
44
+ "message": "Ports are interfaces - cannot depend on implementations"
45
+ },
46
+ {
47
+ "from": "/infrastructure/inbound/",
48
+ "disallow": ["/infrastructure/outbound/"],
49
+ "message": "Inbound adapters should not import outbound adapters - use DI"
50
+ },
51
+ {
52
+ "from": "/presentation/ui/(atoms|molecules)/",
53
+ "disallow": ["/navigation/"],
54
+ "message": "Atoms and molecules must be pure - no navigation imports"
55
+ },
56
+ {
57
+ "from": "/presentation/features/",
58
+ "disallow": ["/presentation/features/(?!common)"],
59
+ "message": "Features should be independent - use shared code in features/common"
60
+ }
61
+ ]
62
+ }
63
+ ]
64
+ }
65
+ }
@@ -1,29 +1,23 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
3
- "plugins": ["typescript", "import", "oxc"],
3
+ "plugins": ["typescript", "import", "oxc", "unicorn"],
4
4
  "jsPlugins": ["eslint-plugin-perfectionist"],
5
5
  "categories": {
6
6
  "correctness": "error",
7
- "suspicious": "warn",
7
+ "suspicious": "error",
8
8
  "perf": "warn",
9
9
  "pedantic": "off",
10
10
  "style": "warn",
11
11
  "nursery": "off"
12
12
  },
13
13
  "rules": {
14
+ // ============================================
15
+ // ENABLED RULES
16
+ // ============================================
17
+
14
18
  "no-unused-vars": "error",
15
- "sort-keys": "off",
16
- "sort-imports": "off",
17
- "func-style": "off",
18
- "no-magic-numbers": "off",
19
- "import/no-named-export": "off",
20
- "import/no-anonymous-default-export": "off",
21
- "import/group-exports": "off",
22
- "no-ternary": "off",
23
- "init-declarations": "off",
24
- "capitalized-comments": "off",
25
- "id-length": "off",
26
- "typescript/consistent-type-definitions": "off",
19
+
20
+ // -- TypeScript --
27
21
  "typescript/consistent-type-imports": [
28
22
  "error",
29
23
  {
@@ -40,19 +34,14 @@
40
34
  "allowTaggedTemplates": true
41
35
  }
42
36
  ],
43
- "perfectionist/sort-array-includes": ["error", { "type": "natural" }],
44
- "perfectionist/sort-classes": ["error", { "type": "natural" }],
37
+
38
+ // -- Perfectionist (sorting/ordering) --
45
39
  "perfectionist/sort-heritage-clauses": ["error", { "type": "natural" }],
46
40
  "perfectionist/sort-intersection-types": ["error", { "type": "natural" }],
47
41
  "perfectionist/sort-jsx-props": ["error", { "type": "natural" }],
48
42
  "perfectionist/sort-named-exports": ["error", { "type": "natural" }],
49
43
  "perfectionist/sort-named-imports": ["error", { "type": "natural" }],
50
- "perfectionist/sort-switch-case": ["error", { "type": "natural" }],
51
44
  "perfectionist/sort-union-types": ["error", { "type": "natural" }],
52
- "perfectionist/sort-variable-declarations": [
53
- "error",
54
- { "type": "natural" }
55
- ],
56
45
  "perfectionist/sort-imports": [
57
46
  "error",
58
47
  {
@@ -68,7 +57,62 @@
68
57
  "unknown"
69
58
  ]
70
59
  }
71
- ]
60
+ ],
61
+
62
+ // ============================================
63
+ // DISABLED - Conflicts with perfectionist
64
+ // ============================================
65
+
66
+ "sort-keys": "off",
67
+ "sort-imports": "off",
68
+
69
+ // ============================================
70
+ // DISABLED - Too strict / opinionated
71
+ // ============================================
72
+
73
+ "func-style": "off",
74
+ "no-magic-numbers": "off",
75
+ "no-ternary": "off",
76
+ "init-declarations": "off",
77
+ "id-length": "off",
78
+ "max-statements": "off",
79
+ "max-params": "off",
80
+ "no-continue": "off",
81
+ "arrow-body-style": "off",
82
+ "prefer-destructuring": "off",
83
+ "typescript/consistent-type-definitions": "off",
84
+ "typescript/array-type": "off",
85
+ "unicorn/no-null": "off",
86
+ "unicorn/no-array-sort": "off",
87
+
88
+ // ============================================
89
+ // ENABLED - Code style enforcement
90
+ // ============================================
91
+
92
+ "capitalized-comments": "warn",
93
+ "new-cap": "error",
94
+ "curly": "error",
95
+ "no-nested-ternary": "error",
96
+ "unicorn/catch-error-name": "error",
97
+ "unicorn/numeric-separators-style": "warn",
98
+
99
+ // ============================================
100
+ // DISABLED - Conflicts with other rules
101
+ // ============================================
102
+
103
+ "import/no-named-export": "off",
104
+ "import/consistent-type-specifier-style": "off",
105
+ "import/prefer-default-export": "off",
106
+ "import/no-default-export": "off",
107
+ "import/group-exports": "off",
108
+ "import/no-anonymous-default-export": "off",
109
+
110
+ // ============================================
111
+ // ENABLED - Clean imports
112
+ // ============================================
113
+
114
+ "import/first": "error",
115
+ "import/no-namespace": "error"
72
116
  },
73
117
  "ignorePatterns": ["dist/**", "node_modules/**"]
74
118
  }
@@ -10,6 +10,16 @@
10
10
  "allow": ["\\.png$", "\\.jpg$", "\\.jpeg$", "\\.gif$", "\\.webp$"]
11
11
  }
12
12
  ],
13
- "remove-ts-extensions/remove-ts-extensions": "error"
13
+ "remove-ts-extensions/remove-ts-extensions": "error",
14
+
15
+ // React 17+ doesn't require importing React
16
+ "react/react-in-jsx-scope": "off",
17
+ // Prop spreading is common in React Native
18
+ "react/jsx-props-no-spreading": "off",
19
+ // Style preferences - not worth enforcing
20
+ "react/jsx-boolean-value": "off",
21
+ "react/jsx-handler-names": "off",
22
+ "react/jsx-curly-brace-presence": "off",
23
+ "unicorn/no-nested-ternary": "off"
14
24
  }
15
25
  }
@@ -0,0 +1,86 @@
1
+ // Custom rule to enforce architecture boundaries
2
+ // Prevents imports between layers (e.g., domain cannot import infra)
3
+
4
+ const architectureBoundariesRule = {
5
+ meta: {
6
+ type: "problem",
7
+ docs: {
8
+ description: "Enforce architecture layer boundaries",
9
+ category: "Best Practices",
10
+ },
11
+ schema: [
12
+ {
13
+ type: "object",
14
+ properties: {
15
+ rules: {
16
+ type: "array",
17
+ items: {
18
+ type: "object",
19
+ properties: {
20
+ from: { type: "string" }, // Regex pattern for source file path
21
+ disallow: {
22
+ type: "array",
23
+ items: { type: "string" }, // Regex patterns for disallowed imports
24
+ },
25
+ message: { type: "string" },
26
+ },
27
+ required: ["from", "disallow"],
28
+ },
29
+ },
30
+ },
31
+ },
32
+ ],
33
+ },
34
+ create(context) {
35
+ const options = context.options[0] || {};
36
+ const rules = options.rules || [];
37
+ const filename = context.getFilename();
38
+
39
+ function checkNode(node) {
40
+ if (!node.source || !node.source.value) {
41
+ return;
42
+ }
43
+
44
+ const importPath = node.source.value;
45
+
46
+ for (const rule of rules) {
47
+ const fromPattern = new RegExp(rule.from);
48
+
49
+ // Check if this file matches the "from" pattern
50
+ if (!fromPattern.test(filename)) {
51
+ continue;
52
+ }
53
+
54
+ // Check if the import matches any disallowed pattern
55
+ for (const disallowPattern of rule.disallow) {
56
+ const disallowRegex = new RegExp(disallowPattern);
57
+
58
+ if (disallowRegex.test(importPath)) {
59
+ context.report({
60
+ node: node.source,
61
+ message:
62
+ rule.message || `Import from "${importPath}" violates architecture boundaries`,
63
+ });
64
+ break;
65
+ }
66
+ }
67
+ }
68
+ }
69
+
70
+ return {
71
+ ImportDeclaration: checkNode,
72
+ ExportNamedDeclaration: checkNode,
73
+ ExportAllDeclaration: checkNode,
74
+ };
75
+ },
76
+ };
77
+
78
+ export default {
79
+ meta: {
80
+ name: "architecture-boundaries",
81
+ version: "1.0.0",
82
+ },
83
+ rules: {
84
+ "architecture-boundaries": architectureBoundariesRule,
85
+ },
86
+ };