dslinter 0.1.1 → 0.1.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.
@@ -0,0 +1,116 @@
1
+ import type { PlaygroundSpec, WorkspaceReport } from "../types/report";
2
+ import type { BuildPlaygroundModules, BuildPlaygroundOptions } from "./buildPlaygroundEntriesFromReport";
3
+
4
+ export type PlaygroundJoinSkipReason = "module_not_found" | "export_not_found";
5
+
6
+ export type PlaygroundJoinSkip = {
7
+ export_name: string;
8
+ rel_path: string;
9
+ globKey: string;
10
+ reason: PlaygroundJoinSkipReason;
11
+ };
12
+
13
+ /**
14
+ * Maps report `rel_path` to a Vite `import.meta.glob` key when the registry lives in
15
+ * `src/playground/` and components live under `src/components/`.
16
+ *
17
+ * Example: `src/components/ui/button.tsx` → `../components/ui/button.tsx`
18
+ */
19
+ export function defaultConsumerGlobKeyFromRelPath(relPath: string): string {
20
+ const trimmed = relPath.replace(/^\/+/, "").replace(/^src\//, "");
21
+ return `../${trimmed}`;
22
+ }
23
+
24
+ export function defaultEmbedGlobKeyFromRelPath(relPath: string): string {
25
+ const trimmed = relPath.replace(/^\/+/, "");
26
+ return `@dslint-scan/${trimmed}`;
27
+ }
28
+
29
+ function getExport(
30
+ mod: Record<string, unknown>,
31
+ exportName: string,
32
+ ): unknown {
33
+ const x = mod[exportName];
34
+ return typeof x === "function" ? x : undefined;
35
+ }
36
+
37
+ /**
38
+ * For each playground spec, explain why it would not join to `modules`.
39
+ */
40
+ export function diagnosePlaygroundJoinSkips(
41
+ report: WorkspaceReport | null | undefined,
42
+ modules: BuildPlaygroundModules,
43
+ options: Pick<BuildPlaygroundOptions, "globKeyFromRelPath"> = {},
44
+ ): PlaygroundJoinSkip[] {
45
+ const specs = report?.playgrounds;
46
+ if (!specs?.length) return [];
47
+
48
+ const globKeyFromRelPath =
49
+ options.globKeyFromRelPath ?? defaultEmbedGlobKeyFromRelPath;
50
+
51
+ const skipped: PlaygroundJoinSkip[] = [];
52
+ for (const spec of specs) {
53
+ const globKey = globKeyFromRelPath(spec.rel_path);
54
+ const mod = modules[globKey];
55
+ if (!mod) {
56
+ skipped.push({
57
+ export_name: spec.export_name,
58
+ rel_path: spec.rel_path,
59
+ globKey,
60
+ reason: "module_not_found",
61
+ });
62
+ continue;
63
+ }
64
+ if (!getExport(mod, spec.export_name)) {
65
+ skipped.push({
66
+ export_name: spec.export_name,
67
+ rel_path: spec.rel_path,
68
+ globKey,
69
+ reason: "export_not_found",
70
+ });
71
+ }
72
+ }
73
+ return skipped;
74
+ }
75
+
76
+ /**
77
+ * Dev-only: log skipped playground joins (module glob / export name mismatches).
78
+ */
79
+ export function logPlaygroundJoinSkips(
80
+ skipped: PlaygroundJoinSkip[],
81
+ options?: { label?: string },
82
+ ): void {
83
+ if (!skipped.length) return;
84
+ if (typeof import.meta !== "undefined" && !import.meta.env?.DEV) return;
85
+
86
+ const label = options?.label ?? "[dslinter] playground preview";
87
+ console.warn(
88
+ `${label}: ${skipped.length} component(s) have a scan row but no live preview.`,
89
+ );
90
+ for (const s of skipped.slice(0, 12)) {
91
+ const hint =
92
+ s.reason === "module_not_found"
93
+ ? `add import.meta.glob key "${s.globKey}" (from rel_path "${s.rel_path}")`
94
+ : `export function ${s.export_name} from "${s.rel_path}"`;
95
+ console.warn(` - ${s.export_name}: ${hint}`);
96
+ }
97
+ if (skipped.length > 12) {
98
+ console.warn(` … and ${skipped.length - 12} more`);
99
+ }
100
+ }
101
+
102
+ export function findPlaygroundSpec(
103
+ report: WorkspaceReport | null | undefined,
104
+ componentId: string,
105
+ ): PlaygroundSpec | undefined {
106
+ return report?.playgrounds?.find(
107
+ (p) => p.export_name === componentId || p.id === componentId,
108
+ );
109
+ }
110
+
111
+ export function findPlaygroundJoinSkip(
112
+ skipped: PlaygroundJoinSkip[] | undefined,
113
+ componentId: string,
114
+ ): PlaygroundJoinSkip | undefined {
115
+ return skipped?.find((s) => s.export_name === componentId);
116
+ }
@@ -20,6 +20,10 @@ import { TokensPane } from "../components/TokensPane";
20
20
  import { DashboardCommandPalette } from "../components/DashboardCommandPalette";
21
21
  import { componentCatalogNamesFromReport } from "../dashboard/aggregate";
22
22
  import { resolvePlaygroundEntry } from "../playground/buildPlaygroundEntriesFromReport";
23
+ import {
24
+ findPlaygroundJoinSkip,
25
+ type PlaygroundJoinSkip,
26
+ } from "../playground/playgroundJoin";
23
27
  import { useHashRoute } from "./useHashRoute";
24
28
 
25
29
  const STORAGE_KEY = "dslinter-dashboard-theme";
@@ -121,6 +125,8 @@ export function useDashboardTheme(): DashboardThemeContextValue {
121
125
 
122
126
  export type DashboardLayoutProps = {
123
127
  playgroundEntries: PlaygroundEntry[];
128
+ /** Join failures from `buildPlaygroundEntriesFromReportWithSkips` — powers inspect-pane hints. */
129
+ playgroundJoinSkips?: PlaygroundJoinSkip[];
124
130
  tokenCatalog?: TokenCatalog;
125
131
  /** Custom intro shown above the governance inventory on `#!/governance`; defaults to package copy. */
126
132
  overview?: ReactNode;
@@ -136,6 +142,7 @@ export type DashboardLayoutProps = {
136
142
 
137
143
  function DashboardLayoutInner({
138
144
  playgroundEntries,
145
+ playgroundJoinSkips,
139
146
  tokenCatalog,
140
147
  overview,
141
148
  reportUrl,
@@ -202,6 +209,10 @@ function DashboardLayoutInner({
202
209
  workspaceReport={dslinterReport.report}
203
210
  reportReady={reportReady}
204
211
  hasPlaygroundSpec={hasPlaygroundSpec}
212
+ playgroundJoinSkip={findPlaygroundJoinSkip(
213
+ playgroundJoinSkips,
214
+ componentId,
215
+ )}
205
216
  onBackToGovernance={() => navigate({ view: "governance" })}
206
217
  />
207
218
  );
@@ -0,0 +1,26 @@
1
+ import type { PlaygroundEntry, WorkspaceReport } from "dslinter";
2
+ import { createPlaygroundRegistry } from "dslinter";
3
+
4
+ /**
5
+ * Eager Vite glob — must cover every path in `dslint-report.json` → `playgrounds[].rel_path`.
6
+ * Nested paths (e.g. `src/components/ui/button.tsx`) require `**`, not a single `*`.
7
+ */
8
+ const modules = import.meta.glob("../components/**/*.{tsx,jsx}", {
9
+ eager: true,
10
+ }) as Record<string, Record<string, unknown>>;
11
+
12
+ const buildWithSkips = createPlaygroundRegistry(modules);
13
+
14
+ /** Live previews for the dashboard (`DashboardLayout` → `playgroundEntries`). */
15
+ export function buildPlaygroundEntries(
16
+ report: WorkspaceReport | null | undefined,
17
+ ): PlaygroundEntry[] {
18
+ return buildWithSkips(report).entries;
19
+ }
20
+
21
+ /** Skipped joins (module glob / export mismatch) — useful for debugging previews. */
22
+ export function getPlaygroundJoinSkips(
23
+ report: WorkspaceReport | null | undefined,
24
+ ) {
25
+ return buildWithSkips(report).skipped;
26
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Add to your existing `vite.config.ts` when using `npx dslinter` dev mode.
3
+ * Adjust paths if your app layout differs from `src/` + `public/dslint-report.json`.
4
+ */
5
+ import path from "node:path";
6
+
7
+ const DSLINT_SERVE_PORT = Number(process.env.DSLINT_SERVE_PORT ?? "7878");
8
+
9
+ // Inside defineConfig(({ mode }) => ({ ... })):
10
+ export const dslinterViteSnippet = {
11
+ resolve: {
12
+ dedupe: ["react", "react-dom"],
13
+ },
14
+ server: {
15
+ proxy:
16
+ // mode === "serve" when started via `npx dslinter` (not plain `vite`)
17
+ {
18
+ "/dslint-report.json": {
19
+ target: `http://127.0.0.1:${DSLINT_SERVE_PORT}`,
20
+ changeOrigin: true,
21
+ },
22
+ "/events": {
23
+ target: `http://127.0.0.1:${DSLINT_SERVE_PORT}`,
24
+ changeOrigin: true,
25
+ },
26
+ },
27
+ },
28
+ };