uilint 0.2.60 → 0.2.61
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-3LKX26SH.js → chunk-SQFSFBUP.js} +264 -24
- package/dist/chunk-SQFSFBUP.js.map +1 -0
- package/dist/index.js +41 -2
- package/dist/index.js.map +1 -1
- package/dist/{init-ui-D43GYBCT.js → init-ui-Z54KCM7G.js} +2 -2
- package/dist/{remove-ui-WPXAIH3W.js → remove-ui-U2VUMSP7.js} +2 -2
- package/package.json +5 -5
- package/dist/chunk-3LKX26SH.js.map +0 -1
- /package/dist/{init-ui-D43GYBCT.js.map → init-ui-Z54KCM7G.js.map} +0 -0
- /package/dist/{remove-ui-WPXAIH3W.js.map → remove-ui-U2VUMSP7.js.map} +0 -0
|
@@ -2897,8 +2897,183 @@ var skillInstaller = {
|
|
|
2897
2897
|
};
|
|
2898
2898
|
|
|
2899
2899
|
// src/commands/init/installers/eslint.ts
|
|
2900
|
-
import { join as
|
|
2900
|
+
import { join as join14 } from "path";
|
|
2901
2901
|
import { ruleRegistry, getRulesByCategory, getCategoryMeta } from "uilint-eslint";
|
|
2902
|
+
|
|
2903
|
+
// src/commands/init/installers/ai-hooks.ts
|
|
2904
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
|
|
2905
|
+
import { join as join13 } from "path";
|
|
2906
|
+
var POST_EDIT_HOOK_SCRIPT = `#!/bin/bash
|
|
2907
|
+
# AI Editor Hook: Run ESLint on edited files
|
|
2908
|
+
# Triggered after file edits to provide lint feedback
|
|
2909
|
+
#
|
|
2910
|
+
# Output: JSON with lint errors for the AI to see
|
|
2911
|
+
|
|
2912
|
+
# Read JSON input from stdin
|
|
2913
|
+
input=$(cat)
|
|
2914
|
+
file_path=$(echo "$input" | jq -r '.tool_input.file_path // .file_path // empty')
|
|
2915
|
+
|
|
2916
|
+
# Exit if no file path
|
|
2917
|
+
if [[ -z "$file_path" ]]; then
|
|
2918
|
+
exit 0
|
|
2919
|
+
fi
|
|
2920
|
+
|
|
2921
|
+
# Only lint TypeScript/JavaScript files
|
|
2922
|
+
if [[ "$file_path" =~ \\.(ts|tsx|js|jsx)$ ]]; then
|
|
2923
|
+
# Find the package directory (look for eslint.config.ts or package.json)
|
|
2924
|
+
dir=$(dirname "$file_path")
|
|
2925
|
+
while [[ "$dir" != "/" ]]; do
|
|
2926
|
+
if [[ -f "$dir/eslint.config.ts" ]] || [[ -f "$dir/eslint.config.js" ]] || [[ -f "$dir/eslint.config.mjs" ]]; then
|
|
2927
|
+
break
|
|
2928
|
+
fi
|
|
2929
|
+
dir=$(dirname "$dir")
|
|
2930
|
+
done
|
|
2931
|
+
|
|
2932
|
+
# If no config found, exit
|
|
2933
|
+
if [[ "$dir" == "/" ]]; then
|
|
2934
|
+
exit 0
|
|
2935
|
+
fi
|
|
2936
|
+
|
|
2937
|
+
# Run ESLint with --fix first (auto-fix what we can), suppress output
|
|
2938
|
+
(cd "$dir" && npx eslint "$file_path" --fix >/dev/null 2>&1) || true
|
|
2939
|
+
|
|
2940
|
+
# Then report ALL remaining issues
|
|
2941
|
+
remaining=$( (cd "$dir" && npx eslint "$file_path" --format stylish 2>/dev/null) | grep -E "^\\s+[0-9]+:[0-9]+" | head -10)
|
|
2942
|
+
|
|
2943
|
+
if [[ -n "$remaining" ]]; then
|
|
2944
|
+
# Output JSON so the AI sees the lint errors
|
|
2945
|
+
jq -n --arg issues "$remaining" '{"additionalContext": ("ESLint errors - fix these:\\n" + $issues)}'
|
|
2946
|
+
# Exit with error code to signal lint issues
|
|
2947
|
+
exit 1
|
|
2948
|
+
fi
|
|
2949
|
+
fi
|
|
2950
|
+
`;
|
|
2951
|
+
var CLAUDE_HOOK_CONFIG = {
|
|
2952
|
+
hooks: {
|
|
2953
|
+
PostToolUse: [
|
|
2954
|
+
{
|
|
2955
|
+
matcher: "Edit|Write",
|
|
2956
|
+
hooks: [
|
|
2957
|
+
{
|
|
2958
|
+
type: "command",
|
|
2959
|
+
command: "bash .claude/hooks/post-edit.sh"
|
|
2960
|
+
}
|
|
2961
|
+
]
|
|
2962
|
+
}
|
|
2963
|
+
]
|
|
2964
|
+
}
|
|
2965
|
+
};
|
|
2966
|
+
var CURSOR_HOOK_CONFIG = {
|
|
2967
|
+
hooks: {
|
|
2968
|
+
afterFileEdit: [
|
|
2969
|
+
{
|
|
2970
|
+
command: "bash .cursor/hooks/post-edit.sh",
|
|
2971
|
+
filePattern: "**/*.{ts,tsx,js,jsx}"
|
|
2972
|
+
}
|
|
2973
|
+
]
|
|
2974
|
+
}
|
|
2975
|
+
};
|
|
2976
|
+
function isHookInstalled(projectPath, provider) {
|
|
2977
|
+
if (provider === "claude") {
|
|
2978
|
+
const settingsPath = join13(projectPath, ".claude", "settings.json");
|
|
2979
|
+
if (!existsSync12(settingsPath)) return false;
|
|
2980
|
+
try {
|
|
2981
|
+
const content = readFileSync9(settingsPath, "utf-8");
|
|
2982
|
+
const settings = JSON.parse(content);
|
|
2983
|
+
const hooks = settings.hooks?.PostToolUse;
|
|
2984
|
+
if (!Array.isArray(hooks)) return false;
|
|
2985
|
+
return hooks.some(
|
|
2986
|
+
(h) => h.matcher?.includes("Edit") && h.hooks?.some((hh) => hh.command?.includes("post-edit"))
|
|
2987
|
+
);
|
|
2988
|
+
} catch {
|
|
2989
|
+
return false;
|
|
2990
|
+
}
|
|
2991
|
+
} else {
|
|
2992
|
+
const hooksPath = join13(projectPath, ".cursor", "hooks.json");
|
|
2993
|
+
if (!existsSync12(hooksPath)) return false;
|
|
2994
|
+
try {
|
|
2995
|
+
const content = readFileSync9(hooksPath, "utf-8");
|
|
2996
|
+
const hooks = JSON.parse(content);
|
|
2997
|
+
const afterFileEdit = hooks.hooks?.afterFileEdit;
|
|
2998
|
+
if (!Array.isArray(afterFileEdit)) return false;
|
|
2999
|
+
return afterFileEdit.some(
|
|
3000
|
+
(h) => h.command?.includes("post-edit")
|
|
3001
|
+
);
|
|
3002
|
+
} catch {
|
|
3003
|
+
return false;
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
function planClaudeHook(projectPath) {
|
|
3008
|
+
const actions = [];
|
|
3009
|
+
const claudeDir = join13(projectPath, ".claude");
|
|
3010
|
+
const hooksDir = join13(claudeDir, "hooks");
|
|
3011
|
+
const settingsPath = join13(claudeDir, "settings.json");
|
|
3012
|
+
actions.push({
|
|
3013
|
+
type: "create_directory",
|
|
3014
|
+
path: claudeDir
|
|
3015
|
+
});
|
|
3016
|
+
actions.push({
|
|
3017
|
+
type: "create_directory",
|
|
3018
|
+
path: hooksDir
|
|
3019
|
+
});
|
|
3020
|
+
actions.push({
|
|
3021
|
+
type: "create_file",
|
|
3022
|
+
path: join13(hooksDir, "post-edit.sh"),
|
|
3023
|
+
content: POST_EDIT_HOOK_SCRIPT,
|
|
3024
|
+
permissions: 493
|
|
3025
|
+
});
|
|
3026
|
+
if (existsSync12(settingsPath)) {
|
|
3027
|
+
actions.push({
|
|
3028
|
+
type: "merge_json",
|
|
3029
|
+
path: settingsPath,
|
|
3030
|
+
merge: CLAUDE_HOOK_CONFIG
|
|
3031
|
+
});
|
|
3032
|
+
} else {
|
|
3033
|
+
actions.push({
|
|
3034
|
+
type: "create_file",
|
|
3035
|
+
path: settingsPath,
|
|
3036
|
+
content: JSON.stringify(CLAUDE_HOOK_CONFIG, null, 2)
|
|
3037
|
+
});
|
|
3038
|
+
}
|
|
3039
|
+
return actions;
|
|
3040
|
+
}
|
|
3041
|
+
function planCursorHook(projectPath) {
|
|
3042
|
+
const actions = [];
|
|
3043
|
+
const cursorDir = join13(projectPath, ".cursor");
|
|
3044
|
+
const hooksDir = join13(cursorDir, "hooks");
|
|
3045
|
+
const hooksJsonPath = join13(cursorDir, "hooks.json");
|
|
3046
|
+
actions.push({
|
|
3047
|
+
type: "create_directory",
|
|
3048
|
+
path: cursorDir
|
|
3049
|
+
});
|
|
3050
|
+
actions.push({
|
|
3051
|
+
type: "create_directory",
|
|
3052
|
+
path: hooksDir
|
|
3053
|
+
});
|
|
3054
|
+
actions.push({
|
|
3055
|
+
type: "create_file",
|
|
3056
|
+
path: join13(hooksDir, "post-edit.sh"),
|
|
3057
|
+
content: POST_EDIT_HOOK_SCRIPT,
|
|
3058
|
+
permissions: 493
|
|
3059
|
+
});
|
|
3060
|
+
if (existsSync12(hooksJsonPath)) {
|
|
3061
|
+
actions.push({
|
|
3062
|
+
type: "merge_json",
|
|
3063
|
+
path: hooksJsonPath,
|
|
3064
|
+
merge: CURSOR_HOOK_CONFIG
|
|
3065
|
+
});
|
|
3066
|
+
} else {
|
|
3067
|
+
actions.push({
|
|
3068
|
+
type: "create_file",
|
|
3069
|
+
path: hooksJsonPath,
|
|
3070
|
+
content: JSON.stringify(CURSOR_HOOK_CONFIG, null, 2)
|
|
3071
|
+
});
|
|
3072
|
+
}
|
|
3073
|
+
return actions;
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3076
|
+
// src/commands/init/installers/eslint.ts
|
|
2902
3077
|
async function promptForField(field, currentValue) {
|
|
2903
3078
|
const hint = field.description ? pc.dim(` ${field.description}`) : "";
|
|
2904
3079
|
switch (field.type) {
|
|
@@ -3187,7 +3362,49 @@ ${semanticCat?.icon ?? "\u{1F9E0}"} ${semanticCat?.name ?? "Semantic rules"} (${
|
|
|
3187
3362
|
}
|
|
3188
3363
|
}
|
|
3189
3364
|
}
|
|
3190
|
-
|
|
3365
|
+
log("");
|
|
3366
|
+
const claudeInstalled = isHookInstalled(project.projectPath, "claude");
|
|
3367
|
+
const cursorInstalled = isHookInstalled(project.projectPath, "cursor");
|
|
3368
|
+
const hookOptions = [];
|
|
3369
|
+
if (!claudeInstalled) {
|
|
3370
|
+
hookOptions.push({
|
|
3371
|
+
value: "claude",
|
|
3372
|
+
label: "Claude Code",
|
|
3373
|
+
hint: "Auto-lint on file edit via .claude/hooks/post-edit.sh"
|
|
3374
|
+
});
|
|
3375
|
+
}
|
|
3376
|
+
if (!cursorInstalled) {
|
|
3377
|
+
hookOptions.push({
|
|
3378
|
+
value: "cursor",
|
|
3379
|
+
label: "Cursor",
|
|
3380
|
+
hint: "Auto-lint on file edit via .cursor/hooks/post-edit.sh"
|
|
3381
|
+
});
|
|
3382
|
+
}
|
|
3383
|
+
let aiHooks = [];
|
|
3384
|
+
if (hookOptions.length > 0) {
|
|
3385
|
+
hookOptions.push({
|
|
3386
|
+
value: "none",
|
|
3387
|
+
label: "None",
|
|
3388
|
+
hint: "Skip AI editor integration"
|
|
3389
|
+
});
|
|
3390
|
+
const selectedHooks = await multiselect({
|
|
3391
|
+
message: "Install AI editor hooks? " + pc.dim("(auto-lint on file edit)"),
|
|
3392
|
+
options: hookOptions,
|
|
3393
|
+
initialValues: [],
|
|
3394
|
+
required: false
|
|
3395
|
+
});
|
|
3396
|
+
aiHooks = selectedHooks.filter((h) => h === "claude" || h === "cursor");
|
|
3397
|
+
if (aiHooks.length > 0) {
|
|
3398
|
+
log(
|
|
3399
|
+
pc.dim(` \u2192 Will install hooks for: ${aiHooks.join(", ")}`)
|
|
3400
|
+
);
|
|
3401
|
+
}
|
|
3402
|
+
} else {
|
|
3403
|
+
log(
|
|
3404
|
+
pc.dim(" AI hooks already installed (Claude and Cursor)")
|
|
3405
|
+
);
|
|
3406
|
+
}
|
|
3407
|
+
return { configuredRules, aiHooks };
|
|
3191
3408
|
},
|
|
3192
3409
|
plan(targets, config, project) {
|
|
3193
3410
|
const actions = [];
|
|
@@ -3202,7 +3419,7 @@ ${semanticCat?.icon ?? "\u{1F9E0}"} ${semanticCat?.name ?? "Semantic rules"} (${
|
|
|
3202
3419
|
for (const target of targets) {
|
|
3203
3420
|
const pkgInfo = project.packages.find((p) => p.path === target.path);
|
|
3204
3421
|
if (!pkgInfo || !pkgInfo.eslintConfigPath) continue;
|
|
3205
|
-
const rulesDir =
|
|
3422
|
+
const rulesDir = join14(target.path, ".uilint", "rules");
|
|
3206
3423
|
actions.push({
|
|
3207
3424
|
type: "create_directory",
|
|
3208
3425
|
path: rulesDir
|
|
@@ -3227,13 +3444,20 @@ ${semanticCat?.icon ?? "\u{1F9E0}"} ${semanticCat?.name ?? "Semantic rules"} (${
|
|
|
3227
3444
|
hasExistingRules: pkgInfo.hasUilintRules
|
|
3228
3445
|
});
|
|
3229
3446
|
}
|
|
3230
|
-
const gitignorePath =
|
|
3447
|
+
const gitignorePath = join14(project.workspaceRoot, ".gitignore");
|
|
3231
3448
|
actions.push({
|
|
3232
3449
|
type: "append_to_file",
|
|
3233
3450
|
path: gitignorePath,
|
|
3234
3451
|
content: "\n# UILint cache\n.uilint/.cache\n",
|
|
3235
3452
|
ifNotContains: ".uilint/.cache"
|
|
3236
3453
|
});
|
|
3454
|
+
const { aiHooks } = eslintConfig;
|
|
3455
|
+
if (aiHooks?.includes("claude")) {
|
|
3456
|
+
actions.push(...planClaudeHook(project.projectPath));
|
|
3457
|
+
}
|
|
3458
|
+
if (aiHooks?.includes("cursor")) {
|
|
3459
|
+
actions.push(...planCursorHook(project.projectPath));
|
|
3460
|
+
}
|
|
3237
3461
|
return { actions, dependencies };
|
|
3238
3462
|
},
|
|
3239
3463
|
async *execute(targets, config, project) {
|
|
@@ -3254,9 +3478,25 @@ ${semanticCat?.icon ?? "\u{1F9E0}"} ${semanticCat?.name ?? "Semantic rules"} (${
|
|
|
3254
3478
|
detail: `\u2192 ${target.hint}`
|
|
3255
3479
|
};
|
|
3256
3480
|
}
|
|
3481
|
+
const { aiHooks } = eslintConfig;
|
|
3482
|
+
if (aiHooks?.includes("claude")) {
|
|
3483
|
+
yield {
|
|
3484
|
+
type: "progress",
|
|
3485
|
+
message: "Installing Claude Code hook",
|
|
3486
|
+
detail: "\u2192 .claude/hooks/post-edit.sh"
|
|
3487
|
+
};
|
|
3488
|
+
}
|
|
3489
|
+
if (aiHooks?.includes("cursor")) {
|
|
3490
|
+
yield {
|
|
3491
|
+
type: "progress",
|
|
3492
|
+
message: "Installing Cursor hook",
|
|
3493
|
+
detail: "\u2192 .cursor/hooks/post-edit.sh"
|
|
3494
|
+
};
|
|
3495
|
+
}
|
|
3496
|
+
const hookSuffix = aiHooks?.length ? ` + ${aiHooks.length} AI hook(s)` : "";
|
|
3257
3497
|
yield {
|
|
3258
3498
|
type: "complete",
|
|
3259
|
-
message: `ESLint plugin installed in ${targets.length} package(s)`
|
|
3499
|
+
message: `ESLint plugin installed in ${targets.length} package(s)${hookSuffix}`
|
|
3260
3500
|
};
|
|
3261
3501
|
},
|
|
3262
3502
|
planRemove(targets, project) {
|
|
@@ -3269,7 +3509,7 @@ ${semanticCat?.icon ?? "\u{1F9E0}"} ${semanticCat?.name ?? "Semantic rules"} (${
|
|
|
3269
3509
|
packagePath: target.path,
|
|
3270
3510
|
configPath: pkgInfo.eslintConfigPath
|
|
3271
3511
|
});
|
|
3272
|
-
const rulesDir =
|
|
3512
|
+
const rulesDir = join14(target.path, ".uilint", "rules");
|
|
3273
3513
|
actions.push({
|
|
3274
3514
|
type: "remove_directory",
|
|
3275
3515
|
path: rulesDir
|
|
@@ -3280,12 +3520,12 @@ ${semanticCat?.icon ?? "\u{1F9E0}"} ${semanticCat?.name ?? "Semantic rules"} (${
|
|
|
3280
3520
|
};
|
|
3281
3521
|
|
|
3282
3522
|
// src/utils/client-boundary-tracer.ts
|
|
3283
|
-
import { existsSync as
|
|
3284
|
-
import { join as
|
|
3523
|
+
import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
|
|
3524
|
+
import { join as join15, dirname as dirname4, relative as relative3 } from "path";
|
|
3285
3525
|
import { parseModule as parseModule4 } from "magicast";
|
|
3286
3526
|
function hasUseClientDirective(filePath) {
|
|
3287
3527
|
try {
|
|
3288
|
-
const content =
|
|
3528
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
3289
3529
|
const mod = parseModule4(content);
|
|
3290
3530
|
const program = mod.$ast;
|
|
3291
3531
|
if (!program || program.type !== "Program") return false;
|
|
@@ -3334,33 +3574,33 @@ function resolveImportPath(importSource, fromFile, projectPath) {
|
|
|
3334
3574
|
let basePath;
|
|
3335
3575
|
if (importSource.startsWith("@/")) {
|
|
3336
3576
|
const withoutAlias = importSource.slice(2);
|
|
3337
|
-
const srcPath =
|
|
3338
|
-
const rootPath =
|
|
3339
|
-
basePath =
|
|
3577
|
+
const srcPath = join15(projectPath, "src", withoutAlias);
|
|
3578
|
+
const rootPath = join15(projectPath, withoutAlias);
|
|
3579
|
+
basePath = existsSync13(dirname4(srcPath)) ? srcPath : rootPath;
|
|
3340
3580
|
} else if (importSource.startsWith("~/")) {
|
|
3341
|
-
basePath =
|
|
3581
|
+
basePath = join15(projectPath, importSource.slice(2));
|
|
3342
3582
|
} else if (importSource.startsWith(".")) {
|
|
3343
|
-
basePath =
|
|
3583
|
+
basePath = join15(fromDir, importSource);
|
|
3344
3584
|
} else {
|
|
3345
3585
|
return null;
|
|
3346
3586
|
}
|
|
3347
3587
|
const extensions = [".tsx", ".ts", ".jsx", ".js"];
|
|
3348
3588
|
for (const ext of extensions) {
|
|
3349
3589
|
const fullPath = basePath + ext;
|
|
3350
|
-
if (
|
|
3590
|
+
if (existsSync13(fullPath)) return fullPath;
|
|
3351
3591
|
}
|
|
3352
3592
|
for (const ext of extensions) {
|
|
3353
|
-
const indexPath =
|
|
3354
|
-
if (
|
|
3593
|
+
const indexPath = join15(basePath, `index${ext}`);
|
|
3594
|
+
if (existsSync13(indexPath)) return indexPath;
|
|
3355
3595
|
}
|
|
3356
|
-
if (
|
|
3596
|
+
if (existsSync13(basePath)) return basePath;
|
|
3357
3597
|
return null;
|
|
3358
3598
|
}
|
|
3359
3599
|
function findLayoutFile2(projectPath, appRoot) {
|
|
3360
3600
|
const extensions = [".tsx", ".jsx", ".ts", ".js"];
|
|
3361
3601
|
for (const ext of extensions) {
|
|
3362
|
-
const layoutPath =
|
|
3363
|
-
if (
|
|
3602
|
+
const layoutPath = join15(projectPath, appRoot, `layout${ext}`);
|
|
3603
|
+
if (existsSync13(layoutPath)) return layoutPath;
|
|
3364
3604
|
}
|
|
3365
3605
|
return null;
|
|
3366
3606
|
}
|
|
@@ -3381,7 +3621,7 @@ function traceClientBoundaries(projectPath, appRoot) {
|
|
|
3381
3621
|
}
|
|
3382
3622
|
let program;
|
|
3383
3623
|
try {
|
|
3384
|
-
const content =
|
|
3624
|
+
const content = readFileSync10(layoutFile, "utf-8");
|
|
3385
3625
|
const mod = parseModule4(content);
|
|
3386
3626
|
program = mod.$ast;
|
|
3387
3627
|
} catch {
|
|
@@ -3418,8 +3658,8 @@ function providersFileExists(projectPath, appRoot) {
|
|
|
3418
3658
|
const names = ["providers", "Providers"];
|
|
3419
3659
|
for (const name of names) {
|
|
3420
3660
|
for (const ext of extensions) {
|
|
3421
|
-
const providersPath =
|
|
3422
|
-
if (
|
|
3661
|
+
const providersPath = join15(projectPath, appRoot, `${name}${ext}`);
|
|
3662
|
+
if (existsSync13(providersPath)) return providersPath;
|
|
3423
3663
|
}
|
|
3424
3664
|
}
|
|
3425
3665
|
return null;
|
|
@@ -3768,4 +4008,4 @@ export {
|
|
|
3768
4008
|
analyze,
|
|
3769
4009
|
execute
|
|
3770
4010
|
};
|
|
3771
|
-
//# sourceMappingURL=chunk-
|
|
4011
|
+
//# sourceMappingURL=chunk-SQFSFBUP.js.map
|