Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 50 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,45 @@ jobs:
permissions:
contents: read
id-token: write # Required for npm provenance (OIDC)
outputs:
tag: ${{ steps.tag.outputs.TAG }}
steps:
- name: Resolve tag/ref
id: tag
shell: bash
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "TAG=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
echo "REF=refs/tags/${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
else
echo "TAG=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
echo "REF=${GITHUB_REF}" >> "$GITHUB_OUTPUT"
fi

- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ steps.tag.outputs.REF }}

- name: Extract version from tag
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/npm/ironposh-web/v}" >> $GITHUB_OUTPUT
shell: bash
run: |
TAG="${{ steps.tag.outputs.TAG }}"
echo "VERSION=${TAG#npm/ironposh-web/v}" >> "$GITHUB_OUTPUT"

- name: Validate Cargo.toml version matches tag
shell: bash
run: |
VERSION="${{ steps.version.outputs.VERSION }}"
CARGO_VERSION="$(python -c 'import tomllib; print(tomllib.load(open(\"crates/ironposh-web/Cargo.toml\",\"rb\"))[\"package\"][\"version\"])')"

if [ "$CARGO_VERSION" != "$VERSION" ]; then
echo "Version mismatch:"
echo "- tag: $VERSION"
echo "- Cargo.toml: $CARGO_VERSION"
exit 1
fi

- name: Install Rust
run: |
Expand Down Expand Up @@ -90,9 +123,24 @@ jobs:
permissions:
contents: write
steps:
- name: Resolve tag/ref
id: tag
shell: bash
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "TAG=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
echo "REF=refs/tags/${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
else
echo "TAG=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
echo "REF=${GITHUB_REF}" >> "$GITHUB_OUTPUT"
fi

- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ steps.tag.outputs.REF }}

- name: Create Release
run: gh release create "${{ github.ref_name }}" --generate-notes --title "${{ github.ref_name }}"
run: gh release create "${{ needs.release-npm-ironposh-web.outputs.tag }}" --generate-notes --title "${{ needs.release-npm-ironposh-web.outputs.tag }}"
env:
GH_TOKEN: ${{ github.token }}
12 changes: 8 additions & 4 deletions crates/ironposh-client-tokio/src/repl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ async fn tab_complete_line(
let escaped = escape_ps_single_quoted(line);
let script = format!("TabExpansion2 -inputScript '{escaped}' -cursorColumn {cursor_utf16}");

info!(cursor_utf16, line_len = line.len(), "tab completion request");
info!(
cursor_utf16,
line_len = line.len(),
"tab completion request"
);

let stream = client.send_script_raw(script).await?;
let mut stream = stream.boxed();
Expand All @@ -97,16 +101,16 @@ async fn tab_complete_line(
warn!(error = %error_record.render_concise(), "tab completion error record");
}
UserEvent::PipelineFinished { .. } => break,
_ => {}
UserEvent::PipelineCreated { .. } => {}
}
}

let Some(ps_value) = output else {
return Ok(None);
};

let completion = ironposh_psrp::CommandCompletion::try_from(&ps_value)
.map_err(|e| anyhow::anyhow!(e))?;
let completion =
ironposh_psrp::CommandCompletion::try_from(&ps_value).map_err(|e| anyhow::anyhow!(e))?;

