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.
- package/README.md +36 -15
- package/dist/cli.js +339 -180
- package/dist/config-lbfW91qB.d.ts +482 -0
- package/dist/{stdio-BcJth14k.js → design-system-A3pRX3Ka.js} +8439 -7212
- package/dist/eslint-worker.d.ts +36 -0
- package/dist/eslint-worker.js +95 -0
- package/dist/eslint.d.ts +23 -0
- package/dist/eslint.js +172 -0
- package/dist/index.d.ts +84 -387
- package/dist/index.js +3 -2
- package/dist/stdio-CEChNFLR.js +1396 -0
- package/dist/workspace-CHvUwgz9.js +227 -0
- package/package.json +22 -2
- package/schema.json +19 -1
|
@@ -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.
|
|
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://
|
|
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)
|
|
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"
|