@hacksmith/doraval 0.2.26 → 0.2.28
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/bin/doraval.js +277 -174
- package/package.json +1 -1
package/bin/doraval.js
CHANGED
|
@@ -599,7 +599,7 @@ var init_dist = __esm(() => {
|
|
|
599
599
|
var require_package = __commonJS((exports, module) => {
|
|
600
600
|
module.exports = {
|
|
601
601
|
name: "@hacksmith/doraval",
|
|
602
|
-
version: "0.2.
|
|
602
|
+
version: "0.2.28",
|
|
603
603
|
author: "Saif",
|
|
604
604
|
repository: {
|
|
605
605
|
type: "git",
|
|
@@ -772,6 +772,23 @@ var init_frontmatter = __esm(() => {
|
|
|
772
772
|
});
|
|
773
773
|
|
|
774
774
|
// src/core/skill-validate.ts
|
|
775
|
+
import { existsSync } from "fs";
|
|
776
|
+
import { resolve } from "path";
|
|
777
|
+
async function loadSkill(dir) {
|
|
778
|
+
const skillMd = resolve(dir, "SKILL.md");
|
|
779
|
+
if (!existsSync(skillMd)) {
|
|
780
|
+
return { ok: false, error: "No SKILL.md found" };
|
|
781
|
+
}
|
|
782
|
+
const raw = await Bun.file(skillMd).text();
|
|
783
|
+
let parsed;
|
|
784
|
+
try {
|
|
785
|
+
parsed = parseFrontmatter(raw);
|
|
786
|
+
} catch {
|
|
787
|
+
return { ok: false, error: "Failed to parse YAML frontmatter in SKILL.md" };
|
|
788
|
+
}
|
|
789
|
+
const existingDirs = OPTIONAL_DIRS.filter((d) => existsSync(resolve(dir, d)));
|
|
790
|
+
return { ok: true, model: parsed, existingDirs };
|
|
791
|
+
}
|
|
775
792
|
function checkFrontmatterPresence(model, _ctx) {
|
|
776
793
|
const keys = Object.keys(model.data);
|
|
777
794
|
if (keys.length === 0) {
|
|
@@ -836,8 +853,9 @@ var merge = (a, b) => ({
|
|
|
836
853
|
errors: [...a.errors, ...b.errors ?? []],
|
|
837
854
|
warnings: [...a.warnings, ...b.warnings ?? []],
|
|
838
855
|
passes: [...a.passes, ...b.passes ?? []]
|
|
839
|
-
}), NAME_REGEX, KNOWN_FIELDS, SUPPORTING_DIRS, EMPTY, checks;
|
|
856
|
+
}), NAME_REGEX, KNOWN_FIELDS, SUPPORTING_DIRS, OPTIONAL_DIRS, EMPTY, checks;
|
|
840
857
|
var init_skill_validate = __esm(() => {
|
|
858
|
+
init_frontmatter();
|
|
841
859
|
NAME_REGEX = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
842
860
|
KNOWN_FIELDS = new Set([
|
|
843
861
|
"name",
|
|
@@ -858,6 +876,7 @@ var init_skill_validate = __esm(() => {
|
|
|
858
876
|
"shell"
|
|
859
877
|
]);
|
|
860
878
|
SUPPORTING_DIRS = ["references", "scripts", "assets", "examples"];
|
|
879
|
+
OPTIONAL_DIRS = ["references", "scripts", "assets"];
|
|
861
880
|
EMPTY = { errors: [], warnings: [], passes: [] };
|
|
862
881
|
checks = [
|
|
863
882
|
checkFrontmatterPresence,
|
|
@@ -876,16 +895,14 @@ var exports_validate = {};
|
|
|
876
895
|
__export(exports_validate, {
|
|
877
896
|
default: () => validate_default
|
|
878
897
|
});
|
|
879
|
-
import { existsSync } from "fs";
|
|
880
|
-
import { resolve } from "path";
|
|
881
|
-
var import_picocolors2,
|
|
898
|
+
import { existsSync as existsSync2 } from "fs";
|
|
899
|
+
import { resolve as resolve2 } from "path";
|
|
900
|
+
var import_picocolors2, validate_default;
|
|
882
901
|
var init_validate = __esm(() => {
|
|
883
902
|
init_dist();
|
|
884
903
|
init_out();
|
|
885
|
-
init_frontmatter();
|
|
886
904
|
init_skill_validate();
|
|
887
905
|
import_picocolors2 = __toESM(require_picocolors(), 1);
|
|
888
|
-
OPTIONAL_DIRS = ["references", "scripts", "assets"];
|
|
889
906
|
validate_default = defineCommand({
|
|
890
907
|
meta: {
|
|
891
908
|
name: "validate",
|
|
@@ -921,16 +938,17 @@ var init_validate = __esm(() => {
|
|
|
921
938
|
},
|
|
922
939
|
async run({ args }) {
|
|
923
940
|
const targetPath = args.path;
|
|
924
|
-
const fullPath =
|
|
925
|
-
if (!
|
|
941
|
+
const fullPath = resolve2(targetPath);
|
|
942
|
+
if (!existsSync2(fullPath)) {
|
|
926
943
|
ui.fail(`Path not found: ${targetPath}
|
|
927
944
|
|
|
928
945
|
Check that the path is correct and the directory exists.`);
|
|
929
946
|
process.exit(1);
|
|
930
947
|
}
|
|
931
|
-
const
|
|
932
|
-
if (!
|
|
933
|
-
|
|
948
|
+
const loaded = await loadSkill(fullPath);
|
|
949
|
+
if (!loaded.ok) {
|
|
950
|
+
if (loaded.error === "No SKILL.md found") {
|
|
951
|
+
ui.fail(`No skill or plugin found at ${targetPath}
|
|
934
952
|
|
|
935
953
|
Searched for:
|
|
936
954
|
\u2022 SKILL.md (Agent Skills spec)
|
|
@@ -939,19 +957,14 @@ Searched for:
|
|
|
939
957
|
Try:
|
|
940
958
|
\u2022 Check the path points to a skill or plugin directory
|
|
941
959
|
\u2022 Use --for to target a specific validator`);
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
const raw = await Bun.file(skillMd).text();
|
|
945
|
-
let parsed;
|
|
946
|
-
try {
|
|
947
|
-
parsed = parseFrontmatter(raw);
|
|
948
|
-
} catch {
|
|
949
|
-
ui.fail(`Failed to parse YAML frontmatter in SKILL.md
|
|
960
|
+
} else {
|
|
961
|
+
ui.fail(`${loaded.error}
|
|
950
962
|
|
|
951
963
|
Fix the YAML syntax and retry.`);
|
|
964
|
+
}
|
|
952
965
|
process.exit(1);
|
|
953
966
|
}
|
|
954
|
-
const
|
|
967
|
+
const { model: parsed, existingDirs } = loaded;
|
|
955
968
|
const { errors, warnings, passes } = validateSkillModel(parsed, {
|
|
956
969
|
existingDirs: [...existingDirs]
|
|
957
970
|
});
|
|
@@ -1060,13 +1073,12 @@ var exports_drift = {};
|
|
|
1060
1073
|
__export(exports_drift, {
|
|
1061
1074
|
default: () => drift_default
|
|
1062
1075
|
});
|
|
1063
|
-
import {
|
|
1064
|
-
import { resolve as resolve2 } from "path";
|
|
1076
|
+
import { resolve as resolve3 } from "path";
|
|
1065
1077
|
var import_picocolors3, drift_default;
|
|
1066
1078
|
var init_drift = __esm(() => {
|
|
1067
1079
|
init_dist();
|
|
1068
1080
|
init_out();
|
|
1069
|
-
|
|
1081
|
+
init_skill_validate();
|
|
1070
1082
|
init_skill_drift();
|
|
1071
1083
|
import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
1072
1084
|
drift_default = defineCommand({
|
|
@@ -1104,22 +1116,19 @@ var init_drift = __esm(() => {
|
|
|
1104
1116
|
},
|
|
1105
1117
|
async run({ args }) {
|
|
1106
1118
|
const targetPath = args.path;
|
|
1107
|
-
const fullPath =
|
|
1108
|
-
const
|
|
1109
|
-
if (!
|
|
1110
|
-
|
|
1119
|
+
const fullPath = resolve3(targetPath);
|
|
1120
|
+
const loaded = await loadSkill(fullPath);
|
|
1121
|
+
if (!loaded.ok) {
|
|
1122
|
+
if (loaded.error === "No SKILL.md found") {
|
|
1123
|
+
ui.fail(`No SKILL.md found at ${targetPath}
|
|
1111
1124
|
|
|
1112
1125
|
Check that the path points to a skill directory containing SKILL.md.`);
|
|
1126
|
+
} else {
|
|
1127
|
+
ui.fail(loaded.error);
|
|
1128
|
+
}
|
|
1113
1129
|
process.exit(1);
|
|
1114
1130
|
}
|
|
1115
|
-
const
|
|
1116
|
-
let parsed;
|
|
1117
|
-
try {
|
|
1118
|
-
parsed = parseFrontmatter(raw);
|
|
1119
|
-
} catch {
|
|
1120
|
-
ui.fail("Failed to parse YAML frontmatter in SKILL.md");
|
|
1121
|
-
process.exit(1);
|
|
1122
|
-
}
|
|
1131
|
+
const { model: parsed } = loaded;
|
|
1123
1132
|
const desc = String(parsed.data.description || "");
|
|
1124
1133
|
const when = String(parsed.data.when_to_use || "");
|
|
1125
1134
|
const { drifts, driftCount, total } = analyzeDrift({
|
|
@@ -2869,7 +2878,7 @@ var exports_bump = {};
|
|
|
2869
2878
|
__export(exports_bump, {
|
|
2870
2879
|
default: () => bump_default
|
|
2871
2880
|
});
|
|
2872
|
-
import { resolve as
|
|
2881
|
+
import { resolve as resolve4, join as join9, dirname, relative } from "path";
|
|
2873
2882
|
import { existsSync as existsSync10, readFileSync, writeFileSync as writeFileSync2, readdirSync as readdirSync4, statSync } from "fs";
|
|
2874
2883
|
function bumpVersion(current, type) {
|
|
2875
2884
|
if (/^\d+\.\d+\.\d+$/.test(type))
|
|
@@ -3005,7 +3014,7 @@ var init_bump = __esm(() => {
|
|
|
3005
3014
|
process.exit(1);
|
|
3006
3015
|
}
|
|
3007
3016
|
const isKnownType = ["patch", "minor", "major"].includes(rawType) || /^\d+\.\d+\.\d+$/.test(rawType);
|
|
3008
|
-
const maybePath =
|
|
3017
|
+
const maybePath = resolve4(rawType);
|
|
3009
3018
|
const looksLikeDir = existsSync10(maybePath) || rawType === "." || rawType.startsWith("./") || rawType.startsWith("../");
|
|
3010
3019
|
if (!isKnownType && looksLikeDir) {
|
|
3011
3020
|
targetPath = rawType;
|
|
@@ -3014,7 +3023,7 @@ var init_bump = __esm(() => {
|
|
|
3014
3023
|
ui.fail(`Unknown bump type "${rawType}". Use patch | minor | major | 1.2.3`);
|
|
3015
3024
|
process.exit(1);
|
|
3016
3025
|
}
|
|
3017
|
-
const root =
|
|
3026
|
+
const root = resolve4(targetPath);
|
|
3018
3027
|
if (!existsSync10(root)) {
|
|
3019
3028
|
ui.fail(`Path does not exist: ${root}`);
|
|
3020
3029
|
process.exit(1);
|
|
@@ -3329,42 +3338,36 @@ var init_new2 = __esm(() => {
|
|
|
3329
3338
|
|
|
3330
3339
|
// src/validators/claude/skill.ts
|
|
3331
3340
|
import { existsSync as existsSync13 } from "fs";
|
|
3332
|
-
import { resolve as
|
|
3333
|
-
var
|
|
3341
|
+
import { resolve as resolve5 } from "path";
|
|
3342
|
+
var claudeSkillValidator;
|
|
3334
3343
|
var init_skill = __esm(() => {
|
|
3335
|
-
init_frontmatter();
|
|
3336
3344
|
init_skill_validate();
|
|
3337
|
-
OPTIONAL_DIRS2 = ["references", "scripts", "assets"];
|
|
3338
3345
|
claudeSkillValidator = {
|
|
3339
3346
|
id: "claude:skill",
|
|
3340
3347
|
provider: "claude",
|
|
3341
3348
|
name: "Claude Skill",
|
|
3342
3349
|
description: "Validates SKILL.md per current Claude Code spec: frontmatter (name/description relaxed to recommended; directory name usually provides the /command), body, supporting files, dynamic injection (!`cmd`), substitutions ($ARGUMENTS, ${CLAUDE_*}), and advanced fields (allowed-tools, context, disable-model-invocation, when_to_use, etc.)",
|
|
3343
3350
|
detect(dir) {
|
|
3344
|
-
return existsSync13(
|
|
3351
|
+
return existsSync13(resolve5(dir, "SKILL.md"));
|
|
3345
3352
|
},
|
|
3346
3353
|
async validate(dir, _opts) {
|
|
3347
|
-
const
|
|
3348
|
-
|
|
3349
|
-
let parsed;
|
|
3350
|
-
try {
|
|
3351
|
-
parsed = parseFrontmatter(raw);
|
|
3352
|
-
} catch {
|
|
3354
|
+
const loaded = await loadSkill(dir);
|
|
3355
|
+
if (!loaded.ok) {
|
|
3353
3356
|
return {
|
|
3354
|
-
errors: [
|
|
3357
|
+
errors: [loaded.error],
|
|
3355
3358
|
warnings: [],
|
|
3356
3359
|
passes: []
|
|
3357
3360
|
};
|
|
3358
3361
|
}
|
|
3359
|
-
const
|
|
3360
|
-
return validateSkillModel(
|
|
3362
|
+
const { model, existingDirs } = loaded;
|
|
3363
|
+
return validateSkillModel(model, { existingDirs: [...existingDirs] });
|
|
3361
3364
|
}
|
|
3362
3365
|
};
|
|
3363
3366
|
});
|
|
3364
3367
|
|
|
3365
3368
|
// src/validators/claude/plugin.ts
|
|
3366
3369
|
import { existsSync as existsSync14, readdirSync as readdirSync6 } from "fs";
|
|
3367
|
-
import { resolve as
|
|
3370
|
+
import { resolve as resolve6, join as join12 } from "path";
|
|
3368
3371
|
function levenshtein(a, b) {
|
|
3369
3372
|
if (a === b)
|
|
3370
3373
|
return 0;
|
|
@@ -3373,15 +3376,21 @@ function levenshtein(a, b) {
|
|
|
3373
3376
|
return n;
|
|
3374
3377
|
if (n === 0)
|
|
3375
3378
|
return m;
|
|
3376
|
-
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
3377
|
-
for (let i = 0;i <= m; i++)
|
|
3378
|
-
dp[i]
|
|
3379
|
-
|
|
3380
|
-
|
|
3379
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
3380
|
+
for (let i = 0;i <= m; i++) {
|
|
3381
|
+
const row = dp[i];
|
|
3382
|
+
row[0] = i;
|
|
3383
|
+
}
|
|
3384
|
+
for (let j = 0;j <= n; j++) {
|
|
3385
|
+
const row = dp[0];
|
|
3386
|
+
row[j] = j;
|
|
3387
|
+
}
|
|
3381
3388
|
for (let i = 1;i <= m; i++) {
|
|
3389
|
+
const row = dp[i];
|
|
3390
|
+
const prev = dp[i - 1];
|
|
3382
3391
|
for (let j = 1;j <= n; j++) {
|
|
3383
3392
|
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
3384
|
-
|
|
3393
|
+
row[j] = Math.min((prev[j] ?? 0) + 1, (row[j - 1] ?? 0) + 1, (prev[j - 1] ?? 0) + cost);
|
|
3385
3394
|
}
|
|
3386
3395
|
}
|
|
3387
3396
|
return dp[m][n];
|
|
@@ -3450,14 +3459,14 @@ var init_plugin = __esm(() => {
|
|
|
3450
3459
|
name: "Claude Plugin",
|
|
3451
3460
|
description: "Validates .claude-plugin/plugin.json manifest (complete schema per Plugins reference), component path rules (replace vs augment), .claude-plugin/ purity, default dirs, single-root-skill layout, unrecognized fields + suggestions, and structure",
|
|
3452
3461
|
detect(dir) {
|
|
3453
|
-
return existsSync14(
|
|
3462
|
+
return existsSync14(resolve6(dir, ".claude-plugin", "plugin.json"));
|
|
3454
3463
|
},
|
|
3455
3464
|
async validate(dir, _opts) {
|
|
3456
3465
|
const errors = [];
|
|
3457
3466
|
const warnings = [];
|
|
3458
3467
|
const passes = [];
|
|
3459
|
-
const manifestPath =
|
|
3460
|
-
const dotClaudePluginDir =
|
|
3468
|
+
const manifestPath = resolve6(dir, ".claude-plugin", "plugin.json");
|
|
3469
|
+
const dotClaudePluginDir = resolve6(dir, ".claude-plugin");
|
|
3461
3470
|
let manifest;
|
|
3462
3471
|
try {
|
|
3463
3472
|
const raw = await Bun.file(manifestPath).text();
|
|
@@ -3553,7 +3562,7 @@ var init_plugin = __esm(() => {
|
|
|
3553
3562
|
errors.push(`${field}: path "${s}" must start with "./"`);
|
|
3554
3563
|
} else if (s.includes("..")) {
|
|
3555
3564
|
errors.push(`${field}: path "${s}" must not use ".." (paths are confined to the plugin tree after cache copy)`);
|
|
3556
|
-
} else if (existsSync14(
|
|
3565
|
+
} else if (existsSync14(resolve6(dir, s))) {
|
|
3557
3566
|
passes.push(`${field}: path "${s}" exists`);
|
|
3558
3567
|
} else {
|
|
3559
3568
|
warnings.push(`${field}: path "${s}" does not exist on disk`);
|
|
@@ -3602,7 +3611,7 @@ var init_plugin = __esm(() => {
|
|
|
3602
3611
|
if (Array.isArray(manifest.dependencies)) {
|
|
3603
3612
|
passes.push(`dependencies: declares ${manifest.dependencies.length} plugin dependency/ies`);
|
|
3604
3613
|
}
|
|
3605
|
-
const skillsDir =
|
|
3614
|
+
const skillsDir = resolve6(dir, "skills");
|
|
3606
3615
|
if (existsSync14(skillsDir)) {
|
|
3607
3616
|
const entries = readdirSync6(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
3608
3617
|
for (const e of entries) {
|
|
@@ -3617,7 +3626,7 @@ var init_plugin = __esm(() => {
|
|
|
3617
3626
|
warnings.push('Default skills/ dir co-exists with manifest "skills" \u2014 manifest path is authoritative; default folder ignored for loading');
|
|
3618
3627
|
}
|
|
3619
3628
|
}
|
|
3620
|
-
const commandsDir =
|
|
3629
|
+
const commandsDir = resolve6(dir, "commands");
|
|
3621
3630
|
if (existsSync14(commandsDir)) {
|
|
3622
3631
|
const mds = readdirSync6(commandsDir).filter((f) => f.endsWith(".md"));
|
|
3623
3632
|
if (mds.length) {
|
|
@@ -3627,7 +3636,7 @@ var init_plugin = __esm(() => {
|
|
|
3627
3636
|
warnings.push('commands/ co-exists with manifest "commands" \u2014 manifest replaces default (dir ignored)');
|
|
3628
3637
|
}
|
|
3629
3638
|
}
|
|
3630
|
-
const agentsDir =
|
|
3639
|
+
const agentsDir = resolve6(dir, "agents");
|
|
3631
3640
|
if (existsSync14(agentsDir)) {
|
|
3632
3641
|
const mds = readdirSync6(agentsDir).filter((f) => f.endsWith(".md"));
|
|
3633
3642
|
if (mds.length) {
|
|
@@ -3637,30 +3646,30 @@ var init_plugin = __esm(() => {
|
|
|
3637
3646
|
warnings.push('agents/ co-exists with manifest "agents" \u2014 manifest replaces default (dir ignored)');
|
|
3638
3647
|
}
|
|
3639
3648
|
}
|
|
3640
|
-
if (existsSync14(
|
|
3649
|
+
if (existsSync14(resolve6(dir, "output-styles"))) {
|
|
3641
3650
|
passes.push("output-styles/ directory present");
|
|
3642
3651
|
if (manifest.outputStyles)
|
|
3643
3652
|
warnings.push("output-styles/ co-exists with manifest outputStyles \u2014 manifest wins");
|
|
3644
3653
|
}
|
|
3645
|
-
if (existsSync14(
|
|
3654
|
+
if (existsSync14(resolve6(dir, "themes")))
|
|
3646
3655
|
passes.push("themes/ present (experimental)");
|
|
3647
|
-
if (existsSync14(
|
|
3656
|
+
if (existsSync14(resolve6(dir, "monitors")) || manifest.experimental?.monitors) {
|
|
3648
3657
|
passes.push("monitors config present (experimental)");
|
|
3649
3658
|
}
|
|
3650
|
-
if (existsSync14(
|
|
3659
|
+
if (existsSync14(resolve6(dir, "bin")))
|
|
3651
3660
|
passes.push("bin/ present (adds executables to Bash tool $PATH)");
|
|
3652
|
-
if (existsSync14(
|
|
3661
|
+
if (existsSync14(resolve6(dir, "settings.json")))
|
|
3653
3662
|
passes.push("settings.json present (plugin defaults for agent/statusline)");
|
|
3654
|
-
if (existsSync14(
|
|
3663
|
+
if (existsSync14(resolve6(dir, "README.md")))
|
|
3655
3664
|
passes.push("README.md present");
|
|
3656
|
-
if (existsSync14(
|
|
3665
|
+
if (existsSync14(resolve6(dir, ".mcp.json")))
|
|
3657
3666
|
passes.push(".mcp.json present (validated by claude:mcp)");
|
|
3658
|
-
if (existsSync14(
|
|
3667
|
+
if (existsSync14(resolve6(dir, ".lsp.json")))
|
|
3659
3668
|
passes.push(".lsp.json present (validated by claude:lsp when registered)");
|
|
3660
|
-
if (existsSync14(
|
|
3669
|
+
if (existsSync14(resolve6(dir, "hooks/hooks.json")) || existsSync14(resolve6(dir, "hooks.json"))) {
|
|
3661
3670
|
passes.push("hooks config present (validated by claude:hooks)");
|
|
3662
3671
|
}
|
|
3663
|
-
if (existsSync14(
|
|
3672
|
+
if (existsSync14(resolve6(dir, "SKILL.md")) && !existsSync14(skillsDir) && manifest.skills === undefined) {
|
|
3664
3673
|
passes.push('Root SKILL.md detected \u2014 plugin will be treated as a single-skill plugin (prefer frontmatter "name" for stable /command)');
|
|
3665
3674
|
}
|
|
3666
3675
|
return { errors, warnings, passes };
|
|
@@ -3670,7 +3679,7 @@ var init_plugin = __esm(() => {
|
|
|
3670
3679
|
|
|
3671
3680
|
// src/validators/claude/marketplace.ts
|
|
3672
3681
|
import { existsSync as existsSync15, readdirSync as readdirSync7 } from "fs";
|
|
3673
|
-
import { resolve as
|
|
3682
|
+
import { resolve as resolve7, join as join13 } from "path";
|
|
3674
3683
|
var claudeMarketplaceValidator;
|
|
3675
3684
|
var init_marketplace = __esm(() => {
|
|
3676
3685
|
claudeMarketplaceValidator = {
|
|
@@ -3679,7 +3688,7 @@ var init_marketplace = __esm(() => {
|
|
|
3679
3688
|
name: "Claude Plugin Marketplace",
|
|
3680
3689
|
description: "Validates marketplace structure: plugins/ directory with valid plugin subdirectories",
|
|
3681
3690
|
detect(dir) {
|
|
3682
|
-
const pluginsDir =
|
|
3691
|
+
const pluginsDir = resolve7(dir, "plugins");
|
|
3683
3692
|
if (!existsSync15(pluginsDir))
|
|
3684
3693
|
return false;
|
|
3685
3694
|
try {
|
|
@@ -3699,7 +3708,7 @@ var init_marketplace = __esm(() => {
|
|
|
3699
3708
|
const errors = [];
|
|
3700
3709
|
const warnings = [];
|
|
3701
3710
|
const passes = [];
|
|
3702
|
-
const pluginsDir =
|
|
3711
|
+
const pluginsDir = resolve7(dir, "plugins");
|
|
3703
3712
|
if (!existsSync15(pluginsDir)) {
|
|
3704
3713
|
errors.push("Missing plugins/ directory");
|
|
3705
3714
|
return { errors, warnings, passes };
|
|
@@ -3711,12 +3720,12 @@ var init_marketplace = __esm(() => {
|
|
|
3711
3720
|
return { errors, warnings, passes };
|
|
3712
3721
|
}
|
|
3713
3722
|
passes.push(`${pluginEntries.length} plugin(s) found`);
|
|
3714
|
-
if (existsSync15(
|
|
3723
|
+
if (existsSync15(resolve7(dir, "README.md"))) {
|
|
3715
3724
|
passes.push("README.md exists at marketplace root");
|
|
3716
3725
|
} else {
|
|
3717
3726
|
warnings.push("No README.md at marketplace root \u2014 recommended for discoverability");
|
|
3718
3727
|
}
|
|
3719
|
-
if (existsSync15(
|
|
3728
|
+
if (existsSync15(resolve7(dir, "LICENSE"))) {
|
|
3720
3729
|
passes.push("LICENSE exists at marketplace root");
|
|
3721
3730
|
} else {
|
|
3722
3731
|
warnings.push("No LICENSE at marketplace root \u2014 recommended");
|
|
@@ -3742,7 +3751,7 @@ var init_marketplace = __esm(() => {
|
|
|
3742
3751
|
|
|
3743
3752
|
// src/validators/claude/hooks.ts
|
|
3744
3753
|
import { existsSync as existsSync16 } from "fs";
|
|
3745
|
-
import { resolve as
|
|
3754
|
+
import { resolve as resolve8 } from "path";
|
|
3746
3755
|
var KNOWN_EVENTS, claudeHooksValidator;
|
|
3747
3756
|
var init_hooks = __esm(() => {
|
|
3748
3757
|
KNOWN_EVENTS = [
|
|
@@ -3783,13 +3792,13 @@ var init_hooks = __esm(() => {
|
|
|
3783
3792
|
name: "Claude Hooks",
|
|
3784
3793
|
description: "Validates hooks/hooks.json (or root hooks.json): all lifecycle events per Plugins reference, hook group structure (matcher + hooks[]), supported hook types (command, http, mcp_tool, prompt, agent)",
|
|
3785
3794
|
detect(dir) {
|
|
3786
|
-
return existsSync16(
|
|
3795
|
+
return existsSync16(resolve8(dir, "hooks", "hooks.json")) || existsSync16(resolve8(dir, "hooks.json"));
|
|
3787
3796
|
},
|
|
3788
3797
|
async validate(dir, _opts) {
|
|
3789
3798
|
const errors = [];
|
|
3790
3799
|
const warnings = [];
|
|
3791
3800
|
const passes = [];
|
|
3792
|
-
const hooksPath = existsSync16(
|
|
3801
|
+
const hooksPath = existsSync16(resolve8(dir, "hooks", "hooks.json")) ? resolve8(dir, "hooks", "hooks.json") : resolve8(dir, "hooks.json");
|
|
3793
3802
|
let config;
|
|
3794
3803
|
try {
|
|
3795
3804
|
const raw = await Bun.file(hooksPath).text();
|
|
@@ -3856,7 +3865,7 @@ var init_hooks = __esm(() => {
|
|
|
3856
3865
|
|
|
3857
3866
|
// src/validators/claude/mcp.ts
|
|
3858
3867
|
import { existsSync as existsSync17 } from "fs";
|
|
3859
|
-
import { resolve as
|
|
3868
|
+
import { resolve as resolve9 } from "path";
|
|
3860
3869
|
var claudeMcpValidator;
|
|
3861
3870
|
var init_mcp = __esm(() => {
|
|
3862
3871
|
claudeMcpValidator = {
|
|
@@ -3865,13 +3874,13 @@ var init_mcp = __esm(() => {
|
|
|
3865
3874
|
name: "Claude MCP Config",
|
|
3866
3875
|
description: "Validates .mcp.json (or inline via plugin.json mcpServers): server entries (stdio: command+args, or url), env, cwd, ${CLAUDE_PLUGIN_ROOT} etc. substitutions per Plugins reference",
|
|
3867
3876
|
detect(dir) {
|
|
3868
|
-
return existsSync17(
|
|
3877
|
+
return existsSync17(resolve9(dir, ".mcp.json"));
|
|
3869
3878
|
},
|
|
3870
3879
|
async validate(dir, _opts) {
|
|
3871
3880
|
const errors = [];
|
|
3872
3881
|
const warnings = [];
|
|
3873
3882
|
const passes = [];
|
|
3874
|
-
const mcpPath =
|
|
3883
|
+
const mcpPath = resolve9(dir, ".mcp.json");
|
|
3875
3884
|
let config;
|
|
3876
3885
|
try {
|
|
3877
3886
|
const raw = await Bun.file(mcpPath).text();
|
|
@@ -3926,7 +3935,7 @@ var init_mcp = __esm(() => {
|
|
|
3926
3935
|
|
|
3927
3936
|
// src/validators/claude/subagent.ts
|
|
3928
3937
|
import { existsSync as existsSync18, readdirSync as readdirSync8 } from "fs";
|
|
3929
|
-
import { resolve as
|
|
3938
|
+
import { resolve as resolve10, join as join14 } from "path";
|
|
3930
3939
|
var claudeSubagentValidator;
|
|
3931
3940
|
var init_subagent = __esm(() => {
|
|
3932
3941
|
init_frontmatter();
|
|
@@ -3936,7 +3945,7 @@ var init_subagent = __esm(() => {
|
|
|
3936
3945
|
name: "Claude Subagents",
|
|
3937
3946
|
description: "Validates agents/*.md (plugin subagents): frontmatter per spec (name, description, model, effort, maxTurns, tools, disallowedTools, skills, memory, background, isolation=worktree), body; warns on disallowed fields (hooks, mcpServers, permissionMode) for security",
|
|
3938
3947
|
detect(dir) {
|
|
3939
|
-
const agentsDir =
|
|
3948
|
+
const agentsDir = resolve10(dir, "agents");
|
|
3940
3949
|
if (!existsSync18(agentsDir))
|
|
3941
3950
|
return false;
|
|
3942
3951
|
try {
|
|
@@ -3949,7 +3958,7 @@ var init_subagent = __esm(() => {
|
|
|
3949
3958
|
const errors = [];
|
|
3950
3959
|
const warnings = [];
|
|
3951
3960
|
const passes = [];
|
|
3952
|
-
const agentsDir =
|
|
3961
|
+
const agentsDir = resolve10(dir, "agents");
|
|
3953
3962
|
const mdFiles = readdirSync8(agentsDir).filter((f) => f.endsWith(".md"));
|
|
3954
3963
|
if (mdFiles.length === 0) {
|
|
3955
3964
|
errors.push("agents/ directory has no .md files");
|
|
@@ -4018,7 +4027,7 @@ var init_subagent = __esm(() => {
|
|
|
4018
4027
|
|
|
4019
4028
|
// src/validators/claude/command.ts
|
|
4020
4029
|
import { existsSync as existsSync19, readdirSync as readdirSync9 } from "fs";
|
|
4021
|
-
import { resolve as
|
|
4030
|
+
import { resolve as resolve11, join as join15 } from "path";
|
|
4022
4031
|
var claudeCommandValidator;
|
|
4023
4032
|
var init_command = __esm(() => {
|
|
4024
4033
|
init_frontmatter();
|
|
@@ -4028,7 +4037,7 @@ var init_command = __esm(() => {
|
|
|
4028
4037
|
name: "Claude Commands",
|
|
4029
4038
|
description: "Validates commands/ (or legacy .claude/commands/) .md files: frontmatter (including rich skill fields), description, body",
|
|
4030
4039
|
detect(dir) {
|
|
4031
|
-
const commandsDir =
|
|
4040
|
+
const commandsDir = resolve11(dir, "commands");
|
|
4032
4041
|
if (!existsSync19(commandsDir))
|
|
4033
4042
|
return false;
|
|
4034
4043
|
try {
|
|
@@ -4041,7 +4050,7 @@ var init_command = __esm(() => {
|
|
|
4041
4050
|
const errors = [];
|
|
4042
4051
|
const warnings = [];
|
|
4043
4052
|
const passes = [];
|
|
4044
|
-
const commandsDir =
|
|
4053
|
+
const commandsDir = resolve11(dir, "commands");
|
|
4045
4054
|
const mdFiles = readdirSync9(commandsDir).filter((f) => f.endsWith(".md"));
|
|
4046
4055
|
if (mdFiles.length === 0) {
|
|
4047
4056
|
errors.push("commands/ directory has no .md files");
|
|
@@ -4079,7 +4088,7 @@ var init_command = __esm(() => {
|
|
|
4079
4088
|
|
|
4080
4089
|
// src/validators/claude/memory.ts
|
|
4081
4090
|
import { existsSync as existsSync20 } from "fs";
|
|
4082
|
-
import { resolve as
|
|
4091
|
+
import { resolve as resolve12 } from "path";
|
|
4083
4092
|
var claudeMemoryValidator;
|
|
4084
4093
|
var init_memory = __esm(() => {
|
|
4085
4094
|
claudeMemoryValidator = {
|
|
@@ -4088,13 +4097,13 @@ var init_memory = __esm(() => {
|
|
|
4088
4097
|
name: "Claude CLAUDE.md",
|
|
4089
4098
|
description: "Validates CLAUDE.md: non-empty, length recommendations, @path imports",
|
|
4090
4099
|
detect(dir) {
|
|
4091
|
-
return existsSync20(
|
|
4100
|
+
return existsSync20(resolve12(dir, "CLAUDE.md"));
|
|
4092
4101
|
},
|
|
4093
4102
|
async validate(dir, _opts) {
|
|
4094
4103
|
const errors = [];
|
|
4095
4104
|
const warnings = [];
|
|
4096
4105
|
const passes = [];
|
|
4097
|
-
const filePath =
|
|
4106
|
+
const filePath = resolve12(dir, "CLAUDE.md");
|
|
4098
4107
|
const raw = await Bun.file(filePath).text();
|
|
4099
4108
|
if (!raw.trim()) {
|
|
4100
4109
|
errors.push("CLAUDE.md is empty");
|
|
@@ -4112,7 +4121,7 @@ var init_memory = __esm(() => {
|
|
|
4112
4121
|
let match;
|
|
4113
4122
|
while ((match = importRegex.exec(raw)) !== null) {
|
|
4114
4123
|
const importPath = match[1];
|
|
4115
|
-
const resolvedImport =
|
|
4124
|
+
const resolvedImport = resolve12(dir, importPath);
|
|
4116
4125
|
if (existsSync20(resolvedImport)) {
|
|
4117
4126
|
passes.push(`@import "${importPath}" exists`);
|
|
4118
4127
|
} else {
|
|
@@ -4126,7 +4135,7 @@ var init_memory = __esm(() => {
|
|
|
4126
4135
|
|
|
4127
4136
|
// src/validators/claude/lsp.ts
|
|
4128
4137
|
import { existsSync as existsSync21 } from "fs";
|
|
4129
|
-
import { resolve as
|
|
4138
|
+
import { resolve as resolve13 } from "path";
|
|
4130
4139
|
var claudeLspValidator;
|
|
4131
4140
|
var init_lsp = __esm(() => {
|
|
4132
4141
|
claudeLspValidator = {
|
|
@@ -4135,14 +4144,14 @@ var init_lsp = __esm(() => {
|
|
|
4135
4144
|
name: "Claude LSP Servers",
|
|
4136
4145
|
description: "Validates .lsp.json (or plugin.json lspServers): language server configs with required command + extensionToLanguage; optional transport, env, settings, diagnostics etc. (binaries installed separately)",
|
|
4137
4146
|
detect(dir) {
|
|
4138
|
-
return existsSync21(
|
|
4147
|
+
return existsSync21(resolve13(dir, ".lsp.json")) || existsSync21(resolve13(dir, ".claude-plugin", "plugin.json"));
|
|
4139
4148
|
},
|
|
4140
4149
|
async validate(dir, _opts) {
|
|
4141
4150
|
const errors = [];
|
|
4142
4151
|
const warnings = [];
|
|
4143
4152
|
const passes = [];
|
|
4144
4153
|
let cfg = null;
|
|
4145
|
-
const lspPath =
|
|
4154
|
+
const lspPath = resolve13(dir, ".lsp.json");
|
|
4146
4155
|
if (existsSync21(lspPath)) {
|
|
4147
4156
|
try {
|
|
4148
4157
|
cfg = JSON.parse(await Bun.file(lspPath).text());
|
|
@@ -4152,7 +4161,7 @@ var init_lsp = __esm(() => {
|
|
|
4152
4161
|
return { errors, warnings, passes };
|
|
4153
4162
|
}
|
|
4154
4163
|
} else {
|
|
4155
|
-
const manifestPath =
|
|
4164
|
+
const manifestPath = resolve13(dir, ".claude-plugin", "plugin.json");
|
|
4156
4165
|
if (existsSync21(manifestPath)) {
|
|
4157
4166
|
try {
|
|
4158
4167
|
const m = JSON.parse(await Bun.file(manifestPath).text());
|
|
@@ -4194,7 +4203,7 @@ var init_lsp = __esm(() => {
|
|
|
4194
4203
|
|
|
4195
4204
|
// src/validators/claude/monitors.ts
|
|
4196
4205
|
import { existsSync as existsSync22 } from "fs";
|
|
4197
|
-
import { resolve as
|
|
4206
|
+
import { resolve as resolve14 } from "path";
|
|
4198
4207
|
var claudeMonitorsValidator;
|
|
4199
4208
|
var init_monitors = __esm(() => {
|
|
4200
4209
|
claudeMonitorsValidator = {
|
|
@@ -4203,7 +4212,7 @@ var init_monitors = __esm(() => {
|
|
|
4203
4212
|
name: "Claude Monitors (experimental)",
|
|
4204
4213
|
description: "Validates monitors/monitors.json (or experimental.monitors): array of {name, command, description, when?}; commands support ${CLAUDE_PLUGIN_*} subs. Monitors run only in interactive CLI sessions.",
|
|
4205
4214
|
detect(dir) {
|
|
4206
|
-
return existsSync22(
|
|
4215
|
+
return existsSync22(resolve14(dir, "monitors", "monitors.json")) || existsSync22(resolve14(dir, "monitors.json")) || existsSync22(resolve14(dir, ".claude-plugin", "plugin.json"));
|
|
4207
4216
|
},
|
|
4208
4217
|
async validate(dir, _opts) {
|
|
4209
4218
|
const errors = [];
|
|
@@ -4211,8 +4220,8 @@ var init_monitors = __esm(() => {
|
|
|
4211
4220
|
const passes = [];
|
|
4212
4221
|
let arr = null;
|
|
4213
4222
|
const candidates = [
|
|
4214
|
-
|
|
4215
|
-
|
|
4223
|
+
resolve14(dir, "monitors", "monitors.json"),
|
|
4224
|
+
resolve14(dir, "monitors.json")
|
|
4216
4225
|
];
|
|
4217
4226
|
for (const p of candidates) {
|
|
4218
4227
|
if (existsSync22(p)) {
|
|
@@ -4230,7 +4239,7 @@ var init_monitors = __esm(() => {
|
|
|
4230
4239
|
}
|
|
4231
4240
|
}
|
|
4232
4241
|
if (!arr) {
|
|
4233
|
-
const mp =
|
|
4242
|
+
const mp = resolve14(dir, ".claude-plugin", "plugin.json");
|
|
4234
4243
|
if (existsSync22(mp)) {
|
|
4235
4244
|
try {
|
|
4236
4245
|
const m = JSON.parse(await Bun.file(mp).text());
|
|
@@ -4422,7 +4431,7 @@ __export(exports_validate_top, {
|
|
|
4422
4431
|
default: () => validate_top_default
|
|
4423
4432
|
});
|
|
4424
4433
|
import { existsSync as existsSync24 } from "fs";
|
|
4425
|
-
import { resolve as
|
|
4434
|
+
import { resolve as resolve15 } from "path";
|
|
4426
4435
|
var import_picocolors13, validate_top_default;
|
|
4427
4436
|
var init_validate_top = __esm(() => {
|
|
4428
4437
|
init_dist();
|
|
@@ -4472,7 +4481,7 @@ var init_validate_top = __esm(() => {
|
|
|
4472
4481
|
Cloning ${import_picocolors13.default.dim(args.path)}...`);
|
|
4473
4482
|
try {
|
|
4474
4483
|
const result = await cloneToTemp(remote);
|
|
4475
|
-
fullPath = remote.subpath ?
|
|
4484
|
+
fullPath = remote.subpath ? resolve15(result.dir, remote.subpath) : result.dir;
|
|
4476
4485
|
cleanup = result.cleanup;
|
|
4477
4486
|
} catch (err) {
|
|
4478
4487
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -4485,7 +4494,7 @@ var init_validate_top = __esm(() => {
|
|
|
4485
4494
|
process.exit(1);
|
|
4486
4495
|
}
|
|
4487
4496
|
} else {
|
|
4488
|
-
fullPath =
|
|
4497
|
+
fullPath = resolve15(args.path);
|
|
4489
4498
|
if (!existsSync24(fullPath)) {
|
|
4490
4499
|
ui.fail(`Path not found: ${args.path}
|
|
4491
4500
|
|
|
@@ -4822,62 +4831,121 @@ Project-specific decisions.
|
|
|
4822
4831
|
});
|
|
4823
4832
|
|
|
4824
4833
|
// src/core/update.ts
|
|
4825
|
-
import {
|
|
4826
|
-
import { existsSync as existsSync25 } from "fs";
|
|
4827
|
-
import { resolve as resolve15 } from "path";
|
|
4834
|
+
import { resolve as resolve16 } from "path";
|
|
4828
4835
|
import { homedir as homedir2 } from "os";
|
|
4829
|
-
function
|
|
4836
|
+
function normalizePath(p) {
|
|
4837
|
+
return p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
4838
|
+
}
|
|
4839
|
+
function isInside(child, parent) {
|
|
4840
|
+
const c = normalizePath(child);
|
|
4841
|
+
const p = normalizePath(parent);
|
|
4842
|
+
return c === p || c.startsWith(`${p}/`);
|
|
4843
|
+
}
|
|
4844
|
+
async function realpathOrSelf(ctx, p) {
|
|
4830
4845
|
try {
|
|
4831
|
-
|
|
4832
|
-
return true;
|
|
4846
|
+
return await ctx.realpath(p);
|
|
4833
4847
|
} catch {
|
|
4834
|
-
return
|
|
4848
|
+
return p;
|
|
4835
4849
|
}
|
|
4836
4850
|
}
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
if (execPath.includes("/Cellar/") || execPath.includes("/homebrew/") || execPath.includes("/opt/homebrew/")) {
|
|
4841
|
-
if (isInPath("brew"))
|
|
4842
|
-
return { type: "homebrew" };
|
|
4851
|
+
function markerMatchesCurrentInstall(marker, realEntry) {
|
|
4852
|
+
if (marker.entrypointRealpath && normalizePath(marker.entrypointRealpath) === realEntry) {
|
|
4853
|
+
return true;
|
|
4843
4854
|
}
|
|
4844
|
-
if (
|
|
4845
|
-
return
|
|
4855
|
+
if (marker.packageRoot && isInside(realEntry, normalizePath(marker.packageRoot))) {
|
|
4856
|
+
return true;
|
|
4846
4857
|
}
|
|
4847
|
-
|
|
4848
|
-
|
|
4858
|
+
return false;
|
|
4859
|
+
}
|
|
4860
|
+
async function detectHomebrew(ctx, entry, realEntry) {
|
|
4861
|
+
const prefix = await ctx.run("brew", ["--prefix", "doraval"]);
|
|
4862
|
+
if (!prefix.ok)
|
|
4863
|
+
return null;
|
|
4864
|
+
const brewPrefix = normalizePath(await realpathOrSelf(ctx, prefix.stdout.trim()));
|
|
4865
|
+
if (isInside(realEntry, brewPrefix) || realEntry.includes("/Cellar/doraval/")) {
|
|
4866
|
+
return { type: "homebrew", source: "probe" };
|
|
4849
4867
|
}
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
if (
|
|
4857
|
-
|
|
4858
|
-
return { type: "npm" };
|
|
4859
|
-
if (p.includes(".bun"))
|
|
4860
|
-
return { type: "bun" };
|
|
4868
|
+
return null;
|
|
4869
|
+
}
|
|
4870
|
+
async function detectNpmGlobal(ctx, entry, realEntry) {
|
|
4871
|
+
const root = await ctx.run("npm", ["root", "-g"]);
|
|
4872
|
+
if (root.ok) {
|
|
4873
|
+
const npmRoot = normalizePath(await realpathOrSelf(ctx, root.stdout.trim()));
|
|
4874
|
+
if (isInside(realEntry, `${npmRoot}/@hacksmith/doraval`)) {
|
|
4875
|
+
return { type: "npm", source: "probe" };
|
|
4861
4876
|
}
|
|
4862
4877
|
}
|
|
4878
|
+
if (realEntry.includes("/lib/node_modules/@hacksmith/doraval/")) {
|
|
4879
|
+
return { type: "npm", source: "path" };
|
|
4880
|
+
}
|
|
4863
4881
|
return null;
|
|
4864
4882
|
}
|
|
4865
|
-
async function
|
|
4883
|
+
async function detectBunGlobal(ctx, entry, realEntry) {
|
|
4884
|
+
const bunBin = await ctx.run("bun", ["pm", "bin", "-g"]);
|
|
4885
|
+
if (bunBin.ok) {
|
|
4886
|
+
for (const name of ["doraval", "dora"]) {
|
|
4887
|
+
const shim = normalizePath(`${bunBin.stdout.trim()}/${name}`);
|
|
4888
|
+
if (await ctx.exists(shim)) {
|
|
4889
|
+
const realShim = normalizePath(await realpathOrSelf(ctx, shim));
|
|
4890
|
+
if (realShim === realEntry || shim === entry) {
|
|
4891
|
+
return { type: "bun", source: "probe" };
|
|
4892
|
+
}
|
|
4893
|
+
}
|
|
4894
|
+
}
|
|
4895
|
+
}
|
|
4896
|
+
if (realEntry.includes("/.bun/install/global/node_modules/@hacksmith/doraval/")) {
|
|
4897
|
+
return { type: "bun", source: "path" };
|
|
4898
|
+
}
|
|
4899
|
+
return null;
|
|
4900
|
+
}
|
|
4901
|
+
function detectTransient(entry, realEntry) {
|
|
4902
|
+
if (realEntry.includes("/_npx/") && realEntry.includes("/node_modules/@hacksmith/doraval/")) {
|
|
4903
|
+
return { type: "transient", via: "npx", source: "path" };
|
|
4904
|
+
}
|
|
4905
|
+
if (realEntry.includes("/.bun/install/cache/")) {
|
|
4906
|
+
return { type: "transient", via: "bunx", source: "path" };
|
|
4907
|
+
}
|
|
4908
|
+
return null;
|
|
4909
|
+
}
|
|
4910
|
+
async function detectInstallMethod(ctx, options) {
|
|
4911
|
+
const env = ctx.env || {};
|
|
4912
|
+
if (env.DORAVAL_TEST) {
|
|
4913
|
+
return { type: "npm", source: "probe" };
|
|
4914
|
+
}
|
|
4866
4915
|
if (options?.force) {
|
|
4867
|
-
|
|
4868
|
-
|
|
4916
|
+
const f = options.force;
|
|
4917
|
+
if (["homebrew", "npm", "bun"].includes(f)) {
|
|
4918
|
+
return { type: f, source: "probe" };
|
|
4869
4919
|
}
|
|
4870
|
-
if (
|
|
4871
|
-
return { type: "transient", via:
|
|
4920
|
+
if (f === "npx" || f === "bunx") {
|
|
4921
|
+
return { type: "transient", via: f, source: "path" };
|
|
4872
4922
|
}
|
|
4873
4923
|
}
|
|
4874
|
-
const
|
|
4875
|
-
if (
|
|
4876
|
-
return
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4924
|
+
const rawEntry = ctx.entrypoint ?? ctx.argv?.[1];
|
|
4925
|
+
if (!rawEntry) {
|
|
4926
|
+
return { type: "unknown", reason: "No CLI entrypoint path available" };
|
|
4927
|
+
}
|
|
4928
|
+
const entry = normalizePath(rawEntry);
|
|
4929
|
+
const realEntry = normalizePath(await realpathOrSelf(ctx, rawEntry));
|
|
4930
|
+
const owners = await Promise.all([
|
|
4931
|
+
detectHomebrew(ctx, entry, realEntry),
|
|
4932
|
+
detectNpmGlobal(ctx, entry, realEntry),
|
|
4933
|
+
detectBunGlobal(ctx, entry, realEntry)
|
|
4934
|
+
]);
|
|
4935
|
+
const owned = owners.filter(Boolean);
|
|
4936
|
+
if (owned.length === 1)
|
|
4937
|
+
return owned[0];
|
|
4938
|
+
const transient = detectTransient(entry, realEntry);
|
|
4939
|
+
if (transient)
|
|
4940
|
+
return transient;
|
|
4941
|
+
const marker = await ctx.readMarker();
|
|
4942
|
+
if (marker && markerMatchesCurrentInstall(marker, realEntry)) {
|
|
4943
|
+
return { type: marker.type, source: "marker" };
|
|
4944
|
+
}
|
|
4945
|
+
if (owned.length > 1) {
|
|
4946
|
+
return { type: "unknown", reason: "Multiple package managers appear to own this path" };
|
|
4947
|
+
}
|
|
4948
|
+
return { type: "unknown", reason: "Could not determine install method" };
|
|
4881
4949
|
}
|
|
4882
4950
|
async function fetchLatestVersionInfo() {
|
|
4883
4951
|
const npmRes = await fetch("https://registry.npmjs.org/@hacksmith/doraval/latest");
|
|
@@ -4905,6 +4973,9 @@ async function fetchLatestVersionInfo() {
|
|
|
4905
4973
|
return { version, summary };
|
|
4906
4974
|
}
|
|
4907
4975
|
function buildUpgradeCommand(method) {
|
|
4976
|
+
if (method.type === "transient" || method.type === "unknown") {
|
|
4977
|
+
throw new Error("Cannot build upgrade command for transient or unknown installs");
|
|
4978
|
+
}
|
|
4908
4979
|
switch (method.type) {
|
|
4909
4980
|
case "homebrew":
|
|
4910
4981
|
return ["brew", "upgrade", "doraval"];
|
|
@@ -4912,8 +4983,6 @@ function buildUpgradeCommand(method) {
|
|
|
4912
4983
|
return ["npm", "install", "-g", "@hacksmith/doraval@latest"];
|
|
4913
4984
|
case "bun":
|
|
4914
4985
|
return ["bun", "add", "-g", "@hacksmith/doraval@latest"];
|
|
4915
|
-
default:
|
|
4916
|
-
throw new Error("Cannot build upgrade command for transient installs");
|
|
4917
4986
|
}
|
|
4918
4987
|
}
|
|
4919
4988
|
function shouldUpdate(current, latest) {
|
|
@@ -4929,27 +4998,26 @@ function shouldUpdate(current, latest) {
|
|
|
4929
4998
|
}
|
|
4930
4999
|
return false;
|
|
4931
5000
|
}
|
|
4932
|
-
async function
|
|
5001
|
+
async function readMarker() {
|
|
4933
5002
|
try {
|
|
4934
5003
|
const { readFile } = await import("fs/promises");
|
|
4935
5004
|
const data = await readFile(MARKER_PATH, "utf8");
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
}
|
|
4940
|
-
return null;
|
|
5005
|
+
return JSON.parse(data);
|
|
5006
|
+
} catch {
|
|
5007
|
+
return null;
|
|
5008
|
+
}
|
|
4941
5009
|
}
|
|
4942
|
-
async function
|
|
5010
|
+
async function writeMarker(marker) {
|
|
4943
5011
|
try {
|
|
4944
5012
|
const { mkdir, writeFile } = await import("fs/promises");
|
|
4945
5013
|
const { dirname: dirname2 } = await import("path");
|
|
4946
5014
|
await mkdir(dirname2(MARKER_PATH), { recursive: true });
|
|
4947
|
-
await writeFile(MARKER_PATH, JSON.stringify(
|
|
5015
|
+
await writeFile(MARKER_PATH, JSON.stringify(marker, null, 2));
|
|
4948
5016
|
} catch {}
|
|
4949
5017
|
}
|
|
4950
5018
|
var MARKER_PATH;
|
|
4951
5019
|
var init_update2 = __esm(() => {
|
|
4952
|
-
MARKER_PATH =
|
|
5020
|
+
MARKER_PATH = resolve16(homedir2(), ".doraval", "install.json");
|
|
4953
5021
|
});
|
|
4954
5022
|
|
|
4955
5023
|
// src/cli/commands/update.ts
|
|
@@ -4958,13 +5026,16 @@ __export(exports_update2, {
|
|
|
4958
5026
|
default: () => update_default2
|
|
4959
5027
|
});
|
|
4960
5028
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
5029
|
+
import { homedir as homedir3 } from "os";
|
|
5030
|
+
import { fileURLToPath } from "url";
|
|
5031
|
+
import { realpath, access } from "fs/promises";
|
|
4961
5032
|
async function confirmUpdate() {
|
|
4962
5033
|
const { createInterface } = await import("readline");
|
|
4963
5034
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
4964
|
-
return new Promise((
|
|
5035
|
+
return new Promise((resolve17) => {
|
|
4965
5036
|
rl.question("Update now? (y/N) ", (answer) => {
|
|
4966
5037
|
rl.close();
|
|
4967
|
-
|
|
5038
|
+
resolve17(answer.toLowerCase().startsWith("y"));
|
|
4968
5039
|
});
|
|
4969
5040
|
});
|
|
4970
5041
|
}
|
|
@@ -4988,14 +5059,37 @@ var init_update3 = __esm(() => {
|
|
|
4988
5059
|
type: "boolean",
|
|
4989
5060
|
description: "Skip confirmation prompt",
|
|
4990
5061
|
default: false
|
|
5062
|
+
},
|
|
5063
|
+
via: {
|
|
5064
|
+
type: "string",
|
|
5065
|
+
description: 'Force install method detection: "homebrew" | "npm" | "bun"'
|
|
4991
5066
|
}
|
|
4992
5067
|
},
|
|
4993
5068
|
async run({ args }) {
|
|
4994
5069
|
const currentVersion = require_package().version;
|
|
4995
|
-
const
|
|
4996
|
-
const
|
|
4997
|
-
|
|
4998
|
-
|
|
5070
|
+
const entrypoint = fileURLToPath(import.meta.url);
|
|
5071
|
+
const ctx = {
|
|
5072
|
+
entrypoint,
|
|
5073
|
+
argv: process.argv,
|
|
5074
|
+
env: process.env,
|
|
5075
|
+
homeDir: homedir3(),
|
|
5076
|
+
realpath: (p) => realpath(p),
|
|
5077
|
+
exists: async (p) => {
|
|
5078
|
+
try {
|
|
5079
|
+
await access(p);
|
|
5080
|
+
return true;
|
|
5081
|
+
} catch {
|
|
5082
|
+
return false;
|
|
5083
|
+
}
|
|
5084
|
+
},
|
|
5085
|
+
run: async (cmd2, args2) => {
|
|
5086
|
+
const res = spawnSync6(cmd2, args2, { encoding: "utf8" });
|
|
5087
|
+
return { ok: res.status === 0, stdout: res.stdout || "" };
|
|
5088
|
+
},
|
|
5089
|
+
readMarker
|
|
5090
|
+
};
|
|
5091
|
+
const method = await detectInstallMethod(ctx, args.via ? { force: args.via } : undefined);
|
|
5092
|
+
if (method.type === "transient") {
|
|
4999
5093
|
ui.info("It looks like you're using doraval via npx or bunx.");
|
|
5000
5094
|
ui.info("These always fetch the latest version on the next run.");
|
|
5001
5095
|
ui.info("");
|
|
@@ -5005,10 +5099,10 @@ var init_update3 = __esm(() => {
|
|
|
5005
5099
|
ui.info(" bun add -g @hacksmith/doraval");
|
|
5006
5100
|
process.exit(0);
|
|
5007
5101
|
}
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
ui.info("
|
|
5011
|
-
process.exit(
|
|
5102
|
+
if (method.type === "unknown") {
|
|
5103
|
+
ui.fail(`Could not determine how doraval was installed: ${method.reason}`);
|
|
5104
|
+
ui.info("You can force it with --via homebrew|npm|bun");
|
|
5105
|
+
process.exit(2);
|
|
5012
5106
|
}
|
|
5013
5107
|
const latestInfo = await fetchLatestVersionInfo();
|
|
5014
5108
|
if (!shouldUpdate(currentVersion, latestInfo.version)) {
|
|
@@ -5039,14 +5133,23 @@ var init_update3 = __esm(() => {
|
|
|
5039
5133
|
if (result.status === 0) {
|
|
5040
5134
|
ui.success(`Successfully updated to ${latestInfo.version}.`);
|
|
5041
5135
|
ui.info("You may need to restart your shell to pick up the new version.");
|
|
5042
|
-
|
|
5136
|
+
const marker = {
|
|
5137
|
+
type: method.type,
|
|
5138
|
+
packageRoot: undefined,
|
|
5139
|
+
entrypointRealpath: await realpath(entrypoint).catch(() => entrypoint),
|
|
5140
|
+
version: latestInfo.version,
|
|
5141
|
+
writtenAt: new Date().toISOString()
|
|
5142
|
+
};
|
|
5143
|
+
await writeMarker(marker);
|
|
5043
5144
|
} else {
|
|
5044
5145
|
ui.fail("Update failed.");
|
|
5045
5146
|
ui.info("Common fixes:");
|
|
5046
|
-
if (cmd[0] === "brew")
|
|
5147
|
+
if (cmd[0] === "brew") {
|
|
5047
5148
|
ui.info(" \u2022 Try: sudo brew upgrade doraval or ensure you are in the admin group");
|
|
5048
|
-
|
|
5149
|
+
}
|
|
5150
|
+
if (cmd[0] === "npm" || cmd[0] === "bun") {
|
|
5049
5151
|
ui.info(" \u2022 Try running with appropriate permissions or check network.");
|
|
5152
|
+
}
|
|
5050
5153
|
ui.info(`
|
|
5051
5154
|
Raw output above.`);
|
|
5052
5155
|
process.exit(result.status ?? 1);
|