@adbayb/stack 0.0.0-next-912a585 → 0.0.0-next-ece28ab

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
@@ -59,19 +59,13 @@ stack release
59
59
 
60
60
  <br>
61
61
 
62
- ## 🏗️ Architecture
62
+ ## 🌍 Ecosystem
63
63
 
64
- ```mermaid
65
- flowchart TD
66
- N("npm init @adbayb"):::strokeWidth -- Runs --> C("@adbayb/create"):::strokeWidth
67
- C -- Runs --> S("@adbayb/stack"):::strokeWidth
68
- S -- "Scaffolds (via template and shared configurations)" --> P("New project"):::strokeWidth
69
- style C fill:#daf2d7,stroke:#90cf8e
70
- style S fill:#daf2d7,stroke:#90cf8e
71
- classDef strokeWidth stroke-width:3px
72
- ```
64
+ ### Available configurations
73
65
 
74
- <br>
66
+ - [@adbayb/stack/eslint](https://github.com/adbayb/stack/blob/main/stack/config/eslint/) to align logical and stylistic rules across projects.
67
+ - [@adbayb/stack/prettier](https://github.com/adbayb/stack/blob/main/stack/config/prettier/) to align code formatting conventions across projects.
68
+ - [@adbayb/stack/typescript](https://github.com/adbayb/stack/blob/main/stack/config/typescript/) to align type checking and processing across projects.
75
69
 
76
70
  ### Used technologies
77
71
 
@@ -87,15 +81,25 @@ flowchart TD
87
81
  - [Scripts](https://conventionalscripts.org/)
88
82
  - [File Structure](https://nx.dev/concepts/decisions/folder-structure)
89
83
  - Monorepo-first structure to:
90
- - Keep a consistent file structure pattern across templates (ease template familiarity and onboarding)
91
- - Ease examples setup
92
- - Not clutter the published package with development-related context, metadata, and artifacts
84
+ - Keep a consistent file structure pattern across templates (ease template familiarity and onboarding).
85
+ - Ease examples setup.
86
+ - Not clutter the published package with development-related context, metadata, and artifacts.
93
87
 
94
88
  <br>
95
89
 
96
- ## 👣 Roadmap
90
+ ## 🏗️ Architecture
91
+
92
+ ### Project creation workflow
97
93
 
98
- - [ ] `stack check`: Lint packages (caret range for dependencies and strict ones for dev dependencies)
94
+ ```mermaid
95
+ flowchart TD
96
+ N("npm init @adbayb"):::strokeWidth -- Runs --> C("@adbayb/create"):::strokeWidth
97
+ C -- Runs --> S("@adbayb/stack create"):::strokeWidth
98
+ S -- "Scaffolds (via template and shared configurations)" --> P("New project"):::strokeWidth
99
+ style C fill:#daf2d7,stroke:#90cf8e
100
+ style S fill:#daf2d7,stroke:#90cf8e
101
+ classDef strokeWidth stroke-width:3px
102
+ ```
99
103
 
100
104
  <br>
101
105
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  ## ✨ Features
10
10
 
11
- This package offers opinionated but extensible **[ESLint](https://eslint.org/)** configurations to align logical and stylistic rules across my projects.
11
+ This package offers opinionated but extensible **[ESLint](https://eslint.org/)** configurations to align logical and stylistic rules across projects.
12
12
 
13
13
  It allows to:
14
14
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  ## ✨ Features
10
10
 
11
- This package offers opinionated but extensible **[Prettier](https://prettier.io/)** configurations to align formatting rules and standards across my projects.
11
+ This package offers opinionated but extensible **[Prettier](https://prettier.io/)** configurations to align code formatting conventions across projects.
12
12
 
13
13
  It allows to:
14
14
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  ## ✨ Features
10
10
 
11
- This package offers opinionated but extensible **[TypeScript](https://www.typescriptlang.org/tsconfig/)** configurations to align type-checking and processing standards across my projects.
11
+ This package offers opinionated but extensible **[TypeScript](https://www.typescriptlang.org/tsconfig/)** configurations to align type checking and processing across projects.
12
12
 
13
13
  It allows to:
14
14
 
package/dist/index.js CHANGED
@@ -2,15 +2,20 @@ 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 { existsSync, cpSync, readFileSync, renameSync, writeFileSync, readdirSync } from 'node:fs';
6
5
  import { writeFile, chmod, mkdir, symlink } from 'node:fs/promises';
6
+ import { existsSync, cpSync, readFileSync, renameSync, writeFileSync, readdirSync } from 'node:fs';
7
7
  import { fdir } from 'fdir';
8
8
 
9
- var version = "0.0.0-next-912a585";
9
+ var version = "0.0.0-next-ece28ab";
10
10
 
11
11
  const VERSION = version;
12
12
 
13
13
  const require = createRequire(import.meta.url);
14
+ function assert(expectedCondition, createError) {
15
+ if (!expectedCondition) {
16
+ throw createError();
17
+ }
18
+ }
14
19
  /**
15
20
  * Helper to format log messages with a welcoming bot.
16
21
  * @param input - Message factory.
@@ -58,6 +63,14 @@ ${input.body}
58
63
  */ const resolveFromStackDirectory = (path)=>{
59
64
  return resolve(import.meta.dirname, "../", path);
60
65
  };
66
+ const createError = (bin, error)=>{
67
+ const errorMessage = `\`${bin}\` command failed.\n${String(error)}`;
68
+ if (error instanceof Error) {
69
+ error.message = errorMessage;
70
+ return error;
71
+ }
72
+ return new Error(errorMessage);
73
+ };
61
74
  const getNpmVersion = async ()=>{
62
75
  try {
63
76
  return await helpers.exec("pnpm -v");
@@ -121,60 +134,6 @@ const eslint = (options)=>async (files = [])=>{
121
134
  throw createError("eslint", error);
122
135
  }
123
136
  };
124
- const checkLints = eslint({
125
- isFixMode: false
126
- });
127
- const fixLints = eslint({
128
- isFixMode: true
129
- });
130
- const fixFormatting = async (files)=>{
131
- let prettierFiles = [];
132
- if (files.length === 0) {
133
- prettierFiles.push(`"**/!(${PRETTIER_IGNORE_FILES.join("|")})"`);
134
- } else {
135
- prettierFiles = files.filter((file)=>{
136
- 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
137
- file !== "README.md";
138
- });
139
- if (prettierFiles.length === 0) return Promise.resolve();
140
- }
141
- const args = [
142
- ...prettierFiles
143
- ];
144
- if (existsSync(resolveFromProjectDirectory(".gitignore"))) {
145
- args.push("--ignore-path .gitignore");
146
- }
147
- args.push("--write");
148
- args.push("--ignore-unknown");
149
- args.push("--no-error-on-unmatched-pattern");
150
- try {
151
- return await helpers.exec(`prettier ${args.join(" ")}`);
152
- } catch (error) {
153
- throw createError("prettier", error);
154
- }
155
- };
156
- const checkTypes = async ()=>{
157
- try {
158
- return await helpers.exec("pnpm --parallel exec tsc --noEmit");
159
- } catch (error) {
160
- throw createError("tsc", error);
161
- }
162
- };
163
- const checkCommit = async ()=>{
164
- try {
165
- return await helpers.exec('commitlint --extends "@commitlint/config-conventional" --edit');
166
- } catch (error) {
167
- throw createError("commitlint", error);
168
- }
169
- };
170
- const createError = (bin, error)=>{
171
- const errorMessage = `\`${bin}\` command failed.\n${String(error)}`;
172
- if (error instanceof Error) {
173
- error.message = errorMessage;
174
- return error;
175
- }
176
- return new Error(errorMessage);
177
- };
178
137
  const turbo = async (command, options = {
179
138
  hasLiveOutput: true
180
139
  })=>{
@@ -193,26 +152,17 @@ const changeset = async (command)=>{
193
152
  throw createError("changeset", error);
194
153
  }
195
154
  };
196
- const TYPESCRIPT_EXTENSIONS = [
197
- "ts",
198
- "tsx",
199
- "cts",
200
- "mts"
201
- ];
202
- /**
203
- * Extensions supported by ESLint.
204
- */ const ESLINT_EXTENSIONS = [
155
+ const ESLINT_EXTENSIONS = [
205
156
  "js",
206
157
  "jsx",
207
158
  "cjs",
208
159
  "mjs",
160
+ "ts",
161
+ "tsx",
162
+ "cts",
163
+ "mts",
209
164
  "md",
210
- "mdx",
211
- ...TYPESCRIPT_EXTENSIONS
212
- ];
213
- const PRETTIER_IGNORE_FILES = [
214
- "CHANGELOG.md",
215
- "pnpm-lock.yaml"
165
+ "mdx"
216
166
  ];
217
167
 
218
168
  const createWatchCommand = (program)=>{
@@ -266,6 +216,7 @@ const createReleaseCommand = (program)=>{
266
216
  description: "Publish package(s) to the registry"
267
217
  }).task({
268
218
  async handler () {
219
+ // TODO: label instead?
269
220
  helpers.message("New changelog entry\n");
270
221
  await changeset("changeset");
271
222
  },
@@ -293,12 +244,12 @@ const createInstallCommand = (program)=>{
293
244
  name: "install",
294
245
  description: "Setup initial requirements"
295
246
  }).task({
296
- label: label$4("Installing `git.pre-commit` hook"),
247
+ label: label$4("Install `git.pre-commit` hook"),
297
248
  async handler () {
298
249
  await installGitHook("pre-commit", `${getStackCommand("fix $(git status --porcelain | awk 'BEGIN{ ORS=\" \" } { print $2 }')", false)} && git add -A`);
299
250
  }
300
251
  }).task({
301
- label: label$4("Installing `git.commit-msg` hook"),
252
+ label: label$4("Install `git.commit-msg` hook"),
302
253
  async handler () {
303
254
  await installGitHook("commit-msg", getStackCommand("check --only commit", false));
304
255
  }
@@ -311,24 +262,59 @@ const installGitHook = async (hook, content)=>{
311
262
  return chmod(filename, "0755");
312
263
  };
313
264
 
265
+ const fixLinter = eslint({
266
+ isFixMode: true
267
+ });
268
+
269
+ const PRETTIER_IGNORE_FILES = [
270
+ "CHANGELOG.md",
271
+ "pnpm-lock.yaml"
272
+ ];
273
+ const fixFormatting = async (files)=>{
274
+ let prettierFiles = [];
275
+ if (files.length === 0) {
276
+ prettierFiles.push(`"**/!(${PRETTIER_IGNORE_FILES.join("|")})"`);
277
+ } else {
278
+ prettierFiles = files.filter((file)=>{
279
+ 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
280
+ file !== "README.md";
281
+ });
282
+ if (prettierFiles.length === 0) return Promise.resolve();
283
+ }
284
+ const args = [
285
+ ...prettierFiles
286
+ ];
287
+ if (existsSync(resolveFromProjectDirectory(".gitignore"))) {
288
+ args.push("--ignore-path .gitignore");
289
+ }
290
+ args.push("--write");
291
+ args.push("--ignore-unknown");
292
+ args.push("--no-error-on-unmatched-pattern");
293
+ try {
294
+ return await helpers.exec(`prettier ${args.join(" ")}`);
295
+ } catch (error) {
296
+ throw createError("prettier", error);
297
+ }
298
+ };
299
+
314
300
  const createFixCommand = (program)=>{
315
301
  program.command({
316
302
  name: "fix",
317
303
  description: "Fix auto-fixable issues"
318
304
  }).task({
319
- label: label$3("Preparing the project"),
305
+ label: label$3("Prepare the project"),
320
306
  async handler () {
321
307
  await turbo("build", {
322
308
  hasLiveOutput: false
323
309
  });
324
310
  }
325
311
  }).task({
326
- label: label$3("Fixing lints"),
312
+ label: label$3("Fix linter issues"),
327
313
  async handler (_, argv) {
328
- await fixLints(argv.operands);
314
+ await fixLinter(argv.operands);
329
315
  }
330
316
  }).task({
331
- label: label$3("Fixing formatting"),
317
+ label: label$3("Fix formatting issues"),
332
318
  async handler (_, argv) {
333
319
  await fixFormatting(argv.operands);
334
320
  }
@@ -379,7 +365,7 @@ const createCreateCommand = (program)=>{
379
365
  }
380
366
  }).task({
381
367
  key: "data",
382
- label: label$2("Evaluating contextual data"),
368
+ label: label$2("Evaluate contextual data"),
383
369
  async handler ({ inputDescription, inputName, inputUrl }) {
384
370
  const nodeVersion = (await request.get("https://resolve-node.vercel.app/lts", "text")).replace("v", "");
385
371
  const npmVersion = (await request.get("https://registry.npmjs.org/pnpm/latest", "json")).version;
@@ -399,7 +385,7 @@ const createCreateCommand = (program)=>{
399
385
  }
400
386
  }).task({
401
387
  label ({ data }) {
402
- return label$2(`Creating \`${data.projectName}\` folder`);
388
+ return label$2(`Create \`${data.projectName}\` folder`);
403
389
  },
404
390
  async handler ({ data }) {
405
391
  const projectPath = resolve(process.cwd(), data.projectName);
@@ -407,18 +393,18 @@ const createCreateCommand = (program)=>{
407
393
  process.chdir(projectPath);
408
394
  }
409
395
  }).task({
410
- label: label$2("Initializing `git`"),
396
+ label: label$2("Initialize `git`"),
411
397
  async handler ({ data }) {
412
398
  await helpers.exec("git init");
413
399
  await helpers.exec(`git remote add origin ${data.projectUrl}`);
414
400
  }
415
401
  }).task({
416
- label: label$2("Applying template"),
402
+ label: label$2("Apply template"),
417
403
  handler ({ data, inputTemplate }) {
418
404
  applyTemplate(inputTemplate, data);
419
405
  }
420
406
  }).task({
421
- label: label$2("Installing dependencies"),
407
+ label: label$2("Install dependencies"),
422
408
  async handler ({ data }) {
423
409
  const localDevDependencies = [
424
410
  "quickbundle",
@@ -436,7 +422,7 @@ const createCreateCommand = (program)=>{
436
422
  }
437
423
  }).task({
438
424
  key: "error",
439
- label: label$2("Cleaning up"),
425
+ label: label$2("Clean up"),
440
426
  async handler ({ data }) {
441
427
  try {
442
428
  // Symlink the package `README.md` file to the root project directory
@@ -518,7 +504,7 @@ const createCleanCommand = (program)=>{
518
504
  description: "Clean the project"
519
505
  }).task({
520
506
  key: "files",
521
- label: label$1("Retrieving removable assets"),
507
+ label: label$1("Retrieve removable files"),
522
508
  async handler () {
523
509
  const cachePath = "node_modules/.cache";
524
510
  const files = await retrieveIgnoredFiles();
@@ -562,10 +548,136 @@ const PRESERVE_FILES = [
562
548
  ".turbo"
563
549
  ];
564
550
 
565
- const onlyValues = [
551
+ const checkTypes = async ()=>{
552
+ try {
553
+ return await helpers.exec("pnpm --parallel exec tsc --noEmit");
554
+ } catch (error) {
555
+ throw createError("tsc", error);
556
+ }
557
+ };
558
+
559
+ const checkPackages = async ()=>{
560
+ const stdout = await helpers.exec("pnpm recursive ls --json");
561
+ 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 ?? {};
569
+ return {
570
+ name: pkg.name,
571
+ dependencies,
572
+ devDependencies,
573
+ peerDependencies
574
+ };
575
+ });
576
+ for (const pkg of packages){
577
+ // 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);
579
+ // Check version range accordingly to our dependency guidelines (ie. dev dependencies must be pinned and dependencies must have caret)
580
+ checkPackagesVersionRange(pkg);
581
+ }
582
+ };
583
+ const checkPackagesVersionRange = ({ name, dependencies, devDependencies, peerDependencies })=>{
584
+ for (const dependencyName of Object.keys(devDependencies)){
585
+ const version = devDependencies[dependencyName];
586
+ assertVersion(version, {
587
+ name: dependencyName,
588
+ consumedBy: name
589
+ });
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.`, {
591
+ name: dependencyName,
592
+ consumedBy: name
593
+ });
594
+ }
595
+ const isPreReleaseVersion = (version)=>/\d+\.\d+\.\d+-(alpha|beta|experimental|next|rc).*/.exec(version);
596
+ const hasNoCaret = (version)=>!isPreReleaseVersion(version) && !/^\^/.exec(version);
597
+ for (const dependencyName of Object.keys(dependencies)){
598
+ const version = dependencies[dependencyName];
599
+ assertVersion(version, {
600
+ name: dependencyName,
601
+ consumedBy: name
602
+ });
603
+ if (version !== "workspace:^" && hasNoCaret(version)) throw createPackageError(`As a dependency, \`${dependencyName}\` version must be prefixed with a caret (or set as "workspace:^" for local packages) to optimize the size (whether of installation or bundle output) on the consumer side.`, {
604
+ name: dependencyName,
605
+ consumedBy: name
606
+ });
607
+ }
608
+ for (const dependencyName of Object.keys(peerDependencies)){
609
+ const version = peerDependencies[dependencyName];
610
+ assertVersion(version, {
611
+ name: dependencyName,
612
+ consumedBy: name
613
+ });
614
+ if (hasNoCaret(version)) /*
615
+ * Why disallowing workspace protocol as a version resolver?
616
+ * To reduce the update frequency needs consumer-side and guarantee on our side the minimum compatible version,
617
+ * the best practice should be to keeping an explicit number version which represents the lowest compatible version from an API perspective.
618
+ */ throw createPackageError(`As a peer dependency, \`${dependencyName}\` version must be explicit (i.e. the "workspace:^" protocol a version resolver is not allowed) and prefixed with a caret to optimize the size (whether of installation or bundle output) on the consumer side.`, {
619
+ name: dependencyName,
620
+ consumedBy: name
621
+ });
622
+ }
623
+ };
624
+ const createPackagesVersionMismatchChecker = ()=>{
625
+ 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;
632
+ for (const dependencyName of Object.keys(dependencies)){
633
+ const depVersion = dependencies[dependencyName];
634
+ if (!depVersion) continue;
635
+ const storedVersion = store.get(dependencyName);
636
+ if (!storedVersion) {
637
+ store.set(dependencyName, depVersion);
638
+ continue;
639
+ }
640
+ const isSameMonorepoVersion = depVersion === storedVersion;
641
+ 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}\`.`, {
643
+ name: dependencyName,
644
+ consumedBy: pkgName
645
+ });
646
+ }
647
+ }
648
+ };
649
+ return (pkg)=>{
650
+ lint(pkg, "development");
651
+ lint(pkg, "production");
652
+ };
653
+ };
654
+ const createPackageError = (message, context)=>{
655
+ return createError("stack check", !context ? message : `\`${context.name}\` consumed by \`${context.consumedBy}\` doesn't conform to our package policy.\n${message}`);
656
+ };
657
+ function assertVersion(version, { name, consumedBy }) {
658
+ assert(version, ()=>createPackageError(`\`${name}\` must have a valid version specified (current version equals to \`${String(version)}\`).`, {
659
+ name,
660
+ consumedBy
661
+ }));
662
+ }
663
+
664
+ const checkLinter = eslint({
665
+ isFixMode: false
666
+ });
667
+
668
+ const checkCommit = async ()=>{
669
+ try {
670
+ return await helpers.exec('commitlint --extends "@commitlint/config-conventional" --edit');
671
+ } catch (error) {
672
+ throw createError("commitlint", error);
673
+ }
674
+ };
675
+
676
+ const ONLY_VALUES = [
566
677
  "commit",
567
- "lint",
568
- "type"
678
+ "linter",
679
+ "packages",
680
+ "types"
569
681
  ];
570
682
  const createCheckCommand = (program)=>{
571
683
  program.command({
@@ -574,10 +686,10 @@ const createCheckCommand = (program)=>{
574
686
  }).option({
575
687
  key: "only",
576
688
  name: "only",
577
- description: `Run only one specified task (accepted value: ${onlyValues.join(", ")})`,
689
+ description: `Run only one specified task (accepted value: ${ONLY_VALUES.join(", ")})`,
578
690
  defaultValue: undefined
579
691
  }).task({
580
- label: label("Preparing the project"),
692
+ label: label("Prepare the project"),
581
693
  async handler () {
582
694
  await turbo("build", {
583
695
  hasLiveOutput: false
@@ -587,25 +699,31 @@ const createCheckCommand = (program)=>{
587
699
  return only === "commit"; // No need to build if only commitlint is run
588
700
  }
589
701
  }).task({
590
- label: label("Checking linters"),
702
+ label: label("Check package guidelines"),
703
+ async handler () {
704
+ await checkPackages();
705
+ },
706
+ skip: ifOnlyDefinedAndNotEqualTo("packages")
707
+ }).task({
708
+ label: label("Check linter rules"),
591
709
  async handler (_, argv) {
592
710
  const filenames = argv.operands;
593
- await checkLints(filenames);
711
+ await checkLinter(filenames);
594
712
  },
595
- skip: ifOnlyDefinedAndNotEqualTo("lint")
713
+ skip: ifOnlyDefinedAndNotEqualTo("linter")
596
714
  }).task({
597
- label: label("Checking types"),
715
+ label: label("Check types"),
598
716
  async handler () {
599
717
  await checkTypes();
600
718
  },
601
719
  skip (context, argv) {
602
- return ifOnlyDefinedAndNotEqualTo("type")(context) || !hasDependency("typescript") || /**
720
+ return ifOnlyDefinedAndNotEqualTo("types")(context) || !hasDependency("typescript") || /**
603
721
  * For now, skip type-checking if some files are passed down.
604
722
  * @see https://github.com/microsoft/TypeScript/issues/27379
605
723
  */ argv.operands.length > 0;
606
724
  }
607
725
  }).task({
608
- label: label("Checking commit"),
726
+ label: label("Check commit"),
609
727
  async handler () {
610
728
  await checkCommit();
611
729
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adbayb/stack",
3
- "version": "0.0.0-next-912a585",
3
+ "version": "0.0.0-next-ece28ab",
4
4
  "description": "My opinionated JavaScript-based toolchain",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -42,7 +42,6 @@
42
42
  },
43
43
  "license": "MIT",
44
44
  "devDependencies": {
45
- "@types/eslint-config-prettier": "6.11.3",
46
45
  "@types/node": "22.8.6",
47
46
  "quickbundle": "2.6.0"
48
47
  },