info!(
replacement_index = completion.replacement_index,
Expand Down
72 changes: 43 additions & 29 deletions crates/ironposh-psrp/src/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@ pub struct CompletionResult {
#[derive(Debug, thiserror::Error)]
pub enum CommandCompletionError {
#[error("expected a PowerShell object for {context}, got {found}")]
ExpectedObject { context: &'static str, found: &'static str },
ExpectedObject {
context: &'static str,
found: &'static str,
},

#[error("missing property {name} in {context}")]
MissingProperty { context: &'static str, name: &'static str },
MissingProperty {
context: &'static str,
name: &'static str,
},

#[error("unexpected type for {context}.{name}: expected {expected}, got {found}")]
UnexpectedType {
Expand All @@ -37,31 +43,30 @@ impl TryFrom<&PsValue> for CommandCompletion {
type Error = CommandCompletionError;

fn try_from(value: &PsValue) -> Result<Self, Self::Error> {
let obj = value.as_object().ok_or_else(|| CommandCompletionError::ExpectedObject {
context: "CommandCompletion",
found: ps_value_kind(value),
})?;
let obj = value
.as_object()
.ok_or_else(|| CommandCompletionError::ExpectedObject {
context: "CommandCompletion",
found: ps_value_kind(value),
})?;

let current_match_index = get_i32(value, "CommandCompletion", "CurrentMatchIndex")?;
let replacement_index = get_i32(value, "CommandCompletion", "ReplacementIndex")?;
let replacement_length = get_i32(value, "CommandCompletion", "ReplacementLength")?;

let matches_value = obj
.adapted_properties
.get("CompletionMatches")
.ok_or(CommandCompletionError::MissingProperty {
let matches_value = obj.adapted_properties.get("CompletionMatches").ok_or(
CommandCompletionError::MissingProperty {
context: "CommandCompletion",
name: "CompletionMatches",
})?;
},
)?;

let matches_obj =
matches_value
.value
.as_object()
.ok_or_else(|| CommandCompletionError::ExpectedObject {
context: "CommandCompletion.CompletionMatches",
found: ps_value_kind(&matches_value.value),
})?;
let matches_obj = matches_value.value.as_object().ok_or_else(|| {
CommandCompletionError::ExpectedObject {
context: "CommandCompletion.CompletionMatches",
found: ps_value_kind(&matches_value.value),
}
})?;

let Container::List(items) = matches_obj.content.container().ok_or_else(|| {
CommandCompletionError::UnexpectedType {
Expand All @@ -70,7 +75,8 @@ impl TryFrom<&PsValue> for CommandCompletion {
expected: "Container(List)",
found: complex_content_kind(&matches_obj.content),
}
})? else {
})?
else {
return Err(CommandCompletionError::UnexpectedType {
context: "CommandCompletion",
name: "CompletionMatches",
Expand All @@ -97,10 +103,12 @@ impl TryFrom<&PsValue> for CompletionResult {
type Error = CommandCompletionError;

fn try_from(value: &PsValue) -> Result<Self, Self::Error> {
let obj = value.as_object().ok_or_else(|| CommandCompletionError::ExpectedObject {
context: "CompletionResult",
found: ps_value_kind(value),
})?;
let obj = value
.as_object()
.ok_or_else(|| CommandCompletionError::ExpectedObject {
context: "CompletionResult",
found: ps_value_kind(value),
})?;

let completion_text = get_string_from_obj(obj, "CompletionResult", "CompletionText")?;
let list_item_text = get_string_from_obj(obj, "CompletionResult", "ListItemText")?;
Expand All @@ -116,11 +124,17 @@ impl TryFrom<&PsValue> for CompletionResult {
}
}

fn get_i32(value: &PsValue, context: &'static str, name: &'static str) -> Result<i32, CommandCompletionError> {
let obj = value.as_object().ok_or_else(|| CommandCompletionError::ExpectedObject {
context,
found: ps_value_kind(value),
})?;
fn get_i32(
value: &PsValue,
context: &'static str,
name: &'static str,
) -> Result<i32, CommandCompletionError> {
let obj = value
.as_object()
.ok_or_else(|| CommandCompletionError::ExpectedObject {
context,
found: ps_value_kind(value),
})?;
let prop = obj
.adapted_properties
.get(name)
Expand Down
4 changes: 2 additions & 2 deletions crates/ironposh-psrp/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
pub mod cores;
pub mod completion;
pub mod cores;
pub mod fragmentation;
pub mod messages;
pub mod ps_value;

use std::str::Utf8Error;

pub use cores::*;
pub use completion::{CommandCompletion, CommandCompletionError, CompletionResult};
pub use cores::*;
pub use fragmentation::*;
pub use messages::*;
pub use ps_value::PsObjectWithType;
Expand Down
1 change: 0 additions & 1 deletion crates/ironposh-psrp/src/tests/command_completion_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,3 @@ fn parse_command_completion_from_tab_expansion2() {
"Expected at least one completion starting with Get-Ser"
);
}

2 changes: 1 addition & 1 deletion crates/ironposh-psrp/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pub mod creation_xml_roundtrip;
pub mod exact_xml_tests;
pub mod parse_real_pipeline_output;

pub mod command_xml_tests;
pub mod command_completion_test;
pub mod command_xml_tests;
pub mod error_record_test;
pub mod parse_real_pipeline_host_call;
4 changes: 3 additions & 1 deletion crates/ironposh-terminal/src/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,9 @@ impl<'a> StdTerm<'a> {
continue;
}

if let Some(outcome) = self.process_event(&mut line, evt, /*edit_line=*/ true)? {
if let Some(outcome) =
self.process_event(&mut line, evt, /*edit_line=*/ true)?
{
return Ok(outcome);
}
}
Expand Down
9 changes: 4 additions & 5 deletions crates/ironposh-web/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ use crate::{
types::{SecurityWarningCallback, WasmCommandCompletion, WasmWinRmConfig},
JsSessionEvent, WasmPowerShellStream,
};
use std::convert::TryFrom;
use futures::StreamExt;
use ironposh_async::RemoteAsyncPowershellClient;
use ironposh_client_core::{connector::WinRmConfig, powershell::PipelineHandle};
use js_sys::{Array, Function, Promise};
use std::convert::TryFrom;
use tracing::{error, info, warn};
use url::Url;
use wasm_bindgen::prelude::*;
Expand Down Expand Up @@ -256,9 +256,8 @@ impl WasmPowerShellClient {
}

let escaped = escape_ps_single_quoted(&input_script);
let script = format!(
"TabExpansion2 -inputScript '{escaped}' -cursorColumn {cursor_column}"
);
let script =
format!("TabExpansion2 -inputScript '{escaped}' -cursorColumn {cursor_column}");

info!(
cursor_column,
Expand All @@ -285,7 +284,7 @@ impl WasmPowerShellClient {
warn!(error_message = %concise, "tab_complete: error record");
}
UserEvent::PipelineFinished { .. } => break,
_ => {}
UserEvent::PipelineCreated { .. } => {}
}
}

Expand Down