uilint 0.2.23 → 0.2.27
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/chunk-P4I4RKBY.js +126 -0
- package/dist/chunk-P4I4RKBY.js.map +1 -0
- package/dist/{chunk-PBEKMDUH.js → chunk-TWUDB36F.js} +62 -54
- package/dist/chunk-TWUDB36F.js.map +1 -0
- package/dist/{chunk-PB5DLLVC.js → chunk-VNANPKR2.js} +256 -30
- package/dist/chunk-VNANPKR2.js.map +1 -0
- package/dist/index.js +481 -20
- package/dist/index.js.map +1 -1
- package/dist/{install-ui-TXV7A34M.js → install-ui-CCZ3XJDE.js} +263 -41
- package/dist/install-ui-CCZ3XJDE.js.map +1 -0
- package/dist/{plan-SIXVCXCK.js → plan-5WHKVACB.js} +95 -40
- package/dist/plan-5WHKVACB.js.map +1 -0
- package/package.json +9 -4
- package/dist/chunk-PB5DLLVC.js.map +0 -1
- package/dist/chunk-PBEKMDUH.js.map +0 -1
- package/dist/install-ui-TXV7A34M.js.map +0 -1
- package/dist/plan-SIXVCXCK.js.map +0 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/package-manager.ts
|
|
4
|
+
import { existsSync } from "fs";
|
|
5
|
+
import { spawn } from "child_process";
|
|
6
|
+
import { dirname, join } from "path";
|
|
7
|
+
function detectPackageManager(projectPath) {
|
|
8
|
+
let dir = projectPath;
|
|
9
|
+
for (; ; ) {
|
|
10
|
+
if (existsSync(join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
11
|
+
if (existsSync(join(dir, "pnpm-workspace.yaml"))) return "pnpm";
|
|
12
|
+
if (existsSync(join(dir, "yarn.lock"))) return "yarn";
|
|
13
|
+
if (existsSync(join(dir, "bun.lockb"))) return "bun";
|
|
14
|
+
if (existsSync(join(dir, "bun.lock"))) return "bun";
|
|
15
|
+
if (existsSync(join(dir, "package-lock.json"))) return "npm";
|
|
16
|
+
const parent = dirname(dir);
|
|
17
|
+
if (parent === dir) break;
|
|
18
|
+
dir = parent;
|
|
19
|
+
}
|
|
20
|
+
return "npm";
|
|
21
|
+
}
|
|
22
|
+
function spawnAsync(command, args, cwd) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const child = spawn(command, args, {
|
|
25
|
+
cwd,
|
|
26
|
+
// Capture output so we can surface it in installer summaries, while still
|
|
27
|
+
// streaming to the user for a good UX.
|
|
28
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
29
|
+
shell: process.platform === "win32"
|
|
30
|
+
});
|
|
31
|
+
const stdoutChunks = [];
|
|
32
|
+
const stderrChunks = [];
|
|
33
|
+
const MAX_CAPTURE = 64 * 1024;
|
|
34
|
+
child.stdout?.on("data", (chunk) => {
|
|
35
|
+
process.stdout.write(chunk);
|
|
36
|
+
stdoutChunks.push(chunk);
|
|
37
|
+
while (Buffer.concat(stdoutChunks).length > MAX_CAPTURE) stdoutChunks.shift();
|
|
38
|
+
});
|
|
39
|
+
child.stderr?.on("data", (chunk) => {
|
|
40
|
+
process.stderr.write(chunk);
|
|
41
|
+
stderrChunks.push(chunk);
|
|
42
|
+
while (Buffer.concat(stderrChunks).length > MAX_CAPTURE) stderrChunks.shift();
|
|
43
|
+
});
|
|
44
|
+
child.on("error", (err) => {
|
|
45
|
+
reject(err);
|
|
46
|
+
});
|
|
47
|
+
child.on("close", (code) => {
|
|
48
|
+
if (code === 0) {
|
|
49
|
+
resolve();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const cmd = `${command} ${args.join(" ")}`.trim();
|
|
53
|
+
const stdout = Buffer.concat(stdoutChunks).toString("utf-8").trim();
|
|
54
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
|
|
55
|
+
const snippet = (stderr || stdout).trim();
|
|
56
|
+
reject(
|
|
57
|
+
new Error(
|
|
58
|
+
`${cmd} exited with ${code}${snippet ? `
|
|
59
|
+
|
|
60
|
+
--- output ---
|
|
61
|
+
${snippet}
|
|
62
|
+
--- end output ---` : ""}`
|
|
63
|
+
)
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async function installDependencies(pm, projectPath, packages, options = { dev: true }) {
|
|
69
|
+
if (!packages.length) return;
|
|
70
|
+
const isDev = options.dev ?? true;
|
|
71
|
+
switch (pm) {
|
|
72
|
+
case "pnpm":
|
|
73
|
+
await spawnAsync(
|
|
74
|
+
"pnpm",
|
|
75
|
+
["add", ...isDev ? ["-D"] : [], ...packages],
|
|
76
|
+
projectPath
|
|
77
|
+
);
|
|
78
|
+
return;
|
|
79
|
+
case "yarn":
|
|
80
|
+
await spawnAsync(
|
|
81
|
+
"yarn",
|
|
82
|
+
["add", ...isDev ? ["-D"] : [], ...packages],
|
|
83
|
+
projectPath
|
|
84
|
+
);
|
|
85
|
+
return;
|
|
86
|
+
case "bun":
|
|
87
|
+
await spawnAsync(
|
|
88
|
+
"bun",
|
|
89
|
+
["add", ...isDev ? ["-d"] : [], ...packages],
|
|
90
|
+
projectPath
|
|
91
|
+
);
|
|
92
|
+
return;
|
|
93
|
+
case "npm":
|
|
94
|
+
default:
|
|
95
|
+
await spawnAsync(
|
|
96
|
+
"npm",
|
|
97
|
+
["install", isDev ? "--save-dev" : "--save", ...packages],
|
|
98
|
+
projectPath
|
|
99
|
+
);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function getTestCoverageCommand(pm) {
|
|
104
|
+
switch (pm) {
|
|
105
|
+
case "pnpm":
|
|
106
|
+
return { command: "pnpm", args: ["test", "--", "--coverage"] };
|
|
107
|
+
case "yarn":
|
|
108
|
+
return { command: "yarn", args: ["test", "--coverage"] };
|
|
109
|
+
case "bun":
|
|
110
|
+
return { command: "bun", args: ["test", "--coverage"] };
|
|
111
|
+
case "npm":
|
|
112
|
+
default:
|
|
113
|
+
return { command: "npm", args: ["test", "--", "--coverage"] };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function runTestsWithCoverage(pm, projectPath) {
|
|
117
|
+
const { command, args } = getTestCoverageCommand(pm);
|
|
118
|
+
await spawnAsync(command, args, projectPath);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export {
|
|
122
|
+
detectPackageManager,
|
|
123
|
+
installDependencies,
|
|
124
|
+
runTestsWithCoverage
|
|
125
|
+
};
|
|
126
|
+
//# sourceMappingURL=chunk-P4I4RKBY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/package-manager.ts"],"sourcesContent":["import { existsSync } from \"fs\";\nimport { spawn } from \"child_process\";\nimport { dirname, join } from \"path\";\n\nexport type PackageManager = \"pnpm\" | \"yarn\" | \"npm\" | \"bun\";\n\n/**\n * Detect which package manager a project uses by looking for lockfiles.\n * Walks up the directory tree to support monorepos.\n */\nexport function detectPackageManager(projectPath: string): PackageManager {\n // Monorepo-friendly detection: walk up to find the lockfile/workspace marker.\n let dir = projectPath;\n for (;;) {\n // pnpm\n if (existsSync(join(dir, \"pnpm-lock.yaml\"))) return \"pnpm\";\n if (existsSync(join(dir, \"pnpm-workspace.yaml\"))) return \"pnpm\";\n\n // yarn\n if (existsSync(join(dir, \"yarn.lock\"))) return \"yarn\";\n\n // bun\n if (existsSync(join(dir, \"bun.lockb\"))) return \"bun\";\n if (existsSync(join(dir, \"bun.lock\"))) return \"bun\";\n\n // npm\n if (existsSync(join(dir, \"package-lock.json\"))) return \"npm\";\n\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n\n // Default: npm (best-effort)\n return \"npm\";\n}\n\nfunction spawnAsync(\n command: string,\n args: string[],\n cwd: string\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn(command, args, {\n cwd,\n // Capture output so we can surface it in installer summaries, while still\n // streaming to the user for a good UX.\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n shell: process.platform === \"win32\",\n });\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n const MAX_CAPTURE = 64 * 1024; // keep last 64KB per stream\n\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n process.stdout.write(chunk);\n stdoutChunks.push(chunk);\n // keep bounded\n while (Buffer.concat(stdoutChunks).length > MAX_CAPTURE) stdoutChunks.shift();\n });\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n process.stderr.write(chunk);\n stderrChunks.push(chunk);\n while (Buffer.concat(stderrChunks).length > MAX_CAPTURE) stderrChunks.shift();\n });\n\n child.on(\"error\", (err) => {\n reject(err);\n });\n\n child.on(\"close\", (code) => {\n if (code === 0) {\n resolve();\n return;\n }\n\n const cmd = `${command} ${args.join(\" \")}`.trim();\n const stdout = Buffer.concat(stdoutChunks).toString(\"utf-8\").trim();\n const stderr = Buffer.concat(stderrChunks).toString(\"utf-8\").trim();\n const snippet = (stderr || stdout).trim();\n\n reject(\n new Error(\n `${cmd} exited with ${code}${\n snippet ? `\\n\\n--- output ---\\n${snippet}\\n--- end output ---` : \"\"\n }`\n )\n );\n });\n });\n}\n\nexport async function installDependencies(\n pm: PackageManager,\n projectPath: string,\n packages: string[],\n options: { dev?: boolean } = { dev: true }\n): Promise<void> {\n if (!packages.length) return;\n\n const isDev = options.dev ?? true;\n\n switch (pm) {\n case \"pnpm\":\n await spawnAsync(\n \"pnpm\",\n [\"add\", ...(isDev ? [\"-D\"] : []), ...packages],\n projectPath\n );\n return;\n case \"yarn\":\n await spawnAsync(\n \"yarn\",\n [\"add\", ...(isDev ? [\"-D\"] : []), ...packages],\n projectPath\n );\n return;\n case \"bun\":\n await spawnAsync(\n \"bun\",\n [\"add\", ...(isDev ? [\"-d\"] : []), ...packages],\n projectPath\n );\n return;\n case \"npm\":\n default:\n await spawnAsync(\n \"npm\",\n [\"install\", isDev ? \"--save-dev\" : \"--save\", ...packages],\n projectPath\n );\n return;\n }\n}\n\n/**\n * Get the command and arguments to run tests with coverage\n */\nexport function getTestCoverageCommand(pm: PackageManager): {\n command: string;\n args: string[];\n} {\n switch (pm) {\n case \"pnpm\":\n return { command: \"pnpm\", args: [\"test\", \"--\", \"--coverage\"] };\n case \"yarn\":\n return { command: \"yarn\", args: [\"test\", \"--coverage\"] };\n case \"bun\":\n return { command: \"bun\", args: [\"test\", \"--coverage\"] };\n case \"npm\":\n default:\n return { command: \"npm\", args: [\"test\", \"--\", \"--coverage\"] };\n }\n}\n\n/**\n * Run tests with coverage for a project\n */\nexport async function runTestsWithCoverage(\n pm: PackageManager,\n projectPath: string\n): Promise<void> {\n const { command, args } = getTestCoverageCommand(pm);\n await spawnAsync(command, args, projectPath);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,56 +1,65 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
3
|
+
// src/commands/install/versioning.ts
|
|
4
|
+
import { createRequire } from "module";
|
|
5
|
+
import { existsSync, readFileSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
var require2 = createRequire(import.meta.url);
|
|
8
|
+
function isWithinDir(childPath, parentPath) {
|
|
9
|
+
const parent = parentPath.endsWith("/") ? parentPath : parentPath + "/";
|
|
10
|
+
return childPath === parentPath || childPath.startsWith(parent);
|
|
11
|
+
}
|
|
12
|
+
function isPnpmWorkspaceRoot(dir) {
|
|
13
|
+
if (!dir) return false;
|
|
14
|
+
return existsSync(join(dir, "pnpm-workspace.yaml"));
|
|
15
|
+
}
|
|
16
|
+
function workspaceHasPackage(workspaceRoot, pkgName) {
|
|
17
|
+
if (!workspaceRoot) return false;
|
|
18
|
+
return existsSync(join(workspaceRoot, "packages", pkgName, "package.json"));
|
|
19
|
+
}
|
|
20
|
+
function tryReadInstalledVersion(pkgName) {
|
|
21
|
+
try {
|
|
22
|
+
const depPkg = require2(`${pkgName}/package.json`);
|
|
23
|
+
const v = depPkg?.version;
|
|
24
|
+
return typeof v === "string" ? v : null;
|
|
25
|
+
} catch {
|
|
26
|
+
try {
|
|
27
|
+
const p = require2.resolve(`${pkgName}/package.json`);
|
|
28
|
+
const raw = readFileSync(p, "utf-8");
|
|
29
|
+
const parsed = JSON.parse(raw);
|
|
30
|
+
return typeof parsed.version === "string" ? parsed.version : null;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
19
34
|
}
|
|
20
|
-
return "npm";
|
|
21
35
|
}
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
+
function getSelfDependencyVersionRange(pkgName) {
|
|
37
|
+
try {
|
|
38
|
+
const pkgJson = require2("uilint/package.json");
|
|
39
|
+
const deps = pkgJson?.dependencies;
|
|
40
|
+
const optDeps = pkgJson?.optionalDependencies;
|
|
41
|
+
const peerDeps = pkgJson?.peerDependencies;
|
|
42
|
+
const devDeps = pkgJson?.devDependencies;
|
|
43
|
+
const v = deps?.[pkgName] ?? optDeps?.[pkgName] ?? peerDeps?.[pkgName] ?? devDeps?.[pkgName];
|
|
44
|
+
return typeof v === "string" ? v : null;
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
36
48
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
await spawnAsync("bun", ["add", ...packages], projectPath);
|
|
48
|
-
return;
|
|
49
|
-
case "npm":
|
|
50
|
-
default:
|
|
51
|
-
await spawnAsync("npm", ["install", "--save", ...packages], projectPath);
|
|
52
|
-
return;
|
|
49
|
+
function toInstallSpecifier(pkgName, options = {}) {
|
|
50
|
+
const { preferWorkspaceProtocol, workspaceRoot, targetProjectPath } = options;
|
|
51
|
+
if (preferWorkspaceProtocol && isPnpmWorkspaceRoot(workspaceRoot) && typeof targetProjectPath === "string" && isWithinDir(targetProjectPath, workspaceRoot || "") && workspaceHasPackage(workspaceRoot, pkgName)) {
|
|
52
|
+
return `${pkgName}@workspace:*`;
|
|
53
|
+
}
|
|
54
|
+
const range = getSelfDependencyVersionRange(pkgName);
|
|
55
|
+
if (!range) return pkgName;
|
|
56
|
+
if (range.startsWith("workspace:")) {
|
|
57
|
+
const installed = tryReadInstalledVersion(pkgName);
|
|
58
|
+
return installed ? `${pkgName}@^${installed}` : pkgName;
|
|
53
59
|
}
|
|
60
|
+
if (range.startsWith("file:")) return pkgName;
|
|
61
|
+
if (range.startsWith("link:")) return pkgName;
|
|
62
|
+
return `${pkgName}@${range}`;
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
// src/commands/install/constants.ts
|
|
@@ -174,11 +183,11 @@ conventions:
|
|
|
174
183
|
`;
|
|
175
184
|
|
|
176
185
|
// src/utils/skill-loader.ts
|
|
177
|
-
import { readFileSync, readdirSync, statSync, existsSync as existsSync2 } from "fs";
|
|
178
|
-
import { join as join2, dirname
|
|
186
|
+
import { readFileSync as readFileSync2, readdirSync, statSync, existsSync as existsSync2 } from "fs";
|
|
187
|
+
import { join as join2, dirname, relative } from "path";
|
|
179
188
|
import { fileURLToPath } from "url";
|
|
180
189
|
var __filename = fileURLToPath(import.meta.url);
|
|
181
|
-
var __dirname =
|
|
190
|
+
var __dirname = dirname(__filename);
|
|
182
191
|
function getSkillsDir() {
|
|
183
192
|
const devPath = join2(__dirname, "..", "..", "skills");
|
|
184
193
|
const prodPath = join2(__dirname, "..", "skills");
|
|
@@ -202,7 +211,7 @@ function collectFiles(dir, baseDir) {
|
|
|
202
211
|
files.push(...collectFiles(fullPath, baseDir));
|
|
203
212
|
} else if (stat.isFile()) {
|
|
204
213
|
const relativePath = relative(baseDir, fullPath);
|
|
205
|
-
const content =
|
|
214
|
+
const content = readFileSync2(fullPath, "utf-8");
|
|
206
215
|
files.push({ relativePath, content });
|
|
207
216
|
}
|
|
208
217
|
}
|
|
@@ -223,9 +232,8 @@ function loadSkill(name) {
|
|
|
223
232
|
}
|
|
224
233
|
|
|
225
234
|
export {
|
|
226
|
-
|
|
227
|
-
installDependencies,
|
|
235
|
+
toInstallSpecifier,
|
|
228
236
|
GENSTYLEGUIDE_COMMAND_MD,
|
|
229
237
|
loadSkill
|
|
230
238
|
};
|
|
231
|
-
//# sourceMappingURL=chunk-
|
|
239
|
+
//# sourceMappingURL=chunk-TWUDB36F.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/install/versioning.ts","../src/commands/install/constants.ts","../src/utils/skill-loader.ts"],"sourcesContent":["import { createRequire } from \"module\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\n\nconst require = createRequire(import.meta.url);\n\nexport interface ToInstallSpecifierOptions {\n /**\n * When true, prefer pnpm workspace protocol for internal packages so local\n * monorepo installs always link the latest workspace version.\n */\n preferWorkspaceProtocol?: boolean;\n /** Workspace root (used for pnpm-workspace detection) */\n workspaceRoot?: string;\n /** Target project path (used to ensure target is within workspace) */\n targetProjectPath?: string;\n}\n\nfunction isWithinDir(childPath: string, parentPath: string): boolean {\n const parent = parentPath.endsWith(\"/\") ? parentPath : parentPath + \"/\";\n return childPath === parentPath || childPath.startsWith(parent);\n}\n\nfunction isPnpmWorkspaceRoot(dir: string | undefined): boolean {\n if (!dir) return false;\n return existsSync(join(dir, \"pnpm-workspace.yaml\"));\n}\n\nfunction workspaceHasPackage(\n workspaceRoot: string | undefined,\n pkgName: string\n): boolean {\n if (!workspaceRoot) return false;\n // In this repo, workspace packages live under /packages/<name>/package.json\n return existsSync(join(workspaceRoot, \"packages\", pkgName, \"package.json\"));\n}\n\nfunction tryReadInstalledVersion(pkgName: string): string | null {\n try {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const depPkg = require(`${pkgName}/package.json`) as Record<\n string,\n unknown\n >;\n const v = depPkg?.version;\n return typeof v === \"string\" ? v : null;\n } catch {\n // Fallback: try filesystem relative to this package (best-effort)\n try {\n const p = require.resolve(`${pkgName}/package.json`);\n const raw = readFileSync(p, \"utf-8\");\n const parsed = JSON.parse(raw) as { version?: string };\n return typeof parsed.version === \"string\" ? parsed.version : null;\n } catch {\n return null;\n }\n }\n}\n\n/**\n * Get the version range for a dependency from uilint's package.json\n *\n * Note: We also check devDependencies so we can expose install-time version\n * ranges (e.g. for uilint-react) without forcing them to be runtime deps of the CLI.\n */\nexport function getSelfDependencyVersionRange(pkgName: string): string | null {\n try {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const pkgJson = require(\"uilint/package.json\") as Record<string, unknown>;\n const deps = pkgJson?.dependencies as Record<string, string> | undefined;\n const optDeps = pkgJson?.optionalDependencies as\n | Record<string, string>\n | undefined;\n const peerDeps = pkgJson?.peerDependencies as\n | Record<string, string>\n | undefined;\n const devDeps = pkgJson?.devDependencies as\n | Record<string, string>\n | undefined;\n\n const v =\n deps?.[pkgName] ??\n optDeps?.[pkgName] ??\n peerDeps?.[pkgName] ??\n devDeps?.[pkgName];\n return typeof v === \"string\" ? v : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Convert package name to install specifier with version (when possible)\n */\nexport function toInstallSpecifier(\n pkgName: string,\n options: ToInstallSpecifierOptions = {}\n): string {\n const { preferWorkspaceProtocol, workspaceRoot, targetProjectPath } = options;\n\n // Local monorepo: force workspace protocol for internal packages.\n if (\n preferWorkspaceProtocol &&\n isPnpmWorkspaceRoot(workspaceRoot) &&\n typeof targetProjectPath === \"string\" &&\n isWithinDir(targetProjectPath, workspaceRoot || \"\") &&\n workspaceHasPackage(workspaceRoot, pkgName)\n ) {\n return `${pkgName}@workspace:*`;\n }\n\n const range = getSelfDependencyVersionRange(pkgName);\n if (!range) return pkgName;\n if (range.startsWith(\"workspace:\")) {\n // In published builds, pnpm usually rewrites workspace: to a semver range.\n // But if it doesn't, fall back to the actually installed dependency version.\n const installed = tryReadInstalledVersion(pkgName);\n return installed ? `${pkgName}@^${installed}` : pkgName;\n }\n if (range.startsWith(\"file:\")) return pkgName;\n if (range.startsWith(\"link:\")) return pkgName;\n return `${pkgName}@${range}`;\n}\n","/**\n * Constants for the install command\n *\n * Contains file content templates for commands.\n */\n\n// ============================================================================\n// Cursor Commands\n// ============================================================================\n\nexport const GENSTYLEGUIDE_COMMAND_MD = `# React Style Guide Generator\n\nAnalyze the React UI codebase to produce a **prescriptive, semantic** style guide. Focus on consistency, intent, and relationships—not specific values.\n\n## Philosophy\n\n1. **Identify the intended architecture** from the best patterns in use\n2. **Prescribe semantic rules** — about consistency and relationships, not pixels\n3. **Stay general** — \"primary buttons should be visually consistent\" not \"buttons use px-4\"\n4. **Focus on intent** — what should FEEL the same, not what values to use\n\n## Analysis Steps\n\n### 1. Detect the Stack\n- Framework: Next.js (App Router? Pages?), Vite, CRA\n- Component system: shadcn, MUI, Chakra, Radix, custom\n- Styling: Tailwind, CSS Modules, styled-components\n- Forms: react-hook-form, Formik, native\n- State: React context, Zustand, Redux, Jotai\n\n### 2. Identify Best Patterns\nExamine the **best-written** components. Look at:\n- \\`components/ui/*\\` — the design system\n- Recently modified files — current standards\n- Shared layouts — structural patterns\n\n### 3. Infer Visual Hierarchy & Intent\nUnderstand the design language:\n- What distinguishes primary vs secondary actions?\n- How is visual hierarchy established?\n- What creates consistency across similar elements?\n\n## Output Format\n\nGenerate at \\`<nextjs app root>/.uilint/styleguide.md\\`:\n\\`\\`\\`yaml\n# Stack\nframework: \nstyling: \ncomponents: \ncomponent_path: \nforms: \n\n# Component Usage (MUST use these)\nuse:\n buttons: \n inputs: \n modals: \n cards: \n feedback: \n icons: \n links: \n\n# Semantic Rules (consistency & relationships)\nsemantics:\n hierarchy:\n - <e.g., \"primary actions must be visually distinct from secondary\">\n - <e.g., \"destructive actions should be visually cautionary\">\n - <e.g., \"page titles should be visually heavier than section titles\">\n consistency:\n - <e.g., \"all primary buttons should share the same visual weight\">\n - <e.g., \"form inputs should have uniform height and padding\">\n - <e.g., \"card padding should be consistent across the app\">\n - <e.g., \"interactive elements should have consistent hover/focus states\">\n spacing:\n - <e.g., \"use the spacing scale — no arbitrary values\">\n - <e.g., \"related elements should be closer than unrelated\">\n - <e.g., \"section spacing should be larger than element spacing\">\n layout:\n - <e.g., \"use gap for sibling spacing, not margin\">\n - <e.g., \"containers should have consistent max-width and padding\">\n\n# Patterns (structural, not values)\npatterns:\n forms: <e.g., \"FormField + Controller + zod schema\">\n conditionals: <e.g., \"cn() for class merging\">\n loading: <e.g., \"Skeleton for content, Spinner for actions\">\n errors: <e.g., \"ErrorBoundary at route, inline for forms\">\n responsive: <e.g., \"mobile-first, standard breakpoints only\">\n\n# Component Authoring\nauthoring:\n - <e.g., \"forwardRef for interactive components\">\n - <e.g., \"variants via CVA or component props, not className overrides\">\n - <e.g., \"extract when used 2+ times\">\n - <e.g., \"'use client' only when needed\">\n\n# Forbidden\nforbidden:\n - <e.g., \"inline style={{}}\">\n - <e.g., \"raw HTML elements when component exists\">\n - <e.g., \"arbitrary values — use scale\">\n - <e.g., \"className overrides that break visual consistency\">\n - <e.g., \"one-off spacing that doesn't match siblings\">\n\n# Legacy (if migration in progress)\nlegacy:\n - <e.g., \"old: CSS modules → new: Tailwind\">\n - <e.g., \"old: Formik → new: react-hook-form\">\n\n# Conventions\nconventions:\n - \n - \n - \n\\`\\`\\`\n\n## Rules\n\n- **Semantic over specific**: \"consistent padding\" not \"p-4\"\n- **Relationships over absolutes**: \"heavier than\" not \"font-bold\"\n- **Intent over implementation**: \"visually distinct\" not \"blue background\"\n- **Prescriptive**: Define target state, not current state\n- **Terse**: No prose. Fragments and short phrases only.\n- **Actionable**: Every rule should be human-verifiable\n- **Omit if N/A**: Skip sections that don't apply\n- **Max 5 items** per section — highest impact only\n`;\n\n","/**\n * Skill Loader Utility\n *\n * Loads Agent Skill files from the bundled skills directory for installation\n * into user projects. Skills follow the Agent Skills specification\n * (agentskills.io).\n */\n\nimport { readFileSync, readdirSync, statSync, existsSync } from \"fs\";\nimport { join, dirname, relative } from \"path\";\nimport { fileURLToPath } from \"url\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Represents a file in a skill directory\n */\nexport interface SkillFile {\n /** Relative path within the skill directory */\n relativePath: string;\n /** File content */\n content: string;\n}\n\n/**\n * Represents a complete skill ready for installation\n */\nexport interface Skill {\n /** Skill name (directory name) */\n name: string;\n /** All files in the skill */\n files: SkillFile[];\n}\n\n/**\n * Get the path to the bundled skills directory\n */\nfunction getSkillsDir(): string {\n // In development: packages/uilint/skills/\n // In production (installed): node_modules/uilint/dist/ -> ../skills/\n const devPath = join(__dirname, \"..\", \"..\", \"skills\");\n const prodPath = join(__dirname, \"..\", \"skills\");\n\n if (existsSync(devPath)) {\n return devPath;\n }\n if (existsSync(prodPath)) {\n return prodPath;\n }\n\n throw new Error(\n \"Could not find skills directory. This is a bug in uilint installation.\"\n );\n}\n\n/**\n * Recursively collect all files in a directory\n */\nfunction collectFiles(dir: string, baseDir: string): SkillFile[] {\n const files: SkillFile[] = [];\n const entries = readdirSync(dir);\n\n for (const entry of entries) {\n const fullPath = join(dir, entry);\n const stat = statSync(fullPath);\n\n if (stat.isDirectory()) {\n files.push(...collectFiles(fullPath, baseDir));\n } else if (stat.isFile()) {\n const relativePath = relative(baseDir, fullPath);\n const content = readFileSync(fullPath, \"utf-8\");\n files.push({ relativePath, content });\n }\n }\n\n return files;\n}\n\n/**\n * Load a specific skill by name\n */\nexport function loadSkill(name: string): Skill {\n const skillsDir = getSkillsDir();\n const skillDir = join(skillsDir, name);\n\n if (!existsSync(skillDir)) {\n throw new Error(`Skill \"${name}\" not found in ${skillsDir}`);\n }\n\n const skillMdPath = join(skillDir, \"SKILL.md\");\n if (!existsSync(skillMdPath)) {\n throw new Error(`Skill \"${name}\" is missing SKILL.md`);\n }\n\n const files = collectFiles(skillDir, skillDir);\n\n return { name, files };\n}\n\n/**\n * Load all available skills\n */\nexport function loadAllSkills(): Skill[] {\n const skillsDir = getSkillsDir();\n const entries = readdirSync(skillsDir);\n const skills: Skill[] = [];\n\n for (const entry of entries) {\n const skillDir = join(skillsDir, entry);\n const stat = statSync(skillDir);\n\n if (stat.isDirectory()) {\n const skillMdPath = join(skillDir, \"SKILL.md\");\n if (existsSync(skillMdPath)) {\n skills.push(loadSkill(entry));\n }\n }\n }\n\n return skills;\n}\n\n/**\n * Get the list of available skill names\n */\nexport function getAvailableSkillNames(): string[] {\n const skillsDir = getSkillsDir();\n const entries = readdirSync(skillsDir);\n const names: string[] = [];\n\n for (const entry of entries) {\n const skillDir = join(skillsDir, entry);\n const stat = statSync(skillDir);\n\n if (stat.isDirectory()) {\n const skillMdPath = join(skillDir, \"SKILL.md\");\n if (existsSync(skillMdPath)) {\n names.push(entry);\n }\n }\n }\n\n return names;\n}\n"],"mappings":"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","names":["require","readFileSync","existsSync","join"]}
|