feat: build pipeline
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
input/*
|
||||||
|
report/*
|
||||||
|
status/*
|
||||||
+7
-7
@@ -21,7 +21,7 @@ RUN apt-get update -y && \
|
|||||||
setcap cap_net_raw,cap_net_admin=eip /bin/tcpdump
|
setcap cap_net_raw,cap_net_admin=eip /bin/tcpdump
|
||||||
|
|
||||||
# Install Chrome browser for Karma
|
# Install Chrome browser for Karma
|
||||||
RUN cd /tmp && wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && apt-get update -y && apt-get install -y ./google-chrome-stable_current_amd64.deb && rm -f google-chrome-stable_current_amd64.deb
|
# RUN cd /tmp && wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && apt-get update -y && apt-get install -y ./google-chrome-stable_current_amd64.deb && rm -f google-chrome-stable_current_amd64.deb
|
||||||
|
|
||||||
RUN if [ ${LOCAL_BUILD} -eq 1 ]; then groupadd nandy; else groupadd -g "${DST_GROUP_ID}" nandy; fi
|
RUN if [ ${LOCAL_BUILD} -eq 1 ]; then groupadd nandy; else groupadd -g "${DST_GROUP_ID}" nandy; fi
|
||||||
RUN useradd -d /home/nandy -m -s /bin/bash -u "${DST_USER_ID}" -g "${DST_GROUP_ID}" nandy;
|
RUN useradd -d /home/nandy -m -s /bin/bash -u "${DST_USER_ID}" -g "${DST_GROUP_ID}" nandy;
|
||||||
@@ -38,13 +38,14 @@ ENV NVM_DIR="$HOME/.nvm"
|
|||||||
RUN echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' >> $HOME/.profile
|
RUN echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' >> $HOME/.profile
|
||||||
|
|
||||||
ENV NVM_DIR $HOME/.nvm
|
ENV NVM_DIR $HOME/.nvm
|
||||||
ENV NODE_VERSION 20.16.0
|
ENV NODE_VERSION 22.18.0
|
||||||
|
|
||||||
RUN curl https://raw.githubusercontent.com/creationix/nvm/v0.40.0/install.sh | bash \
|
RUN curl https://raw.githubusercontent.com/creationix/nvm/v0.40.0/install.sh | env -u NODE_VERSION bash \
|
||||||
&& . $NVM_DIR/nvm.sh \
|
&& . $NVM_DIR/nvm.sh \
|
||||||
&& nvm install $NODE_VERSION \
|
&& nvm install $NODE_VERSION \
|
||||||
&& nvm alias default $NODE_VERSION \
|
&& nvm alias default $NODE_VERSION \
|
||||||
&& nvm use default
|
&& nvm use default \
|
||||||
|
&& npm install --global yarn@1.22.22
|
||||||
|
|
||||||
# Fix git issue with big resps
|
# Fix git issue with big resps
|
||||||
RUN git config --global http.postBuffer 52428800
|
RUN git config --global http.postBuffer 52428800
|
||||||
@@ -68,9 +69,8 @@ RUN source $PROCESSING_VENV/bin/activate && pip install $WORKDIR/runtime_check/
|
|||||||
|
|
||||||
|
|
||||||
COPY --chown=nandy:nandy ./scanfeeder /usr/local/bin/scanfeeder
|
COPY --chown=nandy:nandy ./scanfeeder /usr/local/bin/scanfeeder
|
||||||
COPY --chown=nandy:nandy ./src/npm_package_processing.py $WORKDIR
|
COPY --chown=nandy:nandy ./src/vsix_package_processing.py $WORKDIR
|
||||||
COPY --chown=nandy:nandy ./src/debug_check.py $WORKDIR
|
COPY --chown=nandy:nandy ./meta $HOME/meta
|
||||||
COPY --chown=nandy:nandy ./metadata $HOME/metadata
|
|
||||||
COPY --chown=nandy:nandy ./run.sh /usr/local/bin/run.sh
|
COPY --chown=nandy:nandy ./run.sh /usr/local/bin/run.sh
|
||||||
RUN chmod +x /usr/local/bin/run.sh
|
RUN chmod +x /usr/local/bin/run.sh
|
||||||
COPY --chown=nandy ./pipe_create.sh /usr/local/bin/pipe_create.sh
|
COPY --chown=nandy ./pipe_create.sh /usr/local/bin/pipe_create.sh
|
||||||
|
|||||||
@@ -38,6 +38,12 @@ curl \
|
|||||||
--output "input/${RESULT_INPUT}" \
|
--output "input/${RESULT_INPUT}" \
|
||||||
"${URL}"
|
"${URL}"
|
||||||
|
|
||||||
|
if gzip -t "input/${RESULT_INPUT}" 2>/dev/null; then
|
||||||
|
echo "Decompress gzip-wrapped VSIX"
|
||||||
|
gzip -dc "input/${RESULT_INPUT}" > "input/${RESULT_INPUT}.tmp"
|
||||||
|
mv "input/${RESULT_INPUT}.tmp" "input/${RESULT_INPUT}"
|
||||||
|
fi
|
||||||
|
|
||||||
export RELOAD_CONFIG=1
|
export RELOAD_CONFIG=1
|
||||||
export ADD_METADATA_VOLUME='true'
|
export ADD_METADATA_VOLUME='true'
|
||||||
bash ./run.sh "${RESULT_INPUT}" "$PACKAGE_NAME" "$PACKAGE_VERSION"
|
bash ./run.sh "${RESULT_INPUT}" "$PACKAGE_NAME" "$PACKAGE_VERSION"
|
||||||
|
|||||||
Executable
+35
@@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
update_user_ids() {
|
||||||
|
local target_uid="${DST_USER_ID}"
|
||||||
|
local target_gid="${DST_GROUP_ID}"
|
||||||
|
|
||||||
|
if [ -z "$target_uid" ] || [ -z "$target_gid" ]; then
|
||||||
|
echo "Warning: DST_USER_ID or DST_GROUP_ID not set, using default values"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local current_uid=$(id -u nandy)
|
||||||
|
local current_gid=$(id -g nandy)
|
||||||
|
|
||||||
|
if [ "$current_uid" -eq "$target_uid" ] && [ "$current_gid" -eq "$target_gid" ]; then
|
||||||
|
echo "User nandy already has UID=$target_uid and GID=$target_gid"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Updating nandy user: UID $current_uid -> $target_uid, GID $current_gid -> $target_gid"
|
||||||
|
groupmod -g "$target_gid" nandy
|
||||||
|
usermod -u "$target_uid" nandy
|
||||||
|
find /home/nandy -user "$current_uid" -exec chown -h "$target_uid" {} \; 2>/dev/null || true
|
||||||
|
find /home/nandy -group "$current_gid" -exec chgrp -h "$target_gid" {} \; 2>/dev/null || true
|
||||||
|
echo "User IDs updated successfully"
|
||||||
|
}
|
||||||
|
update_user_ids
|
||||||
|
|
||||||
|
PLUGIN_VERSION=${PLUGIN_VERSION:-"unknown"}
|
||||||
|
PLUGIN_BUILD_DATE=${PLUGIN_BUILD_DATE:-"unknown"}
|
||||||
|
echo "Plugin version: ${PLUGIN_VERSION}"
|
||||||
|
echo "Plugin build date: ${PLUGIN_BUILD_DATE}"
|
||||||
|
|
||||||
|
exec runuser -u nandy -- "$@"
|
||||||
@@ -1,11 +1,26 @@
|
|||||||
# The VSIX manifest points at angular/angular, but the VS Code extension source
|
# The VSIX manifest points at angular/angular, but the VS Code extension source
|
||||||
# lives in angular/vscode-ng-language-service. There is no v20.3.3 tag there;
|
# lives in angular/vscode-ng-language-service. There is no v20.3.3 tag there;
|
||||||
# v20.3.0 is the nearest public tag for the 20.3 line.
|
# v20.3.0 is the nearest public tag for the 20.3 line.
|
||||||
install_dependencies: "corepack enable && corepack prepare yarn@1.22.22 --activate && yarn install --frozen-lockfile"
|
install_dependencies: "yarn install --frozen-lockfile"
|
||||||
source_repository: "https://github.com/angular/vscode-ng-language-service.git"
|
source_repository: "https://github.com/angular/vscode-ng-language-service.git"
|
||||||
source_ref: "v20.3.0"
|
source_ref: "v20.3.0"
|
||||||
build_vsix: "yarn package"
|
build_vsix:
|
||||||
run_tests: "yarn test"
|
- "yarn package"
|
||||||
|
- "mv bazel-bin/ng-template.vsix ./ng-template.vsix"
|
||||||
|
- unzip -o ng-template.vsix -d ng-template
|
||||||
|
- tail -n +5 ng-template/extension/CHANGELOG.md > temp.md && mv temp.md ng-template/extension/CHANGELOG.md
|
||||||
|
- sed 's|bazel-out/k8-fastbuild|bazel-out/darwin_arm64-fastbuild|g' ng-template/extension/index.js > ng-template/extension/indexx.js
|
||||||
|
- mv ng-template/extension/indexx.js ng-template/extension/index.js
|
||||||
|
- sed 's|../report/git_package/angular.ng-template|Users/atscott/github/vscode-ng-language-service|g' ng-template/extension/index.js > ng-template/extension/indexx.js
|
||||||
|
- mv ng-template/extension/indexx.js ng-template/extension/index.js
|
||||||
|
- sed 's|bazel-out/k8-fastbuild|bazel-out/darwin_arm64-fastbuild|g' ng-template/extension/server/index.js > ng-template/extension/server/indexx.js
|
||||||
|
- mv ng-template/extension/server/indexx.js ng-template/extension/server/index.js
|
||||||
|
- sed 's|../report/git_package/angular.ng-template|Users/atscott/github/vscode-ng-language-service|g' ng-template/extension/server/index.js > ng-template/extension/server/indexx.js
|
||||||
|
- mv ng-template/extension/server/indexx.js ng-template/extension/server/index.js
|
||||||
|
- cd ng-template && python3 -m zipfile -c ../ng-templateX.vsix . && cd ..
|
||||||
|
built_vsix_path: "ng-templateX.vsix"
|
||||||
|
run_tests: "npx nyc yarn compile:test && node_modules/.bin/jasmine --config=jasmine.json"
|
||||||
coverage:
|
coverage:
|
||||||
enabled: false
|
tool: "nyc"
|
||||||
|
enabled: true
|
||||||
threshold: 80
|
threshold: 80
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
c8:
|
||||||
|
report:
|
||||||
|
- npx c8 report --reporter=text
|
||||||
|
check:
|
||||||
|
- npx c8 check-coverage --branches 80 --functions 80 --lines 80 --statements 80
|
||||||
|
coverage_report_folder: coverage
|
||||||
|
vitest:
|
||||||
|
report:
|
||||||
|
- npx vitest --coverage --run
|
||||||
|
check:
|
||||||
|
- npx vitest --run --coverage --coverage.thresholds.lines=80 --coverage.thresholds.functions=80 --coverage.thresholds.branches=80 --coverage.thresholds.statements=80
|
||||||
|
coverage_report_folder: coverage
|
||||||
|
nyc:
|
||||||
|
report:
|
||||||
|
- npx nyc report
|
||||||
|
check:
|
||||||
|
- npx nyc check-coverage --branches 80 --functions 80 --lines 80
|
||||||
|
--statements 80
|
||||||
|
coverage_report_folder: .nyc_output
|
||||||
|
jest:
|
||||||
|
report:
|
||||||
|
- npx istanbul report text
|
||||||
|
check:
|
||||||
|
- npx istanbul check-coverage --statements 80 --functions 80 --branches 80 --lines 80
|
||||||
|
coverage_report_folder: coverage
|
||||||
|
istanbul:
|
||||||
|
report:
|
||||||
|
- npx istanbul report text
|
||||||
|
check:
|
||||||
|
- npx istanbul check-coverage --statements 80 --functions 80 --branches 80 --lines 80
|
||||||
|
coverage_report_folder: coverage
|
||||||
|
tap:
|
||||||
|
report:
|
||||||
|
- npx tap report --coverage-report=text --show-full-coverage
|
||||||
|
check:
|
||||||
|
- ""
|
||||||
|
coverage_report_folder: .tap/coverage
|
||||||
|
karma:
|
||||||
|
report:
|
||||||
|
- exit 2
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Commands are executed from the cloned repository root.
|
# Commands are executed from the cloned repository root.
|
||||||
install_dependencies: "node -e 'const [major, minor] = process.versions.node.split(`.`).map(Number); if (major < 22 || (major === 22 && minor < 18)) { console.error(`GitLens 17.12.1 requires Node >=22.18.0; current=${process.versions.node}`); process.exit(1); }' && corepack enable && corepack prepare pnpm@10.33.0 --activate && pnpm install --frozen-lockfile"
|
install_dependencies: "npm i -g c8 && node -e 'const [major, minor] = process.versions.node.split(`.`).map(Number); if (major < 22 || (major === 22 && minor < 18)) { console.error(`GitLens 17.12.1 requires Node >=22.18.0; current=${process.versions.node}`); process.exit(1); }' && corepack enable && corepack prepare pnpm@10.33.0 --activate && pnpm install --frozen-lockfile"
|
||||||
build_vsix: "pnpm exec vsce package --no-dependencies --out source.vsix"
|
build_vsix: "pnpm exec vsce package --no-dependencies --out source.vsix"
|
||||||
run_tests: "npx c8 --clean --temp-directory coverage/tmp node_modules/.bin/vscode-test"
|
run_tests: "npx c8 --clean --temp-directory coverage/tmp node_modules/.bin/vscode-test"
|
||||||
coverage:
|
coverage:
|
||||||
|
|||||||
+2
-2
@@ -37,7 +37,7 @@ do
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
CONTAINER_ID=$(docker ps -a | grep npm_runtime_check | awk '{print $1;}')
|
CONTAINER_ID=$(docker ps -a | grep vsix_runtime_check | awk '{print $1;}')
|
||||||
SYSDIG_FILE=$(awk '{print $3}' <<< ${STR})
|
SYSDIG_FILE=$(awk '{print $3}' <<< ${STR})
|
||||||
|
|
||||||
if [ "${CONTAINER_ID}" == "" ]; then
|
if [ "${CONTAINER_ID}" == "" ]; then
|
||||||
@@ -91,4 +91,4 @@ do
|
|||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ MINIO_USER=${10:-"admin"}
|
|||||||
MINIO_PASSWORD=${11:-"password"}
|
MINIO_PASSWORD=${11:-"password"}
|
||||||
# MinIO bucket name, default is
|
# MinIO bucket name, default is
|
||||||
MINIO_BUCKET=${12:-"taskalo-files"}
|
MINIO_BUCKET=${12:-"taskalo-files"}
|
||||||
PLUGIN_NAME="npm_runtime_check"
|
PLUGIN_NAME="vsix_runtime_check"
|
||||||
SCANFEEDER_DEBUG=${SCANFEEDER_DEBUG:-true}
|
SCANFEEDER_DEBUG=${SCANFEEDER_DEBUG:-true}
|
||||||
USE_SYSDIG=${USE_SYSDIG:-false}
|
USE_SYSDIG=${USE_SYSDIG:-false}
|
||||||
DST_USER_ID=$(id -u)
|
DST_USER_ID=$(id -u)
|
||||||
@@ -69,15 +69,16 @@ fi
|
|||||||
|
|
||||||
MONITOR_ENABLE=$([ "${USE_SYSDIG}" = "true" ] && echo "True" || echo "False")
|
MONITOR_ENABLE=$([ "${USE_SYSDIG}" = "true" ] && echo "True" || echo "False")
|
||||||
USER_HOME='/home/nandy'
|
USER_HOME='/home/nandy'
|
||||||
ADD_METADATA_VOLUME=$([ "${ADD_METADATA_VOLUME}" = "true" ] && echo "-v ${PWD}/metadata:${USER_HOME}/metadata" || \
|
ADD_METADATA_VOLUME=$([ "${ADD_METADATA_VOLUME}" = "true" ] && echo "-v ${PWD}/meta:${USER_HOME}/meta" || \
|
||||||
echo "")
|
echo "")
|
||||||
|
|
||||||
|
|
||||||
NPM_RUNTIME_CHECK_IMAGE=${NPM_RUNTIME_CHECK_IMAGE:-npm_runtime_check}
|
VSIX_RUNTIME_CHECK_IMAGE=${VSIX_RUNTIME_CHECK_IMAGE:-vsix_runtime_check}
|
||||||
|
|
||||||
echo "=====================> start ${NPM_RUNTIME_CHECK_IMAGE}:${NPM_RUNTIME_CHECK_VER:-latest}"
|
echo "=====================> start ${VSIX_RUNTIME_CHECK_IMAGE}:${VSIX_RUNTIME_CHECK_VER:-latest}"
|
||||||
|
|
||||||
docker run --rm -i \
|
docker run --rm -i \
|
||||||
|
--platform linux/amd64 \
|
||||||
--network ${NETWORK} \
|
--network ${NETWORK} \
|
||||||
--add-host=host.docker.internal:host-gateway \
|
--add-host=host.docker.internal:host-gateway \
|
||||||
--privileged \
|
--privileged \
|
||||||
@@ -92,7 +93,7 @@ docker run --rm -i \
|
|||||||
-e PACKAGE_FILE=$PACKAGE_FILE \
|
-e PACKAGE_FILE=$PACKAGE_FILE \
|
||||||
-e PACKAGE_NAME=$PACKAGE_NAME \
|
-e PACKAGE_NAME=$PACKAGE_NAME \
|
||||||
-e PACKAGE_VERSION=$PACKAGE_VERSION \
|
-e PACKAGE_VERSION=$PACKAGE_VERSION \
|
||||||
-e METADATA_PATH="${USER_HOME}/metadata" \
|
-e METADATA_PATH="${USER_HOME}/meta" \
|
||||||
-e PLUGIN_NAME="${PLUGIN_NAME}" \
|
-e PLUGIN_NAME="${PLUGIN_NAME}" \
|
||||||
-e PIPELINE_ID=${PIPELINE_ID} \
|
-e PIPELINE_ID=${PIPELINE_ID} \
|
||||||
-e SCANHUB_URL=${SCANHUB_URL} \
|
-e SCANHUB_URL=${SCANHUB_URL} \
|
||||||
@@ -109,11 +110,11 @@ docker run --rm -i \
|
|||||||
-e LOG_LEVEL="DEBUG" \
|
-e LOG_LEVEL="DEBUG" \
|
||||||
-e T05_DEFAULT_TIMEOUT=1800 \
|
-e T05_DEFAULT_TIMEOUT=1800 \
|
||||||
-e SCANFEEDER_PATH="/usr/local/bin/scanfeeder" \
|
-e SCANFEEDER_PATH="/usr/local/bin/scanfeeder" \
|
||||||
"${NPM_RUNTIME_CHECK_IMAGE}:${NPM_RUNTIME_CHECK_VER:-latest}" bash -l -c "source ${USER_HOME}/processing/venv/bin/activate && python3 ${USER_HOME}/processing/npm_package_processing.py --file_path=/input/${PACKAGE_FILE} --report_path=/report"
|
"${VSIX_RUNTIME_CHECK_IMAGE}:${VSIX_RUNTIME_CHECK_VER:-latest}" bash -l -c "source ${USER_HOME}/processing/venv/bin/activate && python3 ${USER_HOME}/processing/vsix_package_processing.py --file_path=/input/${PACKAGE_FILE} --report_path=/report"
|
||||||
|
|
||||||
RES_PLUGIN=$?
|
RES_PLUGIN=$?
|
||||||
echo "=====================> finished ${NPM_RUNTIME_CHECK_IMAGE}:${NPM_RUNTIME_CHECK_VER:-latest} with code ${RES_PLUGIN}"
|
echo "=====================> finished ${VSIX_RUNTIME_CHECK_IMAGE}:${VSIX_RUNTIME_CHECK_VER:-latest} with code ${RES_PLUGIN}"
|
||||||
|
|
||||||
finish_with_pipe
|
finish_with_pipe
|
||||||
echo "Npm runtime check is done, exit with code ${RES_PLUGIN}"
|
echo "VSIX runtime check is done, exit with code ${RES_PLUGIN}"
|
||||||
exit ${RES_PLUGIN}
|
exit ${RES_PLUGIN}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import gzip
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os.path
|
import os.path
|
||||||
import pathlib
|
import pathlib
|
||||||
import argparse
|
import argparse
|
||||||
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import subprocess
|
import subprocess
|
||||||
import zipfile
|
import zipfile
|
||||||
@@ -42,7 +44,7 @@ class VSIX_T5Checker(T5Checker):
|
|||||||
yaml_specific_version = dict()
|
yaml_specific_version = dict()
|
||||||
|
|
||||||
stage_name='load_yaml_config'
|
stage_name='load_yaml_config'
|
||||||
metadata_root = pathlib.Path(settings.get("metadata_path", "../metadata"))
|
metadata_root = pathlib.Path(settings.get("metadata_path", "../meta"))
|
||||||
coverage_tool_paths = (
|
coverage_tool_paths = (
|
||||||
metadata_root / "coverage_tools.yaml",
|
metadata_root / "coverage_tools.yaml",
|
||||||
pathlib.Path("../meta/coverage_tools.yaml"),
|
pathlib.Path("../meta/coverage_tools.yaml"),
|
||||||
@@ -141,12 +143,25 @@ class VSIX_T5Checker(T5Checker):
|
|||||||
try:
|
try:
|
||||||
result_folder.mkdir(parents=True, exist_ok=False)
|
result_folder.mkdir(parents=True, exist_ok=False)
|
||||||
result_root = result_folder.resolve()
|
result_root = result_folder.resolve()
|
||||||
with zipfile.ZipFile(archive_path) as archive:
|
|
||||||
|
archive_source = archive_path
|
||||||
|
decompressed_path = None
|
||||||
|
with archive_path.open("rb") as package_file:
|
||||||
|
is_gzip_wrapped = package_file.read(2) == b"\x1f\x8b"
|
||||||
|
if is_gzip_wrapped:
|
||||||
|
decompressed_path = result_folder.parent / f"{archive_path.name}.decompressed"
|
||||||
|
with gzip.open(archive_path, "rb") as source, decompressed_path.open("wb") as output:
|
||||||
|
shutil.copyfileobj(source, output)
|
||||||
|
archive_source = decompressed_path
|
||||||
|
|
||||||
|
with zipfile.ZipFile(archive_source) as archive:
|
||||||
for member in archive.infolist():
|
for member in archive.infolist():
|
||||||
destination = (result_folder / member.filename).resolve()
|
destination = (result_folder / member.filename).resolve()
|
||||||
if destination != result_root and result_root not in destination.parents:
|
if destination != result_root and result_root not in destination.parents:
|
||||||
raise ValueError(f"Unsafe path in VSIX archive: {member.filename}")
|
raise ValueError(f"Unsafe path in VSIX archive: {member.filename}")
|
||||||
archive.extractall(result_folder)
|
archive.extractall(result_folder)
|
||||||
|
if decompressed_path:
|
||||||
|
decompressed_path.unlink()
|
||||||
except (OSError, ValueError, zipfile.BadZipFile) as e:
|
except (OSError, ValueError, zipfile.BadZipFile) as e:
|
||||||
self.stage_error(
|
self.stage_error(
|
||||||
stage_name=substep_name,
|
stage_name=substep_name,
|
||||||
|
|||||||
Reference in New Issue
Block a user