Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3552e1a
Skip tarball files for pack.
garethj-msft Apr 12, 2025
65b57d1
Added ability to use kiota as a regular npx command
garethj-msft Apr 12, 2025
1b43aea
Made bootstrapping bypassable
garethj-msft Apr 12, 2025
520716f
Made postinstall skippable.
garethj-msft Apr 12, 2025
b1ca919
SKipped customer-runtime postinstall durign CI build.
garethj-msft Apr 12, 2025
d6819ab
EMpty NPM test project
garethj-msft Apr 13, 2025
ff59293
Slight readme improvements.
garethj-msft Apr 13, 2025
7c08232
Added esliint
garethj-msft Apr 13, 2025
fb1dd9d
Added runtime for installation
garethj-msft Apr 13, 2025
ea4d8c0
Forgot packagelock
garethj-msft Apr 13, 2025
4321a41
Added integration test for e2e npm package
garethj-msft Apr 13, 2025
afe5888
Minor cleanups - linting passes
garethj-msft Apr 13, 2025
008738a
Added workflows to project for easy editing.
garethj-msft Apr 13, 2025
e467702
Merge branch 'main' into garethj/npxsupport
baywet Apr 14, 2025
ab69831
Merge branch 'main' into garethj/npxsupport
garethj-msft Apr 14, 2025
6fd9fd3
Merge branch 'main' into garethj/npxsupport
baywet Jun 17, 2025
d264a40
Merge branch 'main' into garethj/npxsupport
baywet Jul 11, 2025
6edc9aa
chore: fixes esproj package path after merge
baywet Jul 11, 2025
e1b532e
ci: fixes unit test path after merge
baywet Jul 11, 2025
86940b1
ci: fixes install unit test
baywet Jul 11, 2025
db1bc6d
ci: adds nvm support for windows in unit test
baywet Jul 11, 2025
4a364eb
chore: fixes dependency name in unit test
baywet Jul 11, 2025
7a86da4
ci: adds missing setup node
baywet Jul 11, 2025
c069194
chore: upgrades from deprecated API
baywet Jul 11, 2025
4d0c9ef
fix: do not validate hash when it's the dev placeholder
baywet Jul 11, 2025
7aa3198
ci: adds version update for unit test
baywet Jul 11, 2025
ac2f470
chore: removes esproj since it slows down everything and creates file…
baywet Jul 11, 2025
83faca0
docs: adds changelog entry for npx command
baywet Jul 11, 2025
0b21de9
ci: switches to GHA for version script
baywet Jul 11, 2025
9a806ad
ci: fix csproj path
baywet Jul 11, 2025
a4fe05f
ci: life is always better with quotes
baywet Jul 11, 2025
61c6187
Revert "chore: removes esproj since it slows down everything and crea…
baywet Jul 11, 2025
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
18 changes: 11 additions & 7 deletions .github/workflows/build-vscode-extension.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
id: checksecret_job
run: |
echo "is_SONAR_TOKEN_set=${{ env.SONAR_TOKEN != '' }}" >> $GITHUB_OUTPUT

test_and_generate_binaries:
needs: [checksecret]
strategy:
Expand All @@ -31,7 +31,7 @@ jobs:
- architecture: linux-x64
os: ubuntu-latest
- architecture: osx-arm64
os: macOS-latest
os: macOS-latest
runs-on: ${{ matrix.binaries.os }}
steps:
- uses: actions/checkout@v4
Expand All @@ -43,7 +43,7 @@ jobs:
- name: Use .NET 9
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.x'
dotnet-version: "9.x"

- id: last_release
run: |
Expand All @@ -56,8 +56,10 @@ jobs:

- run: scripts/update-vscode-releases.ps1 -version "v${{ steps.last_release.outputs.RELEASE_VERSION }}" -packageJsonFilePath "./vscode/microsoft-kiota/package.json" -runtimeFilePath "./vscode/npm-package/runtime.json" -online
shell: pwsh

- name: Install dependencies
env:
SKIP_POSTINSTALL: "true"
run: npm install
working-directory: vscode

Expand Down Expand Up @@ -140,19 +142,21 @@ jobs:
shell: pwsh

- name: Install dependencies
env:
SKIP_POSTINSTALL: "true"
run: npm install
working-directory: vscode

- run: npm run package:${{ matrix.builds.id }}
if: matrix.builds.id == 'package'
name: 'Package - ${{ matrix.builds.id }}'
name: "Package - ${{ matrix.builds.id }}"
working-directory: vscode

