Skip to content

Commit

Permalink
Added json option to env command (Schniz#800)
Browse files Browse the repository at this point in the history
Co-authored-by: Gal Schlezinger <[email protected]>
  • Loading branch information
2 people authored and nzhl committed Nov 18, 2022
1 parent 65e5188 commit 38d4804
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 45 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilly-apes-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"fnm": minor
---

Add `--json` to `fnm env` to output the env vars as JSON
34 changes: 34 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,40 @@ jobs:
FNM_TARGET_NAME: "release"
FORCE_COLOR: "1"

# e2e_windows_debug:
# runs-on: windows-latest
# name: "e2e/windows/debug"
# environment: Debug
# needs: [e2e_windows]
# if: contains(join(needs.*.result, ','), 'failure')
# steps:
# - uses: actions/checkout@v3
# - uses: actions/download-artifact@v3
# with:
# name: fnm-windows
# path: target/release
# - uses: pnpm/[email protected]
# with:
# run_install: false
# - uses: actions/setup-node@v3
# with:
# node-version: 16.x
# cache: 'pnpm'
# - name: Get pnpm store directory
# id: pnpm-cache
# run: |
# echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
# - uses: actions/cache@v3
# name: Setup pnpm cache
# with:
# path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
# key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
# restore-keys: |
# ${{ runner.os }}-pnpm-store-
# - run: pnpm install
# - name: 🐛 Debug Build
# uses: mxschmitt/action-tmate@v3

e2e_linux:
runs-on: ubuntu-latest
needs: [build_static_linux_binary]
Expand Down
3 changes: 3 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,9 @@ OPTIONS:
-h, --help
Print help information
--json
Print JSON instead of shell commands
--log-level <LOG_LEVEL>
The log level of fnm commands
Expand Down
18 changes: 18 additions & 0 deletions e2e/__snapshots__/env.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Bash outputs json: Bash 1`] = `
"set -e
fnm env --json > file.json"
`;

exports[`Fish outputs json: Fish 1`] = `"fnm env --json > file.json"`;

exports[`PowerShell outputs json: PowerShell 1`] = `
"$ErrorActionPreference = "Stop"
fnm env --json | Out-File file.json -Encoding UTF8"
`;

exports[`Zsh outputs json: Zsh 1`] = `
"set -e
fnm env --json > file.json"
`;
34 changes: 34 additions & 0 deletions e2e/env.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { readFile } from "node:fs/promises"
import { join } from "node:path"
import { script } from "./shellcode/script"
import { Bash, Fish, PowerShell, WinCmd, Zsh } from "./shellcode/shells"
import testCwd from "./shellcode/test-cwd"
import describe from "./describe"

for (const shell of [Bash, Zsh, Fish, PowerShell, WinCmd]) {
describe(shell, () => {
test(`outputs json`, async () => {
const filename = `file.json`
await script(shell)
.then(
shell.redirectOutput(shell.call("fnm", ["env", "--json"]), {
output: filename,
})
)
.takeSnapshot(shell)
.execute(shell)

if (shell.currentlySupported()) {
const file = await readFile(join(testCwd(), filename), "utf8")
expect(JSON.parse(file)).toEqual({
FNM_ARCH: expect.any(String),
FNM_DIR: expect.any(String),
FNM_LOGLEVEL: "info",
FNM_MULTISHELL_PATH: expect.any(String),
FNM_NODE_DIST_MIRROR: expect.any(String),
FNM_VERSION_FILE_STRATEGY: "local",
})
}
})
})
}
9 changes: 6 additions & 3 deletions e2e/shellcode/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ class Script {
const args = [...shell.launchArgs()]

if (shell.forceFile) {
const filename = join(testTmpDir(), "script")
let filename = join(testTmpDir(), "script")
if (typeof shell.forceFile === "string") {
filename = filename + shell.forceFile
}
await writeFile(filename, [...this.lines, "exit 0"].join("\n"))
args.push(filename)
}
Expand Down Expand Up @@ -105,8 +108,8 @@ function streamOutputsAndBuffer(child: execa.ExecaChildProcess) {
const testName = expect.getState().currentTestName ?? "unknown"
const testPath = expect.getState().testPath ?? "unknown"

const stdoutPrefix = chalk.yellow.dim(`[stdout] ${testPath}/${testName}: `)
const stderrPrefix = chalk.red.dim(`[stderr] ${testPath}/${testName}: `)
const stdoutPrefix = chalk.cyan.dim(`[stdout] ${testPath}/${testName}: `)
const stderrPrefix = chalk.magenta.dim(`[stderr] ${testPath}/${testName}: `)

if (child.stdout) {
child.stdout.on("data", (data) => {
Expand Down
13 changes: 5 additions & 8 deletions e2e/shellcode/shells/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const Zsh = {
}),
...cmdEnv.bash,
...cmdCall.all,
...redirectOutput.bash,
...cmdExpectCommandOutput.bash,
...cmdHasOutputContains.bash,
...cmdInSubShell.zsh,
Expand All @@ -58,14 +59,8 @@ export const Fish = {

export const PowerShell = {
...define<Shell>({
binaryName: () => {
if (process.platform === "win32") {
return "powershell.exe"
} else {
return "pwsh"
}
},
forceFile: true,
binaryName: () => "pwsh",
forceFile: ".ps1",
currentlySupported: () => true,
name: () => "PowerShell",
launchArgs: () => ["-NoProfile"],
Expand All @@ -74,6 +69,7 @@ export const PowerShell = {
}),
...cmdEnv.powershell,
...cmdCall.all,
...redirectOutput.powershell,
...cmdExpectCommandOutput.powershell,
...cmdHasOutputContains.powershell,
...cmdInSubShell.powershell,
Expand All @@ -97,4 +93,5 @@ export const WinCmd = {
...cmdEnv.wincmd,
...cmdCall.all,
...cmdExpectCommandOutput.wincmd,
...redirectOutput.bash,
}
5 changes: 4 additions & 1 deletion e2e/shellcode/shells/redirect-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ export type HasRedirectOutput = {

export const redirectOutput = {
bash: define<HasRedirectOutput>({
redirectOutput: (childCommand, opts) => `${childCommand} > ${opts.output}`,
}),
powershell: define<HasRedirectOutput>({
redirectOutput: (childCommand, opts) =>
`(${childCommand}) > ${opts.output}`,
`${childCommand} | Out-File ${opts.output} -Encoding UTF8`,
}),
}
2 changes: 1 addition & 1 deletion e2e/shellcode/shells/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type Shell = {
name(): string
launchArgs(): string[]
dieOnErrors?(): string
forceFile?: true
forceFile?: true | string
}

export type ScriptLine = string
Expand Down
2 changes: 1 addition & 1 deletion e2e/system-node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ for (const shell of [Bash, Fish, PowerShell, WinCmd, Zsh]) {
process.platform === "win32" &&
[WinCmd, PowerShell].includes(shell)
) {
await fs.writeFile(customNode + ".cmd", '@echo "custom node"')
await fs.writeFile(customNode + ".cmd", "@echo custom")
} else {
await fs.writeFile(customNode, `#!/bin/bash\n\necho "custom"\n`)
// set executable
Expand Down
76 changes: 45 additions & 31 deletions src/commands/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::outln;
use crate::path_ext::PathExt;
use crate::shell::{infer_shell, Shell, AVAILABLE_SHELLS};
use colored::Colorize;
use std::collections::HashMap;
use std::fmt::Debug;
use thiserror::Error;

Expand All @@ -15,6 +16,9 @@ pub struct Env {
#[clap(long)]
#[clap(possible_values = AVAILABLE_SHELLS)]
shell: Option<Box<dyn Shell>>,
/// Print JSON instead of shell commands.
#[clap(long, conflicts_with = "shell")]
json: bool,
/// Deprecated. This is the default now.
#[clap(long, hide = true)]
multi: bool,
Expand Down Expand Up @@ -59,50 +63,60 @@ impl Command for Env {
);
}

let shell: Box<dyn Shell> = self
.shell
.or_else(infer_shell)
.ok_or(Error::CantInferShell)?;
let multishell_path = make_symlink(config)?;
let binary_path = if cfg!(windows) {
multishell_path.clone()
} else {
multishell_path.join("bin")
};
println!("{}", shell.path(&binary_path)?);
println!(
"{}",
shell.set_env_var("FNM_MULTISHELL_PATH", multishell_path.to_str().unwrap())
);
println!(
"{}",
shell.set_env_var(

let env_vars = HashMap::from([
(
"FNM_MULTISHELL_PATH",
multishell_path.to_str().unwrap().to_owned(),
),
(
"FNM_VERSION_FILE_STRATEGY",
config.version_file_strategy().as_str()
)
);
println!(
"{}",
shell.set_env_var("FNM_DIR", config.base_dir_with_default().to_str().unwrap())
);
println!(
"{}",
shell.set_env_var("FNM_LOGLEVEL", config.log_level().clone().into())
);
println!(
"{}",
shell.set_env_var("FNM_NODE_DIST_MIRROR", config.node_dist_mirror.as_str())
);
println!(
"{}",
shell.set_env_var("FNM_ARCH", &config.arch.to_string())
);
config.version_file_strategy().as_str().to_owned(),
),
(
"FNM_DIR",
config.base_dir_with_default().to_str().unwrap().to_owned(),
),
(
"FNM_LOGLEVEL",
<&'static str>::from(config.log_level().clone()).to_owned(),
),
(
"FNM_NODE_DIST_MIRROR",
config.node_dist_mirror.as_str().to_owned(),
),
("FNM_ARCH", config.arch.to_string()),
]);

if self.json {
println!("{}", serde_json::to_string(&env_vars).unwrap());
return Ok(());
}

let shell: Box<dyn Shell> = self
.shell
.or_else(infer_shell)
.ok_or(Error::CantInferShell)?;

println!("{}", shell.path(&binary_path)?);

for (name, value) in &env_vars {
println!("{}", shell.set_env_var(name, value));
}

if self.use_on_cd {
println!("{}", shell.use_on_cd(config)?);
}
if let Some(v) = shell.rehash() {
println!("{}", v);
}

Ok(())
}
}
Expand Down

0 comments on commit 38d4804

Please sign in to comment.