onsystem 0.4.0 → 0.6.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.
@@ -0,0 +1,227 @@
1
+ import { J as hasOwnConfig, R as CONFIG_FILES, W as LEGACY_CONFIG_FILES, X as loadConfigWith, Y as loadConfig, _t as relativePath, ct as isWorkspaceRoot, ht as isInside, i as componentFiles, lt as workspacePackageDirs, ot as findDesignSystemPackage, rt as PackageFinder, ut as workspacePatterns } from "./design-system-A3pRX3Ka.js";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import os from "node:os";
5
+ //#region src/workspace.ts
6
+ /**
7
+ * What marks the folder a file belongs to: the nearest one at or above it with a config,
8
+ * a components.json, a package.json or an Nx project.json. The Claude Code hook finds a
9
+ * file's project the same way, so `check` from a workspace root and the hook agree on
10
+ * every file.
11
+ */
12
+ const PROJECT_MARKERS = [
13
+ ...CONFIG_FILES,
14
+ ...LEGACY_CONFIG_FILES,
15
+ "components.json",
16
+ "package.json",
17
+ "project.json"
18
+ ];
19
+ /**
20
+ * A monorepo seen from its root: every workspace package with a design system (an app
21
+ * that uses one, from source or a published library, or the design-system package
22
+ * itself), and how to find the project of
23
+ * any file in it. Without a design system at the root, `check`, `inspect` and the MCP
24
+ * server work from here, each file checked by its own project's design system.
25
+ */
26
+ var Workspace = class {
27
+ root;
28
+ source;
29
+ projects;
30
+ others;
31
+ configFile;
32
+ options;
33
+ rootConfig;
34
+ otherConfigs;
35
+ bare = /* @__PURE__ */ new Map();
36
+ constructor(root, source, projects, others, configFile, options, rootConfig, otherConfigs = []) {
37
+ this.root = root;
38
+ this.source = source;
39
+ this.projects = projects;
40
+ this.others = others;
41
+ this.configFile = configFile;
42
+ this.options = options;
43
+ this.rootConfig = rootConfig;
44
+ this.otherConfigs = otherConfigs;
45
+ }
46
+ /**
47
+ * What the configs of the workspace's packages have to say ({@link ResolvedConfig.warnings}),
48
+ * each once, as they were read when the workspace was found.
49
+ */
50
+ get warnings() {
51
+ const configs = [
52
+ this.rootConfig,
53
+ ...this.projects.map((p) => p.config),
54
+ ...this.otherConfigs
55
+ ];
56
+ return [...new Set(configs.flatMap((c) => c.warnings ?? []))];
57
+ }
58
+ /**
59
+ * The project `target` (a file or a folder) belongs to: the nearest folder at or above
60
+ * it with one of the `PROJECT_MARKERS`, as the hook finds it, up to the workspace root. A folder that holds no design system still gets
61
+ * its config, so its files are checked as `check` from that folder would check them.
62
+ */
63
+ async projectFor(target) {
64
+ const dir = projectDirOf(path.resolve(this.root, target), this.root);
65
+ const known = this.projects.find((p) => p.root === dir);
66
+ if (known) return known;
67
+ let project = this.bare.get(dir);
68
+ if (!project) {
69
+ project = dir === this.root ? Promise.resolve(this.rootProject()) : this.load(dir);
70
+ this.bare.set(dir, project);
71
+ }
72
+ return project;
73
+ }
74
+ rootProject() {
75
+ return {
76
+ root: this.root,
77
+ dir: ".",
78
+ name: packageName(this.root),
79
+ config: this.rootConfig
80
+ };
81
+ }
82
+ /** A project's config read again, as `check` from its folder would read it. */
83
+ configFor(project) {
84
+ return project.root === this.root ? loadConfig(this.options) : loadProjectConfig(project.root, this.root, this.configFile, this.options);
85
+ }
86
+ async load(dir) {
87
+ return {
88
+ root: dir,
89
+ dir: relativePath(this.root, dir) || ".",
90
+ name: packageName(dir),
91
+ config: await loadProjectConfig(dir, this.root, this.configFile, this.options)
92
+ };
93
+ }
94
+ };
95
+ /**
96
+ * The config at `root`, or a `Workspace` when `root` is a monorepo root with no design
97
+ * system of its own. The root is one project, as before, when a config file or
98
+ * `--components` sets the components, when `--config`, `--tokens` or `--docs` is given,
99
+ * or when zero config finds a design system there.
100
+ */
101
+ async function loadTarget(options) {
102
+ const config = await loadConfig(options);
103
+ if (options.config !== void 0 || Boolean(options.components?.length) || Boolean(options.tokens?.length) || Boolean(options.docs?.length) || config.configured || !isWorkspaceRoot(config.root)) return { config };
104
+ if (config.detected !== void 0 || (await componentFiles(config)).length) return { config };
105
+ return {
106
+ config,
107
+ workspace: await discoverWorkspace(config, options)
108
+ };
109
+ }
110
+ /** Every workspace package with a design system, and the rest by folder. */
111
+ async function discoverWorkspace(rootConfig, options) {
112
+ const { root } = rootConfig;
113
+ const source = [
114
+ workspacePatterns(root).source,
115
+ fs.existsSync(path.join(root, "nx.json")) ? "nx.json" : "",
116
+ fs.existsSync(path.join(root, "turbo.json")) ? "turbo.json" : ""
117
+ ].filter(Boolean).join(", ");
118
+ const projects = [];
119
+ const others = [];
120
+ const otherConfigs = [];
121
+ for (const dir of workspacePackageDirs(root)) {
122
+ const config = await loadProjectConfig(dir, root, rootConfig.configFile, options);
123
+ const files = (await componentFiles(config)).length;
124
+ const own = config.configFile !== void 0 && config.configFile !== rootConfig.configFile;
125
+ const library = config.libraries?.some((l) => l.dir !== void 0) ?? false;
126
+ if (files || own || library) projects.push({
127
+ root: dir,
128
+ dir: relativePath(root, dir),
129
+ name: packageName(dir),
130
+ config,
131
+ componentFiles: files
132
+ });
133
+ else {
134
+ others.push(relativePath(root, dir));
135
+ if (config.warnings) otherConfigs.push(config);
136
+ }
137
+ }
138
+ return new Workspace(root, source, projects, others, rootConfig.configFile, options, rootConfig, otherConfigs);
139
+ }
140
+ /**
141
+ * A project's config: its own config file, else the config its design-system package declares
142
+ * (a dependency with `"onsystem"` in its package.json), else the workspace root's, with paths
143
+ * relative to the workspace root, else zero config. `loadConfig` from the project's folder finds
144
+ * the first two the same way. CLI flags that apply to every project carry over.
145
+ */
146
+ function loadProjectConfig(dir, workspaceRoot, rootConfigFile, options) {
147
+ const shared = {
148
+ cwd: options.cwd,
149
+ includeDesignSystem: options.includeDesignSystem,
150
+ jsonConfigOnly: options.jsonConfigOnly
151
+ };
152
+ const packages = new PackageFinder(dir);
153
+ if (!rootConfigFile || hasOwnConfig(dir)) return loadConfigWith({
154
+ ...shared,
155
+ root: dir
156
+ }, { packages });
157
+ const lookup = findDesignSystemPackage(dir, packages);
158
+ if (lookup?.found || lookup?.problems.length) return loadConfigWith({
159
+ ...shared,
160
+ root: dir
161
+ }, {
162
+ packages,
163
+ lookup
164
+ });
165
+ return loadConfigWith({
166
+ ...shared,
167
+ root: dir,
168
+ config: rootConfigFile,
169
+ base: workspaceRoot
170
+ }, {
171
+ packages,
172
+ lookup
173
+ });
174
+ }
175
+ /** The nearest folder at or above `target` (a file or folder) with a project marker, up to `root`. */
176
+ function projectDirOf(target, root) {
177
+ let dir = isDirectory(target) ? target : path.dirname(target);
178
+ while (isInside(root, dir) && dir !== root) {
179
+ if (hasProjectMarker(dir)) return dir;
180
+ dir = path.dirname(dir);
181
+ }
182
+ return root;
183
+ }
184
+ /**
185
+ * The folder to serve `target` (an absolute file or folder, which need not exist yet) from,
186
+ * found by walking up from it: the nearest folder with a project marker, as the Claude Code
187
+ * hook finds a file's project, or the monorepo root above that folder when there is one, as
188
+ * a client that reports the repository as its workspace would have the server started.
189
+ * `loadTarget` there serves the root's own design system, or each project in it. The home
190
+ * folder and the filesystem root are never one. Undefined when no other folder up to the
191
+ * filesystem root has a project marker.
192
+ */
193
+ function findProjectRoot(target, home = os.homedir()) {
194
+ const skipped = (dir) => dir === path.dirname(dir) || samePath(dir, home);
195
+ let dir = isDirectory(target) ? target : path.dirname(target);
196
+ while (skipped(dir) || !hasProjectMarker(dir)) {
197
+ const parent = path.dirname(dir);
198
+ if (parent === dir) return void 0;
199
+ dir = parent;
200
+ }
201
+ for (let up = dir; up !== path.dirname(up); up = path.dirname(up)) if (!skipped(up) && isWorkspaceRoot(up)) return up;
202
+ return dir;
203
+ }
204
+ function samePath(a, b) {
205
+ return path.relative(path.resolve(a), path.resolve(b)) === "";
206
+ }
207
+ /** Whether `dir` holds one of the `PROJECT_MARKERS`. */
208
+ function hasProjectMarker(dir) {
209
+ return PROJECT_MARKERS.some((name) => fs.existsSync(path.join(dir, name)));
210
+ }
211
+ function packageName(dir) {
212
+ try {
213
+ const name = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")).name;
214
+ return typeof name === "string" ? name : void 0;
215
+ } catch {
216
+ return;
217
+ }
218
+ }
219
+ function isDirectory(target) {
220
+ try {
221
+ return fs.statSync(target).isDirectory();
222
+ } catch {
223
+ return false;
224
+ }
225
+ }
226
+ //#endregion
227
+ export { hasProjectMarker as n, loadTarget as r, findProjectRoot as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onsystem",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "mcpName": "io.github.dgesteves/onsystem",
5
5
  "description": "Keeps coding agents on your design system: it knows your real components, props, variants and tokens, catches the moment an agent invents one and has it fix it, and the same check gates your PRs. Local, zero config, works alongside @shadcn/lint.",
6
6
  "keywords": [
@@ -9,6 +9,8 @@
9
9
  "agent-guardrails",
10
10
  "linter",
11
11
  "lint",
12
+ "eslint",
13
+ "eslint-plugin",
12
14
  "react",
13
15
  "shadcn",
14
16
  "cva",
@@ -23,7 +25,7 @@
23
25
  "mcp",
24
26
  "model-context-protocol"
25
27
  ],
26
- "homepage": "https://github.com/dgesteves/onsystem#readme",
28
+ "homepage": "https://onsystem.vercel.app",
27
29
  "bugs": "https://github.com/dgesteves/onsystem/issues",
28
30
  "repository": {
29
31
  "type": "git",
@@ -37,6 +39,10 @@
37
39
  "types": "./dist/index.d.ts",
38
40
  "default": "./dist/index.js"
39
41
  },
42
+ "./eslint": {
43
+ "types": "./dist/eslint.d.ts",
44
+ "default": "./dist/eslint.js"
45
+ },
40
46
  "./schema.json": "./schema.json",
41
47
  "./package.json": "./package.json"
42
48
  },
