@adbayb/stack 2.4.0 → 2.6.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/index.js +2 -2
- package/configs/eslint/index.js +2 -0
- package/configs/eslint/presets/eslint.js +7 -3
- package/configs/eslint/presets/import.js +0 -1
- package/configs/eslint/presets/overridable.js +0 -5
- package/configs/eslint/presets/react.js +19 -3
- package/configs/eslint/presets/sonar.js +1 -4
- package/configs/eslint/presets/stylistic.js +8 -0
- package/configs/eslint/presets/typescript.js +9 -1
- package/configs/eslint/presets/unicorn.js +138 -0
- package/dist/index.js +104 -70
- package/package.json +10 -7
package/bin/index.js
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const package_ = createRequire(import.meta.url)("../package.json");
|
|
7
7
|
|
|
8
|
-
import(join("..",
|
|
8
|
+
import(join("..", package_.exports["."].default));
|
package/configs/eslint/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { config as unicornConfig } from "./presets/unicorn.js";
|
|
1
2
|
import { config as uncategorizedConfig } from "./presets/uncategorized.js";
|
|
2
3
|
import { config as typescriptConfig } from "./presets/typescript.js";
|
|
3
4
|
import { config as testConfig } from "./presets/test.js";
|
|
@@ -24,6 +25,7 @@ export default createConfig(
|
|
|
24
25
|
...sonarConfig,
|
|
25
26
|
...stylisticConfig,
|
|
26
27
|
...testConfig,
|
|
28
|
+
...unicornConfig,
|
|
27
29
|
...uncategorizedConfig,
|
|
28
30
|
...overridableConfig,
|
|
29
31
|
);
|
|
@@ -66,19 +66,23 @@ export const config = [
|
|
|
66
66
|
"no-regex-spaces": "error",
|
|
67
67
|
"no-restricted-syntax": [
|
|
68
68
|
"error",
|
|
69
|
-
|
|
69
|
+
/*
|
|
70
|
+
* https://medium.com/@hbarcelos/why-i-banned-null-from-my-js-code-and-why-you-should-too-13df90323cfa
|
|
71
|
+
* https://www.youtube.com/watch?v=PSGEjv3Tqo0&t=561s
|
|
72
|
+
*/
|
|
70
73
|
{
|
|
71
|
-
message: "Use undefined instead of null",
|
|
74
|
+
message: "Use `undefined` instead of `null`.",
|
|
72
75
|
selector: "VariableDeclarator > Literal[raw='null']",
|
|
73
76
|
},
|
|
74
77
|
{
|
|
75
|
-
message: "Use undefined instead of null",
|
|
78
|
+
message: "Use `undefined` instead of `null`.",
|
|
76
79
|
selector: "AssignmentExpression > Literal[raw='null']",
|
|
77
80
|
},
|
|
78
81
|
],
|
|
79
82
|
"no-self-assign": "error",
|
|
80
83
|
"no-shadow-restricted-names": "error",
|
|
81
84
|
"no-sparse-arrays": "error",
|
|
85
|
+
"no-template-curly-in-string": "error",
|
|
82
86
|
"no-unexpected-multiline": "error",
|
|
83
87
|
"no-unsafe-finally": "error",
|
|
84
88
|
"no-unsafe-optional-chaining": "error",
|
|
@@ -17,7 +17,6 @@ export const config = [
|
|
|
17
17
|
"import-x/no-absolute-path": "error",
|
|
18
18
|
"import-x/no-amd": "error",
|
|
19
19
|
"import-x/no-anonymous-default-export": "error",
|
|
20
|
-
"import-x/no-commonjs": "error",
|
|
21
20
|
"import-x/no-cycle": "error",
|
|
22
21
|
"import-x/no-default-export": "error",
|
|
23
22
|
"import-x/no-deprecated": "error",
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import tseslint from "typescript-eslint";
|
|
2
1
|
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
|
|
3
2
|
|
|
4
3
|
export const config = [
|
|
5
|
-
{
|
|
6
|
-
files: ["**/*.{js,jsx,cjs,mjs}"],
|
|
7
|
-
...tseslint.configs.disableTypeChecked,
|
|
8
|
-
},
|
|
9
4
|
{
|
|
10
5
|
// Relaxed rules for example-like folder, and [config-, story-, and test]-like files
|
|
11
6
|
files: [
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import reactWebApiPlugin from "eslint-plugin-react-web-api";
|
|
2
|
+
import reactHooksExtraPlugin from "eslint-plugin-react-hooks-extra";
|
|
1
3
|
import reactHooksPlugin from "eslint-plugin-react-hooks";
|
|
2
4
|
// eslint-disable-next-line depend/ban-dependencies
|
|
3
5
|
import reactPlugin from "eslint-plugin-react";
|
|
@@ -8,16 +10,29 @@ export const config = [
|
|
|
8
10
|
plugins: {
|
|
9
11
|
"react": reactPlugin,
|
|
10
12
|
"react-hooks": reactHooksPlugin,
|
|
13
|
+
"react-hooks-extra": reactHooksExtraPlugin,
|
|
14
|
+
"react-web-api": reactWebApiPlugin,
|
|
11
15
|
},
|
|
12
16
|
rules: {
|
|
17
|
+
"react-hooks-extra/no-direct-set-state-in-use-effect": "error",
|
|
18
|
+
"react-hooks-extra/no-direct-set-state-in-use-layout-effect": "error",
|
|
19
|
+
"react-hooks-extra/no-redundant-custom-hook": "error",
|
|
20
|
+
"react-hooks-extra/no-unnecessary-use-callback": "error",
|
|
21
|
+
"react-hooks-extra/no-unnecessary-use-memo": "error",
|
|
22
|
+
"react-hooks-extra/prefer-use-state-lazy-initialization": "error",
|
|
13
23
|
"react-hooks/exhaustive-deps": "warn",
|
|
14
24
|
"react-hooks/rules-of-hooks": "error",
|
|
25
|
+
"react-web-api/no-leaked-event-listener": "error",
|
|
26
|
+
"react-web-api/no-leaked-interval": "error",
|
|
27
|
+
"react-web-api/no-leaked-resize-observer": "error",
|
|
28
|
+
"react-web-api/no-leaked-timeout": "error",
|
|
15
29
|
"react/boolean-prop-naming": [
|
|
16
30
|
"error",
|
|
17
31
|
{ rule: "^(is|has)[A-Z]([A-Za-z0-9]?)+" },
|
|
18
32
|
],
|
|
19
33
|
"react/button-has-type": "error",
|
|
20
34
|
"react/checked-requires-onchange-or-readonly": "error",
|
|
35
|
+
"react/destructuring-assignment": ["error", "always"],
|
|
21
36
|
"react/display-name": "error",
|
|
22
37
|
"react/forbid-component-props": "error",
|
|
23
38
|
"react/forward-ref-uses-ref": "error",
|
|
@@ -28,14 +43,13 @@ export const config = [
|
|
|
28
43
|
"react/jsx-handler-names": "error",
|
|
29
44
|
"react/jsx-key": "error",
|
|
30
45
|
"react/jsx-no-bind": "error",
|
|
46
|
+
"react/jsx-no-comment-textnodes": "error",
|
|
31
47
|
"react/jsx-no-constructed-context-values": "error",
|
|
32
48
|
"react/jsx-no-leaked-render": "error",
|
|
33
49
|
"react/jsx-no-script-url": "error",
|
|
34
50
|
"react/jsx-no-target-blank": "error",
|
|
35
51
|
"react/jsx-no-useless-fragment": "error",
|
|
36
|
-
"react/jsx-pascal-case": "error",
|
|
37
52
|
"react/jsx-props-no-spread-multi": "error",
|
|
38
|
-
"react/jsx-sort-props": "error",
|
|
39
53
|
"react/jsx-uses-react": "error",
|
|
40
54
|
"react/jsx-uses-vars": "error",
|
|
41
55
|
"react/no-access-state-in-setstate": "error",
|
|
@@ -56,6 +70,7 @@ export const config = [
|
|
|
56
70
|
"react/no-this-in-sfc": "error",
|
|
57
71
|
"react/no-typos": "error",
|
|
58
72
|
"react/no-unescaped-entities": "error",
|
|
73
|
+
"react/no-unknown-property": "error",
|
|
59
74
|
"react/no-unsafe": "error",
|
|
60
75
|
"react/no-unstable-nested-components": "error",
|
|
61
76
|
"react/no-unused-class-component-methods": "error",
|
|
@@ -63,7 +78,8 @@ export const config = [
|
|
|
63
78
|
"react/no-will-update-set-state": "error",
|
|
64
79
|
"react/prefer-read-only-props": "error",
|
|
65
80
|
"react/prefer-stateless-function": "error",
|
|
66
|
-
"react/
|
|
81
|
+
"react/sort-comp": "error",
|
|
82
|
+
"react/state-in-constructor": ["error", "never"],
|
|
67
83
|
"react/style-prop-object": "error",
|
|
68
84
|
"react/void-dom-elements-no-children": "error",
|
|
69
85
|
},
|
|
@@ -151,7 +151,6 @@ export const config = [
|
|
|
151
151
|
"sonarjs/no-unenclosed-multiline-block": "error",
|
|
152
152
|
"sonarjs/no-uniq-key": "error",
|
|
153
153
|
"sonarjs/no-unsafe-unzip": "error",
|
|
154
|
-
"sonarjs/no-unstable-nested-components": "error",
|
|
155
154
|
"sonarjs/no-unthrown-error": "error",
|
|
156
155
|
"sonarjs/no-unused-collection": "error",
|
|
157
156
|
"sonarjs/no-useless-call": "error",
|
|
@@ -193,11 +192,9 @@ export const config = [
|
|
|
193
192
|
"sonarjs/sonar-no-empty-character-class": "error",
|
|
194
193
|
"sonarjs/sonar-no-fallthrough": "error",
|
|
195
194
|
"sonarjs/sonar-no-invalid-regexp": "error",
|
|
196
|
-
"sonarjs/sonar-no-magic-numbers": "error",
|
|
197
195
|
"sonarjs/sonar-no-misleading-character-class": "error",
|
|
198
196
|
"sonarjs/sonar-no-regex-spaces": "error",
|
|
199
|
-
"sonarjs/sonar-no-unused-class-component-methods": "error",
|
|
200
|
-
"sonarjs/sonar-prefer-regexp-exec": "error",
|
|
197
|
+
// "sonarjs/sonar-no-unused-class-component-methods": "error", // Disabled since it involves `eslint-plugin-react` activation
|
|
201
198
|
"sonarjs/sql-queries": "error",
|
|
202
199
|
"sonarjs/stable-tests": "error",
|
|
203
200
|
"sonarjs/stateful-regex": "error",
|
|
@@ -9,6 +9,9 @@ export const config = [
|
|
|
9
9
|
"@stylistic": stylistic,
|
|
10
10
|
},
|
|
11
11
|
rules: {
|
|
12
|
+
"@stylistic/jsx-pascal-case": "error",
|
|
13
|
+
"@stylistic/jsx-self-closing-comp": "error",
|
|
14
|
+
"@stylistic/jsx-sort-props": "error",
|
|
12
15
|
"@stylistic/lines-between-class-members": ["error", "always"],
|
|
13
16
|
"@stylistic/multiline-comment-style": ["error", "starred-block"],
|
|
14
17
|
"@stylistic/padding-line-between-statements": [
|
|
@@ -59,6 +62,11 @@ export const config = [
|
|
|
59
62
|
prev: ["cjs-export", "export"],
|
|
60
63
|
},
|
|
61
64
|
],
|
|
65
|
+
"@stylistic/quotes": [
|
|
66
|
+
"error",
|
|
67
|
+
"double",
|
|
68
|
+
{ allowTemplateLiterals: false, avoidEscape: true },
|
|
69
|
+
],
|
|
62
70
|
"@stylistic/spaced-comment": [
|
|
63
71
|
"error",
|
|
64
72
|
"always",
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import tseslint from "typescript-eslint";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
CWD,
|
|
5
|
+
JAVASCRIPT_EXTENSIONS,
|
|
6
|
+
JAVASCRIPT_LIKE_EXTENSIONS,
|
|
7
|
+
} from "../constants.js";
|
|
4
8
|
|
|
5
9
|
export const config = [
|
|
6
10
|
{
|
|
@@ -169,4 +173,8 @@ export const config = [
|
|
|
169
173
|
"@typescript-eslint/use-unknown-in-catch-callback-variable": "error",
|
|
170
174
|
},
|
|
171
175
|
},
|
|
176
|
+
{
|
|
177
|
+
files: JAVASCRIPT_EXTENSIONS,
|
|
178
|
+
...tseslint.configs.disableTypeChecked,
|
|
179
|
+
},
|
|
172
180
|
];
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/* eslint-disable unicorn/string-content */
|
|
2
|
+
import unicornPlugin from "eslint-plugin-unicorn";
|
|
3
|
+
|
|
4
|
+
import { JAVASCRIPT_LIKE_EXTENSIONS } from "../constants.js";
|
|
5
|
+
|
|
6
|
+
export const config = [
|
|
7
|
+
{
|
|
8
|
+
files: JAVASCRIPT_LIKE_EXTENSIONS,
|
|
9
|
+
plugins: {
|
|
10
|
+
unicorn: unicornPlugin,
|
|
11
|
+
},
|
|
12
|
+
rules: {
|
|
13
|
+
"unicorn/better-regex": "error",
|
|
14
|
+
"unicorn/catch-error-name": [
|
|
15
|
+
"error",
|
|
16
|
+
{
|
|
17
|
+
name: "error",
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
"unicorn/consistent-empty-array-spread": "error",
|
|
21
|
+
"unicorn/consistent-existence-index-check": "error",
|
|
22
|
+
"unicorn/consistent-function-scoping": "error",
|
|
23
|
+
"unicorn/custom-error-definition": "error",
|
|
24
|
+
"unicorn/error-message": "error",
|
|
25
|
+
"unicorn/escape-case": "error",
|
|
26
|
+
"unicorn/expiring-todo-comments": "error",
|
|
27
|
+
"unicorn/explicit-length-check": "error",
|
|
28
|
+
"unicorn/filename-case": [
|
|
29
|
+
"error",
|
|
30
|
+
{
|
|
31
|
+
cases: {
|
|
32
|
+
camelCase: true,
|
|
33
|
+
pascalCase: true,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
"unicorn/new-for-builtins": "error",
|
|
38
|
+
"unicorn/no-abusive-eslint-disable": "error",
|
|
39
|
+
"unicorn/no-array-callback-reference": "error",
|
|
40
|
+
"unicorn/no-array-push-push": "error",
|
|
41
|
+
"unicorn/no-await-in-promise-methods": "error",
|
|
42
|
+
"unicorn/no-console-spaces": "error",
|
|
43
|
+
"unicorn/no-document-cookie": "error",
|
|
44
|
+
"unicorn/no-empty-file": "error",
|
|
45
|
+
"unicorn/no-for-loop": "error",
|
|
46
|
+
"unicorn/no-instanceof-array": "error",
|
|
47
|
+
"unicorn/no-invalid-fetch-options": "error",
|
|
48
|
+
"unicorn/no-invalid-remove-event-listener": "error",
|
|
49
|
+
"unicorn/no-length-as-slice-end": "error",
|
|
50
|
+
"unicorn/no-new-buffer": "error",
|
|
51
|
+
"unicorn/no-object-as-default-parameter": "error",
|
|
52
|
+
"unicorn/no-process-exit": "error",
|
|
53
|
+
"unicorn/no-single-promise-in-promise-methods": "error",
|
|
54
|
+
"unicorn/no-static-only-class": "error",
|
|
55
|
+
"unicorn/no-typeof-undefined": "error",
|
|
56
|
+
"unicorn/no-unnecessary-polyfills": "error",
|
|
57
|
+
"unicorn/no-unreadable-array-destructuring": "error",
|
|
58
|
+
"unicorn/no-unreadable-iife": "error",
|
|
59
|
+
"unicorn/no-unused-properties": "error",
|
|
60
|
+
"unicorn/no-useless-fallback-in-spread": "error",
|
|
61
|
+
"unicorn/no-useless-length-check": "error",
|
|
62
|
+
"unicorn/no-useless-promise-resolve-reject": "error",
|
|
63
|
+
"unicorn/no-useless-spread": "error",
|
|
64
|
+
"unicorn/no-zero-fractions": "error",
|
|
65
|
+
"unicorn/number-literal-case": "error",
|
|
66
|
+
"unicorn/prefer-add-event-listener": "error",
|
|
67
|
+
"unicorn/prefer-array-find": "error",
|
|
68
|
+
"unicorn/prefer-array-flat": "error",
|
|
69
|
+
"unicorn/prefer-array-flat-map": "error",
|
|
70
|
+
"unicorn/prefer-array-index-of": "error",
|
|
71
|
+
"unicorn/prefer-array-some": "error",
|
|
72
|
+
"unicorn/prefer-blob-reading-methods": "error",
|
|
73
|
+
"unicorn/prefer-code-point": "error",
|
|
74
|
+
"unicorn/prefer-date-now": "error",
|
|
75
|
+
"unicorn/prefer-dom-node-append": "error",
|
|
76
|
+
"unicorn/prefer-dom-node-remove": "error",
|
|
77
|
+
"unicorn/prefer-dom-node-text-content": "error",
|
|
78
|
+
"unicorn/prefer-event-target": "error",
|
|
79
|
+
"unicorn/prefer-export-from": "error",
|
|
80
|
+
"unicorn/prefer-global-this": "error",
|
|
81
|
+
"unicorn/prefer-includes": "error",
|
|
82
|
+
"unicorn/prefer-keyboard-event-key": "error",
|
|
83
|
+
"unicorn/prefer-logical-operator-over-ternary": "error",
|
|
84
|
+
"unicorn/prefer-math-min-max": "error",
|
|
85
|
+
"unicorn/prefer-math-trunc": "error",
|
|
86
|
+
"unicorn/prefer-modern-dom-apis": "error",
|
|
87
|
+
"unicorn/prefer-modern-math-apis": "error",
|
|
88
|
+
"unicorn/prefer-module": "error",
|
|
89
|
+
"unicorn/prefer-native-coercion-functions": "error",
|
|
90
|
+
"unicorn/prefer-negative-index": "error",
|
|
91
|
+
"unicorn/prefer-number-properties": "error",
|
|
92
|
+
"unicorn/prefer-object-from-entries": "error",
|
|
93
|
+
"unicorn/prefer-optional-catch-binding": "error",
|
|
94
|
+
"unicorn/prefer-prototype-methods": "error",
|
|
95
|
+
"unicorn/prefer-query-selector": "error",
|
|
96
|
+
"unicorn/prefer-reflect-apply": "error",
|
|
97
|
+
"unicorn/prefer-regexp-test": "error",
|
|
98
|
+
"unicorn/prefer-set-has": "error",
|
|
99
|
+
"unicorn/prefer-set-size": "error",
|
|
100
|
+
"unicorn/prefer-spread": "error",
|
|
101
|
+
"unicorn/prefer-string-raw": "error",
|
|
102
|
+
"unicorn/prefer-string-replace-all": "error",
|
|
103
|
+
"unicorn/prefer-string-slice": "error",
|
|
104
|
+
"unicorn/prefer-string-starts-ends-with": "error",
|
|
105
|
+
"unicorn/prefer-string-trim-start-end": "error",
|
|
106
|
+
"unicorn/prefer-structured-clone": "error",
|
|
107
|
+
"unicorn/prefer-switch": "error",
|
|
108
|
+
"unicorn/prefer-ternary": "error",
|
|
109
|
+
"unicorn/prefer-top-level-await": "error",
|
|
110
|
+
"unicorn/prefer-type-error": "error",
|
|
111
|
+
"unicorn/prevent-abbreviations": [
|
|
112
|
+
"error",
|
|
113
|
+
{
|
|
114
|
+
allowList: {
|
|
115
|
+
props: true,
|
|
116
|
+
Props: true,
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
"unicorn/relative-url-style": ["error", "always"],
|
|
121
|
+
"unicorn/require-array-join-separator": "error",
|
|
122
|
+
"unicorn/require-number-to-fixed-digits-argument": "error",
|
|
123
|
+
"unicorn/string-content": [
|
|
124
|
+
"error",
|
|
125
|
+
{
|
|
126
|
+
patterns: {
|
|
127
|
+
"->": "→",
|
|
128
|
+
"\\.\\.\\.": "…",
|
|
129
|
+
"^http:\\/\\/": String.raw`^https:\/\/`,
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
"unicorn/switch-case-braces": "error",
|
|
134
|
+
"unicorn/text-encoding-identifier-case": "error",
|
|
135
|
+
"unicorn/throw-new-error": "error",
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
];
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { helpers, termost } from 'termost';
|
|
|
2
2
|
import process from 'node:process';
|
|
3
3
|
import { resolve, join } from 'node:path';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
|
-
import { writeFile, chmod, mkdir, symlink } from 'node:fs/promises';
|
|
5
|
+
import { writeFile, chmod, mkdir, symlink, rm } from 'node:fs/promises';
|
|
6
6
|
import { existsSync, cpSync, readFileSync, renameSync, writeFileSync, readdirSync } from 'node:fs';
|
|
7
7
|
import { fdir } from 'fdir';
|
|
8
8
|
|
|
@@ -96,7 +96,7 @@ const getStackCommand = (command, isNodeRuntime = true)=>{
|
|
|
96
96
|
const hasDependency = (packageName)=>{
|
|
97
97
|
return Boolean(require.resolve(packageName));
|
|
98
98
|
};
|
|
99
|
-
const
|
|
99
|
+
const setPackageManager = async ()=>{
|
|
100
100
|
return helpers.exec("corepack enable");
|
|
101
101
|
};
|
|
102
102
|
const request = {
|
|
@@ -117,35 +117,53 @@ const eslint = (options)=>async (files = [])=>{
|
|
|
117
117
|
eslintFiles.push(".");
|
|
118
118
|
} else {
|
|
119
119
|
eslintFiles = files.filter((file)=>{
|
|
120
|
-
return ESLINT_EXTENSIONS.some((
|
|
120
|
+
return ESLINT_EXTENSIONS.some((extension)=>file.endsWith(extension));
|
|
121
121
|
});
|
|
122
|
-
if (eslintFiles.length === 0) return
|
|
122
|
+
if (eslintFiles.length === 0) return;
|
|
123
123
|
}
|
|
124
|
-
const
|
|
124
|
+
const arguments_ = [
|
|
125
125
|
...eslintFiles
|
|
126
126
|
];
|
|
127
|
-
|
|
128
|
-
args.push(`--cache-location ${resolveFromProjectDirectory("node_modules/.cache/.eslintcache")}`);
|
|
129
|
-
// @note: prevent errors when no matched file is found
|
|
130
|
-
args.push("--no-error-on-unmatched-pattern");
|
|
127
|
+
arguments_.push("--cache", `--cache-location ${resolveFromProjectDirectory("node_modules/.cache/.eslintcache")}`, "--no-error-on-unmatched-pattern");
|
|
131
128
|
if (options.isFixMode) {
|
|
132
|
-
|
|
129
|
+
arguments_.push("--fix");
|
|
133
130
|
}
|
|
134
131
|
try {
|
|
135
|
-
return await helpers.exec(`eslint ${
|
|
132
|
+
return await helpers.exec(`eslint ${arguments_.join(" ")}`);
|
|
136
133
|
} catch (error) {
|
|
137
134
|
throw createError("eslint", error);
|
|
138
135
|
}
|
|
139
136
|
};
|
|
140
|
-
const turbo = async (command, options = {
|
|
141
|
-
hasLiveOutput: true
|
|
142
|
-
})=>{
|
|
137
|
+
const turbo = async (command, options = {})=>{
|
|
143
138
|
try {
|
|
144
|
-
|
|
139
|
+
const { excludeExamples = false, hasLiveOutput = true, ...restOptions } = options;
|
|
140
|
+
return await helpers.exec(`turbo run ${command} ${excludeExamples ? "--filter !@examples/*" : ""}`, {
|
|
141
|
+
...restOptions,
|
|
142
|
+
hasLiveOutput
|
|
143
|
+
});
|
|
145
144
|
} catch (error) {
|
|
146
145
|
throw createError("turbo", error);
|
|
147
146
|
}
|
|
148
147
|
};
|
|
148
|
+
const logCheckableFiles = (files)=>{
|
|
149
|
+
if (files.length === 0) {
|
|
150
|
+
helpers.message("The whole project will be checked.", {
|
|
151
|
+
label: false,
|
|
152
|
+
lineBreak: {
|
|
153
|
+
end: true,
|
|
154
|
+
start: false
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
helpers.message(files.join("\n "), {
|
|
160
|
+
label: "Following files will be checked:",
|
|
161
|
+
lineBreak: {
|
|
162
|
+
end: true,
|
|
163
|
+
start: false
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
};
|
|
149
167
|
const changeset = async (command)=>{
|
|
150
168
|
try {
|
|
151
169
|
return await helpers.exec(command, {
|
|
@@ -168,9 +186,7 @@ const ESLINT_EXTENSIONS = [
|
|
|
168
186
|
"mdx"
|
|
169
187
|
];
|
|
170
188
|
|
|
171
|
-
var version = "2.
|
|
172
|
-
|
|
173
|
-
const VERSION = version;
|
|
189
|
+
var version = "2.6.0";
|
|
174
190
|
|
|
175
191
|
const createWatchCommand = (program)=>{
|
|
176
192
|
program.command({
|
|
@@ -223,7 +239,6 @@ const createReleaseCommand = (program)=>{
|
|
|
223
239
|
description: "Publish package(s) to the registry"
|
|
224
240
|
}).task({
|
|
225
241
|
async handler () {
|
|
226
|
-
// TODO: label instead?
|
|
227
242
|
helpers.message("New changelog entry\n");
|
|
228
243
|
await changeset("changeset");
|
|
229
244
|
},
|
|
@@ -253,7 +268,9 @@ const createInstallCommand = (program)=>{
|
|
|
253
268
|
}).task({
|
|
254
269
|
label: label$4("Install `git.pre-commit` hook"),
|
|
255
270
|
async handler () {
|
|
256
|
-
|
|
271
|
+
const lineBreakMatcher = String.raw`\n|\r\n`;
|
|
272
|
+
const modifiedFilesCommand = `node -e 'console.log(require("child_process").execSync("git status --porcelain", {encoding: "utf8"}).split(/${lineBreakMatcher}/).filter(Boolean).map(item => item.split(" ").at(-1)).join(" "))'`;
|
|
273
|
+
await installGitHook("pre-commit", `${getStackCommand(`fix $(${modifiedFilesCommand})`, false)} && git add -A`);
|
|
257
274
|
}
|
|
258
275
|
}).task({
|
|
259
276
|
label: label$4("Install `git.commit-msg` hook"),
|
|
@@ -286,19 +303,17 @@ const fixFormatting = async (files)=>{
|
|
|
286
303
|
return !PRETTIER_IGNORE_FILES.some((filename)=>file.endsWith(filename)) && // The root `README.md` file is ignored to prevent error due to its symbolic link nature when specified explicitly as a file
|
|
287
304
|
file !== "README.md";
|
|
288
305
|
});
|
|
289
|
-
if (prettierFiles.length === 0) return
|
|
306
|
+
if (prettierFiles.length === 0) return;
|
|
290
307
|
}
|
|
291
|
-
const
|
|
308
|
+
const arguments_ = [
|
|
292
309
|
...prettierFiles
|
|
293
310
|
];
|
|
294
311
|
if (existsSync(resolveFromProjectDirectory(".gitignore"))) {
|
|
295
|
-
|
|
312
|
+
arguments_.push("--ignore-path .gitignore");
|
|
296
313
|
}
|
|
297
|
-
|
|
298
|
-
args.push("--ignore-unknown");
|
|
299
|
-
args.push("--no-error-on-unmatched-pattern");
|
|
314
|
+
arguments_.push("--write", "--ignore-unknown", "--no-error-on-unmatched-pattern");
|
|
300
315
|
try {
|
|
301
|
-
return await helpers.exec(`prettier ${
|
|
316
|
+
return await helpers.exec(`prettier ${arguments_.join(" ")}`);
|
|
302
317
|
} catch (error) {
|
|
303
318
|
throw createError("prettier", error);
|
|
304
319
|
}
|
|
@@ -308,10 +323,15 @@ const createFixCommand = (program)=>{
|
|
|
308
323
|
program.command({
|
|
309
324
|
name: "fix",
|
|
310
325
|
description: "Fix auto-fixable issues"
|
|
326
|
+
}).task({
|
|
327
|
+
handler (_, argv) {
|
|
328
|
+
logCheckableFiles(argv.operands);
|
|
329
|
+
}
|
|
311
330
|
}).task({
|
|
312
331
|
label: label$3("Prepare the project"),
|
|
313
332
|
async handler () {
|
|
314
333
|
await turbo("build", {
|
|
334
|
+
excludeExamples: true,
|
|
315
335
|
hasLiveOutput: false
|
|
316
336
|
});
|
|
317
337
|
}
|
|
@@ -336,7 +356,7 @@ const createCreateCommand = (program)=>{
|
|
|
336
356
|
}).task({
|
|
337
357
|
handler () {
|
|
338
358
|
botMessage({
|
|
339
|
-
title: `I'm Stack v${
|
|
359
|
+
title: `I'm Stack v${version}, your bot assistant`,
|
|
340
360
|
description: "I can guarantee you a project creation in under 1 minute 🚀",
|
|
341
361
|
type: "information"
|
|
342
362
|
});
|
|
@@ -420,21 +440,21 @@ const createCreateCommand = (program)=>{
|
|
|
420
440
|
}).task({
|
|
421
441
|
label: label$2("Set up the package manager"),
|
|
422
442
|
async handler () {
|
|
423
|
-
await
|
|
443
|
+
await setPackageManager();
|
|
424
444
|
}
|
|
425
445
|
}).task({
|
|
426
446
|
label: label$2("Install dependencies"),
|
|
427
447
|
async handler ({ data }) {
|
|
428
|
-
const
|
|
448
|
+
const localDevelopmentDependencies = [
|
|
429
449
|
"quickbundle",
|
|
430
450
|
"vitest"
|
|
431
451
|
];
|
|
432
|
-
const
|
|
452
|
+
const globalDevelopmentDependencies = [
|
|
433
453
|
"@adbayb/stack"
|
|
434
454
|
];
|
|
435
455
|
try {
|
|
436
|
-
await helpers.exec(`pnpm add ${
|
|
437
|
-
await helpers.exec(`pnpm add ${
|
|
456
|
+
await helpers.exec(`pnpm add ${globalDevelopmentDependencies.join(" ")} --save-dev --ignore-workspace-root-check`);
|
|
457
|
+
await helpers.exec(`pnpm add ${localDevelopmentDependencies.join(" ")} --save-dev --filter ${data.projectName}`);
|
|
438
458
|
await helpers.exec("pnpm install");
|
|
439
459
|
} catch (error) {
|
|
440
460
|
throw createError("pnpm", error);
|
|
@@ -476,7 +496,7 @@ const label$2 = (message)=>`${message} 🔨`;
|
|
|
476
496
|
const projectRootPath = resolveFromProjectDirectory("./");
|
|
477
497
|
const templateExpressionRegExp = /{{(.*?)}}/g;
|
|
478
498
|
const evaluate = (content)=>{
|
|
479
|
-
return content.
|
|
499
|
+
return content.replaceAll(templateExpressionRegExp, (_, key)=>dataModel[key] || "");
|
|
480
500
|
};
|
|
481
501
|
/** Copy the template before mutations. */ cpSync(templateRootPath, projectRootPath, {
|
|
482
502
|
force: true,
|
|
@@ -484,15 +504,15 @@ const label$2 = (message)=>`${message} 🔨`;
|
|
|
484
504
|
});
|
|
485
505
|
/** Template file mutations. */ new fdir().withBasePath().glob(`**/*${templateExtension}`).crawl(projectRootPath).sync().forEach((templateFilePath)=>{
|
|
486
506
|
const projectFilePath = templateFilePath.slice(0, templateFilePath.lastIndexOf(templateExtension));
|
|
487
|
-
const content = evaluate(readFileSync(templateFilePath, "
|
|
507
|
+
const content = evaluate(readFileSync(templateFilePath, "utf8"));
|
|
488
508
|
renameSync(templateFilePath, projectFilePath);
|
|
489
|
-
writeFileSync(projectFilePath, content, "
|
|
509
|
+
writeFileSync(projectFilePath, content, "utf8");
|
|
490
510
|
});
|
|
491
511
|
/** Template folder mutations. */ new fdir().withBasePath().onlyDirs().filter((path)=>{
|
|
492
|
-
return
|
|
512
|
+
return templateExpressionRegExp.test(path);
|
|
493
513
|
}).crawl(projectRootPath).sync()// Re-order from longest to lowest path length to apply first renaming operations on deepest file structure
|
|
494
514
|
.sort((a, b)=>b.length - a.length).forEach((templateFolderPath)=>{
|
|
495
|
-
const newPath = templateFolderPath.
|
|
515
|
+
const newPath = templateFolderPath.replaceAll(templateExpressionRegExp, (_, dataModelKey)=>{
|
|
496
516
|
return dataModel[dataModelKey];
|
|
497
517
|
});
|
|
498
518
|
renameSync(templateFolderPath, newPath);
|
|
@@ -524,7 +544,12 @@ const createCleanCommand = (program)=>{
|
|
|
524
544
|
}
|
|
525
545
|
}).task({
|
|
526
546
|
handler ({ files }) {
|
|
527
|
-
helpers.message(
|
|
547
|
+
helpers.message(files.join("\n "), {
|
|
548
|
+
label: "Removed assets",
|
|
549
|
+
lineBreak: {
|
|
550
|
+
end: false,
|
|
551
|
+
start: true
|
|
552
|
+
},
|
|
528
553
|
type: "information"
|
|
529
554
|
});
|
|
530
555
|
},
|
|
@@ -535,14 +560,17 @@ const createCleanCommand = (program)=>{
|
|
|
535
560
|
};
|
|
536
561
|
const label$1 = (message)=>`${message} 🧹`;
|
|
537
562
|
const cleanFiles = async (files)=>{
|
|
538
|
-
return
|
|
563
|
+
return Promise.all(files.map(async (file)=>rm(file, {
|
|
564
|
+
force: true,
|
|
565
|
+
recursive: true
|
|
566
|
+
})));
|
|
539
567
|
};
|
|
540
568
|
const isDirectoryExistAndNotEmpty = (path)=>{
|
|
541
569
|
return existsSync(path) && readdirSync(path).length > 0;
|
|
542
570
|
};
|
|
543
571
|
const retrieveIgnoredFiles = async ()=>{
|
|
544
|
-
const rawFiles = await helpers.exec(
|
|
545
|
-
return rawFiles.split(/\n/).filter(
|
|
572
|
+
const rawFiles = await helpers.exec("git clean -fdXn");
|
|
573
|
+
return rawFiles.split(/\n|\r\n/).filter((cleanOutput)=>!PRESERVE_FILES.some((excludedFile)=>cleanOutput.includes(excludedFile))).map((cleanOutput)=>cleanOutput.split(" ").at(-1));
|
|
546
574
|
};
|
|
547
575
|
const PRESERVE_FILES = [
|
|
548
576
|
"node_modules"
|
|
@@ -559,25 +587,25 @@ const checkTypes = async ()=>{
|
|
|
559
587
|
const checkPackages = async ()=>{
|
|
560
588
|
const stdout = await helpers.exec("pnpm recursive ls --json");
|
|
561
589
|
const checkPackagesVersionMismatch = createPackagesVersionMismatchChecker();
|
|
562
|
-
const packages = JSON.parse(stdout).map((
|
|
563
|
-
const
|
|
564
|
-
assert(
|
|
565
|
-
const
|
|
566
|
-
const peerDependencies =
|
|
567
|
-
const devDependencies =
|
|
568
|
-
const dependencies =
|
|
590
|
+
const packages = JSON.parse(stdout).map((package_)=>{
|
|
591
|
+
const packagePath = join(package_.path, "package.json");
|
|
592
|
+
assert(package_.name, ()=>createPackageError(`\`${packagePath}\` must have a name field.`));
|
|
593
|
+
const packageContent = require(packagePath);
|
|
594
|
+
const peerDependencies = packageContent.peerDependencies ?? {};
|
|
595
|
+
const devDependencies = packageContent.devDependencies ?? {};
|
|
596
|
+
const dependencies = packageContent.dependencies ?? {};
|
|
569
597
|
return {
|
|
570
|
-
name:
|
|
598
|
+
name: package_.name,
|
|
571
599
|
dependencies,
|
|
572
600
|
devDependencies,
|
|
573
601
|
peerDependencies
|
|
574
602
|
};
|
|
575
603
|
});
|
|
576
|
-
for (const
|
|
604
|
+
for (const package_ of packages){
|
|
577
605
|
// Check version mismatches to guarantee a single copy for a given package in the monorepo (use case: prevent singleton-like issues with React contexts)
|
|
578
|
-
checkPackagesVersionMismatch(
|
|
606
|
+
checkPackagesVersionMismatch(package_);
|
|
579
607
|
// Check version range accordingly to our dependency guidelines (ie. dev dependencies must be pinned and dependencies must have caret)
|
|
580
|
-
checkPackagesVersionRange(
|
|
608
|
+
checkPackagesVersionRange(package_);
|
|
581
609
|
}
|
|
582
610
|
};
|
|
583
611
|
const checkPackagesVersionRange = ({ name, dependencies, devDependencies, peerDependencies })=>{
|
|
@@ -587,13 +615,11 @@ const checkPackagesVersionRange = ({ name, dependencies, devDependencies, peerDe
|
|
|
587
615
|
name: dependencyName,
|
|
588
616
|
consumedBy: name
|
|
589
617
|
});
|
|
590
|
-
if (version !== "workspace:*" && !/^\d/.
|
|
618
|
+
if (version !== "workspace:*" && !/^\d/.test(version)) throw createPackageError(`As a dev dependency, \`${dependencyName}\` version must be fixed (or set as "workspace:*" for local packages) to reduce accidental breaking change risks due to an implicit semver upgrade.`, {
|
|
591
619
|
name: dependencyName,
|
|
592
620
|
consumedBy: name
|
|
593
621
|
});
|
|
594
622
|
}
|
|
595
|
-
const isPreReleaseVersion = (version)=>/\d+\.\d+\.\d+-(alpha|beta|experimental|next|rc).*/.exec(version);
|
|
596
|
-
const hasNoCaret = (version)=>!isPreReleaseVersion(version) && !/^\^/.exec(version);
|
|
597
623
|
for (const dependencyName of Object.keys(dependencies)){
|
|
598
624
|
const version = dependencies[dependencyName];
|
|
599
625
|
assertVersion(version, {
|
|
@@ -623,12 +649,12 @@ const checkPackagesVersionRange = ({ name, dependencies, devDependencies, peerDe
|
|
|
623
649
|
};
|
|
624
650
|
const createPackagesVersionMismatchChecker = ()=>{
|
|
625
651
|
const monorepoDependencies = new Map();
|
|
626
|
-
const
|
|
627
|
-
const lint = (
|
|
628
|
-
const
|
|
629
|
-
const
|
|
630
|
-
const store =
|
|
631
|
-
const dependencies =
|
|
652
|
+
const monorepoDevelopmentDependencies = new Map();
|
|
653
|
+
const lint = (package_, type)=>{
|
|
654
|
+
const packageName = package_.name;
|
|
655
|
+
const isDevelopment = type === "development";
|
|
656
|
+
const store = isDevelopment ? monorepoDevelopmentDependencies : monorepoDependencies;
|
|
657
|
+
const dependencies = isDevelopment ? package_.devDependencies : package_.dependencies;
|
|
632
658
|
for (const dependencyName of Object.keys(dependencies)){
|
|
633
659
|
const depVersion = dependencies[dependencyName];
|
|
634
660
|
if (!depVersion) continue;
|
|
@@ -639,20 +665,20 @@ const createPackagesVersionMismatchChecker = ()=>{
|
|
|
639
665
|
}
|
|
640
666
|
const isSameMonorepoVersion = depVersion === storedVersion;
|
|
641
667
|
if (!isSameMonorepoVersion) {
|
|
642
|
-
throw createPackageError(`Mismatched versions: received version \`${depVersion}\` while others use \`${storedVersion}\`. To prevent issues with singleton-like code (React contexts,
|
|
668
|
+
throw createPackageError(`Mismatched versions: received version \`${depVersion}\` while others use \`${storedVersion}\`. To prevent issues with singleton-like code (React contexts, …), please make sure to update all packages to use the same \`${dependencyName}\` version (either \`${storedVersion}\` or \`${depVersion}\`).`, {
|
|
643
669
|
name: dependencyName,
|
|
644
|
-
consumedBy:
|
|
670
|
+
consumedBy: packageName
|
|
645
671
|
});
|
|
646
672
|
}
|
|
647
673
|
}
|
|
648
674
|
};
|
|
649
|
-
return (
|
|
650
|
-
lint(
|
|
651
|
-
lint(
|
|
675
|
+
return (package_)=>{
|
|
676
|
+
lint(package_, "development");
|
|
677
|
+
lint(package_, "production");
|
|
652
678
|
};
|
|
653
679
|
};
|
|
654
680
|
const createPackageError = (message, context)=>{
|
|
655
|
-
return createError("stack check", !context ? message : `\`${context.name}\` consumed by \`${context.consumedBy}\` doesn't conform to
|
|
681
|
+
return createError("stack check", !context ? message : `\`${context.name}\` consumed by \`${context.consumedBy}\` doesn't conform to package guidelines.\n${message}`);
|
|
656
682
|
};
|
|
657
683
|
function assertVersion(version, { name, consumedBy }) {
|
|
658
684
|
assert(version, ()=>createPackageError(`\`${name}\` must have a valid version specified (current version equals to \`${String(version)}\`).`, {
|
|
@@ -660,6 +686,8 @@ function assertVersion(version, { name, consumedBy }) {
|
|
|
660
686
|
consumedBy
|
|
661
687
|
}));
|
|
662
688
|
}
|
|
689
|
+
const isPreReleaseVersion = (version)=>/\d+\.\d+\.\d+-(alpha|beta|experimental|next|rc).*/.exec(version);
|
|
690
|
+
const hasNoCaret = (version)=>!isPreReleaseVersion(version) && !version.startsWith("^");
|
|
663
691
|
|
|
664
692
|
const checkLinter = eslint({
|
|
665
693
|
isFixMode: false
|
|
@@ -688,15 +716,21 @@ const createCheckCommand = (program)=>{
|
|
|
688
716
|
name: "only",
|
|
689
717
|
description: `Run only one specified task (accepted value: ${ONLY_VALUES.join(", ")})`,
|
|
690
718
|
defaultValue: undefined
|
|
719
|
+
}).task({
|
|
720
|
+
handler (_, argv) {
|
|
721
|
+
logCheckableFiles(argv.operands);
|
|
722
|
+
},
|
|
723
|
+
skip: ifOnlyDefinedAndNotEqualTo("linter")
|
|
691
724
|
}).task({
|
|
692
725
|
label: label("Prepare the project"),
|
|
693
726
|
async handler () {
|
|
694
727
|
await turbo("build", {
|
|
728
|
+
excludeExamples: true,
|
|
695
729
|
hasLiveOutput: false
|
|
696
730
|
});
|
|
697
731
|
},
|
|
698
732
|
skip ({ only }) {
|
|
699
|
-
return only === "commit"; // No need to build if only
|
|
733
|
+
return only === "commit"; // No need to build if only commit is checked
|
|
700
734
|
}
|
|
701
735
|
}).task({
|
|
702
736
|
label: label("Check package guidelines"),
|
|
@@ -759,7 +793,7 @@ const createProgram = (...commandFactories)=>{
|
|
|
759
793
|
type: "error"
|
|
760
794
|
});
|
|
761
795
|
},
|
|
762
|
-
version:
|
|
796
|
+
version: version
|
|
763
797
|
});
|
|
764
798
|
for (const commandBuilder of commandFactories){
|
|
765
799
|
commandBuilder(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adbayb/stack",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "My opinionated JavaScript-based toolchain",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"@changesets/cli": "^2.27.9",
|
|
51
51
|
"@commitlint/cli": "^19.5.0",
|
|
52
52
|
"@commitlint/config-conventional": "^19.5.0",
|
|
53
|
-
"@eslint/compat": "^1.2.
|
|
54
|
-
"@eslint/eslintrc": "^3.
|
|
53
|
+
"@eslint/compat": "^1.2.3",
|
|
54
|
+
"@eslint/eslintrc": "^3.2.0",
|
|
55
55
|
"@stylistic/eslint-plugin": "^2.10.1",
|
|
56
|
-
"@vitest/eslint-plugin": "^1.1.
|
|
56
|
+
"@vitest/eslint-plugin": "^1.1.10",
|
|
57
57
|
"eslint-config-prettier": "^9.1.0",
|
|
58
58
|
"eslint-import-resolver-typescript": "^3.6.3",
|
|
59
59
|
"eslint-plugin-depend": "^0.12.0",
|
|
@@ -61,18 +61,21 @@
|
|
|
61
61
|
"eslint-plugin-jest-formatting": "^3.1.0",
|
|
62
62
|
"eslint-plugin-jsdoc": "^50.5.0",
|
|
63
63
|
"eslint-plugin-mdx": "^3.1.5",
|
|
64
|
-
"eslint-plugin-n": "^17.13.
|
|
64
|
+
"eslint-plugin-n": "^17.13.2",
|
|
65
65
|
"eslint-plugin-prettier": "^5.2.1",
|
|
66
|
+
"eslint-plugin-react-hooks-extra": "^1.16.1",
|
|
66
67
|
"eslint-plugin-react-hooks": "^5.0.0",
|
|
68
|
+
"eslint-plugin-react-web-api": "^1.16.1",
|
|
67
69
|
"eslint-plugin-react": "^7.37.2",
|
|
68
70
|
"eslint-plugin-sonarjs": "^2.0.4",
|
|
69
71
|
"eslint-plugin-sort-keys-custom-order": "^2.2.0",
|
|
72
|
+
"eslint-plugin-unicorn": "^56.0.0",
|
|
70
73
|
"eslint": "^9.14.0",
|
|
71
74
|
"fdir": "^6.4.2",
|
|
72
75
|
"globals": "^15.12.0",
|
|
73
76
|
"prettier": "^3.3.3",
|
|
74
|
-
"termost": "^1.
|
|
75
|
-
"turbo": "^2.
|
|
77
|
+
"termost": "^1.4.0",
|
|
78
|
+
"turbo": "^2.3.0",
|
|
76
79
|
"typescript-eslint": "^8.14.0",
|
|
77
80
|
"typescript": "^5.6.3"
|
|
78
81
|
},
|