command-stream 1.11.0

Modern shell command execution library with streaming, async iteration, and event support
Documentation
use command_stream::fy::{translate_source, SourceOptions};

#[test]
fn preserves_literal_braces_in_custom_module_names() {
    let result = translate_source(
        "import x from \"zx\";",
        &SourceOptions {
            from: "javascript".into(),
            module_name: "./{local}".into(),
            ..Default::default()
        },
    )
    .unwrap();
    assert_eq!(result.code, "import x from \"./{local}/zx\";");
    assert!(result.diagnostics.is_empty());
}

#[test]
fn matches_the_shared_default_shell_program_fixture() {
    let source = include_str!("../../fixtures/fy/sample.sh");
    let expected = include_str!("../../fixtures/fy/sample-program.mjs");
    assert_eq!(
        translate_source(source, &SourceOptions::default())
            .unwrap()
            .code,
        expected
    );
}

#[tokio::test]
async fn virtual_fy_is_a_literal_command_name() {
    let result = command_stream::run("$fy --help").await.unwrap();
    assert_eq!(result.code, 0);
    assert!(result.stdout.contains("Usage:"));
}

#[test]
fn native_cli_migrates_javascript_stdin() {
    use std::io::Write;
    use std::process::{Command, Stdio};
    let mut child = Command::new(env!("CARGO_BIN_EXE_command-stream"))
        .args(["fy", "--from", "zx", "-"])
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .unwrap();
    child
        .stdin
        .take()
        .unwrap()
        .write_all(b"import { $ } from 'zx';")
        .unwrap();
    let output = child.wait_with_output().unwrap();
    assert!(
        output.status.success(),
        "{}",
        String::from_utf8_lossy(&output.stderr)
    );
    assert_eq!(
        String::from_utf8(output.stdout).unwrap(),
        "import { $ } from \"command-stream/zx\";"
    );
}

#[test]
fn native_cli_retains_shell_script_basename() {
    let dir = tempfile::tempdir().unwrap();
    let input = dir.path().join("original.sh");
    std::fs::write(&input, "printf '%s' \"$0\"").unwrap();
    let output = std::process::Command::new(env!("CARGO_BIN_EXE_command-stream"))
        .arg("fy")
        .arg(input)
        .output()
        .unwrap();
    assert!(output.status.success());
    assert!(String::from_utf8(output.stdout)
        .unwrap()
        .contains("'-c', source, \"original.sh\""));
}

#[test]
fn leaves_locally_bound_require_and_unrelated_modules_intact() {
    let source = "function local(require) { return require(\"zx\"); }\nimport x from \"toString\"; import y from \"__proto__\";";
    let result = translate_source(
        source,
        &SourceOptions {
            from: "javascript".into(),
            ..Default::default()
        },
    )
    .unwrap();
    assert_eq!(result.code, source);
    assert!(result
        .diagnostics
        .join("\n")
        .contains("locally bound require"));
}

#[test]
fn decodes_escaped_specifiers_and_detects_commonjs_filenames() {
    let source = r"const sh = require('shell\x6as');";
    let result = translate_source(
        source,
        &SourceOptions {
            filename: "source.cjs".into(),
            ..Default::default()
        },
    )
    .unwrap();
    assert!(result.code.contains("require(\"command-stream/shelljs\")"));
    assert!(result.code.contains("export default __fyModule.exports"));
}

#[test]
fn migrates_the_shared_competitor_fixture() {
    let options = SourceOptions {
        from: "javascript".into(),
        ..Default::default()
    };
    let translation =
        translate_source(include_str!("../../fixtures/fy/competitors.js"), &options).unwrap();
    assert_eq!(
        translation.code,
        include_str!("../../fixtures/fy/competitors.mjs")
    );
    assert!(translation.diagnostics.is_empty());
}

#[test]
fn migrates_the_shared_commonjs_fixture() {
    let translation = translate_source(
        include_str!("../../fixtures/fy/commonjs.cjs"),
        &SourceOptions {
            filename: "commonjs.cjs".into(),
            ..Default::default()
        },
    )
    .unwrap();
    assert_eq!(
        translation.code,
        include_str!("../../fixtures/fy/commonjs.mjs")
    );
    assert!(translation.diagnostics.is_empty());
}

#[test]
fn migration_does_not_rewrite_strings_comments_regexes_or_property_calls() {
    let source = "// import x from 'zx'\nconst s = \"require('zx')\";\nconst re = /require('zx')/; obj.require('zx');\nconst t = `${(await import('zx')).VERSION}`;";
    let options = SourceOptions {
        from: "javascript".into(),
        ..Default::default()
    };
    let code = translate_source(source, &options).unwrap().code;
    assert!(code.contains("// import x from 'zx'"));
    assert!(code.contains("/require('zx')/"));
    assert!(code.contains("obj.require('zx')"));
    assert!(code.contains("import(\"command-stream/zx\")"));
}

#[test]
fn shell_programs_load_command_stream_with_use_m_by_default() {
    let result = translate_source(
        "greet() { echo \"$1\"; }; greet hello",
        &SourceOptions::default(),
    )
    .unwrap();
    assert!(result.code.contains("from 'use-m'"));
    assert!(result.code.contains("await use(\"command-stream\")"));
    assert!(result.code.contains("...process.argv.slice(2)"));
    assert!(result.code.contains("process.exitCode = result.code"));
}

#[test]
fn rejects_unknown_languages_and_invalid_javascript() {
    assert!(translate_source(
        "echo hi",
        &SourceOptions {
            from: "unknown".into(),
            ..Default::default()
        }
    )
    .is_err());
    assert!(translate_source(
        "import {",
        &SourceOptions {
            from: "javascript".into(),
            ..Default::default()
        }
    )
    .is_err());
}