@jterrazz/typescript 5.2.0 → 6.0.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/bin/typescript.sh CHANGED
@@ -87,19 +87,37 @@ case "$COMMAND" in
87
87
  --on-success "node --enable-source-maps dist/index.js"
88
88
  ;;
89
89
 
90
+ docs)
91
+ printf "${CYAN_BG}${BRIGHT_WHITE} TYPESCRIPT ${NC} Generating API docs...\n\n"
92
+
93
+ bash "$SCRIPT_DIR/commands/docs.sh" "$PROJECT_ROOT" "$PACKAGE_ROOT"
94
+
95
+ printf "\n${GREEN}Docs generated at .docs/${NC}\n"
96
+ ;;
97
+
98
+ check|fix)
99
+ exec bash "$SCRIPT_DIR/commands/check.sh" "$COMMAND" "$@"
100
+ ;;
101
+
90
102
  *)
91
- printf "${CYAN_BG}${BRIGHT_WHITE} TYPESCRIPT ${NC} TypeScript/Node project toolkit\n\n"
103
+ printf "${CYAN_BG}${BRIGHT_WHITE} TYPESCRIPT ${NC} TypeScript toolchain\n\n"
92
104
  printf "Usage: typescript <command>\n\n"
93
105
  printf "Commands:\n"
94
106
  printf " build Build application (ESM + types)\n"
95
107
  printf " bundle Bundle library (ESM + CJS + types)\n"
96
108
  printf " start Run the built application\n"
97
- printf " dev Build, run, and rebuild on changes\n\n"
109
+ printf " dev Build, run, and rebuild on changes\n"
110
+ printf " docs Generate API reference + llms.txt from TSDoc\n"
111
+ printf " check Check types, lint, formatting, and unused code\n"
112
+ printf " fix Auto-fix lint and formatting issues\n\n"
98
113
  printf "Examples:\n"
99
114
  printf " typescript build\n"
100
115
  printf " typescript bundle\n"
101
116
  printf " typescript start\n"
102
117
  printf " typescript dev\n"
118
+ printf " typescript docs\n"
119
+ printf " typescript check\n"
120
+ printf " typescript fix\n"
103
121
  exit 1
104
122
  ;;
105
123
  esac
