feat(ui): Improved project approval settings
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Markdown, type MarkdownTheme } from "@earendil-works/pi-tui";
|
||||
import chalk from "chalk";
|
||||
import { selectConfig } from "./cli/config-selector.ts";
|
||||
import { createProjectTrustContext } from "./cli/project-trust.ts";
|
||||
import {
|
||||
APP_NAME,
|
||||
detectInstallMethod,
|
||||
@@ -12,7 +13,10 @@ import {
|
||||
type SelfUpdateCommand,
|
||||
VERSION,
|
||||
} from "./config.ts";
|
||||
import type { ExtensionFactory } from "./core/extensions/types.ts";
|
||||
import { DefaultPackageManager } from "./core/package-manager.ts";
|
||||
import { type AppMode, resolveProjectTrusted } from "./core/project-trust.ts";
|
||||
import { DefaultResourceLoader } from "./core/resource-loader.ts";
|
||||
import { SettingsManager } from "./core/settings-manager.ts";
|
||||
import { hasProjectTrustInputs, ProjectTrustStore } from "./core/trust-manager.ts";
|
||||
import { spawnProcess } from "./utils/child-process.ts";
|
||||
@@ -425,22 +429,82 @@ function parseProjectTrustOverride(args: readonly string[]): boolean | undefined
|
||||
return trustOverride;
|
||||
}
|
||||
|
||||
function resolveProjectTrusted(cwd: string, agentDir: string, trustOverride: boolean | undefined): boolean {
|
||||
if (trustOverride !== undefined) {
|
||||
return trustOverride;
|
||||
}
|
||||
return !hasProjectTrustInputs(cwd) || new ProjectTrustStore(agentDir).get(cwd) === true;
|
||||
export interface PackageCommandRuntimeOptions {
|
||||
extensionFactories?: ExtensionFactory[];
|
||||
}
|
||||
|
||||
export async function handleConfigCommand(args: string[]): Promise<boolean> {
|
||||
interface CommandSettingsResult {
|
||||
settingsManager: SettingsManager;
|
||||
projectTrustWarnings: string[];
|
||||
}
|
||||
|
||||
function getCommandAppMode(): AppMode {
|
||||
return process.stdin.isTTY && process.stdout.isTTY ? "interactive" : "print";
|
||||
}
|
||||
|
||||
function reportProjectTrustWarnings(warnings: readonly string[]): void {
|
||||
for (const warning of warnings) {
|
||||
console.error(chalk.yellow(`Warning: ${warning}`));
|
||||
}
|
||||
}
|
||||
|
||||
async function createCommandSettingsManager(options: {
|
||||
cwd: string;
|
||||
agentDir: string;
|
||||
projectTrustOverride?: boolean;
|
||||
extensionFactories?: ExtensionFactory[];
|
||||
}): Promise<CommandSettingsResult> {
|
||||
const settingsManager = SettingsManager.create(options.cwd, options.agentDir, { projectTrusted: false });
|
||||
const projectTrustWarnings: string[] = [];
|
||||
const appMode = getCommandAppMode();
|
||||
const extensionsResult =
|
||||
options.projectTrustOverride === undefined && hasProjectTrustInputs(options.cwd)
|
||||
? await new DefaultResourceLoader({
|
||||
cwd: options.cwd,
|
||||
agentDir: options.agentDir,
|
||||
settingsManager,
|
||||
extensionFactories: options.extensionFactories,
|
||||
}).loadProjectTrustExtensions()
|
||||
: undefined;
|
||||
for (const error of extensionsResult?.errors ?? []) {
|
||||
projectTrustWarnings.push(`Failed to load extension "${error.path}": ${error.error}`);
|
||||
}
|
||||
|
||||
const projectTrusted = await resolveProjectTrusted({
|
||||
cwd: options.cwd,
|
||||
trustStore: new ProjectTrustStore(options.agentDir),
|
||||
trustOverride: options.projectTrustOverride,
|
||||
defaultProjectTrust: settingsManager.getDefaultProjectTrust(),
|
||||
extensionsResult,
|
||||
projectTrustContext: createProjectTrustContext({
|
||||
cwd: options.cwd,
|
||||
mode: appMode,
|
||||
settingsManager,
|
||||
hasUI: appMode === "interactive",
|
||||
}),
|
||||
onExtensionError: (message) => projectTrustWarnings.push(message),
|
||||
});
|
||||
settingsManager.setProjectTrusted(projectTrusted);
|
||||
return { settingsManager, projectTrustWarnings };
|
||||
}
|
||||
|
||||
export async function handleConfigCommand(
|
||||
args: string[],
|
||||
runtimeOptions: PackageCommandRuntimeOptions = {},
|
||||
): Promise<boolean> {
|
||||
if (args[0] !== "config") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const cwd = process.cwd();
|
||||
const agentDir = getAgentDir();
|
||||
const projectTrusted = parseProjectTrustOverride(args) ?? true;
|
||||
const settingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted });
|
||||
const { settingsManager, projectTrustWarnings } = await createCommandSettingsManager({
|
||||
cwd,
|
||||
agentDir,
|
||||
projectTrustOverride: parseProjectTrustOverride(args),
|
||||
extensionFactories: runtimeOptions.extensionFactories,
|
||||
});
|
||||
reportProjectTrustWarnings(projectTrustWarnings);
|
||||
reportSettingsErrors(settingsManager, "config command");
|
||||
const packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });
|
||||
const resolvedPaths = await packageManager.resolve();
|
||||
@@ -455,7 +519,10 @@ export async function handleConfigCommand(args: string[]): Promise<boolean> {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
export async function handlePackageCommand(args: string[]): Promise<boolean> {
|
||||
export async function handlePackageCommand(
|
||||
args: string[],
|
||||
runtimeOptions: PackageCommandRuntimeOptions = {},
|
||||
): Promise<boolean> {
|
||||
const options = parsePackageCommand(args);
|
||||
if (!options) {
|
||||
return false;
|
||||
@@ -505,13 +572,18 @@ export async function handlePackageCommand(args: string[]): Promise<boolean> {
|
||||
const cwd = process.cwd();
|
||||
const agentDir = getAgentDir();
|
||||
const writesProjectPackageConfig = (options.command === "install" || options.command === "remove") && options.local;
|
||||
const projectTrusted = resolveProjectTrusted(cwd, agentDir, options.projectTrustOverride);
|
||||
if (!projectTrusted && writesProjectPackageConfig) {
|
||||
const { settingsManager, projectTrustWarnings } = await createCommandSettingsManager({
|
||||
cwd,
|
||||
agentDir,
|
||||
projectTrustOverride: options.projectTrustOverride,
|
||||
extensionFactories: runtimeOptions.extensionFactories,
|
||||
});
|
||||
reportProjectTrustWarnings(projectTrustWarnings);
|
||||
if (!settingsManager.isProjectTrusted() && writesProjectPackageConfig) {
|
||||
console.error(chalk.red("Project is not trusted. Use --approve to modify local package config."));
|
||||
process.exitCode = 1;
|
||||
return true;
|
||||
}
|
||||
const settingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted });
|
||||
reportSettingsErrors(settingsManager, "package command");
|
||||
const selfUpdateNpmCommand = settingsManager.getGlobalSettings().npmCommand;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user