refactor: remove deprecated GitLab wrapper module and related code
This commit is contained in:
183
lib/gitlab.ts
183
lib/gitlab.ts
@@ -1,183 +0,0 @@
|
|||||||
/**
|
|
||||||
* @deprecated This module is deprecated and kept only for reference.
|
|
||||||
* Use lib/providers/index.ts with createProvider() for GitLab/GitHub abstraction.
|
|
||||||
*
|
|
||||||
* GitLab wrapper using glab CLI.
|
|
||||||
* Handles label transitions, issue fetching, and MR verification.
|
|
||||||
*/
|
|
||||||
import { execFile } from "node:child_process";
|
|
||||||
import { promisify } from "node:util";
|
|
||||||
|
|
||||||
const execFileAsync = promisify(execFile);
|
|
||||||
|
|
||||||
// State labels — each issue has exactly ONE at a time
|
|
||||||
const STATE_LABELS = [
|
|
||||||
"Planning",
|
|
||||||
"To Do",
|
|
||||||
"Doing",
|
|
||||||
"To Test",
|
|
||||||
"Testing",
|
|
||||||
"Done",
|
|
||||||
"To Improve",
|
|
||||||
"Refining",
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export type StateLabel = (typeof STATE_LABELS)[number];
|
|
||||||
|
|
||||||
type GlabOptions = {
|
|
||||||
repoPath: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
async function glab(
|
|
||||||
args: string[],
|
|
||||||
opts: GlabOptions,
|
|
||||||
): Promise<string> {
|
|
||||||
const { stdout } = await execFileAsync("glab", args, {
|
|
||||||
cwd: opts.repoPath,
|
|
||||||
timeout: 30_000,
|
|
||||||
});
|
|
||||||
return stdout.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GitLabIssue = {
|
|
||||||
iid: number;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
labels: string[];
|
|
||||||
state: string;
|
|
||||||
web_url: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch a single issue by ID.
|
|
||||||
*/
|
|
||||||
export async function getIssue(
|
|
||||||
issueId: number,
|
|
||||||
opts: GlabOptions,
|
|
||||||
): Promise<GitLabIssue> {
|
|
||||||
const raw = await glab(
|
|
||||||
["issue", "view", String(issueId), "--output", "json"],
|
|
||||||
opts,
|
|
||||||
);
|
|
||||||
return JSON.parse(raw) as GitLabIssue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List issues with a specific label.
|
|
||||||
*/
|
|
||||||
export async function listIssuesByLabel(
|
|
||||||
label: StateLabel,
|
|
||||||
opts: GlabOptions,
|
|
||||||
): Promise<GitLabIssue[]> {
|
|
||||||
try {
|
|
||||||
const raw = await glab(
|
|
||||||
["issue", "list", "--label", label, "--output", "json"],
|
|
||||||
opts,
|
|
||||||
);
|
|
||||||
return JSON.parse(raw) as GitLabIssue[];
|
|
||||||
} catch {
|
|
||||||
// glab returns error when no issues found
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transition an issue from one state label to another.
|
|
||||||
* Uses --unlabel + --label to ensure only one state label at a time.
|
|
||||||
*/
|
|
||||||
export async function transitionLabel(
|
|
||||||
issueId: number,
|
|
||||||
from: StateLabel,
|
|
||||||
to: StateLabel,
|
|
||||||
opts: GlabOptions,
|
|
||||||
): Promise<void> {
|
|
||||||
await glab(
|
|
||||||
[
|
|
||||||
"issue",
|
|
||||||
"update",
|
|
||||||
String(issueId),
|
|
||||||
"--unlabel",
|
|
||||||
from,
|
|
||||||
"--label",
|
|
||||||
to,
|
|
||||||
],
|
|
||||||
opts,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close an issue.
|
|
||||||
*/
|
|
||||||
export async function closeIssue(
|
|
||||||
issueId: number,
|
|
||||||
opts: GlabOptions,
|
|
||||||
): Promise<void> {
|
|
||||||
await glab(["issue", "close", String(issueId)], opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reopen an issue.
|
|
||||||
*/
|
|
||||||
export async function reopenIssue(
|
|
||||||
issueId: number,
|
|
||||||
opts: GlabOptions,
|
|
||||||
): Promise<void> {
|
|
||||||
await glab(["issue", "reopen", String(issueId)], opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the current state label on an issue matches expected.
|
|
||||||
*/
|
|
||||||
export function hasStateLabel(
|
|
||||||
issue: GitLabIssue,
|
|
||||||
expected: StateLabel,
|
|
||||||
): boolean {
|
|
||||||
return issue.labels.includes(expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current state label of an issue (first match from STATE_LABELS).
|
|
||||||
*/
|
|
||||||
export function getCurrentStateLabel(
|
|
||||||
issue: GitLabIssue,
|
|
||||||
): StateLabel | null {
|
|
||||||
for (const label of STATE_LABELS) {
|
|
||||||
if (issue.labels.includes(label)) {
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if any merged MR exists for a specific issue.
|
|
||||||
*/
|
|
||||||
export async function hasMergedMR(
|
|
||||||
issueId: number,
|
|
||||||
opts: GlabOptions,
|
|
||||||
): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const raw = await glab(
|
|
||||||
["mr", "list", "--output", "json", "--state", "merged"],
|
|
||||||
opts,
|
|
||||||
);
|
|
||||||
const mrs = JSON.parse(raw) as Array<{ title: string; description: string }>;
|
|
||||||
const pattern = `#${issueId}`;
|
|
||||||
return mrs.some(
|
|
||||||
(mr) =>
|
|
||||||
mr.title.includes(pattern) || (mr.description ?? "").includes(pattern),
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve the repo path from projects.json repo field (handles ~/).
|
|
||||||
*/
|
|
||||||
export function resolveRepoPath(repoField: string): string {
|
|
||||||
if (repoField.startsWith("~/")) {
|
|
||||||
return repoField.replace("~", process.env.HOME ?? "/home/lauren");
|
|
||||||
}
|
|
||||||
return repoField;
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import { homedir } from "node:os";
|
||||||
import { TIER_MIGRATION } from "./tiers.js";
|
import { TIER_MIGRATION } from "./tiers.js";
|
||||||
|
|
||||||
export type WorkerState = {
|
export type WorkerState = {
|
||||||
@@ -229,3 +230,14 @@ export async function deactivateWorker(
|
|||||||
issueId: null,
|
issueId: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve repo path from projects.json repo field (handles ~/ expansion).
|
||||||
|
* Uses os.homedir() for cross-platform home directory resolution.
|
||||||
|
*/
|
||||||
|
export function resolveRepoPath(repoField: string): string {
|
||||||
|
if (repoField.startsWith("~/")) {
|
||||||
|
return repoField.replace("~", homedir());
|
||||||
|
}
|
||||||
|
return repoField;
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { execFileSync } from "node:child_process";
|
|||||||
import type { TaskManager } from "./task-manager.js";
|
import type { TaskManager } from "./task-manager.js";
|
||||||
import { GitLabProvider } from "./gitlab.js";
|
import { GitLabProvider } from "./gitlab.js";
|
||||||
import { GitHubProvider } from "./github.js";
|
import { GitHubProvider } from "./github.js";
|
||||||
import { resolveRepoPath } from "../utils.js";
|
import { resolveRepoPath } from "../projects.js";
|
||||||
|
|
||||||
export type ProviderOptions = {
|
export type ProviderOptions = {
|
||||||
provider?: "gitlab" | "github";
|
provider?: "gitlab" | "github";
|
||||||
@@ -24,7 +24,9 @@ function detectProvider(repoPath: string): "gitlab" | "github" {
|
|||||||
const url = execFileSync("git", ["remote", "get-url", "origin"], {
|
const url = execFileSync("git", ["remote", "get-url", "origin"], {
|
||||||
cwd: repoPath,
|
cwd: repoPath,
|
||||||
timeout: 5_000,
|
timeout: 5_000,
|
||||||
}).toString().trim();
|
})
|
||||||
|
.toString()
|
||||||
|
.trim();
|
||||||
|
|
||||||
if (url.includes("github.com")) return "github";
|
if (url.includes("github.com")) return "github";
|
||||||
return "gitlab";
|
return "gitlab";
|
||||||
@@ -39,9 +41,12 @@ export type ProviderWithType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function createProvider(opts: ProviderOptions): ProviderWithType {
|
export function createProvider(opts: ProviderOptions): ProviderWithType {
|
||||||
const repoPath = opts.repoPath ?? (opts.repo ? resolveRepoPath(opts.repo) : null);
|
const repoPath =
|
||||||
|
opts.repoPath ?? (opts.repo ? resolveRepoPath(opts.repo) : null);
|
||||||
if (!repoPath) {
|
if (!repoPath) {
|
||||||
throw new Error("Either repoPath or repo must be provided to createProvider");
|
throw new Error(
|
||||||
|
"Either repoPath or repo must be provided to createProvider",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = opts.provider ?? detectProvider(repoPath);
|
const type = opts.provider ?? detectProvider(repoPath);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import type { ToolContext } from "../types.js";
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { readProjects, writeProjects, emptyWorkerState } from "../projects.js";
|
import { readProjects, writeProjects, emptyWorkerState } from "../projects.js";
|
||||||
import { resolveRepoPath } from "../utils.js";
|
import { resolveRepoPath } from "../projects.js";
|
||||||
import { createProvider } from "../task-managers/index.js";
|
import { createProvider } from "../task-managers/index.js";
|
||||||
import { log as auditLog } from "../audit.js";
|
import { log as auditLog } from "../audit.js";
|
||||||
import { DEV_TIERS, QA_TIERS } from "../tiers.js";
|
import { DEV_TIERS, QA_TIERS } from "../tiers.js";
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { log as auditLog } from "../audit.js";
|
|||||||
import { dispatchTask } from "../dispatch.js";
|
import { dispatchTask } from "../dispatch.js";
|
||||||
import { type StateLabel } from "../task-managers/task-manager.js";
|
import { type StateLabel } from "../task-managers/task-manager.js";
|
||||||
import { createProvider } from "../task-managers/index.js";
|
import { createProvider } from "../task-managers/index.js";
|
||||||
import { resolveRepoPath } from "../utils.js";
|
import { resolveRepoPath } from "../projects.js";
|
||||||
import {
|
import {
|
||||||
deactivateWorker,
|
deactivateWorker,
|
||||||
getProject,
|
getProject,
|
||||||
|
|||||||
13
lib/utils.ts
13
lib/utils.ts
@@ -1,13 +0,0 @@
|
|||||||
/**
|
|
||||||
* Shared utilities for DevClaw.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve the repo path from projects.json repo field (handles ~/).
|
|
||||||
*/
|
|
||||||
export function resolveRepoPath(repoField: string): string {
|
|
||||||
if (repoField.startsWith("~/")) {
|
|
||||||
return repoField.replace("~", process.env.HOME ?? "/home/lauren");
|
|
||||||
}
|
|
||||||
return repoField;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user