@adbayb/stack 2.3.1 → 2.5.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 CHANGED
@@ -3,6 +3,6 @@
3
3
  import { join } from "node:path";
4
4
  import { createRequire } from "node:module";
5
5
 
6
- const pkg = createRequire(import.meta.url)("../package.json");
6
+ const package_ = createRequire(import.meta.url)("../package.json");
7
7
 
8
- import(join("..", pkg.exports["."].default));
8
+ import(join("..", package_.exports["."].default));
@@ -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
- // https://medium.com/@hbarcelos/why-i-banned-null-from-my-js-code-and-why-you-should-too-13df90323cfa
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,4 +1,7 @@
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";
4
+ // eslint-disable-next-line depend/ban-dependencies
2
5
  import reactPlugin from "eslint-plugin-react";
3
6
 
4
7
  export const config = [
@@ -7,16 +10,29 @@ export const config = [
7
10
  plugins: {
8
11
  "react": reactPlugin,
9
12
  "react-hooks": reactHooksPlugin,
13
+ "react-hooks-extra": reactHooksExtraPlugin,
14
+ "react-web-api": reactWebApiPlugin,
10
15
  },
11
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",
12
23
  "react-hooks/exhaustive-deps": "warn",
13
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",
14
29
  "react/boolean-prop-naming": [
15
30
  "error",
16
31
  { rule: "^(is|has)[A-Z]([A-Za-z0-9]?)+" },
17
32
  ],
18
33
  "react/button-has-type": "error",
19
34
  "react/checked-requires-onchange-or-readonly": "error",
35
+ "react/destructuring-assignment": ["error", "always"],
20
36
  "react/display-name": "error",
21
37
  "react/forbid-component-props": "error",
22
38
  "react/forward-ref-uses-ref": "error",
@@ -27,14 +43,13 @@ export const config = [
27
43
  "react/jsx-handler-names": "error",
28
44
  "react/jsx-key": "error",
29
45
  "react/jsx-no-bind": "error",
46
+ "react/jsx-no-comment-textnodes": "error",
30
47
  "react/jsx-no-constructed-context-values": "error",
31
48
  "react/jsx-no-leaked-render": "error",
32
49
  "react/jsx-no-script-url": "error",
33
50
  "react/jsx-no-target-blank": "error",
34
51
  "react/jsx-no-useless-fragment": "error",
35
- "react/jsx-pascal-case": "error",
36
52
  "react/jsx-props-no-spread-multi": "error",
37
- "react/jsx-sort-props": "error",
38
53
  "react/jsx-uses-react": "error",
39
54
  "react/jsx-uses-vars": "error",
40
55
  "react/no-access-state-in-setstate": "error",
@@ -55,6 +70,7 @@ export const config = [
55
70
  "react/no-this-in-sfc": "error",
56
71
  "react/no-typos": "error",
57
72
  "react/no-unescaped-entities": "error",
73
+ "react/no-unknown-property": "error",
58
74
  "react/no-unsafe": "error",
59
75
  "react/no-unstable-nested-components": "error",
60
76
  "react/no-unused-class-component-methods": "error",
@@ -62,7 +78,8 @@ export const config = [
62
78
  "react/no-will-update-set-state": "error",
63
79
  "react/prefer-read-only-props": "error",
64
80
  "react/prefer-stateless-function": "error",
65
- "react/self-closing-comp": "error",
81
+ "react/sort-comp": "error",
82
+ "react/state-in-constructor": ["error", "never"],
66
83
  "react/style-prop-object": "error",
67
84
  "react/void-dom-elements-no-children": "error",
68
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 { CWD, JAVASCRIPT_LIKE_EXTENSIONS } from "../constants.js";
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
  ];
@@ -1,4 +1,5 @@
1
1
  import sortKeysCustomOrderPlugin from "eslint-plugin-sort-keys-custom-order";
2
+ import dependPlugin from "eslint-plugin-depend";
2
3
  import { FlatCompat } from "@eslint/eslintrc";
3
4
 
4
5
  import { JAVASCRIPT_LIKE_EXTENSIONS } from "../constants.js";
@@ -7,9 +8,11 @@ export const config = [
7
8
  {
8
9
  files: JAVASCRIPT_LIKE_EXTENSIONS,
9
10
  plugins: {
11
+ "depend": dependPlugin,
10
12
  "sort-keys-custom-order": sortKeysCustomOrderPlugin,
11
13
  },
12
14
  rules: {
15
+ "depend/ban-dependencies": "error",
13
16
  "sort-keys-custom-order/object-keys": [
14
17
  "error",
15
18
  {
@@ -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 setPkgManager = async ()=>{
99
+ const setPackageManager = async ()=>{
100
100
  return helpers.exec("corepack enable");
101
101
  };
102
102
  const request = {
@@ -117,31 +117,30 @@ const eslint = (options)=>async (files = [])=>{
117
117
  eslintFiles.push(".");
118
118
  } else {
119
119
  eslintFiles = files.filter((file)=>{
120
- return ESLINT_EXTENSIONS.some((ext)=>file.endsWith(ext));
120
+ return ESLINT_EXTENSIONS.some((extension)=>file.endsWith(extension));
121
121
  });
122
- if (eslintFiles.length === 0) return Promise.resolve();
122
+ if (eslintFiles.length === 0) return;
123
123
  }
124
- const args = [
124
+ const arguments_ = [
125
125
  ...eslintFiles
126
126
  ];
127
- args.push("--cache");
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
- args.push("--fix");
129
+ arguments_.push("--fix");
133
130
  }
134
131
  try {
135
- return await helpers.exec(`eslint ${args.join(" ")}`);
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
- return await helpers.exec(`turbo run ${command}`, options);
139
+ const { hasLiveOutput = true, ...restOptions } = options;
140
+ return await helpers.exec(`turbo run ${command}`, {
141
+ ...restOptions,
142
+ hasLiveOutput
143
+ });
145
144
  } catch (error) {
146
145
  throw createError("turbo", error);
147
146
  }
@@ -168,9 +167,7 @@ const ESLINT_EXTENSIONS = [
168
167
  "mdx"
169
168
  ];
170
169
 
171
- var version = "2.3.1";
172
-
173
- const VERSION = version;
170
+ var version = "2.5.0";
174
171
 
175
172
  const createWatchCommand = (program)=>{
176
173
  program.command({
@@ -286,19 +283,17 @@ const fixFormatting = async (files)=>{
286
283
  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
284
  file !== "README.md";
288
285
  });
289
- if (prettierFiles.length === 0) return Promise.resolve();
286
+ if (prettierFiles.length === 0) return;
290
287
  }
291
- const args = [
288
+ const arguments_ = [
292
289
  ...prettierFiles
293
290
  ];
294
291
  if (existsSync(resolveFromProjectDirectory(".gitignore"))) {
295
- args.push("--ignore-path .gitignore");
292
+ arguments_.push("--ignore-path .gitignore");
296
293
  }
297
- args.push("--write");
298
- args.push("--ignore-unknown");
299
- args.push("--no-error-on-unmatched-pattern");
294
+ arguments_.push("--write", "--ignore-unknown", "--no-error-on-unmatched-pattern");
300
295
  try {
301
- return await helpers.exec(`prettier ${args.join(" ")}`);
296
+ return await helpers.exec(`prettier ${arguments_.join(" ")}`);
302
297
  } catch (error) {
303
298
  throw createError("prettier", error);
304
299
  }
@@ -336,7 +331,7 @@ const createCreateCommand = (program)=>{
336
331
  }).task({
337
332
  handler () {
338
333
  botMessage({
339
- title: `I'm Stack v${VERSION}, your bot assistant`,
334
+ title: `I'm Stack v${version}, your bot assistant`,
340
335
  description: "I can guarantee you a project creation in under 1 minute 🚀",
341
336
  type: "information"
342
337
  });
@@ -420,21 +415,21 @@ const createCreateCommand = (program)=>{
420
415
  }).task({
421
416
  label: label$2("Set up the package manager"),
422
417
  async handler () {
423
- await setPkgManager();
418
+ await setPackageManager();
424
419
  }
425
420
  }).task({
426
421
  label: label$2("Install dependencies"),
427
422
  async handler ({ data }) {
428
- const localDevDependencies = [
423
+ const localDevelopmentDependencies = [
429
424
  "quickbundle",
430
425
  "vitest"
431
426
  ];
432
- const globalDevDependencies = [
427
+ const globalDevelopmentDependencies = [
433
428
  "@adbayb/stack"
434
429
  ];
435
430
  try {
436
- await helpers.exec(`pnpm add ${globalDevDependencies.join(" ")} --save-dev --ignore-workspace-root-check`);
437
- await helpers.exec(`pnpm add ${localDevDependencies.join(" ")} --save-dev --filter ${data.projectName}`);
431
+ await helpers.exec(`pnpm add ${globalDevelopmentDependencies.join(" ")} --save-dev --ignore-workspace-root-check`);
432
+ await helpers.exec(`pnpm add ${localDevelopmentDependencies.join(" ")} --save-dev --filter ${data.projectName}`);
438
433
  await helpers.exec("pnpm install");
439
434
  } catch (error) {
440
435
  throw createError("pnpm", error);
@@ -476,7 +471,7 @@ const label$2 = (message)=>`${message} 🔨`;
476
471
  const projectRootPath = resolveFromProjectDirectory("./");
477
472
  const templateExpressionRegExp = /{{(.*?)}}/g;
478
473
  const evaluate = (content)=>{
479
- return content.replace(templateExpressionRegExp, (_, key)=>dataModel[key] || "");
474
+ return content.replaceAll(templateExpressionRegExp, (_, key)=>dataModel[key] || "");
480
475
  };
481
476
  /** Copy the template before mutations. */ cpSync(templateRootPath, projectRootPath, {
482
477
  force: true,
@@ -484,15 +479,15 @@ const label$2 = (message)=>`${message} 🔨`;
484
479
  });
485
480
  /** Template file mutations. */ new fdir().withBasePath().glob(`**/*${templateExtension}`).crawl(projectRootPath).sync().forEach((templateFilePath)=>{
486
481
  const projectFilePath = templateFilePath.slice(0, templateFilePath.lastIndexOf(templateExtension));
487
- const content = evaluate(readFileSync(templateFilePath, "utf-8"));
482
+ const content = evaluate(readFileSync(templateFilePath, "utf8"));
488
483
  renameSync(templateFilePath, projectFilePath);
489
- writeFileSync(projectFilePath, content, "utf-8");
484
+ writeFileSync(projectFilePath, content, "utf8");
490
485
  });
491
486
  /** Template folder mutations. */ new fdir().withBasePath().onlyDirs().filter((path)=>{
492
- return Boolean(templateExpressionRegExp.exec(path));
487
+ return templateExpressionRegExp.test(path);
493
488
  }).crawl(projectRootPath).sync()// Re-order from longest to lowest path length to apply first renaming operations on deepest file structure
494
489
  .sort((a, b)=>b.length - a.length).forEach((templateFolderPath)=>{
495
- const newPath = templateFolderPath.replace(templateExpressionRegExp, (_, dataModelKey)=>{
490
+ const newPath = templateFolderPath.replaceAll(templateExpressionRegExp, (_, dataModelKey)=>{
496
491
  return dataModel[dataModelKey];
497
492
  });
498
493
  renameSync(templateFolderPath, newPath);
@@ -524,7 +519,8 @@ const createCleanCommand = (program)=>{
524
519
  }
525
520
  }).task({
526
521
  handler ({ files }) {
527
- helpers.message(`Removed assets: ${files.join(", ")}\n`, {
522
+ helpers.message(files.join("\n "), {
523
+ label: "Removed assets",
528
524
  type: "information"
529
525
  });
530
526
  },
@@ -535,14 +531,17 @@ const createCleanCommand = (program)=>{
535
531
  };
536
532
  const label$1 = (message)=>`${message} 🧹`;
537
533
  const cleanFiles = async (files)=>{
538
- return helpers.exec(`rm -rf ${files.join(" ")}`);
534
+ return Promise.all(files.map(async (file)=>rm(file, {
535
+ force: true,
536
+ recursive: true
537
+ })));
539
538
  };
540
539
  const isDirectoryExistAndNotEmpty = (path)=>{
541
540
  return existsSync(path) && readdirSync(path).length > 0;
542
541
  };
543
542
  const retrieveIgnoredFiles = async ()=>{
544
- const rawFiles = await helpers.exec(`git clean -fdXn | grep -v '${PRESERVE_FILES.join("\\|")}' | cut -c 14-`);
545
- return rawFiles.split(/\n/).filter(Boolean);
543
+ const rawFiles = await helpers.exec("git clean -fdXn");
544
+ return rawFiles.split(/\n|\r\n/).filter((cleanOutput)=>!PRESERVE_FILES.some((excludedFile)=>cleanOutput.includes(excludedFile))).map((cleanOutput)=>cleanOutput.split(" ").at(-1));
546
545
  };
547
546
  const PRESERVE_FILES = [
548
547
  "node_modules"
@@ -559,25 +558,25 @@ const checkTypes = async ()=>{
559
558
  const checkPackages = async ()=>{
560
559
  const stdout = await helpers.exec("pnpm recursive ls --json");
561
560
  const checkPackagesVersionMismatch = createPackagesVersionMismatchChecker();
562
- const packages = JSON.parse(stdout).map((pkg)=>{
563
- const pkgPath = join(pkg.path, "package.json");
564
- assert(pkg.name, ()=>createPackageError(`\`${pkgPath}\` must have a name field.`));
565
- const pkgContent = require(pkgPath);
566
- const peerDependencies = pkgContent.peerDependencies ?? {};
567
- const devDependencies = pkgContent.devDependencies ?? {};
568
- const dependencies = pkgContent.dependencies ?? {};
561
+ const packages = JSON.parse(stdout).map((package_)=>{
562
+ const packagePath = join(package_.path, "package.json");
563
+ assert(package_.name, ()=>createPackageError(`\`${packagePath}\` must have a name field.`));
564
+ const packageContent = require(packagePath);
565
+ const peerDependencies = packageContent.peerDependencies ?? {};
566
+ const devDependencies = packageContent.devDependencies ?? {};
567
+ const dependencies = packageContent.dependencies ?? {};
569
568
  return {
570
- name: pkg.name,
569
+ name: package_.name,
571
570
  dependencies,
572
571
  devDependencies,
573
572
  peerDependencies
574
573
  };
575
574
  });
576
- for (const pkg of packages){
575
+ for (const package_ of packages){
577
576
  // 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(pkg);
577
+ checkPackagesVersionMismatch(package_);
579
578
  // Check version range accordingly to our dependency guidelines (ie. dev dependencies must be pinned and dependencies must have caret)
580
- checkPackagesVersionRange(pkg);
579
+ checkPackagesVersionRange(package_);
581
580
  }
582
581
  };
583
582
  const checkPackagesVersionRange = ({ name, dependencies, devDependencies, peerDependencies })=>{
@@ -587,13 +586,11 @@ const checkPackagesVersionRange = ({ name, dependencies, devDependencies, peerDe
587
586
  name: dependencyName,
588
587
  consumedBy: name
589
588
  });
590
- if (version !== "workspace:*" && !/^\d/.exec(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.`, {
589
+ 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
590
  name: dependencyName,
592
591
  consumedBy: name
593
592
  });
594
593
  }
595
- const isPreReleaseVersion = (version)=>/\d+\.\d+\.\d+-(alpha|beta|experimental|next|rc).*/.exec(version);
596
- const hasNoCaret = (version)=>!isPreReleaseVersion(version) && !/^\^/.exec(version);
597
594
  for (const dependencyName of Object.keys(dependencies)){
598
595
  const version = dependencies[dependencyName];
599
596
  assertVersion(version, {
@@ -623,12 +620,12 @@ const checkPackagesVersionRange = ({ name, dependencies, devDependencies, peerDe
623
620
  };
624
621
  const createPackagesVersionMismatchChecker = ()=>{
625
622
  const monorepoDependencies = new Map();
626
- const monorepoDevDependencies = new Map();
627
- const lint = (pkg, type)=>{
628
- const pkgName = pkg.name;
629
- const isDev = type === "development";
630
- const store = isDev ? monorepoDevDependencies : monorepoDependencies;
631
- const dependencies = isDev ? pkg.devDependencies : pkg.dependencies;
623
+ const monorepoDevelopmentDependencies = new Map();
624
+ const lint = (package_, type)=>{
625
+ const packageName = package_.name;
626
+ const isDevelopment = type === "development";
627
+ const store = isDevelopment ? monorepoDevelopmentDependencies : monorepoDependencies;
628
+ const dependencies = isDevelopment ? package_.devDependencies : package_.dependencies;
632
629
  for (const dependencyName of Object.keys(dependencies)){
633
630
  const depVersion = dependencies[dependencyName];
634
631
  if (!depVersion) continue;
@@ -639,16 +636,16 @@ const createPackagesVersionMismatchChecker = ()=>{
639
636
  }
640
637
  const isSameMonorepoVersion = depVersion === storedVersion;
641
638
  if (!isSameMonorepoVersion) {
642
- 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}\`.`, {
639
+ 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
640
  name: dependencyName,
644
- consumedBy: pkgName
641
+ consumedBy: packageName
645
642
  });
646
643
  }
647
644
  }
648
645
  };
649
- return (pkg)=>{
650
- lint(pkg, "development");
651
- lint(pkg, "production");
646
+ return (package_)=>{
647
+ lint(package_, "development");
648
+ lint(package_, "production");
652
649
  };
653
650
  };
654
651
  const createPackageError = (message, context)=>{
@@ -660,6 +657,8 @@ function assertVersion(version, { name, consumedBy }) {
660
657
  consumedBy
661
658
  }));
662
659
  }
660
+ const isPreReleaseVersion = (version)=>/\d+\.\d+\.\d+-(alpha|beta|experimental|next|rc).*/.exec(version);
661
+ const hasNoCaret = (version)=>!isPreReleaseVersion(version) && !version.startsWith("^");
663
662
 
664
663
  const checkLinter = eslint({
665
664
  isFixMode: false
@@ -752,15 +751,14 @@ const createProgram = (...commandFactories)=>{
752
751
  const program = termost({
753
752
  name: "stack",
754
753
  description: "Toolbox to easily scaffold and maintain a project",
755
- version: VERSION
756
- }, {
757
754
  onException () {
758
755
  botMessage({
759
756
  title: "Oops, an error occurred",
760
757
  description: "Keep calm and carry on with some coffee ☕️",
761
758
  type: "error"
762
759
  });
763
- }
760
+ },
761
+ version: version
764
762
  });
765
763
  for (const commandBuilder of commandFactories){
766
764
  commandBuilder(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adbayb/stack",
3
- "version": "2.3.1",
3
+ "version": "2.5.0",
4
4
  "description": "My opinionated JavaScript-based toolchain",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -43,36 +43,40 @@
43
43
  "license": "MIT",
44
44
  "devDependencies": {
45
45
  "@types/node": "22.9.0",
46
- "quickbundle": "2.6.0"
46
+ "quickbundle": "2.8.0"
47
47
  },
48
48
  "dependencies": {
49
49
  "@changesets/changelog-github": "^0.5.0",
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.2",
54
- "@eslint/eslintrc": "^3.1.0",
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.7",
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
- "eslint-plugin-import-x": "^4.4.0",
59
+ "eslint-plugin-depend": "^0.12.0",
60
+ "eslint-plugin-import-x": "^4.4.2",
60
61
  "eslint-plugin-jest-formatting": "^3.1.0",
61
- "eslint-plugin-jsdoc": "^50.4.3",
62
+ "eslint-plugin-jsdoc": "^50.5.0",
62
63
  "eslint-plugin-mdx": "^3.1.5",
63
- "eslint-plugin-n": "^17.13.1",
64
+ "eslint-plugin-n": "^17.13.2",
64
65
  "eslint-plugin-prettier": "^5.2.1",
66
+ "eslint-plugin-react-hooks-extra": "^1.16.1",
65
67
  "eslint-plugin-react-hooks": "^5.0.0",
68
+ "eslint-plugin-react-web-api": "^1.16.1",
66
69
  "eslint-plugin-react": "^7.37.2",
67
70
  "eslint-plugin-sonarjs": "^2.0.4",
68
71
  "eslint-plugin-sort-keys-custom-order": "^2.2.0",
72
+ "eslint-plugin-unicorn": "^56.0.0",
69
73
  "eslint": "^9.14.0",
70
74
  "fdir": "^6.4.2",
71
75
  "globals": "^15.12.0",
72
76
  "prettier": "^3.3.3",
73
- "termost": "^0.18.0",
74
- "turbo": "^2.2.3",
75
- "typescript-eslint": "^8.13.0",
77
+ "termost": "^1.2.0",
78
+ "turbo": "^2.3.0",
79
+ "typescript-eslint": "^8.14.0",
76
80
  "typescript": "^5.6.3"
77
81
  },
78
82
  "scripts": {