@codluv/versionguard 0.4.0 → 0.6.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.
- package/dist/calver.d.ts +1 -0
- package/dist/calver.d.ts.map +1 -1
- package/dist/changelog.d.ts +52 -0
- package/dist/changelog.d.ts.map +1 -1
- package/dist/chunks/{index-B3R60bYJ.js → index-DeZAx4Le.js} +535 -52
- package/dist/chunks/index-DeZAx4Le.js.map +1 -0
- package/dist/ckm/engine.d.ts +92 -0
- package/dist/ckm/engine.d.ts.map +1 -0
- package/dist/ckm/index.d.ts +32 -0
- package/dist/ckm/index.d.ts.map +1 -0
- package/dist/ckm/types.d.ts +168 -0
- package/dist/ckm/types.d.ts.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +140 -17
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/fix/index.d.ts.map +1 -1
- package/dist/hooks.d.ts +14 -7
- package/dist/hooks.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -33
- package/dist/init-wizard.d.ts +25 -0
- package/dist/init-wizard.d.ts.map +1 -1
- package/dist/project-root.d.ts +74 -0
- package/dist/project-root.d.ts.map +1 -0
- package/dist/scheme-rules.d.ts +32 -0
- package/dist/scheme-rules.d.ts.map +1 -0
- package/dist/semver.d.ts +9 -2
- package/dist/semver.d.ts.map +1 -1
- package/dist/tag/index.d.ts +1 -1
- package/dist/tag/index.d.ts.map +1 -1
- package/dist/types.d.ts +58 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/chunks/index-B3R60bYJ.js.map +0 -1
|
@@ -6,6 +6,17 @@ import { parse as parse$2 } from "smol-toml";
|
|
|
6
6
|
import * as yaml from "js-yaml";
|
|
7
7
|
import { globSync } from "glob";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
+
function validateModifier(modifier, schemeRules) {
|
|
10
|
+
if (!modifier || !schemeRules?.allowedModifiers) return null;
|
|
11
|
+
const baseModifier = modifier.replace(/[\d.]+$/, "") || modifier;
|
|
12
|
+
if (!schemeRules.allowedModifiers.includes(baseModifier)) {
|
|
13
|
+
return {
|
|
14
|
+
message: `Modifier "${modifier}" is not allowed. Allowed: ${schemeRules.allowedModifiers.join(", ")}`,
|
|
15
|
+
severity: "error"
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
9
20
|
const VALID_TOKENS = /* @__PURE__ */ new Set([
|
|
10
21
|
"YYYY",
|
|
11
22
|
"YY",
|
|
@@ -200,13 +211,10 @@ function validate$2(version, calverFormat, preventFutureDates = true, schemeRule
|
|
|
200
211
|
});
|
|
201
212
|
}
|
|
202
213
|
}
|
|
203
|
-
if (parsed.modifier
|
|
204
|
-
const
|
|
205
|
-
if (
|
|
206
|
-
errors.push(
|
|
207
|
-
message: `Modifier "${parsed.modifier}" is not allowed. Allowed: ${schemeRules.allowedModifiers.join(", ")}`,
|
|
208
|
-
severity: "error"
|
|
209
|
-
});
|
|
214
|
+
if (parsed.modifier) {
|
|
215
|
+
const modifierError = validateModifier(parsed.modifier, schemeRules);
|
|
216
|
+
if (modifierError) {
|
|
217
|
+
errors.push(modifierError);
|
|
210
218
|
}
|
|
211
219
|
}
|
|
212
220
|
if (schemeRules?.maxNumericSegments) {
|
|
@@ -381,10 +389,109 @@ function addVersionEntry(changelogPath, version, date = (/* @__PURE__ */ new Dat
|
|
|
381
389
|
const updated = `${content.slice(0, insertIndex)}${block}${content.slice(insertIndex)}`;
|
|
382
390
|
fs.writeFileSync(changelogPath, updated, "utf-8");
|
|
383
391
|
}
|
|
392
|
+
function isChangesetMangled(changelogPath) {
|
|
393
|
+
if (!fs.existsSync(changelogPath)) return false;
|
|
394
|
+
const content = fs.readFileSync(changelogPath, "utf-8");
|
|
395
|
+
return /^## \d+\.\d+/m.test(content) && content.includes("## [Unreleased]");
|
|
396
|
+
}
|
|
397
|
+
const SECTION_MAP = {
|
|
398
|
+
"Major Changes": "Changed",
|
|
399
|
+
"Minor Changes": "Added",
|
|
400
|
+
"Patch Changes": "Fixed"
|
|
401
|
+
};
|
|
402
|
+
function fixChangesetMangling(changelogPath, date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)) {
|
|
403
|
+
if (!fs.existsSync(changelogPath)) return false;
|
|
404
|
+
const content = fs.readFileSync(changelogPath, "utf-8");
|
|
405
|
+
const versionMatch = content.match(/^## (\d+\.\d+\.\d+[^\n]*)\n/m);
|
|
406
|
+
if (!versionMatch || versionMatch.index === void 0) return false;
|
|
407
|
+
const fullHeader = versionMatch[0];
|
|
408
|
+
if (fullHeader.includes("[")) return false;
|
|
409
|
+
const version = versionMatch[1].trim();
|
|
410
|
+
if (content.includes(`## [${version}]`)) return false;
|
|
411
|
+
const startIndex = versionMatch.index;
|
|
412
|
+
const preambleMatch = content.indexOf("All notable changes", startIndex);
|
|
413
|
+
const unreleasedMatch = content.indexOf("## [Unreleased]", startIndex);
|
|
414
|
+
let endIndex;
|
|
415
|
+
if (preambleMatch !== -1 && preambleMatch < unreleasedMatch) {
|
|
416
|
+
endIndex = preambleMatch;
|
|
417
|
+
} else if (unreleasedMatch !== -1) {
|
|
418
|
+
endIndex = unreleasedMatch;
|
|
419
|
+
} else {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
const changesetsBlock = content.slice(startIndex + fullHeader.length, endIndex).trim();
|
|
423
|
+
const transformedSections = transformChangesetsContent(changesetsBlock);
|
|
424
|
+
const newEntry = `## [${version}] - ${date}
|
|
425
|
+
|
|
426
|
+
${transformedSections}
|
|
427
|
+
|
|
428
|
+
`;
|
|
429
|
+
const beforeChangesets = content.slice(0, startIndex);
|
|
430
|
+
const afterChangesets = content.slice(endIndex);
|
|
431
|
+
const unreleasedInAfter = afterChangesets.indexOf("## [Unreleased]");
|
|
432
|
+
if (unreleasedInAfter === -1) {
|
|
433
|
+
const rebuilt2 = `${beforeChangesets}${newEntry}${afterChangesets}`;
|
|
434
|
+
fs.writeFileSync(changelogPath, rebuilt2, "utf-8");
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
const unreleasedLineEnd = afterChangesets.indexOf("\n", unreleasedInAfter);
|
|
438
|
+
const afterUnreleased = unreleasedLineEnd !== -1 ? afterChangesets.slice(0, unreleasedLineEnd + 1) : afterChangesets;
|
|
439
|
+
const rest = unreleasedLineEnd !== -1 ? afterChangesets.slice(unreleasedLineEnd + 1) : "";
|
|
440
|
+
const rebuilt = `${beforeChangesets}${afterUnreleased}
|
|
441
|
+
${newEntry}${rest}`;
|
|
442
|
+
const withLinks = updateCompareLinks(rebuilt, version);
|
|
443
|
+
fs.writeFileSync(changelogPath, withLinks, "utf-8");
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
446
|
+
function transformChangesetsContent(block) {
|
|
447
|
+
const lines = block.split("\n");
|
|
448
|
+
const result = [];
|
|
449
|
+
for (const line of lines) {
|
|
450
|
+
const sectionMatch = line.match(/^### (.+)/);
|
|
451
|
+
if (sectionMatch) {
|
|
452
|
+
const mapped = SECTION_MAP[sectionMatch[1]] ?? sectionMatch[1];
|
|
453
|
+
result.push(`### ${mapped}`);
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
const entryMatch = line.match(
|
|
457
|
+
/^(\s*-\s+)[a-f0-9]{7,}: (?:feat|fix|chore|docs|refactor|perf|test|ci|build|style)(?:\([^)]*\))?: (.+)/
|
|
458
|
+
);
|
|
459
|
+
if (entryMatch) {
|
|
460
|
+
result.push(`${entryMatch[1]}${entryMatch[2]}`);
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
const simpleHashMatch = line.match(/^(\s*-\s+)[a-f0-9]{7,}: (.+)/);
|
|
464
|
+
if (simpleHashMatch) {
|
|
465
|
+
result.push(`${simpleHashMatch[1]}${simpleHashMatch[2]}`);
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
result.push(line);
|
|
469
|
+
}
|
|
470
|
+
return result.join("\n");
|
|
471
|
+
}
|
|
472
|
+
function updateCompareLinks(content, version) {
|
|
473
|
+
const unreleasedLinkRegex = /\[Unreleased\]: (https:\/\/[^\s]+\/compare\/v)([\d.]+)(\.\.\.HEAD)/;
|
|
474
|
+
const match = content.match(unreleasedLinkRegex);
|
|
475
|
+
if (match) {
|
|
476
|
+
const baseUrl = match[1].replace(/v$/, "");
|
|
477
|
+
const previousVersion = match[2];
|
|
478
|
+
const newUnreleasedLink = `[Unreleased]: ${baseUrl}v${version}...HEAD`;
|
|
479
|
+
const newVersionLink = `[${version}]: ${baseUrl}v${previousVersion}...v${version}`;
|
|
480
|
+
let updated = content.replace(unreleasedLinkRegex, newUnreleasedLink);
|
|
481
|
+
if (!updated.includes(`[${version}]:`)) {
|
|
482
|
+
updated = updated.replace(newUnreleasedLink, `${newUnreleasedLink}
|
|
483
|
+
${newVersionLink}`);
|
|
484
|
+
}
|
|
485
|
+
return updated;
|
|
486
|
+
}
|
|
487
|
+
return content;
|
|
488
|
+
}
|
|
384
489
|
function escapeRegExp$1(value) {
|
|
385
490
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
386
491
|
}
|
|
387
492
|
const HOOK_NAMES$1 = ["pre-commit", "pre-push", "post-tag"];
|
|
493
|
+
const VG_BLOCK_START = "# >>> versionguard >>>";
|
|
494
|
+
const VG_BLOCK_END = "# <<< versionguard <<<";
|
|
388
495
|
function installHooks(config, cwd = process.cwd()) {
|
|
389
496
|
const gitDir = findGitDir(cwd);
|
|
390
497
|
if (!gitDir) {
|
|
@@ -395,7 +502,36 @@ function installHooks(config, cwd = process.cwd()) {
|
|
|
395
502
|
for (const hookName of HOOK_NAMES$1) {
|
|
396
503
|
if (config.hooks[hookName]) {
|
|
397
504
|
const hookPath = path.join(hooksDir, hookName);
|
|
398
|
-
|
|
505
|
+
const vgBlock = generateHookBlock(hookName);
|
|
506
|
+
if (fs.existsSync(hookPath)) {
|
|
507
|
+
const existing = fs.readFileSync(hookPath, "utf-8");
|
|
508
|
+
if (existing.includes(VG_BLOCK_START)) {
|
|
509
|
+
const updated = replaceVgBlock(existing, vgBlock);
|
|
510
|
+
fs.writeFileSync(hookPath, updated, { encoding: "utf-8", mode: 493 });
|
|
511
|
+
} else if (isLegacyVgHook(existing)) {
|
|
512
|
+
fs.writeFileSync(hookPath, `#!/bin/sh
|
|
513
|
+
|
|
514
|
+
${vgBlock}
|
|
515
|
+
`, {
|
|
516
|
+
encoding: "utf-8",
|
|
517
|
+
mode: 493
|
|
518
|
+
});
|
|
519
|
+
} else {
|
|
520
|
+
const appended = `${existing.trimEnd()}
|
|
521
|
+
|
|
522
|
+
${vgBlock}
|
|
523
|
+
`;
|
|
524
|
+
fs.writeFileSync(hookPath, appended, { encoding: "utf-8", mode: 493 });
|
|
525
|
+
}
|
|
526
|
+
} else {
|
|
527
|
+
fs.writeFileSync(hookPath, `#!/bin/sh
|
|
528
|
+
|
|
529
|
+
${vgBlock}
|
|
530
|
+
`, {
|
|
531
|
+
encoding: "utf-8",
|
|
532
|
+
mode: 493
|
|
533
|
+
});
|
|
534
|
+
}
|
|
399
535
|
}
|
|
400
536
|
}
|
|
401
537
|
}
|
|
@@ -407,7 +543,21 @@ function uninstallHooks(cwd = process.cwd()) {
|
|
|
407
543
|
const hooksDir = path.join(gitDir, "hooks");
|
|
408
544
|
for (const hookName of HOOK_NAMES$1) {
|
|
409
545
|
const hookPath = path.join(hooksDir, hookName);
|
|
410
|
-
if (fs.existsSync(hookPath)
|
|
546
|
+
if (!fs.existsSync(hookPath)) continue;
|
|
547
|
+
const content = fs.readFileSync(hookPath, "utf-8");
|
|
548
|
+
if (!content.includes("versionguard")) continue;
|
|
549
|
+
if (content.includes(VG_BLOCK_START)) {
|
|
550
|
+
const cleaned = removeVgBlock(content);
|
|
551
|
+
const trimmed = cleaned.trim();
|
|
552
|
+
if (!trimmed || trimmed === "#!/bin/sh") {
|
|
553
|
+
fs.unlinkSync(hookPath);
|
|
554
|
+
} else if (isLegacyVgHook(trimmed)) {
|
|
555
|
+
fs.unlinkSync(hookPath);
|
|
556
|
+
} else {
|
|
557
|
+
fs.writeFileSync(hookPath, `${trimmed}
|
|
558
|
+
`, { encoding: "utf-8", mode: 493 });
|
|
559
|
+
}
|
|
560
|
+
} else if (isLegacyVgHook(content)) {
|
|
411
561
|
fs.unlinkSync(hookPath);
|
|
412
562
|
}
|
|
413
563
|
}
|
|
@@ -436,9 +586,8 @@ function areHooksInstalled(cwd = process.cwd()) {
|
|
|
436
586
|
return fs.existsSync(hookPath) && fs.readFileSync(hookPath, "utf-8").includes("versionguard");
|
|
437
587
|
});
|
|
438
588
|
}
|
|
439
|
-
function
|
|
440
|
-
return
|
|
441
|
-
# versionguard
|
|
589
|
+
function generateHookBlock(hookName) {
|
|
590
|
+
return `${VG_BLOCK_START}
|
|
442
591
|
# VersionGuard ${hookName} hook
|
|
443
592
|
# --no-install prevents accidentally downloading an unscoped package
|
|
444
593
|
# if @codluv/versionguard is not installed locally
|
|
@@ -448,8 +597,36 @@ if [ $status -ne 0 ]; then
|
|
|
448
597
|
echo "VersionGuard validation failed."
|
|
449
598
|
exit $status
|
|
450
599
|
fi
|
|
600
|
+
${VG_BLOCK_END}`;
|
|
601
|
+
}
|
|
602
|
+
function generateHookScript(hookName) {
|
|
603
|
+
return `#!/bin/sh
|
|
604
|
+
|
|
605
|
+
${generateHookBlock(hookName)}
|
|
451
606
|
`;
|
|
452
607
|
}
|
|
608
|
+
function isLegacyVgHook(content) {
|
|
609
|
+
if (!content.includes("versionguard validate")) return false;
|
|
610
|
+
if (content.includes(VG_BLOCK_START)) return false;
|
|
611
|
+
if (content.includes("husky")) return false;
|
|
612
|
+
if (content.includes("lefthook")) return false;
|
|
613
|
+
if (content.includes("pre-commit run")) return false;
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
616
|
+
function replaceVgBlock(content, newBlock) {
|
|
617
|
+
const startIdx = content.indexOf(VG_BLOCK_START);
|
|
618
|
+
const endIdx = content.indexOf(VG_BLOCK_END);
|
|
619
|
+
if (startIdx === -1 || endIdx === -1) return content;
|
|
620
|
+
return content.slice(0, startIdx) + newBlock + content.slice(endIdx + VG_BLOCK_END.length);
|
|
621
|
+
}
|
|
622
|
+
function removeVgBlock(content) {
|
|
623
|
+
const startIdx = content.indexOf(VG_BLOCK_START);
|
|
624
|
+
const endIdx = content.indexOf(VG_BLOCK_END);
|
|
625
|
+
if (startIdx === -1 || endIdx === -1) return content;
|
|
626
|
+
const before = content.slice(0, startIdx).replace(/\n\n$/, "\n");
|
|
627
|
+
const after = content.slice(endIdx + VG_BLOCK_END.length).replace(/^\n\n/, "\n");
|
|
628
|
+
return before + after;
|
|
629
|
+
}
|
|
453
630
|
class GitTagSource {
|
|
454
631
|
/** Human-readable provider name. */
|
|
455
632
|
name = "git-tag";
|
|
@@ -1167,17 +1344,42 @@ function getStructuralErrors(version) {
|
|
|
1167
1344
|
});
|
|
1168
1345
|
return errors;
|
|
1169
1346
|
}
|
|
1170
|
-
function validate$1(version) {
|
|
1171
|
-
|
|
1347
|
+
function validate$1(version, semverConfig, schemeRules) {
|
|
1348
|
+
let input = version;
|
|
1349
|
+
if (input.startsWith("v") || input.startsWith("V")) {
|
|
1350
|
+
if (semverConfig?.allowVPrefix) {
|
|
1351
|
+
input = input.slice(1);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
const parsed = parse(input);
|
|
1172
1355
|
if (!parsed) {
|
|
1173
1356
|
return {
|
|
1174
1357
|
valid: false,
|
|
1175
1358
|
errors: getStructuralErrors(version)
|
|
1176
1359
|
};
|
|
1177
1360
|
}
|
|
1361
|
+
const errors = [];
|
|
1362
|
+
if (semverConfig && !semverConfig.allowBuildMetadata && parsed.build.length > 0) {
|
|
1363
|
+
errors.push({
|
|
1364
|
+
message: `Build metadata is not allowed: "${parsed.build.join(".")}"`,
|
|
1365
|
+
severity: "error"
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
if (semverConfig?.requirePrerelease && parsed.prerelease.length === 0) {
|
|
1369
|
+
errors.push({
|
|
1370
|
+
message: "A prerelease label is required (e.g., 1.2.3-alpha.1)",
|
|
1371
|
+
severity: "error"
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
if (parsed.prerelease.length > 0) {
|
|
1375
|
+
const modifierError = validateModifier(parsed.prerelease[0], schemeRules);
|
|
1376
|
+
if (modifierError) {
|
|
1377
|
+
errors.push(modifierError);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1178
1380
|
return {
|
|
1179
|
-
valid:
|
|
1180
|
-
errors
|
|
1381
|
+
valid: errors.filter((e) => e.severity === "error").length === 0,
|
|
1382
|
+
errors,
|
|
1181
1383
|
version: { type: "semver", version: parsed }
|
|
1182
1384
|
};
|
|
1183
1385
|
}
|
|
@@ -1358,12 +1560,177 @@ function checkHardcodedVersions(expectedVersion, config, ignorePatterns, cwd = p
|
|
|
1358
1560
|
}
|
|
1359
1561
|
return mismatches;
|
|
1360
1562
|
}
|
|
1563
|
+
const DEFAULT_SEMVER_CONFIG = {
|
|
1564
|
+
allowVPrefix: false,
|
|
1565
|
+
allowBuildMetadata: true,
|
|
1566
|
+
requirePrerelease: false
|
|
1567
|
+
};
|
|
1568
|
+
function getSemVerConfig(config) {
|
|
1569
|
+
return { ...DEFAULT_SEMVER_CONFIG, ...config.versioning.semver };
|
|
1570
|
+
}
|
|
1361
1571
|
function getCalVerConfig(config) {
|
|
1362
1572
|
if (!config.versioning.calver) {
|
|
1363
1573
|
throw new Error('CalVer configuration is required when versioning.type is "calver"');
|
|
1364
1574
|
}
|
|
1365
1575
|
return config.versioning.calver;
|
|
1366
1576
|
}
|
|
1577
|
+
function deriveTopicSlug(conceptName) {
|
|
1578
|
+
return conceptName.replace(/Config$/, "").replace(/Result$/, "").replace(/Options$/, "").toLowerCase();
|
|
1579
|
+
}
|
|
1580
|
+
function isTopicConcept(name) {
|
|
1581
|
+
return name.endsWith("Config") && name !== "VersionGuardConfig";
|
|
1582
|
+
}
|
|
1583
|
+
function operationMatchesTopic(op, topicSlug, conceptNames) {
|
|
1584
|
+
const haystack = `${op.name} ${op.what}`.toLowerCase();
|
|
1585
|
+
if (haystack.includes(topicSlug)) return true;
|
|
1586
|
+
return conceptNames.some((n) => haystack.includes(n.toLowerCase()));
|
|
1587
|
+
}
|
|
1588
|
+
function createCkmEngine(manifest) {
|
|
1589
|
+
const topics = deriveTopics(manifest);
|
|
1590
|
+
return {
|
|
1591
|
+
topics,
|
|
1592
|
+
getTopicIndex: (toolName = "tool") => formatTopicIndex(topics, toolName),
|
|
1593
|
+
getTopicContent: (name) => formatTopicContent(topics, name),
|
|
1594
|
+
getTopicJson: (name) => buildTopicJson(topics, manifest, name),
|
|
1595
|
+
getManifest: () => manifest
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
function deriveTopics(manifest) {
|
|
1599
|
+
const topics = [];
|
|
1600
|
+
for (const concept of manifest.concepts) {
|
|
1601
|
+
if (!isTopicConcept(concept.name)) continue;
|
|
1602
|
+
const slug = deriveTopicSlug(concept.name);
|
|
1603
|
+
const conceptNames = [concept.name];
|
|
1604
|
+
const relatedConcepts = manifest.concepts.filter(
|
|
1605
|
+
(c) => c.name !== concept.name && (c.name.toLowerCase().includes(slug) || slug.includes(deriveTopicSlug(c.name)))
|
|
1606
|
+
);
|
|
1607
|
+
conceptNames.push(...relatedConcepts.map((c) => c.name));
|
|
1608
|
+
const operations = manifest.operations.filter(
|
|
1609
|
+
(op) => operationMatchesTopic(op, slug, conceptNames)
|
|
1610
|
+
);
|
|
1611
|
+
const configSchema = manifest.configSchema.filter(
|
|
1612
|
+
(c) => conceptNames.some((n) => c.key?.startsWith(n))
|
|
1613
|
+
);
|
|
1614
|
+
const constraints = manifest.constraints.filter(
|
|
1615
|
+
(c) => conceptNames.some((n) => c.enforcedBy?.includes(n)) || operations.some((o) => c.enforcedBy?.includes(o.name))
|
|
1616
|
+
);
|
|
1617
|
+
topics.push({
|
|
1618
|
+
name: slug,
|
|
1619
|
+
summary: concept.what,
|
|
1620
|
+
concepts: [concept, ...relatedConcepts],
|
|
1621
|
+
operations,
|
|
1622
|
+
configSchema,
|
|
1623
|
+
constraints
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
return topics;
|
|
1627
|
+
}
|
|
1628
|
+
function formatTopicIndex(topics, toolName) {
|
|
1629
|
+
const lines = [
|
|
1630
|
+
`${toolName} CKM — Codebase Knowledge Manifest`,
|
|
1631
|
+
"",
|
|
1632
|
+
`Usage: ${toolName} ckm [topic] [--json] [--llm]`,
|
|
1633
|
+
"",
|
|
1634
|
+
"Topics:"
|
|
1635
|
+
];
|
|
1636
|
+
const maxName = Math.max(...topics.map((t) => t.name.length));
|
|
1637
|
+
for (const topic of topics) {
|
|
1638
|
+
lines.push(` ${topic.name.padEnd(maxName + 2)}${topic.summary}`);
|
|
1639
|
+
}
|
|
1640
|
+
lines.push("");
|
|
1641
|
+
lines.push("Flags:");
|
|
1642
|
+
lines.push(" --json Machine-readable CKM output (concepts, operations, config schema)");
|
|
1643
|
+
lines.push(" --llm Full API context for LLM agents (forge-ts llms.txt)");
|
|
1644
|
+
return lines.join("\n");
|
|
1645
|
+
}
|
|
1646
|
+
function formatTopicContent(topics, topicName) {
|
|
1647
|
+
const topic = topics.find((t) => t.name === topicName);
|
|
1648
|
+
if (!topic) return null;
|
|
1649
|
+
const lines = [`# ${topic.summary}`, ""];
|
|
1650
|
+
if (topic.concepts.length > 0) {
|
|
1651
|
+
lines.push("## Concepts", "");
|
|
1652
|
+
for (const c of topic.concepts) {
|
|
1653
|
+
lines.push(` ${c.name} — ${c.what}`);
|
|
1654
|
+
if (c.properties) {
|
|
1655
|
+
for (const p of c.properties) {
|
|
1656
|
+
const def = findDefault(topic.configSchema, c.name, p.name);
|
|
1657
|
+
lines.push(` ${p.name}: ${p.type}${def ? ` = ${def}` : ""}`);
|
|
1658
|
+
if (p.description) {
|
|
1659
|
+
lines.push(` ${p.description}`);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
lines.push("");
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
if (topic.operations.length > 0) {
|
|
1667
|
+
lines.push("## Operations", "");
|
|
1668
|
+
for (const o of topic.operations) {
|
|
1669
|
+
lines.push(` ${o.name}() — ${o.what}`);
|
|
1670
|
+
if (o.inputs) {
|
|
1671
|
+
for (const i of o.inputs) {
|
|
1672
|
+
lines.push(` @param ${i.name}: ${i.description}`);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
lines.push("");
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
if (topic.configSchema.length > 0) {
|
|
1679
|
+
lines.push("## Config Fields", "");
|
|
1680
|
+
for (const c of topic.configSchema) {
|
|
1681
|
+
lines.push(` ${c.key}: ${c.type}${c.default ? ` = ${c.default}` : ""}`);
|
|
1682
|
+
if (c.description) {
|
|
1683
|
+
lines.push(` ${c.description}`);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
lines.push("");
|
|
1687
|
+
}
|
|
1688
|
+
if (topic.constraints.length > 0) {
|
|
1689
|
+
lines.push("## Constraints", "");
|
|
1690
|
+
for (const c of topic.constraints) {
|
|
1691
|
+
lines.push(` [${c.id}] ${c.rule}`);
|
|
1692
|
+
lines.push(` Enforced by: ${c.enforcedBy}`);
|
|
1693
|
+
}
|
|
1694
|
+
lines.push("");
|
|
1695
|
+
}
|
|
1696
|
+
return lines.join("\n");
|
|
1697
|
+
}
|
|
1698
|
+
function findDefault(schema, conceptName, propName) {
|
|
1699
|
+
return schema.find((c) => c.key === `${conceptName}.${propName}`)?.default;
|
|
1700
|
+
}
|
|
1701
|
+
function buildTopicJson(topics, manifest, topicName) {
|
|
1702
|
+
if (!topicName) {
|
|
1703
|
+
return {
|
|
1704
|
+
topics: topics.map((t) => ({
|
|
1705
|
+
name: t.name,
|
|
1706
|
+
summary: t.summary,
|
|
1707
|
+
concepts: t.concepts.length,
|
|
1708
|
+
operations: t.operations.length,
|
|
1709
|
+
configFields: t.configSchema.length,
|
|
1710
|
+
constraints: t.constraints.length
|
|
1711
|
+
})),
|
|
1712
|
+
ckm: {
|
|
1713
|
+
concepts: manifest.concepts.length,
|
|
1714
|
+
operations: manifest.operations.length,
|
|
1715
|
+
constraints: manifest.constraints.length,
|
|
1716
|
+
workflows: manifest.workflows.length,
|
|
1717
|
+
configSchema: manifest.configSchema.length
|
|
1718
|
+
}
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1721
|
+
const topic = topics.find((t) => t.name === topicName);
|
|
1722
|
+
if (!topic) {
|
|
1723
|
+
return { error: `Unknown topic: ${topicName}`, topics: topics.map((t) => t.name) };
|
|
1724
|
+
}
|
|
1725
|
+
return {
|
|
1726
|
+
topic: topic.name,
|
|
1727
|
+
summary: topic.summary,
|
|
1728
|
+
concepts: topic.concepts,
|
|
1729
|
+
operations: topic.operations,
|
|
1730
|
+
configSchema: topic.configSchema,
|
|
1731
|
+
constraints: topic.constraints
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1367
1734
|
const CONFIG_FILE_NAMES = [
|
|
1368
1735
|
".versionguard.yml",
|
|
1369
1736
|
".versionguard.yaml",
|
|
@@ -1377,6 +1744,11 @@ const DEFAULT_CONFIG = {
|
|
|
1377
1744
|
maxNumericSegments: 3,
|
|
1378
1745
|
allowedModifiers: ["dev", "alpha", "beta", "rc"]
|
|
1379
1746
|
},
|
|
1747
|
+
semver: {
|
|
1748
|
+
allowVPrefix: false,
|
|
1749
|
+
allowBuildMetadata: true,
|
|
1750
|
+
requirePrerelease: false
|
|
1751
|
+
},
|
|
1380
1752
|
calver: {
|
|
1381
1753
|
format: "YYYY.MM.PATCH",
|
|
1382
1754
|
preventFutureDates: true,
|
|
@@ -1456,8 +1828,16 @@ function initConfig(cwd = process.cwd()) {
|
|
|
1456
1828
|
}
|
|
1457
1829
|
function generateDefaultConfig() {
|
|
1458
1830
|
return `# VersionGuard Configuration
|
|
1831
|
+
# Change "type" to switch between semver and calver — both blocks are always present.
|
|
1459
1832
|
versioning:
|
|
1460
1833
|
type: semver
|
|
1834
|
+
semver:
|
|
1835
|
+
allowVPrefix: false
|
|
1836
|
+
allowBuildMetadata: true
|
|
1837
|
+
requirePrerelease: false
|
|
1838
|
+
calver:
|
|
1839
|
+
format: "YYYY.MM.PATCH"
|
|
1840
|
+
preventFutureDates: true
|
|
1461
1841
|
|
|
1462
1842
|
sync:
|
|
1463
1843
|
files:
|
|
@@ -1870,6 +2250,17 @@ function fixAll(config, targetVersion, cwd = process.cwd()) {
|
|
|
1870
2250
|
const syncResults = fixSyncIssues(config, cwd);
|
|
1871
2251
|
results.push(...syncResults);
|
|
1872
2252
|
if (config.changelog.enabled) {
|
|
2253
|
+
const changelogPath = path.join(cwd, config.changelog.file);
|
|
2254
|
+
if (isChangesetMangled(changelogPath)) {
|
|
2255
|
+
const fixed = fixChangesetMangling(changelogPath);
|
|
2256
|
+
if (fixed) {
|
|
2257
|
+
results.push({
|
|
2258
|
+
fixed: true,
|
|
2259
|
+
message: `Restructured ${config.changelog.file} from Changesets format to Keep a Changelog`,
|
|
2260
|
+
file: changelogPath
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
1873
2264
|
const changelogResult = fixChangelog(version, config, cwd);
|
|
1874
2265
|
if (changelogResult.fixed) {
|
|
1875
2266
|
results.push(changelogResult);
|
|
@@ -2028,6 +2419,92 @@ function runGuardChecks(config, cwd) {
|
|
|
2028
2419
|
warnings
|
|
2029
2420
|
};
|
|
2030
2421
|
}
|
|
2422
|
+
const PROJECT_MARKERS = [
|
|
2423
|
+
".versionguard.yml",
|
|
2424
|
+
".versionguard.yaml",
|
|
2425
|
+
"versionguard.yml",
|
|
2426
|
+
"versionguard.yaml",
|
|
2427
|
+
".git",
|
|
2428
|
+
"package.json",
|
|
2429
|
+
"Cargo.toml",
|
|
2430
|
+
"pyproject.toml",
|
|
2431
|
+
"pubspec.yaml",
|
|
2432
|
+
"composer.json",
|
|
2433
|
+
"pom.xml",
|
|
2434
|
+
"go.mod",
|
|
2435
|
+
"mix.exs",
|
|
2436
|
+
"Gemfile",
|
|
2437
|
+
".csproj"
|
|
2438
|
+
];
|
|
2439
|
+
function findProjectRoot(startDir) {
|
|
2440
|
+
let current = path.resolve(startDir);
|
|
2441
|
+
while (true) {
|
|
2442
|
+
for (const marker of PROJECT_MARKERS) {
|
|
2443
|
+
if (marker.startsWith(".") && marker !== ".git" && !marker.startsWith(".version")) {
|
|
2444
|
+
try {
|
|
2445
|
+
const files = fs.readdirSync(current);
|
|
2446
|
+
if (files.some((f) => f.endsWith(marker))) {
|
|
2447
|
+
return buildResult(current, marker);
|
|
2448
|
+
}
|
|
2449
|
+
} catch {
|
|
2450
|
+
}
|
|
2451
|
+
} else if (fs.existsSync(path.join(current, marker))) {
|
|
2452
|
+
return buildResult(current, marker);
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
const parent = path.dirname(current);
|
|
2456
|
+
if (parent === current) {
|
|
2457
|
+
return {
|
|
2458
|
+
found: false,
|
|
2459
|
+
root: path.resolve(startDir),
|
|
2460
|
+
hasConfig: false,
|
|
2461
|
+
hasGit: false,
|
|
2462
|
+
hasManifest: false
|
|
2463
|
+
};
|
|
2464
|
+
}
|
|
2465
|
+
current = parent;
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
function buildResult(root, marker) {
|
|
2469
|
+
const configNames = [
|
|
2470
|
+
".versionguard.yml",
|
|
2471
|
+
".versionguard.yaml",
|
|
2472
|
+
"versionguard.yml",
|
|
2473
|
+
"versionguard.yaml"
|
|
2474
|
+
];
|
|
2475
|
+
return {
|
|
2476
|
+
found: true,
|
|
2477
|
+
root,
|
|
2478
|
+
marker,
|
|
2479
|
+
hasConfig: configNames.some((c) => fs.existsSync(path.join(root, c))),
|
|
2480
|
+
hasGit: fs.existsSync(path.join(root, ".git")),
|
|
2481
|
+
hasManifest: [
|
|
2482
|
+
"package.json",
|
|
2483
|
+
"Cargo.toml",
|
|
2484
|
+
"pyproject.toml",
|
|
2485
|
+
"pubspec.yaml",
|
|
2486
|
+
"composer.json",
|
|
2487
|
+
"pom.xml",
|
|
2488
|
+
"VERSION"
|
|
2489
|
+
].some((m) => fs.existsSync(path.join(root, m)))
|
|
2490
|
+
};
|
|
2491
|
+
}
|
|
2492
|
+
function formatNotProjectError(cwd, command) {
|
|
2493
|
+
const dir = path.basename(cwd) || cwd;
|
|
2494
|
+
const lines = [
|
|
2495
|
+
`Not a VersionGuard project: ${dir}`,
|
|
2496
|
+
"",
|
|
2497
|
+
"No .versionguard.yml, .git directory, or manifest file found.",
|
|
2498
|
+
"",
|
|
2499
|
+
"To get started:",
|
|
2500
|
+
" versionguard init Set up a new project interactively",
|
|
2501
|
+
" versionguard init --yes Set up with defaults",
|
|
2502
|
+
"",
|
|
2503
|
+
"Or run from a project root directory:",
|
|
2504
|
+
` cd /path/to/project && versionguard ${command}`
|
|
2505
|
+
];
|
|
2506
|
+
return lines.join("\n");
|
|
2507
|
+
}
|
|
2031
2508
|
function runGit(cwd, args, encoding) {
|
|
2032
2509
|
return childProcess.execFileSync("git", args, {
|
|
2033
2510
|
cwd,
|
|
@@ -2192,7 +2669,7 @@ function getTagPreflightError(config, cwd, expectedVersion, allowAutoFix = false
|
|
|
2192
2669
|
return "Working tree must be clean before creating or validating release tags";
|
|
2193
2670
|
}
|
|
2194
2671
|
const version = expectedVersion ?? getPackageVersion(cwd, config.manifest);
|
|
2195
|
-
const versionResult = config.versioning.type === "semver" ? validate$1(version) : validate$2(
|
|
2672
|
+
const versionResult = config.versioning.type === "semver" ? validate$1(version, getSemVerConfig(config), config.versioning.schemeRules) : validate$2(
|
|
2196
2673
|
version,
|
|
2197
2674
|
config.versioning.calver?.format ?? "YYYY.MM.PATCH",
|
|
2198
2675
|
config.versioning.calver?.preventFutureDates ?? true,
|
|
@@ -2277,7 +2754,7 @@ function suggestTagMessage(version, cwd = process.cwd()) {
|
|
|
2277
2754
|
}
|
|
2278
2755
|
function validateVersion(version, config) {
|
|
2279
2756
|
if (config.versioning.type === "semver") {
|
|
2280
|
-
return validate$1(version);
|
|
2757
|
+
return validate$1(version, getSemVerConfig(config), config.versioning.schemeRules);
|
|
2281
2758
|
}
|
|
2282
2759
|
const calverConfig = getCalVerConfig(config);
|
|
2283
2760
|
return validate$2(
|
|
@@ -2405,53 +2882,59 @@ function isWorktreeClean(cwd) {
|
|
|
2405
2882
|
}
|
|
2406
2883
|
}
|
|
2407
2884
|
export {
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2885
|
+
validateVersion as $,
|
|
2886
|
+
checkHardcodedVersions as A,
|
|
2887
|
+
checkHookIntegrity as B,
|
|
2888
|
+
checkHooksPathOverride as C,
|
|
2889
|
+
checkHuskyBypass as D,
|
|
2890
|
+
detectManifests as E,
|
|
2891
|
+
fixChangelog as F,
|
|
2414
2892
|
GitTagSource as G,
|
|
2415
|
-
|
|
2416
|
-
|
|
2893
|
+
fixPackageVersion as H,
|
|
2894
|
+
getAllTags as I,
|
|
2417
2895
|
JsonVersionSource as J,
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2896
|
+
getCalVerConfig as K,
|
|
2897
|
+
getLatestTag as L,
|
|
2898
|
+
getSemVerConfig as M,
|
|
2899
|
+
getTagFeedback as N,
|
|
2900
|
+
getVersionSource as O,
|
|
2901
|
+
initConfig as P,
|
|
2902
|
+
resolveVersionSource as Q,
|
|
2425
2903
|
RegexVersionSource as R,
|
|
2426
|
-
|
|
2904
|
+
semver as S,
|
|
2427
2905
|
TomlVersionSource as T,
|
|
2906
|
+
suggestTagMessage as U,
|
|
2428
2907
|
VersionFileSource as V,
|
|
2908
|
+
sync as W,
|
|
2909
|
+
syncVersion as X,
|
|
2429
2910
|
YamlVersionSource as Y,
|
|
2911
|
+
validateChangelog as Z,
|
|
2912
|
+
validateTagForPush as _,
|
|
2430
2913
|
installHooks as a,
|
|
2431
2914
|
getPackageVersion as b,
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2915
|
+
createCkmEngine as c,
|
|
2916
|
+
getVersionFeedback as d,
|
|
2917
|
+
getSyncFeedback as e,
|
|
2918
|
+
getChangelogFeedback as f,
|
|
2436
2919
|
getConfig as g,
|
|
2437
2920
|
handlePostTag as h,
|
|
2438
2921
|
isValidCalVerFormat as i,
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2922
|
+
doctor as j,
|
|
2923
|
+
fixAll as k,
|
|
2924
|
+
isChangesetMangled as l,
|
|
2925
|
+
fixChangesetMangling as m,
|
|
2926
|
+
fixSyncIssues as n,
|
|
2927
|
+
setPackageVersion as o,
|
|
2928
|
+
createTag as p,
|
|
2929
|
+
areHooksInstalled as q,
|
|
2447
2930
|
runGuardChecks as r,
|
|
2448
2931
|
suggestNextVersion as s,
|
|
2449
|
-
|
|
2932
|
+
findProjectRoot as t,
|
|
2450
2933
|
uninstallHooks as u,
|
|
2451
2934
|
validate as v,
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2935
|
+
formatNotProjectError as w,
|
|
2936
|
+
calver as x,
|
|
2937
|
+
canBump as y,
|
|
2938
|
+
checkEnforceHooksPolicy as z
|
|
2456
2939
|
};
|
|
2457
|
-
//# sourceMappingURL=index-
|
|
2940
|
+
//# sourceMappingURL=index-DeZAx4Le.js.map
|