chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
dirs:
|
||||
- .
|
||||
ignorePatterns:
|
||||
- pattern: "/github/"
|
||||
- pattern: "./actions"
|
||||
- pattern: "./blob"
|
||||
- pattern: "./issues"
|
||||
- pattern: "./discussions"
|
||||
- pattern: "./pulls"
|
||||
- pattern: "https:\/\/platform.openai.com"
|
||||
- pattern: "https:\/\/outlook.office.com/bookings"
|
||||
excludedDirs:
|
||||
# Folders which include links to localhost, since it's not ignored with regular expressions
|
||||
- ./python/samples/demos/telemetry
|
||||
- ./python/samples/demos/process_with_dapr
|
||||
- ./dotnet/samples/Demos/ProcessWithDapr
|
||||
- ./dotnet/samples/Demos/CopilotAgentPlugins
|
||||
# Exclude folders that contain documents with links prone to becoming broken and temporarily unavailable. This should be removed when the link checker is implemented as a background job that does not block PRs.
|
||||
- ./docs/decisions
|
||||
- ./dotnet/samples
|
||||
- ./dotnet/src/IntegrationTests # Cannot reach https://www.microsoft.com/en-us/bing/apis/bing-web-search-api Status: 404" location:{path:"dotnet/src/IntegrationTests/README.md"
|
||||
- ./dotnet/src/Experimental/Orchestration.Flow.IntegrationTests # Cannot reach https://www.microsoft.com/en-us/bing/apis/bing-web-search-api Status: 404" location:{path:"dotnet/src/Experimental/Orchestration.Flow.IntegrationTests/README.md"
|
||||
- ./python/samples
|
||||
baseUrl: https://github.com/microsoft/semantic-kernel/
|
||||
aliveStatusCodes:
|
||||
- 200
|
||||
- 206
|
||||
- 429
|
||||
- 500
|
||||
- 503
|
||||
useGitIgnore: true
|
||||
@@ -0,0 +1,14 @@
|
||||
# @microsoft/octo-semantickernel-pr-dotnet owns any files in the dotnet
|
||||
# directory at the root of the repository and any of its
|
||||
# subdirectories.
|
||||
/dotnet/ @microsoft/octo-semantickernel-pr-dotnet
|
||||
|
||||
# @microsoft/octo-semantickernel-pr-python owns any files in the python
|
||||
# directory at the root of the repository and any of its
|
||||
# subdirectories.
|
||||
/python/ @microsoft/octo-semantickernel-pr-python
|
||||
|
||||
# @microsoft/octo-semantickernel-pr-python owns any files in the java
|
||||
# directory at the root of the repository and any of its
|
||||
# subdirectories.
|
||||
/java/ @microsoft/octo-semantickernel-pr-java
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: 'Bug: '
|
||||
type: 'bug'
|
||||
labels: ["bug"]
|
||||
projects: ["semantic-kernel"]
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Platform**
|
||||
- Language: [e.g. C#, Python]
|
||||
- Source: [e.g. NuGet package version 0.1.0, pip package version 0.1.0, main branch of repository]
|
||||
- AI model: [e.g. OpenAI:GPT-4o-mini(2024-07-18)]
|
||||
- IDE: [e.g. Visual Studio, VS Code]
|
||||
- OS: [e.g. Windows, Mac]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: Feature graduation
|
||||
about: Plan the graduation of an experimental feature
|
||||
title: 'Graduate XXX feature'
|
||||
labels: ["feature_graduation"]
|
||||
type: 'feature'
|
||||
projects: ["semantic-kernel"]
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
name: Feature graduation
|
||||
about: Plan the graduation of an experimental feature
|
||||
|
||||
---
|
||||
|
||||
Checklist to be completed when graduating an experimental feature
|
||||
|
||||
- [ ] Notify PM's and EM's that feature is ready for graduation
|
||||
- [ ] Contact PM for list of sample use cases
|
||||
- [ ] Verify there are sample implementations for each of the use cases
|
||||
- [ ] Verify telemetry and logging are complete
|
||||
- [ ] Verify API docs are complete and arrange to have them published
|
||||
- [ ] Make appropriate updates to Learn docs
|
||||
- [ ] Make appropriate updates to Concept samples
|
||||
- [ ] Make appropriate updates to Blog posts
|
||||
- [ ] Verify there are no serious open Issues
|
||||
- [ ] Update table in EXPERIMENTS.md
|
||||
- [ ] Remove SKEXP flag from the experimental code
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: 'New Feature: '
|
||||
labels: ''
|
||||
type: 'feature'
|
||||
projects: ["semantic-kernel"]
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
<!-- ⚠️⚠️ Do Not Delete This! feature_request_template ⚠️⚠️ -->
|
||||
<!-- Please read our Rules of Conduct: https://opensource.microsoft.com/codeofconduct/ -->
|
||||
<!-- Please search existing issues to avoid creating duplicates. -->
|
||||
|
||||
<!-- Describe the feature you'd like. -->
|
||||
@@ -0,0 +1,61 @@
|
||||
# Typos configuration file
|
||||
#
|
||||
# Info: https://github.com/marketplace/actions/typos-action
|
||||
# Install: brew install typos-cli
|
||||
# Install: conda install typos
|
||||
# Run: typos -c .github/_typos.toml
|
||||
|
||||
[files]
|
||||
extend-exclude = [
|
||||
"_typos.toml",
|
||||
"package-lock.json",
|
||||
"*.bicep",
|
||||
"encoder.json",
|
||||
"vocab.bpe",
|
||||
"CodeTokenizerTests.cs",
|
||||
"test_code_tokenizer.py",
|
||||
"*response.json",
|
||||
"test_content.txt",
|
||||
"google_what_is_the_semantic_kernel.json",
|
||||
"what-is-semantic-kernel.json",
|
||||
"serializedChatHistoryV1_15_1.json",
|
||||
"MultipleFunctionsVsParameters.cs",
|
||||
"PopulationByCountry.csv",
|
||||
"PopulationByAdmin1.csv",
|
||||
"WomensSuffrage.txt",
|
||||
"SK-dotnet.slnx.DotSettings",
|
||||
"**/azure_ai_search_hotel_samples/README.md",
|
||||
"**/Demos/ProcessFrameworkWithAspire/ProcessFramework.Aspire/ProcessFramework.Aspire.ProcessOrchestrator/Program.cs",
|
||||
"**/Demos/ProcessFrameworkWithAspire/**/*.http",
|
||||
"**/samples/Concepts/Resources/travel-destination-overview.txt"
|
||||
]
|
||||
|
||||
[default.extend-words]
|
||||
ACI = "ACI" # Azure Container Instance
|
||||
exercize = "exercize" # test typos
|
||||
gramatical = "gramatical" # test typos
|
||||
Guid = "Guid" # Globally Unique Identifier
|
||||
HD = "HD" # Test header value
|
||||
EOF = "EOF" # End of File
|
||||
ans = "ans" # Short for answers
|
||||
arange = "arange" # Method in Python numpy package
|
||||
prompty = "prompty" # prompty is a format name.
|
||||
ist = "ist" # German language
|
||||
dall = "dall" # OpenAI model name
|
||||
pn = "pn" # Kiota parameter
|
||||
nin = "nin" # MongoDB "not in" operator
|
||||
asend = "asend" # Async generator method
|
||||
Magentic = "Magentic" # Magentic is a name of an agentic pattern
|
||||
|
||||
[default.extend-identifiers]
|
||||
ags = "ags" # Azure Graph Service
|
||||
|
||||
[type.jupyter]
|
||||
extend-ignore-re = [
|
||||
'"[A-Fa-f0-9]{8}"', # cell id strings
|
||||
]
|
||||
|
||||
[type.msbuild]
|
||||
extend-ignore-re = [
|
||||
'Version=".*"', # ignore package version numbers
|
||||
]
|
||||
@@ -0,0 +1,44 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
# Maintain dependencies for nuget
|
||||
- package-ecosystem: "nuget"
|
||||
directory: "dotnet/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
ignore:
|
||||
# For all System.* and Microsoft.Extensions/Bcl.* packages, ignore all major version updates
|
||||
- dependency-name: "System.*"
|
||||
update-types: ["version-update:semver-major"]
|
||||
- dependency-name: "Microsoft.Extensions.*"
|
||||
update-types: ["version-update:semver-major"]
|
||||
- dependency-name: "Microsoft.Bcl.*"
|
||||
update-types: ["version-update:semver-major"]
|
||||
- dependency-name: "Moq"
|
||||
labels:
|
||||
- ".NET"
|
||||
- "dependencies"
|
||||
|
||||
# Maintain dependencies for pip
|
||||
- package-ecosystem: "pip"
|
||||
directory: "python/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
labels:
|
||||
- "python"
|
||||
- "dependencies"
|
||||
|
||||
# Maintain dependencies for github-actions
|
||||
- package-ecosystem: "github-actions"
|
||||
# Workflow files stored in the
|
||||
# default location of `.github/workflows`
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "tuesday"
|
||||
@@ -0,0 +1,43 @@
|
||||
# Add 'kernel' label to any change within Connectors, Extensions, Skills, and tests directories
|
||||
kernel:
|
||||
- dotnet/src/Connectors/**/*
|
||||
- dotnet/src/Extensions/**/*
|
||||
- dotnet/src/Skills/**/*
|
||||
- dotnet/src/IntegrationTests/**/*
|
||||
- dotnet/src/SemanticKernel.UnitTests/**/*
|
||||
|
||||
# Add 'kernel.core' label to any change within the 'SemanticKernel', 'SemanticKernel.Abstractions', or 'SemanticKernel.MetaPackage' directories
|
||||
kernel.core:
|
||||
- dotnet/src/SemanticKernel/**/*
|
||||
- dotnet/src/SemanticKernel.Abstractions/**/*
|
||||
- dotnet/src/SemanticKernel.MetaPackage/**/*
|
||||
|
||||
# Add 'python' label to any change within the 'python' directory
|
||||
python:
|
||||
- python/**/*
|
||||
|
||||
# Add 'java' label to any change within the 'java' directory
|
||||
java:
|
||||
- java/**/*
|
||||
|
||||
# Add 'samples' label to any change within the 'samples' directory
|
||||
samples:
|
||||
- samples/**/*
|
||||
|
||||
# Add '.NET' label to any change within samples or kernel 'dotnet' directories.
|
||||
.NET:
|
||||
- dotnet/**/*
|
||||
|
||||
# Add 'copilot chat' label to any change within the 'samples/apps/copilot-chat-app' directory
|
||||
copilot chat:
|
||||
- samples/apps/copilot-chat-app/**/*
|
||||
|
||||
# Add 'documentation' label to any change within the 'docs' directory, or any '.md' files
|
||||
documentation:
|
||||
- docs/**/*
|
||||
- '**/*.md'
|
||||
|
||||
# Add 'memory' label to any memory connectors in dotnet/ or python/
|
||||
memory:
|
||||
- dotnet/src/Connectors/Connectors.Memory.*/**/*
|
||||
- python/semantic_kernel/connectors/memory/**/*
|
||||
@@ -0,0 +1,23 @@
|
||||
### Motivation and Context
|
||||
|
||||
<!-- Thank you for your contribution to the semantic-kernel repo!
|
||||
Please help reviewers and future users, providing the following information:
|
||||
1. Why is this change required?
|
||||
2. What problem does it solve?
|
||||
3. What scenario does it contribute to?
|
||||
4. If it fixes an open issue, please link to the issue here.
|
||||
-->
|
||||
|
||||
### Description
|
||||
|
||||
<!-- Describe your changes, the overall approach, the underlying design.
|
||||
These notes will help understanding how your code works. Thanks! -->
|
||||
|
||||
### Contribution Checklist
|
||||
|
||||
<!-- Before submitting this PR, please make sure: -->
|
||||
|
||||
- [ ] The code builds clean without any errors or warnings
|
||||
- [ ] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations
|
||||
- [ ] All unit tests pass, and I have added new tests where possible
|
||||
- [ ] I didn't break anyone :smile:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,78 @@
|
||||
param (
|
||||
[string]$JsonReportPath,
|
||||
[double]$CoverageThreshold
|
||||
)
|
||||
|
||||
$jsonContent = Get-Content $JsonReportPath -Raw | ConvertFrom-Json
|
||||
$coverageBelowThreshold = $false
|
||||
|
||||
$nonExperimentalAssemblies = [System.Collections.Generic.HashSet[string]]::new()
|
||||
|
||||
$assembliesCollection = @(
|
||||
'Microsoft.SemanticKernel.Abstractions'
|
||||
'Microsoft.SemanticKernel.Core'
|
||||
'Microsoft.SemanticKernel.PromptTemplates.Handlebars'
|
||||
'Microsoft.SemanticKernel.Connectors.OpenAI'
|
||||
'Microsoft.SemanticKernel.Connectors.AzureOpenAI'
|
||||
'Microsoft.SemanticKernel.Yaml'
|
||||
'Microsoft.SemanticKernel.Agents.Abstractions'
|
||||
'Microsoft.SemanticKernel.Agents.Core'
|
||||
'Microsoft.SemanticKernel.Agents.OpenAI'
|
||||
)
|
||||
|
||||
foreach ($assembly in $assembliesCollection) {
|
||||
$nonExperimentalAssemblies.Add($assembly)
|
||||
}
|
||||
|
||||
function Get-FormattedValue {
|
||||
param (
|
||||
[float]$Coverage,
|
||||
[bool]$UseIcon = $false
|
||||
)
|
||||
$formattedNumber = "{0:N1}" -f $Coverage
|
||||
$icon = if (-not $UseIcon) { "" } elseif ($Coverage -ge $CoverageThreshold) { '✅' } else { '❌' }
|
||||
|
||||
return "$formattedNumber% $icon"
|
||||
}
|
||||
|
||||
$lineCoverage = $jsonContent.summary.linecoverage
|
||||
$branchCoverage = $jsonContent.summary.branchcoverage
|
||||
|
||||
$totalTableData = [PSCustomObject]@{
|
||||
'Metric' = 'Total Coverage'
|
||||
'Line Coverage' = Get-FormattedValue -Coverage $lineCoverage
|
||||
'Branch Coverage' = Get-FormattedValue -Coverage $branchCoverage
|
||||
}
|
||||
|
||||
$totalTableData | Format-Table -AutoSize
|
||||
|
||||
$assemblyTableData = @()
|
||||
|
||||
foreach ($assembly in $jsonContent.coverage.assemblies) {
|
||||
$assemblyName = $assembly.name
|
||||
$assemblyLineCoverage = $assembly.coverage
|
||||
$assemblyBranchCoverage = $assembly.branchcoverage
|
||||
|
||||
$isNonExperimentalAssembly = $nonExperimentalAssemblies -contains $assemblyName
|
||||
|
||||
if ($isNonExperimentalAssembly -and ($assemblyLineCoverage -lt $CoverageThreshold -or $assemblyBranchCoverage -lt $CoverageThreshold)) {
|
||||
$coverageBelowThreshold = $true
|
||||
}
|
||||
|
||||
$assemblyTableData += [PSCustomObject]@{
|
||||
'Assembly Name' = $assemblyName
|
||||
'Line' = Get-FormattedValue -Coverage $assemblyLineCoverage -UseIcon $isNonExperimentalAssembly
|
||||
'Branch' = Get-FormattedValue -Coverage $assemblyBranchCoverage -UseIcon $isNonExperimentalAssembly
|
||||
}
|
||||
}
|
||||
|
||||
$sortedTable = $assemblyTableData | Sort-Object {
|
||||
$nonExperimentalAssemblies -contains $_.'Assembly Name'
|
||||
} -Descending
|
||||
|
||||
$sortedTable | Format-Table -AutoSize
|
||||
|
||||
if ($coverageBelowThreshold) {
|
||||
Write-Host "Code coverage is lower than defined threshold: $CoverageThreshold. Stopping the task."
|
||||
exit 1
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Close inactive issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
|
||||
jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
with:
|
||||
days-before-issue-stale: 90
|
||||
days-before-issue-close: 14
|
||||
stale-issue-label: "stale"
|
||||
stale-issue-message: "This issue is stale because it has been open for 90 days with no activity."
|
||||
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,70 @@
|
||||
# CodeQL is the code analysis engine developed by GitHub to automate security checks.
|
||||
# The results are shown as code scanning alerts in GitHub. For more details, visit:
|
||||
# https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/about-code-scanning-with-codeql
|
||||
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
# TODO: Add "feature*" back in again, once we determine the cause of the ongoing CodeQL failures.
|
||||
branches: ["main", "experimental*", "*-development"]
|
||||
schedule:
|
||||
- cron: "17 11 * * 2"
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: ["csharp", "python"]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Use only 'java' to analyze code written in Java, Kotlin or both
|
||||
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
if: ${{ matrix.language != 'java' }}
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
@@ -0,0 +1,139 @@
|
||||
name: DevFlow PR Review
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- ready_for_review
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: Pull request number to review
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: devflow-pr-review-${{ github.repository }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
DEVFLOW_REPOSITORY: ${{ vars.DF_REPO }}
|
||||
DEVFLOW_REF: main
|
||||
TARGET_REPO_PATH: ${{ github.workspace }}/target-repo
|
||||
DEVFLOW_PATH: ${{ github.workspace }}/devflow
|
||||
|
||||
jobs:
|
||||
review:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
if: ${{ github.event_name != 'pull_request_target' || !github.event.pull_request.draft }}
|
||||
|
||||
steps:
|
||||
- name: Resolve PR metadata
|
||||
id: pr
|
||||
shell: bash
|
||||
env:
|
||||
PR_HTML_URL: ${{ github.event.pull_request.html_url }}
|
||||
PR_NUMBER_EVENT: ${{ github.event.pull_request.number }}
|
||||
PR_NUMBER_INPUT: ${{ inputs.pr_number }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${GITHUB_EVENT_NAME}" == "pull_request_target" ]]; then
|
||||
pr_number="${PR_NUMBER_EVENT}"
|
||||
pr_url="${PR_HTML_URL}"
|
||||
else
|
||||
pr_number="${PR_NUMBER_INPUT}"
|
||||
pr_url="https://github.com/${GITHUB_REPOSITORY}/pull/${pr_number}"
|
||||
fi
|
||||
|
||||
if [[ ! "$pr_number" =~ ^[1-9][0-9]*$ ]]; then
|
||||
echo "Could not determine PR number; for workflow_dispatch runs, the 'pr_number' input is required when not running on pull_request_target." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "pr_url=${pr_url}" >> "$GITHUB_OUTPUT"
|
||||
echo "pr_number=${pr_number}" >> "$GITHUB_OUTPUT"
|
||||
echo "repo=${GITHUB_REPOSITORY}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Safe checkout: base repo only, not the untrusted PR head.
|
||||
- name: Checkout target repo base
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.base.sha || github.sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
path: target-repo
|
||||
|
||||
# Private DevFlow checkout: the PAT/token grants access to this repo's code.
|
||||
- name: Checkout DevFlow
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: ${{ env.DEVFLOW_REPOSITORY }}
|
||||
ref: ${{ env.DEVFLOW_REF }}
|
||||
token: ${{ secrets.DEVFLOW_TOKEN }}
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
path: devflow
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: "0.5.x"
|
||||
enable-cache: true
|
||||
|
||||
- name: Install DevFlow dependencies
|
||||
working-directory: ${{ env.DEVFLOW_PATH }}
|
||||
run: uv sync --frozen
|
||||
|
||||
- name: Classify PR relevance
|
||||
id: spam
|
||||
working-directory: ${{ env.DEVFLOW_PATH }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_COPILOT_TOKEN: ${{ secrets.GH_COPILOT_TOKEN }}
|
||||
SK_REPO_PATH: ${{ env.TARGET_REPO_PATH }}
|
||||
AGENT_REPO_PATH: ${{ env.TARGET_REPO_PATH }}
|
||||
PR_REPO: ${{ steps.pr.outputs.repo }}
|
||||
PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
|
||||
run: |
|
||||
uv run python scripts/classify_pr_spam.py \
|
||||
--repo "$PR_REPO" \
|
||||
--pr-number "$PR_NUMBER" \
|
||||
--repo-path "${TARGET_REPO_PATH}" \
|
||||
--apply-labels
|
||||
|
||||
- name: Stop after spam gate
|
||||
if: ${{ steps.spam.outputs.decision != 'allow' }}
|
||||
shell: bash
|
||||
env:
|
||||
SPAM_DECISION: ${{ steps.spam.outputs.decision }}
|
||||
run: |
|
||||
echo "Skipping review because spam gate decided: ${SPAM_DECISION}"
|
||||
|
||||
- name: Run PR review
|
||||
if: ${{ steps.spam.outputs.decision == 'allow' }}
|
||||
id: review
|
||||
working-directory: ${{ env.DEVFLOW_PATH }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_COPILOT_TOKEN: ${{ secrets.GH_COPILOT_TOKEN }}
|
||||
SK_REPO_PATH: ${{ env.TARGET_REPO_PATH }}
|
||||
AGENT_REPO_PATH: ${{ env.TARGET_REPO_PATH }}
|
||||
PR_URL: ${{ steps.pr.outputs.pr_url }}
|
||||
run: |
|
||||
uv run python scripts/trigger_pr_review.py \
|
||||
--pr-url "$PR_URL" \
|
||||
--github-username "$GITHUB_ACTOR" \
|
||||
--no-require-comment-selection
|
||||
@@ -0,0 +1,244 @@
|
||||
#
|
||||
# This workflow will build and run all unit tests using dotnet docker containers,
|
||||
# each targeting a single version of the dotnet SDK.
|
||||
#
|
||||
|
||||
name: dotnet-build-and-test
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches: ["main", "feature*"]
|
||||
merge_group:
|
||||
branches: ["main"]
|
||||
|
||||
env:
|
||||
COVERAGE_THRESHOLD: 80
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: "write"
|
||||
|
||||
jobs:
|
||||
paths-filter:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
dotnetChanges: ${{ steps.filter.outputs.dotnet }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
dotnet:
|
||||
- 'dotnet/**'
|
||||
- '**/dotnet/**'
|
||||
- '.github/workflows/check-coverage.ps1'
|
||||
- '.github/workflows/dotnet-build-and-test.yml'
|
||||
# run only if 'dotnet' files were changed
|
||||
- name: dotnet tests
|
||||
if: steps.filter.outputs.dotnet == 'true'
|
||||
run: echo "Dotnet file"
|
||||
# run only if not 'dotnet' files were changed
|
||||
- name: not dotnet tests
|
||||
if: steps.filter.outputs.dotnet != 'true'
|
||||
run: echo "NOT dotnet file"
|
||||
dotnet-build-and-test:
|
||||
needs: paths-filter
|
||||
if: needs.paths-filter.outputs.dotnetChanges == 'true'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- {
|
||||
dotnet: "10.0",
|
||||
os: "ubuntu-latest",
|
||||
configuration: Release,
|
||||
integration-tests: true,
|
||||
environment: "integration",
|
||||
}
|
||||
- { dotnet: "10.0", os: "windows-latest", configuration: Debug }
|
||||
- { dotnet: "9.0", os: "windows-latest", configuration: Release }
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
environment: ${{ matrix.environment }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
global-json-file: ${{ github.workspace }}/dotnet/global.json
|
||||
|
||||
- name: Build dotnet solutions
|
||||
shell: bash
|
||||
run: |
|
||||
export SOLUTIONS=$(find ./dotnet/ -type f -name "*.slnx" | grep -v "MEVD.slnx" | tr '\n' ' ')
|
||||
for solution in $SOLUTIONS; do
|
||||
dotnet build $solution -c ${{ matrix.configuration }} --warnaserror
|
||||
done
|
||||
|
||||
- name: Package install check
|
||||
shell: bash
|
||||
run: |
|
||||
export SOLUTIONS=$(find ./dotnet/ -type f -name "*.slnx" | grep -v "MEVD.slnx" | tr '\n' ' ')
|
||||
for solution in $SOLUTIONS; do
|
||||
dotnet pack $solution -c ${{ matrix.configuration }} --no-build --no-restore --output ./artifacts
|
||||
done
|
||||
dotnet new console --name packcheck --output consoleapp
|
||||
|
||||
# Create minimal nuget.config and use only dotnet nuget commands
|
||||
echo '<?xml version="1.0" encoding="utf-8"?><configuration><packageSources><clear /></packageSources></configuration>' > consoleapp/nuget.config
|
||||
|
||||
# Add sources with local first using dotnet nuget commands
|
||||
dotnet nuget add source ../artifacts --name local --configfile consoleapp/nuget.config
|
||||
dotnet nuget add source https://api.nuget.org/v3/index.json --name nuget.org --configfile consoleapp/nuget.config
|
||||
|
||||
# Change to project directory to ensure local nuget.config is used
|
||||
cd consoleapp
|
||||
dotnet add packcheck.csproj package Microsoft.SemanticKernel
|
||||
dotnet build -c ${{ matrix.configuration }} packcheck.csproj
|
||||
cd ..
|
||||
|
||||
# Clean up
|
||||
rm -rf ./artifacts
|
||||
rm -rf ./consoleapp
|
||||
|
||||
- name: Run Unit Tests
|
||||
shell: bash
|
||||
run: |
|
||||
export UT_PROJECTS=$(find ./dotnet -type f -name "*.UnitTests.csproj" | grep -v -E "(Experimental.Orchestration.Flow.UnitTests.csproj|Experimental.Assistants.UnitTests.csproj)" | grep -v "test/VectorData/" | tr '\n' ' ')
|
||||
for project in $UT_PROJECTS; do
|
||||
dotnet test -c ${{ matrix.configuration }} $project --no-build -v Normal --logger trx --collect:"XPlat Code Coverage" --results-directory:"TestResults/Coverage/" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.ExcludeByAttribute=GeneratedCodeAttribute,CompilerGeneratedAttribute,ExcludeFromCodeCoverageAttribute
|
||||
done
|
||||
|
||||
- name: Run AOT Unit Tests
|
||||
shell: pwsh
|
||||
run: .github/workflows/test-aot-compatibility.ps1 ${{ matrix.dotnet }}
|
||||
|
||||
- name: Azure CLI Login
|
||||
if: github.event_name != 'pull_request' && matrix.integration-tests
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
|
||||
- name: Run Integration Tests
|
||||
shell: bash
|
||||
if: github.event_name != 'pull_request' && matrix.integration-tests
|
||||
run: |
|
||||
export INTEGRATION_TEST_PROJECTS=$(find ./dotnet -type f -name "*IntegrationTests.csproj" | grep -v "Experimental.Orchestration.Flow.IntegrationTests.csproj" | grep -v "VectorDataIntegrationTests.csproj" | tr '\n' ' ')
|
||||
for project in $INTEGRATION_TEST_PROJECTS; do
|
||||
dotnet test -f net10.0 -c ${{ matrix.configuration }} $project --no-build -v Normal --logger trx
|
||||
done
|
||||
env:
|
||||
# Azure OpenAI Deployments
|
||||
AzureOpenAI__Endpoint: ${{ secrets.AZUREOPENAI__ENDPOINT }}
|
||||
AzureOpenAI__DeploymentName: ${{ vars.AZUREOPENAI__DEPLOYMENTNAME }}
|
||||
AzureOpenAI__ChatDeploymentName: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
|
||||
AzureOpenAIEmbeddings__Endpoint: ${{ secrets.AZUREOPENAI__ENDPOINT }}
|
||||
AzureOpenAIEmbeddings__DeploymentName: ${{ vars.AZUREOPENAIEMBEDDING__DEPLOYMENTNAME }}
|
||||
AzureOpenAITextToAudio__Endpoint: ${{ secrets.AZUREOPENAITEXTTOAUDIO__ENDPOINT }}
|
||||
AzureOpenAITextToAudio__DeploymentName: ${{ vars.AZUREOPENAITEXTTOAUDIO__DEPLOYMENTNAME }}
|
||||
AzureOpenAIAudioToText__Endpoint: ${{ secrets.AZUREOPENAIAUDIOTOTEXT__ENDPOINT }}
|
||||
AzureOpenAIAudioToText__DeploymentName: ${{ vars.AZUREOPENAIAUDIOTOTEXT__DEPLOYMENTNAME }}
|
||||
AzureOpenAITextToImage__Endpoint: ${{ secrets.AZUREOPENAITEXTTOIMAGE__ENDPOINT }}
|
||||
AzureOpenAITextToImage__DeploymentName: ${{ vars.AZUREOPENAITEXTTOIMAGE__DEPLOYMENTNAME }}
|
||||
Planners__AzureOpenAI__Endpoint: ${{ secrets.PLANNERS__AZUREOPENAI__ENDPOINT }}
|
||||
Planners__AzureOpenAI__DeploymentName: ${{ vars.PLANNERS__AZUREOPENAI__DEPLOYMENTNAME }}
|
||||
# OpenAI Models
|
||||
OpenAI__ApiKey: ${{ secrets.OPENAI__APIKEY }}
|
||||
OpenAI__ChatModelId: ${{ vars.OPENAI__CHATMODELID }}
|
||||
OpenAIEmbeddings__ApiKey: ${{ secrets.OPENAIEMBEDDINGS__APIKEY }}
|
||||
OpenAIEmbeddings__ModelId: ${{ vars.OPENAIEMBEDDINGS__MODELID }}
|
||||
OpenAITextToAudio__ApiKey: ${{ secrets.OPENAITEXTTOAUDIO__APIKEY }}
|
||||
OpenAITextToAudio__ModelId: ${{ vars.OPENAITEXTTOAUDIO__MODELID }}
|
||||
OpenAIAudioToText__ApiKey: ${{ secrets.OPENAIAUDIOTOTEXT__APIKEY }}
|
||||
OpenAIAudioToText__ModelId: ${{ vars.OPENAIAUDIOTOTEXT__MODELID }}
|
||||
OpenAITextToImage__ApiKey: ${{ secrets.OPENAITEXTTOIMAGE__APIKEY }}
|
||||
OpenAITextToImage__ModelId: ${{ vars.OPENAITEXTTOIMAGE__MODELID }}
|
||||
Planners__OpenAI__ApiKey: ${{ secrets.PLANNERS__OPENAI__APIKEY }}
|
||||
Planners__OpenAI__ModelId: ${{ vars.PLANNERS__OPENAI__MODELID }}
|
||||
# Bing Web Search
|
||||
Bing__ApiKey: ${{ secrets.BING__APIKEY }}
|
||||
# Google Web Search
|
||||
Google__SearchEngineId: ${{ secrets.GOOGLE__SEARCHENGINEID }}
|
||||
Google__ApiKey: ${{ secrets.GOOGLE__APIKEY }}
|
||||
# Azure AI Inference Endpoint
|
||||
AzureAIInference__ApiKey: ${{ secrets.AZUREAIINFERENCE__APIKEY }}
|
||||
AzureAIInference__Endpoint: ${{ secrets.AZUREAIINFERENCE__ENDPOINT }}
|
||||
AzureAIInference__ChatModelId: ${{ vars.AZUREAIINFERENCE__CHATMODELID }}
|
||||
# Azure AI Foundry
|
||||
AzureAI__Endpoint: ${{ secrets.AZUREAI__ENDPOINT }}
|
||||
AzureAI__ConnectionString: ${{ secrets.AZUREAI__CONNECTIONSTRING }}
|
||||
AzureAI__ChatModelId: ${{ vars.AZUREAI__CHATMODELID }}
|
||||
|
||||
# Generate test reports and check coverage
|
||||
- name: Generate test reports
|
||||
uses: danielpalme/ReportGenerator-GitHub-Action@5.4.12
|
||||
with:
|
||||
reports: "./TestResults/Coverage/**/coverage.cobertura.xml"
|
||||
targetdir: "./TestResults/Reports"
|
||||
reporttypes: "HtmlInline;JsonSummary"
|
||||
|
||||
- name: Upload coverage report artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: CoverageReport-${{ matrix.os }}-${{ matrix.dotnet }}-${{ matrix.configuration }} # Artifact name
|
||||
path: ./TestResults/Reports # Directory containing files to upload
|
||||
|
||||
- name: Check coverage
|
||||
shell: pwsh
|
||||
run: .github/workflows/check-coverage.ps1 -JsonReportPath "TestResults/Reports/Summary.json" -CoverageThreshold $env:COVERAGE_THRESHOLD
|
||||
|
||||
# This final job is required to satisfy the merge queue. It must only run (or succeed) if no tests failed
|
||||
dotnet-build-and-test-check:
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
needs: [dotnet-build-and-test]
|
||||
steps:
|
||||
- name: Get Date
|
||||
shell: bash
|
||||
run: |
|
||||
echo "date=$(date +'%m/%d/%Y %H:%M:%S')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run Type is Daily
|
||||
if: ${{ github.event_name == 'schedule' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "run_type=Daily" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run Type is Manual
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "run_type=Manual" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run Type is ${{ github.event_name }}
|
||||
if: ${{ github.event_name != 'schedule' && github.event_name != 'workflow_dispatch'}}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "run_type=${{ github.event_name }}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Fail workflow if tests failed
|
||||
id: check_tests_failed
|
||||
if: contains(join(needs.*.result, ','), 'failure')
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: core.setFailed('Integration Tests Failed!')
|
||||
|
||||
- name: Fail workflow if tests cancelled
|
||||
id: check_tests_cancelled
|
||||
if: contains(join(needs.*.result, ','), 'cancelled')
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: core.setFailed('Integration Tests Cancelled!')
|
||||
@@ -0,0 +1,129 @@
|
||||
#
|
||||
# This workflow will build and run all tests using dotnet docker containers,
|
||||
# each targeting a single version of the dotnet SDK.
|
||||
#
|
||||
|
||||
name: dotnet-ci
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 7 * * *'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-and-test-docker:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { os: ubuntu-latest, dotnet: '10.0', configuration: Debug }
|
||||
- { os: ubuntu-latest, dotnet: '10.0', configuration: Release }
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
clean: true
|
||||
persist-credentials: false
|
||||
|
||||
- name: Find solutions
|
||||
shell: bash
|
||||
run: echo "solutions=$(find ./ -type f -name "*.slnx" | tr '\n' ' ')" >> $GITHUB_ENV
|
||||
|
||||
- name: Pull container dotnet/sdk:${{ matrix.dotnet }}
|
||||
run: |
|
||||
docker pull mcr.microsoft.com/dotnet/sdk:${{ matrix.dotnet }}
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
for solution in ${{ env.solutions }}; do
|
||||
docker run --rm -v $(pwd):/app -w /app -e GITHUB_ACTIONS='true' mcr.microsoft.com/dotnet/sdk:${{ matrix.dotnet }} /bin/sh -c "dotnet build -c ${{ matrix.configuration }} /app/$solution"
|
||||
done
|
||||
|
||||
- name: Find test projects
|
||||
shell: bash
|
||||
run: echo "testprojects=$(find ./dotnet -type f -name "*UnitTests.csproj" | tr '\n' ' ')" >> $GITHUB_ENV
|
||||
|
||||
- name: Run Tests
|
||||
shell: bash
|
||||
run: |
|
||||
for project in ${{ env.testprojects }}; do
|
||||
docker run --rm -v $(pwd):/app -w /app mcr.microsoft.com/dotnet/sdk:${{ matrix.dotnet }} /bin/sh -c "dotnet test -c ${{ matrix.configuration }} /app/$project --no-build -v Normal --logger trx"
|
||||
done
|
||||
|
||||
- name: Upload dotnet test results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dotnet-testresults-${{ matrix.configuration }}
|
||||
path: ./TestResults
|
||||
if: ${{ always() }}
|
||||
|
||||
build-and-test-windows:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest]
|
||||
configuration: [Release, Debug]
|
||||
dotnet-version: ['10.0.x']
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
NUGET_CERT_REVOCATION_MODE: offline
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
clean: true
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup .NET SDK ${{ matrix.dotnet-version }}
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: ${{ matrix.dotnet-version }}
|
||||
env:
|
||||
NUGET_AUTH_TOKEN: ${{ secrets.GPR_READ_TOKEN }}
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
# Look to see if there is a cache hit for the corresponding requirements file
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nuget
|
||||
|
||||
- name: Find solutions
|
||||
shell: bash
|
||||
run: echo "solutions=$(find ./dotnet -type f -name "*.slnx" | tr '\n' ' ')" >> $GITHUB_ENV
|
||||
|
||||
- name: Restore dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
for solution in ${{ env.solutions }}; do
|
||||
dotnet restore $solution
|
||||
done
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: |
|
||||
for solution in ${{ env.solutions }}; do
|
||||
dotnet build $solution --no-restore --configuration ${{ matrix.configuration }}
|
||||
done
|
||||
|
||||
- name: Find test projects
|
||||
shell: bash
|
||||
run: echo "testprojects=$(find ./dotnet -type f -name "*Tests.csproj" | tr '\n' ' ')" >> $GITHUB_ENV
|
||||
|
||||
- name: Run Tests
|
||||
shell: bash
|
||||
run: |
|
||||
for project in ${{ env.testprojects }}; do
|
||||
dotnet test $project --verbosity normal --logger trx --results-directory ./TestResults --configuration ${{ matrix.configuration }}
|
||||
done
|
||||
|
||||
- name: Upload dotnet test results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dotnet-testresults-${{ matrix.configuration }}
|
||||
path: ./TestResults
|
||||
if: ${{ always() }}
|
||||
@@ -0,0 +1,94 @@
|
||||
#
|
||||
# This workflow runs the dotnet formatter on all c-sharp code.
|
||||
#
|
||||
|
||||
name: dotnet-format
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches: ["main", "feature*"]
|
||||
paths:
|
||||
- "dotnet/**"
|
||||
- "samples/dotnet/**"
|
||||
- "**.cs"
|
||||
- "**.csproj"
|
||||
- "**.editorconfig"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check-format:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { dotnet: "10.0", configuration: Release, os: ubuntu-latest }
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
NUGET_CERT_REVOCATION_MODE: offline
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: jitterbit/get-changed-files@v1
|
||||
continue-on-error: true
|
||||
|
||||
- name: No C# files changed
|
||||
id: no-csharp
|
||||
if: github.event_name == 'pull_request' && steps.changed-files.outputs.added_modified == ''
|
||||
run: echo "No C# files changed"
|
||||
|
||||
# This step will loop over the changed files and find the nearest .csproj file for each one, then store the unique csproj files in a variable
|
||||
- name: Find csproj files
|
||||
id: find-csproj
|
||||
if: github.event_name != 'pull_request' || steps.changed-files.outputs.added_modified != '' || steps.changed-files.outcome == 'failure'
|
||||
run: |
|
||||
csproj_files=()
|
||||
exclude_files=("Experimental.Orchestration.Flow.csproj" "Experimental.Orchestration.Flow.UnitTests.csproj" "Experimental.Orchestration.Flow.IntegrationTests.csproj")
|
||||
if [[ ${{ steps.changed-files.outcome }} == 'success' ]]; then
|
||||
for file in ${{ steps.changed-files.outputs.added_modified }}; do
|
||||
echo "$file was changed"
|
||||
dir="./$file"
|
||||
while [[ $dir != "." && $dir != "/" && $dir != $GITHUB_WORKSPACE ]]; do
|
||||
if find "$dir" -maxdepth 1 -name "*.csproj" -print -quit | grep -q .; then
|
||||
csproj_path="$(find "$dir" -maxdepth 1 -name "*.csproj" -print -quit)"
|
||||
if [[ ! "${exclude_files[@]}" =~ "${csproj_path##*/}" ]]; then
|
||||
csproj_files+=("$csproj_path")
|
||||
fi
|
||||
break
|
||||
fi
|
||||
|
||||
dir=$(echo ${dir%/*})
|
||||
done
|
||||
done
|
||||
else
|
||||
# if the changed-files step failed, run dotnet on the whole slnx instead of specific projects
|
||||
csproj_files=$(find ./ -type f -name "*.slnx" | tr '\n' ' ');
|
||||
fi
|
||||
csproj_files=($(printf "%s\n" "${csproj_files[@]}" | sort -u))
|
||||
echo "Found ${#csproj_files[@]} unique csproj/slnx files: ${csproj_files[*]}"
|
||||
echo "::set-output name=csproj_files::${csproj_files[*]}"
|
||||
|
||||
- name: Pull container dotnet/sdk:${{ matrix.dotnet }}
|
||||
if: steps.find-csproj.outputs.csproj_files != ''
|
||||
run: docker pull mcr.microsoft.com/dotnet/sdk:${{ matrix.dotnet }}
|
||||
|
||||
# This step will run dotnet format on each of the unique csproj files and fail if any changes are made
|
||||
- name: Run dotnet format
|
||||
if: steps.find-csproj.outputs.csproj_files != ''
|
||||
run: |
|
||||
for csproj in ${{ steps.find-csproj.outputs.csproj_files }}; do
|
||||
echo "Running dotnet format on $csproj"
|
||||
docker run --rm -v $(pwd):/app -w /app mcr.microsoft.com/dotnet/sdk:${{ matrix.dotnet }} /bin/sh -c "dotnet format $csproj --verify-no-changes --verbosity diagnostic"
|
||||
done
|
||||
@@ -0,0 +1,67 @@
|
||||
#
|
||||
# This workflow will run all dotnet integrations tests.
|
||||
#
|
||||
|
||||
name: dotnet-integration-tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
merge_group:
|
||||
branches: ["main"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
integration-tests:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
configuration: [Debug]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
with:
|
||||
clean: true
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v5
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
- name: Find projects
|
||||
shell: bash
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
run: echo "projects=$(find ./dotnet -type f -name "*Tests.csproj" | tr '\n' ' ')" >> $GITHUB_ENV
|
||||
|
||||
- name: Integration Tests
|
||||
shell: bash
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
env: # Set Azure credentials secret as an input
|
||||
AzureOpenAI__Label: azure-text-davinci-003
|
||||
AzureOpenAIEmbedding__Label: azure-text-embedding-ada-002
|
||||
AzureOpenAI__DeploymentName: ${{ vars.AZUREOPENAI__DEPLOYMENTNAME }}
|
||||
AzureOpenAIEmbeddings__DeploymentName: ${{ vars.AZUREOPENAIEMBEDDING__DEPLOYMENTNAME }}
|
||||
AzureOpenAI__Endpoint: ${{ secrets.AZUREOPENAI__ENDPOINT }}
|
||||
AzureOpenAIEmbeddings__Endpoint: ${{ secrets.AZUREOPENAI__ENDPOINT }}
|
||||
AzureOpenAI__ApiKey: ${{ secrets.AZUREOPENAI__APIKEY }}
|
||||
AzureOpenAIEmbeddings__ApiKey: ${{ secrets.AZUREOPENAI__APIKEY }}
|
||||
AzureAI__ConnectionString: ${{ secrets.AZUREAI__CONNECTIONSTRING }}
|
||||
Bing__ApiKey: ${{ secrets.BING__APIKEY }}
|
||||
OpenAI__ApiKey: ${{ secrets.OPENAI__APIKEY }}
|
||||
run: |
|
||||
for project in ${{ env.projects }}; do
|
||||
dotnet test $project --verbosity normal --logger trx --results-directory ./TestResults --configuration ${{ matrix.configuration }}
|
||||
done
|
||||
|
||||
- name: Upload dotnet test results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dotnet-testresults-${{ matrix.configuration }}
|
||||
path: ./TestResults
|
||||
if: ${{ github.event_name != 'pull_request' && always() }}
|
||||
@@ -0,0 +1,107 @@
|
||||
name: Label Discussions
|
||||
|
||||
on:
|
||||
discussion:
|
||||
types:
|
||||
- created
|
||||
|
||||
jobs:
|
||||
label_discussions:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
discussions: write
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Always add "triage" label
|
||||
if: >
|
||||
github.event.discussion.node_id != '' &&
|
||||
(github.event.discussion.category.name == 'General' ||
|
||||
github.event.discussion.category.name == 'Ideas' ||
|
||||
github.event.discussion.category.name == 'Q&A')
|
||||
uses: octokit/graphql-action@v2.x
|
||||
with:
|
||||
query: |
|
||||
mutation($labelableId: ID!) {
|
||||
addLabelsToLabelable(
|
||||
input: {
|
||||
labelableId: $labelableId
|
||||
labelIds: ["LA_kwDOJDJ_Yc8AAAABU2klmQ"]
|
||||
}
|
||||
) {
|
||||
clientMutationId
|
||||
}
|
||||
}
|
||||
variables: |
|
||||
{
|
||||
"labelableId": "${{ github.event.discussion.node_id }}"
|
||||
}
|
||||
|
||||
- name: Conditionally add "python" label
|
||||
if: >
|
||||
github.event.discussion.node_id != '' &&
|
||||
(github.event.discussion.category.name == 'General' ||
|
||||
github.event.discussion.category.name == 'Ideas' ||
|
||||
github.event.discussion.category.name == 'Q&A') &&
|
||||
(contains(github.event.discussion.body, 'python') ||
|
||||
contains(github.event.discussion.body, 'Python') ||
|
||||
contains(github.event.discussion.title, 'python') ||
|
||||
contains(github.event.discussion.title, 'Python'))
|
||||
uses: octokit/graphql-action@v2.x
|
||||
with:
|
||||
query: |
|
||||
mutation($labelableId: ID!) {
|
||||
addLabelsToLabelable(
|
||||
input: {
|
||||
labelableId: $labelableId
|
||||
labelIds: ["LA_kwDOJDJ_Yc8AAAABO0f2Lg"]
|
||||
}
|
||||
) {
|
||||
clientMutationId
|
||||
}
|
||||
}
|
||||
variables: |
|
||||
{
|
||||
"labelableId": "${{ github.event.discussion.node_id }}"
|
||||
}
|
||||
|
||||
- name: Conditionally add ".NET" label
|
||||
if: >
|
||||
github.event.discussion.node_id != '' &&
|
||||
(github.event.discussion.category.name == 'General' ||
|
||||
github.event.discussion.category.name == 'Ideas' ||
|
||||
github.event.discussion.category.name == 'Q&A') &&
|
||||
(contains(github.event.discussion.body, '.net') ||
|
||||
contains(github.event.discussion.body, '.NET') ||
|
||||
contains(github.event.discussion.title, '.net') ||
|
||||
contains(github.event.discussion.title, '.NET') ||
|
||||
contains(github.event.discussion.body, 'dotnet') ||
|
||||
contains(github.event.discussion.body, 'DotNet') ||
|
||||
contains(github.event.discussion.title, 'dotnet') ||
|
||||
contains(github.event.discussion.title, 'DotNet') ||
|
||||
contains(github.event.discussion.body, 'c#') ||
|
||||
contains(github.event.discussion.body, 'C#') ||
|
||||
contains(github.event.discussion.title, 'c#') ||
|
||||
contains(github.event.discussion.title, 'C#') ||
|
||||
contains(github.event.discussion.body, 'csharp') ||
|
||||
contains(github.event.discussion.body, 'CSharp') ||
|
||||
contains(github.event.discussion.title, 'csharp') ||
|
||||
contains(github.event.discussion.title, 'CSharp'))
|
||||
uses: octokit/graphql-action@v2.x
|
||||
with:
|
||||
query: |
|
||||
mutation($labelableId: ID!) {
|
||||
addLabelsToLabelable(
|
||||
input: {
|
||||
labelableId: $labelableId
|
||||
labelIds: ["LA_kwDOJDJ_Yc8AAAABN_r7Lw"]
|
||||
}
|
||||
) {
|
||||
clientMutationId
|
||||
}
|
||||
}
|
||||
variables: |
|
||||
{
|
||||
"labelableId": "${{ github.event.discussion.node_id }}"
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
name: Label issues
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- reopened
|
||||
- opened
|
||||
|
||||
jobs:
|
||||
label_issues:
|
||||
name: "Issue: add labels"
|
||||
if: ${{ github.event.action == 'opened' || github.event.action == 'reopened' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
|
||||
script: |
|
||||
// Get the issue body and title
|
||||
const body = context.payload.issue.body
|
||||
let title = context.payload.issue.title
|
||||
|
||||
// Define the labels array
|
||||
let labels = ["triage"]
|
||||
|
||||
// Check if the body or the title contains the word 'python' (case-insensitive)
|
||||
if ((body != null && body.match(/python/i)) || (title != null && title.match(/python/i))) {
|
||||
// Add the 'python' label to the array
|
||||
labels.push("python")
|
||||
}
|
||||
|
||||
// Check if the body or the title contains the word 'java' (case-insensitive)
|
||||
if ((body != null && body.match(/java/i)) || (title != null && title.match(/java/i))) {
|
||||
// Add the 'java' label to the array
|
||||
labels.push("java")
|
||||
}
|
||||
|
||||
// Check if the body or the title contains the words 'dotnet', '.net', 'c#' or 'csharp' (case-insensitive)
|
||||
if ((body != null && body.match(/.net/i)) || (title != null && title.match(/.net/i)) ||
|
||||
(body != null && body.match(/dotnet/i)) || (title != null && title.match(/dotnet/i)) ||
|
||||
(body != null && body.match(/C#/i)) || (title != null && title.match(/C#/i)) ||
|
||||
(body != null && body.match(/csharp/i)) || (title != null && title.match(/csharp/i))) {
|
||||
// Add the '.NET' label to the array
|
||||
labels.push(".NET")
|
||||
}
|
||||
|
||||
// Add the labels to the issue
|
||||
github.rest.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: labels
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
# This workflow will triage pull requests and apply a label based on the
|
||||
# paths that are modified in the pull request.
|
||||
#
|
||||
# To use this workflow, you will need to set up a .github/labeler.yml
|
||||
# file with configuration. For more information, see:
|
||||
# https://github.com/actions/labeler
|
||||
|
||||
name: Label pull request
|
||||
on: [pull_request_target]
|
||||
|
||||
jobs:
|
||||
add_label:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/labeler@v4
|
||||
with:
|
||||
repo-token: "${{ secrets.GH_ACTIONS_PR_WRITE }}"
|
||||
@@ -0,0 +1,73 @@
|
||||
name: Label title prefix
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
pull_request_target:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
add_title_prefix:
|
||||
name: "Issue/PR: add title prefix"
|
||||
continue-on-error: true
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
name: "Issue/PR: update title"
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
let prefixLabels = {
|
||||
"python": "Python",
|
||||
"java": "Java",
|
||||
".NET": ".Net"
|
||||
};
|
||||
|
||||
function addTitlePrefix(title, prefix)
|
||||
{
|
||||
// Update the title based on the label and prefix
|
||||
// Check if the title starts with the prefix (case-sensitive)
|
||||
if (!title.startsWith(prefix + ": ")) {
|
||||
// If not, check if the first word is the label (case-insensitive)
|
||||
if (title.match(new RegExp(`^${prefix}`, 'i'))) {
|
||||
// If yes, replace it with the prefix (case-sensitive)
|
||||
title = title.replace(new RegExp(`^${prefix}`, 'i'), prefix);
|
||||
} else {
|
||||
// If not, prepend the prefix to the title
|
||||
title = prefix + ": " + title;
|
||||
}
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
labelAdded = context.payload.label.name
|
||||
|
||||
// Check if the issue or PR has the label
|
||||
if (labelAdded in prefixLabels) {
|
||||
let prefix = prefixLabels[labelAdded];
|
||||
switch(context.eventName) {
|
||||
case 'issues':
|
||||
github.rest.issues.update({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title: addTitlePrefix(context.payload.issue.title, prefix)
|
||||
});
|
||||
break
|
||||
|
||||
case 'pull_request_target':
|
||||
github.rest.pulls.update({
|
||||
pull_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title: addTitlePrefix(context.payload.pull_request.title, prefix)
|
||||
});
|
||||
break
|
||||
default:
|
||||
core.setFailed('Unrecognited eventName: ' + context.eventName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
name: Check .md links
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
markdown-link-check:
|
||||
runs-on: ubuntu-22.04
|
||||
# check out the latest version of the code
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Checks the status of hyperlinks in all files
|
||||
- name: Run linkspector
|
||||
uses: umbrelladocs/action-linkspector@v1
|
||||
with:
|
||||
reporter: local
|
||||
filter_mode: nofilter
|
||||
fail_on_error: true
|
||||
config_file: ".github/.linkspector.yml"
|
||||
@@ -0,0 +1,32 @@
|
||||
name: Merge Gatekeeper
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "main", "feature*" ]
|
||||
merge_group:
|
||||
branches: ["main"]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
merge-gatekeeper:
|
||||
runs-on: ubuntu-latest
|
||||
# Restrict permissions of the GITHUB_TOKEN.
|
||||
# Docs: https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs
|
||||
permissions:
|
||||
checks: read
|
||||
statuses: read
|
||||
steps:
|
||||
- name: Run Merge Gatekeeper
|
||||
# NOTE: v1 is updated to reflect the latest v1.x.y. Please use any tag/branch that suits your needs:
|
||||
# https://github.com/upsidr/merge-gatekeeper/tags
|
||||
# https://github.com/upsidr/merge-gatekeeper/branches
|
||||
uses: upsidr/merge-gatekeeper@v1
|
||||
if: github.event_name == 'pull_request'
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout: 3600
|
||||
interval: 30
|
||||
ignored: python-tests-coverage
|
||||
@@ -0,0 +1,48 @@
|
||||
name: Python Build Assets
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
python-build-assets:
|
||||
if: github.event_name == 'release' && startsWith(github.event.release.tag_name, 'python-')
|
||||
name: Python Build Assets and add to Release
|
||||
runs-on: ubuntu-latest
|
||||
environment: "integration"
|
||||
env:
|
||||
UV_PYTHON: "3.10"
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: "0.5.x"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ runner.os }}-${{ matrix.python-version }}
|
||||
cache-dependency-glob: "**/uv.lock"
|
||||
- name: Check version
|
||||
run: |
|
||||
echo "Building and uploading Python package version: ${{ github.event.release.tag_name }}"
|
||||
- name: Build the package
|
||||
run: cd python && make build
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
python/dist/*
|
||||
- name: Azure Login
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
- name: Start DevOps pipeline
|
||||
uses: azure/cli@v2
|
||||
with:
|
||||
inlineScript: |
|
||||
az pipelines run --id ${{ vars.ADO_PYTHON_RELEASE_ID }} --org ${{ vars.ADO_ORG }} --project ${{ vars.ADO_PROJECT_NAME }} --parameters tag=${{ github.event.release.tag_name }} delay=0
|
||||
@@ -0,0 +1,566 @@
|
||||
#
|
||||
# This workflow will run all python integrations tests.
|
||||
#
|
||||
|
||||
name: Python Integration Tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
merge_group:
|
||||
branches: ["main"]
|
||||
schedule:
|
||||
- cron: "0 0 * * *" # Run at midnight UTC daily
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: "write"
|
||||
|
||||
env:
|
||||
# Configure a constant location for the uv cache
|
||||
UV_CACHE_DIR: /tmp/.uv-cache
|
||||
Python_Integration_Tests: Python_Integration_Tests
|
||||
INTEGRATION_TEST_SERVICE_SETUP_EXCEPTION: ${{ true }}
|
||||
AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME }} # azure-text-embedding-ada-002
|
||||
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_CHAT_DEPLOYMENT_NAME }}
|
||||
AZURE_OPENAI_TEXT_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_TEXT_DEPLOYMENT_NAME }}
|
||||
AZURE_OPENAI_AUDIO_TO_TEXT_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_AUDIO_TO_TEXT_DEPLOYMENT_NAME }}
|
||||
AZURE_OPENAI_TEXT_TO_AUDIO_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_TEXT_TO_AUDIO_DEPLOYMENT_NAME }}
|
||||
AZURE_OPENAI_TEXT_TO_IMAGE_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_TEXT_TO_IMAGE_DEPLOYMENT_NAME }}
|
||||
AZURE_OPENAI_API_VERSION: ${{ vars.AZURE_OPENAI_API_VERSION }}
|
||||
AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }}
|
||||
AZURE_OPENAI_AUDIO_TO_TEXT_ENDPOINT: ${{ secrets.AZURE_OPENAI_AUDIO_TO_TEXT_ENDPOINT }}
|
||||
AZURE_OPENAI_TEXT_TO_AUDIO_ENDPOINT: ${{ secrets.AZURE_OPENAI_TEXT_TO_AUDIO_ENDPOINT }}
|
||||
AZURE_AI_AGENT_ENDPOINT: ${{ secrets.AZURE_AI_AGENT_ENDPOINT }}
|
||||
AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME: ${{ secrets.AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME }}
|
||||
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME }}
|
||||
BING_API_KEY: ${{ secrets.BING_API_KEY }}
|
||||
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI_RESPONSES_MODEL_ID }}
|
||||
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI_CHAT_MODEL_ID }}
|
||||
OPENAI_TEXT_MODEL_ID: ${{ vars.OPENAI_TEXT_MODEL_ID }}
|
||||
OPENAI_EMBEDDING_MODEL_ID: ${{ vars.OPENAI_EMBEDDING_MODEL_ID }}
|
||||
OPENAI_AUDIO_TO_TEXT_MODEL_ID: ${{ vars.OPENAI_AUDIO_TO_TEXT_MODEL_ID }}
|
||||
OPENAI_TEXT_TO_AUDIO_MODEL_ID: ${{ vars.OPENAI_TEXT_TO_AUDIO_MODEL_ID }}
|
||||
OPENAI_TEXT_TO_IMAGE_MODEL_ID: ${{ vars.OPENAI_TEXT_TO_IMAGE_MODEL_ID }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
PINECONE_API_KEY: ${{ secrets.PINECONE__APIKEY }}
|
||||
POSTGRES_CONNECTION_STRING: ${{secrets.POSTGRES__CONNECTIONSTR}}
|
||||
POSTGRES_MAX_POOL: ${{ vars.POSTGRES_MAX_POOL }}
|
||||
AZURE_AI_SEARCH_API_KEY: ${{secrets.AZURE_AI_SEARCH_API_KEY}}
|
||||
AZURE_AI_SEARCH_ENDPOINT: ${{secrets.AZURE_AI_SEARCH_ENDPOINT}}
|
||||
MONGODB_ATLAS_CONNECTION_STRING: ${{secrets.MONGODB_ATLAS_CONNECTION_STRING}}
|
||||
AZURE_KEY_VAULT_ENDPOINT: ${{secrets.AZURE_KEY_VAULT_ENDPOINT}}
|
||||
AZURE_KEY_VAULT_CLIENT_ID: ${{secrets.AZURE_KEY_VAULT_CLIENT_ID}}
|
||||
AZURE_KEY_VAULT_CLIENT_SECRET: ${{secrets.AZURE_KEY_VAULT_CLIENT_SECRET}}
|
||||
ACA_POOL_MANAGEMENT_ENDPOINT: ${{secrets.ACA_POOL_MANAGEMENT_ENDPOINT}}
|
||||
MISTRALAI_API_KEY: ${{secrets.MISTRALAI_API_KEY}}
|
||||
MISTRALAI_CHAT_MODEL_ID: ${{ vars.MISTRALAI_CHAT_MODEL_ID }}
|
||||
MISTRALAI_EMBEDDING_MODEL_ID: ${{ vars.MISTRALAI_EMBEDDING_MODEL_ID }}
|
||||
ANTHROPIC_API_KEY: ${{secrets.ANTHROPIC_API_KEY}}
|
||||
ANTHROPIC_CHAT_MODEL_ID: ${{ vars.ANTHROPIC_CHAT_MODEL_ID }}
|
||||
OLLAMA_CHAT_MODEL_ID: "${{ vars.OLLAMA_CHAT_MODEL_ID || '' }}" # llama3.2:1b
|
||||
OLLAMA_CHAT_MODEL_ID_IMAGE: "${{ vars.OLLAMA_CHAT_MODEL_ID_IMAGE || '' }}" # moondream
|
||||
OLLAMA_CHAT_MODEL_ID_TOOL_CALL: "${{ vars.OLLAMA_CHAT_MODEL_ID_TOOL_CALL || '' }}" # llama3.2:1b
|
||||
OLLAMA_TEXT_MODEL_ID: "${{ vars.OLLAMA_TEXT_MODEL_ID || '' }}" # llama3.2:1b
|
||||
OLLAMA_EMBEDDING_MODEL_ID: "${{ vars.OLLAMA_EMBEDDING_MODEL_ID || '' }}" # nomic-embed-text
|
||||
GOOGLE_AI_GEMINI_MODEL_ID: ${{ vars.GOOGLE_AI_GEMINI_MODEL_ID }}
|
||||
GOOGLE_AI_EMBEDDING_MODEL_ID: ${{ vars.GOOGLE_AI_EMBEDDING_MODEL_ID }}
|
||||
GOOGLE_AI_API_KEY: ${{ secrets.GOOGLE_AI_API_KEY }}
|
||||
GOOGLE_AI_CLOUD_PROJECT_ID: ${{ vars.GOOGLE_AI_CLOUD_PROJECT_ID }}
|
||||
GOOGLE_AI_CLOUD_REGION: ${{ vars.GOOGLE_AI_CLOUD_REGION }}
|
||||
VERTEX_AI_PROJECT_ID: ${{ vars.VERTEX_AI_PROJECT_ID }}
|
||||
VERTEX_AI_GEMINI_MODEL_ID: ${{ vars.VERTEX_AI_GEMINI_MODEL_ID }}
|
||||
VERTEX_AI_EMBEDDING_MODEL_ID: ${{ vars.VERTEX_AI_EMBEDDING_MODEL_ID }}
|
||||
REDIS_CONNECTION_STRING: ${{ vars.REDIS_CONNECTION_STRING }}
|
||||
AZURE_COSMOS_DB_NO_SQL_URL: ${{ vars.AZURE_COSMOS_DB_NO_SQL_URL }}
|
||||
AZURE_COSMOS_DB_NO_SQL_KEY: ${{ secrets.AZURE_COSMOS_DB_NO_SQL_KEY }}
|
||||
BEDROCK_AGENT_AGENT_RESOURCE_ROLE_ARN: ${{ secrets.BEDROCK_AGENT_AGENT_RESOURCE_ROLE_ARN }}
|
||||
BEDROCK_AGENT_FOUNDATION_MODEL: ${{ vars.BEDROCK_AGENT_FOUNDATION_MODEL }}
|
||||
|
||||
jobs:
|
||||
paths-filter:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
pythonChanges: ${{ steps.filter.outputs.python}}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
python:
|
||||
- 'python/**'
|
||||
# run only if 'python' files were changed
|
||||
- name: python tests
|
||||
if: steps.filter.outputs.python == 'true'
|
||||
run: echo "Python file"
|
||||
# run only if not 'python' files were changed
|
||||
- name: not python tests
|
||||
if: steps.filter.outputs.python != 'true'
|
||||
run: echo "NOT python file"
|
||||
|
||||
python-merge-gate-ai-services:
|
||||
name: Python Pre-Merge Integration Tests - AI Services (incl samples using those)
|
||||
needs: paths-filter
|
||||
if: github.event_name != 'pull_request' && github.event_name != 'schedule' && needs.paths-filter.outputs.pythonChanges == 'true'
|
||||
strategy:
|
||||
max-parallel: 1
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.11"]
|
||||
os: [ubuntu-latest]
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python
|
||||
runs-on: ${{ matrix.os }}
|
||||
environment: "integration"
|
||||
env:
|
||||
UV_PYTHON: ${{ matrix.python-version }}
|
||||
COMPLETIONS_CONCEPT_SAMPLE: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: "0.5.x"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ runner.os }}-${{ matrix.python-version }}
|
||||
cache-dependency-glob: "**/uv.lock"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv sync --all-extras --dev
|
||||
- name: Google auth
|
||||
uses: google-github-actions/auth@v3
|
||||
with:
|
||||
project_id: ${{ vars.VERTEX_AI_PROJECT_ID }}
|
||||
credentials_json: ${{ secrets.VERTEX_AI_SERVICE_ACCOUNT_KEY }}
|
||||
- name: Set up gcloud
|
||||
uses: google-github-actions/setup-gcloud@v3
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v5
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
- name: Azure CLI Login
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
- name: Run Integration Tests
|
||||
id: run_tests_ai_services
|
||||
shell: bash
|
||||
run: |
|
||||
uv run pytest -v --log-cli-level=INFO --durations=20 -n logical --dist loadfile --dist worksteal -m "not ollama" ./tests/integration/completions ./tests/integration/embeddings ./tests/samples ./tests/integration/cross_language
|
||||
|
||||
python-merge-gate-multi-modality:
|
||||
name: Python Pre-Merge Integration Tests - Multi-Modality
|
||||
needs: paths-filter
|
||||
if: github.event_name != 'pull_request' && github.event_name != 'schedule' && needs.paths-filter.outputs.pythonChanges == 'true'
|
||||
strategy:
|
||||
max-parallel: 1
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.11"]
|
||||
os: [ubuntu-latest]
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python
|
||||
runs-on: ${{ matrix.os }}
|
||||
environment: "integration"
|
||||
env:
|
||||
UV_PYTHON: ${{ matrix.python-version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: "0.5.x"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ runner.os }}-${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv sync --all-extras --dev
|
||||
- name: Azure CLI Login
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
- name: Run Integration Tests
|
||||
id: run_tests_multi_modality
|
||||
shell: bash
|
||||
run: |
|
||||
uv run pytest -v --log-cli-level=INFO --durations=20 -n logical --dist loadfile --dist worksteal ./tests/integration/audio_to_text ./tests/integration/text_to_audio ./tests/integration/text_to_image
|
||||
|
||||
python-merge-gate-agents:
|
||||
name: Python Pre-Merge Integration Tests - Agents
|
||||
needs: paths-filter
|
||||
if: github.event_name != 'pull_request' && github.event_name != 'schedule' && needs.paths-filter.outputs.pythonChanges == 'true'
|
||||
strategy:
|
||||
max-parallel: 1
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.11"]
|
||||
os: [ubuntu-latest]
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python
|
||||
runs-on: ${{ matrix.os }}
|
||||
environment: "integration"
|
||||
env:
|
||||
UV_PYTHON: ${{ matrix.python-version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: "0.5.x"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ runner.os }}-${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv sync --all-extras --dev
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v5
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
- name: Azure CLI Login
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
- name: Run Integration Tests
|
||||
id: run_tests_agents
|
||||
shell: bash
|
||||
run: |
|
||||
uv run pytest -v --log-cli-level=INFO --durations=20 -n logical --dist loadfile --dist worksteal ./tests/integration/agents
|
||||
|
||||
python-merge-gate-ollama:
|
||||
name: Python Pre-Merge Integration Tests - Ollama
|
||||
needs: paths-filter
|
||||
# Ollama tests are very unstable at the moment. It often fails to pull models from the Ollama server. Thus, this job is disabled for now.
|
||||
if: false && github.event_name != 'pull_request' && github.event_name != 'schedule' && needs.paths-filter.outputs.pythonChanges == 'true'
|
||||
strategy:
|
||||
max-parallel: 1
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.11"]
|
||||
os: [ubuntu-latest]
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python
|
||||
runs-on: ${{ matrix.os }}
|
||||
environment: "integration"
|
||||
env:
|
||||
UV_PYTHON: ${{ matrix.python-version }}
|
||||
COMPLETIONS_CONCEPT_SAMPLE: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: "0.5.x"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ runner.os }}-${{ matrix.python-version }}
|
||||
cache-dependency-glob: "**/uv.lock"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv sync --all-extras --dev
|
||||
- name: Install Ollama
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
curl -fsSL https://ollama.com/install.sh | sh
|
||||
ollama serve &
|
||||
sleep 5
|
||||
- name: Pull model in Ollama
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
ollama pull ${{ vars.OLLAMA_CHAT_MODEL_ID }}
|
||||
ollama pull ${{ vars.OLLAMA_CHAT_MODEL_ID_IMAGE }}
|
||||
ollama pull ${{ vars.OLLAMA_CHAT_MODEL_ID_TOOL_CALL }}
|
||||
ollama pull ${{ vars.OLLAMA_TEXT_MODEL_ID }}
|
||||
ollama pull ${{ vars.OLLAMA_EMBEDDING_MODEL_ID }}
|
||||
ollama list
|
||||
- name: Run Integration Tests
|
||||
id: run_tests_ai_services
|
||||
shell: bash
|
||||
run: |
|
||||
uv run pytest -v --log-cli-level=INFO --durations=0 -n logical --dist loadfile --dist worksteal -m ollama --timeout=300 ./tests/integration/completions ./tests/integration/embeddings
|
||||
|
||||
python-merge-gate-memory:
|
||||
name: Python Pre-Merge Integration Tests - Memory (incl samples using those)
|
||||
needs: paths-filter
|
||||
if: github.event_name != 'pull_request' && github.event_name != 'schedule' && needs.paths-filter.outputs.pythonChanges == 'true'
|
||||
strategy:
|
||||
max-parallel: 1
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.11"]
|
||||
os: [ubuntu-latest]
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python
|
||||
runs-on: ${{ matrix.os }}
|
||||
environment: "integration"
|
||||
env:
|
||||
UV_PYTHON: ${{ matrix.python-version }}
|
||||
MEMORY_CONCEPT_SAMPLE: "true"
|
||||
# Service containers to run with for the memory connectors, this only works on Ubuntu
|
||||
services:
|
||||
# Label used to access the service container
|
||||
redis:
|
||||
# Docker Hub image
|
||||
image: redis/redis-stack-server:latest
|
||||
ports:
|
||||
# Opens tcp port 6379 on the host and service container
|
||||
- 6379:6379
|
||||
weaviate:
|
||||
image: cr.weaviate.io/semitechnologies/weaviate:1.33.3
|
||||
ports:
|
||||
- 8080:8080
|
||||
- 50051:50051
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: "0.5.x"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ runner.os }}-${{ matrix.python-version }}
|
||||
cache-dependency-glob: "**/uv.lock"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv sync --all-extras --dev
|
||||
- name: Azure CLI Login
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
- name: Run Integration Tests
|
||||
id: run_tests_memory
|
||||
timeout-minutes: 15
|
||||
shell: bash
|
||||
run: |
|
||||
uv run pytest -v --log-cli-level=INFO --durations=20 -n logical --dist loadfile --dist worksteal ./tests/integration/memory ./tests/samples
|
||||
|
||||
python-integration-tests:
|
||||
name: Python Integration Tests - Scheduled run
|
||||
needs: paths-filter
|
||||
if: (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && needs.paths-filter.outputs.pythonChanges == 'true'
|
||||
strategy:
|
||||
max-parallel: 1
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.10", "3.11", "3.12"]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
UV_PYTHON: ${{ matrix.python-version }}
|
||||
MEMORY_CONCEPT_SAMPLE: "true"
|
||||
COMPLETIONS_CONCEPT_SAMPLE: "true"
|
||||
# Service containers to run with for the memory connectors, this only works on Ubuntu
|
||||
services:
|
||||
# Label used to access the service container
|
||||
redis:
|
||||
# Docker Hub image
|
||||
image: redis/redis-stack-server:latest
|
||||
ports:
|
||||
# Opens tcp port 6379 on the host and service container
|
||||
- 6379:6379
|
||||
weaviate:
|
||||
image: cr.weaviate.io/semitechnologies/weaviate:1.26.6
|
||||
ports:
|
||||
- 8080:8080
|
||||
- 50051:50051
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: "0.5.x"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ runner.os }}-${{ matrix.python-version }}
|
||||
cache-dependency-glob: "**/uv.lock"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv sync --all-extras --dev
|
||||
- name: Install Ollama
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
curl -fsSL https://ollama.com/install.sh | sh
|
||||
ollama serve &
|
||||
sleep 5
|
||||
- name: Pull model in Ollama
|
||||
# Ollama tests are very unstable at the moment. It often fails to pull models from the Ollama server. Thus, Ollama is disabled for now.
|
||||
if: false && matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
ollama pull ${{ vars.OLLAMA_CHAT_MODEL_ID }}
|
||||
ollama pull ${{ vars.OLLAMA_CHAT_MODEL_ID_IMAGE }}
|
||||
ollama pull ${{ vars.OLLAMA_CHAT_MODEL_ID_TOOL_CALL }}
|
||||
ollama pull ${{ vars.OLLAMA_TEXT_MODEL_ID }}
|
||||
ollama pull ${{ vars.OLLAMA_EMBEDDING_MODEL_ID }}
|
||||
ollama list
|
||||
- name: Google auth
|
||||
uses: google-github-actions/auth@v3
|
||||
with:
|
||||
project_id: ${{ vars.VERTEX_AI_PROJECT_ID }}
|
||||
credentials_json: ${{ secrets.VERTEX_AI_SERVICE_ACCOUNT_KEY }}
|
||||
- name: Set up gcloud
|
||||
uses: google-github-actions/setup-gcloud@v3
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v5
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
- name: Start Azure Cosmos DB emulator
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
Write-Host "Launching Cosmos DB Emulator"
|
||||
Import-Module "$env:ProgramFiles\Azure Cosmos DB Emulator\PSModules\Microsoft.Azure.CosmosDB.Emulator"
|
||||
Start-CosmosDbEmulator
|
||||
- name: Azure CLI Login
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
- name: Run Integration Tests - Completions
|
||||
id: run_tests_completions
|
||||
timeout-minutes: 10
|
||||
shell: bash
|
||||
# Ollama tests are very unstable at the moment. It often fails to pull models from the Ollama server. Thus, Ollama is disabled for now.
|
||||
run: |
|
||||
uv run pytest -v -n logical --dist loadfile --dist worksteal -m "not ollama" ./tests/integration/completions
|
||||
- name: Run Integration Tests - Embeddings
|
||||
id: run_tests_embeddings
|
||||
timeout-minutes: 5
|
||||
shell: bash
|
||||
# Ollama tests are very unstable at the moment. It often fails to pull models from the Ollama server. Thus, Ollama is disabled for now.
|
||||
run: |
|
||||
uv run pytest -v -n logical --dist loadfile --dist worksteal -m "not ollama" ./tests/integration/embeddings
|
||||
- name: Run Integration Tests - Memory
|
||||
id: run_tests_memory
|
||||
timeout-minutes: 5
|
||||
shell: bash
|
||||
run: |
|
||||
uv run pytest -v -n logical --dist loadfile --dist worksteal ./tests/integration/memory
|
||||
- name: Run Integration Tests - Cross Language
|
||||
id: run_tests_cross_language
|
||||
timeout-minutes: 5
|
||||
shell: bash
|
||||
run: |
|
||||
uv run pytest -v -n logical --dist loadfile --dist worksteal ./tests/integration/cross_language
|
||||
- name: Run Integration Tests - Planning
|
||||
id: run_tests_planning
|
||||
timeout-minutes: 5
|
||||
shell: bash
|
||||
run: |
|
||||
uv run pytest -v -n logical --dist loadfile --dist worksteal ./tests/integration/planning
|
||||
- name: Run Integration Tests - Samples
|
||||
id: run_tests_samples
|
||||
timeout-minutes: 5
|
||||
shell: bash
|
||||
run: |
|
||||
uv run pytest -v -n logical --dist loadfile --dist worksteal ./tests/samples
|
||||
- name: Run Integration Tests - Agents
|
||||
id: run_tests_agents
|
||||
timeout-minutes: 10
|
||||
shell: bash
|
||||
run: |
|
||||
uv run pytest -v -n logical --dist loadfile --dist worksteal ./tests/integration/agents
|
||||
- name: Run Integration Tests - Multi-Modality
|
||||
id: run_tests_multi_modality
|
||||
timeout-minutes: 5
|
||||
shell: bash
|
||||
run: |
|
||||
uv run pytest -v -n logical --dist loadfile --dist worksteal ./tests/integration/audio_to_text ./tests/integration/text_to_audio ./tests/integration/text_to_image
|
||||
|
||||
# This final job is required to satisfy the merge queue. It must only run (or succeed) if no tests failed
|
||||
python-integration-tests-check:
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
max-parallel: 1
|
||||
fail-fast: false
|
||||
needs:
|
||||
[
|
||||
python-merge-gate-ai-services,
|
||||
python-merge-gate-ollama,
|
||||
python-merge-gate-memory,
|
||||
python-merge-gate-agents,
|
||||
python-merge-gate-multi-modality,
|
||||
python-integration-tests,
|
||||
]
|
||||
steps:
|
||||
- name: Get Date
|
||||
shell: bash
|
||||
run: |
|
||||
echo "date=$(date +'%m/%d/%Y %H:%M:%S')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run Type is Daily
|
||||
if: ${{ github.event_name == 'schedule' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "run_type=Daily" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run Type is Manual
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "run_type=Manual" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run Type is ${{ github.event_name }}
|
||||
if: ${{ github.event_name != 'schedule' && github.event_name != 'workflow_dispatch'}}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "run_type=${{ github.event_name }}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Fail workflow if tests failed
|
||||
id: check_tests_failed
|
||||
if: contains(join(needs.*.result, ','), 'failure')
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: core.setFailed('Integration Tests Failed!')
|
||||
|
||||
- name: Fail workflow if tests cancelled
|
||||
id: check_tests_cancelled
|
||||
if: contains(join(needs.*.result, ','), 'cancelled')
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: core.setFailed('Integration Tests Cancelled!')
|
||||
|
||||
- name: Microsoft Teams Notification
|
||||
uses: skitionek/notify-microsoft-teams@v1.0.9
|
||||
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
||||
with:
|
||||
webhook_url: ${{ secrets.MSTEAMS_WEBHOOK }}
|
||||
dry_run: ${{ env.run_type != 'Daily' && env.run_type != 'Manual'}}
|
||||
job: ${{ toJson(job) }}
|
||||
steps: ${{ toJson(steps) }}
|
||||
title: "{title: ` ${{ env.run_type }}: ${{ env.date }} `, text: ` ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}`}"
|
||||
|
||||
- name: Microsoft Teams Notification (Dry Run)
|
||||
uses: skitionek/notify-microsoft-teams@v1.0.9
|
||||
if: github.ref != 'refs/heads/main'
|
||||
with:
|
||||
webhook_url: NONE
|
||||
dry_run: ${{ env.run_type != 'Daily' && env.run_type != 'Manual'}}
|
||||
job: ${{ toJson(job) }}
|
||||
steps: ${{ toJson(steps) }}
|
||||
title: "{title: ` ${{ env.run_type }}: ${{ env.date }} `, text: ` ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}`}"
|
||||
@@ -0,0 +1,41 @@
|
||||
name: Python Code Quality Checks
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches: ["main", "feature*"]
|
||||
paths:
|
||||
- "python/**"
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
if: "!cancelled()"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.10"]
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python
|
||||
env:
|
||||
# Configure a constant location for the uv cache
|
||||
UV_CACHE_DIR: /tmp/.uv-cache
|
||||
UV_PYTHON: ${{ matrix.python-version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: "0.5.x"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ runner.os }}-${{ matrix.python-version }}
|
||||
cache-dependency-glob: "**/uv.lock"
|
||||
- name: Install the project
|
||||
run: uv sync --all-extras --dev
|
||||
- uses: pre-commit/action@v3.0.1
|
||||
name: Run Pre-Commit Hooks
|
||||
with:
|
||||
extra_args: --config python/.pre-commit-config.yaml --all-files
|
||||
- name: Run Mypy
|
||||
run: uv run mypy -p semantic_kernel --config-file mypy.ini
|
||||
@@ -0,0 +1,30 @@
|
||||
name: Python Start Release on ADO
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Tag to release"
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
python-start-ado-release-job:
|
||||
name: Trigger ADO Pipeline for Python Release
|
||||
runs-on: ubuntu-latest
|
||||
environment: "integration"
|
||||
steps:
|
||||
- name: Azure Login
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
- name: Start DevOps pipeline
|
||||
uses: azure/cli@v2
|
||||
with:
|
||||
inlineScript: |
|
||||
az pipelines run --id ${{ vars.ADO_PYTHON_RELEASE_ID }} --org ${{ vars.ADO_ORG }} --project ${{ vars.ADO_PROJECT_NAME }} --parameters tag=${{ inputs.tag }} delay=0
|
||||
@@ -0,0 +1,57 @@
|
||||
name: Python Test Coverage Report
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Python Test Coverage"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
python-test-coverage-report:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.workflow_run.conclusion == 'success'
|
||||
continue-on-error: false
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Download coverage report
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
path: ./python
|
||||
merge-multiple: true
|
||||
- name: Display structure of downloaded files
|
||||
run: ls
|
||||
- name: Read and set PR number
|
||||
# Need to read the PR number from the file saved in the previous workflow
|
||||
# because the workflow_run event does not have access to the PR number
|
||||
# The PR number is needed to post the comment on the PR
|
||||
run: |
|
||||
PR_NUMBER=$(cat pr_number)
|
||||
if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
|
||||
echo "::error::PR number file contains invalid content"
|
||||
exit 1
|
||||
fi
|
||||
echo "PR number: $PR_NUMBER"
|
||||
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
|
||||
- name: Pytest coverage comment
|
||||
id: coverageComment
|
||||
uses: MishaKav/pytest-coverage-comment@v1.1.57
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-number: ${{ env.PR_NUMBER }}
|
||||
pytest-xml-coverage-path: python/python-coverage.xml
|
||||
title: "Python Test Coverage Report"
|
||||
badge-title: "Python Test Coverage"
|
||||
junitxml-title: "Python Unit Test Overview"
|
||||
junitxml-path: python/pytest.xml
|
||||
default-branch: "main"
|
||||
report-only-changed-files: true
|
||||
@@ -0,0 +1,49 @@
|
||||
name: Python Test Coverage
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: ["main", "feature*"]
|
||||
paths:
|
||||
- "python/semantic_kernel/**"
|
||||
- "python/tests/unit/**"
|
||||
env:
|
||||
# Configure a constant location for the uv cache
|
||||
UV_CACHE_DIR: /tmp/.uv-cache
|
||||
|
||||
jobs:
|
||||
python-tests-coverage:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: false
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python
|
||||
env:
|
||||
UV_PYTHON: "3.10"
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
# Save the PR number to a file since the workflow_run event
|
||||
# in the coverage report workflow does not have access to it
|
||||
- name: Save PR number
|
||||
run: |
|
||||
echo ${{ github.event.number }} > ./pr_number
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: "0.5.x"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ runner.os }}-${{ env.UV_PYTHON }}
|
||||
cache-dependency-glob: "**/uv.lock"
|
||||
- name: Install the project
|
||||
run: uv sync --all-extras --dev -U --prerelease=if-necessary-or-explicit
|
||||
- name: Test with pytest
|
||||
run: uv run --frozen pytest -q --junitxml=pytest.xml --cov=semantic_kernel --cov-report=term-missing:skip-covered --cov-report=xml:python-coverage.xml ./tests/unit --ignore=./tests/unit/processes/dapr_runtime
|
||||
- name: Upload coverage report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: |
|
||||
python/python-coverage.xml
|
||||
python/pytest.xml
|
||||
python/pr_number
|
||||
overwrite: true
|
||||
retention-days: 1
|
||||
if-no-files-found: error
|
||||
@@ -0,0 +1,74 @@
|
||||
name: Python Unit Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: ["main", "feature*"]
|
||||
paths:
|
||||
- "python/**"
|
||||
env:
|
||||
# Configure a constant location for the uv cache
|
||||
UV_CACHE_DIR: /tmp/.uv-cache
|
||||
|
||||
jobs:
|
||||
python-unit-tests:
|
||||
name: Python Unit Tests
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
python-version: ["3.10", "3.11", "3.12"]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
experimental: [false]
|
||||
test-suite: ["unit-all-except-dapr", "dapr"]
|
||||
exclude:
|
||||
- python-version: "3.10"
|
||||
os: macos-latest
|
||||
- python-version: "3.11"
|
||||
os: macos-latest
|
||||
include:
|
||||
- python-version: "3.13"
|
||||
os: "ubuntu-latest"
|
||||
experimental: true
|
||||
test-suite: "unit-all-except-dapr"
|
||||
env:
|
||||
UV_PYTHON: ${{ matrix.python-version }}
|
||||
permissions:
|
||||
contents: write
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: "0.5.x"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ runner.os }}-${{ matrix.python-version }}
|
||||
cache-dependency-glob: "**/uv.lock"
|
||||
- name: Install the project (all extras)
|
||||
if: matrix.test-suite == 'unit-all-except-dapr'
|
||||
run: uv sync --all-extras --dev -U --prerelease=if-necessary-or-explicit
|
||||
- name: Install the project (dapr tests)
|
||||
if: matrix.test-suite == 'dapr'
|
||||
run: uv sync --extra pandas --dev -U --prerelease=if-necessary-or-explicit && uv pip install "dapr>=1.14.0" "dapr-ext-fastapi>=1.14.0" "flask-dapr>=1.14.0"
|
||||
- name: Test with pytest (all except dapr)
|
||||
if: matrix.test-suite == 'unit-all-except-dapr'
|
||||
env:
|
||||
PYTHON_GIL: ${{ matrix.gil }}
|
||||
run: uv run --frozen pytest --junitxml=pytest.xml ./tests/unit --ignore=tests/unit/processes/dapr_runtime
|
||||
- name: Test dapr with pytest
|
||||
if: matrix.test-suite == 'dapr'
|
||||
env:
|
||||
PYTHON_GIL: ${{ matrix.gil }}
|
||||
run: uv run --frozen pytest --junitxml=pytest-dapr.xml ./tests/unit/processes/dapr_runtime
|
||||
- name: Surface failing tests
|
||||
if: ${{ !matrix.experimental && matrix.test-suite == 'unit-all-except-dapr' }}
|
||||
uses: pmeier/pytest-results-action@v0.7.2
|
||||
with:
|
||||
path: python/pytest.xml
|
||||
summary: true
|
||||
display-options: fEX
|
||||
fail-on-empty: true
|
||||
title: Test results
|
||||
@@ -0,0 +1,48 @@
|
||||
param([string]$targetNetFramework)
|
||||
|
||||
$targetNetFramework = "net$targetNetFramework"
|
||||
|
||||
$rootDirectory = Get-Location
|
||||
|
||||
Write-Host "Publishing the SemanticKernel.AotTests application."
|
||||
|
||||
dotnet publish $rootDirectory/dotnet/src/SemanticKernel.AotTests/SemanticKernel.AotTests.csproj --framework $targetNetFramework | Tee-Object -Variable publishOutput
|
||||
|
||||
$warningFound = $false
|
||||
|
||||
if ($LastExitCode -ne 0)
|
||||
{
|
||||
Write-Host "Errors were detected while publishing the application. See the output for more details."
|
||||
Exit $LastExitCode
|
||||
}
|
||||
elseif ($publishOutput -like "*analysis warning IL*" -or $publishOutput -like "*analysis error IL*")
|
||||
{
|
||||
Write-Host "Native AOT analysis warnings were detected while publishing the application. See the output for more details."
|
||||
Exit 1
|
||||
}
|
||||
|
||||
Write-Host "The application was published successfully."
|
||||
|
||||
$runtime = $IsWindows ? "win-x64" : "linux-x64"
|
||||
|
||||
$appPublishDirectory = Join-Path -Path $rootDirectory -ChildPath dotnet/src/SemanticKernel.AotTests/bin/Release/$targetNetFramework/$runtime/publish
|
||||
|
||||
$appFileName = $IsWindows ? "SemanticKernel.AotTests.exe" : "SemanticKernel.AotTests"
|
||||
|
||||
$app = Join-Path -Path $appPublishDirectory -ChildPath $appFileName
|
||||
|
||||
Write-Host "Executing the SemanticKernel.AotTests application."
|
||||
|
||||
& $app
|
||||
|
||||
if ($LastExitCode -ne 0)
|
||||
{
|
||||
$testPassed = 1
|
||||
Write-Host "There was an error while executing the application. The Last Exit Code is: $LastExitCode"
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host "The application was executed successfully."
|
||||
}
|
||||
|
||||
Exit $testPassed
|
||||
@@ -0,0 +1,31 @@
|
||||
# Check pull requests for typos.
|
||||
#
|
||||
# Configuration: .github/_typos.toml
|
||||
#
|
||||
# Info: https://github.com/marketplace/actions/typos-action
|
||||
# Local install: brew install typos-cli
|
||||
# Local install: conda install typos
|
||||
# Local run: typos -c .github/_typos.toml
|
||||
|
||||
name: Spell Check
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches: ["main", "feature*"]
|
||||
|
||||
jobs:
|
||||
run:
|
||||
name: Spell Check with Typos
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Use custom config file
|
||||
uses: crate-ci/typos@v1.36.2
|
||||
with:
|
||||
config: .github/_typos.toml
|
||||
write_changes: false
|
||||
Executable
+107
@@ -0,0 +1,107 @@
|
||||
#!/bin/bash
|
||||
|
||||
POSITIONAL_ARGS=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-f|--file)
|
||||
file="$2"
|
||||
shift # past argument
|
||||
shift # past value
|
||||
;;
|
||||
-p|--propsFile)
|
||||
propsFile="$2"
|
||||
shift # past argument
|
||||
shift # past value
|
||||
;;
|
||||
-b|--buildAndRevisionNumber)
|
||||
buildAndRevisionNumber="$2"
|
||||
shift # past argument
|
||||
shift # past value
|
||||
;;
|
||||
-*|--*)
|
||||
echo "Unknown option $1"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
POSITIONAL_ARGS+=("$1") # save positional arg
|
||||
shift # past argument
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters
|
||||
|
||||
if [ -z "$file" ]; then
|
||||
echo "ERROR: Parameter file (-f|--file) not provided"
|
||||
exit 1;
|
||||
elif [ ! -f "$file" ]; then
|
||||
echo "ERROR: file ${file} not found"
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
if [ -n "$(cat $file | grep -i "<IsPackable>false</IsPackable>")" ]; then
|
||||
echo "Project is marked as NOT packable - skipping."
|
||||
exit 0;
|
||||
fi
|
||||
|
||||
if [ -z "$propsFile" ]; then
|
||||
echo "ERROR: Parameter propsFile (-f|--file) not provided"
|
||||
exit 1;
|
||||
elif [ ! -f "$propsFile" ]; then
|
||||
echo "ERROR: propsFile ${file} not found"
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
if [ -z "$buildAndRevisionNumber" ]; then
|
||||
echo "ERROR: Parameter buildAndRevisionNumber (-b|--buildAndRevisionNumber) not provided"
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
propsVersionString=$(cat $propsFile | grep -i "<Version>");
|
||||
regex="<Version>([0-9.]*)<\/Version>"
|
||||
if [[ $propsVersionString =~ $regex ]]; then
|
||||
propsVersion=${BASH_REMATCH[1]}
|
||||
else
|
||||
echo "ERROR: Version tag not found in propsFile"
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
if [ -z "$propsVersion" ]; then
|
||||
echo "ERROR: Version tag not found in propsFile"
|
||||
exit 1;
|
||||
elif [[ ! "$propsVersion" =~ ^0.* ]]; then
|
||||
echo "ERROR: Version expected to start with 0. Actual: ${propsVersion}"
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
fullVersionString="${propsVersion}.${buildAndRevisionNumber}-preview"
|
||||
|
||||
if [[ ! "$fullVersionString" =~ ^0.* ]]; then
|
||||
echo "ERROR: Version expected to start with 0. Actual: ${fullVersionString}"
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
echo "==== Project: ${file} ====";
|
||||
echo "propsFile = ${propsFile}"
|
||||
echo "buildAndRevisionNumber = ${buildAndRevisionNumber}"
|
||||
echo "version prefix from propsFile = ${propsVersion}"
|
||||
echo "full version string: ${fullVersionString}"
|
||||
|
||||
versionInProj=$(cat $file | grep -i "<Version>");
|
||||
if [ -n "$versionInProj" ]; then
|
||||
# Version tag already exists in the csproj. Let's replace it.
|
||||
echo "Updating version tag..."
|
||||
content=$(cat $file | sed --expression="s/<Version>\([0-9]*.[0-9]*\)<\/Version>/<Version>$fullVersionString<\/Version>/g");
|
||||
else
|
||||
# Version tag not found in the csproj. Let's add it.
|
||||
echo "Project is packable - adding version tag..."
|
||||
content=$(cat $file | sed --expression="s/<\/Project>/<PropertyGroup><Version>$fullVersionString<\/Version><\/PropertyGroup><\/Project>/g");
|
||||
fi
|
||||
|
||||
if [ $? -ne 0 ]; then exit 1; fi
|
||||
echo "$content" && echo "$content" > $file;
|
||||
if [ $? -ne 0 ]; then exit 1; fi
|
||||
|
||||
echo "DONE";
|
||||
echo "";
|
||||
Reference in New Issue
Block a user