dslinter 0.1.4 → 0.1.13
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/CHANGELOG.md +80 -1
- package/README.md +46 -35
- package/bin/lib/project-root.mjs +20 -14
- package/bin/lib/project-root.test.mjs +21 -0
- package/bin/lib/scaffold-config.mjs +75 -0
- package/bin/lib/scaffold-config.test.mjs +33 -0
- package/bin/modes/build.mjs +6 -0
- package/bin/modes/dev.mjs +26 -2
- package/bin/modes/init.mjs +39 -36
- package/bin/modes/init.test.mjs +16 -0
- package/dashboard-dist/assets/DashboardLayoutAuto-Bm7yfyC-.css +1 -0
- package/dashboard-dist/assets/DashboardLayoutAuto-DgwO_itB.js +1 -0
- package/dashboard-dist/assets/index-Cbv7vXvH.css +1 -0
- package/dashboard-dist/assets/index-e20cwqnb.js +206 -0
- package/dashboard-dist/index.html +2 -2
- package/index.cjs +52 -52
- package/package.json +16 -7
- package/src/components/ComponentInspectPane.tsx +7 -11
- package/src/dashboard/ComponentUsageDetails.tsx +9 -3
- package/src/index.ts +1 -0
- package/src/playground/scanPlaygroundModules.ts +1 -0
- package/src/playground/usePlaygroundFromReport.test.ts +37 -0
- package/src/playground/usePlaygroundFromReport.ts +23 -0
- package/src/playground/virtual-playground-modules.d.ts +6 -0
- package/src/shell/DashboardLayout.tsx +37 -4
- package/src/shell/DashboardLayoutAuto.tsx +20 -0
- package/templates/vite.dslint-scan-alias.snippet.ts +2 -12
- package/templates/vite.dslinter.snippet.ts +11 -19
- package/vite/collectScanModules.test.ts +42 -0
- package/vite/collectScanModules.ts +60 -0
- package/vite/consumer.config.mjs +22 -0
- package/vite/plugin.ts +140 -0
- package/dashboard-dist/assets/index-BhDQfrwA.css +0 -1
- package/dashboard-dist/assets/index-DGUG_3SK.js +0 -205
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,93 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.1.13
|
|
4
|
+
|
|
5
|
+
[compare changes](https://github.com/jrmybtlr/DSLinter/compare/v0.1.12...v0.1.13)
|
|
6
|
+
|
|
7
|
+
### 🏡 Chore
|
|
8
|
+
|
|
9
|
+
- **release:** V0.1.12 ([7c2190a](https://github.com/jrmybtlr/DSLinter/commit/7c2190a))
|
|
10
|
+
|
|
11
|
+
### ❤️ Contributors
|
|
12
|
+
|
|
13
|
+
- Jeremy Butler <jeremy.butler@laravel.com>
|
|
14
|
+
|
|
15
|
+
## v0.1.12
|
|
16
|
+
|
|
17
|
+
[compare changes](https://github.com/jrmybtlr/DSLinter/compare/v0.1.10...v0.1.12)
|
|
18
|
+
|
|
19
|
+
### 🏡 Chore
|
|
20
|
+
|
|
21
|
+
- **release:** V0.1.11 ([bc2d134](https://github.com/jrmybtlr/DSLinter/commit/bc2d134))
|
|
22
|
+
|
|
23
|
+
### ❤️ Contributors
|
|
24
|
+
|
|
25
|
+
- Jeremy Butler <jeremy.butler@laravel.com>
|
|
26
|
+
|
|
27
|
+
## v0.1.11
|
|
28
|
+
|
|
29
|
+
[compare changes](https://github.com/jrmybtlr/DSLinter/compare/v0.1.10...v0.1.11)
|
|
30
|
+
|
|
31
|
+
## v0.1.10
|
|
32
|
+
|
|
33
|
+
[compare changes](https://github.com/jrmybtlr/DSLinter/compare/v0.1.8...v0.1.10)
|
|
34
|
+
|
|
35
|
+
### 🏡 Chore
|
|
36
|
+
|
|
37
|
+
- **release:** V0.1.9 ([a54ea04](https://github.com/jrmybtlr/DSLinter/commit/a54ea04))
|
|
38
|
+
|
|
39
|
+
### ❤️ Contributors
|
|
40
|
+
|
|
41
|
+
- Jeremy Butler <jeremy.butler@laravel.com>
|
|
42
|
+
|
|
43
|
+
## v0.1.8
|
|
44
|
+
|
|
45
|
+
[compare changes](https://github.com/jrmybtlr/DSLinter/compare/v0.1.7...v0.1.8)
|
|
46
|
+
|
|
47
|
+
### 🚀 Enhancements
|
|
48
|
+
|
|
49
|
+
- Auto-scaffold dslint config and add scan scope options ([572e97b](https://github.com/jrmybtlr/DSLinter/commit/572e97b))
|
|
50
|
+
|
|
51
|
+
### 🩹 Fixes
|
|
52
|
+
|
|
53
|
+
- Warn when css_entrypoint path is missing ([88a5038](https://github.com/jrmybtlr/DSLinter/commit/88a5038))
|
|
54
|
+
- Don't scope CSS discovery to include_dirs ([7e6df94](https://github.com/jrmybtlr/DSLinter/commit/7e6df94))
|
|
55
|
+
|
|
56
|
+
## v0.1.7
|
|
57
|
+
|
|
58
|
+
[compare changes](https://github.com/jrmybtlr/DSLinter/compare/v0.1.5...v0.1.7)
|
|
59
|
+
|
|
60
|
+
### 🏡 Chore
|
|
61
|
+
|
|
62
|
+
- **release:** V0.1.6 ([1463fbc](https://github.com/jrmybtlr/DSLinter/commit/1463fbc))
|
|
63
|
+
|
|
64
|
+
### ❤️ Contributors
|
|
65
|
+
|
|
66
|
+
- Jeremy Butler <jeremy.butler@laravel.com>
|
|
67
|
+
|
|
68
|
+
## v0.1.6
|
|
69
|
+
|
|
70
|
+
[compare changes](https://github.com/jrmybtlr/DSLinter/compare/v0.1.5...v0.1.6)
|
|
71
|
+
|
|
72
|
+
## v0.1.5
|
|
73
|
+
|
|
74
|
+
[compare changes](https://github.com/jrmybtlr/DSLinter/compare/v0.1.4...v0.1.5)
|
|
75
|
+
|
|
3
76
|
## v0.1.4
|
|
4
77
|
|
|
5
78
|
[compare changes](https://github.com/jrmybtlr/DSLinter/compare/v0.1.3...v0.1.4)
|
|
6
79
|
|
|
7
80
|
## Unreleased
|
|
8
81
|
|
|
82
|
+
### 🚀 Enhancements
|
|
83
|
+
|
|
84
|
+
- **`dslinter/vite` plugin** — virtual `playground-modules` map keyed by `@dslint-scan/<rel_path>`; scanner proxy, react dedupe, `optimizeDeps.exclude`, and `server.fs.allow` for the scan root.
|
|
85
|
+
- **`DashboardLayout autoPlayground`** and **`usePlaygroundFromReport`** — zero-config live previews without `buildRegistry.ts`.
|
|
86
|
+
- **`npx dslinter`** auto-merges the plugin into consumer Vite via `vite/consumer.config.mjs` when a host app is detected.
|
|
87
|
+
|
|
9
88
|
### 🩹 Fixes
|
|
10
89
|
|
|
11
|
-
- Remove `@/` path aliases from published `src/` so host apps with their own `@/*` Vite alias (Laravel/Inertia, etc.) no longer resolve dslinter UI imports to the wrong tree.
|
|
90
|
+
- Remove `@/` path aliases from published `src/` so host apps with their own `@/*` Vite alias (Laravel/Inertia, etc.) no longer resolve dslinter UI imports to the wrong tree.
|
|
12
91
|
|
|
13
92
|
## v0.1.3
|
|
14
93
|
|
package/README.md
CHANGED
|
@@ -48,26 +48,48 @@ The crates.io crate **`dslint`** is a **different project**. Use **`cargo instal
|
|
|
48
48
|
Typical usage:
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
|
-
npx dslinter init # scaffold src/playground/buildRegistry.ts
|
|
51
|
+
npx dslinter init # scaffold .dslint.json + src/playground/buildRegistry.ts
|
|
52
52
|
npx dslinter # dev (watch + dashboard)
|
|
53
53
|
npx dslinter --report /path/to/repo --json
|
|
54
54
|
npx dslinter --report --output public/dslint-report.json
|
|
55
55
|
npx dslinter --watch --output public/dslint-report.json
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
Set `DSLINT_SERVE_PORT` to override the default scanner port (`7878`).
|
|
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.
|
|
59
60
|
|
|
60
|
-
###
|
|
61
|
+
### Zero-config live previews (recommended)
|
|
62
|
+
|
|
63
|
+
**`npx dslinter .`** from your project root auto-merges the dslinter Vite plugin (playground module glob, scanner proxy, react dedupe). In your app:
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { DashboardLayout, useWorkspaceReport } from "dslinter";
|
|
67
|
+
|
|
68
|
+
const dslinterReport = useWorkspaceReport({
|
|
69
|
+
reportUrl: "/dslint-report.json",
|
|
70
|
+
watchUrl: "/events",
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
<DashboardLayout autoPlayground dslinterReport={dslinterReport} tokenCatalog={...} />;
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
No `buildRegistry.ts` scaffold required. Works with Laravel/Inertia (`resources/js/Components/...`) and existing `@/*` → `resources/js/*` aliases.
|
|
77
|
+
|
|
78
|
+
**Direct `vite --mode serve`:** add one line to `vite.config.ts`:
|
|
61
79
|
|
|
62
|
-
|
|
80
|
+
```ts
|
|
81
|
+
import dslinter from "dslinter/vite";
|
|
63
82
|
|
|
64
|
-
|
|
83
|
+
export default defineConfig({
|
|
84
|
+
plugins: [dslinter(), /* your plugins */],
|
|
85
|
+
});
|
|
86
|
+
```
|
|
65
87
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
88
|
+
The plugin sets `DSLINT_SCAN_ROOT` from the environment (set by `npx dslinter`) or defaults to `process.cwd()`.
|
|
89
|
+
|
|
90
|
+
### Consumer Vite (Laravel, Inertia, existing `@/*` aliases)
|
|
69
91
|
|
|
70
|
-
|
|
92
|
+
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.
|
|
71
93
|
|
|
72
94
|
## Styles (Tailwind v4)
|
|
73
95
|
|
|
@@ -84,44 +106,33 @@ You do **not** need alias overrides such as `@/components` → `node_modules/dsl
|
|
|
84
106
|
@import "dslinter/theme.css";
|
|
85
107
|
```
|
|
86
108
|
|
|
87
|
-
## Live component previews (
|
|
109
|
+
## Live component previews (advanced / custom glob)
|
|
88
110
|
|
|
89
|
-
|
|
111
|
+
Use **`autoPlayground`** (above) for zero-config previews. Optionally scaffold a narrower glob for faster dev or custom controls:
|
|
90
112
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
113
|
+
- `npx dslinter init` → `src/playground/buildRegistry.ts`
|
|
114
|
+
- `npx dslinter init --laravel` → `resources/js/playground/buildRegistry.ts`
|
|
115
|
+
|
|
116
|
+
`npx dslinter init` now also scaffolds a starter `.dslint.json` (unless one already exists), including:
|
|
117
|
+
- `include_dirs` (directory scope for discovery)
|
|
118
|
+
- `ignore_globs` (file/directory ignores)
|
|
119
|
+
- `css_entrypoints` (main CSS entry files for token analysis)
|
|
94
120
|
|
|
95
121
|
```tsx
|
|
96
122
|
import { useMemo } from "react";
|
|
97
123
|
import { DashboardLayout, useWorkspaceReport } from "dslinter";
|
|
98
|
-
import {
|
|
99
|
-
buildPlaygroundEntries,
|
|
100
|
-
getPlaygroundJoinSkips,
|
|
101
|
-
} from "./playground/buildRegistry";
|
|
102
|
-
|
|
103
|
-
const dslinterReport = useWorkspaceReport({
|
|
104
|
-
reportUrl: "/dslint-report.json",
|
|
105
|
-
watchUrl: "/events",
|
|
106
|
-
});
|
|
124
|
+
import { buildPlaygroundEntries } from "./playground/buildRegistry";
|
|
107
125
|
|
|
126
|
+
const dslinterReport = useWorkspaceReport({ reportUrl: "/dslint-report.json", watchUrl: "/events" });
|
|
108
127
|
const playgroundEntries = useMemo(
|
|
109
128
|
() => buildPlaygroundEntries(dslinterReport.report),
|
|
110
129
|
[dslinterReport.report],
|
|
111
130
|
);
|
|
112
|
-
const playgroundJoinSkips = useMemo(
|
|
113
|
-
() => getPlaygroundJoinSkips(dslinterReport.report),
|
|
114
|
-
[dslinterReport.report],
|
|
115
|
-
);
|
|
116
131
|
|
|
117
|
-
<DashboardLayout
|
|
118
|
-
playgroundEntries={playgroundEntries}
|
|
119
|
-
playgroundJoinSkips={playgroundJoinSkips}
|
|
120
|
-
dslinterReport={dslinterReport}
|
|
121
|
-
/>;
|
|
132
|
+
<DashboardLayout playgroundEntries={playgroundEntries} dslinterReport={dslinterReport} />;
|
|
122
133
|
```
|
|
123
134
|
|
|
124
|
-
|
|
135
|
+
Or use `usePlaygroundFromReport(report)` with `dslinter/playground-modules` when the Vite plugin is active.
|
|
125
136
|
|
|
126
137
|
Run the scanner from the **project root** (`npx dslinter .`) so `playgrounds[].rel_path` matches your repo layout.
|
|
127
138
|
|
|
@@ -129,8 +140,8 @@ Run the scanner from the **project root** (`npx dslinter .`) so `playgrounds[].r
|
|
|
129
140
|
|
|
130
141
|
`DashboardLayout` needs:
|
|
131
142
|
|
|
132
|
-
- **`
|
|
133
|
-
- **`playgroundJoinSkips`** (optional) —
|
|
143
|
+
- **`autoPlayground`** (recommended) — or **`playgroundEntries`** from a custom registry / `usePlaygroundFromReport`.
|
|
144
|
+
- **`playgroundJoinSkips`** (optional) — auto-filled when using `autoPlayground`.
|
|
134
145
|
- **`tokenCatalog`** — token wall data (see `demo/src/tokenCatalog.ts`).
|
|
135
146
|
- **`dslinterReport`** — from `useWorkspaceReport({ reportUrl: "/dslint-report.json", ... })`.
|
|
136
147
|
|
package/bin/lib/project-root.mjs
CHANGED
|
@@ -27,12 +27,26 @@ function maxMtimeInDir(dir, latest = 0) {
|
|
|
27
27
|
return latest;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} [root]
|
|
32
|
+
* @returns {boolean} true when embed SPA sources exist (monorepo / git checkout).
|
|
33
|
+
*/
|
|
34
|
+
export function hasEmbedDashboard(root = packageRoot) {
|
|
35
|
+
return existsSync(join(root, "index.html"));
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
/**
|
|
31
39
|
* Rebuild `dashboard-dist/` when embed sources are newer than the bundle (or dist is missing).
|
|
40
|
+
* Published npm installs omit embed sources; use prebuilt `dashboard-dist/` only.
|
|
32
41
|
* @param {string} root
|
|
33
42
|
*/
|
|
34
43
|
export function ensureDashboardBuilt(root = packageRoot) {
|
|
35
|
-
const
|
|
44
|
+
const distDir = join(root, "dashboard-dist");
|
|
45
|
+
if (!hasEmbedDashboard(root)) {
|
|
46
|
+
return dashboardDirIfReady(distDir);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const distIndex = join(distDir, "index.html");
|
|
36
50
|
const force =
|
|
37
51
|
process.env.DSLINTER_REBUILD_DASHBOARD === "1" ||
|
|
38
52
|
process.env.DSLINTER_REBUILD_DASHBOARD?.toLowerCase() === "true";
|
|
@@ -40,12 +54,9 @@ export function ensureDashboardBuilt(root = packageRoot) {
|
|
|
40
54
|
let needsBuild = force || !existsSync(distIndex);
|
|
41
55
|
if (!needsBuild) {
|
|
42
56
|
const distMtime = statSync(distIndex).mtimeMs;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
needsBuild = true;
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
57
|
+
const embedDir = join(root, "embed");
|
|
58
|
+
if (existsSync(embedDir) && maxMtimeInDir(embedDir) > distMtime) {
|
|
59
|
+
needsBuild = true;
|
|
49
60
|
}
|
|
50
61
|
const configPath = join(root, "vite.config.ts");
|
|
51
62
|
if (existsSync(configPath) && statSync(configPath).mtimeMs > distMtime) {
|
|
@@ -53,7 +64,7 @@ export function ensureDashboardBuilt(root = packageRoot) {
|
|
|
53
64
|
}
|
|
54
65
|
}
|
|
55
66
|
|
|
56
|
-
if (!needsBuild) return dashboardDirIfReady(
|
|
67
|
+
if (!needsBuild) return dashboardDirIfReady(distDir);
|
|
57
68
|
|
|
58
69
|
process.stderr.write("[dslinter] Building dashboard bundle (dashboard-dist)…\n");
|
|
59
70
|
const result = spawnSync("npm", ["run", "build:dashboard"], {
|
|
@@ -64,12 +75,7 @@ export function ensureDashboardBuilt(root = packageRoot) {
|
|
|
64
75
|
if (result.status !== 0) {
|
|
65
76
|
throw new Error("dslinter: dashboard build failed");
|
|
66
77
|
}
|
|
67
|
-
return dashboardDirIfReady(
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** @returns {boolean} */
|
|
71
|
-
export function hasEmbedDashboard() {
|
|
72
|
-
return existsSync(join(packageRoot, "index.html"));
|
|
78
|
+
return dashboardDirIfReady(distDir);
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
const VITE_CONFIG_NAMES = ["vite.config.ts", "vite.config.js", "vite.config.mjs", "vite.config.cjs"];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { ensureDashboardBuilt, hasEmbedDashboard } from "./project-root.mjs";
|
|
6
|
+
|
|
7
|
+
describe("ensureDashboardBuilt (published install layout)", () => {
|
|
8
|
+
it("returns prebuilt dashboard-dist without spawning build when embed sources are absent", () => {
|
|
9
|
+
const root = mkdtempSync(join(tmpdir(), "dslinter-published-"));
|
|
10
|
+
mkdirSync(join(root, "dashboard-dist"), { recursive: true });
|
|
11
|
+
writeFileSync(join(root, "dashboard-dist", "index.html"), "<!doctype html>");
|
|
12
|
+
mkdirSync(join(root, "src"), { recursive: true });
|
|
13
|
+
writeFileSync(join(root, "src", "index.ts"), "export {};\n");
|
|
14
|
+
|
|
15
|
+
expect(hasEmbedDashboard(root)).toBe(false);
|
|
16
|
+
|
|
17
|
+
const dist = ensureDashboardBuilt(root);
|
|
18
|
+
expect(dist).toBeTruthy();
|
|
19
|
+
expect(dist).toContain("dashboard-dist");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
const CONFIG_NAMES = [".dslint.json", "dslint.json"];
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} targetDir
|
|
8
|
+
* @returns {string | null}
|
|
9
|
+
*/
|
|
10
|
+
export function findDslintConfigPath(targetDir) {
|
|
11
|
+
for (const name of CONFIG_NAMES) {
|
|
12
|
+
const candidate = join(targetDir, name);
|
|
13
|
+
if (existsSync(candidate)) return candidate;
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} targetDir
|
|
20
|
+
* @param {string[]} candidates
|
|
21
|
+
* @returns {string[]}
|
|
22
|
+
*/
|
|
23
|
+
function existingPaths(targetDir, candidates) {
|
|
24
|
+
return candidates.filter((rel) => existsSync(join(targetDir, rel)));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} targetDir
|
|
29
|
+
* @param {"laravel" | "default"} layout
|
|
30
|
+
*/
|
|
31
|
+
function buildStarterConfig(targetDir, layout) {
|
|
32
|
+
const includeCandidates =
|
|
33
|
+
layout === "laravel"
|
|
34
|
+
? ["resources/js/Components", "resources/js/components", "resources/js"]
|
|
35
|
+
: ["src/components", "src/ui", "src"];
|
|
36
|
+
const cssCandidates =
|
|
37
|
+
layout === "laravel"
|
|
38
|
+
? ["resources/css/app.css", "src/index.css"]
|
|
39
|
+
: ["src/index.css", "src/styles.css", "src/app.css", "app/globals.css"];
|
|
40
|
+
|
|
41
|
+
const includeDirs = existingPaths(targetDir, includeCandidates);
|
|
42
|
+
const cssEntrypoints = existingPaths(targetDir, cssCandidates);
|
|
43
|
+
const groupPrefix = includeDirs[0];
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
include_dirs: includeDirs,
|
|
47
|
+
ignore_globs: [],
|
|
48
|
+
css_entrypoints: cssEntrypoints,
|
|
49
|
+
...(groupPrefix
|
|
50
|
+
? {
|
|
51
|
+
playground_groups: {
|
|
52
|
+
components: [groupPrefix],
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
: {}),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {{ targetDir: string; layout: "laravel" | "default" }} opts
|
|
61
|
+
* @returns {{ created: boolean; path: string; existed: boolean }}
|
|
62
|
+
*/
|
|
63
|
+
export function ensureDslintConfig(opts) {
|
|
64
|
+
const targetDir = resolve(opts.targetDir);
|
|
65
|
+
const existing = findDslintConfigPath(targetDir);
|
|
66
|
+
if (existing) {
|
|
67
|
+
return { created: false, path: existing, existed: true };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const configPath = join(targetDir, ".dslint.json");
|
|
71
|
+
mkdirSync(targetDir, { recursive: true });
|
|
72
|
+
const payload = buildStarterConfig(targetDir, opts.layout);
|
|
73
|
+
writeFileSync(configPath, `${JSON.stringify(payload, null, 2)}\n`);
|
|
74
|
+
return { created: true, path: configPath, existed: false };
|
|
75
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { mkdtempSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { ensureDslintConfig } from "./scaffold-config.mjs";
|
|
6
|
+
|
|
7
|
+
describe("ensureDslintConfig", () => {
|
|
8
|
+
it("writes starter config with detected include/css paths", () => {
|
|
9
|
+
const root = mkdtempSync(join(tmpdir(), "dslinter-scaffold-"));
|
|
10
|
+
mkdirSync(join(root, "src", "components"), { recursive: true });
|
|
11
|
+
writeFileSync(join(root, "src", "index.css"), "@theme { --color-primary: #000; }");
|
|
12
|
+
|
|
13
|
+
const result = ensureDslintConfig({ targetDir: root, layout: "default" });
|
|
14
|
+
expect(result.created).toBe(true);
|
|
15
|
+
|
|
16
|
+
const raw = readFileSync(result.path, "utf8");
|
|
17
|
+
const parsed = JSON.parse(raw);
|
|
18
|
+
expect(parsed.include_dirs).toContain("src/components");
|
|
19
|
+
expect(parsed.css_entrypoints).toContain("src/index.css");
|
|
20
|
+
expect(parsed.ignore_globs).toEqual([]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("does not overwrite an existing config", () => {
|
|
24
|
+
const root = mkdtempSync(join(tmpdir(), "dslinter-scaffold-existing-"));
|
|
25
|
+
const existing = join(root, ".dslint.json");
|
|
26
|
+
writeFileSync(existing, "{\n \"ignore_globs\": [\"custom/**\"]\n}\n");
|
|
27
|
+
|
|
28
|
+
const result = ensureDslintConfig({ targetDir: root, layout: "default" });
|
|
29
|
+
expect(result.created).toBe(false);
|
|
30
|
+
expect(result.path).toBe(existing);
|
|
31
|
+
expect(readFileSync(existing, "utf8")).toContain("custom/**");
|
|
32
|
+
});
|
|
33
|
+
});
|
package/bin/modes/build.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { resolve } from "node:path";
|
|
2
3
|
import { defaultReportPath, findViteRoot, resolveViteBin } from "../lib/project-root.mjs";
|
|
3
4
|
import { runScannerSync } from "../lib/run-scanner.mjs";
|
|
4
5
|
|
|
@@ -32,9 +33,14 @@ export function runBuildMode({ scanPath, outputPath, scannerArgs }) {
|
|
|
32
33
|
process.stderr.write(`dslinter: vite not installed in ${viteRoot}. Run npm install.\n`);
|
|
33
34
|
process.exit(1);
|
|
34
35
|
}
|
|
36
|
+
const scanAbs = resolve(scanPath);
|
|
35
37
|
const child = spawnSync(process.execPath, [viteBin, "build"], {
|
|
36
38
|
cwd: viteRoot,
|
|
37
39
|
stdio: "inherit",
|
|
40
|
+
env: {
|
|
41
|
+
...process.env,
|
|
42
|
+
DSLINT_SCAN_ROOT: scanAbs,
|
|
43
|
+
},
|
|
38
44
|
});
|
|
39
45
|
process.exit(child.status === null ? 1 : child.status);
|
|
40
46
|
}
|
package/bin/modes/dev.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { writeDevBanner } from "../lib/dev-banner.mjs";
|
|
13
13
|
import { findAvailablePort, warnIfPortBusy } from "../lib/port-check.mjs";
|
|
14
14
|
import { spawnScanner } from "../lib/run-scanner.mjs";
|
|
15
|
+
import { ensureDslintConfig } from "../lib/scaffold-config.mjs";
|
|
15
16
|
import { waitForPort } from "../lib/wait-for-port.mjs";
|
|
16
17
|
|
|
17
18
|
const POLL_MS = 150;
|
|
@@ -56,6 +57,10 @@ export async function runDevMode({ scanPath, outputPath, scannerArgs, servePort
|
|
|
56
57
|
const port = servePort ?? defaultServePort();
|
|
57
58
|
const reportPath = defaultReportPath(scanPath, outputPath);
|
|
58
59
|
const scanAbs = resolve(scanPath);
|
|
60
|
+
const configResult = ensureDslintConfig({ targetDir: scanAbs, layout: "default" });
|
|
61
|
+
if (configResult.created) {
|
|
62
|
+
process.stderr.write(`dslinter: scaffolded ${configResult.path}\n`);
|
|
63
|
+
}
|
|
59
64
|
const consumerViteRoot = findViteRoot(process.cwd());
|
|
60
65
|
const embedRoot = getDashboardPackageRoot();
|
|
61
66
|
const embedViteBin = hasEmbedDashboard() ? resolveViteBin(embedRoot) : null;
|
|
@@ -66,6 +71,10 @@ export async function runDevMode({ scanPath, outputPath, scannerArgs, servePort
|
|
|
66
71
|
process.env.DSLINTER_NO_EMBED_VITE?.trim() !== "1";
|
|
67
72
|
|
|
68
73
|
const bundledDist = useEmbedViteDev ? null : resolveBundledDashboardDir();
|
|
74
|
+
const useConsumerViteDev =
|
|
75
|
+
consumerViteRoot != null &&
|
|
76
|
+
bundledDist == null &&
|
|
77
|
+
process.env.DSLINTER_NO_CONSUMER_VITE?.trim() !== "1";
|
|
69
78
|
|
|
70
79
|
const args = [...scannerArgs];
|
|
71
80
|
const hasServe = args.some((a) => a === "--serve" || a.startsWith("--serve="));
|
|
@@ -172,7 +181,7 @@ export async function runDevMode({ scanPath, outputPath, scannerArgs, servePort
|
|
|
172
181
|
return;
|
|
173
182
|
}
|
|
174
183
|
|
|
175
|
-
if (
|
|
184
|
+
if (useConsumerViteDev) {
|
|
176
185
|
const viteBin = resolveViteBin(consumerViteRoot);
|
|
177
186
|
if (!viteBin) {
|
|
178
187
|
process.stderr.write(`dslinter: vite not installed in ${consumerViteRoot}. Run npm install.\n`);
|
|
@@ -183,9 +192,23 @@ export async function runDevMode({ scanPath, outputPath, scannerArgs, servePort
|
|
|
183
192
|
const uiPort = await resolveUiPort(5173);
|
|
184
193
|
const dashboardUrl = `http://localhost:${uiPort}/`;
|
|
185
194
|
|
|
195
|
+
const consumerConfig = join(
|
|
196
|
+
getDashboardPackageRoot(),
|
|
197
|
+
"vite",
|
|
198
|
+
"consumer.config.mjs",
|
|
199
|
+
);
|
|
186
200
|
const vite = spawn(
|
|
187
201
|
process.execPath,
|
|
188
|
-
[
|
|
202
|
+
[
|
|
203
|
+
viteBin,
|
|
204
|
+
"--config",
|
|
205
|
+
consumerConfig,
|
|
206
|
+
"--mode",
|
|
207
|
+
"serve",
|
|
208
|
+
"--port",
|
|
209
|
+
String(uiPort),
|
|
210
|
+
"--strictPort",
|
|
211
|
+
],
|
|
189
212
|
{
|
|
190
213
|
cwd: consumerViteRoot,
|
|
191
214
|
stdio: "inherit",
|
|
@@ -193,6 +216,7 @@ export async function runDevMode({ scanPath, outputPath, scannerArgs, servePort
|
|
|
193
216
|
...process.env,
|
|
194
217
|
DSLINT_SERVE_PORT: String(port),
|
|
195
218
|
DSLINT_SCAN_ROOT: scanAbs,
|
|
219
|
+
DSLINT_VITE_ROOT: consumerViteRoot,
|
|
196
220
|
},
|
|
197
221
|
},
|
|
198
222
|
);
|
package/bin/modes/init.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { ensureDslintConfig } from "../lib/scaffold-config.mjs";
|
|
4
5
|
|
|
5
6
|
const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
6
7
|
|
|
@@ -39,62 +40,64 @@ export function runInitMode(opts = {}) {
|
|
|
39
40
|
templateName,
|
|
40
41
|
);
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
);
|
|
46
|
-
|
|
43
|
+
const configResult = ensureDslintConfig({ targetDir, layout });
|
|
44
|
+
const wroteRegistry = !existsSync(registryPath);
|
|
45
|
+
if (wroteRegistry) {
|
|
46
|
+
mkdirSync(registryDir, { recursive: true });
|
|
47
|
+
copyFileSync(templatePath, registryPath);
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
mkdirSync(registryDir, { recursive: true });
|
|
50
|
-
copyFileSync(templatePath, registryPath);
|
|
51
|
-
|
|
52
50
|
const snippetPath = join(registryDir, "vite.dslinter.snippet.ts");
|
|
53
51
|
const snippetTemplate = join(
|
|
54
52
|
packageRoot,
|
|
55
53
|
"templates",
|
|
56
54
|
"vite.dslinter.snippet.ts",
|
|
57
55
|
);
|
|
58
|
-
if (!existsSync(snippetPath) && existsSync(snippetTemplate)) {
|
|
56
|
+
if (wroteRegistry && !existsSync(snippetPath) && existsSync(snippetTemplate)) {
|
|
59
57
|
copyFileSync(snippetTemplate, snippetPath);
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
const appHintPath = join(registryDir, "README.txt");
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
"",
|
|
81
|
-
|
|
82
|
-
|
|
61
|
+
if (wroteRegistry) {
|
|
62
|
+
writeFileSync(
|
|
63
|
+
appHintPath,
|
|
64
|
+
[
|
|
65
|
+
"Recommended (no registry file):",
|
|
66
|
+
"",
|
|
67
|
+
" import { DashboardLayout, useWorkspaceReport } from 'dslinter';",
|
|
68
|
+
" const dslinterReport = useWorkspaceReport({ reportUrl: '/dslint-report.json', watchUrl: '/events' });",
|
|
69
|
+
" <DashboardLayout autoPlayground dslinterReport={dslinterReport} ... />",
|
|
70
|
+
"",
|
|
71
|
+
"Optional — custom glob via this registry:",
|
|
72
|
+
"",
|
|
73
|
+
" import { buildPlaygroundEntries } from './playground/buildRegistry';",
|
|
74
|
+
" <DashboardLayout playgroundEntries={buildPlaygroundEntries(dslinterReport.report)} ... />",
|
|
75
|
+
"",
|
|
76
|
+
"Run the scanner from the repo root: npx dslinter .",
|
|
77
|
+
"",
|
|
78
|
+
].join("\n"),
|
|
79
|
+
);
|
|
80
|
+
}
|
|
83
81
|
|
|
84
82
|
process.stdout.write(
|
|
85
83
|
[
|
|
86
|
-
|
|
84
|
+
wroteRegistry
|
|
85
|
+
? "dslinter init: wrote playground registry"
|
|
86
|
+
: "dslinter init: playground registry already exists (kept)",
|
|
87
87
|
` ${registryPath}`,
|
|
88
88
|
"",
|
|
89
|
+
configResult.created
|
|
90
|
+
? "dslinter init: wrote config"
|
|
91
|
+
: "dslinter init: config already exists (kept)",
|
|
92
|
+
` ${configResult.path}`,
|
|
93
|
+
"",
|
|
89
94
|
`Layout: ${layout}`,
|
|
90
95
|
"",
|
|
91
96
|
"Next:",
|
|
92
|
-
` 1.
|
|
93
|
-
" 2.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
: " No @ alias overrides needed for dslinter internal UI.",
|
|
97
|
-
" 3. Run npx dslinter . from the project root",
|
|
97
|
+
` 1. Use <DashboardLayout autoPlayground /> (see ${registryDir}/README.txt)`,
|
|
98
|
+
" 2. Run npx dslinter . from the project root (merges dslinter/vite automatically)",
|
|
99
|
+
" Or add plugins: [dslinter()] from dslinter/vite for direct vite --mode serve",
|
|
100
|
+
" 3. Optional: use buildRegistry.ts for a narrower glob or control overrides",
|
|
98
101
|
"",
|
|
99
102
|
].join("\n"),
|
|
100
103
|
);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { existsSync, mkdtempSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { runInitMode } from "./init.mjs";
|
|
6
|
+
|
|
7
|
+
describe("runInitMode", () => {
|
|
8
|
+
it("scaffolds config and playground registry", () => {
|
|
9
|
+
const root = mkdtempSync(join(tmpdir(), "dslinter-init-"));
|
|
10
|
+
mkdirSync(join(root, "src", "components"), { recursive: true });
|
|
11
|
+
runInitMode({ targetDir: root, argv: [] });
|
|
12
|
+
|
|
13
|
+
expect(existsSync(join(root, ".dslint.json"))).toBe(true);
|
|
14
|
+
expect(existsSync(join(root, "src", "playground", "buildRegistry.ts"))).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
});
|