refactor(extensions): simplify lib helpers, tool registration and kit-doctor
Behavior-preserving cleanups: - extract shared resolveBaseUrl helper for jira/confluence base-URL logic - extract shared registerTool helper; port jira/gitlab/confluence tools to it - make scanSecrets declarative (filter/map) - collapse ok/warn pair into status() and drop double existsSync in kit-doctor Typecheck clean; npm test 71 PASS, 0 FAIL.
This commit is contained in:
@@ -36,6 +36,18 @@ function base(url: string): string {
|
||||
return url.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an effective base URL from company.json + an env override.
|
||||
* The env var wins when set (and not "TODO"); otherwise the config value
|
||||
* (if set and not "TODO") is used. Trailing slashes are trimmed. Returns ""
|
||||
* when neither is configured; callers decide whether to throw.
|
||||
*/
|
||||
export function resolveBaseUrl(envName: string, cfgValue: string | null | undefined): string {
|
||||
const fromCfg = cfgValue && cfgValue !== "TODO" ? cfgValue : "";
|
||||
const env = process.env[envName];
|
||||
return (env && env !== "TODO" ? env : fromCfg).replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
/** Build a URL with query params; undefined/empty values are dropped. */
|
||||
export function buildUrl(
|
||||
baseUrl: string,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* `docsUrl` comes from config/company.json; `CONFLUENCE_TOKEN` from the env.
|
||||
* No pi API — unit-testable.
|
||||
*/
|
||||
import { apiRequest, requireToken, ApiError } from "./atlassian.ts";
|
||||
import { apiRequest, requireToken, ApiError, resolveBaseUrl } from "./atlassian.ts";
|
||||
import { readLocalConfig } from "./company-config.ts";
|
||||
import * as path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
@@ -14,8 +14,7 @@ const CONFIG_PATH = path.join(HERE, "..", "..", "config", "company.json");
|
||||
/** Resolve the Confluence base URL from company.json (env override for labs). */
|
||||
export function confluenceBaseUrl(): string {
|
||||
const cfg = readLocalConfig(CONFIG_PATH);
|
||||
const fromCfg = cfg.docsUrl && cfg.docsUrl !== "TODO" ? cfg.docsUrl : "";
|
||||
const url = (process.env.CONFLUENCE_URL && process.env.CONFLUENCE_URL !== "TODO" ? process.env.CONFLUENCE_URL : fromCfg).replace(/\/+$/, "");
|
||||
const url = resolveBaseUrl("CONFLUENCE_URL", cfg.docsUrl);
|
||||
if (!url) throw new ApiError("Не задан docsUrl в config/company.json и нет CONFLUENCE_URL.");
|
||||
return url;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* `trackerUrl` comes from config/company.json; `JIRA_TOKEN` from the env.
|
||||
* No pi API — unit-testable (see test/api.test.ts).
|
||||
*/
|
||||
import { apiRequest, requireToken, ApiError } from "./atlassian.ts";
|
||||
import { apiRequest, requireToken, ApiError, resolveBaseUrl } from "./atlassian.ts";
|
||||
import { readLocalConfig } from "./company-config.ts";
|
||||
import * as path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
@@ -11,12 +11,11 @@ import { fileURLToPath } from "node:url";
|
||||
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
||||
const CONFIG_PATH = path.join(HERE, "..", "..", "config", "company.json");
|
||||
|
||||
/** Resolve the tracker base URL — prefer company.json; env override wins. */
|
||||
/** Resolve the tracker base URL — company.json default, env override wins. */
|
||||
export function jiraBaseUrl(): string {
|
||||
const cfg = readLocalConfig(CONFIG_PATH);
|
||||
const fromCfg = cfg.trackerUrl && cfg.trackerUrl !== "TODO" ? cfg.trackerUrl : "";
|
||||
// env TRACKER_URL may override the packaged default for local labs.
|
||||
return (process.env.TRACKER_URL && process.env.TRACKER_URL !== "TODO" ? process.env.TRACKER_URL : fromCfg).replace(/\/+$/, "");
|
||||
return resolveBaseUrl("TRACKER_URL", cfg.trackerUrl);
|
||||
}
|
||||
|
||||
function token(): string {
|
||||
|
||||
@@ -29,12 +29,8 @@ export const SECRET_PATTERNS: SecretPattern[] = [
|
||||
/** Return the names of secret patterns found in `text` (empty if none). */
|
||||
export function scanSecrets(text: string): string[] {
|
||||
if (!text) return [];
|
||||
const hits: string[] = [];
|
||||
for (const p of SECRET_PATTERNS) {
|
||||
// Use a non-stateful copy (no global flag) to avoid lastIndex surprises.
|
||||
if (p.re.test(text)) hits.push(p.name);
|
||||
}
|
||||
return hits;
|
||||
// No pattern carries the /g flag, so `test` stays stateless and order is preserved.
|
||||
return SECRET_PATTERNS.filter((p) => p.re.test(text)).map((p) => p.name);
|
||||
}
|
||||
|
||||
/** Replace every secret occurrence with a labelled placeholder. */
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Shared native pi tool registration.
|
||||
* The three *-tools.ts extensions each register tools whose `execute` bodies
|
||||
* are byte-identical apart from the error prefix in the failure payload.
|
||||
* This helper DRYs that boilerplate; extensions only supply the spec.
|
||||
*/
|
||||
|
||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
export interface ToolSpec {
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: Record<string, unknown>;
|
||||
/** Prefix in the error payload, e.g. "jira" → "jira error: …". */
|
||||
errorPrefix: string;
|
||||
run: (params: any) => Promise<unknown>;
|
||||
}
|
||||
|
||||
/** Register a native tool whose result/error payload is formatted uniformly. */
|
||||
export function registerTool(pi: ExtensionAPI, spec: ToolSpec): void {
|
||||
pi.registerTool({
|
||||
name: spec.name,
|
||||
label: spec.name,
|
||||
description: spec.description,
|
||||
parameters: spec.parameters as any,
|
||||
async execute(_id: string, params: unknown) {
|
||||
try {
|
||||
const out = await spec.run(params ?? {});
|
||||
return { content: [{ type: "text", text: JSON.stringify(out ?? {}, null, 2) }], details: {} };
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
return { content: [{ type: "text", text: `${spec.errorPrefix} error: ${msg}` }], isError: true, details: {} };
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user