@yunarch/config-web 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,17 +14,14 @@
14
14
  - [📖 Why use this?](#-why-use-this)
15
15
  - [📦 What’s included?](#-whats-included)
16
16
  - [⚙️ Installation](#️-installation)
17
- - [⚠️ Caveats](#️-caveats)
18
- - [Code Formatting](#code-formatting)
19
- - [Linting](#linting)
20
17
  - [Prettier](#prettier)
18
+ - [Oxfmt](#oxfmt)
21
19
  - [ESlint](#eslint)
22
20
  - [Override configuration](#override-configuration)
23
21
  - [Typescript Type aware rules](#typescript-type-aware-rules)
24
22
  - [Oxlint](#oxlint)
25
- - [Enabling ESLint and Oxlint Simultaneously](#enabling-eslint-and-oxlint-simultaneously)
26
- - [Biome](#biome)
27
- - [Enabling ESLint and Biome Simultaneously](#enabling-eslint-and-biome-simultaneously)
23
+ - [Typescript Type aware rules](#typescript-type-aware-rules-1)
24
+ - [Running Oxlint and ESLint together](#running-oxlint-and-eslint-together)
28
25
  - [Typescript](#typescript)
29
26
  - [ts-reset](#ts-reset)
30
27
  - [Utilities](#utilities)
@@ -33,7 +30,7 @@
33
30
 
34
31
  ## 📖 Why use this?
35
32
 
36
- Even experienced developers can waste valuable time configuring tools from scratch. Instead of manually setting up linters, formatters, and TypeScript settings, this package provides a ready-to-use configuration that is both easy to implement and extensible. It helps maintain clean, consistent code without the overhead of ongoing tools configuration maintenance allowing users to choose between traditional options (e.g., Prettier, ESlint) and more performant alternatives (e.g., Biome, Oxlint).
33
+ Even experienced developers can waste valuable time configuring tools from scratch. Instead of manually setting up linters, formatters, and TypeScript settings, this package provides a ready-to-use configuration that is both easy to implement and extensible. It helps maintain clean, consistent code without the overhead of ongoing tools configuration maintenance allowing users to choose between traditional options (e.g., Prettier, ESlint) and more performant alternatives (e.g., Oxfmt, Oxlint).
37
34
 
38
35
  > [!IMPORTANT]
39
36
  > Please keep in mind that this is still **a personal config** with a lot of opinions. Changes might not always work for everyone and every use case.
@@ -44,7 +41,7 @@ Even experienced developers can waste valuable time configuring tools from scrat
44
41
 
45
42
  This package provides ready-to-use configurations for:
46
43
 
47
- - **Shared configs for Code Style & Linting:** Pre-configured yet extensible setups for Prettier, ESLint, Oxlint, and Biome.
44
+ - **Shared configs for Code Style & Linting:** Pre-configured yet extensible setups for Prettier and ESLint, offering high-performance alternatives via Oxfmt and Oxlint.
48
45
  - **TypeScript:** Best-practice default config and utilities.
49
46
  - **CLI Tools:** Useful command-line tools for streamlining workflows
50
47
 
@@ -65,39 +62,19 @@ npm install ---save-dev @yunarch/config-web
65
62
  // To use Prettier
66
63
  npm install --save-dev prettier
67
64
 
68
- // To use Biome
69
- npm install --save-dev @biomejs/biome
70
-
71
65
  // To use eslint
72
66
  npm install --save-dev eslint
73
67
 
68
+ // To use Oxfmt
69
+ npm install --save-dev oxfmt
70
+
74
71
  // To use Oxlint
75
- npm install --save-dev oxlint
72
+ npm install --save-dev oxlint oxlint-tsgolint
76
73
  ```
77
74
 
78
- ## ⚠️ Caveats
79
-
80
- ### Code Formatting
81
-
82
- While both `Prettier` and `Biome` are configured to format code in the same way, there are some [differences](https://biomejs.dev/formatter/differences-with-prettier/) between the two.
83
-
84
- Language support for [Prettier](https://prettier.io/docs/) and [Biome](https://biomejs.dev/internals/language-support/).
85
-
86
- > [!WARNING]
87
- > While it's technically possible to use both tools in the same project, **each file should be formatted by only one formatter** to avoid conflicts. This repository uses this hybrid setup, but for simplicity and consistency, **we recommend choosing a single formatter** for your own project.
88
-
89
- ### Linting
90
-
91
- We offer a strict yet configurable `ESLint` setup with autocomplete support. Additionally, since the `ESLint` ecosystem is extensive but can sometimes be slow, this configuration allows leveraging `Oxlint` or `Biome` for certain rules, boosting speed without compromising flexibility.
92
-
93
- For small projects, `Oxlint` or `Biome` should be sufficient. However, for big projects or if you want to maintain consistent code style across multiple projects. I recommend `ESlint` and if need it a performance boost then combining `ESLint` with either `Oxlint` or `Biome`.
94
-
95
- > [!NOTE]
96
- > Avoid using all three tools (`ESLint`, `Oxlint`, and `Biome`) simultaneously, as this may lead to conflicts between `Oxlint` and `Biome` that you'll need to manually resolve.
97
-
98
75
  ## Prettier
99
76
 
100
- The easiest way to use the prettier configuration as-is is to set it directly in your `package.json`:
77
+ The easiest way to use the `prettier` configuration as-is is to set it directly in your `package.json`:
101
78
 
102
79
  ```json
103
80
  "prettier": "@yunarch/config-web/prettier"
@@ -116,14 +93,34 @@ export default {
116
93
  ```
117
94
 
118
95
  > [!TIP]
119
- > Add a `.prettierignore` file to ignore certain files and folder completly or use the CLI option [--ignore-path](https://prettier.io/docs/cli#--ignore-path) to indicate a path to a file containing patterns that describe files to ignore. By default, Prettier looks for `./.gitignore` and `./.prettierignore`.
96
+ > Add a `.prettierignore` file to ignore certain files and folder completly or use the CLI option [--ignore-path](https://prettier.io/docs/cli#--ignore-path) to indicate a path to a file containing patterns that describe files to ignore.
97
+ >
98
+ > By default, Prettier looks for `./.gitignore` and `./.prettierignore`.
99
+
100
+ ## Oxfmt
101
+
102
+ To use `Oxfmt`, create a `.oxfmtrc.json` [configuration file](https://oxc.rs/docs/guide/usage/formatter/config.html) and extend the shared preset:
103
+
104
+ ```jsonc
105
+ {
106
+ "$schema": "./node_modules/oxfmt/configuration_schema.json",
107
+ "extends": ["@yunarch/config-web/oxfmt"],
108
+ }
109
+ ```
110
+
111
+ > [!NOTE]
112
+ > `Oxfmt` uses `ignorePatterns` in its configuration file instead of `.prettierignore` file but for compatibility, `.prettierignore` file is also supported.
113
+ >
114
+ > See [Oxfmt ignore files](https://oxc.rs/docs/guide/usage/formatter/ignore-files.html) for details.
115
+
116
+ > [!CAUTION]
117
+ > Currently, `Oxfmt` does not extends configuration:
118
+ > https://github.com/oxc-project/oxc/issues/16394
120
119
 
121
120
  ## ESlint
122
121
 
123
122
  To use the ESlint linter, create a [ESlint configuration file](https://eslint.org/docs/latest/use/configure/configuration-files):
124
123
 
125
- Typically, you only need to use the `config` configuration as it is:
126
-
127
124
  ```js
128
125
  // eslint.config.js
129
126
  import { config } from '@yunarch/config-web/eslint';
@@ -231,7 +228,7 @@ To use the oxlint linter, create a `.oxlintrc.json` [configuration file](https:/
231
228
 
232
229
  ```jsonc
233
230
  {
234
- "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
231
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
235
232
  "extends": ["@yunarch/config-web/oxlint"],
236
233
  "categories": { "correctness": "error", "perf": "error" },
237
234
  "rules": {
@@ -250,58 +247,52 @@ To use the oxlint linter, create a `.oxlintrc.json` [configuration file](https:/
250
247
  > Currently, `Oxlint` does not resolve configuration file paths automatically. To extend a config, you must explicitly provide the full path, like so:
251
248
  > `"extends": ["./node_modules/@yunarch/config-web/dist/config.oxlint.json"]`
252
249
 
253
- ### Enabling ESLint and Oxlint Simultaneously
254
-
255
- If you want to offload certain rules to Oxlint, which will reduce linting time, you can configure `ESlint` as follows:
250
+ ### Typescript Type aware rules
256
251
 
257
- ```js
258
- import { config } from '@yunarch/config-web/eslint';
252
+ `Oxlint` Type-aware linting requires an additional dependency:
259
253
 
260
- export default config({
261
- oxlint: {
262
- oxlintConfigPath: './.oxlintrc.json',
263
- },
264
- });
254
+ ```
255
+ npm install --save-dev oxlint-tsgolint
265
256
  ```
266
257
 
267
- ## Biome
268
-
269
- To use the Biome, create a `biome.json` [configuration file](https://biomejs.dev/reference/configuration/):
258
+ To run `Oxlint` with type-aware linting, you must pass the `--type-aware` flag:
270
259
 
271
- ```jsonc
272
- {
273
- "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
274
- "extends": ["@yunarch/config-web/biome"],
275
- "linter": {
276
- "enabled": true,
277
- },
278
- }
260
+ ```
261
+ oxlint --type-aware
279
262
  ```
280
263
 
281
- That’s it! Biome will now use the shared config to lint and format your code. However:
264
+ > [!TIP]
265
+ > In editor and LSP-based integrations like VS Code, type-aware linting can be enabled by setting the `typeAware` option to `true`, see the [Editors](https://oxc.rs/docs/guide/usage/linter/editors.html) page for more information.
282
266
 
283
- - If you prefer not to use Biome as a linter, simply remove the `"linter"` section. Linting is disabled by default unless explicitly enabled.
284
- - If you prefer to use Biome only as a linter, disable the formatter by: `"formatter": { "enabled": false }`.
267
+ > [!WARNING]
268
+ > Type-aware linting is powered by typescript-go so TypeScript 7.0+ is required.
285
269
 
286
- > [!TIP]
287
- > Enable [vcs.useIgnoreFile](https://biomejs.dev/guides/integrate-in-vcs/#use-the-ignore-file), to allow Biome to ignore all the files and directories listed in your VCS ignore file.
270
+ ### Running Oxlint and ESLint together
271
+
272
+ If not all required rules are available in `Oxlint`, you can run `Oxlint` and `ESLint` side by side.
288
273
 
289
- ### Enabling ESLint and Biome Simultaneously
274
+ Because `Oxlint` is significantly faster than `ESLint`, it is recommended to run `Oxlint` first to catch errors early, then fall back to `ESLint` only when necessary:
290
275
 
291
- If you want to offload certain rules to `Biome`, which will reduce linting time, you can configure `ESlint` as follows:
276
+ ```sh
277
+ oxlint && eslint
278
+ ```
279
+
280
+ To offload rules to `Oxlint` and significantly reduce overall linting time, you can configure `ESlint` as follows:
292
281
 
293
282
  ```js
294
283
  import { config } from '@yunarch/config-web/eslint';
295
284
 
296
285
  export default config({
297
- biome: {
298
- biomeConfigPath: './biome.json',
286
+ oxlint: {
287
+ oxlintConfigPath: './.oxlintrc.json',
299
288
  },
300
289
  });
301
290
  ```
302
291
 
303
- > [!IMPORTANT]
304
- > This feature is under development and will be available in a future release.
292
+ This reduces duplicate diagnostics, can help cut down your linting time considerably, and allows `ESLint` to focus only on rules that `Oxlint` does not yet support.
293
+
294
+ > [!NOTE]
295
+ > Once remaining important rules have been added in `Oxlint` you should consider moving fully to `Oxlint` if you want to use that one to simplify your setup and reduce the number of dependencies for your project.
305
296
 
306
297
  ## Typescript
307
298
 
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
- import{c as d}from"../chunk-LXZC4N54.js";import{styleText as p}from"util";import{styleText as l}from"util";var m=["blue","green","yellow","grey","white","cyan"],g=o=>`${((Bun.nanoseconds()-o)/1e6/1e3).toFixed(2)}s`;function b({start:o,tasks:e,failedTasks:n}){let c=e-n,a=g(o),t=n>0?l("red",`${n} failed`):"",i=c>0?l("green",`${c} successful`):"";console.log(""),console.log(l(["white","bold"],"Tasks: "),`${t}${t&&i?"|":""}${i}`,l("gray",`-- ${e} total`)),console.log(l(["white","bold"],"Time: "),l("gray",`${a}
2
+ import{c as d}from"../chunk-66TS4QAV.js";import{styleText as p}from"util";import{styleText as l}from"util";var m=["blue","green","yellow","gray","white","cyan"],g=o=>`${((Bun.nanoseconds()-o)/1e6/1e3).toFixed(2)}s`;function b({start:o,tasks:e,failedTasks:n}){let c=e-n,a=g(o),t=n>0?l("red",`${n} failed`):"",i=c>0?l("green",`${c} successful`):"";console.log(""),console.log(l(["white","bold"],"Tasks: "),`${t}${t&&i?"|":""}${i}`,l("gray",`-- ${e} total`)),console.log(l(["white","bold"],"Time: "),l("gray",`${a}
3
3
  `))}function f({index:o,script:e,continueOnError:n,reportTime:c}){let a=m[o%m.length],t=Bun.nanoseconds(),i=Bun.spawn(["bun","run",e],{stdout:"pipe",stderr:"pipe",env:{...Bun.env,FORCE_COLOR:"1"},onExit(r,s){s===1&&!n&&process.exit(1)}});return i.stdout.pipeTo(new WritableStream({write(r){let s=new TextDecoder().decode(r).split(`
4
4
  `);for(let u of s)console.log(l([a,"bold"],`${e}:`),u)}})),i.stderr.pipeTo(new WritableStream({write(r){let s=new TextDecoder().decode(r).split(`
5
5
  `);for(let u of s)console.log(l([a,"bold"],`${e}:`),u)}})),i.exited.then(r=>{r===0&&c&&console.log(l([a,"bold"],`${e}:`),l(["gray"],"Finished in"),l(["white","bold"],g(t)))}),i}async function x(o,e){let{continueOnError:n,reportTime:c}=e,a=Bun.nanoseconds(),t=o.map((s,u)=>f({index:u,script:s,continueOnError:n,reportTime:c})),r=(await Promise.allSettled(t.map(s=>s.exited))).filter(s=>s.status==="rejected"||s.value!==0).length;return b({start:a,tasks:o.length,failedTasks:r}),r>0?1:0}async function T(o,e){let{continueOnError:n,reportTime:c}=e,a=Bun.nanoseconds(),t=0;for(let[i,r]of o.entries())await f({index:i,script:r,continueOnError:n,reportTime:c}).exited!==0&&t++;return b({start:a,tasks:o.length,failedTasks:t}),t>0?1:0}d().name("bun-run-all").description("Run given package scripts in parallel or sequential by using bun.").argument("<scripts...>","A list of package scripts' names.").option("-c, --continue-on-error","Continue executing other/subsequent tasks even if a task threw an error").option("-p, --parallel","Run a group of tasks in parallel.").option("-s, --sequential","Run a group of tasks sequentially.").option("-t, --time","Report execution time for each task.").addHelpText("after",`
@@ -0,0 +1 @@
1
+ import{execFile as c}from"child_process";import{promisify as p,styleText as t,types as l}from"util";import{Command as u}from"commander";import y from"ora";var O=p(c);async function h(r){let{command:e,name:s,options:a}=r,n=y(s);n.spinner=a?.spinner??"aesthetic";let m=Date.now();n.start();try{let o=l.isPromise(e)?await e:await e(n);return n.succeed(a?.showTime?`${t("dim",`${Date.now()-m}ms`)} ${s}`:void 0),await new Promise(i=>{setTimeout(i,0)}),o}catch(o){let i=o;throw n.fail(t("red",i.stderr??i.message??"")),o}}function P(){let r=new u;return r.configureHelp({styleTitle:e=>t("bold",e),styleCommandText:e=>t("cyan",e),styleCommandDescription:e=>t("magenta",e),styleDescriptionText:e=>t("italic",e),styleOptionText:e=>t("green",e),styleArgumentText:e=>t("yellow",e),styleSubcommandText:e=>t("blue",e)}).configureOutput({outputError:(e,s)=>{s(t("red",e))}}),r}export{O as a,h as b,P as c};
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{c as M}from"../chunk-LXZC4N54.js";import{existsSync as N,statSync as F}from"fs";import v from"path";import{styleText as e}from"util";import{styleText as m}from"util";function H(f){let c=new Map;for(let o of f)c.has(o.service.name)||c.set(o.service.name,{service:o.service,handlers:[]}),c.get(o.service.name)?.handlers.push(o);if(c.size===0){console.log(m("green","\u2714 No missing handlers found"));return}for(let{service:o,handlers:i}of c.values()){console.log(`${m("underline",o.name)}${m("gray",` (${o.path})`)}`);for(let[a,s]of i.entries()){let t=a===i.length-1;console.log(` ${t?"\u2514\u2500":"\u251C\u2500"} ${m("cyan",s.service.toHandleHttpMethod)} ${m("yellow",s.service.toHandleUrl)}`),console.log(` ${t?" ":"\u2502"} \u251C\u2500 ${m("gray","Used in:")}`);for(let[p,l]of s.usedIn.entries()){let r=p===s.usedIn.length-1;console.log(` ${t?" ":"\u2502"} ${r?"\u2502 \u2514\u2500":"\u2502 \u251C\u2500"} ${l}`)}console.log(` ${t?" ":"\u2502"} \u2514\u2500 ${m("green","Suggested handler:")}`),console.log(` ${t?" ":"\u2502"} ${m("dim","\u2192")} ${s.suggestedPath}`),a<i.length-1&&console.log(` ${t?" ":"\u2502"}`)}console.log("")}console.log(m("red",`\u2718 ${f.length} missing ${f.length===1?"handler":"handlers"}`))}import{pathToFileURL as C}from"url";async function E({mswSetupFilePath:f,mswSetupConst:c}){let o=new Map,i=await import(C(f).href);if(!Object.hasOwn(i,c))throw new Error("MSW setup constant not found in the setup file");let a=i[c];if(!a||typeof a.listHandlers!="function")throw new Error("MSW setup constant does not have a listHandlers() method");let s=a.listHandlers();for(let t of s){if(!("info"in t)||!t.info?.path||!t.info.method)continue;let p=t.info.path,l=t.info.method.toUpperCase(),r=p.replaceAll(/:(?<temp1>[^/]+)/g,"{$1}"),d=`${l}:${r}`;o.set(d,{path:p,httpMethod:l,url:r})}return o}import{existsSync as W,readFileSync as U}from"fs";import I from"path";import _ from"fast-glob";import n from"typescript";async function T(f){let c=[],o=I.join(f,"services");if(!W(o))throw new Error(`Services directory not found: ${o}`);let i=await _("**/*Service.ts",{cwd:o,absolute:!0,ignore:["**/node_modules/**"]});for(let a of i){let s=I.basename(a,".ts"),t=U(a,"utf8"),p=n.createSourceFile(a,t,n.ScriptTarget.Latest,!0);n.forEachChild(p,l=>{if(n.isClassDeclaration(l)&&l.name?.text===s){for(let r of l.members)if(n.isMethodDeclaration(r)&&n.isIdentifier(r.name)&&r.modifiers&&r.modifiers.some(d=>d.kind===n.SyntaxKind.PublicKeyword)&&r.modifiers.some(d=>d.kind===n.SyntaxKind.StaticKeyword)){let d=r.name.text,y,h,x=u=>{if(n.isReturnStatement(u)&&u.expression&&n.isCallExpression(u.expression)&&n.isIdentifier(u.expression.expression)&&u.expression.expression.text==="__request"){let S=u.expression.arguments[1];if(n.isObjectLiteralExpression(S))for(let g of S.properties)n.isPropertyAssignment(g)&&n.isIdentifier(g.name)&&g.name.text==="url"&&n.isStringLiteral(g.initializer)?y=g.initializer.text:n.isPropertyAssignment(g)&&n.isIdentifier(g.name)&&g.name.text==="method"&&n.isStringLiteral(g.initializer)&&(h=g.initializer.text.toUpperCase())}n.forEachChild(u,x)};r.body&&n.forEachChild(r.body,x),y&&h?c.push({path:a,name:s,methodName:d,toHandleUrl:y,toHandleHttpMethod:h}):(y||console.warn(`No URL found for ${d} request in service ${s} (${a})`),h||console.warn(`No HTTP method found for ${d} request in service ${s} (${a})`))}}})}return c}async function P({genPath:f,srcPath:c}){let o=await T(f),i=new Map;for(let s of o)i.has(s.name)||i.set(s.name,new Map),i.get(s.name)?.set(s.methodName,{serviceInfo:s,files:new Set});let a=await _("**/*.{ts,tsx}",{cwd:c,absolute:!0,ignore:["**/node_modules/**","**/__tests__/**"]});for(let s of a)try{let t=U(s,"utf8"),p=n.createSourceFile(s,t,n.ScriptTarget.Latest,!0),l=r=>{if(n.isCallExpression(r)&&n.isPropertyAccessExpression(r.expression)&&n.isIdentifier(r.expression.expression)&&r.expression.expression.text.endsWith("Service")){let d=r.expression.expression.text,y=r.expression.name.text,h=i.get(d)?.get(y);if(!h)return;h.files.add(s)}n.forEachChild(r,l)};l(p)}catch(t){throw t instanceof Error?new Error(`Error parsing ${s}: ${t.message}`):new Error(`Error parsing ${s}: Unknown error`)}for(let[s,t]of i.entries()){for(let[p,l]of t.entries())l.files.size===0&&t.delete(p);t.size===0&&i.delete(s)}return i}import L from"path";function b(f,c,o){let i=[];for(let[a,s]of f.entries())for(let[t,p]of s.entries()){let{serviceInfo:l}=p,r=l.toHandleHttpMethod,d=l.toHandleUrl;!c.has(`${r}:${d}`)&&!c.has(`${r}:*${d}`)&&i.push({type:"missing_handler",service:l,usedIn:[...p.files],suggestedPath:L.join(o,`handlers/services/${a}/${t}.ts`)})}return i}M().name("openapi-sync-lint-msw-handlers").description("Lint MSW handlers against OpenAPI generated services from `openapi-sync`.\nIt checks for missing handlers based on generated services and your MSW setup.").requiredOption("--gen <path>","The output folder from `openapi-sync` script. Where the generated models and openapi schema and type definitions are saved.").requiredOption("--msw-setup-file <path>","Path to the MSW setup file (file that configures MSW setupServer or setupWorker).").requiredOption("--msw-setup-const <const>","Name of the constant that holds the MSW setup (e.g., server or worker).").addHelpText("after",`
2
+ import{c as M}from"../chunk-66TS4QAV.js";import{existsSync as N,statSync as F}from"fs";import v from"path";import{styleText as e}from"util";import{styleText as m}from"util";function H(f){let c=new Map;for(let o of f)c.has(o.service.name)||c.set(o.service.name,{service:o.service,handlers:[]}),c.get(o.service.name)?.handlers.push(o);if(c.size===0){console.log(m("green","\u2714 No missing handlers found"));return}for(let{service:o,handlers:i}of c.values()){console.log(`${m("underline",o.name)}${m("gray",` (${o.path})`)}`);for(let[a,s]of i.entries()){let t=a===i.length-1;console.log(` ${t?"\u2514\u2500":"\u251C\u2500"} ${m("cyan",s.service.toHandleHttpMethod)} ${m("yellow",s.service.toHandleUrl)}`),console.log(` ${t?" ":"\u2502"} \u251C\u2500 ${m("gray","Used in:")}`);for(let[p,l]of s.usedIn.entries()){let r=p===s.usedIn.length-1;console.log(` ${t?" ":"\u2502"} ${r?"\u2502 \u2514\u2500":"\u2502 \u251C\u2500"} ${l}`)}console.log(` ${t?" ":"\u2502"} \u2514\u2500 ${m("green","Suggested handler:")}`),console.log(` ${t?" ":"\u2502"} ${m("dim","\u2192")} ${s.suggestedPath}`),a<i.length-1&&console.log(` ${t?" ":"\u2502"}`)}console.log("")}console.log(m("red",`\u2718 ${f.length} missing ${f.length===1?"handler":"handlers"}`))}import{pathToFileURL as C}from"url";async function E({mswSetupFilePath:f,mswSetupConst:c}){let o=new Map,i=await import(C(f).href);if(!Object.hasOwn(i,c))throw new Error("MSW setup constant not found in the setup file");let a=i[c];if(!a||typeof a.listHandlers!="function")throw new Error("MSW setup constant does not have a listHandlers() method");let s=a.listHandlers();for(let t of s){if(!("info"in t)||!t.info?.path||!t.info.method)continue;let p=t.info.path,l=t.info.method.toUpperCase(),r=p.replaceAll(/:(?<temp1>[^/]+)/g,"{$1}"),d=`${l}:${r}`;o.set(d,{path:p,httpMethod:l,url:r})}return o}import{existsSync as W,readFileSync as U}from"fs";import I from"path";import _ from"fast-glob";import n from"typescript";async function T(f){let c=[],o=I.join(f,"services");if(!W(o))throw new Error(`Services directory not found: ${o}`);let i=await _("**/*Service.ts",{cwd:o,absolute:!0,ignore:["**/node_modules/**"]});for(let a of i){let s=I.basename(a,".ts"),t=U(a,"utf8"),p=n.createSourceFile(a,t,n.ScriptTarget.Latest,!0);n.forEachChild(p,l=>{if(n.isClassDeclaration(l)&&l.name?.text===s){for(let r of l.members)if(n.isMethodDeclaration(r)&&n.isIdentifier(r.name)&&r.modifiers&&r.modifiers.some(d=>d.kind===n.SyntaxKind.PublicKeyword)&&r.modifiers.some(d=>d.kind===n.SyntaxKind.StaticKeyword)){let d=r.name.text,y,h,x=u=>{if(n.isReturnStatement(u)&&u.expression&&n.isCallExpression(u.expression)&&n.isIdentifier(u.expression.expression)&&u.expression.expression.text==="__request"){let S=u.expression.arguments[1];if(n.isObjectLiteralExpression(S))for(let g of S.properties)n.isPropertyAssignment(g)&&n.isIdentifier(g.name)&&g.name.text==="url"&&n.isStringLiteral(g.initializer)?y=g.initializer.text:n.isPropertyAssignment(g)&&n.isIdentifier(g.name)&&g.name.text==="method"&&n.isStringLiteral(g.initializer)&&(h=g.initializer.text.toUpperCase())}n.forEachChild(u,x)};r.body&&n.forEachChild(r.body,x),y&&h?c.push({path:a,name:s,methodName:d,toHandleUrl:y,toHandleHttpMethod:h}):(y||console.warn(`No URL found for ${d} request in service ${s} (${a})`),h||console.warn(`No HTTP method found for ${d} request in service ${s} (${a})`))}}})}return c}async function P({genPath:f,srcPath:c}){let o=await T(f),i=new Map;for(let s of o)i.has(s.name)||i.set(s.name,new Map),i.get(s.name)?.set(s.methodName,{serviceInfo:s,files:new Set});let a=await _("**/*.{ts,tsx}",{cwd:c,absolute:!0,ignore:["**/node_modules/**","**/__tests__/**"]});for(let s of a)try{let t=U(s,"utf8"),p=n.createSourceFile(s,t,n.ScriptTarget.Latest,!0),l=r=>{if(n.isCallExpression(r)&&n.isPropertyAccessExpression(r.expression)&&n.isIdentifier(r.expression.expression)&&r.expression.expression.text.endsWith("Service")){let d=r.expression.expression.text,y=r.expression.name.text,h=i.get(d)?.get(y);if(!h)return;h.files.add(s)}n.forEachChild(r,l)};l(p)}catch(t){throw t instanceof Error?new Error(`Error parsing ${s}: ${t.message}`):new Error(`Error parsing ${s}: Unknown error`)}for(let[s,t]of i.entries()){for(let[p,l]of t.entries())l.files.size===0&&t.delete(p);t.size===0&&i.delete(s)}return i}import L from"path";function b(f,c,o){let i=[];for(let[a,s]of f.entries())for(let[t,p]of s.entries()){let{serviceInfo:l}=p,r=l.toHandleHttpMethod,d=l.toHandleUrl;!c.has(`${r}:${d}`)&&!c.has(`${r}:*${d}`)&&i.push({type:"missing_handler",service:l,usedIn:[...p.files],suggestedPath:L.join(o,`handlers/services/${a}/${t}.ts`)})}return i}M().name("openapi-sync-lint-msw-handlers").description("Lint MSW handlers against OpenAPI generated services from `openapi-sync`.\nIt checks for missing handlers based on generated services and your MSW setup.").requiredOption("--gen <path>","The output folder from `openapi-sync` script. Where the generated models and openapi schema and type definitions are saved.").requiredOption("--msw-setup-file <path>","Path to the MSW setup file (file that configures MSW setupServer or setupWorker).").requiredOption("--msw-setup-const <const>","Name of the constant that holds the MSW setup (e.g., server or worker).").addHelpText("after",`
3
3
  Example usage:
4
4
  ${e("dim","$")} ${e("cyan","openapi-sync-lint-msw-handlers")} ${e("green","--gen")} ${e("yellow","./src/api/gen")} ${e("green","--msw-setup-file")} ${e("yellow","./src/api/__tests__/node.js")} ${e("green","--msw-setup-const")} ${e("yellow","server")}
5
5
 
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{a as o,b as a,c as w}from"../chunk-LXZC4N54.js";import{existsSync as y}from"fs";import{mkdir as b,readFile as k,writeFile as O}from"fs/promises";import d from"path";import{styleText as n}from"util";import $ from"@inquirer/confirm";async function P(e,t){await a({name:"Generating models",command:async()=>{await o("npx",["openapi-typescript-codegen","--input",e,"--output",t,"--client","fetch"],{shell:!0})}})}import{writeFile as B}from"fs/promises";var x=`
2
+ import{a as o,b as a,c as w}from"../chunk-66TS4QAV.js";import{existsSync as y}from"fs";import{mkdir as b,readFile as k,writeFile as O}from"fs/promises";import d from"path";import{styleText as n}from"util";import $ from"@inquirer/confirm";async function P(e,t){await a({name:"Generating models",command:async()=>{await o("npx",["openapi-typescript-codegen","--input",e,"--output",t,"--client","fetch"],{shell:!0})}})}import{writeFile as B}from"fs/promises";var x=`
3
3
  import {
4
4
  http as mswHttp,
5
5
  type DefaultBodyType,
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{c as a}from"../chunk-LXZC4N54.js";import{execSync as u}from"child_process";import{styleText as t}from"util";import{execSync as p}from"child_process";import i from"@inquirer/select";async function c(){let n=p("npx turbo ls",{encoding:"utf8",stdio:"pipe"}).split(`
2
+ import{c as a}from"../chunk-66TS4QAV.js";import{execSync as u}from"child_process";import{styleText as t}from"util";import{execSync as p}from"child_process";import i from"@inquirer/select";async function c(){let n=p("npx turbo ls",{encoding:"utf8",stdio:"pipe"}).split(`
3
3
  `).slice(1).map(e=>e.trim()).filter(Boolean).map(e=>e.split(" ")[0]);return await i({message:"Select a package to run the script:",choices:n.map(e=>({name:e,value:e}))})}async function s(){return await i({message:"Select a mode to load different env files:",choices:[{name:"development",value:"development"},{name:"staging",value:"staging"},{name:"production",value:"production"}]})}a().name("turbo-select").description(`A CLI tool to filter and select a single package from the Turborepo package list and run a script command.
4
4
  Additionally, allow to prompt environment mode (development, staging, production), for example, when using Vite.`).requiredOption("--run <script>","The package script command to execute (e.g., --run=dev).").option("--select-env","An environment mode (development, staging, production) If using for example vite.").addHelpText("after",`
5
5
  Example usage: