dslinter 0.1.13 → 0.2.2

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.
Files changed (181) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +50 -29
  3. package/bin/dslinter.mjs +26 -5
  4. package/bin/lib/config-hide-component.mjs +44 -0
  5. package/bin/lib/config-hide-component.test.mjs +33 -0
  6. package/bin/lib/constants.mjs +20 -0
  7. package/bin/lib/dev-banner.mjs +16 -51
  8. package/bin/lib/dev-banner.test.mjs +20 -18
  9. package/bin/lib/enrich-playgrounds-from-ts.mjs +201 -0
  10. package/bin/lib/enrich-playgrounds-from-ts.test.mjs +74 -0
  11. package/bin/lib/enrich-report-cli.mjs +14 -0
  12. package/bin/lib/env.mjs +20 -0
  13. package/bin/lib/infer-prop-types-from-ts.mjs +381 -0
  14. package/bin/lib/infer-prop-types-from-ts.test.mjs +174 -0
  15. package/bin/lib/parse-args.mjs +13 -1
  16. package/bin/lib/parse-args.test.mjs +7 -1
  17. package/bin/lib/paths.mjs +8 -0
  18. package/bin/lib/project-root.mjs +72 -10
  19. package/bin/lib/project-root.test.mjs +32 -1
  20. package/bin/lib/prompt.mjs +31 -0
  21. package/bin/lib/resolve-project.mjs +78 -0
  22. package/bin/lib/resolve-project.test.mjs +74 -0
  23. package/bin/lib/run-scanner.mjs +40 -6
  24. package/bin/lib/scaffold-config.mjs +128 -9
  25. package/bin/lib/scaffold-config.test.mjs +24 -2
  26. package/bin/lib/scan-host.mjs +44 -0
  27. package/bin/lib/scan-host.test.mjs +41 -0
  28. package/bin/lib/setup-readiness.mjs +153 -0
  29. package/bin/lib/setup-readiness.test.mjs +32 -0
  30. package/bin/modes/build.mjs +31 -6
  31. package/bin/modes/dev.mjs +55 -21
  32. package/bin/modes/init.mjs +3 -22
  33. package/bin/modes/init.test.mjs +1 -1
  34. package/bin/modes/mcp.mjs +49 -0
  35. package/bin/modes/report.mjs +29 -4
  36. package/bin/modes/watch.mjs +85 -0
  37. package/dashboard-dist/assets/DashboardLayoutAuto-Bja3BuZZ.css +1 -0
  38. package/dashboard-dist/assets/DashboardLayoutAuto-h0gP_iKd.js +1 -0
  39. package/dashboard-dist/assets/axe-DDaE9JTN.js +20 -0
  40. package/dashboard-dist/assets/index-B9sZ6wHm.css +1 -0
  41. package/dashboard-dist/assets/index-DIDBt5ed.js +218 -0
  42. package/dashboard-dist/index.html +2 -2
  43. package/index.cjs +53 -52
  44. package/index.d.ts +3 -0
  45. package/package.json +18 -12
  46. package/shared/env.ts +15 -0
  47. package/shared/paths.ts +8 -0
  48. package/shared/reportPath.test.ts +19 -0
  49. package/shared/reportPath.ts +12 -0
  50. package/shared/servePort.ts +16 -0
  51. package/src/components/ComponentInspectPane.tsx +67 -19
  52. package/src/components/ComponentPlaygroundPane.tsx +262 -113
  53. package/src/components/DashboardCommandPalette.tsx +6 -11
  54. package/src/components/GovernancePane.tsx +2 -2
  55. package/src/components/HideFromCatalogButton.tsx +44 -0
  56. package/src/components/OpenInEditorButton.tsx +36 -0
  57. package/src/components/PlaygroundA11yAndCode.tsx +53 -53
  58. package/src/components/PlaygroundAppThemeWrapper.tsx +82 -0
  59. package/src/components/PlaygroundControls.tsx +5 -11
  60. package/src/components/PlaygroundPreviewErrorBoundary.tsx +54 -0
  61. package/src/components/PlaygroundUsageCode.tsx +6 -4
  62. package/src/components/PlaygroundVariantMatrix.tsx +101 -34
  63. package/src/components/Section.tsx +5 -2
  64. package/src/components/Sidebar.tsx +131 -46
  65. package/src/components/TruncatedPath.tsx +44 -0
  66. package/src/components/controlApiTable.test.ts +29 -0
  67. package/src/components/controlApiTable.ts +3 -0
  68. package/src/components/playgroundUsageHighlight.ts +14 -3
  69. package/src/components/ui/badge.tsx +1 -1
  70. package/src/components/ui/table.tsx +2 -2
  71. package/src/dashboard/ComponentCatalog.tsx +16 -23
  72. package/src/dashboard/ComponentUsageDetails.tsx +6 -15
  73. package/src/dashboard/DashboardBody.tsx +0 -35
  74. package/src/dashboard/FindingsList.tsx +65 -55
  75. package/src/dashboard/ScannedTokenWall.tsx +3 -3
  76. package/src/dashboard/aggregate.test.ts +74 -0
  77. package/src/dashboard/aggregate.ts +145 -21
  78. package/src/dashboard/catalogVisibility.test.ts +93 -0
  79. package/src/dashboard/catalogVisibility.ts +108 -0
  80. package/src/dashboard/editorLink.test.ts +57 -0
  81. package/src/dashboard/editorLink.ts +71 -0
  82. package/src/dashboard/paths.test.ts +49 -0
  83. package/src/dashboard/paths.ts +51 -3
  84. package/src/dashboard/updateDslintConfig.ts +22 -0
  85. package/src/dashboard/useWorkspaceReport.ts +21 -17
  86. package/src/index.ts +26 -0
  87. package/src/mcp/agent-context.ts +148 -0
  88. package/src/mcp/agent-query.test.ts +89 -0
  89. package/src/mcp/agent-query.ts +373 -0
  90. package/src/mcp/config.ts +53 -0
  91. package/src/mcp/index.ts +18 -0
  92. package/src/mcp/normalize-paths.ts +65 -0
  93. package/src/mcp/report-cache.ts +212 -0
  94. package/src/mcp/rule-catalog.json +156 -0
  95. package/src/mcp/rule-catalog.ts +33 -0
  96. package/src/mcp/schemas.ts +54 -0
  97. package/src/mcp/server.test.ts +44 -0
  98. package/src/mcp/server.ts +343 -0
  99. package/src/mcp/start.ts +29 -0
  100. package/src/mcp/verify-loop.test.ts +49 -0
  101. package/src/mcp/verify-loop.ts +149 -0
  102. package/src/playground/appPreviewTheme.test.ts +148 -0
  103. package/src/playground/appPreviewTheme.ts +137 -0
  104. package/src/playground/buildCompoundPlaygroundEntries.test.ts +348 -0
  105. package/src/playground/buildCompoundPlaygroundEntries.ts +625 -0
  106. package/src/playground/buildPlaygroundEntriesFromReport.test.ts +420 -6
  107. package/src/playground/buildPlaygroundEntriesFromReport.ts +206 -285
  108. package/src/playground/catalogIdFromPlaygroundExport.test.ts +15 -0
  109. package/src/playground/catalogIdFromPlaygroundExport.ts +8 -0
  110. package/src/playground/collectDefinedPlaygrounds.test.ts +59 -0
  111. package/src/playground/collectDefinedPlaygrounds.ts +68 -0
  112. package/src/playground/controls.ts +177 -0
  113. package/src/playground/createPlaygroundRegistry.ts +1 -1
  114. package/src/playground/definePlayground.tsx +88 -16
  115. package/src/playground/definePlaygroundFromKit.ts +17 -0
  116. package/src/playground/embedGlobKey.ts +8 -0
  117. package/src/playground/enrichKitControls.test.ts +25 -0
  118. package/src/playground/enrichKitControls.ts +197 -0
  119. package/src/playground/expandPlaygroundControls.test.ts +50 -0
  120. package/src/playground/expandPlaygroundControls.ts +97 -0
  121. package/src/playground/inferKitJsx.test.ts +77 -0
  122. package/src/playground/inferKitJsx.ts +165 -0
  123. package/src/playground/inferKitParams.test.ts +41 -0
  124. package/src/playground/inferKitParams.ts +113 -0
  125. package/src/playground/inferPropTypesFromTs.d.mts +47 -0
  126. package/src/playground/inferPropTypesFromTs.mjs +343 -0
  127. package/src/playground/inferPropTypesFromTs.test.ts +227 -0
  128. package/src/playground/inferPropTypesFromTs.ts +17 -0
  129. package/src/playground/mergePlaygroundEntries.test.ts +32 -0
  130. package/src/playground/mergePlaygroundEntries.ts +28 -0
  131. package/src/playground/playgroundJoin.test.ts +79 -19
  132. package/src/playground/playgroundJoin.ts +47 -22
  133. package/src/playground/playgroundModuleExport.test.ts +42 -0
  134. package/src/playground/playgroundModuleExport.ts +22 -0
  135. package/src/playground/playgroundSpecsKey.ts +8 -0
  136. package/src/playground/propCoerce.ts +91 -0
  137. package/src/playground/scanVariantA11y.test.ts +46 -0
  138. package/src/playground/scanVariantA11y.ts +107 -0
  139. package/src/playground/snippet.ts +83 -0
  140. package/src/playground/usePlaygroundFromReport.test.ts +18 -8
  141. package/src/playground/usePlaygroundFromReport.ts +3 -1
  142. package/src/report/a11yForModule.ts +2 -7
  143. package/src/report/a11yScoring.test.ts +24 -0
  144. package/src/report/a11yScoring.ts +17 -0
  145. package/src/report/index.ts +6 -0
  146. package/src/shell/DashboardLayout.tsx +71 -45
  147. package/src/shell/DashboardLayoutAuto.tsx +0 -4
  148. package/src/shell/hashRoute.test.ts +7 -15
  149. package/src/shell/hashRoute.ts +31 -31
  150. package/src/shell/useHashRoute.ts +38 -13
  151. package/src/styles/dashboard-theme.css +18 -7
  152. package/src/types/controls.ts +11 -0
  153. package/src/types/playground.ts +4 -0
  154. package/src/types/report.ts +32 -9
  155. package/templates/playground/buildRegistry.ts +1 -1
  156. package/templates/vite.dslinter.snippet.ts +15 -4
  157. package/vite/collectScanModules.test.ts +91 -3
  158. package/vite/collectScanModules.ts +94 -29
  159. package/vite/consumer.config.mjs +6 -3
  160. package/vite/consumerAlias.test.ts +47 -0
  161. package/vite/consumerAlias.ts +114 -0
  162. package/vite/embedTailwindSources.test.ts +74 -0
  163. package/vite/embedTailwindSources.ts +97 -0
  164. package/vite/loadConsumerAliases.test.ts +131 -0
  165. package/vite/loadConsumerAliases.ts +155 -0
  166. package/vite/openFileInEditor.mjs +196 -0
  167. package/vite/openFileInEditor.test.mjs +87 -0
  168. package/vite/plugin.resolve.test.ts +72 -0
  169. package/vite/plugin.ts +216 -19
  170. package/vite/reportPath.test.ts +19 -0
  171. package/vite/resolveWayfinderImport.ts +56 -0
  172. package/vite/shims/inertia-react.tsx +85 -0
  173. package/vite/shims/wayfinder-actions.ts +33 -0
  174. package/vite/shims/wayfinder-routes.ts +30 -0
  175. package/vite/shims/ziggy-js.ts +12 -0
  176. package/dashboard-dist/assets/DashboardLayoutAuto-Bm7yfyC-.css +0 -1
  177. package/dashboard-dist/assets/DashboardLayoutAuto-DgwO_itB.js +0 -1
  178. package/dashboard-dist/assets/index-Cbv7vXvH.css +0 -1
  179. package/dashboard-dist/assets/index-e20cwqnb.js +0 -206
  180. package/src/components/playgroundUsageTwoslash.ts +0 -69
  181. package/templates/vite.dslint-scan-alias.snippet.ts +0 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,77 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.2.2
