574 lines
24 KiB
Bash
Executable file
574 lines
24 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd -P)
|
|
repo_root=$(CDPATH= cd "$script_dir/.." && pwd -P)
|
|
fixture_version=1.0.0
|
|
|
|
for required_command in cargo jq bun rg; do
|
|
if ! command -v "$required_command" >/dev/null 2>&1; then
|
|
printf 'version-sync: required command not found: %s\n' "$required_command" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
current_version=$(bash "$repo_root/scripts/workspace-version.sh")
|
|
|
|
cd "$repo_root"
|
|
|
|
errors=0
|
|
fixture_scan_skipped=0
|
|
|
|
report_missing() {
|
|
file=$1
|
|
message=$2
|
|
printf '%s:1: error: %s\n' "$file" "$message" >&2
|
|
errors=1
|
|
}
|
|
|
|
require_regex() {
|
|
file=$1
|
|
pattern=$2
|
|
message=$3
|
|
if [[ ! -f "$file" ]] || ! rg --quiet -- "$pattern" "$file"; then
|
|
report_missing "$file" "$message"
|
|
fi
|
|
}
|
|
|
|
require_statement() {
|
|
file=$1
|
|
statement_pattern=$2
|
|
message=$3
|
|
require_regex "$file" \
|
|
"^[[:space:]]*${statement_pattern}[[:space:]]*(#.*)?$" \
|
|
"$message"
|
|
}
|
|
|
|
require_single_assignment() {
|
|
file=$1
|
|
variable=$2
|
|
assignment_count=$(rg --count-matches \
|
|
"^[[:space:]]*${variable}[[:space:]]*=" "$file" || true)
|
|
if [[ "$assignment_count" != 1 ]]; then
|
|
report_missing "$file" \
|
|
"expected exactly one active ${variable} assignment (found ${assignment_count:-0})"
|
|
fi
|
|
}
|
|
|
|
workflow_job_block() {
|
|
job=$1
|
|
awk -v job="$job" '
|
|
$0 == " " job ":" {
|
|
in_job = 1
|
|
}
|
|
in_job && $0 ~ /^ [0-9A-Za-z_-]+:$/ && $0 != " " job ":" {
|
|
exit
|
|
}
|
|
in_job {
|
|
print
|
|
}
|
|
' "$release_workflow"
|
|
}
|
|
|
|
require_workflow_job_regex() {
|
|
job=$1
|
|
pattern=$2
|
|
message=$3
|
|
job_block=$(workflow_job_block "$job")
|
|
if [[ -z "$job_block" ]] || ! rg --quiet -- "$pattern" <<< "$job_block"; then
|
|
report_missing "$release_workflow" "$message"
|
|
fi
|
|
}
|
|
|
|
validate_macos_version_behavior() {
|
|
file=$1
|
|
interpreter=$2
|
|
validation_pattern=$3
|
|
|
|
if ! rg --quiet -- "$validation_pattern" "$file"; then
|
|
return
|
|
fi
|
|
|
|
validation_status=0
|
|
validation_output=$(env -u OPENPENCIL_VERSION OPENPENCIL_VALIDATE_VERSION_ONLY=1 \
|
|
"$interpreter" "$file" 2>&1) || validation_status=$?
|
|
if [[ "$validation_status" -ne 0 || "$validation_output" != "$current_version" ]]; then
|
|
report_missing "$file" \
|
|
'validate-only mode without an override must print the canonical version and exit 0'
|
|
fi
|
|
|
|
validation_status=0
|
|
validation_output=$(OPENPENCIL_VERSION="$current_version" \
|
|
OPENPENCIL_VALIDATE_VERSION_ONLY=1 "$interpreter" "$file" 2>&1) || \
|
|
validation_status=$?
|
|
if [[ "$validation_status" -ne 0 || "$validation_output" != "$current_version" ]]; then
|
|
report_missing "$file" \
|
|
'validate-only mode with a matching override must print the canonical version and exit 0'
|
|
fi
|
|
|
|
mismatch_version=9.9.9
|
|
if [[ "$current_version" == "$mismatch_version" ]]; then
|
|
mismatch_version=0.0.0
|
|
fi
|
|
expected_mismatch="bundle-macos: error: OPENPENCIL_VERSION (${mismatch_version}) must match Cargo workspace version (${current_version})"
|
|
validation_status=0
|
|
validation_output=$(OPENPENCIL_VERSION="$mismatch_version" \
|
|
OPENPENCIL_VALIDATE_VERSION_ONLY=1 "$interpreter" "$file" 2>&1) || \
|
|
validation_status=$?
|
|
if [[ "$validation_status" -ne 1 || "$validation_output" != "$expected_mismatch" ]]; then
|
|
report_missing "$file" \
|
|
'mismatched OPENPENCIL_VERSION must fail validation with actionable error'
|
|
fi
|
|
}
|
|
|
|
reject_example_semver_tokens() {
|
|
file=$1
|
|
semver_pattern='(^|[^0-9A-Za-z.])(v?(0|[1-9][0-9]*)[.](0|[1-9][0-9]*)[.](0|[1-9][0-9]*)(-[0-9A-Za-z-]+([.][0-9A-Za-z-]+)*)?([+][0-9A-Za-z-]+([.][0-9A-Za-z-]+)*)?)([^0-9A-Za-z.]|[.]+([^0-9A-Za-z.]|$)|$)'
|
|
rg_status=0
|
|
matches=$(rg --line-number --with-filename --color never -- \
|
|
"$semver_pattern" "$file") || rg_status=$?
|
|
|
|
if [[ "$rg_status" -gt 1 ]]; then
|
|
printf '%s:1: error: failed to scan version examples (rg status %s)\n' \
|
|
"$file" "$rg_status" >&2
|
|
errors=1
|
|
return
|
|
fi
|
|
|
|
if [[ -n "$matches" ]]; then
|
|
while IFS=: read -r match_file match_line match_text; do
|
|
if [[ "$match_file" == scripts/package-windows.nsi && \
|
|
"$match_text" == ' !define VERSION "0.0.0"' ]]; then
|
|
continue
|
|
fi
|
|
printf '%s:%s: error: version examples must use X.Y.Z or <version>, not a SemVer release\n' \
|
|
"$match_file" "$match_line" >&2
|
|
errors=1
|
|
done <<< "$matches"
|
|
fi
|
|
}
|
|
|
|
validate_top_level_readmes() {
|
|
semver_pattern='(?<![0-9A-Za-z.])v?(?:0|[1-9][0-9]*)[.](?:0|[1-9][0-9]*)[.](?:0|[1-9][0-9]*)(?:-[0-9A-Za-z-]+(?:[.][0-9A-Za-z-]+)*)?(?:[+][0-9A-Za-z-]+(?:[.][0-9A-Za-z-]+)*)?(?![0-9A-Za-z]|[.][0-9A-Za-z])'
|
|
readme_files=(README*.md)
|
|
|
|
for file in "${readme_files[@]}"; do
|
|
rg_status=0
|
|
matches=$(rg --pcre2 --only-matching --line-number --with-filename \
|
|
--color never -- "$semver_pattern" "$file") || rg_status=$?
|
|
if [[ "$rg_status" -gt 1 ]]; then
|
|
printf '%s:1: error: failed to scan README version policy (rg status %s)\n' \
|
|
"$file" "$rg_status" >&2
|
|
errors=1
|
|
continue
|
|
fi
|
|
|
|
if [[ -n "$matches" ]]; then
|
|
while IFS=: read -r match_file match_line token; do
|
|
if [[ "$token" == v0.7.5 ]]; then
|
|
continue
|
|
fi
|
|
printf '%s:%s: error: top-level READMEs must not contain active product SemVer releases; use vX.Y.Z or the workspace-version reader\n' \
|
|
"$match_file" "$match_line" >&2
|
|
errors=1
|
|
done <<< "$matches"
|
|
fi
|
|
done
|
|
}
|
|
|
|
validate_version_sync_ci_readme_paths() {
|
|
ci_workflow=.github/workflows/version-sync.yml
|
|
if [[ ! -f "$ci_workflow" ]]; then
|
|
report_missing "$ci_workflow" \
|
|
'version-sync CI must run for top-level README changes in pull requests and pushes'
|
|
return
|
|
fi
|
|
|
|
read -r pull_request_readmes push_readmes < <(
|
|
awk '
|
|
/^ pull_request:[[:space:]]*$/ { event = "pull_request"; next }
|
|
/^ push:[[:space:]]*$/ { event = "push"; next }
|
|
/^[^[:space:]]/ { event = "" }
|
|
/^[[:space:]]*-[[:space:]]+"README[*][.]md"[[:space:]]*$/ {
|
|
if (event == "pull_request") pull_request_count++
|
|
if (event == "push") push_count++
|
|
}
|
|
END { print pull_request_count + 0, push_count + 0 }
|
|
' "$ci_workflow"
|
|
)
|
|
if [[ "$pull_request_readmes" != 1 || "$push_readmes" != 1 ]]; then
|
|
report_missing "$ci_workflow" \
|
|
'version-sync CI must run for top-level README changes in pull requests and pushes'
|
|
fi
|
|
}
|
|
|
|
reject_matches() {
|
|
mode=$1
|
|
file=$2
|
|
pattern=$3
|
|
message=$4
|
|
rg_status=0
|
|
if [[ "$mode" == fixed ]]; then
|
|
matches=$(rg --fixed-strings --line-number --with-filename --color never -- \
|
|
"$pattern" "$file") || rg_status=$?
|
|
else
|
|
matches=$(rg --line-number --with-filename --color never -- \
|
|
"$pattern" "$file") || rg_status=$?
|
|
fi
|
|
|
|
if [[ "$rg_status" -gt 1 ]]; then
|
|
printf '%s:1: error: failed to scan version policy (rg status %s)\n' \
|
|
"$file" "$rg_status" >&2
|
|
errors=1
|
|
return
|
|
fi
|
|
|
|
if [[ -n "$matches" ]]; then
|
|
while IFS=: read -r match_file match_line _; do
|
|
printf '%s:%s: error: %s\n' "$match_file" "$match_line" "$message" >&2
|
|
done <<< "$matches"
|
|
errors=1
|
|
fi
|
|
}
|
|
|
|
validate_manifest_workspace_version() {
|
|
manifest=$1
|
|
relative_manifest=$2
|
|
|
|
if [[ ! -f "$manifest" ]]; then
|
|
report_missing "$relative_manifest" \
|
|
'local op-* package manifest returned by cargo metadata does not exist'
|
|
return
|
|
fi
|
|
|
|
manifest_result=$(awk '
|
|
BEGIN {
|
|
in_package = 0
|
|
inheritance_count = 0
|
|
package_header_line = 1
|
|
declaration_line = 0
|
|
}
|
|
{
|
|
line = $0
|
|
sub(/\r$/, "", line)
|
|
|
|
if (line ~ /^[[:space:]]*\[[^]]+\][[:space:]]*(#.*)?$/) {
|
|
header = line
|
|
sub(/[[:space:]]*#.*/, "", header)
|
|
gsub(/[[:space:]]/, "", header)
|
|
in_package = (header == "[package]")
|
|
if (in_package && package_header_line == 1) {
|
|
package_header_line = NR
|
|
}
|
|
next
|
|
}
|
|
|
|
if (!in_package || line ~ /^[[:space:]]*#/) {
|
|
next
|
|
}
|
|
|
|
code = line
|
|
sub(/[[:space:]]*#.*/, "", code)
|
|
if (code ~ /^[[:space:]]*version[.]workspace[[:space:]]*=[[:space:]]*true[[:space:]]*$/) {
|
|
inheritance_count++
|
|
if (declaration_line == 0) declaration_line = NR
|
|
next
|
|
}
|
|
if (declaration_line == 0 &&
|
|
code ~ /^[[:space:]]*version([.]workspace)?[[:space:]]*=/) {
|
|
declaration_line = NR
|
|
}
|
|
}
|
|
END {
|
|
diagnostic_line = declaration_line == 0 ? package_header_line : declaration_line
|
|
printf "%d\t%d\n", inheritance_count, diagnostic_line
|
|
}
|
|
' "$manifest")
|
|
IFS=$'\t' read -r inheritance_count diagnostic_line <<< "$manifest_result"
|
|
|
|
if [[ "$inheritance_count" != 1 ]]; then
|
|
printf '%s:%s: error: local op-* package must declare exactly one active version.workspace = true in [package] (found %s)\n' \
|
|
"$relative_manifest" "$diagnostic_line" "$inheritance_count" >&2
|
|
errors=1
|
|
fi
|
|
}
|
|
|
|
validate_workspace_package_versions() {
|
|
if ! metadata=$(cargo metadata --no-deps --format-version 1 --locked); then
|
|
report_missing Cargo.lock \
|
|
'cargo metadata --locked failed; run scripts/sync-version.sh to refresh the lockfile'
|
|
return
|
|
fi
|
|
|
|
package_rows_status=0
|
|
package_rows=$(printf '%s\n' "$metadata" | jq -r \
|
|
--arg crates_prefix "$repo_root/crates/" \
|
|
'
|
|
.packages[]?
|
|
| select(.name | startswith("op-"))
|
|
| select(.manifest_path | startswith($crates_prefix))
|
|
| [.manifest_path, .name, .version]
|
|
| @tsv
|
|
' 2>&1) || package_rows_status=$?
|
|
if [[ "$package_rows_status" -ne 0 ]]; then
|
|
printf '%s\n' "$package_rows" >&2
|
|
report_missing Cargo.toml 'failed to inspect cargo metadata with jq'
|
|
return
|
|
fi
|
|
|
|
if [[ -z "$package_rows" ]]; then
|
|
report_missing Cargo.toml \
|
|
'cargo metadata found no local op-* workspace packages under crates; verify workspace members and repository path resolution'
|
|
return
|
|
fi
|
|
|
|
while IFS=$'\t' read -r manifest package package_version; do
|
|
relative_manifest=${manifest#"$repo_root"/}
|
|
validate_manifest_workspace_version "$manifest" "$relative_manifest"
|
|
if [[ "$package_version" != "$current_version" ]]; then
|
|
printf '%s:1: error: workspace package %s has version %s; expected %s\n' \
|
|
"$relative_manifest" "$package" "$package_version" "$current_version" >&2
|
|
errors=1
|
|
fi
|
|
done <<< "$package_rows"
|
|
}
|
|
|
|
validate_package_versions() {
|
|
package_status=0
|
|
package_output=$(cd packages && bun run sync-version:check 2>&1) || package_status=$?
|
|
if [[ "$package_status" -ne 0 ]]; then
|
|
printf '%s\n' "$package_output" >&2
|
|
report_missing packages \
|
|
'bun run sync-version:check failed; run scripts/sync-version.sh to repair package drift'
|
|
fi
|
|
}
|
|
|
|
validate_release_tag() {
|
|
tag_name=
|
|
if [[ "${GITHUB_REF:-}" == refs/tags/v* ]]; then
|
|
tag_name=${GITHUB_REF#refs/tags/}
|
|
elif [[ -z "${GITHUB_REF:-}" && "${GITHUB_REF_NAME:-}" == v* ]]; then
|
|
tag_name=$GITHUB_REF_NAME
|
|
fi
|
|
|
|
if [[ -n "$tag_name" && "${tag_name#v}" != "$current_version" ]]; then
|
|
report_missing environment \
|
|
"release tag ${tag_name} does not match Cargo workspace version ${current_version}"
|
|
fi
|
|
}
|
|
|
|
validate_cli_bundle_version_template() {
|
|
bundle=crates/op-cli/assets/skill-bundle.json
|
|
sentinel=__OPENPENCIL_VERSION__
|
|
sentinel_count=$(rg --fixed-strings --count-matches -- "$sentinel" "$bundle" || true)
|
|
if [[ "${sentinel_count:-0}" != 5 ]]; then
|
|
report_missing "$bundle" \
|
|
"expected exactly 5 version sentinels ${sentinel} (found ${sentinel_count:-0})"
|
|
fi
|
|
reject_matches fixed "$bundle" "$current_version" \
|
|
'embedded CLI bundle must not contain the canonical version literal'
|
|
}
|
|
|
|
validate_rust_product_version_producers() {
|
|
require_statement crates/op-editor-core/src/state.rs \
|
|
'version:[[:space:]]*env!\("CARGO_PKG_VERSION"\)[.]to_owned\(\),' \
|
|
'empty documents must derive their version from CARGO_PKG_VERSION'
|
|
|
|
host_support=crates/op-editor-core/src/host_support.rs
|
|
read -r host_production_version_count host_test_version_count < <(
|
|
awk \
|
|
-v needle='src.replace("__OPENPENCIL_VERSION__", env!("CARGO_PKG_VERSION"))' \
|
|
'
|
|
/^#[[:space:]]*\[cfg\(test\)\][[:space:]]*$/ { in_tests = 1 }
|
|
index($0, needle) {
|
|
if (in_tests) test_count++
|
|
else production_count++
|
|
}
|
|
END { print production_count + 0, test_count + 0 }
|
|
' "$host_support"
|
|
)
|
|
if [[ "$host_production_version_count" != 2 ]]; then
|
|
report_missing "$host_support" \
|
|
"expected exactly 2 production document templates to derive from CARGO_PKG_VERSION (found ${host_production_version_count:-0})"
|
|
fi
|
|
if [[ "$host_test_version_count" != 0 ]]; then
|
|
report_missing "$host_support" \
|
|
'ordinary test fixtures must use stable 1.0.0 instead of CARGO_PKG_VERSION'
|
|
fi
|
|
|
|
cli_source=crates/op-cli/src/app_control_cli.rs
|
|
require_regex "$cli_source" 'env!\("CARGO_PKG_VERSION"\)' \
|
|
'CLI starter documents must derive their version from CARGO_PKG_VERSION'
|
|
reject_matches regex "$cli_source" \
|
|
'"version"[^[:cntrl:]]*"[0-9]+[.][0-9]+[.][0-9]+' \
|
|
'CLI starter documents must not hard-code a product version'
|
|
|
|
reject_matches regex crates/op-host-desktop/Cargo.toml \
|
|
'^[[:space:]]*op-host-native[[:space:]]*=.*path[[:space:]]*=.*version[[:space:]]*=' \
|
|
'local op-host-native dependency must not duplicate the product version'
|
|
}
|
|
|
|
validate_workspace_package_versions
|
|
validate_package_versions
|
|
validate_release_tag
|
|
validate_cli_bundle_version_template
|
|
validate_rust_product_version_producers
|
|
validate_top_level_readmes
|
|
validate_version_sync_ci_readme_paths
|
|
|
|
if [[ "$current_version" == "$fixture_version" ]]; then
|
|
printf 'version-sync: current product version %s equals stable fixture version %s; skipping literal fixture drift scan because stable fixtures and product-version literals are indistinguishable\n' \
|
|
"$current_version" "$fixture_version"
|
|
fixture_scan_skipped=1
|
|
else
|
|
rg_status=0
|
|
matches=$(rg \
|
|
--fixed-strings \
|
|
--line-number \
|
|
--with-filename \
|
|
--color never \
|
|
--glob '*.rs' \
|
|
--glob '!**/op-host-desktop/src/update_check.rs' \
|
|
"$current_version" \
|
|
crates) || rg_status=$?
|
|
|
|
if [[ "$rg_status" -gt 1 ]]; then
|
|
printf 'version-sync: failed to scan Rust sources with rg (status %s)\n' "$rg_status" >&2
|
|
exit "$rg_status"
|
|
fi
|
|
|
|
if [[ -n "$matches" ]]; then
|
|
printf 'version-sync: ordinary Rust fixtures copy current product version %s:\n' \
|
|
"$current_version" >&2
|
|
printf '%s\n' "$matches" >&2
|
|
printf 'version-sync: use stable %s test data unless a test explicitly covers compatibility, migration, or updates\n' \
|
|
"$fixture_version" >&2
|
|
errors=1
|
|
fi
|
|
fi
|
|
|
|
for macos_script in scripts/bundle-macos.sh tools/bundle-macos.sh; do
|
|
require_single_assignment "$macos_script" CANONICAL_VERSION
|
|
require_single_assignment "$macos_script" APP_VERSION
|
|
if [[ "$macos_script" == scripts/bundle-macos.sh ]]; then
|
|
require_statement "$macos_script" \
|
|
'CANONICAL_VERSION[[:space:]]*=[[:space:]]*"\$\("\$WS_ROOT/scripts/workspace-version[.]sh"\)"' \
|
|
'macOS packaging must assign CANONICAL_VERSION from scripts/workspace-version.sh'
|
|
require_statement "$macos_script" \
|
|
'/usr/libexec/PlistBuddy[[:space:]]+-c[[:space:]]+"Set :CFBundleShortVersionString \$APP_VERSION"[[:space:]]+"\$PLIST"' \
|
|
'CFBundleShortVersionString must use APP_VERSION'
|
|
require_statement "$macos_script" \
|
|
'if[[:space:]]+\[\[[[:space:]]*"\$APP_VERSION"[[:space:]]*!=[[:space:]]*"\$CANONICAL_VERSION"[[:space:]]*\]\][[:space:]]*;[[:space:]]*then' \
|
|
'OPENPENCIL_VERSION overrides must be rejected when they differ from Cargo'
|
|
validation_pattern='^[[:space:]]*if[[:space:]]+\[\[[[:space:]]*"\$\{OPENPENCIL_VALIDATE_VERSION_ONLY:-\}"[[:space:]]*==[[:space:]]*1[[:space:]]*\]\][[:space:]]*;[[:space:]]*then[[:space:]]*$'
|
|
require_regex "$macos_script" "$validation_pattern" \
|
|
'macOS packaging must support OPENPENCIL_VALIDATE_VERSION_ONLY immediately after version validation'
|
|
validate_macos_version_behavior "$macos_script" bash "$validation_pattern"
|
|
else
|
|
require_statement "$macos_script" \
|
|
'CANONICAL_VERSION[[:space:]]*=[[:space:]]*"\$\("\$ROOT/scripts/workspace-version[.]sh"\)"' \
|
|
'macOS packaging must assign CANONICAL_VERSION from scripts/workspace-version.sh'
|
|
require_statement "$macos_script" \
|
|
'<key>CFBundleShortVersionString</key><string>\$\{APP_VERSION\}</string>' \
|
|
'CFBundleShortVersionString must use APP_VERSION'
|
|
require_statement "$macos_script" \
|
|
'if[[:space:]]+\[[[:space:]]*"\$APP_VERSION"[[:space:]]*!=[[:space:]]*"\$CANONICAL_VERSION"[[:space:]]*\][[:space:]]*;[[:space:]]*then' \
|
|
'OPENPENCIL_VERSION overrides must be rejected when they differ from Cargo'
|
|
validation_pattern='^[[:space:]]*if[[:space:]]+\[[[:space:]]*"\$\{OPENPENCIL_VALIDATE_VERSION_ONLY:-\}"[[:space:]]*=[[:space:]]*1[[:space:]]*\][[:space:]]*;[[:space:]]*then[[:space:]]*$'
|
|
require_regex "$macos_script" "$validation_pattern" \
|
|
'macOS packaging must support OPENPENCIL_VALIDATE_VERSION_ONLY immediately after version validation'
|
|
validate_macos_version_behavior "$macos_script" sh "$validation_pattern"
|
|
fi
|
|
require_statement "$macos_script" \
|
|
'APP_VERSION[[:space:]]*=[[:space:]]*"\$\{OPENPENCIL_VERSION:-\$CANONICAL_VERSION\}"' \
|
|
'OPENPENCIL_VERSION must default to the Cargo workspace version'
|
|
reject_matches regex "$macos_script" \
|
|
'OPENPENCIL_VERSION:-[0-9]+[.][0-9]+[.][0-9]+' \
|
|
'OPENPENCIL_VERSION must fall back to the Cargo workspace version'
|
|
reject_matches regex "$macos_script" \
|
|
'CFBundleShortVersionString[^[:cntrl:]]*[0-9]+[.][0-9]+[.][0-9]+' \
|
|
'CFBundleShortVersionString must use the resolved Cargo workspace version'
|
|
done
|
|
|
|
require_regex scripts/package-windows.nsi \
|
|
'^[[:space:]]*;[[:space:]]*makensis[[:space:]]+"/DVERSION=X[.]Y[.]Z"' \
|
|
'NSIS compile example must use /DVERSION=X.Y.Z'
|
|
require_regex scripts/install-op.sh \
|
|
'^[[:space:]]*#[[:space:]]*OP_VERSION=(X[.]Y[.]Z|<version>)[[:space:]]+[.]/install-op[.]sh' \
|
|
'install usage example must use OP_VERSION=X.Y.Z or OP_VERSION=<version>'
|
|
reject_example_semver_tokens scripts/package-windows.nsi
|
|
reject_example_semver_tokens scripts/install-op.sh
|
|
|
|
release_workflow=.github/workflows/rust-release.yml
|
|
require_workflow_job_regex version \
|
|
'^[[:space:]]*-[[:space:]]+uses:[[:space:]]+actions/checkout@v4[[:space:]]*$' \
|
|
'version preflight must check out the repository'
|
|
require_workflow_job_regex version \
|
|
'^[[:space:]]*version:[[:space:]]*\$\{\{[[:space:]]*steps[.]version[.]outputs[.]version[[:space:]]*\}\}[[:space:]]*$' \
|
|
'version preflight must expose the canonical version as a job output'
|
|
require_workflow_job_regex version \
|
|
'^[[:space:]]*(-[[:space:]]+)?id:[[:space:]]+version[[:space:]]*$' \
|
|
'version preflight must identify the canonical version step'
|
|
require_workflow_job_regex version \
|
|
'^[[:space:]]*cargo_version[[:space:]]*=[[:space:]]*"\$\(scripts/workspace-version[.]sh\)"[[:space:]]*$' \
|
|
'release version computation must invoke scripts/workspace-version.sh'
|
|
require_workflow_job_regex version \
|
|
'^[[:space:]]*tag_version[[:space:]]*=[[:space:]]*"\$\{GITHUB_REF_NAME#v\}"[[:space:]]*$' \
|
|
'release version computation must derive the version from v* tags'
|
|
require_workflow_job_regex version \
|
|
'^[[:space:]]*if[[:space:]]+\[\[[[:space:]]*"\$tag_version"[[:space:]]*!=[[:space:]]*"\$cargo_version"[[:space:]]*\]\][[:space:]]*;[[:space:]]*then[[:space:]]*$' \
|
|
'release tags must be compared with the Cargo workspace version'
|
|
require_workflow_job_regex version \
|
|
'^[[:space:]]*echo[[:space:]]+"version=\$cargo_version"[[:space:]]*>>[[:space:]]*"\$GITHUB_OUTPUT"[[:space:]]*$' \
|
|
'version preflight must write the canonical version to GITHUB_OUTPUT'
|
|
require_single_assignment "$release_workflow" cargo_version
|
|
require_single_assignment "$release_workflow" tag_version
|
|
require_workflow_job_regex build \
|
|
'^[[:space:]]*needs:[[:space:]]*version[[:space:]]*$' \
|
|
'build must depend on the version preflight job'
|
|
require_workflow_job_regex web-docker \
|
|
'^[[:space:]]*needs:[[:space:]]*version[[:space:]]*$' \
|
|
'web-docker must depend on the version preflight job'
|
|
require_workflow_job_regex sdk-packages \
|
|
'^[[:space:]]*needs:[[:space:]]*version[[:space:]]*$' \
|
|
'sdk-packages must depend on the version preflight job'
|
|
require_workflow_job_regex release-draft \
|
|
'^[[:space:]]*needs:[[:space:]]*\[version,[[:space:]]*build,[[:space:]]*web-docker,[[:space:]]*sdk-packages,[[:space:]]*vsix\][[:space:]]*$' \
|
|
'release-draft must preserve artifact dependencies and depend on version preflight'
|
|
require_workflow_job_regex package-managers \
|
|
'^[[:space:]]*needs:[[:space:]]*\[version,[[:space:]]*release-draft\][[:space:]]*$' \
|
|
'package-managers must preserve release dependency and depend on version preflight'
|
|
for version_consumer in build web-docker sdk-packages release-draft package-managers; do
|
|
require_workflow_job_regex "$version_consumer" \
|
|
'needs[.]version[.]outputs[.]version' \
|
|
"${version_consumer} must consume the canonical version job output"
|
|
done
|
|
require_workflow_job_regex sdk-packages \
|
|
'bun[[:space:]]+run[[:space:]]+sync-version:check' \
|
|
'sdk-packages must verify package versions after installing dependencies'
|
|
reject_matches regex "$release_workflow" \
|
|
'^[[:space:]]*version[[:space:]]*=.*GITHUB_REF_NAME#v' \
|
|
'publish paths must consume the canonical version job output'
|
|
reject_matches regex "$release_workflow" \
|
|
'^[[:space:]]*tag[[:space:]]*=[[:space:]]*"\$GITHUB_REF_NAME"' \
|
|
'publish paths must not begin an independent two-step tag derivation'
|
|
reject_matches regex "$release_workflow" \
|
|
'^[[:space:]]*version[[:space:]]*=[[:space:]]*"\$\{tag#v\}"' \
|
|
'publish paths must consume the canonical version job output'
|
|
reject_matches fixed "$release_workflow" 'echo "OP_VERSION=${GITHUB_REF_NAME#v}"' \
|
|
'OP_VERSION must not be written directly from the release tag'
|
|
reject_matches fixed "$release_workflow" 'echo "OP_VERSION=${ver:-0.0.0}"' \
|
|
'OP_VERSION must not use an independent manifest parser or fallback'
|
|
|
|
if [[ "$errors" -ne 0 ]]; then
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$fixture_scan_skipped" -eq 0 ]]; then
|
|
printf 'version-sync: no ordinary Rust fixtures copy current product version %s\n' \
|
|
"$current_version"
|
|
fi
|
|
printf 'version-sync: all managed versions derive from Cargo workspace version %s\n' \
|
|
"$current_version"
|