Browse Source

Added json option to env command (#800)

Co-authored-by: Gal Schlezinger <gal@spitfire.co.il>
remotes/origin/clean-multishell-on-shell-exit
Ezekiel Warren 2 years ago committed by GitHub
parent
commit
405b987f34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      .changeset/chilly-apes-travel.md
  2. 34
      .github/workflows/rust.yml
  3. 3
      docs/commands.md
  4. 18
      e2e/__snapshots__/env.test.ts.snap
  5. 34
      e2e/env.test.ts
  6. 9
      e2e/shellcode/script.ts
  7. 13
      e2e/shellcode/shells/index.ts
  8. 5
      e2e/shellcode/shells/redirect-output.ts
  9. 2
      e2e/shellcode/shells/types.ts
  10. 2
      e2e/system-node.test.ts
  11. 76
      src/commands/env.rs

5
.changeset/chilly-apes-travel.md

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
---
"fnm": minor
---
Add `--json` to `fnm env` to output the env vars as JSON

34
.github/workflows/rust.yml

@ -159,6 +159,40 @@ jobs: @@ -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/action-setup@v2.2.2
# 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]

3
docs/commands.md

@ -325,6 +325,9 @@ OPTIONS: @@ -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

18
e2e/__snapshots__/env.test.ts.snap

@ -0,0 +1,18 @@ @@ -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
e2e/env.test.ts

@ -0,0 +1,34 @@ @@ -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
e2e/shellcode/script.ts

@ -40,7 +40,10 @@ class Script { @@ -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)
}
@ -105,8 +108,8 @@ function streamOutputsAndBuffer(child: execa.ExecaChildProcess) { @@ -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) => {

13
e2e/shellcode/shells/index.ts

@ -34,6 +34,7 @@ export const Zsh = { @@ -34,6 +34,7 @@ export const Zsh = {
}),
...cmdEnv.bash,
...cmdCall.all,
...redirectOutput.bash,
...cmdExpectCommandOutput.bash,
...cmdHasOutputContains.bash,
...cmdInSubShell.zsh,
@ -58,14 +59,8 @@ export const Fish = { @@ -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"],
@ -74,6 +69,7 @@ export const PowerShell = { @@ -74,6 +69,7 @@ export const PowerShell = {
}),
...cmdEnv.powershell,
...cmdCall.all,
...redirectOutput.powershell,
...cmdExpectCommandOutput.powershell,
...cmdHasOutputContains.powershell,
...cmdInSubShell.powershell,
@ -97,4 +93,5 @@ export const WinCmd = { @@ -97,4 +93,5 @@ export const WinCmd = {
...cmdEnv.wincmd,
...cmdCall.all,
...cmdExpectCommandOutput.wincmd,
...redirectOutput.bash,
}

5
e2e/shellcode/shells/redirect-output.ts

@ -7,7 +7,10 @@ export type HasRedirectOutput = { @@ -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
e2e/shellcode/shells/types.ts

@ -5,7 +5,7 @@ export type Shell = { @@ -5,7 +5,7 @@ export type Shell = {
name(): string
launchArgs(): string[]
dieOnErrors?(): string
forceFile?: true
forceFile?: true | string
}
export type ScriptLine = string

2
e2e/system-node.test.ts

@ -18,7 +18,7 @@ for (const shell of [Bash, Fish, PowerShell, WinCmd, Zsh]) { @@ -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

76
src/commands/env.rs

@ -6,6 +6,7 @@ use crate::outln; @@ -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;
@ -15,6 +16,9 @@ pub struct Env { @@ -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,
@ -59,50 +63,60 @@ impl Command for Env { @@ -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(())
}
}

Loading…
Cancel
Save