4
+
5
+ [compare changes](https://github.com/jrmybtlr/DSLinter/compare/v0.2.1...v0.2.2)
6
+
7
+ ### ✅ Tests
8
+
9
+ - **collectScanModules:** Enhance case sensitivity tests for include_dirs ([32c2065](https://github.com/jrmybtlr/DSLinter/commit/32c2065))
10
+
11
+ ### ❤️ Contributors
12
+
13
+ - Jeremy Butler <jeremy.butler@laravel.com>
14
+
15
+ ## v0.2.1
16
+
17
+ [compare changes](https://github.com/jrmybtlr/DSLinter/compare/v0.2.0...v0.2.1)
18
+
19
+ ### 🩹 Fixes
20
+
21
+ - **ci:** Build NAPI and demo report before publish tests ([fdf497d](https://github.com/jrmybtlr/DSLinter/commit/fdf497d))
22
+ - **report-cache:** Improve report path validation by ensuring report root matches project root ([7510dde](https://github.com/jrmybtlr/DSLinter/commit/7510dde))
23
+
24
+ ### 💅 Refactors
25
+
26
+ - Enhance path resolution for include directories and improve case sensitivity handling ([22c96fb](https://github.com/jrmybtlr/DSLinter/commit/22c96fb))
27
+
28
+ ### ❤️ Contributors
29
+
30
+ - Jeremy Butler <jeremy.butler@laravel.com>
31
+
32
+ ## v0.2.0
33
+
34
+ [compare changes](https://github.com/jrmybtlr/DSLinter/compare/v0.1.13...v0.2.0)
35
+
36
+ ### 🚀 Enhancements
37
+
38
+ - **website:** Switch deploy config from pages to workers ([22d7e35](https://github.com/jrmybtlr/DSLinter/commit/22d7e35))
39
+ - **playground:** Add user info playground component and enhance controls handling ([625f7c2](https://github.com/jrmybtlr/DSLinter/commit/625f7c2))
40
+
41
+ ### 🔥 Performance
42
+
43
+ - Precompute newline_offsets once in analyze_vue_file, pass to merge_template_usages ([a2839b3](https://github.com/jrmybtlr/DSLinter/commit/a2839b3))
44
+
45
+ ### 🩹 Fixes
46
+
47
+ - **dashboard:** Address PR review feedback on publish files and API export ([d3a1f1b](https://github.com/jrmybtlr/DSLinter/commit/d3a1f1b))
48
+ - Import website main stylesheet in entrypoint ([79fa1ec](https://github.com/jrmybtlr/DSLinter/commit/79fa1ec))
49
+ - Remove duplicate typescript devDependency in dashboard package ([03e3dc1](https://github.com/jrmybtlr/DSLinter/commit/03e3dc1))
50
+ - Address review comments - remove duplicate typescript dep, add .d.mts declaration, remove dead code ([cbfd7ab](https://github.com/jrmybtlr/DSLinter/commit/cbfd7ab))
51
+
52
+ ### 💅 Refactors
53
+
54
+ - **rust:** Consolidate usage maps, DRY FileScan ctors, remove scan_and_evaluate, fix scoring bugs, use lazy_regex! ([5033b13](https://github.com/jrmybtlr/DSLinter/commit/5033b13))
55
+ - **ts:** Split playground module, useRef cancellation, Set dedup, useMemo fixes, DashboardLayout, Sidebar, barrel index ([d57a673](https://github.com/jrmybtlr/DSLinter/commit/d57a673))
56
+
57
+ ### 📖 Documentation
58
+
59
+ - Update README config options ([5f2be14](https://github.com/jrmybtlr/DSLinter/commit/5f2be14))
60
+ - Remove trailing spaces in README ([1fc4c1d](https://github.com/jrmybtlr/DSLinter/commit/1fc4c1d))
61
+
62
+ ### 🏡 Chore
63
+
64
+ - **release:** V0.1.13 ([3cd1a20](https://github.com/jrmybtlr/DSLinter/commit/3cd1a20))
65
+ - **dashboard:** Remove dead code and dedupe helpers ([cb41bcd](https://github.com/jrmybtlr/DSLinter/commit/cb41bcd))
66
+ - Begin optimisation pass over src/ and packages/dashboard ([8ec1a34](https://github.com/jrmybtlr/DSLinter/commit/8ec1a34))
67
+ - Plan cloudflare workers website deploy ([affabf5](https://github.com/jrmybtlr/DSLinter/commit/affabf5))
68
+ - Add semver as a dev dependency and update pnpm lockfile ([18afb12](https://github.com/jrmybtlr/DSLinter/commit/18afb12))
69
+
70
+ ### ❤️ Contributors
71
+
72
+ - Jeremy Butler <jeremy.butler@laravel.com>
73
+ - Cursor Agent ([@cursoragent](https://github.com/cursoragent))
74
+
3
75
  ## v0.1.13
4
76
 
5
77
  [compare changes](https://github.com/jrmybtlr/DSLinter/compare/v0.1.12...v0.1.13)
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # dslinter
2
2
 
3
- React UI for the **DSLinter dashboard**: component playground shell, token wall, and governance panels. It expects a **`dslint-report.json`** file produced by the **`dslinter`** CLI (Rust scanner powered by [Oxc](https://oxc.rs) in this repo).
3
+ React UI for the **DSLinter dashboard**: component playground shell, token wall, and governance panels. It expects a **`dslinter-report.json`** file produced by the **`dslinter`** CLI (Rust scanner powered by [Oxc](https://oxc.rs) in this repo).
4
4
 
5
5
  **Previously published as `@dslinter/dashboard`.** Migrate with `npm install dslinter` and replace imports `@dslinter/dashboard` → `dslinter`, and `@dslinter/dashboard/theme.css` → `dslinter/theme.css`.
6
6
 
@@ -24,9 +24,10 @@ The **`dslinter`** command orchestrates the Rust scanner (via **napi-rs**, same
24
24
  | Mode | Flag | Behavior |
25
25
  |------|------|----------|
26
26
  | Dev (default locally) | _(none)_ | `--serve`, watch, write `--output`, start Vite `--mode serve` |
27
- | Report | `--report` | One-shot scan; human stdout or `--json`; `--output` writes JSON |
28
- | Watch | `--watch` | Watch + write JSON only |
29
- | Build | `--build` | One-shot report to `--output`, then `vite build` |
27
+ | Report | `--report` | One-shot scan; human stdout or `--json`; `--output` writes JSON; enriches playground prop kinds/options from TypeScript when `tsconfig.json` is present |
28
+ | Watch | `--watch` | Watch + write JSON only; re-enriches playgrounds after each scan |
29
+ | Build | `--build` | One-shot report to `--output` (with TS enrichment), then `vite build` |
30
+ | MCP | `mcp` | Stdio MCP server for AI agents (catalog, findings, agent context) |
30
31
  | CI default | `CI=true` | Same as `--report` |
31
32
 
32
33
  Scanner flags: `--json`, `-p` / `--parallel`, `--fail-on-warnings`, `--max-warnings`, `--output`, `[PATH]`. Low-level: `--serve <port>` (watch + HTTP, no Vite).
@@ -37,43 +38,48 @@ On **`npm install dslinter`**, npm installs the platform **`@dslinter/binding-*`
37
38
 
38
39
  | Variable | Purpose |
39
40
  |----------|---------|
40
- | `DSLINT_BIN` | Use a cargo-built `dslinter` binary instead of the NAPI binding. |
41
- | `DSLINT_ALLOW_PATH=1` | Allow `dslinter` on `PATH` when the binding is missing. |
41
+ | `DSLINTER_BIN` | Use a cargo-built `dslinter` binary instead of the NAPI binding. |
42
+ | `DSLINTER_ALLOW_PATH=1` | Allow `dslinter` on `PATH` when the binding is missing. |
42
43
  | `NAPI_RS_NATIVE_LIBRARY_PATH` | Point at a specific `.node` file (napi-rs escape hatch). |
43
44
 
44
45
  ### Do not `cargo install dslint`
45
46
 
46
- The crates.io crate **`dslint`** is a **different project**. Use **`cargo install --git https://github.com/jrmybtlr/DSLinter dslinter --locked`** or **`DSLINT_BIN`** for local Rust builds.
47
+ The crates.io crate **`dslint`** is a **different project**. Use **`cargo install --git https://github.com/jrmybtlr/DSLinter dslinter --locked`** or **`DSLINTER_BIN`** for local Rust builds.
47
48
 
48
49
  Typical usage:
49
50
 
50
51
  ```bash
51
- npx dslinter init # scaffold .dslint.json + src/playground/buildRegistry.ts
52
- npx dslinter # dev (watch + dashboard)
53
- npx dslinter --report /path/to/repo --json
54
- npx dslinter --report --output public/dslint-report.json
55
- npx dslinter --watch --output public/dslint-report.json
52
+ npx dslinter # dev (watch + dashboard) — run from any project subdirectory
53
+ npx dslinter --yes # dev + auto-create .dslinter.json and public/ without prompting
54
+ npx dslinter init # optional: scaffold buildRegistry.ts for custom controls
55
+ npx dslinter --report demo --json
56
+ npx dslinter --report --output public/dslinter-report.json
57
+ npx dslinter --watch --output public/dslinter-report.json
56
58
  ```
57
59
 
58
- Set `DSLINT_SERVE_PORT` to override the default scanner port (`7878`).
59
- On first local run, `npx dslinter` scaffolds `.dslint.json` automatically if missing.
60
+ Set `DSLINTER_SERVE_PORT` to override the default scanner port (`7878`).
61
+ When dev mode prints both a **Dashboard** URL and a **Scanner API** URL, open the **Dashboard** URL for the UI (port 7878 is the scanner API only).
62
+
63
+ On first local run in an interactive terminal, `npx dslinter` asks whether to create minimal setup files (`.dslinter.json` and `public/`). In CI or with `--yes`, those files are created automatically. Set `DSLINTER_NO_SCAFFOLD=1` to skip all writes.
64
+
65
+ | Flag / variable | Purpose |
66
+ |-----------------|--------|
67
+ | `--yes` / `-y` | Create minimal scaffold without prompting |
68
+ | `DSLINTER_NO_SCAFFOLD=1` | Never write scaffold files |
69
+ | `DSLINTER_USE_CONSUMER_VITE=1` | Use your app's Vite dev server as the dashboard UI (embedded `<DashboardLayout />`) |
70
+ | `DSLINTER_NO_EMBED_VITE=1` | Disable standalone embed dashboard dev server |
60
71
 
61
72
  ### Zero-config live previews (recommended)
62
73
 
63
- **`npx dslinter .`** from your project root auto-merges the dslinter Vite plugin (playground module glob, scanner proxy, react dedupe). In your app:
74
+ Run **`npx dslinter`** from your repo (including `resources/js/Components` or other subdirectories the CLI resolves the project root automatically). For **Laravel / Inertia** apps, dev mode starts a **standalone DSLinter dashboard** with live previews; you do **not** need to add `<DashboardLayout />` to your Inertia app.
64
75
 
65
- ```tsx
66
- import { DashboardLayout, useWorkspaceReport } from "dslinter";
76
+ Previews load your components with your Vite `@/` aliases and Inertia stubs (`usePage`, `<Link>`, etc.) so isolated components render without a full page visit.
67
77
 
68
- const dslinterReport = useWorkspaceReport({
69
- reportUrl: "/dslint-report.json",
70
- watchUrl: "/events",
71
- });
78
+ The embed dev server registers Tailwind `@source` paths for your `.dslinter.json` **`include_dirs`** (for example `resources/js/components`), so component utility classes like `px-3.5` are generated in preview CSS. For 100% parity with your app's full CSS pipeline (theme entry, `@custom-variant`, etc.), use `DSLINTER_USE_CONSUMER_VITE=1` instead.
72
79
 
73
- <DashboardLayout autoPlayground dslinterReport={dslinterReport} tokenCatalog={...} />;
74
- ```
80
+ The prebuilt **`dashboard-dist`** bundle shipped on npm does not run this Vite transform; use embed dev mode (monorepo / git checkout) or consumer Vite for accurate preview styling.
75
81
 
76
- No `buildRegistry.ts` scaffold required. Works with Laravel/Inertia (`resources/js/Components/...`) and existing `@/*` `resources/js/*` aliases.
82
+ For apps that already embed the dashboard (like this repo's `demo/`), dev mode uses your app's Vite server when `src/App.tsx` imports `DashboardLayout` from `dslinter`.
77
83
 
78
84
  **Direct `vite --mode serve`:** add one line to `vite.config.ts`:
79
85
 
@@ -85,12 +91,27 @@ export default defineConfig({
85
91
  });
86
92
  ```
87
93
 
88
- The plugin sets `DSLINT_SCAN_ROOT` from the environment (set by `npx dslinter`) or defaults to `process.cwd()`.
94
+ The plugin sets `DSLINTER_SCAN_ROOT` from the environment (set by `npx dslinter`) or defaults to `process.cwd()`.
89
95
 
90
96
  ### Consumer Vite (Laravel, Inertia, existing `@/*` aliases)
91
97
 
92
98
  Published `dslinter` source uses **relative imports only** — your app's `@/*` alias does not hijack dslinter internal UI. You do **not** need `@/components` → `node_modules/dslinter` alias overrides.
93
99
 
100
+ ### Laravel / Inertia (zero app code)
101
+
102
+ ```bash
103
+ cd my-laravel-app
104
+ npx dslinter
105
+ ```
106
+
107
+ Open the **Dashboard** URL from the terminal banner (default port `5175`). The scanner writes `public/dslinter-report.json` at the project root.
108
+
109
+ No Inertia route, no `buildRegistry.ts`, and no `vite.config` edits are required for dev previews.
110
+
111
+ **Optional — embed dashboard in your app:** set `DSLINTER_USE_CONSUMER_VITE=1`, add `plugins: [dslinter()]` from `dslinter/vite`, and render `<DashboardLayout autoPlayground dslinterReport={...} />`.
112
+
113
+ **Optional — custom playground controls:** `npx dslinter init --laravel` scaffolds `resources/js/playground/buildRegistry.ts`.
114
+
94
115
  ## Styles (Tailwind v4)
95
116
 
96
117
  1. In your app CSS, load Tailwind, then point Tailwind at this package so utility scanning picks up dashboard classes:
@@ -113,7 +134,7 @@ Use **`autoPlayground`** (above) for zero-config previews. Optionally scaffold a
113
134
  - `npx dslinter init` → `src/playground/buildRegistry.ts`
114
135
  - `npx dslinter init --laravel` → `resources/js/playground/buildRegistry.ts`
115
136
 
116
- `npx dslinter init` now also scaffolds a starter `.dslint.json` (unless one already exists), including:
137
+ `npx dslinter init` now also scaffolds a starter `.dslinter.json` (unless one already exists), including:
117
138
  - `include_dirs` (directory scope for discovery)
118
139
  - `ignore_globs` (file/directory ignores)
119
140
  - `css_entrypoints` (main CSS entry files for token analysis)
@@ -123,7 +144,7 @@ import { useMemo } from "react";
123
144
  import { DashboardLayout, useWorkspaceReport } from "dslinter";
124
145
  import { buildPlaygroundEntries } from "./playground/buildRegistry";
125
146
 
126
- const dslinterReport = useWorkspaceReport({ reportUrl: "/dslint-report.json", watchUrl: "/events" });
147
+ const dslinterReport = useWorkspaceReport({ reportUrl: "/dslinter-report.json", watchUrl: "/events" });
127
148
  const playgroundEntries = useMemo(
128
149
  () => buildPlaygroundEntries(dslinterReport.report),
129
150
  [dslinterReport.report],
@@ -143,7 +164,7 @@ Run the scanner from the **project root** (`npx dslinter .`) so `playgrounds[].r
143
164
  - **`autoPlayground`** (recommended) — or **`playgroundEntries`** from a custom registry / `usePlaygroundFromReport`.
144
165
  - **`playgroundJoinSkips`** (optional) — auto-filled when using `autoPlayground`.
145
166
  - **`tokenCatalog`** — token wall data (see `demo/src/tokenCatalog.ts`).
146
- - **`dslinterReport`** — from `useWorkspaceReport({ reportUrl: "/dslint-report.json", ... })`.
167
+ - **`dslinterReport`** — from `useWorkspaceReport({ reportUrl: "/dslinter-report.json", ... })`.
147
168
 
148
169
  ```tsx
149
170
  import { useMemo } from "react";
@@ -156,7 +177,7 @@ import { tokenCatalog } from "./tokenCatalog";
156
177
 
157
178
  export default function App() {
158
179
  const dslinterReport = useWorkspaceReport({
159
- reportUrl: "/dslint-report.json",
180
+ reportUrl: "/dslinter-report.json",
160
181
  refreshIntervalMs: 5000,
161
182
  });
162
183
 
package/bin/dslinter.mjs CHANGED
@@ -1,12 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * DSLinter CLI: mode routing (dev / report / watch / build) + scanner (NAPI or DSLINT_BIN).
3
+ * DSLinter CLI: mode routing (dev / report / watch / build) + scanner (NAPI or DSLINTER_BIN).
4
4
  */
5
5
  import { parseDslinterArgs } from "./lib/parse-args.mjs";
6
+ import { withScannerScanPath } from "./lib/resolve-project.mjs";
7
+ import { logScanScopeHint } from "./lib/project-root.mjs";
6
8
  import { runScannerInternal } from "./lib/run-scanner.mjs";
7
9
  import { runBuildMode } from "./modes/build.mjs";
8
10
  import { runDevMode } from "./modes/dev.mjs";
9
11
  import { runInitMode } from "./modes/init.mjs";
12
+ import { runMcpMode } from "./modes/mcp.mjs";
10
13
  import { runReportMode } from "./modes/report.mjs";
11
14
 
12
15
  const rawArgs = process.argv.slice(2);
@@ -16,6 +19,10 @@ if (rawArgs[0] === "init") {
16
19
  process.exit(0);
17
20
  }
18
21
 
22
+ if (rawArgs[0] === "mcp") {
23
+ await runMcpMode({ argv: rawArgs.slice(1) });
24
+ }
25
+
19
26
  if (process.env.DSLINTER_INTERNAL === "1") {
20
27
  runScannerInternal(rawArgs);
21
28
  }
@@ -36,23 +43,37 @@ try {
36
43
  process.exit(1);
37
44
  }
38
45
 
39
- const { mode, scannerArgs } = parsed;
46
+ const { mode } = parsed;
47
+ logScanScopeHint({
48
+ scanPath: parsed.scanPath,
49
+ projectRoot: parsed.projectRoot,
50
+ explicitScanPath: parsed.explicitScanPath,
51
+ });
52
+ const scannerArgs = withScannerScanPath(parsed.scannerArgs, parsed.scanPath);
53
+ const runParsed = { ...parsed, scannerArgs };
40
54
 
41
55
  switch (mode) {
42
56
  case "dev":
43
- await runDevMode(parsed);
57
+ await runDevMode(runParsed);
44
58
  break;
45
59
  case "report":
46
- runReportMode(["--report", ...scannerArgs]);
60
+ await runReportMode({
61
+ scanPath: runParsed.scanPath,
62
+ projectRoot: runParsed.projectRoot,
63
+ outputPath: runParsed.outputPath,
64
+ scannerArgs: runParsed.scannerArgs,
65
+ });
47
66
  break;
48
67
  case "watch":
68
+ process.env.DSLINTER_PROJECT_ROOT = runParsed.projectRoot;
49
69
  runScannerInternal(["--watch", ...scannerArgs]);
50
70
  break;
51
71
  case "scanner":
72
+ process.env.DSLINTER_PROJECT_ROOT = runParsed.projectRoot;
52
73
  runScannerInternal(scannerArgs);
53
74
  break;
54
75
  case "build":
55
- runBuildMode(parsed);
76
+ await runBuildMode(runParsed);
56
77
  break;
57
78
  default:
58
79
  process.stderr.write(`dslinter: unknown mode ${mode}\n`);
@@ -0,0 +1,44 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ const CONFIG_FILE_NAMES = [".dslinter.json", "dslinter.json"];
4
+ const DEFAULT_CONFIG_FILE_NAME = ".dslinter.json";
5
+
6
+ /**
7
+ * @param {string} projectRoot
8
+ * @returns {string}
9
+ */
10
+ export function findDslintConfigPath(projectRoot) {
11
+ for (const name of CONFIG_FILE_NAMES) {
12
+ const candidate = join(projectRoot, name);
13
+ if (existsSync(candidate)) return candidate;
14
+ }
15
+ return join(projectRoot, DEFAULT_CONFIG_FILE_NAME);
16
+ }
17
+
18
+ /**
19
+ * @param {string} projectRoot
20
+ * @param {string} componentName
21
+ * @returns {{ hidden_components: string[] }}
22
+ */
23
+ export function hideComponentInDslintConfig(projectRoot, componentName) {
24
+ const name = String(componentName ?? "").trim();
25
+ if (!name) {
26
+ throw new Error("component name is required");
27
+ }
28
+ const configPath = findDslintConfigPath(projectRoot);
29
+ let config = {};
30
+ if (existsSync(configPath)) {
31
+ try {
32
+ config = JSON.parse(readFileSync(configPath, "utf8"));
33
+ } catch (e) {
34
+ throw new Error(`Invalid JSON in ${configPath}: ${e.message}`);
35
+ }
36
+ }
37
+ const hidden = Array.isArray(config.hidden_components)
38
+ ? [...config.hidden_components]
39
+ : [];
40
+ if (!hidden.includes(name)) hidden.push(name);
41
+ config.hidden_components = hidden;
42
+ writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
43
+ return { hidden_components: hidden };
44
+ }
@@ -0,0 +1,33 @@
1
+ import { mkdtempSync, readFileSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, describe, expect, it } from "vitest";
5
+ import { hideComponentInDslintConfig } from "./config-hide-component.mjs";
6
+
7
+ describe("hideComponentInDslintConfig", () => {
8
+ const dirs = [];
9
+
10
+ afterEach(() => {
11
+ for (const dir of dirs) rmSync(dir, { recursive: true, force: true });
12
+ dirs.length = 0;
13
+ });
14
+
15
+ it("appends hidden_components to a new config file", () => {
16
+ const root = mkdtempSync(join(tmpdir(), "dslint-hide-"));
17
+ dirs.push(root);
18
+ const result = hideComponentInDslintConfig(root, "Foo");
19
+ expect(result.hidden_components).toEqual(["Foo"]);
20
+ const written = JSON.parse(
21
+ readFileSync(join(root, ".dslinter.json"), "utf8"),
22
+ );
23
+ expect(written.hidden_components).toEqual(["Foo"]);
24
+ });
25
+
26
+ it("does not duplicate names", () => {
27
+ const root = mkdtempSync(join(tmpdir(), "dslint-hide-"));
28
+ dirs.push(root);
29
+ hideComponentInDslintConfig(root, "Foo");
30
+ const result = hideComponentInDslintConfig(root, "Foo");
31
+ expect(result.hidden_components).toEqual(["Foo"]);
32
+ });
33
+ });
@@ -0,0 +1,20 @@
1
+ import { readEnv } from "./env.mjs";
2
+
3
+ /** @typedef {import("node:process").ProcessEnv} ProcessEnv */
4
+
5
+ /** Default scanner HTTP port when `DSLINTER_SERVE_PORT` is unset. */
6
+ export const DEFAULT_SERVE_PORT = 7878;
7
+
8
+ /**
9
+ * Resolve scanner HTTP port from `DSLINTER_SERVE_PORT` or {@link DEFAULT_SERVE_PORT}.
10
+ * @param {ProcessEnv} [env]
11
+ * @returns {number}
12
+ */
13
+ export function resolveServePort(env = process.env) {
14
+ const fromEnv = readEnv("SERVE_PORT", env);
15
+ if (fromEnv) {
16
+ const n = Number.parseInt(fromEnv, 10);
17
+ if (Number.isFinite(n) && n > 0 && n <= 65535) return n;
18
+ }
19
+ return DEFAULT_SERVE_PORT;
20
+ }
@@ -172,33 +172,23 @@ export function formatDevBanner(opts) {
172
172
  const maxBox = Math.min(Math.max(terminalCols, 64), 96);
173
173
 
174
174
  const scanAbs = resolve(opts.scanPath);
175
- const reportAbs = resolve(opts.reportPath);
176
- const apiBase = `http://127.0.0.1:${opts.apiPort}`;
177
-
178
- const apiStatusPlain = opts.apiAvailable ? "listening" : "unavailable — port in use";
179
- const bundledStatusPlain = opts.apiAvailable ? "ready" : "port busy";
175
+ const dashboardUrl = opts.dashboardUrl ?? opts.bundledUrl ?? null;
180
176
  const scanPlain = shortenPath(scanAbs, 80);
181
- const reportPlain = shortenPath(reportAbs, 80);
177
+ const scannerWarnPlain = opts.apiAvailable
178
+ ? null
179
+ : `unavailable — port ${opts.apiPort} in use`;
182
180
 
183
181
  /** @type {number[]} */
184
182
  const plainWidths = [
185
183
  ...LOGO.map((l) => visibleLength(l)),
186
184
  14 + 2 + scanPlain.length,
187
- 14 + 2 + reportPlain.length,
188
185
  ];
189
- if (opts.dashboardUrl) plainWidths.push(14 + 2 + opts.dashboardUrl.length);
190
- if (opts.bundledUrl) {
191
- plainWidths.push(14 + 2 + `${opts.bundledUrl} (${bundledStatusPlain})`.length);
192
- }
193
- plainWidths.push(14 + 2 + `${apiBase} (${apiStatusPlain})`.length);
194
- if (opts.apiAvailable) {
195
- plainWidths.push(14 + 2 + `${apiBase}/dslint-report.json`.length);
196
- plainWidths.push(14 + 2 + `${apiBase}/events`.length);
197
- }
186
+ if (dashboardUrl) plainWidths.push(14 + 2 + dashboardUrl.length);
187
+ if (scannerWarnPlain) plainWidths.push(14 + 2 + scannerWarnPlain.length);
198
188
  if (opts.pollMs) plainWidths.push(14 + 2 + `polling every ${opts.pollMs} ms`.length);
199
- const footerPlain = opts.bundledUrl
200
- ? " Open the Bundled UI URL in your browser. Ctrl+C to stop."
201
- : " Open the Dashboard URL in your browser. Ctrl+C to stop.";
189
+ const footerPlain = dashboardUrl
190
+ ? " Open the Dashboard in your browser. Ctrl+C to stop."
191
+ : " Ctrl+C to stop.";
202
192
  plainWidths.push(visibleLength(footerPlain));
203
193
 
204
194
  const contentWidth = Math.min(maxBox - 4, Math.max(...plainWidths, 40));
@@ -212,45 +202,20 @@ export function formatDevBanner(opts) {
212
202
  styledRows.push(
213
203
  ...row(color.label("Scan path"), scanPlain, contentWidth, color.value),
214
204
  );
215
- styledRows.push(
216
- ...row(color.label("Report file"), reportPlain, contentWidth, color.value),
217
- );
218
- styledRows.push("");
219
- if (opts.dashboardUrl && !opts.bundledUrl) {
220
- styledRows.push(
221
- ...row(color.label("Dashboard"), opts.dashboardUrl, contentWidth, color.url),
222
- );
223
- }
224
- if (opts.bundledUrl) {
225
- const status = opts.apiAvailable ? color.ok(bundledStatusPlain) : color.warn(bundledStatusPlain);
205
+ if (opts.pollMs) {
226
206
  styledRows.push(
227
- ...row(
228
- color.label("Bundled UI"),
229
- `${opts.bundledUrl} (${bundledStatusPlain})`,
230
- contentWidth,
231
- () => `${color.url(opts.bundledUrl)} ${color.dim("(")}${status}${color.dim(")")}`,
232
- ),
207
+ ...row(color.label("Watch"), `polling every ${opts.pollMs} ms`, contentWidth, color.dim),
233
208
  );
234
209
  }
235
- const apiStatus = opts.apiAvailable ? color.ok(apiStatusPlain) : color.err(apiStatusPlain);
236
- styledRows.push(
237
- ...row(
238
- color.label("Scanner API"),
239
- `${apiBase} (${apiStatusPlain})`,
240
- contentWidth,
241
- () => `${color.url(apiBase)} ${color.dim("(")}${apiStatus}${color.dim(")")}`,
242
- ),
243
- );
244
- if (opts.apiAvailable) {
210
+ styledRows.push("");
211
+ if (dashboardUrl) {
245
212
  styledRows.push(
246
- ...row("", `${apiBase}/dslint-report.json`, contentWidth, color.dim),
213
+ ...row(color.label("Dashboard"), dashboardUrl, contentWidth, color.url),
247
214
  );
248
- styledRows.push(...row("", `${apiBase}/events`, contentWidth, color.dim));
249
215
  }
250
- if (opts.pollMs) {
251
- styledRows.push("");
216
+ if (scannerWarnPlain) {
252
217
  styledRows.push(
253
- ...row(color.label("Watch"), `polling every ${opts.pollMs} ms`, contentWidth, color.dim),
218
+ ...row(color.label("Scanner"), scannerWarnPlain, contentWidth, color.err),
254
219
  );
255
220
  }
256
221
  styledRows.push("");
@@ -22,59 +22,61 @@ describe("shortenPath", () => {
22
22
  });
23
23
 
24
24
  describe("formatDevBanner", () => {
25
- it("includes logo, scan path, report, bundled UI, and API URLs", () => {
25
+ it("includes logo, scan path, dashboard URL, and watch info", () => {
26
26
  const text = formatDevBanner({
27
27
  scanPath: "/tmp/components",
28
- reportPath: "/tmp/components/public/dslint-report.json",
28
+ reportPath: "/tmp/components/public/dslinter-report.json",
29
29
  apiPort: 7878,
30
30
  apiAvailable: true,
31
31
  dashboardUrl: "http://localhost:5173/",
32
- bundledUrl: "http://127.0.0.1:7878/",
33
32
  pollMs: 150,
34
33
  });
35
34
  expect(text).toContain(LOGO[0]);
36
35
  expect(text).toContain(LOGO[1]);
37
36
  expect(text).toContain("Scan path");
38
- expect(text).toContain("Report file");
39
- expect(text).toContain("Bundled UI");
40
- expect(text).not.toContain("http://localhost:5173/");
41
- expect(text).not.toMatch(/\bDashboard\b/);
42
- expect(text).toContain("7878");
43
- expect(text).toContain("dslint-report.json");
37
+ expect(text).not.toContain("Report file");
38
+ expect(text).toContain("Dashboard");
39
+ expect(text).toContain("http://localhost:5173/");
40
+ expect(text).not.toContain("Scanner API");
41
+ expect(text).not.toContain("dslinter-report.json");
42
+ expect(text).not.toContain("/events");
44
43
  expect(text).toContain("polling every 150 ms");
45
- expect(text).toContain("Open the Bundled UI URL");
44
+ expect(text).toContain("Open the Dashboard in your browser");
46
45
  });
47
46
 
48
- it("shows dashboard URL when bundled UI is not served", () => {
47
+ it("shows bundled URL as the dashboard when no separate dev server", () => {
49
48
  const text = formatDevBanner({
50
49
  scanPath: "/tmp/components",
51
- reportPath: "/tmp/components/public/dslint-report.json",
50
+ reportPath: "/tmp/components/public/dslinter-report.json",
52
51
  apiPort: 7878,
53
52
  apiAvailable: true,
54
- dashboardUrl: "http://localhost:5173/",
53
+ bundledUrl: "http://127.0.0.1:7878/",
55
54
  pollMs: 150,
56
55
  });
57
56
  expect(text).toContain("Dashboard");
58
- expect(text).toContain("http://localhost:5173/");
59
- expect(text).toContain("Open the Dashboard URL");
57
+ expect(text).toContain("http://127.0.0.1:7878/");
58
+ expect(text).not.toContain("Bundled UI");
59
+ expect(text).not.toContain("Scanner API");
60
60
  });
61
61
 
62
- it("marks API unavailable when port is busy", () => {
62
+ it("marks scanner unavailable when port is busy", () => {
63
63
  const text = formatDevBanner({
64
64
  scanPath: ".",
65
- reportPath: "./public/dslint-report.json",
65
+ reportPath: "./public/dslinter-report.json",
66
66
  apiPort: 7878,
67
67
  apiAvailable: false,
68
68
  dashboardUrl: "http://localhost:5174/",
69
69
  });
70
+ expect(text).toContain("Scanner");
70
71
  expect(text).toContain("unavailable");
72
+ expect(text).not.toContain("7878/");
71
73
  expect(text).not.toContain("/events");
72
74
  });
73
75
 
74
76
  it("keeps right border aligned on every row", () => {
75
77
  const text = formatDevBanner({
76
78
  scanPath: "/very/long/path/that/could/push/the/box/wider/than/usual/Components",
77
- reportPath: "/very/long/path/public/dslint-report.json",
79
+ reportPath: "/very/long/path/public/dslinter-report.json",
78
80
  apiPort: 7878,
79
81
  apiAvailable: false,
80
82
  dashboardUrl: "http://localhost:5175/",