@@ -62,8 +68,17 @@
62
68
  "stemmer": "^2.0.1",
63
69
  "tinyglobby": "^0.2.17",
64
70
  "typescript": "~6.0.3",
71
+ "yaml": "^2.9.1",
65
72
  "zod": "^4.6.5"
66
73
  },
74
+ "peerDependencies": {
75
+ "eslint": ">=9.0.0"
76
+ },
77
+ "peerDependenciesMeta": {
78
+ "eslint": {
79
+ "optional": true
80
+ }
81
+ },
67
82
  "devDependencies": {
68
83
  "@changesets/changelog-github": "^1.0.1",
69
84
  "@changesets/cli": "^3.0.3",
@@ -72,8 +87,12 @@
72
87
  "@modelcontextprotocol/sdk": "^1.32.1",
73
88
  "@types/culori": "^4.0.1",
74
89
  "@types/node": "^24.19.1",
90
+ "ajv": "^8.20.0",
91
+ "ajv-draft-04": "^1.0.0",
92
+ "ajv-formats": "^3.0.1",
75
93
  "eslint": "^10.12.0",
76
94
  "eslint-config-prettier": "^10.1.8",
95
+ "eslint-v9": "npm:eslint@9.39.5",
77
96
  "geist": "^1.7.2",
78
97
  "globals": "^17.13.0",
79
98
  "opentype.js": "^2.0.0",
@@ -98,6 +117,7 @@
98
117
  "pack-test": "node scripts/pack-test.mjs",
99
118
  "assets": "node scripts/build-assets.mjs",
100
119
  "palette": "node scripts/build-palette.mjs",
120
+ "sync-plugins": "node scripts/sync-plugins.mjs",
101
121
  "corpus": "node scripts/corpus.mjs",
102
122
  "demo:check": "cd examples/shadcn-demo && node ../../dist/cli.js check \"app/**/*.tsx\"",
103
123
  "demo:inspect": "node dist/cli.js inspect --root examples/shadcn-demo",
package/schema.json CHANGED
@@ -29,7 +29,7 @@
29
29
  }
30
30
  },
31
31
  "tokens": {
32
- "description": "Token files: W3C DTCG JSON (*.json) or CSS custom properties (*.css).",
32
+ "description": "Token files: W3C DTCG JSON (*.json), CSS custom properties (*.css) or a DESIGN.md's front matter (*.md).",
33
33
  "anyOf": [
34
34
  {
35
35
  "anyOf": [
@@ -116,6 +116,24 @@
116
116
  "type": "string"
117
117
  }
118
118
  },
119
+ "libraries": {
120
+ "description": "Published component libraries to read from their type declarations when the project depends on them. Omitted: every one onsystem reads (mui). false: none. A list: only those, e.g. [\"mui\"].",
121
+ "anyOf": [
122
+ {
123
+ "type": "boolean",
124
+ "const": false
125
+ },
126
+ {
127
+ "type": "array",
128
+ "items": {
129
+ "type": "string",
130
+ "enum": [
131
+ "mui"
132
+ ]
133
+ }
134
+ }
135
+ ]
136
+ },
119
137
  "includeDesignSystem": {
120
138
  "description": "check: lint the design system's own component files too. Off by default: they implement the scale and primitives the rules enforce.",
121
139
  "type": "boolean"