@grekt/cli 6.39.0 → 6.40.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.
Files changed (2) hide show
  1. package/dist/index.js +353 -12
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -91853,6 +91853,9 @@ var INDEX_FILE = join8(GREKT_DIR, "index");
91853
91853
 
91854
91854
  // src/shared/filesystem/filesystem.ts
91855
91855
  import { dirname as dirname3, join as join9 } from "path";
91856
+ function normalizePath(path8) {
91857
+ return path8.replace(/\\/g, "/");
91858
+ }
91856
91859
  function ensureDir(filepath) {
91857
91860
  const dir = dirname3(filepath);
91858
91861
  if (!fs.exists(dir)) {
@@ -103332,6 +103335,343 @@ function findArtifacts(basePath) {
103332
103335
  return artifacts;
103333
103336
  }
103334
103337
 
103338
+ // src/commands/changelog/git.ts
103339
+ function exec(args) {
103340
+ return shell.execFile("git", args).trim();
103341
+ }
103342
+ function execOrNull(args) {
103343
+ try {
103344
+ const result = exec(args);
103345
+ return result || null;
103346
+ } catch {
103347
+ return null;
103348
+ }
103349
+ }
103350
+ function splitLines(output) {
103351
+ if (!output)
103352
+ return [];
103353
+ return output.split(`
103354
+ `).map((line) => line.trim()).filter(Boolean);
103355
+ }
103356
+ function detectDefaultBranch() {
103357
+ const symbolicRef = execOrNull([
103358
+ "symbolic-ref",
103359
+ "refs/remotes/origin/HEAD"
103360
+ ]);
103361
+ if (symbolicRef) {
103362
+ const parts = symbolicRef.split("/");
103363
+ return parts[parts.length - 1] ?? "main";
103364
+ }
103365
+ return "main";
103366
+ }
103367
+ function getFirstCommit() {
103368
+ return execOrNull(["rev-list", "--max-parents=0", "HEAD"]);
103369
+ }
103370
+ function detectBaseRef(overrideSince) {
103371
+ if (overrideSince) {
103372
+ const resolved = execOrNull(["rev-parse", "--verify", overrideSince]);
103373
+ if (!resolved) {
103374
+ throw new Error(`Invalid ref: ${overrideSince}`);
103375
+ }
103376
+ return overrideSince;
103377
+ }
103378
+ const currentBranch = execOrNull(["rev-parse", "--abbrev-ref", "HEAD"]);
103379
+ const defaultBranch = detectDefaultBranch();
103380
+ const isDefaultBranch = currentBranch === defaultBranch || currentBranch === "HEAD";
103381
+ if (isDefaultBranch) {
103382
+ return null;
103383
+ }
103384
+ const hasRemote = execOrNull(["remote"]);
103385
+ if (hasRemote) {
103386
+ return `origin/${defaultBranch}`;
103387
+ }
103388
+ warning("No remote found, falling back to local refs");
103389
+ return defaultBranch;
103390
+ }
103391
+ function detectArtifactBaseRef(artifactName) {
103392
+ const lastTag = execOrNull([
103393
+ "describe",
103394
+ "--tags",
103395
+ "--abbrev=0",
103396
+ "--match",
103397
+ `${artifactName}@*`
103398
+ ]);
103399
+ if (lastTag)
103400
+ return lastTag;
103401
+ const firstCommit = getFirstCommit();
103402
+ if (firstCommit)
103403
+ return firstCommit;
103404
+ warning(`${artifactName}: no tags found, falling back to HEAD~1`);
103405
+ return "HEAD~1";
103406
+ }
103407
+ function getChangedFiles(baseRef, path8) {
103408
+ const baseArgs = ["diff", "--name-only"];
103409
+ const pathFilter = path8 ? ["--", path8] : [];
103410
+ const output = execOrNull([...baseArgs, `${baseRef}...HEAD`, ...pathFilter]) ?? execOrNull([...baseArgs, baseRef, "HEAD", ...pathFilter]);
103411
+ return splitLines(output);
103412
+ }
103413
+ function getCommitsForPath(baseRef, path8) {
103414
+ const output = execOrNull([
103415
+ "log",
103416
+ "--format=%H %s",
103417
+ `${baseRef}..HEAD`,
103418
+ "--",
103419
+ path8
103420
+ ]);
103421
+ return splitLines(output);
103422
+ }
103423
+
103424
+ // src/commands/changelog/conventional-commits.ts
103425
+ var CONVENTIONAL_COMMIT_REGEX = /^([a-f0-9]+)\s+(\w+)(\(.+?\))?(!)?\s*:\s*(.+)$/;
103426
+ function parseConventionalCommit(line) {
103427
+ if (line.includes("\x00"))
103428
+ return null;
103429
+ const match = line.match(CONVENTIONAL_COMMIT_REGEX);
103430
+ if (!match)
103431
+ return null;
103432
+ const hash = match[1] ?? "";
103433
+ const type = match[2] ?? "";
103434
+ const scopeRaw = match[3];
103435
+ const bang = match[4];
103436
+ const message = match[5] ?? "";
103437
+ const scope = scopeRaw ? scopeRaw.slice(1, -1) : null;
103438
+ const breaking = bang === "!" || message.toUpperCase().includes("BREAKING CHANGE");
103439
+ return {
103440
+ hash,
103441
+ type,
103442
+ scope,
103443
+ breaking,
103444
+ message,
103445
+ raw: line
103446
+ };
103447
+ }
103448
+ function determineBumpType(commits) {
103449
+ if (commits.some((commit) => commit.breaking))
103450
+ return "major";
103451
+ if (commits.some((commit) => commit.type === "feat"))
103452
+ return "minor";
103453
+ return "patch";
103454
+ }
103455
+ function mapFilesToArtifacts(changedFiles, artifacts) {
103456
+ const result = new Map;
103457
+ for (const file of changedFiles) {
103458
+ for (const artifact of artifacts) {
103459
+ const normalized = normalizePath(artifact.relativePath);
103460
+ const prefix = normalized.endsWith("/") ? normalized : `${normalized}/`;
103461
+ if (file.startsWith(prefix)) {
103462
+ const existing = result.get(artifact.relativePath) ?? [];
103463
+ existing.push(file);
103464
+ result.set(artifact.relativePath, existing);
103465
+ break;
103466
+ }
103467
+ }
103468
+ }
103469
+ return result;
103470
+ }
103471
+
103472
+ // src/commands/changelog/changeset-output.ts
103473
+ import { join as join31 } from "path";
103474
+ import { randomBytes as randomBytes3 } from "crypto";
103475
+ var CHANGESET_DIR = ".changeset";
103476
+ function generateChangesetFile(artifacts, workspaceRoot) {
103477
+ const changesetDir = join31(workspaceRoot, CHANGESET_DIR);
103478
+ if (!fs.exists(changesetDir)) {
103479
+ fs.mkdir(changesetDir, { recursive: true });
103480
+ }
103481
+ const filename = `${randomBytes3(4).toString("hex")}.md`;
103482
+ const filepath = join31(changesetDir, filename);
103483
+ const content = buildChangesetContent(artifacts);
103484
+ fs.writeFile(filepath, content);
103485
+ return filepath;
103486
+ }
103487
+ function buildChangesetContent(artifacts) {
103488
+ const frontmatterLines = [];
103489
+ const bodyLines = [];
103490
+ for (const entry of artifacts) {
103491
+ const { name: name2 } = entry.artifact.manifest;
103492
+ frontmatterLines.push(`"${name2}": ${entry.calculatedBump}`);
103493
+ if (entry.commits.length > 0) {
103494
+ for (const commit of entry.commits) {
103495
+ bodyLines.push(`- ${name2}: ${commit.type}: ${commit.message}`);
103496
+ }
103497
+ } else {
103498
+ bodyLines.push(`- ${name2}: changed files detected`);
103499
+ }
103500
+ }
103501
+ return ["---", ...frontmatterLines, "---", "", ...bodyLines, ""].join(`
103502
+ `);
103503
+ }
103504
+ function previewChangesetContent(artifacts) {
103505
+ return buildChangesetContent(artifacts);
103506
+ }
103507
+ function toOutputEntry(entry) {
103508
+ return {
103509
+ name: entry.artifact.manifest.name,
103510
+ version: entry.artifact.manifest.version,
103511
+ bump: entry.calculatedBump,
103512
+ commits: entry.commits.map((commit) => ({
103513
+ hash: commit.hash,
103514
+ type: commit.type,
103515
+ scope: commit.scope,
103516
+ breaking: commit.breaking,
103517
+ message: commit.message
103518
+ })),
103519
+ changedFiles: entry.changedFiles
103520
+ };
103521
+ }
103522
+ function formatJson(artifacts, baseRef) {
103523
+ return JSON.stringify({ baseRef, artifacts: artifacts.map(toOutputEntry) }, null, 2);
103524
+ }
103525
+ function formatYaml(artifacts, baseRef) {
103526
+ return $stringify2({
103527
+ baseRef,
103528
+ artifacts: artifacts.map(toOutputEntry)
103529
+ });
103530
+ }
103531
+
103532
+ // src/commands/changelog/changelog.ts
103533
+ var BUMP_CHOICES = [
103534
+ { name: "patch", value: "patch" },
103535
+ { name: "minor", value: "minor" },
103536
+ { name: "major", value: "major" },
103537
+ { name: "skip", value: "skip" }
103538
+ ];
103539
+ var changelogCommand = new Command("changelog").description("Generate changesets from git history for workspace artifacts").option("--ci", "Unattended mode (no prompts, auto-generate from commits)").option("--format <format>", "Output format: changeset (default), json, yaml", "changeset").option("--since <ref>", "Override base ref (auto-detected otherwise)").option("--dry-run", "Preview without writing").action(async (options2) => {
103540
+ const cwd = process.cwd();
103541
+ if (!isWorkspaceRoot(fs, cwd)) {
103542
+ error("Not a workspace (grekt-workspace.yaml not found)");
103543
+ info("Run this command from your workspace root");
103544
+ process.exit(1);
103545
+ }
103546
+ const workspace = await loadWorkspace(cwd);
103547
+ if (!workspace || workspace.artifacts.length === 0) {
103548
+ error("No artifacts found in workspace");
103549
+ process.exit(1);
103550
+ }
103551
+ const globalBaseRef = detectBaseRef(options2.since);
103552
+ const artifactChangelogs = globalBaseRef ? buildFromGlobalRef(workspace.artifacts, globalBaseRef, options2.ci) : buildPerArtifact(workspace.artifacts, options2.ci);
103553
+ if (artifactChangelogs.length === 0) {
103554
+ info("No artifact changes detected");
103555
+ process.exit(0);
103556
+ }
103557
+ const displayRef = globalBaseRef ?? "per-artifact tags";
103558
+ if (options2.ci) {
103559
+ printSummary(artifactChangelogs, displayRef);
103560
+ outputResult(artifactChangelogs, displayRef, cwd, options2);
103561
+ } else {
103562
+ await withPromptHandler(() => handleInteractiveMode(artifactChangelogs, displayRef, cwd, options2));
103563
+ }
103564
+ });
103565
+ function buildFromGlobalRef(artifacts, baseRef, ciMode) {
103566
+ const changedFiles = getChangedFiles(baseRef);
103567
+ if (changedFiles.length === 0)
103568
+ return [];
103569
+ const filesByArtifact = mapFilesToArtifacts(changedFiles, artifacts);
103570
+ const result = [];
103571
+ for (const artifact of artifacts) {
103572
+ const files = filesByArtifact.get(artifact.relativePath);
103573
+ if (!files)
103574
+ continue;
103575
+ result.push(buildArtifactChangelog(artifact, files, baseRef, ciMode));
103576
+ }
103577
+ return result;
103578
+ }
103579
+ function buildPerArtifact(artifacts, ciMode) {
103580
+ const result = [];
103581
+ for (const artifact of artifacts) {
103582
+ const baseRef = detectArtifactBaseRef(artifact.manifest.name);
103583
+ const changedFiles = getChangedFiles(baseRef, artifact.relativePath);
103584
+ if (changedFiles.length === 0)
103585
+ continue;
103586
+ result.push(buildArtifactChangelog(artifact, changedFiles, baseRef, ciMode));
103587
+ }
103588
+ return result;
103589
+ }
103590
+ function buildArtifactChangelog(artifact, changedFiles, baseRef, ciMode) {
103591
+ const commitLines = getCommitsForPath(baseRef, artifact.relativePath);
103592
+ const commits = commitLines.map(parseConventionalCommit).filter((commit) => commit !== null);
103593
+ const nonConventionalCount = commitLines.length - commits.length;
103594
+ if (nonConventionalCount > 0 && ciMode) {
103595
+ warning(`${artifact.manifest.name}: ${nonConventionalCount} non-conventional commit(s) ignored`);
103596
+ }
103597
+ const calculatedBump = commits.length > 0 ? determineBumpType(commits) : "patch";
103598
+ if (commits.length === 0 && ciMode) {
103599
+ warning(`${artifact.manifest.name}: no conventional commits found, defaulting to patch`);
103600
+ }
103601
+ return {
103602
+ artifact,
103603
+ commits,
103604
+ calculatedBump,
103605
+ changedFiles
103606
+ };
103607
+ }
103608
+ function printSummary(artifactChangelogs, baseRef) {
103609
+ newline();
103610
+ info(`Base ref: ${colors5.highlight(baseRef)}`);
103611
+ info(`${artifactChangelogs.length} artifact(s) with changes:`);
103612
+ newline();
103613
+ for (const entry of artifactChangelogs) {
103614
+ const { name: name2, version: version3 } = entry.artifact.manifest;
103615
+ log(` ${name2} ${colors5.dim(`v${version3}`)} ${symbols.arrow} ${colors5.bold(entry.calculatedBump)} ${colors5.dim(`(${entry.commits.length} commit(s))`)}`);
103616
+ }
103617
+ newline();
103618
+ }
103619
+ async function handleInteractiveMode(artifactChangelogs, baseRef, cwd, options2) {
103620
+ printSummary(artifactChangelogs, baseRef);
103621
+ const finalChangelogs = [];
103622
+ for (const entry of artifactChangelogs) {
103623
+ const { name: name2 } = entry.artifact.manifest;
103624
+ const bump = await esm_default6({
103625
+ message: `${name2}: bump type?`,
103626
+ choices: BUMP_CHOICES,
103627
+ default: entry.calculatedBump
103628
+ });
103629
+ if (bump === "skip") {
103630
+ info(`Skipping ${name2}`);
103631
+ continue;
103632
+ }
103633
+ finalChangelogs.push({
103634
+ ...entry,
103635
+ calculatedBump: bump
103636
+ });
103637
+ }
103638
+ if (finalChangelogs.length === 0) {
103639
+ newline();
103640
+ info("All artifacts skipped");
103641
+ return;
103642
+ }
103643
+ newline();
103644
+ const confirmed = await esm_default3({
103645
+ message: `Generate changeset for ${finalChangelogs.length} artifact(s)?`,
103646
+ default: true
103647
+ });
103648
+ if (!confirmed) {
103649
+ info("Cancelled");
103650
+ return;
103651
+ }
103652
+ outputResult(finalChangelogs, baseRef, cwd, options2);
103653
+ }
103654
+ function outputResult(artifactChangelogs, baseRef, cwd, options2) {
103655
+ const format = options2.format ?? "changeset";
103656
+ if (format === "json") {
103657
+ log(formatJson(artifactChangelogs, baseRef));
103658
+ return;
103659
+ }
103660
+ if (format === "yaml") {
103661
+ log(formatYaml(artifactChangelogs, baseRef));
103662
+ return;
103663
+ }
103664
+ if (options2.dryRun) {
103665
+ newline();
103666
+ info("Dry run — changeset content:");
103667
+ newline();
103668
+ log(previewChangesetContent(artifactChangelogs));
103669
+ return;
103670
+ }
103671
+ const filepath = generateChangesetFile(artifactChangelogs, cwd);
103672
+ newline();
103673
+ success(`Changeset written to ${filepath}`);
103674
+ }
103335
103675
  // src/commands/workspace/workspace.ts
103336
103676
  var listSubcommand = new Command("list").description("List all artifacts in the workspace").action(async () => {
103337
103677
  const cwd = process.cwd();
@@ -103355,7 +103695,7 @@ var listSubcommand = new Command("list").description("List all artifacts in the
103355
103695
  });
103356
103696
  var workspaceCommand = new Command("workspace").description("Manage monorepo workspaces").addCommand(listSubcommand);
103357
103697
  // src/commands/worktree/worktree.ts
103358
- import { join as join31, dirname as dirname11, isAbsolute as isAbsolute4 } from "path";
103698
+ import { join as join32, dirname as dirname11, isAbsolute as isAbsolute4 } from "path";
103359
103699
  var GREKT_DIR2 = ".grekt";
103360
103700
  function getGitCommonDir() {
103361
103701
  try {
@@ -103372,7 +103712,7 @@ function getWorktreeRoot() {
103372
103712
  }
103373
103713
  }
103374
103714
  function resolveOriginalRepoRoot(commonDir, worktreeRoot) {
103375
- const absoluteCommonDir = isAbsolute4(commonDir) ? commonDir : join31(worktreeRoot, commonDir);
103715
+ const absoluteCommonDir = isAbsolute4(commonDir) ? commonDir : join32(worktreeRoot, commonDir);
103376
103716
  return dirname11(absoluteCommonDir);
103377
103717
  }
103378
103718
  function isInsideWorktree(commonDir) {
@@ -103395,8 +103735,8 @@ var syncSubcommand = new Command("sync").description("Copy .grekt/ from the orig
103395
103735
  process.exit(1);
103396
103736
  }
103397
103737
  const originalRepoRoot = resolveOriginalRepoRoot(commonDir, worktreeRoot);
103398
- const sourcePath = join31(originalRepoRoot, GREKT_DIR2);
103399
- const destPath = join31(worktreeRoot, GREKT_DIR2);
103738
+ const sourcePath = join32(originalRepoRoot, GREKT_DIR2);
103739
+ const destPath = join32(worktreeRoot, GREKT_DIR2);
103400
103740
  if (!fs.exists(sourcePath)) {
103401
103741
  info("No .grekt/ directory found in the original repository");
103402
103742
  process.exit(0);
@@ -103419,7 +103759,7 @@ var syncSubcommand = new Command("sync").description("Copy .grekt/ from the orig
103419
103759
  });
103420
103760
  var worktreeCommand = new Command("worktree").description("Manage git worktree integration").addCommand(syncSubcommand);
103421
103761
  // src/commands/scan.ts
103422
- import { join as join32, resolve as resolve8 } from "path";
103762
+ import { join as join33, resolve as resolve8 } from "path";
103423
103763
  var VALID_BADGES = ["certified", "conditional", "suspicious", "rejected"];
103424
103764
  var BADGE_COLORS = {
103425
103765
  certified: colors5.success,
@@ -103515,7 +103855,7 @@ var scanCommand = new Command("scan").description("Scan artifacts for security i
103515
103855
  });
103516
103856
  async function scanRemoteArtifact(source, projectRoot, jsonOutput, failOnThreshold) {
103517
103857
  const displayName = getSourceDisplayName(source);
103518
- const tempDir = join32(projectRoot, ARTIFACTS_DIR, `.tmp-scan-${cryptoProvider.randomUUID()}`);
103858
+ const tempDir = join33(projectRoot, ARTIFACTS_DIR, `.tmp-scan-${cryptoProvider.randomUUID()}`);
103519
103859
  try {
103520
103860
  if (!jsonOutput) {
103521
103861
  const spin = spinner(`Downloading ${colors5.highlight(displayName)}...`);
@@ -103641,14 +103981,14 @@ async function scanAllInstalled(projectRoot, jsonOutput, failOnThreshold) {
103641
103981
  }
103642
103982
  const config = getConfig(projectRoot);
103643
103983
  const trustKey = process.env.GREKT_TRUST_KEY;
103644
- const artifactsDir = join32(projectRoot, ARTIFACTS_DIR);
103984
+ const artifactsDir = join33(projectRoot, ARTIFACTS_DIR);
103645
103985
  const results = [];
103646
103986
  const errors4 = [];
103647
103987
  if (!jsonOutput) {
103648
103988
  log(`Scanning ${colors5.bold(String(artifactIds.length))} artifact${artifactIds.length === 1 ? "" : "s"}...`);
103649
103989
  }
103650
103990
  for (const artifactId of artifactIds) {
103651
- const artifactDir = join32(artifactsDir, artifactId);
103991
+ const artifactDir = join33(artifactsDir, artifactId);
103652
103992
  if (!fs.exists(artifactDir)) {
103653
103993
  errors4.push({ artifactId, message: "Not installed (directory missing)" });
103654
103994
  continue;
@@ -103996,7 +104336,7 @@ var whoamiCommand = new Command("whoami").description("Show current user").actio
103996
104336
  // package.json
103997
104337
  var package_default = {
103998
104338
  name: "@grekt/cli",
103999
- version: "6.39.0",
104339
+ version: "6.40.0",
104000
104340
  description: "AI tools versioned, synced, and shared across tools and teams",
104001
104341
  type: "module",
104002
104342
  bin: {
@@ -104065,13 +104405,13 @@ var package_default = {
104065
104405
  // src/update-check/update-check.ts
104066
104406
  import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
104067
104407
  import { homedir as homedir3 } from "os";
104068
- import { join as join33 } from "path";
104408
+ import { join as join34 } from "path";
104069
104409
  var CACHE_FILENAME = ".update-check";
104070
104410
  var STALENESS_MS = 24 * 60 * 60 * 1000;
104071
104411
  var FETCH_TIMEOUT_MS = 1500;
104072
104412
  var GITHUB_RELEASES_URL = "https://api.github.com/repos/grekt-labs/cli/releases/latest";
104073
104413
  function getCachePath() {
104074
- return join33(homedir3(), ".grekt", CACHE_FILENAME);
104414
+ return join34(homedir3(), ".grekt", CACHE_FILENAME);
104075
104415
  }
104076
104416
  function isOptedOut() {
104077
104417
  return process.env.GREKT_NO_UPDATE_CHECK === "1";
@@ -104090,7 +104430,7 @@ function readCache() {
104090
104430
  }
104091
104431
  function writeCache(cache2) {
104092
104432
  try {
104093
- const dir = join33(homedir3(), ".grekt");
104433
+ const dir = join34(homedir3(), ".grekt");
104094
104434
  if (!existsSync2(dir)) {
104095
104435
  mkdirSync3(dir, { recursive: true });
104096
104436
  }
@@ -104198,6 +104538,7 @@ program2.addCommand(versionsCommand);
104198
104538
  program2.addCommand(outdatedCommand);
104199
104539
  program2.addCommand(upgradeCommand);
104200
104540
  program2.addCommand(versionCommand);
104541
+ program2.addCommand(changelogCommand);
104201
104542
  program2.addCommand(workspaceCommand);
104202
104543
  program2.addCommand(worktreeCommand);
104203
104544
  program2.addCommand(scanCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grekt/cli",
3
- "version": "6.39.0",
3
+ "version": "6.40.0",
4
4
  "description": "AI tools versioned, synced, and shared across tools and teams",
5
5
  "type": "module",
6
6
  "bin": {