Merge pull request #4143 from code-yeongyu/fix/shell-env-csh-support

fix(shell-env): add csh shell support to detection and env prefix
This commit is contained in:
YeonGyu-Kim
2026-05-18 14:11:35 +09:00
committed by GitHub
3 changed files with 130 additions and 4 deletions
@@ -231,6 +231,18 @@ describe("#given buildShellAwareGitPrefix", () => {
})
})
describe("#when shell type is csh", () => {
it("#then returns csh setenv syntax", () => {
const result = buildShellAwareGitPrefix("GIT_MASTER=1", "csh")
expect(result).toBe("setenv GIT_MASTER 1;")
})
it("#then handles multiple env vars", () => {
const result = buildShellAwareGitPrefix("CI=true GIT_MASTER=1", "csh")
expect(result).toBe("setenv CI true; setenv GIT_MASTER 1;")
})
})
describe("#when prefix is empty", () => {
it("#then returns empty string", () => {
const result = buildShellAwareGitPrefix("", "powershell")
@@ -319,4 +331,61 @@ describe("#given PowerShell shell detection in injectGitMasterConfig", () => {
expect(result).not.toContain("$env:")
})
})
describe("#when shell is csh (SHELL set to /bin/csh)", () => {
it("#then emits setenv prefix syntax in csh code block", () => {
process.env.SHELL = "/bin/csh"
delete process.env.PSModulePath
delete process.env.MSYSTEM
Object.defineProperty(process, "platform", { value: "linux" })
const result = injectGitMasterConfig(SAMPLE_TEMPLATE, {
commit_footer: false,
include_co_authored_by: false,
git_env_prefix: "GIT_MASTER=1",
})
expect(result).toContain("setenv GIT_MASTER 1;")
expect(result).toContain("```csh")
expect(result).not.toContain("```bash\nsetenv")
})
it("#then does NOT prefix bash code blocks with setenv syntax", () => {
process.env.SHELL = "/bin/csh"
delete process.env.PSModulePath
delete process.env.MSYSTEM
Object.defineProperty(process, "platform", { value: "linux" })
const result = injectGitMasterConfig(SAMPLE_TEMPLATE, {
commit_footer: false,
include_co_authored_by: false,
git_env_prefix: "GIT_MASTER=1",
})
const bashBlockMatch = result.match(/```bash\r?\n([\s\S]*?)```/g)
if (bashBlockMatch) {
for (const block of bashBlockMatch) {
expect(block).not.toContain("setenv")
}
}
})
})
describe("#when shell is tcsh (SHELL set to /usr/local/bin/tcsh)", () => {
it("#then emits setenv prefix syntax in csh code block", () => {
process.env.SHELL = "/usr/local/bin/tcsh"
delete process.env.PSModulePath
delete process.env.MSYSTEM
Object.defineProperty(process, "platform", { value: "darwin" })
const result = injectGitMasterConfig(SAMPLE_TEMPLATE, {
commit_footer: false,
include_co_authored_by: false,
git_env_prefix: "GIT_MASTER=1",
})
expect(result).toContain("setenv GIT_MASTER 1;")
expect(result).toContain("```csh")
})
})
})
@@ -26,15 +26,17 @@ export function parseBashEnvPrefix(prefix: string): Record<string, string> {
/**
* Build the shell-aware command prefix for git commands.
* Uses the shared shell detection and env prefix builder to emit correct syntax
* for PowerShell ($env:VAR='value';), cmd (set VAR="value" &&), or unix (VAR=value).
* for PowerShell ($env:VAR='value';), cmd (set VAR="value" &&),
* csh (setenv VAR value;), or unix (VAR=value).
*
* For unix shells, we use the inline VAR=value prefix style (not export) to match
* the original behavior where the env var applies only to the immediately following command.
* For csh/tcsh, we use setenv syntax since csh does not support inline VAR=value.
*/
export function buildShellAwareGitPrefix(bashPrefix: string, shellType?: ShellType): string {
if (!bashPrefix) return ""
const resolvedShellType = shellType ?? detectShellType()
if (resolvedShellType === "unix" || resolvedShellType === "csh") {
if (resolvedShellType === "unix") {
return bashPrefix
}
const envRecord = parseBashEnvPrefix(bashPrefix)
@@ -48,8 +50,8 @@ export function injectGitMasterConfig(template: string, config?: GitMasterConfig
const shellType = detectShellType()
const shellPrefix = gitEnvPrefix ? buildShellAwareGitPrefix(gitEnvPrefix, shellType) : ""
const codeBlockLang = shellType === "powershell" ? "pwsh" : "bash"
const skipBashBlockPrefixing = shellType === "powershell" || shellType === "cmd"
const codeBlockLang = shellType === "powershell" ? "pwsh" : shellType === "csh" ? "csh" : "bash"
const skipBashBlockPrefixing = shellType === "powershell" || shellType === "cmd" || shellType === "csh"
let result = gitEnvPrefix ? injectGitEnvPrefix(template, shellPrefix, codeBlockLang) : template
+55
View File
@@ -120,6 +120,39 @@ describe("shell-env", () => {
expect(result).toBe("unix")
})
test("#given SHELL set to /bin/csh #when detectShellType is called #then returns csh", () => {
delete process.env.PSModulePath
delete process.env.MSYSTEM
process.env.SHELL = "/bin/csh"
Object.defineProperty(process, "platform", { value: "linux" })
const result = detectShellType()
expect(result).toBe("csh")
})
test("#given SHELL set to /bin/tcsh #when detectShellType is called #then returns csh", () => {
delete process.env.PSModulePath
delete process.env.MSYSTEM
process.env.SHELL = "/bin/tcsh"
Object.defineProperty(process, "platform", { value: "linux" })
const result = detectShellType()
expect(result).toBe("csh")
})
test("#given SHELL set to /usr/local/bin/tcsh #when detectShellType is called #then returns csh", () => {
delete process.env.PSModulePath
delete process.env.MSYSTEM
process.env.SHELL = "/usr/local/bin/tcsh"
Object.defineProperty(process, "platform", { value: "darwin" })
const result = detectShellType()
expect(result).toBe("csh")
})
})
describe("shellEscape", () => {
@@ -311,5 +344,27 @@ describe("shell-env", () => {
expect(result).toBe("")
})
})
describe("csh", () => {
test("#given single environment variable #when buildEnvPrefix is called with csh #then builds setenv command", () => {
const result = buildEnvPrefix({ VAR: "value" }, "csh")
expect(result).toBe("setenv VAR value;")
})
test("#given multiple environment variables #when buildEnvPrefix is called with csh #then builds separate setenv commands", () => {
const result = buildEnvPrefix({ VAR1: "val1", VAR2: "val2" }, "csh")
expect(result).toBe("setenv VAR1 val1; setenv VAR2 val2;")
})
test("#given env var with spaces #when buildEnvPrefix is called with csh #then escapes value with single quotes", () => {
const result = buildEnvPrefix({ MSG: "has spaces" }, "csh")
expect(result).toBe("setenv MSG 'has spaces';")
})
test("#given empty env object #when buildEnvPrefix is called with csh #then returns empty string", () => {
const result = buildEnvPrefix({}, "csh")
expect(result).toBe("")
})
})
})
})