fix: improve trans script migration hint

This commit is contained in:
Codex
2026-06-20 07:23:11 +00:00
parent d05b963064
commit 4d4feacb31
7 changed files with 133 additions and 13 deletions
+66 -10
View File
@@ -118,6 +118,36 @@ export interface SshRuntimeTimeoutHint {
note: string;
}
export interface RemovedShellAliasReplacementExamples {
posixSh: string;
bash: string;
templatePosixSh: string;
templateBash: string;
}
export class SshRemovedShellAliasError extends Error {
code = "ssh-removed-shell-alias";
level = "error";
entrypoint: string;
route: string;
operation: string;
replacementExamples: RemovedShellAliasReplacementExamples;
migrationHint: string;
note = "`sh` runs POSIX /bin/sh; `bash` is only for Bash-specific syntax. The removed alias intentionally no longer chooses a hidden shell dialect.";
constructor(operation: string, route: string, operationArgs: string[]) {
const entrypoint = sshDisplayEntrypoint();
const replacementExamples = removedShellAliasReplacementExamples(entrypoint, route, operationArgs);
super(`${entrypoint} ${route} ${operation} operation has been removed because it hides the shell dialect; use explicit sh for POSIX /bin/sh or bash for Bash syntax`);
this.name = "SshRemovedShellAliasError";
this.entrypoint = entrypoint;
this.route = route;
this.operation = operation;
this.replacementExamples = replacementExamples;
this.migrationHint = `Rewrite as ${replacementExamples.posixSh} for POSIX shell, or ${replacementExamples.bash} for Bash syntax.`;
}
}
export interface SshStdoutTruncationHint {
code: "ssh-stdout-truncated";
level: "warning";
@@ -920,7 +950,7 @@ export function isSshSkillDiscoveryArgs(args: string[]): boolean {
return subcommand === "skills" || subcommand === "skill-discover" || subcommand === "discover-skills" || (subcommand === "skill" && args[1] === "discover");
}
export function parseSshArgs(args: string[]): ParsedSshArgs {
export function parseSshArgs(args: string[], routeRaw = "<route>"): ParsedSshArgs {
const subcommand = args[0] ?? "";
if (isSshFileTransferOperation(args)) {
return { remoteCommand: null, requiresStdin: false, invocationKind: "helper" };
@@ -949,7 +979,7 @@ export function parseSshArgs(args: string[]): ParsedSshArgs {
return { remoteCommand: null, requiresStdin: true, invocationKind: "helper" };
}
if (subcommand === "script" || subcommand === "shell") {
throw removedShellAliasError(subcommand);
throw removedShellAliasError(subcommand, routeRaw, args.slice(1));
}
if (subcommand === "sh" || subcommand === "bash") {
return buildShellCommand(args.slice(1), subcommand, `ssh ${subcommand}`);
@@ -1035,7 +1065,7 @@ export function parseSshInvocation(target: string, args: string[]): ParsedSshInv
if ((operationArgs[0] ?? "") === "k3s") {
throw new Error(`ssh k3s shorthand is unsupported; use route syntax instead: trans ${route.providerId}:k3s ${operationArgs.slice(1).join(" ")}`.trim());
}
return { providerId: route.providerId, route, parsed: parseSshArgs(operationArgs) };
return { providerId: route.providerId, route, parsed: parseSshArgs(operationArgs, route.raw) };
}
export function parseSshRoute(target: string): ParsedSshRoute {
@@ -1669,7 +1699,7 @@ function parseK3sControlPlaneOperation(route: ParsedSshRoute, args: string[]): P
throw new Error("remote patch entrypoints are `apply-patch` for the default v2 engine and `apply-patch-v1` for the legacy helper");
}
if (operation === "script" || operation === "shell") {
throw removedShellAliasError(operation);
throw removedShellAliasError(operation, route.raw, args.slice(1));
}
if (operation === "sh" || operation === "bash") {
return buildK3sScriptOperation(args.slice(1), operation, `ssh ${route.providerId}:k3s ${operation}`);
@@ -1678,7 +1708,7 @@ function parseK3sControlPlaneOperation(route: ParsedSshRoute, args: string[]): P
if (args.length > 1) throw new Error(`ssh ${route.providerId}:k3s guard does not accept extra arguments`);
return { remoteCommand: buildK3sGuardCommand(route.providerId), requiresStdin: false, invocationKind: "helper" };
}
return { remoteCommand: buildK3sCommand(route.providerId, args), requiresStdin: false, invocationKind: "helper" };
return { remoteCommand: buildK3sCommand(route.providerId, route.raw, args), requiresStdin: false, invocationKind: "helper" };
}
function parseK3sTargetOperation(route: ParsedSshRoute, args: string[]): ParsedSshArgs {
@@ -1706,7 +1736,7 @@ function parseK3sTargetOperation(route: ParsedSshRoute, args: string[]): ParsedS
throw new Error("remote patch entrypoints are `apply-patch` for the default v2 engine and `apply-patch-v1` for the legacy helper");
}
if (operation === "script" || operation === "shell") {
throw removedShellAliasError(operation);
throw removedShellAliasError(operation, route.raw, operationArgs);
}
if (operation === "sh" || operation === "bash") {
return buildK3sScriptOperation([...targetArgs, ...operationArgs], operation, `ssh ${route.raw} ${operation}`);
@@ -1831,7 +1861,7 @@ function effectiveApplyPatchV2Invocation(invocation: ParsedSshInvocation, argv:
};
}
function buildK3sCommand(providerId: string, args: string[]): string {
function buildK3sCommand(providerId: string, routeRaw: string, args: string[]): string {
const action = args[0] ?? "";
if (action.length === 0 || action === "--help" || action === "-h" || action === "help") {
throw new Error("ssh k3s requires a subcommand: guard, kubectl, get, describe, logs or exec");
@@ -1839,7 +1869,7 @@ function buildK3sCommand(providerId: string, args: string[]): string {
if (action === "guard") return buildK3sGuardCommand(providerId);
if (action === "exec") return buildK3sExecCommand(args.slice(1));
if (action === "script" || action === "shell") {
throw removedShellAliasError(action);
throw removedShellAliasError(action, routeRaw, args.slice(1));
}
if (action === "sh" || action === "bash") {
const parsed = buildK3sScriptOperation(args.slice(1), action, `ssh k3s ${action}`);
@@ -1920,8 +1950,34 @@ function buildK3sExecCommand(args: string[]): string {
return shellArgv(["env", `KUBECONFIG=${nativeK3sKubeconfig}`, "kubectl", ...kubectlArgs]);
}
function removedShellAliasError(operation: string): Error {
return new Error(`ssh ${operation} operation has been removed because it hides the shell dialect; use explicit \`sh\` for POSIX /bin/sh or \`bash\` for Bash syntax`);
function removedShellAliasError(operation: string, routeRaw: string, operationArgs: string[]): Error {
return new SshRemovedShellAliasError(operation, routeRaw, operationArgs);
}
function sshDisplayEntrypoint(): string {
const raw = process.env.UNIDESK_SSH_ENTRYPOINT?.trim();
return raw === "trans" || raw === "tran" || raw === "ssh" ? raw : "ssh";
}
function removedShellAliasReplacementExamples(entrypoint: string, route: string, operationArgs: string[]): RemovedShellAliasReplacementExamples {
const command = removedShellAliasInlineCommand(operationArgs);
const commandText = command === null ? "<command>" : command;
return {
posixSh: `${entrypoint} ${route} sh -- ${shellQuote(commandText)}`,
bash: `${entrypoint} ${route} bash -- ${shellQuote(commandText)}`,
templatePosixSh: `${entrypoint} ${route} sh -- '<command>'`,
templateBash: `${entrypoint} ${route} bash -- '<command>'`,
};
}
function removedShellAliasInlineCommand(operationArgs: string[]): string | null {
if (operationArgs.length === 0) return null;
const separatorIndex = operationArgs.indexOf("--");
if (separatorIndex >= 0) {
const command = operationArgs.slice(separatorIndex + 1);
return command.length === 1 ? command[0] ?? null : null;
}
return operationArgs.length === 1 && !(operationArgs[0] ?? "").startsWith("-") ? operationArgs[0] ?? null : null;
}
function buildK3sScriptOperation(args: string[], shell: "sh" | "bash", commandName: string): ParsedSshArgs {