- run: |
npm i -g @vscode/vsce
vsce package
working-directory: vscode/${{ matrix.builds.path }}
name: 'Package - ${{ matrix.builds.id }}'
name: "Package - ${{ matrix.builds.id }}"
if: matrix.builds.id == 'vscode'

- name: Upload artifact
Expand All @@ -171,4 +175,4 @@ jobs:
run: exit 0
- name: One or more build matrix options failed
if: ${{ contains(needs.*.result, 'failure') }}
run: exit 1
run: exit 1
2 changes: 2 additions & 0 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ jobs:
- name: Restore dependencies
run: dotnet restore
- name: Build
env:
SKIP_POSTINSTALL: "true"
run: dotnet build --no-restore -c Release

- name: Perform CodeQL Analysis
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ jobs:
- name: Check formatting
run: dotnet format --verify-no-changes
- name: Build
env:
SKIP_POSTINSTALL: "true"
run: dotnet build kiota.sln --no-restore
- name: Test
run: dotnet test kiota.sln --no-build --verbosity normal --collect:"XPlat Code Coverage"
Expand Down
49 changes: 44 additions & 5 deletions kiota.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30804.86
# Visual Studio Version 17
VisualStudioVersion = 17.13.35931.197
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "kiota", "src\kiota\kiota.csproj", "{944FCE5E-0CFA-4018-B353-E14FA1395007}"
EndProject
Expand All @@ -19,6 +19,28 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EAAC5CEA-33B
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KiotaGenerated", "src\Kiota.Generated\KiotaGenerated.csproj", "{01ABDF23-60CD-4CE3-8DC7-8654C4BA1EE8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kiota.NPM.IntegrationTests", "tests\Kiota.NPM.IntegrationTests\Kiota.NPM.IntegrationTests.csproj", "{51296EA6-7A4A-2A93-5060-1FBD4CB31CC5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NPMProjects", "NPMProjects", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
EndProject
Project("{54A90642-561A-4BB1-A94E-469ADEE60C69}") = "npm-package", "vscode\npm-package\npm-package.esproj", "{C6BFB632-723D-238A-EA11-44E78644612E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Workflows", "Workflows", "{099BEFDC-D101-4DB6-B028-E24376B6DC50}"
ProjectSection(SolutionItems) = preProject
.github\workflows\auto-merge-dependabot.yml = .github\workflows\auto-merge-dependabot.yml
.github\workflows\build-vscode-extension.yml = .github\workflows\build-vscode-extension.yml
.github\workflows\check-translations.yml = .github\workflows\check-translations.yml
.github\workflows\codeql-analysis.yml = .github\workflows\codeql-analysis.yml
.github\workflows\codeql-required-workaround.yml = .github\workflows\codeql-required-workaround.yml
.github\workflows\dotnet-required-workaround.yml = .github\workflows\dotnet-required-workaround.yml
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
.github\workflows\idempotency-tests.yml = .github\workflows\idempotency-tests.yml
.github\workflows\integration-tests.yml = .github\workflows\integration-tests.yml
.github\workflows\load-tests.yml = .github\workflows\load-tests.yml
.github\workflows\project-auto-add.yml = .github\workflows\project-auto-add.yml
.github\workflows\sonarcloud.yml = .github\workflows\sonarcloud.yml
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -49,15 +71,32 @@ Global
{01ABDF23-60CD-4CE3-8DC7-8654C4BA1EE8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{01ABDF23-60CD-4CE3-8DC7-8654C4BA1EE8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{01ABDF23-60CD-4CE3-8DC7-8654C4BA1EE8}.Release|Any CPU.Build.0 = Release|Any CPU
{51296EA6-7A4A-2A93-5060-1FBD4CB31CC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{51296EA6-7A4A-2A93-5060-1FBD4CB31CC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{51296EA6-7A4A-2A93-5060-1FBD4CB31CC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{51296EA6-7A4A-2A93-5060-1FBD4CB31CC5}.Release|Any CPU.Build.0 = Release|Any CPU
{C6BFB632-723D-238A-EA11-44E78644612E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C6BFB632-723D-238A-EA11-44E78644612E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C6BFB632-723D-238A-EA11-44E78644612E}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{C6BFB632-723D-238A-EA11-44E78644612E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C6BFB632-723D-238A-EA11-44E78644612E}.Release|Any CPU.Build.0 = Release|Any CPU
{C6BFB632-723D-238A-EA11-44E78644612E}.Release|Any CPU.Deploy.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {85C3AE3B-2D83-4457-B2F9-E56D64F4E443}
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{944FCE5E-0CFA-4018-B353-E14FA1395007} = {EAAC5CEA-33B8-495D-9CD0-B36794B8AFE7}
{1F5FC53F-061A-4CED-8B53-FC5C63DBEBFF} = {EAAC5CEA-33B8-495D-9CD0-B36794B8AFE7}
{A2B2F620-BC5D-47EA-8B98-5A942EC9CA9A} = {2DF34BB8-B19F-4623-9E3D-9F59A14C0660}
{019E5612-7663-40A2-A2EA-46E39D31F0A2} = {2DF34BB8-B19F-4623-9E3D-9F59A14C0660}
{E4C108A5-A13F-4C3F-B32A-86210A4EC52A} = {2DF34BB8-B19F-4623-9E3D-9F59A14C0660}
{01ABDF23-60CD-4CE3-8DC7-8654C4BA1EE8} = {EAAC5CEA-33B8-495D-9CD0-B36794B8AFE7}
{51296EA6-7A4A-2A93-5060-1FBD4CB31CC5} = {2DF34BB8-B19F-4623-9E3D-9F59A14C0660}
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {EAAC5CEA-33B8-495D-9CD0-B36794B8AFE7}
{C6BFB632-723D-238A-EA11-44E78644612E} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {85C3AE3B-2D83-4457-B2F9-E56D64F4E443}
EndGlobalSection
EndGlobal
6 changes: 6 additions & 0 deletions tests/Kiota.NPM.IntegrationTests/.runsettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<RunSettings>
<xUnit>
<ShowLiveOutput>true</ShowLiveOutput>
</xUnit>
</RunSettings>
13 changes: 13 additions & 0 deletions tests/Kiota.NPM.IntegrationTests/Assets/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "kiota-consumer",
"version": "1.0.0",
"description": "",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"dependencies": {
"@microsoft/kiota": "file:./microsoft-kiota-1.0.0.tgz"
}
}
207 changes: 207 additions & 0 deletions tests/Kiota.NPM.IntegrationTests/InstallTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
using System;
using System.Diagnostics;
using System.IO;
using Xunit;
using Xunit.Abstractions;


namespace Kiota.NPM.IntegrationTests;

public class InstallTests
{
private readonly ITestOutputHelper _outputHelper;

public InstallTests(ITestOutputHelper outputHelper)
{
_outputHelper = outputHelper ?? throw new ArgumentNullException(nameof(outputHelper));
}

[Fact]
public void Install_Creates_Working_Bin_Command()
{
// This test first compiles then packs the Kiota project to a local folder created for
// the individual test run, such that there is a tarball waiting in the folder.
// Then it copies the package.json file from the Assets folder to the test folder.
// Then it runs npm install in the test folder.
// Finally, it asserts two things:
// Firstly, if the kiota command is available in the node_modules/.bin folder.
// Secondly, if the npx command can be used to run the kiota command successfully
// with the '--version' command line option which must successfully return a string which conforms to a pattern expressing the version of kiota.

// Create a temporary directory for the test
var testDir = Path.Combine(Path.GetTempPath(), $"kiota-npm-test-{Guid.NewGuid()}");
Directory.CreateDirectory(testDir);

try
{
// Pack the project using npm
var projectDir = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../../../vscode/npm-package"));
_outputHelper.WriteLine($"Running pack in {projectDir}");
var npmPackProcess = RunProcess("npm", $"pack --pack-destination {testDir}", projectDir);
_outputHelper.WriteLine("Checking if npm pack process exited successfully.");
Assert.Equal(0, npmPackProcess.exitCode);

// Copy package.json from Assets folder to test directory
var assetsDir = Path.Combine(AppContext.BaseDirectory, "../../../Assets");
File.Copy(Path.Combine(assetsDir, "package.json"), Path.Combine(testDir, "package.json"));

// Run npm install in the test directory
var npmProcess = RunProcess("npm", "install", testDir);
_outputHelper.WriteLine("Checking if npm install process exited successfully.");
Assert.Equal(0, npmProcess.exitCode);

// Assert 1: Check if kiota exists in node_modules/.bin
var kiotaPath = Path.Combine(testDir, "node_modules", ".bin", "kiota");
if (OperatingSystem.IsWindows())
{
kiotaPath += ".cmd"; // On Windows, the bin command is a .cmd file
}
_outputHelper.WriteLine($"Checking if Kiota command exists at {kiotaPath}.");
Assert.True(File.Exists(kiotaPath), $"Kiota command not found at {kiotaPath}");

// Assert 2: Run kiota --version using npx and check output
var npxProcess = RunProcess("npx", "kiota --version", testDir);
_outputHelper.WriteLine("Checking if npx process to run 'kiota --version' exited successfully.");
Assert.Equal(0, npxProcess.exitCode);

// Check if output matches version pattern (e.g., 1.2.3 or 1.2.3-preview.4)
var versionPattern = @"^\d+\.\d+\.\d+.*$";
_outputHelper.WriteLine("Checking if the output of 'kiota --version' matches the expected version pattern.");
Assert.Matches(versionPattern, npxProcess.output);
}
finally
{
// Clean up
try
{
Directory.Delete(testDir, true);
}
catch
{
// Best effort cleanup
}
}
}

private (int exitCode, string output) RunProcess(string fileName, string arguments, string workingDirectory)
{
// Try to find npm or npx in standard locations if not found directly
if ((fileName == "npm" || fileName == "npx") && OperatingSystem.IsWindows())
{
// Check common locations for npm/npx on Windows
string commandExtension = ".cmd";
string[] possiblePaths = new[]
{
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "nodejs", $"{fileName}{commandExtension}"),
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "nodejs", $"{fileName}{commandExtension}"),
// Add from npm global prefix
Path.Combine(Environment.GetEnvironmentVariable("APPDATA") ?? "", "npm", $"{fileName}{commandExtension}"),
// Try npm global installation path
Path.Combine(Environment.GetEnvironmentVariable("APPDATA") ?? "", "npm", "node_modules", "npm", "bin", $"{fileName}{commandExtension}")
};

foreach (var path in possiblePaths)
{
if (File.Exists(path))
{
_outputHelper.WriteLine($"Found {fileName} at {path}");
fileName = path;
break;
}
}
}
else if ((fileName == "npm" || fileName == "npx") && (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()))
{
// Check common locations for npm/npx on Linux/macOS
string[] possiblePaths = new[]
{
$"/usr/bin/{fileName}",
$"/usr/local/bin/{fileName}",
$"/opt/homebrew/bin/{fileName}", // Common on macOS with Homebrew
$"{Environment.GetEnvironmentVariable("HOME")}/.npm/bin/{fileName}", // User's npm bin directory
$"{Environment.GetEnvironmentVariable("HOME")}/.nvm/versions/node/*/bin/{fileName}" // nvm installations
};

foreach (var path in possiblePaths)
{
// Handle wildcard paths (for nvm)
if (path.Contains("*"))
{
var directory = Path.GetDirectoryName(path);
if (directory != null && Directory.Exists(directory.Replace("*", "")))
{
// Get the highest version directory
var dirs = Directory.GetDirectories(directory.Replace("*", ""));
if (dirs.Length > 0)
{
var exactPath = Path.Combine(dirs[^1], Path.GetFileName(path));
if (File.Exists(exactPath))
{
_outputHelper.WriteLine($"Found {fileName} at {exactPath}");
fileName = exactPath;
break;
}
}
}
continue;
}

if (File.Exists(path))
{
_outputHelper.WriteLine($"Found {fileName} at {path}");
fileName = path;
break;
}
}
}

// Fall back to PATH environment if still using the short name
if (fileName == "npm" || fileName == "npx")
{
// Log that we're using PATH resolution
_outputHelper.WriteLine($"Using PATH environment to resolve {fileName}");
}

using (var process = new Process())
{
process.StartInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
WorkingDirectory = workingDirectory,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};

var outputBuilder = new StringWriter();

process.OutputDataReceived += (sender, args) =>
{
if (args.Data != null)
{
outputBuilder.WriteLine(args.Data);
}
};

process.ErrorDataReceived += (sender, args) =>
{
if (args.Data != null)
{
outputBuilder.WriteLine($"Error: {args.Data}");
}
};

if (!process.Start())
{
throw new InvalidOperationException($"Failed to start process '{fileName}' in directory '{workingDirectory}'");
}
process.BeginOutputReadLine();
process.BeginErrorReadLine();

process.WaitForExit();
return (process.ExitCode, outputBuilder.ToString().Trim());
}
}
}
Loading
Loading