package/package.json CHANGED
@@ -1,35 +1,50 @@
1
1
  {
2
- "name": "@jterrazz/typescript",
3
- "version": "5.2.0",
4
- "author": "Jean-Baptiste Terrazzoni <contact@jterrazz.com>",
5
- "repository": {
6
- "type": "git",
7
- "url": "https://github.com/jterrazz/package-typescript"
8
- },
9
- "bin": {
10
- "typescript": "bin/typescript.sh"
11
- },
12
- "files": [
13
- "bin",
14
- "presets"
15
- ],
16
- "type": "module",
17
- "publishConfig": {
18
- "registry": "https://registry.npmjs.org/"
19
- },
20
- "scripts": {
21
- "lint": "codestyle check",
22
- "lint:fix": "codestyle fix",
23
- "test": "vitest --run",
24
- "build": "# no build script"
25
- },
26
- "dependencies": {
27
- "tsdown": "^0.21.5"
28
- },
29
- "devDependencies": {
30
- "@jterrazz/codestyle": "^2.1.0",
31
- "@jterrazz/test": "^3.4.0",
32
- "@types/node": "^25.5.0",
33
- "vitest": "^4.1.2"
34
- }
2
+ "name": "@jterrazz/typescript",
3
+ "version": "6.0.0",
4
+ "author": "Jean-Baptiste Terrazzoni <contact@jterrazz.com>",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/jterrazz/package-typescript"
8
+ },
9
+ "bin": {
10
+ "typescript": "bin/typescript.sh"
11
+ },
12
+ "files": [
13
+ "bin",
14
+ "presets",
15
+ "src"
16
+ ],
17
+ "type": "module",
18
+ "main": "src/index.js",
19
+ "exports": {
20
+ ".": "./src/index.js",
21
+ "./tsconfig/*": "./presets/tsconfig/*.json",
22
+ "./tsdown/*": "./presets/tsdown/*.js",
23
+ "./presets/tsconfig/*": "./presets/tsconfig/*.json",
24
+ "./presets/tsdown/*": "./presets/tsdown/*.js"
25
+ },
26
+ "publishConfig": {
27
+ "registry": "https://registry.npmjs.org/"
28
+ },
29
+ "scripts": {
30
+ "build": "# no build script",
31
+ "lint": "./bin/typescript.sh check --ignore-pattern '**/fixtures/**'",
32
+ "lint:fix": "./bin/typescript.sh fix --ignore-pattern '**/fixtures/**'",
33
+ "test": "vitest --run"
34
+ },
35
+ "dependencies": {
36
+ "@typescript/native-preview": "^7.0.0-dev.20260327.2",
37
+ "eslint-plugin-perfectionist": "^5.7.0",
38
+ "knip": "^6.2.0",
39
+ "oxfmt": "^0.42.0",
40
+ "oxlint": "^1.57.0",
41
+ "tsdown": "^0.21.5",
42
+ "typedoc": "^0.28.19",
43
+ "typedoc-plugin-markdown": "^4.11.0"
44
+ },
45
+ "devDependencies": {
46
+ "@jterrazz/test": "^5.3.2",
47
+ "@types/node": "^25.5.0",
48
+ "vitest": "^4.1.2"
49
+ }
35
50
  }
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://unpkg.com/knip@6/schema.json",
3
+ "ignoreDependencies": ["@jterrazz/codestyle", "@jterrazz/test", "@jterrazz/typescript"],
4
+ "ignoreBinaries": ["oxlint", "oxfmt"]
5
+ }
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'oxfmt';
2
+
3
+ export default defineConfig({
4
+ printWidth: 100,
5
+ tabWidth: 4,
6
+ useTabs: false,
7
+ semi: true,
8
+ singleQuote: true,
9
+ trailingComma: 'all',
10
+ bracketSpacing: true,
11
+ endOfLine: 'lf',
12
+ });
@@ -0,0 +1,39 @@
1
+ export default [
2
+ {
3
+ from: '/domain/',
4
+ disallow: [
5
+ '/application/',
6
+ '/infrastructure/',
7
+ '/presentation/',
8
+ '/di/',
9
+ '/config/',
10
+ '/generated/',
11
+ ],
12
+ message: 'Domain layer must be pure - cannot import from other layers',
13
+ },
14
+ {
15
+ from: '/application/use-cases/',
16
+ disallow: ['/infrastructure/', '/presentation/', '/di/'],
17
+ message: 'Use cases can only depend on domain and ports',
18
+ },
19
+ {
20
+ from: '/application/ports/',
21
+ disallow: ['/infrastructure/', '/presentation/', '/di/'],
22
+ message: 'Ports are interfaces - cannot depend on implementations',
23
+ },
24
+ {
25
+ from: '/infrastructure/inbound/',
26
+ disallow: ['/infrastructure/outbound/'],
27
+ message: 'Inbound adapters should not import outbound adapters - use DI',
28
+ },
29
+ {
30
+ from: '/presentation/ui/(atoms|molecules)/',
31
+ disallow: ['/navigation/'],
32
+ message: 'Atoms and molecules must be pure - no navigation imports',
33
+ },
34
+ {
35
+ from: '/presentation/features/',
36
+ disallow: ['/presentation/features/(?!common)'],
37
+ message: 'Features should be independent - use shared code in features/common',
38
+ },
39
+ ];
@@ -0,0 +1,13 @@
1
+ import { resolve } from 'node:path';
2
+ import { defineConfig } from 'oxlint';
3
+
4
+ import defaultRules from './hexagonal-rules.js';
5
+
6
+ const pluginPath = resolve(import.meta.dirname, '../plugins/codestyle.js');
7
+
8
+ export default defineConfig({
9
+ jsPlugins: [pluginPath],
10
+ rules: {
11
+ 'codestyle/arch-hexagonal': ['error', { rules: defaultRules }],
12
+ },
13
+ });
@@ -0,0 +1,128 @@
1
+ import { createRequire } from 'node:module';
2
+ import { defineConfig } from 'oxlint';
3
+
4
+ const require = createRequire(import.meta.url);
5
+ const perfectionistPath = require.resolve('eslint-plugin-perfectionist');
6
+
7
+ export default defineConfig({
8
+ plugins: ['typescript', 'import', 'oxc', 'unicorn'],
9
+ jsPlugins: [perfectionistPath],
10
+ categories: {
11
+ correctness: 'error',
12
+ suspicious: 'error',
13
+ perf: 'error',
14
+ pedantic: 'off',
15
+ style: 'error',
16
+ nursery: 'off',
17
+ },
18
+ rules: {
19
+ // ============================================
20
+ // ENABLED RULES
21
+ // ============================================
22
+
23
+ 'no-unused-vars': 'error',
24
+
25
+ // -- TypeScript --
26
+ 'typescript/consistent-type-imports': [
27
+ 'error',
28
+ {
29
+ disallowTypeAnnotations: true,
30
+ fixStyle: 'inline-type-imports',
31
+ prefer: 'type-imports',
32
+ },
33
+ ],
34
+ 'typescript/no-unused-expressions': [
35
+ 'error',
36
+ {
37
+ allowShortCircuit: true,
38
+ allowTernary: true,
39
+ allowTaggedTemplates: true,
40
+ },
41
+ ],
42
+
43
+ // -- Perfectionist (sorting/ordering) --
44
+ 'perfectionist/sort-heritage-clauses': ['error', { type: 'natural' }],
45
+ 'perfectionist/sort-intersection-types': ['error', { type: 'natural' }],
46
+ 'perfectionist/sort-jsx-props': ['error', { type: 'natural' }],
47
+ 'perfectionist/sort-named-exports': ['error', { type: 'natural' }],
48
+ 'perfectionist/sort-named-imports': ['error', { type: 'natural' }],
49
+ 'perfectionist/sort-union-types': ['error', { type: 'natural' }],
50
+ 'perfectionist/sort-imports': [
51
+ 'error',
52
+ {
53
+ type: 'alphabetical',
54
+ order: 'asc',
55
+ ignoreCase: true,
56
+ newlinesBetween: 1,
57
+ groups: [
58
+ ['builtin', 'external'],
59
+ 'internal',
60
+ ['parent', 'sibling', 'index'],
61
+ 'style',
62
+ 'unknown',
63
+ ],
64
+ },
65
+ ],
66
+
67
+ // ============================================
68
+ // DISABLED - Conflicts with perfectionist
69
+ // ============================================
70
+
71
+ 'sort-keys': 'off',
72
+ 'sort-imports': 'off',
73
+
74
+ // ============================================
75
+ // DISABLED - Too strict / opinionated
76
+ // ============================================
77
+
78
+ 'func-style': 'off',
79
+ 'no-magic-numbers': 'off',
80
+ 'no-ternary': 'off',
81
+ 'init-declarations': 'off',
82
+ 'id-length': 'off',
83
+ 'max-statements': 'off',
84
+ 'max-params': 'off',
85
+ 'no-continue': 'off',
86
+ 'arrow-body-style': 'off',
87
+ 'prefer-destructuring': 'off',
88
+ 'new-cap': 'off',
89
+ 'no-await-in-loop': 'off',
90
+ 'typescript/consistent-type-definitions': 'off',
91
+ 'typescript/array-type': 'off',
92
+ 'typescript/no-inferrable-types': 'off',
93
+ 'unicorn/no-null': 'off',
94
+ 'unicorn/no-array-sort': 'off',
95
+ 'import/no-unassigned-import': 'off',
96
+ 'import/no-named-default': 'off',
97
+ 'import/no-nodejs-modules': 'off',
98
+
99
+ // ============================================
100
+ // ENABLED - Code style enforcement
101
+ // ============================================
102
+
103
+ 'capitalized-comments': 'error',
104
+ curly: 'error',
105
+ 'no-nested-ternary': 'error',
106
+ 'unicorn/catch-error-name': 'error',
107
+ 'unicorn/numeric-separators-style': 'error',
108
+
109
+ // ============================================
110
+ // DISABLED - Conflicts with other rules
111
+ // ============================================
112
+
113
+ 'import/no-named-export': 'off',
114
+ 'import/consistent-type-specifier-style': 'off',
115
+ 'import/prefer-default-export': 'off',
116
+ 'import/no-default-export': 'off',
117
+ 'import/group-exports': 'off',
118
+ 'import/no-anonymous-default-export': 'off',
119
+
120
+ // ============================================
121
+ // ENABLED - Clean imports
122
+ // ============================================
123
+
124
+ 'import/first': 'error',
125
+ 'import/no-namespace': 'error',
126
+ },
127
+ ignorePatterns: ['dist/**', 'node_modules/**'],
128
+ });
@@ -0,0 +1,33 @@
1
+ import { resolve } from 'node:path';
2
+ import { defineConfig } from 'oxlint';
3
+
4
+ import base from './base.js';
5
+
6
+ const pluginPath = resolve(import.meta.dirname, 'plugins/codestyle.js');
7
+
8
+ export default defineConfig({
9
+ extends: [base],
10
+ plugins: ['typescript', 'import', 'react'],
11
+ jsPlugins: [pluginPath],
12
+ rules: {
13
+ 'typescript/no-require-imports': [
14
+ 'error',
15
+ {
16
+ allow: [
17
+ String.raw`\.png$`,
18
+ String.raw`\.jpg$`,
19
+ String.raw`\.jpeg$`,
20
+ String.raw`\.gif$`,
21
+ String.raw`\.webp$`,
22
+ ],
23
+ },
24
+ ],
25
+ 'codestyle/imports-without-ext': 'error',
26
+ 'react/react-in-jsx-scope': 'off',
27
+ 'react/jsx-props-no-spreading': 'off',
28
+ 'react/jsx-boolean-value': 'off',
29
+ 'react/jsx-handler-names': 'off',
30
+ 'react/jsx-curly-brace-presence': 'off',
31
+ 'unicorn/no-nested-ternary': 'off',
32
+ },
33
+ });
@@ -0,0 +1,23 @@
1
+ import { resolve } from 'node:path';
2
+ import { defineConfig } from 'oxlint';
3
+
4
+ import base from './base.js';
5
+
6
+ const pluginPath = resolve(import.meta.dirname, 'plugins/codestyle.js');
7
+
8
+ export default defineConfig({
9
+ extends: [base],
10
+ plugins: ['typescript', 'import', 'react', 'nextjs'],
11
+ jsPlugins: [pluginPath],
12
+ ignorePatterns: ['dist/**', 'node_modules/**', '.next/**', 'next-env.d.ts'],
13
+ rules: {
14
+ 'codestyle/imports-without-ext': 'error',
15
+ 'react/react-in-jsx-scope': 'off',
16
+ 'react/jsx-props-no-spreading': 'off',
17
+ 'react/jsx-boolean-value': 'off',
18
+ 'react/jsx-handler-names': 'off',
19
+ 'react/jsx-curly-brace-presence': 'off',
20
+ 'react/jsx-max-depth': 'off',
21
+ 'unicorn/no-nested-ternary': 'off',
22
+ },
23
+ });
@@ -0,0 +1,14 @@
1
+ import { resolve } from 'node:path';
2
+ import { defineConfig } from 'oxlint';
3
+
4
+ import base from './base.js';
5
+
6
+ const pluginPath = resolve(import.meta.dirname, 'plugins/codestyle.js');
7
+
8
+ export default defineConfig({
9
+ extends: [base],
10
+ jsPlugins: [pluginPath],
11
+ rules: {
12
+ 'codestyle/imports-with-ext': 'error',
13
+ },
14
+ });
@@ -0,0 +1,194 @@
1
+ // Codestyle plugin - custom rules for code quality
2
+ // Contains: architecture boundaries, import extensions
3
+
4
+ import defaultHexagonalRules from '../architectures/hexagonal-rules.js';
5
+
6
+ // ============================================
7
+ // Hexagonal - Architecture boundary rules
8
+ // ============================================
9
+
10
+ function createHexagonalRule() {
11
+ return {
12
+ meta: {
13
+ type: 'problem',
14
+ docs: {
15
+ description: 'Enforce hexagonal architecture layer boundaries',
16
+ category: 'Best Practices',
17
+ },
18
+ schema: [
19
+ {
20
+ type: 'object',
21
+ properties: {
22
+ rules: {
23
+ type: 'array',
24
+ items: {
25
+ type: 'object',
26
+ properties: {
27
+ from: { type: 'string' },
28
+ disallow: { type: 'array', items: { type: 'string' } },
29
+ message: { type: 'string' },
30
+ },
31
+ required: ['from', 'disallow'],
32
+ },
33
+ },
34
+ },
35
+ },
36
+ ],
37
+ },
38
+ create(context) {
39
+ const options = context.options[0];
40
+ const rules = options && options.rules ? options.rules : defaultHexagonalRules;
41
+ const filename = context.getFilename();
42
+
43
+ function checkNode(node) {
44
+ if (!node.source || !node.source.value) {
45
+ return;
46
+ }
47
+
48
+ const importPath = node.source.value;
49
+
50
+ for (const rule of rules) {
51
+ const fromPattern = new RegExp(rule.from);
52
+ if (!fromPattern.test(filename)) {
53
+ continue;
54
+ }
55
+
56
+ for (const disallowPattern of rule.disallow) {
57
+ if (new RegExp(disallowPattern).test(importPath)) {
58
+ context.report({
59
+ node: node.source,
60
+ message: rule.message || 'Import violates architecture boundaries',
61
+ });
62
+ break;
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ return {
69
+ ImportDeclaration: checkNode,
70
+ ExportNamedDeclaration: checkNode,
71
+ ExportAllDeclaration: checkNode,
72
+ };
73
+ },
74
+ };
75
+ }
76
+
77
+ // ============================================
78
+ // Imports-with-ext - Require .js extensions
79
+ // ============================================
80
+
81
+ const importsWithExtRule = {
82
+ meta: {
83
+ type: 'problem',
84
+ docs: {
85
+ description: 'Require .js extension in imports for Node.js ESM compatibility',
86
+ category: 'Best Practices',
87
+ },
88
+ fixable: 'code',
89
+ schema: [],
90
+ },
91
+ create(context) {
92
+ const hasExtension = /\.[a-zA-Z0-9]+$/;
93
+
94
+ function checkNode(node) {
95
+ if (!node.source || !node.source.value) {
96
+ return;
97
+ }
98
+
99
+ const importPath = node.source.value;
100
+
101
+ // Only check relative imports
102
+ if (!importPath.startsWith('.')) {
103
+ return;
104
+ }
105
+
106
+ // Skip if it already has an extension
107
+ if (hasExtension.test(importPath)) {
108
+ return;
109
+ }
110
+
111
+ // Skip type-only imports (they are erased at runtime)
112
+ if (node.importKind === 'type') {
113
+ return;
114
+ }
115
+
116
+ context.report({
117
+ node: node.source,
118
+ message: 'Missing .js extension in import (required for Node.js ESM)',
119
+ fix(fixer) {
120
+ const newPath = `${importPath}.js`;
121
+ return fixer.replaceText(node.source, `'${newPath}'`);
122
+ },
123
+ });
124
+ }
125
+
126
+ return {
127
+ ImportDeclaration: checkNode,
128
+ ExportNamedDeclaration: checkNode,
129
+ ExportAllDeclaration: checkNode,
130
+ };
131
+ },
132
+ };
133
+
134
+ // ============================================
135
+ // Imports-without-ext - Remove extensions
136
+ // ============================================
137
+
138
+ const importsWithoutExtRule = {
139
+ meta: {
140
+ type: 'problem',
141
+ docs: {
142
+ description: 'Remove .js, .jsx, .ts, .tsx extensions from imports',
143
+ category: 'Best Practices',
144
+ },
145
+ fixable: 'code',
146
+ schema: [],
147
+ },
148
+ create(context) {
149
+ const extensionsToRemove = /\.(?:js|jsx|ts|tsx)$/;
150
+
151
+ function checkNode(node) {
152
+ if (!node.source || !node.source.value) {
153
+ return;
154
+ }
155
+
156
+ const importPath = node.source.value;
157
+
158
+ // Check both relative imports and path alias imports
159
+ if (!importPath.startsWith('.') && !importPath.startsWith('@/')) {
160
+ return;
161
+ }
162
+
163
+ if (extensionsToRemove.test(importPath)) {
164
+ const match = importPath.match(extensionsToRemove);
165
+ context.report({
166
+ node: node.source,
167
+ message: `Remove "${match[0]}" extension from import`,
168
+ fix(fixer) {
169
+ const newPath = importPath.replace(extensionsToRemove, '');
170
+ return fixer.replaceText(node.source, `'${newPath}'`);
171
+ },
172
+ });
173
+ }
174
+ }
175
+
176
+ return {
177
+ ImportDeclaration: checkNode,
178
+ ExportNamedDeclaration: checkNode,
179
+ ExportAllDeclaration: checkNode,
180
+ };
181
+ },
182
+ };
183
+
184
+ export default {
185
+ meta: {
186
+ name: 'codestyle',
187
+ version: '1.0.0',
188
+ },
189
+ rules: {
190
+ 'arch-hexagonal': createHexagonalRule(),
191
+ 'imports-with-ext': importsWithExtRule,
192
+ 'imports-without-ext': importsWithoutExtRule,
193
+ },
194
+ };
@@ -1,21 +1,21 @@
1
1
  {
2
- "display": "Expo",
3
- "include": ["../../../../**/*.ts", "../../../../**/*.tsx"],
4
- "exclude": ["../../../../node_modules"],
2
+ "display": "Expo",
3
+ "include": ["../../../../**/*.ts", "../../../../**/*.tsx"],
4
+ "exclude": ["../../../../node_modules"],
5
5
 
6
- "compilerOptions": {
7
- "allowJs": true,
8
- "baseUrl": ".",
9
- "esModuleInterop": true,
10
- "jsx": "react-native",
11
- "lib": ["DOM", "ESNext"],
12
- "moduleResolution": "node",
13
- "noEmit": true,
14
- "paths": {
15
- "@/*": ["src/*"]
16
- },
17
- "resolveJsonModule": true,
18
- "skipLibCheck": true,
19
- "target": "ESNext"
20
- }
6
+ "compilerOptions": {
7
+ "allowJs": true,
8
+ "baseUrl": ".",
9
+ "esModuleInterop": true,
10
+ "jsx": "react-native",
11
+ "lib": ["DOM", "ESNext"],
12
+ "moduleResolution": "node",
13
+ "noEmit": true,
14
+ "paths": {
15
+ "@/*": ["src/*"]
16
+ },
17
+ "resolveJsonModule": true,
18
+ "skipLibCheck": true,
19
+ "target": "ESNext"
20
+ }
21
21
  }