onsystem 0.0.1 → 0.5.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,386 @@
1
+ import * as z from "zod";
2
+ //#region src/detect.d.ts
3
+ /**
4
+ * How apps import a design-system module, from a package's `exports`:
5
+ * `@midday/ui/button` → `../../packages/ui/src/components/button.tsx`, or a
6
+ * pattern (`@workspace/ui/components/*` → `…/src/components/*.tsx`). A
7
+ * specifier without `*` and a pattern target map every matching file to one
8
+ * module: a package that exports its components from a barrel.
9
+ */
10
+ interface ImportMapping {
11
+ specifier: string;
12
+ /** File or pattern relative to the root, with forward slashes. */
13
+ target: string;
14
+ }
15
+ /** One of several design systems an app uses, and how often its code imports it. */
16
+ interface DesignSystemSource {
17
+ source: string;
18
+ components: string[];
19
+ imports: number;
20
+ }
21
+ //#endregion
22
+ //#region src/config.d.ts
23
+ declare const RULE_IDS: readonly ["no-hardcoded-color", "no-hardcoded-spacing", "no-hardcoded-radius", "prefer-design-system-component", "no-unknown-component", "no-unknown-prop", "no-unknown-variant", "icon-button-accessible-name"];
24
+ type RuleId = (typeof RULE_IDS)[number];
25
+ declare const severitySchema: z.ZodEnum<{
26
+ off: "off";
27
+ warn: "warn";
28
+ error: "error";
29
+ }>;
30
+ type RuleSeverity = z.infer<typeof severitySchema>;
31
+ declare const ruleOptionsSchema: z.ZodObject<{
32
+ allow: z.ZodOptional<z.ZodArray<z.ZodString>>;
33
+ }, z.core.$strict>;
34
+ type RuleOptions = z.infer<typeof ruleOptionsSchema>;
35
+ declare const configSchema: z.ZodObject<{
36
+ $schema: z.ZodOptional<z.ZodString>;
37
+ components: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
38
+ exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
39
+ tokens: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
40
+ path: z.ZodString;
41
+ prefix: z.ZodOptional<z.ZodString>;
42
+ }, z.core.$strict>]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
43
+ path: z.ZodString;
44
+ prefix: z.ZodOptional<z.ZodString>;
45
+ }, z.core.$strict>]>>]>>;
46
+ docs: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
47
+ tsconfig: z.ZodOptional<z.ZodString>;
48
+ importPath: z.ZodOptional<z.ZodString>;
49
+ elements: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
50
+ includeDesignSystem: z.ZodOptional<z.ZodBoolean>;
51
+ rules: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
52
+ "no-hardcoded-color": "no-hardcoded-color";
53
+ "no-hardcoded-spacing": "no-hardcoded-spacing";
54
+ "no-hardcoded-radius": "no-hardcoded-radius";
55
+ "prefer-design-system-component": "prefer-design-system-component";
56
+ "no-unknown-component": "no-unknown-component";
57
+ "no-unknown-prop": "no-unknown-prop";
58
+ "no-unknown-variant": "no-unknown-variant";
59
+ "icon-button-accessible-name": "icon-button-accessible-name";
60
+ }> & z.core.$partial, z.ZodUnion<readonly [z.ZodEnum<{
61
+ off: "off";
62
+ warn: "warn";
63
+ error: "error";
64
+ }>, z.ZodTuple<[z.ZodEnum<{
65
+ off: "off";
66
+ warn: "warn";
67
+ error: "error";
68
+ }>], null>, z.ZodTuple<[z.ZodEnum<{
69
+ off: "off";
70
+ warn: "warn";
71
+ error: "error";
72
+ }>, z.ZodObject<{
73
+ allow: z.ZodOptional<z.ZodArray<z.ZodString>>;
74
+ }, z.core.$strict>], null>]>>>;
75
+ overrides: z.ZodOptional<z.ZodArray<z.ZodObject<{
76
+ files: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
77
+ rules: z.ZodRecord<z.ZodEnum<{
78
+ "no-hardcoded-color": "no-hardcoded-color";
79
+ "no-hardcoded-spacing": "no-hardcoded-spacing";
80
+ "no-hardcoded-radius": "no-hardcoded-radius";
81
+ "prefer-design-system-component": "prefer-design-system-component";
82
+ "no-unknown-component": "no-unknown-component";
83
+ "no-unknown-prop": "no-unknown-prop";
84
+ "no-unknown-variant": "no-unknown-variant";
85
+ "icon-button-accessible-name": "icon-button-accessible-name";
86
+ }> & z.core.$partial, z.ZodUnion<readonly [z.ZodEnum<{
87
+ off: "off";
88
+ warn: "warn";
89
+ error: "error";
90
+ }>, z.ZodTuple<[z.ZodEnum<{
91
+ off: "off";
92
+ warn: "warn";
93
+ error: "error";
94
+ }>], null>, z.ZodTuple<[z.ZodEnum<{
95
+ off: "off";
96
+ warn: "warn";
97
+ error: "error";
98
+ }>, z.ZodObject<{
99
+ allow: z.ZodOptional<z.ZodArray<z.ZodString>>;
100
+ }, z.core.$strict>], null>]>>;
101
+ }, z.core.$strict>>>;
102
+ }, z.core.$strict>;
103
+ type Config = z.input<typeof configSchema>;
104
+ /** Identity helper for typed `onsystem.config.ts` files. */
105
+ declare function defineConfig(config: Config): Config;
106
+ interface TokenSourceConfig {
107
+ path: string;
108
+ prefix?: string | undefined;
109
+ }
110
+ interface ResolvedRule {
111
+ severity: RuleSeverity;
112
+ options: RuleOptions;
113
+ }
114
+ /** Rule settings for the files that match `files` (root-relative globs). */
115
+ interface ResolvedOverride {
116
+ files: string[];
117
+ rules: Partial<Record<RuleId, ResolvedRule>>;
118
+ }
119
+ interface ResolvedConfig {
120
+ root: string;
121
+ configFile?: string | undefined;
122
+ components: string[];
123
+ exclude: string[];
124
+ tokens: TokenSourceConfig[];
125
+ docs: string[];
126
+ tsconfig?: string | undefined;
127
+ importPath?: string | undefined;
128
+ /** Import specifiers for component files, from the `exports` of the package that holds them. */
129
+ imports?: ImportMapping[] | undefined;
130
+ /** How the design system was found when the config does not say (`components.json`, a workspace package). */
131
+ detected?: string | undefined;
132
+ /**
133
+ * Several design systems found at once, the most imported first: its components win
134
+ * where two share a name, and are the ones suggested for native elements.
135
+ */
136
+ designSystems?: DesignSystemSource[] | undefined;
137
+ /** A Tailwind v3 config, which maps color classes to custom properties (`primary` → `hsl(var(--primary))`). */
138
+ tailwindConfig?: string | undefined;
139
+ elements: Record<string, string>;
140
+ /** `check` lints the component files themselves, which it skips by default. */
141
+ includeDesignSystem: boolean;
142
+ rules: Record<RuleId, ResolvedRule>;
143
+ /** Per-file rule settings, applied in order over `rules`. */
144
+ overrides?: ResolvedOverride[] | undefined;
145
+ /**
146
+ * The folder `overrides` globs are relative to, when it is not the root: the workspace
147
+ * root, for a project that takes its settings from a config there.
148
+ */
149
+ base?: string | undefined;
150
+ /** `components` comes from the config file or `--components`, not from detection or the defaults. */
151
+ configured?: boolean | undefined;
152
+ /**
153
+ * What to tell the user about the config's name: a config file found under its name from
154
+ * before the rename to onsystem. The CLI prints these once per run, the server logs them once.
155
+ */
156
+ deprecations?: string[] | undefined;
157
+ }
158
+ interface LoadConfigOptions {
159
+ /** Where to look for a config file when `root` and `config` are not given. */
160
+ cwd?: string;
161
+ /** Project root. Defaults to the config file's directory, then `cwd`. */
162
+ root?: string | undefined;
163
+ /** Explicit config file path. */
164
+ config?: string | undefined;
165
+ /**
166
+ * The folder the config file's paths and globs are written relative to, when it is not
167
+ * the root: a workspace root whose config a project in it takes on.
168
+ */
169
+ base?: string | undefined;
170
+ /** CLI overrides; replace the config file's values when set. */
171
+ components?: string[] | undefined;
172
+ tokens?: string[] | undefined;
173
+ docs?: string[] | undefined;
174
+ includeDesignSystem?: boolean | undefined;
175
+ /**
176
+ * Read a JSON config only: a JavaScript or TypeScript config is an error rather than run.
177
+ * For a project the MCP server found from the path a tool call names, which a prompt can
178
+ * choose, unlike the folder the server was started in.
179
+ */
180
+ jsonConfigOnly?: boolean | undefined;
181
+ }
182
+ declare function loadConfig(options?: LoadConfigOptions): Promise<ResolvedConfig>;
183
+ /** JSON Schema for editor completion in `onsystem.config.json`. */
184
+ declare function configJsonSchema(): Record<string, unknown>;
185
+ //#endregion
186
+ //#region src/types.d.ts
187
+ /**
188
+ * The design-system model the server exposes. Everything here is plain JSON so
189
+ * it can be cached on disk and returned as MCP structured content.
190
+ */
191
+ declare const TOKEN_CATEGORIES: readonly ["color", "spacing", "radius", "typography", "shadow", "other"];
192
+ type TokenCategory = (typeof TOKEN_CATEGORIES)[number];
193
+ interface SourceLocation {
194
+ /** Path relative to the project root, with forward slashes. */
195
+ file: string;
196
+ /** 1-based line. */
197
+ line: number;
198
+ }
199
+ interface Token {
200
+ /**
201
+ * Canonical name: the DTCG path for JSON tokens (`color.brand.500`) or the
202
+ * custom property without its dashes for CSS tokens (`primary`, `radius-md`).
203
+ */
204
+ name: string;
205
+ category: TokenCategory;
206
+ /** The value with aliases resolved, as authored (`oklch(0.205 0 0)`, `0.5rem`). */
207
+ value: string;
208
+ /** Values for other modes or themes, e.g. `{ dark: "oklch(0.922 0 0)" }`. */
209
+ modes?: Record<string, string>;
210
+ /** The custom property to reference from CSS, e.g. `--primary`. */
211
+ cssVar?: string;
212
+ /**
213
+ * Tailwind theme key when the token is exposed through `@theme`
214
+ * (`primary` for `--color-primary`, `md` for `--radius-md`).
215
+ */
216
+ tailwind?: string;
217
+ /** Ready-to-paste usages, most idiomatic first: `bg-primary`, `var(--primary)`. */
218
+ usage: string[];
219
+ description?: string;
220
+ deprecated?: string | true;
221
+ /** Name of the token this one aliases, if any. */
222
+ aliasOf?: string;
223
+ /** Where the value comes from. `tailwind-default` marks Tailwind built-ins such as `--spacing`. */
224
+ origin?: 'tailwind-default';
225
+ source: SourceLocation;
226
+ }
227
+ interface PropInfo {
228
+ name: string;
229
+ /** TypeScript type as written by the checker, without `| undefined`. */
230
+ type: string;
231
+ required: boolean;
232
+ default?: string;
233
+ description?: string;
234
+ /** Allowed values when the type is a finite set of literals. */
235
+ values?: string[];
236
+ deprecated?: string | true;
237
+ /** `variant` when the prop comes from a `cva()` definition. */
238
+ kind: 'prop' | 'variant';
239
+ }
240
+ interface VariantInfo {
241
+ /** Variant prop name, e.g. `variant` or `size`. */
242
+ name: string;
243
+ values: string[];
244
+ default?: string;
245
+ /** Classes applied for each value, straight from the `cva()` config. */
246
+ classes: Record<string, string>;
247
+ }
248
+ interface CompoundVariantInfo {
249
+ when: Record<string, string | string[]>;
250
+ classes: string;
251
+ }
252
+ interface InheritedProps {
253
+ /** Source text of the type the props come from, e.g. `React.ComponentProps<"button">`. */
254
+ from: string;
255
+ count: number;
256
+ /** Key into `DesignSystemModel.propSets` with the full list of names. */
257
+ set: string;
258
+ /** Names in the set marked `@deprecated` (React Aria's `onClick`), left out of summaries. */
259
+ deprecated?: string[];
260
+ }
261
+ interface ExampleInfo {
262
+ title?: string;
263
+ code: string;
264
+ lang: string;
265
+ source: 'docs' | 'jsdoc';
266
+ }
267
+ interface DocSection {
268
+ heading: string;
269
+ body: string;
270
+ }
271
+ interface ComponentDocs {
272
+ file: string;
273
+ description?: string;
274
+ sections: DocSection[];
275
+ frontmatter: Record<string, string | string[]>;
276
+ }
277
+ interface ComponentInfo {
278
+ /** How the component is referenced in JSX: `Button`, `CardHeader` or `Tabs.List`. */
279
+ name: string;
280
+ /** Other names that resolve to the same component (e.g. `Card.Header` when `CardHeader` is also exported). */
281
+ aliases: string[];
282
+ description?: string;
283
+ deprecated?: string | true;
284
+ /** Suggested import specifier, e.g. `@/components/ui/button`. */
285
+ importPath: string;
286
+ /** Exported binding name, or the static member path for compound members. */
287
+ exportName: string;
288
+ source: SourceLocation;
289
+ /** The root component this one belongs to (`Card` for `CardHeader`). */
290
+ parent?: string;
291
+ /** Direct sub-components, in declaration order. */
292
+ subcomponents: string[];
293
+ /** Native element it renders or wraps, when it can be inferred (`button`, `input`). */
294
+ element?: string;
295
+ props: PropInfo[];
296
+ inherits: InheritedProps[];
297
+ /**
298
+ * True when part of the props type could not be resolved (missing
299
+ * `node_modules`, a type error). The linter then skips unknown-prop checks.
300
+ */
301
+ openProps: boolean;
302
+ variants: VariantInfo[];
303
+ compoundVariants: CompoundVariantInfo[];
304
+ examples: ExampleInfo[];
305
+ docs?: ComponentDocs;
306
+ /** Utility classes used in the component source; mapped to tokens at query time. */
307
+ classNames: string[];
308
+ /** CSS custom properties referenced in the component source. */
309
+ cssVars: string[];
310
+ }
311
+ interface DesignSystemModel {
312
+ version: 1;
313
+ root: string;
314
+ components: ComponentInfo[];
315
+ tokens: Token[];
316
+ /** Shared inherited prop-name lists, deduplicated across components. */
317
+ propSets: Record<string, string[]>;
318
+ /**
319
+ * Every value the component files export, components or not (`Icons`,
320
+ * `buttonVariants`), so the linter tells an invented name from a real export.
321
+ */
322
+ exports: string[];
323
+ /** Non-fatal problems found while extracting (unparseable files, unmatched docs). */
324
+ warnings: string[];
325
+ /**
326
+ * The component files extraction read, relative to the root: a name one of them exports
327
+ * that is not a component was seen, while a file outside them was never looked at.
328
+ */
329
+ sources?: string[];
330
+ stats: {
331
+ files: {
332
+ components: number;
333
+ tokens: number;
334
+ docs: number;
335
+ };
336
+ durationMs: number;
337
+ fromCache: boolean;
338
+ };
339
+ }
340
+ type Severity = 'error' | 'warning';
341
+ interface TextEdit {
342
+ /** 0-based offsets into the checked source, end exclusive. */
343
+ range: [number, number];
344
+ text: string;
345
+ }
346
+ interface Diagnostic {
347
+ ruleId: string;
348
+ severity: Severity;
349
+ message: string;
350
+ /** 1-based. */
351
+ line: number;
352
+ /** 1-based. */
353
+ column: number;
354
+ endLine: number;
355
+ endColumn: number;
356
+ /** The offending source text. */
357
+ source: string;
358
+ /** What to write instead, e.g. `bg-destructive` or `<Button>`. */
359
+ suggestion?: string;
360
+ /** Edits that apply the suggestion, when it can be done mechanically. */
361
+ fix?: TextEdit[];
362
+ }
363
+ interface CheckResult {
364
+ file: string;
365
+ diagnostics: Diagnostic[];
366
+ errorCount: number;
367
+ warningCount: number;
368
+ /** With a baseline: findings it accepted, left out of `diagnostics` and the counts. */
369
+ baselined?: number;
370
+ /**
371
+ * Components imported from design-system modules the model does not include
372
+ * (extraction is incomplete), which the rules could not check.
373
+ */
374
+ unchecked?: {
375
+ names: string[];
376
+ modules: string[];
377
+ };
378
+ /**
379
+ * Why the file was not checked: it renders outside the browser's CSS (an
380
+ * Open Graph image through next/og, an email through React Email), where
381
+ * design tokens and classes do not apply.
382
+ */
383
+ skipped?: string;
384
+ }
385
+ //#endregion
386
+ export { RuleId as C, loadConfig as D, defineConfig as E, ImportMapping as O, ResolvedRule as S, configJsonSchema as T, VariantInfo as _, DesignSystemModel as a, RULE_IDS as b, ExampleInfo as c, Severity as d, SourceLocation as f, TokenCategory as g, Token as h, CompoundVariantInfo as i, InheritedProps as l, TextEdit as m, ComponentDocs as n, Diagnostic as o, TOKEN_CATEGORIES as p, ComponentInfo as r, DocSection as s, CheckResult as t, PropInfo as u, Config as v, RuleOptions as w, ResolvedConfig as x, LoadConfigOptions as y };
@@ -0,0 +1,193 @@
1
+ import { F as CONFIG_FILES, G as loadConfig, V as LEGACY_CONFIG_FILES, X as workspacePackageDirs, Y as isWorkspaceRoot, Z as workspacePatterns, i as componentFiles, it as relativePath, nt as isInside } from "./design-system-CnEv5sIL.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, or the design-system package itself), and how to find the project of
22
+ * any file in it. Without a design system at the root, `check`, `inspect` and the MCP
23
+ * server work from here, each file checked by its own project's design system.
24
+ */
25
+ var Workspace = class {
26
+ root;
27
+ source;
28
+ projects;
29
+ others;
30
+ configFile;
31
+ options;
32
+ rootConfig;
33
+ bare = /* @__PURE__ */ new Map();
34
+ constructor(root, source, projects, others, configFile, options, rootConfig) {
35
+ this.root = root;
36
+ this.source = source;
37
+ this.projects = projects;
38
+ this.others = others;
39
+ this.configFile = configFile;
40
+ this.options = options;
41
+ this.rootConfig = rootConfig;
42
+ }
43
+ /**
44
+ * The project `target` (a file or a folder) belongs to: the nearest folder at or above
45
+ * 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
46
+ * its config, so its files are checked as `check` from that folder would check them.
47
+ */
48
+ async projectFor(target) {
49
+ const dir = projectDirOf(path.resolve(this.root, target), this.root);
50
+ const known = this.projects.find((p) => p.root === dir);
51
+ if (known) return known;
52
+ let project = this.bare.get(dir);
53
+ if (!project) {
54
+ project = dir === this.root ? Promise.resolve(this.rootProject()) : this.load(dir);
55
+ this.bare.set(dir, project);
56
+ }
57
+ return project;
58
+ }
59
+ rootProject() {
60
+ return {
61
+ root: this.root,
62
+ dir: ".",
63
+ name: packageName(this.root),
64
+ config: this.rootConfig
65
+ };
66
+ }
67
+ /** A project's config read again, as `check` from its folder would read it. */
68
+ configFor(project) {
69
+ return project.root === this.root ? loadConfig(this.options) : loadProjectConfig(project.root, this.root, this.configFile, this.options);
70
+ }
71
+ async load(dir) {
72
+ return {
73
+ root: dir,
74
+ dir: relativePath(this.root, dir) || ".",
75
+ name: packageName(dir),
76
+ config: await loadProjectConfig(dir, this.root, this.configFile, this.options)
77
+ };
78
+ }
79
+ };
80
+ /**
81
+ * The config at `root`, or a `Workspace` when `root` is a monorepo root with no design
82
+ * system of its own. The root is one project, as before, when a config file or
83
+ * `--components` sets the components, when `--config`, `--tokens` or `--docs` is given,
84
+ * or when zero config finds a design system there.
85
+ */
86
+ async function loadTarget(options) {
87
+ const config = await loadConfig(options);
88
+ if (options.config !== void 0 || Boolean(options.components?.length) || Boolean(options.tokens?.length) || Boolean(options.docs?.length) || config.configured || !isWorkspaceRoot(config.root)) return { config };
89
+ if (config.detected !== void 0 || (await componentFiles(config)).length) return { config };
90
+ return {
91
+ config,
92
+ workspace: await discoverWorkspace(config, options)
93
+ };
94
+ }
95
+ /** Every workspace package with a design system, and the rest by folder. */
96
+ async function discoverWorkspace(rootConfig, options) {
97
+ const { root } = rootConfig;
98
+ const source = [
99
+ workspacePatterns(root).source,
100
+ fs.existsSync(path.join(root, "nx.json")) ? "nx.json" : "",
101
+ fs.existsSync(path.join(root, "turbo.json")) ? "turbo.json" : ""
102
+ ].filter(Boolean).join(", ");
103
+ const projects = [];
104
+ const others = [];
105
+ for (const dir of workspacePackageDirs(root)) {
106
+ const config = await loadProjectConfig(dir, root, rootConfig.configFile, options);
107
+ const files = (await componentFiles(config)).length;
108
+ const own = config.configFile !== void 0 && config.configFile !== rootConfig.configFile;
109
+ if (files || own) projects.push({
110
+ root: dir,
111
+ dir: relativePath(root, dir),
112
+ name: packageName(dir),
113
+ config,
114
+ componentFiles: files
115
+ });
116
+ else others.push(relativePath(root, dir));
117
+ }
118
+ return new Workspace(root, source, projects, others, rootConfig.configFile, options, rootConfig);
119
+ }
120
+ /**
121
+ * A project's config: its own config file, or the workspace root's, with paths relative to
122
+ * the workspace root, or zero config. CLI flags that apply to every project carry over.
123
+ */
124
+ function loadProjectConfig(dir, workspaceRoot, rootConfigFile, options) {
125
+ const shared = {
126
+ cwd: options.cwd,
127
+ includeDesignSystem: options.includeDesignSystem,
128
+ jsonConfigOnly: options.jsonConfigOnly
129
+ };
130
+ if ([...CONFIG_FILES, ...LEGACY_CONFIG_FILES].some((name) => fs.existsSync(path.join(dir, name))) || !rootConfigFile) return loadConfig({
131
+ ...shared,
132
+ root: dir
133
+ });
134
+ return loadConfig({
135
+ ...shared,
136
+ root: dir,
137
+ config: rootConfigFile,
138
+ base: workspaceRoot
139
+ });
140
+ }
141
+ /** The nearest folder at or above `target` (a file or folder) with a project marker, up to `root`. */
142
+ function projectDirOf(target, root) {
143
+ let dir = isDirectory(target) ? target : path.dirname(target);
144
+ while (isInside(root, dir) && dir !== root) {
145
+ if (hasProjectMarker(dir)) return dir;
146
+ dir = path.dirname(dir);
147
+ }
148
+ return root;
149
+ }
150
+ /**
151
+ * The folder to serve `target` (an absolute file or folder, which need not exist yet) from,
152
+ * found by walking up from it: the nearest folder with a project marker, as the Claude Code
153
+ * hook finds a file's project, or the monorepo root above that folder when there is one, as
154
+ * a client that reports the repository as its workspace would have the server started.
155
+ * `loadTarget` there serves the root's own design system, or each project in it. The home
156
+ * folder and the filesystem root are never one. Undefined when no other folder up to the
157
+ * filesystem root has a project marker.
158
+ */
159
+ function findProjectRoot(target, home = os.homedir()) {
160
+ const skipped = (dir) => dir === path.dirname(dir) || samePath(dir, home);
161
+ let dir = isDirectory(target) ? target : path.dirname(target);
162
+ while (skipped(dir) || !hasProjectMarker(dir)) {
163
+ const parent = path.dirname(dir);
164
+ if (parent === dir) return void 0;
165
+ dir = parent;
166
+ }
167
+ for (let up = dir; up !== path.dirname(up); up = path.dirname(up)) if (!skipped(up) && isWorkspaceRoot(up)) return up;
168
+ return dir;
169
+ }
170
+ function samePath(a, b) {
171
+ return path.relative(path.resolve(a), path.resolve(b)) === "";
172
+ }
173
+ /** Whether `dir` holds one of the `PROJECT_MARKERS`. */
174
+ function hasProjectMarker(dir) {
175
+ return PROJECT_MARKERS.some((name) => fs.existsSync(path.join(dir, name)));
176
+ }
177
+ function packageName(dir) {
178
+ try {
179
+ const name = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")).name;
180
+ return typeof name === "string" ? name : void 0;
181
+ } catch {
182
+ return;
183
+ }
184
+ }
185
+ function isDirectory(target) {
186
+ try {
187
+ return fs.statSync(target).isDirectory();
188
+ } catch {
189
+ return false;
190
+ }
191
+ }
192
+ //#endregion
193
+ export { hasProjectMarker as n, loadTarget as r, findProjectRoot as t };