chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
name: "cache-hf"
|
||||
description: "Cache HuggingFace Hub artifacts to avoid 429s in CI"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
with:
|
||||
path: ~/.cache/huggingface/hub/datasets--*
|
||||
key: ${{ runner.os }}-hf-datasets-${{ hashFiles('tests/data/test_huggingface_dataset_and_source.py') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-hf-datasets
|
||||
@@ -0,0 +1,8 @@
|
||||
name: "check-component-ids"
|
||||
description: "Verify that all componentIds in the MLflow UI are registered in the componentId registry and vice versa."
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: ./.github/actions/setup-node
|
||||
- run: node ${GITHUB_ACTION_PATH}/index.js
|
||||
shell: bash
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
||||
const { extractComponentIdsFromSource } = require("./utils");
|
||||
|
||||
const registry = require("./componentId-registry");
|
||||
|
||||
// --- Main ---
|
||||
const codeIds = extractComponentIdsFromSource(__dirname);
|
||||
const registryKeys = new Set(Object.keys(registry));
|
||||
|
||||
// Check 1: componentIds in code but not in registry
|
||||
const unregistered = [...codeIds].filter((id) => !registryKeys.has(id)).sort();
|
||||
|
||||
// Check 2: componentIds in registry but not in code (stale)
|
||||
const stale = [...registryKeys].filter((id) => !codeIds.has(id)).sort();
|
||||
|
||||
let failed = false;
|
||||
|
||||
if (unregistered.length > 0) {
|
||||
failed = true;
|
||||
console.error(
|
||||
`\n❌ Found ${unregistered.length} componentId(s) in code but NOT in the registry:\n`
|
||||
);
|
||||
for (const id of unregistered) {
|
||||
console.error(` + ${id}`);
|
||||
}
|
||||
console.error("\nAdd these to .github/actions/check-component-ids/componentId-registry.js");
|
||||
}
|
||||
|
||||
if (stale.length > 0) {
|
||||
failed = true;
|
||||
console.error(`\n❌ Found ${stale.length} stale componentId(s) in registry but NOT in code:\n`);
|
||||
for (const id of stale) {
|
||||
console.error(` - ${id}`);
|
||||
}
|
||||
console.error("\nRemove these from .github/actions/check-component-ids/componentId-registry.js");
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(`✅ componentId registry is in sync. ${registryKeys.size} entries verified.`);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Regenerates the componentId registry from source code.
|
||||
*
|
||||
* Usage (from repo root):
|
||||
* node .github/actions/check-component-ids/regenerate.js
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { extractComponentIdsFromSource } = require("./utils");
|
||||
|
||||
const codeIds = extractComponentIdsFromSource(__dirname);
|
||||
const sorted = [...codeIds].sort();
|
||||
|
||||
// Group by prefix for readability
|
||||
const groups = {};
|
||||
for (const id of sorted) {
|
||||
let prefix;
|
||||
if (id.startsWith("codegen_")) {
|
||||
prefix = "Codegen (auto-generated)";
|
||||
} else if (id.startsWith("mlflow.")) {
|
||||
const parts = id.split(".");
|
||||
prefix = parts[0] + "." + parts[1];
|
||||
} else if (id.startsWith("shared.")) {
|
||||
const parts = id.split(".");
|
||||
prefix = parts[0] + "." + parts[1];
|
||||
} else {
|
||||
prefix = "Other";
|
||||
}
|
||||
if (!groups[prefix]) groups[prefix] = [];
|
||||
groups[prefix].push(id);
|
||||
}
|
||||
|
||||
// Load existing registry to preserve descriptions
|
||||
let existingDescriptions = {};
|
||||
try {
|
||||
existingDescriptions = require("./componentId-registry");
|
||||
} catch {
|
||||
// First run or broken registry — start fresh
|
||||
}
|
||||
|
||||
let output = `/**
|
||||
* Curated registry of all componentIds used in the MLflow UI.
|
||||
*
|
||||
* Every static componentId string literal in non-test source files must
|
||||
* have an entry here. The CI job \`check-component-ids\` verifies this
|
||||
* bidirectionally: code IDs must be in the registry, and registry
|
||||
* entries must exist in code.
|
||||
*
|
||||
* Format: key = componentId string, value = optional description of the
|
||||
* component (blank by default, especially for generated entries)
|
||||
*/
|
||||
module.exports = {\n`;
|
||||
|
||||
for (const gk of Object.keys(groups).sort()) {
|
||||
output += ` // -- ${gk} --\n`;
|
||||
for (const id of groups[gk]) {
|
||||
const escaped = id.replace(/"/g, '\\"');
|
||||
const desc = (existingDescriptions[id] || "").replace(/"/g, '\\"');
|
||||
output += ` "${escaped}": "${desc}",\n`;
|
||||
}
|
||||
output += "\n";
|
||||
}
|
||||
output += "};\n";
|
||||
|
||||
const outPath = path.join(__dirname, "componentId-registry.js");
|
||||
fs.writeFileSync(outPath, output);
|
||||
console.log(`✅ Registry regenerated with ${sorted.length} entries at ${outPath}`);
|
||||
@@ -0,0 +1,66 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const EXTENSIONS = [".js", ".jsx", ".ts", ".tsx"];
|
||||
// Skip test files — they don't need registered componentIds
|
||||
const TEST_PATTERN = /\.test\.[jt]sx?$/;
|
||||
|
||||
const EXTRACT_PATTERNS = [
|
||||
/(?:componentId|data-component-id)=["']([^"']+)["']/g,
|
||||
/componentId:\s*["']([^"']+)["']/g,
|
||||
// Match static strings inside JSX expressions like componentId={"value"},
|
||||
// componentId={cond ?? "fallback"}, componentId={cond ? "a" : "b"}, etc.
|
||||
// Uses [^\n}]* to avoid matching across lines.
|
||||
/componentId=\{[^\n}]*["']([^"'\n`]+)["'][^\n}]*\}/g,
|
||||
];
|
||||
|
||||
function findFiles(dir) {
|
||||
const results = [];
|
||||
function walk(d) {
|
||||
for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
|
||||
const full = path.join(d, entry.name);
|
||||
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
||||
walk(full);
|
||||
} else if (
|
||||
entry.isFile() &&
|
||||
EXTENSIONS.some((ext) => full.endsWith(ext)) &&
|
||||
!TEST_PATTERN.test(full)
|
||||
) {
|
||||
results.push(full);
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(dir);
|
||||
return results;
|
||||
}
|
||||
|
||||
function extractComponentIds(files) {
|
||||
const ids = new Set();
|
||||
for (const file of files) {
|
||||
const content = fs.readFileSync(file, "utf8");
|
||||
for (const pat of EXTRACT_PATTERNS) {
|
||||
pat.lastIndex = 0;
|
||||
let m;
|
||||
while ((m = pat.exec(content)) !== null) {
|
||||
ids.add(m[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all static componentIds from the MLflow UI source directory.
|
||||
* @param {string} actionDir - path to this action's directory (used to resolve the repo root)
|
||||
* @returns {Set<string>} set of componentId strings found in source
|
||||
*/
|
||||
function extractComponentIdsFromSource(actionDir) {
|
||||
const srcDir = path.resolve(
|
||||
process.env.GITHUB_WORKSPACE || path.join(actionDir, "../../.."),
|
||||
"mlflow/server/js/src"
|
||||
);
|
||||
const files = findFiles(srcDir);
|
||||
return extractComponentIds(files);
|
||||
}
|
||||
|
||||
module.exports = { extractComponentIdsFromSource };
|
||||
@@ -0,0 +1,9 @@
|
||||
name: "free-disk-space"
|
||||
description: "free disk space"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- shell: bash
|
||||
run: |
|
||||
# Run in background to save time
|
||||
sudo rm -rf /usr/local/lib/android &
|
||||
@@ -0,0 +1,27 @@
|
||||
name: "setup-java"
|
||||
description: "Set up Java"
|
||||
inputs:
|
||||
java-version:
|
||||
description: "java-version"
|
||||
default: "17"
|
||||
required: false
|
||||
|
||||
distribution:
|
||||
description: "distribution"
|
||||
default: temurin
|
||||
required: false
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: ${{ inputs.java-version }}
|
||||
distribution: ${{ inputs.distribution }}
|
||||
|
||||
- name: Set MLFLOW_DOCKER_OPENJDK_VERSION
|
||||
shell: bash
|
||||
env:
|
||||
JAVA_VERSION: ${{ inputs.java-version }}
|
||||
run: |
|
||||
echo "MLFLOW_DOCKER_OPENJDK_VERSION=$JAVA_VERSION" >> $GITHUB_ENV
|
||||
@@ -0,0 +1,19 @@
|
||||
name: "setup-node"
|
||||
description: "Set up Node"
|
||||
inputs:
|
||||
node-version:
|
||||
description: "Node version to use."
|
||||
default: "24"
|
||||
required: false
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
- run: |
|
||||
# allowScripts opt-in install-script policy requires npm >= 11.16.0
|
||||
# (https://github.com/npm/cli/pull/9360)
|
||||
npm install -g npm@"^11.16.0"
|
||||
shell: bash
|
||||
@@ -0,0 +1,62 @@
|
||||
name: "setup-pyenv"
|
||||
description: "Setup pyenv"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
########## Ubuntu ##########
|
||||
- name: Install python build tools
|
||||
if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
# Ref: https://github.com/pyenv/pyenv/wiki#suggested-build-environment
|
||||
# Note: llvm is optional (required only for building PyPy/clang)
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get purge -y man-db || true
|
||||
sudo apt-get install -y --no-install-recommends make build-essential libssl-dev zlib1g-dev \
|
||||
libbz2-dev libreadline-dev libsqlite3-dev wget curl \
|
||||
libncursesw5-dev xz-utils libffi-dev liblzma-dev
|
||||
- name: Install pyenv
|
||||
if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
# Pin to pyenv v2.6.25 by tag + SHA verification for reproducible builds
|
||||
git clone --branch v2.6.25 --depth 1 https://github.com/pyenv/pyenv.git "$HOME/.pyenv"
|
||||
actual_sha=$(git -C "$HOME/.pyenv" rev-parse HEAD)
|
||||
expected_sha="aa2e8b82605b8ba085dd15f7a31fe1990fabdcfa"
|
||||
if [ "$actual_sha" != "$expected_sha" ]; then
|
||||
echo "::error::pyenv SHA mismatch: expected $expected_sha, got $actual_sha"
|
||||
exit 1
|
||||
fi
|
||||
- name: Setup environment variables
|
||||
if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
PYENV_ROOT="$HOME/.pyenv"
|
||||
PYENV_BIN="$PYENV_ROOT/bin"
|
||||
echo "$PYENV_BIN" >> $GITHUB_PATH
|
||||
echo "PYENV_ROOT=$PYENV_ROOT" >> $GITHUB_ENV
|
||||
- name: Check pyenv version
|
||||
if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
pyenv --version
|
||||
|
||||
########## Windows ##########
|
||||
- name: Install pyenv
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
uv pip install --system pyenv-win --target $HOME\\.pyenv
|
||||
- name: Setup environment variables
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "PYENV=$USERPROFILE\.pyenv\pyenv-win\\" >> $GITHUB_ENV
|
||||
echo "PYENV_ROOT=$USERPROFILE\.pyenv\pyenv-win\\" >> $GITHUB_ENV
|
||||
echo "PYENV_HOME=$USERPROFILE\.pyenv\pyenv-win\\" >> $GITHUB_ENV
|
||||
echo "$USERPROFILE\.pyenv\pyenv-win\\bin\\" >> $GITHUB_PATH
|
||||
- name: Check pyenv version
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
pyenv --version
|
||||
@@ -0,0 +1,79 @@
|
||||
name: "setup-python"
|
||||
description: "Ensures to install a python version that's available on Anaconda"
|
||||
inputs:
|
||||
python-version:
|
||||
description: "The python version to install. If unspecified, install the minimum python version mlflow supports."
|
||||
required: false
|
||||
pin-micro-version:
|
||||
description: "Whether to pin to a specific micro version for Anaconda compatibility. Set to false for workflows that don't need conda/pyenv to hit the runner's pre-installed Python cache and avoid a ~9s download."
|
||||
required: false
|
||||
default: "true"
|
||||
outputs:
|
||||
python-version:
|
||||
description: "The installed python version"
|
||||
value: ${{ steps.get-python-version.outputs.version }}
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: get-python-version
|
||||
id: get-python-version
|
||||
shell: bash
|
||||
# We used to use `conda search python=3.x` to dynamically fetch the latest available version
|
||||
# in 3.x on Anaconda, but it turned out `conda search` is very slow (takes 40 ~ 50 seconds).
|
||||
# This overhead sums up to a significant amount of delay in the cross version tests
|
||||
# where we trigger more than 100 GitHub Actions runs.
|
||||
env:
|
||||
PYTHON_VERSION_INPUT: ${{ inputs.python-version }}
|
||||
PIN_MICRO_VERSION: ${{ inputs.pin-micro-version }}
|
||||
run: |
|
||||
python_version="$PYTHON_VERSION_INPUT"
|
||||
if [ -z "$python_version" ]; then
|
||||
python_version=$(cat .python-version)
|
||||
fi
|
||||
if [[ "$PIN_MICRO_VERSION" == "true" ]]; then
|
||||
if [[ "$python_version" == "3.10" ]]; then
|
||||
if [ "$RUNNER_OS" == "Linux" ]; then
|
||||
python_version="3.10.20"
|
||||
else
|
||||
python_version="3.10.11"
|
||||
fi
|
||||
elif [[ "$python_version" == "3.11" ]]; then
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
python_version="3.11.9"
|
||||
else
|
||||
python_version="3.11.15"
|
||||
fi
|
||||
elif [[ "$python_version" == "3.12" ]]; then
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
python_version="3.12.10"
|
||||
else
|
||||
python_version="3.12.13"
|
||||
fi
|
||||
else
|
||||
echo "Invalid python version: '$python_version'. Must be '3.10', '3.11', or '3.12'."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo "version=$python_version" >> $GITHUB_OUTPUT
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: ${{ steps.get-python-version.outputs.version }}
|
||||
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
with:
|
||||
version: "0.11.14"
|
||||
# Caching disabled to avoid cache-poisoning exposure across CI workflows.
|
||||
enable-cache: false
|
||||
- run: |
|
||||
# The default `first-index` strategy is too strict. Use `unsafe-first-match` instead.
|
||||
# https://docs.astral.sh/uv/configuration/environment/#uv_index_strategy
|
||||
echo "UV_INDEX_STRATEGY=unsafe-first-match" >> $GITHUB_ENV
|
||||
# Disable progress bars and spinners to reduce CI log clutter
|
||||
# https://docs.astral.sh/uv/reference/environment/#uv_no_progress
|
||||
echo "UV_NO_PROGRESS=1" >> $GITHUB_ENV
|
||||
# Exclude packages newer than 7 days to guard against supply chain attacks
|
||||
# https://docs.astral.sh/uv/reference/environment/#uv_exclude_newer
|
||||
echo "UV_EXCLUDE_NEWER=P7D" >> $GITHUB_ENV
|
||||
# Disable Hugging Face download progress bars to reduce CI log clutter
|
||||
# https://huggingface.co/docs/huggingface_hub/package_reference/environment_variables#hfhubdisableprogressbars
|
||||
echo "HF_HUB_DISABLE_PROGRESS_BARS=1" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
@@ -0,0 +1,21 @@
|
||||
name: "show-versions"
|
||||
description: "Show python package versions sorted by release date"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- shell: bash
|
||||
run: |
|
||||
# Activate the virtual environment if .venv exists
|
||||
if [ -d .venv ]; then
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
source .venv/Scripts/activate
|
||||
else
|
||||
source .venv/bin/activate
|
||||
fi
|
||||
fi
|
||||
pip --disable-pip-version-check install ./dev/pypi > /dev/null
|
||||
echo ">>> package versions"
|
||||
status=0
|
||||
python dev/show_package_release_dates.py || status=$?
|
||||
echo "<<< package versions"
|
||||
exit $status
|
||||
@@ -0,0 +1,6 @@
|
||||
name: "untracked"
|
||||
description: "Detect untracked files"
|
||||
runs:
|
||||
using: "node24"
|
||||
main: "index.js"
|
||||
post: "post.js"
|
||||
@@ -0,0 +1 @@
|
||||
// Does nothing
|
||||
@@ -0,0 +1,20 @@
|
||||
const { exec } = require("child_process");
|
||||
|
||||
exec("git ls-files --others --exclude-standard", (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error(`An error occurred: ${error}`);
|
||||
process.exit(error.code || 1);
|
||||
}
|
||||
|
||||
const untrackedFiles = stdout.trim();
|
||||
|
||||
if (untrackedFiles === "") {
|
||||
console.log("No untracked files found.");
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log("Untracked files found:");
|
||||
console.log(untrackedFiles);
|
||||
console.log("Consider adding them to .gitignore.");
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
name: "update-requirements"
|
||||
description: "Update requirements YAML specifications and re-generate requirements text files"
|
||||
|
||||
outputs:
|
||||
updated:
|
||||
description: "Indicates whether the requirements have been updated"
|
||||
value: ${{ steps.update-requirements.outputs.updated }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Update requirements
|
||||
id: update-requirements
|
||||
shell: bash --noprofile --norc -exo pipefail {0}
|
||||
run: |
|
||||
python bin/install.py
|
||||
python dev/update_requirements.py --requirements-yaml-location requirements
|
||||
if [ -z "$(git status --porcelain)" ]; then
|
||||
echo "updated=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
python dev/pyproject.py
|
||||
git diff --color=always
|
||||
echo "updated=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
Reference in New Issue
Block a user