onsystem 0.10.1 → 0.10.3
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/dist/cli.js +23 -13
- package/dist/{config-BbTW1jKU.d.ts → config-JaNAiR9Z.d.ts} +6 -8
- package/dist/{design-system-CPH4C7-1.js → design-system-7wZoJsMm.js} +240 -121
- package/dist/eslint-worker.d.ts +2 -4
- package/dist/eslint-worker.js +3 -4
- package/dist/eslint.d.ts +0 -2
- package/dist/eslint.js +3 -5
- package/dist/index.d.ts +40 -55
- package/dist/index.js +2 -2
- package/dist/{stdio-BK2kMN1x.js → stdio-B31hp2UZ.js} +182 -57
- package/dist/{workspace-BINqxi7p.js → workspace-C5pMS6Xv.js} +10 -19
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as hasProjectMarker, r as loadTarget, t as findProjectRoot } from "./workspace-
|
|
1
|
+
import { At as relativePath, Ct as isWorkspaceRoot, Dt as isInside, Et as SCRIPT_FILE, Ft as truncate, It as unique, J as ConfigError, Lt as defaultProjectConfig, Mt as closest, Nt as json, Pt as plural, X as DEFAULT_SEVERITY, bt as detectProject, ct as loadConfig, d as descriptionOf, dt as formatPx, f as searchComponents, gt as notReadYet, h as uncheckedNotice, ht as libraryTitle, kt as pathOf, mt as LIBRARY_IDS, n as DesignSystemHost, ot as excludedBy, pt as LIBRARIES, q as CONFIG_FILES, r as buildModel, rt as RULE_IDS, tt as LEGACY_CONFIG_FILES, u as buildSearchIndex, v as NAME, xt as findDesignSystemPackage, y as VERSION, z as TOKEN_CATEGORIES } from "./design-system-7wZoJsMm.js";
|
|
2
|
+
import { i as projectDirOf, n as hasProjectMarker, r as loadTarget, t as findProjectRoot } from "./workspace-C5pMS6Xv.js";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -7,7 +7,7 @@ import * as z from "zod";
|
|
|
7
7
|
import fsp from "node:fs/promises";
|
|
8
8
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/server";
|
|
9
9
|
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
10
|
-
|
|
10
|
+
// src/explain.ts
|
|
11
11
|
/**
|
|
12
12
|
* What `inspect --explain` says about one project: every candidate zero config looked
|
|
13
13
|
* at and why each was taken or not, the config it resolved to, tokens whose value did
|
|
@@ -19,8 +19,11 @@ function explainProject(ds, cwd, indent = "") {
|
|
|
19
19
|
const out = (line = "") => lines.push(line ? `${indent}${line}` : "");
|
|
20
20
|
out("How the design system was found");
|
|
21
21
|
for (const step of packageSteps(config, cwd)) out(` ${(step.accepted ? "found" : "no").padEnd(6)} ${step.candidate}: ${step.reason}`);
|
|
22
|
-
if (config.configured)
|
|
23
|
-
|
|
22
|
+
if (config.configured) {
|
|
23
|
+
out(` components come from ${config.inherits && config.designSystemPackage ? display(cwd, config.designSystemPackage.config) : config.configFile ? `${display(cwd, config.configFile)}${over(config, cwd)}` : "--components"}, so zero config did not look`);
|
|
24
|
+
const unpublished = unpublishedComponents(ds);
|
|
25
|
+
if (unpublished) out(` ${unpublished}`);
|
|
26
|
+
} else {
|
|
24
27
|
const steps = [];
|
|
25
28
|
detectProject(config.root, config.tsconfig, steps, config.designSystemPackage);
|
|
26
29
|
for (const step of steps) out(` ${(step.accepted ? "found" : "no").padEnd(6)} ${step.candidate}: ${step.reason}`);
|
|
@@ -85,6 +88,22 @@ function explainProject(ds, cwd, indent = "") {
|
|
|
85
88
|
}
|
|
86
89
|
return lines;
|
|
87
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Why a project that takes on the config of a design-system package installed in node_modules
|
|
93
|
+
* knows none of its components: the package publishes neither their sources nor their type
|
|
94
|
+
* declarations, or `"libraries": false` turns off the declarations it publishes. The one message
|
|
95
|
+
* for it, where a project without components would otherwise be told to check the "components"
|
|
96
|
+
* globs. Undefined otherwise.
|
|
97
|
+
*/
|
|
98
|
+
function unpublishedComponents(ds) {
|
|
99
|
+
const declared = ds.config.designSystemPackage;
|
|
100
|
+
if (!declared?.installed || !ds.config.inherits || ds.model.components.length) return void 0;
|
|
101
|
+
const tokens = ds.model.tokens.length ? " The token rules still check against the tokens it publishes." : "";
|
|
102
|
+
const name = pathOf(declared.name);
|
|
103
|
+
const see = "See https://onsystem.vercel.app/docs/design-system-packages#what-apps-get";
|
|
104
|
+
if (ds.config.librariesOff?.some((library) => library.package === declared.name)) return `${name} publishes no component sources, and "libraries": false in the config turns off the type declarations it publishes, so its components are unknown here and the component rules did not run: set "libraries" to [], which reads its declarations and no library, or remove it.${tokens} ${see}`;
|
|
105
|
+
return `${name} publishes neither its component sources nor their type declarations, so its components are unknown here and the component rules did not run: ask its team to add its sources to "files" in its package.json, or to publish type declarations.${tokens} ${see}`;
|
|
106
|
+
}
|
|
88
107
|
/** Why a library the project depends on is not read: `left out by "libraries" in onsystem.config.json`. */
|
|
89
108
|
function librariesLeftOut(config, cwd) {
|
|
90
109
|
return `left out by "libraries" in ${config.configFile ? display(cwd, config.configFile) : "the config"}`;
|
|
@@ -151,8 +170,7 @@ function packageSteps(config, cwd) {
|
|
|
151
170
|
reason: `${declared.name} (${declared.installed ? "installed" : "a workspace package"}) declares ${display(cwd, declared.config)}, which this project ${config.configFile === declared.config ? "takes on" : `reads under ${display(cwd, config.configFile ?? "")}`}, with its paths from ${display(cwd, declared.dir)}${lookup.others.length ? `; ${lookup.others.join(", ")} also declare one, not read: one config is taken on, from the package the code imports most` : ""}${skipped}`
|
|
152
171
|
}];
|
|
153
172
|
}
|
|
154
|
-
|
|
155
|
-
//#region src/not-read.ts
|
|
173
|
+
// src/not-read.ts
|
|
156
174
|
const LIMITS_DOCS = "https://onsystem.vercel.app/docs/how-it-works#limits";
|
|
157
175
|
/**
|
|
158
176
|
* What `check` and `check_ui` could not read, said after the results as the notice for missing
|
|
@@ -228,8 +246,7 @@ function listOf$2(items, max = 3) {
|
|
|
228
246
|
if (items.length <= max) return `${items.slice(0, -1).join(", ")} and ${items.at(-1) ?? ""}`;
|
|
229
247
|
return `${items.slice(0, max).join(", ")} and ${items.length - max} more`;
|
|
230
248
|
}
|
|
231
|
-
|
|
232
|
-
//#region src/server/render.ts
|
|
249
|
+
// src/server/render.ts
|
|
233
250
|
/**
|
|
234
251
|
* Tool results are read by models, so they are rendered as compact Markdown:
|
|
235
252
|
* fewer tokens than JSON, and the same information. The JSON is still sent
|
|
@@ -564,8 +581,12 @@ function listTail(listed) {
|
|
|
564
581
|
if (listed.omitted) lines.push("", `${listed.omitted.toLocaleString("en-US")} more not shown: pass a higher \`limit\`, or a \`query\`.`);
|
|
565
582
|
return lines;
|
|
566
583
|
}
|
|
567
|
-
/**
|
|
568
|
-
|
|
584
|
+
/**
|
|
585
|
+
* The shared explanation when a project has no design-system components to show. `reason`, when
|
|
586
|
+
* known, says why instead ({@link unpublishedComponents}).
|
|
587
|
+
*/
|
|
588
|
+
function noComponentsMessage(root, project, monorepo = false, reason) {
|
|
589
|
+
if (reason) return answer([reason]);
|
|
569
590
|
if (monorepo) return answer([`No design-system components found in any project of this monorepo (root: ${root}). Check the "components" globs in the config. Run \`onsystem inspect\` there to see what was looked at.`]);
|
|
570
591
|
return answer([`No design-system components found${project ? ` in ${pathOf(project)}` : ""}. Check the "components" globs in the config (root: ${root}).`]);
|
|
571
592
|
}
|
|
@@ -573,7 +594,7 @@ function noComponentsMessage(root, project, monorepo = false) {
|
|
|
573
594
|
function renderComponentList(ds, project, filter = {}) {
|
|
574
595
|
const roots = ds.roots();
|
|
575
596
|
const where = project ? ` in ${pathOf(project)}` : "";
|
|
576
|
-
if (!roots.length) return noComponentsMessage(ds.root, project);
|
|
597
|
+
if (!roots.length) return noComponentsMessage(ds.root, project, false, unpublishedComponents(ds));
|
|
577
598
|
const listed = listComponents(roots, (c) => c, filter);
|
|
578
599
|
if (!listed.matched) return answer([`No components match ${filterText(filter)}${where} (${roots.length} in all). Call list_components without filters to see them.`]);
|
|
579
600
|
const marks = new Marks();
|
|
@@ -688,7 +709,7 @@ function renderAmbiguous(name, entries) {
|
|
|
688
709
|
}
|
|
689
710
|
/** get_component's answer for a name no component has, with the nearest and all of them. */
|
|
690
711
|
function renderNotFound(name, guess, names, where = "") {
|
|
691
|
-
return answer([`No component named ${json(name)}${where}.` + (guess ? ` Did you mean ${json(guess)}?` : "") + ` Available: ${names.join(", ")}.`]);
|
|
712
|
+
return answer([`No component named ${json(name)}${where}.` + (guess ? ` Did you mean ${json(guess)}?` : "") + (names.length ? ` Available: ${names.join(", ")}.` : "")]);
|
|
692
713
|
}
|
|
693
714
|
/**
|
|
694
715
|
* The build-with-design-system prompt: `task`, the steps, and what the design system has, its
|
|
@@ -1080,8 +1101,7 @@ function renderCheck(result, capped = capDiagnostics(result, Infinity), notices
|
|
|
1080
1101
|
lines.push("", result.errorCount ? "Fix every error, then run check_ui again." : "Only warnings: fix them if the design system has an equivalent.", ...after);
|
|
1081
1102
|
return answer(lines);
|
|
1082
1103
|
}
|
|
1083
|
-
|
|
1084
|
-
//#region src/server/index.ts
|
|
1104
|
+
// src/server/index.ts
|
|
1085
1105
|
const INSTRUCTIONS = `This server describes the project's design system: its React components (props, variants, parts), design tokens and usage docs, and lints UI code against it.
|
|
1086
1106
|
When writing or changing UI in this project:
|
|
1087
1107
|
1. Find components with search_components (by intent) or list_components.
|
|
@@ -1555,7 +1575,7 @@ function register(serve, { getDesignSystem, getProjects }) {
|
|
|
1555
1575
|
const { ds } = found;
|
|
1556
1576
|
const component = ds.getComponent(name);
|
|
1557
1577
|
if (!component) {
|
|
1558
|
-
if (!ds.roots().length) return refuse(noComponentsMessage(ds.root, found.project?.dir));
|
|
1578
|
+
if (!ds.roots().length) return refuse(noComponentsMessage(ds.root, found.project?.dir, false, unpublishedComponents(ds)));
|
|
1559
1579
|
const names = ds.roots().map((c) => c.name);
|
|
1560
1580
|
return refuse(renderNotFound(name, ds.suggestComponent(name), names));
|
|
1561
1581
|
}
|
|
@@ -1822,8 +1842,7 @@ function register(serve, { getDesignSystem, getProjects }) {
|
|
|
1822
1842
|
return prompted("Build UI with the design system", renderPrompt(task, "catalog" in found ? [...new Map(found.catalog.roots().map((e) => [e.component.name, e.component])).values()] : found.ds.roots(), "catalog" in found ? found.catalog.getTokens().map((t) => t.token) : found.ds.tokens, "catalog" in found ? found.set.dirs : void 0));
|
|
1823
1843
|
});
|
|
1824
1844
|
}
|
|
1825
|
-
|
|
1826
|
-
//#region src/catalog.ts
|
|
1845
|
+
// src/catalog.ts
|
|
1827
1846
|
/**
|
|
1828
1847
|
* Every project's components and tokens in one list, for a workspace root when no path
|
|
1829
1848
|
* picks a project. A component used by several projects (a design-system package) is
|
|
@@ -1965,8 +1984,7 @@ function packageOwner(root) {
|
|
|
1965
1984
|
return name;
|
|
1966
1985
|
};
|
|
1967
1986
|
}
|
|
1968
|
-
|
|
1969
|
-
//#region src/server/projects.ts
|
|
1987
|
+
// src/server/projects.ts
|
|
1970
1988
|
/**
|
|
1971
1989
|
* A workspace root's projects for the MCP server: one `DesignSystemHost` per project,
|
|
1972
1990
|
* created on first use and watched from then on, and the merged catalog, rebuilt when a
|
|
@@ -2025,6 +2043,7 @@ var WorkspaceProjects = class {
|
|
|
2025
2043
|
host = new DesignSystemHost(project.config, {
|
|
2026
2044
|
cache: this.options.cache,
|
|
2027
2045
|
logger: this.options.logger,
|
|
2046
|
+
build: this.options.build,
|
|
2028
2047
|
loadConfig: () => this.workspace.configFor(project)
|
|
2029
2048
|
});
|
|
2030
2049
|
host.onChange(() => this.options.onChange?.());
|
|
@@ -2033,8 +2052,7 @@ var WorkspaceProjects = class {
|
|
|
2033
2052
|
return host;
|
|
2034
2053
|
}
|
|
2035
2054
|
};
|
|
2036
|
-
|
|
2037
|
-
//#region src/server/stdio.ts
|
|
2055
|
+
// src/server/stdio.ts
|
|
2038
2056
|
/**
|
|
2039
2057
|
* Starts the server (on stdio unless another transport is given), for clients on
|
|
2040
2058
|
* either protocol era: a 2026-07-28 client (stateless, `server/discover`) and a
|
|
@@ -2054,8 +2072,17 @@ var WorkspaceProjects = class {
|
|
|
2054
2072
|
* project marker, or the monorepo root above that, served as if the server had been
|
|
2055
2073
|
* started there, except that a config there is read only when it is JSON: a prompt can
|
|
2056
2074
|
* choose the path, so a JavaScript or TypeScript config is not run. Each root found is
|
|
2057
|
-
* loaded once and kept; a request without an absolute path is answered from
|
|
2058
|
-
* found so far.
|
|
2075
|
+
* loaded once and kept, by its real path; a request without an absolute path is answered from
|
|
2076
|
+
* the one root found so far. The same holds for a `--root` (or a client's root) that is no
|
|
2077
|
+
* project but holds projects in its folders, such as a folder of repositories, except that only
|
|
2078
|
+
* an absolute path in that folder is answered, from the project in it that holds the file (never
|
|
2079
|
+
* from a folder outside or above it), and a file in none of them from that folder, as before.
|
|
2080
|
+
* Links are resolved: a path, or the project it leads to, that is really outside the folder is
|
|
2081
|
+
* refused, a workspace in it serves only its packages that are really in it, and a project in it
|
|
2082
|
+
* that a link replaces while the server runs is not built again from where the link leads.
|
|
2083
|
+
*
|
|
2084
|
+
* A load that fails, such as on an invalid config, is not kept: the next request loads
|
|
2085
|
+
* again, so fixing the config is enough, also when it was invalid at startup.
|
|
2059
2086
|
*/
|
|
2060
2087
|
function serveStdio$1(options) {
|
|
2061
2088
|
const { logger } = options;
|
|
@@ -2076,15 +2103,28 @@ function serveStdio$1(options) {
|
|
|
2076
2103
|
/**
|
|
2077
2104
|
* The project at `root` (the working directory when undefined), or a monorepo root's
|
|
2078
2105
|
* projects. `jsonConfigOnly` for a root found from a tool call's path: its configs, and those
|
|
2079
|
-
* of the projects in it, are read only when they are JSON, never run.
|
|
2106
|
+
* of the projects in it, are read only when they are JSON, never run. `within`: the folder of
|
|
2107
|
+
* projects it was found in, for the error on a config that is not JSON, and outside which no
|
|
2108
|
+
* workspace package is served, and no project or package is built.
|
|
2080
2109
|
*/
|
|
2081
|
-
const load = async (root, jsonConfigOnly = false) => {
|
|
2110
|
+
const load = async (root, jsonConfigOnly = false, within) => {
|
|
2082
2111
|
const configOptions = {
|
|
2083
2112
|
...options,
|
|
2084
2113
|
root,
|
|
2085
|
-
jsonConfigOnly
|
|
2114
|
+
jsonConfigOnly,
|
|
2115
|
+
jsonConfigWithin: jsonConfigOnly ? within?.dir : void 0
|
|
2086
2116
|
};
|
|
2087
|
-
const
|
|
2117
|
+
const target = await loadTarget(configOptions);
|
|
2118
|
+
const { config } = target;
|
|
2119
|
+
let { workspace } = target;
|
|
2120
|
+
if (workspace && within) {
|
|
2121
|
+
const outside = workspace.projects.filter((p) => !isInside(within.real, realPath(p.root)));
|
|
2122
|
+
if (outside.length) {
|
|
2123
|
+
logger.warn(`${pathOf(workspace.root)} names workspace packages outside ${pathOf(within.dir)}, the folder the server answers for (links resolved), which it leaves out: ${outside.map((p) => pathOf(p.root)).join(", ")}. To include them, start the server with --root ${pathOf(workspace.root)}.`);
|
|
2124
|
+
workspace = workspace.without(outside.map((p) => p.root));
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
const build = within ? buildWithin(within) : void 0;
|
|
2088
2128
|
for (const text of config.deprecations ?? []) logger.warn(text);
|
|
2089
2129
|
for (const text of (workspace ? workspace.warnings : config.warnings) ?? []) logger.warn(text);
|
|
2090
2130
|
if (workspace) {
|
|
@@ -2092,7 +2132,8 @@ function serveStdio$1(options) {
|
|
|
2092
2132
|
cache: options.cache,
|
|
2093
2133
|
watch: options.watch,
|
|
2094
2134
|
logger,
|
|
2095
|
-
onChange: notify
|
|
2135
|
+
onChange: notify,
|
|
2136
|
+
build
|
|
2096
2137
|
});
|
|
2097
2138
|
logger.info(`monorepo root: ${workspace.projects.length} of ${workspace.projects.length + workspace.others.length} workspace packages have a design system${workspace.projects.length ? ` (${workspace.projects.map((p) => p.dir).join(", ")})` : ""}`);
|
|
2098
2139
|
closers.push(() => {
|
|
@@ -2104,7 +2145,8 @@ function serveStdio$1(options) {
|
|
|
2104
2145
|
const host = new DesignSystemHost(config, {
|
|
2105
2146
|
cache: options.cache,
|
|
2106
2147
|
logger,
|
|
2107
|
-
loadConfig: () => loadConfig(configOptions)
|
|
2148
|
+
loadConfig: () => loadConfig(configOptions),
|
|
2149
|
+
build
|
|
2108
2150
|
});
|
|
2109
2151
|
host.onChange(notify);
|
|
2110
2152
|
if (options.watch) host.watch();
|
|
@@ -2116,44 +2158,85 @@ function serveStdio$1(options) {
|
|
|
2116
2158
|
});
|
|
2117
2159
|
return { host };
|
|
2118
2160
|
};
|
|
2161
|
+
/**
|
|
2162
|
+
* The roots loaded or loading, by `key` (the real path of `dir`, for a root found from a path).
|
|
2163
|
+
* One that fails is logged and dropped, so the next request loads it again, and until then it
|
|
2164
|
+
* is no project found.
|
|
2165
|
+
*/
|
|
2166
|
+
const found = /* @__PURE__ */ new Map();
|
|
2167
|
+
const loadOnce = (key, dir, start) => {
|
|
2168
|
+
const known = found.get(key);
|
|
2169
|
+
if (known) return known.served;
|
|
2170
|
+
const served = start();
|
|
2171
|
+
found.set(key, {
|
|
2172
|
+
dir,
|
|
2173
|
+
served
|
|
2174
|
+
});
|
|
2175
|
+
served.catch((error) => {
|
|
2176
|
+
logger.error(error.message);
|
|
2177
|
+
if (found.get(key)?.served === served) found.delete(key);
|
|
2178
|
+
});
|
|
2179
|
+
return served;
|
|
2180
|
+
};
|
|
2119
2181
|
let started = false;
|
|
2120
2182
|
/** One project for every request: the one at `root`, or the working directory. */
|
|
2121
2183
|
const serveOne = (root) => {
|
|
2122
2184
|
if (started) return;
|
|
2123
2185
|
started = true;
|
|
2124
|
-
const
|
|
2125
|
-
served
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
decide(() => served);
|
|
2186
|
+
const dir = path.resolve(options.cwd, root ?? ".");
|
|
2187
|
+
const served = () => loadOnce(dir, dir, () => load(root));
|
|
2188
|
+
served();
|
|
2189
|
+
decide(served);
|
|
2129
2190
|
};
|
|
2130
|
-
/**
|
|
2131
|
-
|
|
2191
|
+
/**
|
|
2192
|
+
* Each request's project, from the absolute path it names. `within`: a folder that is no
|
|
2193
|
+
* project but holds some (the `--root` or client root the server was started for), which
|
|
2194
|
+
* answers for a file in none of them. Then only a path in it is answered, from a project in
|
|
2195
|
+
* it: never from a folder outside or above it, links resolved, nor from a relative path, which
|
|
2196
|
+
* the tools would read against whichever project answers.
|
|
2197
|
+
*/
|
|
2198
|
+
const serveByPath = (within) => {
|
|
2132
2199
|
if (started) return;
|
|
2133
2200
|
started = true;
|
|
2134
|
-
|
|
2135
|
-
|
|
2201
|
+
const folder = within === void 0 ? void 0 : {
|
|
2202
|
+
dir: within,
|
|
2203
|
+
real: realPath(within)
|
|
2204
|
+
};
|
|
2205
|
+
const where = within ? `${pathOf(within)} holds no project of its own, only projects in its folders` : `${options.cwd} holds no project and the client reported no roots`;
|
|
2206
|
+
logger.info(`${where}: finding each request's project from the absolute path it names`);
|
|
2207
|
+
const askForPath = `Pass \`path\` (for check_ui with \`code\`, \`filename\`) as the absolute path of the file you are editing, and it answers from that file's project. Or start the server with --root <project folder>.`;
|
|
2136
2208
|
decide(async (target) => {
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
if (!
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
return served;
|
|
2209
|
+
const named = target !== void 0 && target !== "";
|
|
2210
|
+
if (within && named && !path.isAbsolute(target)) throw new ProjectNotFoundError(`onsystem cannot tell which project ${pathOf(target)} is in: it is a relative path, and ${pathOf(within)} holds no project of its own, only projects in its folders. ${askForPath}`);
|
|
2211
|
+
if (named && path.isAbsolute(target)) {
|
|
2212
|
+
const file = path.resolve(target);
|
|
2213
|
+
if (within && !isInside(within, file)) throw new ProjectNotFoundError(`${pathOf(target)} is outside ${pathOf(within)}, the folder the server answers for. Pass the absolute path of a file inside it, or start the server with --root <project folder>.`);
|
|
2214
|
+
const up = findProjectRoot(file);
|
|
2215
|
+
const dir = within && !(up && isInside(within, up)) ? projectDirOf(file, within) : up;
|
|
2216
|
+
if (!dir) throw new ProjectNotFoundError(`No project at or above ${pathOf(target)}: no folder from there up holds a package.json, components.json, project.json or onsystem config (the home folder and the filesystem root do not count). Pass the absolute path of a file in your project.`);
|
|
2217
|
+
const real = realPath(dir);
|
|
2218
|
+
if (folder && !(isInside(folder.real, real) && isInside(folder.real, realPath(file)))) throw new ProjectNotFoundError(`${pathOf(target)} leads outside ${pathOf(folder.dir)}, the folder the server answers for, through a link. Pass the absolute path of a file inside it, or start the server with --root <project folder>.`);
|
|
2219
|
+
if (!found.has(real)) logger.info(`using ${dir}, the project of ${target}`);
|
|
2220
|
+
return loadOnce(real, dir, () => load(dir, dir !== within, folder));
|
|
2150
2221
|
}
|
|
2151
2222
|
const [only, ...others] = found.values();
|
|
2152
|
-
if (only && !others.length) return only;
|
|
2153
|
-
throw new ProjectNotFoundError(only ? `Requests have named files in ${found.size} projects (${[...found.
|
|
2223
|
+
if (only && !others.length) return only.served;
|
|
2224
|
+
throw new ProjectNotFoundError(only ? `Requests have named files in ${found.size} projects (${[...found.values()].map((f) => pathOf(f.dir)).join(", ")}). Pass \`path\` (for check_ui with \`code\`, \`filename\`) as the absolute path of the file you are editing, so the answer comes from its project.` : within ? `onsystem cannot tell which project to answer for: ${pathOf(within)} holds no project of its own, only projects in its folders. ${askForPath}` : `onsystem cannot tell which project to answer for: it was started in ${options.cwd}, which holds no project, and the client reported no workspace roots. ${askForPath}`);
|
|
2154
2225
|
});
|
|
2155
2226
|
};
|
|
2156
|
-
|
|
2227
|
+
/** Flags that describe the project themselves: the folder they apply to is the one served. */
|
|
2228
|
+
const described = options.config !== void 0 || Boolean(options.components?.length || options.tokens?.length || options.docs?.length);
|
|
2229
|
+
/**
|
|
2230
|
+
* The project at `root` (the working directory when undefined). A folder that is no project but
|
|
2231
|
+
* holds projects (a folder of repositories, which `--root ${workspaceFolder}` names when the
|
|
2232
|
+
* editor has their parent open) would mix their tokens: each request is answered from its path.
|
|
2233
|
+
*/
|
|
2234
|
+
const serveAt = (root) => {
|
|
2235
|
+
const dir = root === void 0 ? void 0 : path.resolve(options.cwd, root);
|
|
2236
|
+
if (dir !== void 0 && !described && holdsOnlyProjects(dir)) serveByPath(dir);
|
|
2237
|
+
else serveOne(root);
|
|
2238
|
+
};
|
|
2239
|
+
if (options.root !== void 0 || options.config !== void 0 || [...CONFIG_FILES, ...LEGACY_CONFIG_FILES].some((file) => fs.existsSync(path.join(options.cwd, file))) || hasProjectMarker(options.cwd)) serveAt(options.root);
|
|
2157
2240
|
/**
|
|
2158
2241
|
* When nothing else says where the project is: the working directory when flags
|
|
2159
2242
|
* (`--components`, `--tokens`, `--docs`) describe it, else each request's path.
|
|
@@ -2189,7 +2272,7 @@ function serveStdio$1(options) {
|
|
|
2189
2272
|
return;
|
|
2190
2273
|
}
|
|
2191
2274
|
if (root !== options.cwd) logger.info(`using client root ${root}`);
|
|
2192
|
-
|
|
2275
|
+
serveAt(root);
|
|
2193
2276
|
});
|
|
2194
2277
|
};
|
|
2195
2278
|
return server;
|
|
@@ -2206,6 +2289,49 @@ function serveStdio$1(options) {
|
|
|
2206
2289
|
await handle.close();
|
|
2207
2290
|
} });
|
|
2208
2291
|
}
|
|
2292
|
+
/**
|
|
2293
|
+
* `buildModel` for a project found in the folder `within`, which refuses to build it once its
|
|
2294
|
+
* folder leads outside `within` (a project or workspace package replaced by a link while the
|
|
2295
|
+
* server watches it): that rebuild fails, so the model built from inside the folder stays.
|
|
2296
|
+
*/
|
|
2297
|
+
function buildWithin(within) {
|
|
2298
|
+
return async (config, options) => {
|
|
2299
|
+
if (!isInside(within.real, realPath(config.root))) throw new ConfigError(`${pathOf(config.root)} now leads outside ${pathOf(within.dir)}, the folder the server answers for, through a link: it is not read from there.`);
|
|
2300
|
+
return buildModel(config, options);
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2303
|
+
/**
|
|
2304
|
+
* Whether `dir` is no project (no project marker, no workspace file) but a folder in it, or in one
|
|
2305
|
+
* of its folders, is one. Dot folders and `node_modules` are not looked in.
|
|
2306
|
+
*/
|
|
2307
|
+
function holdsOnlyProjects(dir) {
|
|
2308
|
+
if (hasProjectMarker(dir) || isWorkspaceRoot(dir)) return false;
|
|
2309
|
+
const folders = (from) => {
|
|
2310
|
+
try {
|
|
2311
|
+
return fs.readdirSync(from, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => path.join(from, e.name));
|
|
2312
|
+
} catch {
|
|
2313
|
+
return [];
|
|
2314
|
+
}
|
|
2315
|
+
};
|
|
2316
|
+
const below = folders(dir);
|
|
2317
|
+
return below.some(hasProjectMarker) || below.some((f) => folders(f).some(hasProjectMarker));
|
|
2318
|
+
}
|
|
2319
|
+
/**
|
|
2320
|
+
* Where `p` really is: links resolved, in the letter case the file system keeps
|
|
2321
|
+
* (`realpathSync.native`; plain `realpathSync` keeps the case given on macOS). A path that does
|
|
2322
|
+
* not exist yet, such as the `filename` of new code, from the nearest folder above it that does.
|
|
2323
|
+
*/
|
|
2324
|
+
function realPath(p) {
|
|
2325
|
+
const rest = [];
|
|
2326
|
+
for (let dir = path.resolve(p);;) try {
|
|
2327
|
+
return path.join(fs.realpathSync.native(dir), ...rest);
|
|
2328
|
+
} catch {
|
|
2329
|
+
const parent = path.dirname(dir);
|
|
2330
|
+
if (parent === dir) return path.resolve(p);
|
|
2331
|
+
rest.unshift(path.basename(dir));
|
|
2332
|
+
dir = parent;
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2209
2335
|
/** First `file://` root the client reports, if it supports roots (deprecated in 2026-07-28). */
|
|
2210
2336
|
async function clientRoot(server) {
|
|
2211
2337
|
if (!server.server.getClientCapabilities()?.roots) return void 0;
|
|
@@ -2217,5 +2343,4 @@ async function clientRoot(server) {
|
|
|
2217
2343
|
return;
|
|
2218
2344
|
}
|
|
2219
2345
|
}
|
|
2220
|
-
//#endregion
|
|
2221
2346
|
export { notReadNotices as a, missingDependencies as i, INSTRUCTIONS as n, explainProject as o, createServer as r, librariesLeftOut as s, serveStdio$1 as t };
|
|
@@ -1,21 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { At as relativePath, Ct as isWorkspaceRoot, Dt as isInside, Tt as workspacePatterns, ct as loadConfig, i as componentFiles, lt as loadConfigWith, nt as PROJECT_MARKERS, st as hasOwnConfig, vt as PackageFinder, wt as workspacePackageDirs, xt as findDesignSystemPackage } from "./design-system-7wZoJsMm.js";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import os from "node:os";
|
|
5
|
-
|
|
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
|
-
];
|
|
5
|
+
// src/workspace.ts
|
|
19
6
|
/**
|
|
20
7
|
* A monorepo seen from its root: every workspace package with a design system (an app
|
|
21
8
|
* that uses one, from source or a published library, or the design-system package
|
|
@@ -23,7 +10,7 @@ const PROJECT_MARKERS = [
|
|
|
23
10
|
* any file in it. Without a design system at the root, `check`, `inspect` and the MCP
|
|
24
11
|
* server work from here, each file checked by its own project's design system.
|
|
25
12
|
*/
|
|
26
|
-
var Workspace = class {
|
|
13
|
+
var Workspace = class Workspace {
|
|
27
14
|
root;
|
|
28
15
|
source;
|
|
29
16
|
projects;
|
|
@@ -71,6 +58,10 @@ var Workspace = class {
|
|
|
71
58
|
}
|
|
72
59
|
return project;
|
|
73
60
|
}
|
|
61
|
+
/** The workspace without the projects at `roots`. */
|
|
62
|
+
without(roots) {
|
|
63
|
+
return new Workspace(this.root, this.source, this.projects.filter((p) => !roots.includes(p.root)), this.others, this.configFile, this.options, this.rootConfig, this.otherConfigs);
|
|
64
|
+
}
|
|
74
65
|
rootProject() {
|
|
75
66
|
return {
|
|
76
67
|
root: this.root,
|
|
@@ -148,7 +139,8 @@ function loadProjectConfig(dir, workspaceRoot, rootConfigFile, options) {
|
|
|
148
139
|
const shared = {
|
|
149
140
|
cwd: options.cwd,
|
|
150
141
|
includeDesignSystem: options.includeDesignSystem,
|
|
151
|
-
jsonConfigOnly: options.jsonConfigOnly
|
|
142
|
+
jsonConfigOnly: options.jsonConfigOnly,
|
|
143
|
+
jsonConfigWithin: options.jsonConfigWithin
|
|
152
144
|
};
|
|
153
145
|
const packages = new PackageFinder(dir);
|
|
154
146
|
if (!rootConfigFile || hasOwnConfig(dir)) return loadConfigWith({
|
|
@@ -224,5 +216,4 @@ function isDirectory(target) {
|
|
|
224
216
|
return false;
|
|
225
217
|
}
|
|
226
218
|
}
|
|
227
|
-
|
|
228
|
-
export { hasProjectMarker as n, loadTarget as r, findProjectRoot as t };
|
|
219
|
+
export { projectDirOf as i, 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.10.
|
|
3
|
+
"version": "0.10.3",
|
|
4
4
|
"mcpName": "io.github.dgesteves/onsystem",
|
|
5
5
|
"description": "Keeps coding agents on your design system: catches palette colors, native elements and APIs your system lacks, at every edit and pull request, with the fix. Set it up once in your design-system package; every app in your monorepo gets it.",
|
|
6
6
|
"keywords": [
|