mirror of
https://github.com/cline/cline.git
synced 2026-09-19 02:05:44 +08:00
* feat(core): persist plan/act mode, tool auto-approve, and compaction mode in global settings
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(cli): restore /settings general toggles across restarts
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix(core): make global settings updates cross-process safe
Targeted setters previously did unlocked read-modify-write cycles over the
shared global-settings.json, so concurrent hosts (two CLIs, or CLI + VS Code)
could silently discard each other's changes. Route all setters through a new
updateGlobalSettings(mutate) helper that re-reads the latest on-disk state
under a short-lived lock file (with stale-lock reclaim and a bounded wait)
and replaces the file atomically via temp-file rename so readers never see
torn writes.
* Revert "fix(core): make global settings updates cross-process safe"
This reverts commit 198c1c831b.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
537 lines
17 KiB
TypeScript
537 lines
17 KiB
TypeScript
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import {
|
|
appendHookAudit,
|
|
configureSandboxEnvironment,
|
|
formatToolInput,
|
|
formatToolOutput,
|
|
isCliHookPayload,
|
|
normalizeAutoApproveArgs,
|
|
parseArgs,
|
|
} from "./helpers";
|
|
|
|
type EnvSnapshot = {
|
|
CLINE_DATA_DIR: string | undefined;
|
|
CLINE_DB_DATA_DIR: string | undefined;
|
|
CLINE_HOOKS_LOG_PATH: string | undefined;
|
|
CLINE_SESSION_ID: string | undefined;
|
|
CLINE_SESSION_DATA_DIR: string | undefined;
|
|
};
|
|
|
|
function captureEnv(): EnvSnapshot {
|
|
return {
|
|
CLINE_DATA_DIR: process.env.CLINE_DATA_DIR,
|
|
CLINE_DB_DATA_DIR: process.env.CLINE_DB_DATA_DIR,
|
|
CLINE_HOOKS_LOG_PATH: process.env.CLINE_HOOKS_LOG_PATH,
|
|
CLINE_SESSION_ID: process.env.CLINE_SESSION_ID,
|
|
CLINE_SESSION_DATA_DIR: process.env.CLINE_SESSION_DATA_DIR,
|
|
};
|
|
}
|
|
|
|
function restoreEnv(snapshot: EnvSnapshot): void {
|
|
process.env.CLINE_DATA_DIR = snapshot.CLINE_DATA_DIR;
|
|
process.env.CLINE_DB_DATA_DIR = snapshot.CLINE_DB_DATA_DIR;
|
|
process.env.CLINE_HOOKS_LOG_PATH = snapshot.CLINE_HOOKS_LOG_PATH;
|
|
process.env.CLINE_SESSION_ID = snapshot.CLINE_SESSION_ID;
|
|
process.env.CLINE_SESSION_DATA_DIR = snapshot.CLINE_SESSION_DATA_DIR;
|
|
}
|
|
|
|
describe("parseArgs", () => {
|
|
it("returns defaults when no arguments are supplied", () => {
|
|
const parsed = parseArgs([]);
|
|
expect(parsed).toEqual({
|
|
verbose: false,
|
|
interactive: false,
|
|
outputMode: "text",
|
|
mode: "act",
|
|
modeExplicitlySet: false,
|
|
sandbox: false,
|
|
acpMode: false,
|
|
thinking: false,
|
|
reasoningEffort: undefined,
|
|
defaultToolAutoApprove: true,
|
|
});
|
|
});
|
|
|
|
it("parses prompt, runtime flags, and global approval settings", () => {
|
|
const parsed = parseArgs([
|
|
"--verbose",
|
|
"--auto-approve",
|
|
"false",
|
|
"--cwd",
|
|
"/tmp/work",
|
|
"--team-name",
|
|
"dev-team",
|
|
"--provider",
|
|
"openai",
|
|
"--model",
|
|
"gpt-5",
|
|
"--key",
|
|
"abc123",
|
|
"--thinking",
|
|
"high",
|
|
"--plan",
|
|
"Audit",
|
|
"the",
|
|
"repo",
|
|
]);
|
|
|
|
expect(parsed.prompt).toBe("Audit the repo");
|
|
expect(parsed.verbose).toBe(true);
|
|
expect(parsed.defaultToolAutoApprove).toBe(false);
|
|
expect(parsed.autoApproveOverride).toBe(false);
|
|
expect(parsed.thinking).toBe(true);
|
|
expect(parsed.reasoningEffort).toBe("high");
|
|
expect(parsed.outputMode).toBe("text");
|
|
expect(parsed.mode).toBe("plan");
|
|
expect(parsed.cwd).toBe("/tmp/work");
|
|
expect(parsed.teamName).toBe("dev-team");
|
|
expect(parsed.provider).toBe("openai");
|
|
expect(parsed.model).toBe("gpt-5");
|
|
expect(parsed.key).toBe("abc123");
|
|
expect(parsed.sandbox).toBe(false);
|
|
});
|
|
|
|
it("parses provider via -P shorthand", () => {
|
|
const parsed = parseArgs(["-P", "cline"]);
|
|
expect(parsed.provider).toBe("cline");
|
|
});
|
|
|
|
it("enables sandbox automatically when --data-dir is set", () => {
|
|
const parsed = parseArgs(["--data-dir", "./.tmp-cline"]);
|
|
expect(parsed.sandbox).toBe(true);
|
|
expect(parsed.dataDir).toBe("./.tmp-cline");
|
|
});
|
|
|
|
it("does not enable sandbox when --data-dir is omitted", () => {
|
|
const parsed = parseArgs([]);
|
|
expect(parsed.sandbox).toBe(false);
|
|
expect(parsed.dataDir).toBeUndefined();
|
|
});
|
|
|
|
it("parses --auto-approve false as global approval-off", () => {
|
|
const parsed = parseArgs([
|
|
"--auto-approve",
|
|
"false",
|
|
"tell me about this repo",
|
|
]);
|
|
expect(parsed.defaultToolAutoApprove).toBe(false);
|
|
expect(parsed.autoApproveOverride).toBe(false);
|
|
expect(parsed.prompt).toBe("tell me about this repo");
|
|
});
|
|
|
|
it("treats bare --auto-approve as true", () => {
|
|
expect(
|
|
normalizeAutoApproveArgs(["--auto-approve", "Audit the repo"]),
|
|
).toEqual(["--auto-approve", "true", "Audit the repo"]);
|
|
const parsed = parseArgs(["--auto-approve", "Audit the repo"]);
|
|
expect(parsed.defaultToolAutoApprove).toBe(true);
|
|
expect(parsed.autoApproveOverride).toBe(true);
|
|
expect(parsed.prompt).toBe("Audit the repo");
|
|
});
|
|
|
|
it("records invalid --auto-approve values", () => {
|
|
const parsed = parseArgs(["--auto-approve=maybe"]);
|
|
expect(parsed.invalidAutoApprove).toBe("maybe");
|
|
});
|
|
|
|
it("accepts legacy --autoapprove as --auto-approve", () => {
|
|
const parsed = parseArgs(["--autoapprove", "false"]);
|
|
expect(parsed.defaultToolAutoApprove).toBe(false);
|
|
expect(parsed.autoApproveOverride).toBe(false);
|
|
});
|
|
|
|
it("supports json output flags and validates explicit output modes", () => {
|
|
const parsedJsonAlias = parseArgs(["--json", "hello"]);
|
|
expect(parsedJsonAlias.outputMode).toBe("json");
|
|
expect(parsedJsonAlias.prompt).toBe("hello");
|
|
});
|
|
|
|
it("parses act/plan mode flags", () => {
|
|
const parsedPlan = parseArgs(["--plan"]);
|
|
expect(parsedPlan.mode).toBe("plan");
|
|
|
|
const parsedAct = parseArgs(["-a"]);
|
|
expect(parsedAct.mode).toBe("act");
|
|
});
|
|
|
|
it("parses bare --thinking as medium before a flag", () => {
|
|
const parsed = parseArgs(["--thinking", "--plan", "Audit the repo"]);
|
|
expect(parsed.thinking).toBe(true);
|
|
expect(parsed.reasoningEffort).toBe("medium");
|
|
expect(parsed.mode).toBe("plan");
|
|
expect(parsed.prompt).toBe("Audit the repo");
|
|
});
|
|
|
|
it("parses --thinking with explicit level", () => {
|
|
const parsed = parseArgs(["--thinking", "high"]);
|
|
expect(parsed.thinking).toBe(true);
|
|
expect(parsed.reasoningEffort).toBe("high");
|
|
});
|
|
|
|
it("parses --thinking none as disabled", () => {
|
|
const parsed = parseArgs(["--thinking", "none"]);
|
|
expect(parsed.thinking).toBe(false);
|
|
expect(parsed.reasoningEffort).toBeUndefined();
|
|
});
|
|
|
|
it("parses and validates thinking level with equals notation", () => {
|
|
const parsedInvalid = parseArgs(["--thinking=ultra"]);
|
|
expect(parsedInvalid.reasoningEffort).toBeUndefined();
|
|
expect(parsedInvalid.invalidThinkingLevel).toBe("ultra");
|
|
});
|
|
|
|
it("parses and validates thinking level with space notation", () => {
|
|
const parsedInvalid = parseArgs(["--thinking", "ultra", "Audit"]);
|
|
expect(parsedInvalid.reasoningEffort).toBeUndefined();
|
|
expect(parsedInvalid.invalidThinkingLevel).toBe("ultra");
|
|
expect(parsedInvalid.prompt).toBe("Audit");
|
|
});
|
|
|
|
it("accepts legacy --reasoning-effort as --thinking", () => {
|
|
const parsed = parseArgs(["--reasoning-effort", "high"]);
|
|
expect(parsed.thinking).toBe(true);
|
|
expect(parsed.reasoningEffort).toBe("high");
|
|
});
|
|
|
|
it("validates legacy --reasoning-effort invalid levels", () => {
|
|
const parsedSpace = parseArgs(["--reasoning-effort", "ultra", "Audit"]);
|
|
expect(parsedSpace.reasoningEffort).toBeUndefined();
|
|
expect(parsedSpace.invalidThinkingLevel).toBe("ultra");
|
|
expect(parsedSpace.prompt).toBe("Audit");
|
|
|
|
const parsedEquals = parseArgs(["--reasoning-effort=ultra", "Audit"]);
|
|
expect(parsedEquals.reasoningEffort).toBeUndefined();
|
|
expect(parsedEquals.invalidThinkingLevel).toBe("ultra");
|
|
expect(parsedEquals.prompt).toBe("Audit");
|
|
});
|
|
|
|
it("parses --retries when valid", () => {
|
|
const parsed = parseArgs(["--retries", "5"]);
|
|
expect(parsed.retries).toBe(5);
|
|
expect(parsed.invalidRetries).toBeUndefined();
|
|
});
|
|
|
|
it("supports yolo as an auto-approval shortcut", () => {
|
|
const parsedYolo = parseArgs(["--yolo"]);
|
|
expect(parsedYolo.mode).toBe("yolo");
|
|
expect(parsedYolo.defaultToolAutoApprove).toBe(true);
|
|
expect(parsedYolo.autoApproveOverride).toBe(true);
|
|
});
|
|
|
|
it("marks explicit mode flags so persisted settings do not override them", () => {
|
|
expect(parseArgs([]).modeExplicitlySet).toBe(false);
|
|
expect(parseArgs(["Audit the repo"]).modeExplicitlySet).toBe(false);
|
|
expect(parseArgs(["--plan"]).modeExplicitlySet).toBe(true);
|
|
expect(parseArgs(["--act"]).modeExplicitlySet).toBe(true);
|
|
expect(parseArgs(["--yolo"]).modeExplicitlySet).toBe(true);
|
|
expect(parseArgs(["--zen", "do it"]).modeExplicitlySet).toBe(true);
|
|
});
|
|
|
|
it("parses --zen flag for background hub dispatch", () => {
|
|
const parsedLong = parseArgs(["--zen", "do it"]);
|
|
expect(parsedLong.mode).toBe("zen");
|
|
expect(parsedLong.prompt).toBe("do it");
|
|
|
|
const parsedShort = parseArgs(["-z", "do it"]);
|
|
expect(parsedShort.mode).toBe("zen");
|
|
});
|
|
|
|
it("parses timeout and validates invalid values", () => {
|
|
const parsed = parseArgs(["-t", "30"]);
|
|
expect(parsed.timeoutSeconds).toBe(30);
|
|
|
|
const invalid = parseArgs(["--timeout", "abc"]);
|
|
expect(invalid.invalidTimeoutSeconds).toBe("abc");
|
|
});
|
|
|
|
it("records invalid --retries values", () => {
|
|
const parsed = parseArgs(["--retries", "0"]);
|
|
expect(parsed.retries).toBeUndefined();
|
|
expect(parsed.invalidRetries).toBe("0");
|
|
});
|
|
});
|
|
|
|
describe("format helpers", () => {
|
|
it("truncates run_commands with commands array", () => {
|
|
const result = formatToolInput("run_commands", {
|
|
commands: [
|
|
"echo hello",
|
|
"npm run very-very-long-command-name-that-will-truncate",
|
|
],
|
|
});
|
|
expect(result).toContain("echo hello");
|
|
expect(result.length).toBeLessThanOrEqual(120);
|
|
});
|
|
|
|
it("truncates run_commands with commands as single string", () => {
|
|
const longCmd = `echo ${"x".repeat(200)}`;
|
|
const result = formatToolInput("run_commands", { commands: longCmd });
|
|
expect(result).toContain("echo");
|
|
expect(result.length).toBeLessThanOrEqual(120);
|
|
});
|
|
|
|
it("truncates run_commands with bare string input", () => {
|
|
const longCmd = `echo ${"x".repeat(200)}`;
|
|
const result = formatToolInput("run_commands", longCmd);
|
|
expect(result).toContain("echo");
|
|
expect(result.length).toBeLessThanOrEqual(120);
|
|
});
|
|
|
|
it("truncates run_commands with bare string array input", () => {
|
|
const result = formatToolInput("run_commands", [
|
|
"echo hello",
|
|
"echo world",
|
|
]);
|
|
expect(result).toContain("echo hello; echo world");
|
|
});
|
|
|
|
it("truncates run_commands with structured command input", () => {
|
|
const result = formatToolInput("run_commands", {
|
|
commands: [{ command: "git", args: ["status", "--short"] }],
|
|
});
|
|
expect(result).toContain("git status --short");
|
|
});
|
|
|
|
it("truncates run_commands with bare structured command", () => {
|
|
const result = formatToolInput("run_commands", {
|
|
command: "git",
|
|
args: ["log", "--oneline"],
|
|
});
|
|
expect(result).toContain("git log --oneline");
|
|
});
|
|
|
|
it("handles structured command with non-array args gracefully", () => {
|
|
const result = formatToolInput("run_commands", {
|
|
commands: [{ command: "git", args: "status" }],
|
|
});
|
|
expect(result).toBe("git");
|
|
});
|
|
|
|
it("formats known tool input payloads with truncation", () => {
|
|
expect(
|
|
formatToolInput("team_run_task", {
|
|
runMode: "sync",
|
|
agentId: "coder",
|
|
task: "implement feature with extensive acceptance criteria and checks",
|
|
}),
|
|
).toContain("sync coder:");
|
|
expect(
|
|
formatToolInput("team_member", {
|
|
action: "spawn",
|
|
agentId: "reviewer",
|
|
rolePrompt: "Review changes and call out risks",
|
|
}),
|
|
).toContain("spawn reviewer:");
|
|
expect(
|
|
formatToolInput("team_task", {
|
|
action: "complete",
|
|
taskId: "task_0012",
|
|
summary: "Done and verified",
|
|
}),
|
|
).toContain("complete task_0012:");
|
|
expect(
|
|
formatToolInput("team_message", {
|
|
action: "send",
|
|
toAgentId: "lead",
|
|
subject: "Status update",
|
|
}),
|
|
).toContain("send lead:");
|
|
});
|
|
|
|
it("formats ask_question as a readable prompt", () => {
|
|
expect(
|
|
formatToolInput("ask_question", {
|
|
question: "How can I best assist you today?",
|
|
options: [
|
|
"Help me understand or analyze code in a repository",
|
|
"Help me create or edit files",
|
|
"Help me run commands or tests",
|
|
],
|
|
}),
|
|
).toBe(
|
|
[
|
|
"The agent is waiting for your input.",
|
|
"How can I best assist you today?",
|
|
"1. Help me understand or analyze code in a repository",
|
|
"2. Help me create or edit files",
|
|
"3. Help me run commands or tests",
|
|
"> Reply with an option number or type your answer.",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
|
|
it("summarizes structured tool outputs", () => {
|
|
expect(formatToolOutput("simple text output")).toBe("simple text output");
|
|
expect(
|
|
formatToolOutput([
|
|
{ result: "first" },
|
|
{ result: "second" },
|
|
{ result: "third" },
|
|
]),
|
|
).toBe("first (+2 more)");
|
|
expect(formatToolOutput(null)).toBe("");
|
|
});
|
|
});
|
|
|
|
describe("hook payload validation and audit logging", () => {
|
|
let tempDir = "";
|
|
|
|
afterEach(() => {
|
|
if (tempDir) {
|
|
rmSync(tempDir, { recursive: true, force: true });
|
|
tempDir = "";
|
|
}
|
|
});
|
|
|
|
it("validates hook payload structure", async () => {
|
|
expect(
|
|
await isCliHookPayload({
|
|
clineVersion: "",
|
|
hookName: "tool_call",
|
|
timestamp: new Date().toISOString(),
|
|
taskId: "conv_1",
|
|
workspaceRoots: [],
|
|
userId: "agent_1",
|
|
agent_id: "agent_1",
|
|
parent_agent_id: null,
|
|
iteration: 1,
|
|
tool_call: {
|
|
id: "call_1",
|
|
name: "read_files",
|
|
input: { file_paths: ["README.md"] },
|
|
},
|
|
}),
|
|
).toBe(true);
|
|
expect(await isCliHookPayload({ hookName: "tool_call" })).toBe(false);
|
|
expect(await isCliHookPayload(null)).toBe(false);
|
|
});
|
|
|
|
it("writes hook audits to global log", async () => {
|
|
tempDir = mkdtempSync(path.join(os.tmpdir(), "cli-helper-audit-"));
|
|
const expectedPath = path.join(tempDir, "logs", "hooks.jsonl");
|
|
const env = captureEnv();
|
|
process.env.CLINE_DATA_DIR = tempDir;
|
|
delete process.env.CLINE_HOOKS_LOG_PATH;
|
|
delete process.env.CLINE_SESSION_ID;
|
|
delete process.env.CLINE_SESSION_DATA_DIR;
|
|
|
|
await appendHookAudit({
|
|
clineVersion: "",
|
|
hookName: "tool_call",
|
|
timestamp: new Date().toISOString(),
|
|
taskId: "conv_1",
|
|
sessionContext: { rootSessionId: "session_from_context" },
|
|
workspaceRoots: [],
|
|
userId: "agent_1",
|
|
iteration: 1,
|
|
agent_id: "agent_1",
|
|
parent_agent_id: null,
|
|
tool_call: {
|
|
id: "call_1",
|
|
name: "read_files",
|
|
input: { file_paths: ["README.md"] },
|
|
},
|
|
});
|
|
restoreEnv(env);
|
|
|
|
expect(existsSync(expectedPath)).toBe(true);
|
|
const content = readFileSync(expectedPath, "utf8");
|
|
expect(content).toContain('"hookName":"tool_call"');
|
|
expect(content).toContain('"agent_id":"agent_1"');
|
|
});
|
|
|
|
it("writes hook audits to CLINE_HOOKS_LOG_PATH when set", async () => {
|
|
tempDir = mkdtempSync(path.join(os.tmpdir(), "cli-helper-env-audit-"));
|
|
const expectedPath = path.join(tempDir, "hooks", "from-env.jsonl");
|
|
const env = captureEnv();
|
|
process.env.CLINE_HOOKS_LOG_PATH = expectedPath;
|
|
delete process.env.CLINE_DATA_DIR;
|
|
delete process.env.CLINE_SESSION_ID;
|
|
delete process.env.CLINE_SESSION_DATA_DIR;
|
|
|
|
await appendHookAudit({
|
|
clineVersion: "",
|
|
hookName: "tool_result",
|
|
timestamp: new Date().toISOString(),
|
|
taskId: "conv_3",
|
|
workspaceRoots: [],
|
|
userId: "agent_3",
|
|
iteration: 1,
|
|
agent_id: "agent_3",
|
|
parent_agent_id: null,
|
|
tool_result: {
|
|
id: "call_3",
|
|
name: "read_files",
|
|
input: { file_paths: ["README.md"] },
|
|
output: "ok",
|
|
durationMs: 5,
|
|
startedAt: new Date("2026-01-01T00:00:00.000Z"),
|
|
endedAt: new Date("2026-01-01T00:00:00.005Z"),
|
|
},
|
|
});
|
|
restoreEnv(env);
|
|
|
|
expect(existsSync(expectedPath)).toBe(true);
|
|
const content = readFileSync(expectedPath, "utf8");
|
|
expect(content).toContain('"hookName":"tool_result"');
|
|
});
|
|
});
|
|
|
|
describe("sandbox environment", () => {
|
|
it("sets sandbox-specific storage paths", () => {
|
|
const root = mkdtempSync(path.join(os.tmpdir(), "cli-helper-sandbox-"));
|
|
const previous = {
|
|
CLINE_SANDBOX: process.env.CLINE_SANDBOX,
|
|
CLINE_SANDBOX_DATA_DIR: process.env.CLINE_SANDBOX_DATA_DIR,
|
|
CLINE_DATA_DIR: process.env.CLINE_DATA_DIR,
|
|
CLINE_DB_DATA_DIR: process.env.CLINE_DB_DATA_DIR,
|
|
CLINE_SESSION_DATA_DIR: process.env.CLINE_SESSION_DATA_DIR,
|
|
CLINE_TEAM_DATA_DIR: process.env.CLINE_TEAM_DATA_DIR,
|
|
CLINE_PROVIDER_SETTINGS_PATH: process.env.CLINE_PROVIDER_SETTINGS_PATH,
|
|
CLINE_HOOKS_LOG_PATH: process.env.CLINE_HOOKS_LOG_PATH,
|
|
};
|
|
try {
|
|
const resolved = configureSandboxEnvironment({
|
|
enabled: true,
|
|
cwd: root,
|
|
explicitDir: "./sandbox-state",
|
|
});
|
|
expect(resolved).toBe(path.join(root, "sandbox-state"));
|
|
expect(process.env.CLINE_SANDBOX).toBe("1");
|
|
expect(process.env.CLINE_SANDBOX_DATA_DIR).toBe(
|
|
path.join(root, "sandbox-state"),
|
|
);
|
|
expect(process.env.CLINE_DATA_DIR).toBe(path.join(root, "sandbox-state"));
|
|
expect(process.env.CLINE_DB_DATA_DIR).toBe(
|
|
path.join(root, "sandbox-state", "db"),
|
|
);
|
|
expect(process.env.CLINE_SESSION_DATA_DIR).toBe(
|
|
path.join(root, "sandbox-state", "sessions"),
|
|
);
|
|
expect(process.env.CLINE_TEAM_DATA_DIR).toBe(
|
|
path.join(root, "sandbox-state", "teams"),
|
|
);
|
|
expect(process.env.CLINE_PROVIDER_SETTINGS_PATH).toBe(
|
|
path.join(root, "sandbox-state", "settings", "providers.json"),
|
|
);
|
|
expect(process.env.CLINE_HOOKS_LOG_PATH).toBe(
|
|
path.join(root, "sandbox-state", "logs", "hooks.jsonl"),
|
|
);
|
|
} finally {
|
|
process.env.CLINE_SANDBOX = previous.CLINE_SANDBOX;
|
|
process.env.CLINE_SANDBOX_DATA_DIR = previous.CLINE_SANDBOX_DATA_DIR;
|
|
process.env.CLINE_DATA_DIR = previous.CLINE_DATA_DIR;
|
|
process.env.CLINE_DB_DATA_DIR = previous.CLINE_DB_DATA_DIR;
|
|
process.env.CLINE_SESSION_DATA_DIR = previous.CLINE_SESSION_DATA_DIR;
|
|
process.env.CLINE_TEAM_DATA_DIR = previous.CLINE_TEAM_DATA_DIR;
|
|
process.env.CLINE_PROVIDER_SETTINGS_PATH =
|
|
previous.CLINE_PROVIDER_SETTINGS_PATH;
|
|
process.env.CLINE_HOOKS_LOG_PATH = previous.CLINE_HOOKS_LOG_PATH;
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|