From bd53eaec3e1a20c517cf3eb15663c0826b7dc27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Potoc=CC=8Cek?= Date: Mon, 11 May 2026 11:36:28 +0200 Subject: [PATCH 01/22] Add universal-codecharta composite actions and reusable workflow Introduces a CodeCharta + DependaCharta integration as a reusable workflow + two composite actions, so consumer repos drop in a ~20-line workflow file instead of vendoring the full bash pipeline. .github/actions/universal-codecharta-snapshot/ Runs ccsh + tokei + gitlogparser + DependaCharta and merges the outputs into ${project_name}.cc.json.gz (+ .cg.json). Supports single snapshot (HEAD) or history (N recent merges). Stack profile enum (swift/kotlin/kmp/ft-fullstack/multi) maps to default ccsh -fe= and tokei --type filters; both overridable. .github/actions/universal-codecharta-publish/ Publishes artifacts to the data repo (default futuredapp/codebase-architectures). Modes: preview, history, bulk-history, delete-preview, set-latest. Idempotent on bulk-history; retry-on-conflict push. .github/workflows/universal-codecharta.yml Reusable workflow orchestrating three jobs (generate / cleanup / backfill) with hardcoded org constants. PR previews, develop/main history snapshots with optional PR-title meta sidecars, and workflow_dispatch backfill. Internal action refs pinned to @ci/codecharta-actions for smoke testing; bump via tools/bump-action-refs.sh before tagging a release. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../universal-codecharta-publish/action.yml | 93 ++++ .../codecharta-publish.sh | 236 +++++++++ .../universal-codecharta-snapshot/action.yml | 104 ++++ .../codecharta.sh | 455 ++++++++++++++++++ .github/workflows/universal-codecharta.yml | 338 +++++++++++++ 5 files changed, 1226 insertions(+) create mode 100644 .github/actions/universal-codecharta-publish/action.yml create mode 100755 .github/actions/universal-codecharta-publish/codecharta-publish.sh create mode 100644 .github/actions/universal-codecharta-snapshot/action.yml create mode 100755 .github/actions/universal-codecharta-snapshot/codecharta.sh create mode 100644 .github/workflows/universal-codecharta.yml diff --git a/.github/actions/universal-codecharta-publish/action.yml b/.github/actions/universal-codecharta-publish/action.yml new file mode 100644 index 00000000..dc015802 --- /dev/null +++ b/.github/actions/universal-codecharta-publish/action.yml @@ -0,0 +1,93 @@ +name: CodeCharta publish +description: > + Publishes a CodeCharta snapshot (or removes a preview) into a data repo — + `futuredapp/codebase-architectures` by default. Clones the data repo, copies + the artifacts into `projects//...`, rebuilds the manifest, and + pushes back to the data repo's default branch. Handles concurrent commits + via retry-on-conflict. + +inputs: + mode: + required: true + description: > + One of: preview, history, bulk-history, delete-preview, set-latest. + `preview` and `delete-preview` accept a numeric `pr_number`. `history` + accepts `stem` + `date` + paths. `bulk-history` accepts `snapshots_dir`. + `set-latest` takes no extra inputs. + project_name: + required: true + description: > + Output folder name in the data repo (`projects//`). + data_repo: + required: false + default: futuredapp/codebase-architectures + description: > + `/` of the data repository that holds the published + snapshots and the manifest. + data_repo_branch: + required: false + default: main + description: > + Branch in the data repo to push to. + data_repo_token: + required: true + description: > + Token with write access to the data repo. Use a fine-grained PAT or an + app token scoped to `Contents: read+write`. + pr_number: + required: false + description: > + Pull-request number. Required for `preview` and `delete-preview`. + stem: + required: false + description: > + Snapshot stem, either `pr-` or `merge-`. Required for + `history`, `backfill-history`, `set-latest`. + date: + required: false + description: > + Snapshot date as `YYYY-MM-DD`. Required for `history`, `backfill-history`. + Optional for `set-latest` (will pick newest entry if omitted). + cc_path: + required: false + description: > + Absolute path to the local `.cc.json.gz` to publish. Required for + `preview`, `history`, `backfill-history`. + cg_path: + required: false + description: > + Absolute path to the local `.cg.json` to publish. Required for `preview` + and `history`. Optional for `backfill-history` (history snapshots from + `mode: history` of the snapshot action do not include `.cg.json`). + meta_path: + required: false + description: > + Absolute path to the local `.meta.json` sidecar (PR title/author/etc.). + Optional; carried through to the data repo when present. + snapshots_dir: + required: false + description: > + Absolute path to a directory containing historical snapshots named + `--(pr-|merge-).cc.json.gz` (with optional + `.meta.json` sidecars). Required for `bulk-history`. One clone, idempotent + copies for entries not yet present, one summary commit, one push. + +runs: + using: composite + steps: + - name: Publish to data repo + shell: bash + run: ${{ github.action_path }}/codecharta-publish.sh + env: + CODECHARTA_PUBLISH_MODE: ${{ inputs.mode }} + PROJECT_NAME: ${{ inputs.project_name }} + DATA_REPO: ${{ inputs.data_repo }} + DATA_REPO_BRANCH: ${{ inputs.data_repo_branch }} + CODEBASE_ARCHITECTURES_TOKEN: ${{ inputs.data_repo_token }} + PR_NUMBER: ${{ inputs.pr_number }} + STEM: ${{ inputs.stem }} + DATE: ${{ inputs.date }} + SOURCE_CC_PATH: ${{ inputs.cc_path }} + SOURCE_CG_PATH: ${{ inputs.cg_path }} + SOURCE_META_PATH: ${{ inputs.meta_path }} + SNAPSHOTS_DIR: ${{ inputs.snapshots_dir }} diff --git a/.github/actions/universal-codecharta-publish/codecharta-publish.sh b/.github/actions/universal-codecharta-publish/codecharta-publish.sh new file mode 100755 index 00000000..c192c812 --- /dev/null +++ b/.github/actions/universal-codecharta-publish/codecharta-publish.sh @@ -0,0 +1,236 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Publish CodeCharta snapshots to the data repo (default +# `futuredapp/codebase-architectures`). Invoked by the +# universal-codecharta-publish composite action. +# +# Inputs (env, set by action.yml from action inputs): +# CODECHARTA_PUBLISH_MODE One of: preview, history, bulk-history, +# delete-preview, set-latest. +# PROJECT_NAME Folder name under `projects/`. +# DATA_REPO `/`, e.g. `futuredapp/codebase-architectures`. +# DATA_REPO_BRANCH Branch to push to (default: main). +# CODEBASE_ARCHITECTURES_TOKEN +# Write-scoped token for the data repo. +# PR_NUMBER Pull-request number (preview/delete-preview). +# STEM `pr-` or `merge-` (history mode). +# DATE `YYYY-MM-DD` (history mode). +# SOURCE_CC_PATH Absolute local path to .cc.json.gz to publish. +# SOURCE_CG_PATH Absolute local path to .cg.json to publish. +# SOURCE_META_PATH Optional absolute local path to .meta.json. +# SNAPSHOTS_DIR Absolute path to dir of historical snapshots +# (bulk-history mode). + +require_env() { + local name="$1" + if [[ -z "${!name:-}" ]]; then + echo "missing required env: ${name}" >&2 + exit 64 + fi +} + +require_env CODECHARTA_PUBLISH_MODE +require_env PROJECT_NAME +require_env DATA_REPO +require_env CODEBASE_ARCHITECTURES_TOKEN + +DATA_REPO_BRANCH="${DATA_REPO_BRANCH:-main}" +MODE="${CODECHARTA_PUBLISH_MODE}" + +case "${MODE}" in + preview | delete-preview) + require_env PR_NUMBER + if [[ ! "${PR_NUMBER}" =~ ^[0-9]+$ ]]; then + echo "${MODE}: PR_NUMBER must be numeric (got '${PR_NUMBER}')" >&2 + exit 64 + fi + ;; + history) + require_env STEM + require_env DATE + if [[ ! "${STEM}" =~ ^(pr-[0-9]+|merge-[0-9a-f]{7,})$ ]]; then + echo "${MODE}: STEM must match 'pr-' or 'merge-' (got '${STEM}')" >&2 + exit 64 + fi + if [[ ! "${DATE}" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then + echo "${MODE}: DATE must be YYYY-MM-DD (got '${DATE}')" >&2 + exit 64 + fi + ;; + bulk-history) + require_env SNAPSHOTS_DIR + if [[ ! -d "${SNAPSHOTS_DIR}" ]]; then + echo "${MODE}: SNAPSHOTS_DIR must be a directory (got '${SNAPSHOTS_DIR}')" >&2 + exit 64 + fi + ;; + set-latest) + # No required args; picks newest history entry by lexicographic sort. + ;; + *) + echo "Unknown CODECHARTA_PUBLISH_MODE: '${MODE}'" >&2 + echo "Expected one of: preview, history, bulk-history, delete-preview, set-latest" >&2 + exit 64 + ;; +esac + +WORK_DIR="$(mktemp -d)" +trap 'rm -rf "${WORK_DIR}"' EXIT + +DATA_REPO_NAME="${DATA_REPO##*/}" +DATA_REPO_DIR="${WORK_DIR}/${DATA_REPO_NAME}" + +echo "Cloning ${DATA_REPO}…" +git clone --depth=1 --branch "${DATA_REPO_BRANCH}" \ + "https://x-access-token:${CODEBASE_ARCHITECTURES_TOKEN}@github.com/${DATA_REPO}.git" \ + "${DATA_REPO_DIR}" + +cd "${DATA_REPO_DIR}" +git config user.email "codecharta-bot@users.noreply.github.com" +git config user.name "codecharta-bot" +git remote set-url origin "https://x-access-token:${CODEBASE_ARCHITECTURES_TOKEN}@github.com/${DATA_REPO}.git" + +PROJECT_DIR="projects/${PROJECT_NAME}" +mkdir -p "${PROJECT_DIR}/previews" "${PROJECT_DIR}/history" + +apply_change() { + case "${MODE}" in + preview) + require_env SOURCE_CC_PATH + require_env SOURCE_CG_PATH + cp "${SOURCE_CC_PATH}" "${PROJECT_DIR}/previews/pr-${PR_NUMBER}.cc.json.gz" + cp "${SOURCE_CG_PATH}" "${PROJECT_DIR}/previews/pr-${PR_NUMBER}.cg.json" + ;; + history) + require_env SOURCE_CC_PATH + require_env SOURCE_CG_PATH + local basename="${DATE}-${STEM}" + cp "${SOURCE_CC_PATH}" "${PROJECT_DIR}/history/${basename}.cc.json.gz" + cp "${SOURCE_CG_PATH}" "${PROJECT_DIR}/history/${basename}.cg.json" + if [[ -n "${SOURCE_META_PATH:-}" && -f "${SOURCE_META_PATH}" ]]; then + cp "${SOURCE_META_PATH}" "${PROJECT_DIR}/history/${basename}.meta.json" + fi + cp "${PROJECT_DIR}/history/${basename}.cc.json.gz" "${PROJECT_DIR}/history/latest.cc.json.gz" + cp "${PROJECT_DIR}/history/${basename}.cg.json" "${PROJECT_DIR}/history/latest.cg.json" + if [[ -f "${PROJECT_DIR}/history/${basename}.meta.json" ]]; then + cp "${PROJECT_DIR}/history/${basename}.meta.json" "${PROJECT_DIR}/history/latest.meta.json" + fi + ;; + bulk-history) + # Iterate over --.cc.json.gz files. Idempotent + # per entry: skip files that already exist in the data repo. + shopt -s nullglob + local cc base rest entry_date entry_stem entry_cc entry_meta + local total=0 + local copied=0 + for cc in "${SNAPSHOTS_DIR}/${PROJECT_NAME}"-*.cc.json.gz; do + total=$(( total + 1 )) + base="$(basename "${cc}" .cc.json.gz)" + rest="${base#${PROJECT_NAME}-}" + if [[ ! "${rest}" =~ ^([0-9]{4}-[0-9]{2}-[0-9]{2})-((pr|merge)-[0-9a-f]+)$ ]]; then + echo "Skipping unrecognised filename: ${base}" >&2 + continue + fi + entry_date="${BASH_REMATCH[1]}" + entry_stem="${BASH_REMATCH[2]}" + local entry_basename="${entry_date}-${entry_stem}" + local entry_target_cc="${PROJECT_DIR}/history/${entry_basename}.cc.json.gz" + local entry_target_meta="${PROJECT_DIR}/history/${entry_basename}.meta.json" + entry_cc="${cc}" + entry_meta="${cc%.cc.json.gz}.meta.json" + + if [[ ! -f "${entry_target_cc}" ]]; then + cp "${entry_cc}" "${entry_target_cc}" + copied=$(( copied + 1 )) + fi + if [[ -f "${entry_meta}" && ! -f "${entry_target_meta}" ]]; then + cp "${entry_meta}" "${entry_target_meta}" + fi + done + echo "Bulk-history scanned ${total} entries; wrote ${copied} new history files." + ;; + delete-preview) + rm -f "${PROJECT_DIR}/previews/pr-${PR_NUMBER}.cc.json.gz" \ + "${PROJECT_DIR}/previews/pr-${PR_NUMBER}.cg.json" + ;; + set-latest) + local newest + newest=$(ls -1 "${PROJECT_DIR}/history/" 2>/dev/null \ + | grep -E '\.cc\.json\.gz$' \ + | grep -v '^latest' \ + | sort -r \ + | head -1 || true) + if [[ -z "${newest}" ]]; then + echo "No history entries found; nothing to set as latest." + return + fi + local base="${newest%.cc.json.gz}" + cp "${PROJECT_DIR}/history/${newest}" "${PROJECT_DIR}/history/latest.cc.json.gz" + if [[ -f "${PROJECT_DIR}/history/${base}.cg.json" ]]; then + cp "${PROJECT_DIR}/history/${base}.cg.json" "${PROJECT_DIR}/history/latest.cg.json" + fi + if [[ -f "${PROJECT_DIR}/history/${base}.meta.json" ]]; then + cp "${PROJECT_DIR}/history/${base}.meta.json" "${PROJECT_DIR}/history/latest.meta.json" + fi + ;; + esac +} + +apply_change + +if [[ -f scripts/build-manifest.mjs ]]; then + echo "Rebuilding manifest…" + node scripts/build-manifest.mjs +fi + +if git diff --quiet && git diff --staged --quiet; then + echo "No changes after publish; nothing to commit." + exit 0 +fi + +git add . + +case "${MODE}" in + preview) + COMMIT_MSG="Update preview for ${PROJECT_NAME} PR #${PR_NUMBER}" + ;; + history) + COMMIT_MSG="Add history snapshot ${PROJECT_NAME} ${DATE}-${STEM}" + ;; + bulk-history) + COMMIT_MSG="Backfill history snapshots for ${PROJECT_NAME}" + ;; + delete-preview) + COMMIT_MSG="Drop preview for ${PROJECT_NAME} PR #${PR_NUMBER}" + ;; + set-latest) + COMMIT_MSG="Refresh latest snapshot pointer for ${PROJECT_NAME}" + ;; +esac + +git commit -m "${COMMIT_MSG}" + +# Retry-on-conflict push: pull-rebase, regenerate manifest from filesystem +# state (deterministic), and try again. Up to 5 attempts with linear backoff. +attempt=1 +while (( attempt <= 5 )); do + if git push origin "${DATA_REPO_BRANCH}"; then + echo "Pushed on attempt ${attempt}." + exit 0 + fi + echo "Push rejected (attempt ${attempt}); rebasing and retrying…" + git pull --rebase origin "${DATA_REPO_BRANCH}" + if [[ -f scripts/build-manifest.mjs ]]; then + node scripts/build-manifest.mjs + if ! git diff --quiet manifest.json; then + git add manifest.json + git commit --amend --no-edit + fi + fi + sleep $(( attempt * 3 )) + (( attempt++ )) +done + +echo "Push failed after 5 attempts; giving up." >&2 +exit 1 diff --git a/.github/actions/universal-codecharta-snapshot/action.yml b/.github/actions/universal-codecharta-snapshot/action.yml new file mode 100644 index 00000000..6ab4760f --- /dev/null +++ b/.github/actions/universal-codecharta-snapshot/action.yml @@ -0,0 +1,104 @@ +name: CodeCharta snapshot +description: > + Runs the CodeCharta + DependaCharta analysis pipeline against the checked-out + repository. Produces a `${project_name}.cc.json.gz` map (and `.cg.json` graph + in snapshot mode). Supports a single snapshot of HEAD or N historical + snapshots over recent merge commits. + +inputs: + mode: + required: true + description: > + `snapshot` to analyze HEAD (produces map + graph); `history` to walk N + recent first-parent merges and produce one map per merge into + `build/codecharta/pull-requests/` (no `.cg.json` per snapshot). + project_name: + required: true + description: > + Used as the output file prefix. Conventionally equal to the consumer + repo's name; appears as the folder name in the data repo's + `projects//` tree. + stack: + required: false + default: multi + description: > + Stack profile that determines the default ccsh file-extension filter and + tokei language filter. One of: swift, kotlin, kmp, ft-fullstack, multi. + `multi` omits both filters and lets the analyzers consider every + recognized file. + file_extensions: + required: false + description: > + Optional override for the `-fe=` flag passed to ccsh unifiedparser. + Comma-separated list of file extensions, e.g. `kt,swift`. When unset, + defaults derived from `stack` apply. + tokei_types: + required: false + description: > + Optional override for the `--type` flag passed to tokei. Comma-separated + list of tokei language names, e.g. `Kotlin,Swift`. When unset, defaults + derived from `stack` apply. + extra_excludes: + required: false + description: > + Comma-separated regex patterns to exclude in addition to the analyzer + defaults. Applied to ccsh `-e=` and tokei `--exclude` uniformly. + history_limit: + required: false + default: '20' + description: > + Maximum number of first-parent merge commits to walk in `history` mode. + Ignored in `snapshot` mode. + dependacharta_image: + required: false + default: ghcr.io/futuredapp/dependacharta-swift:fork-v0.2.1 + description: > + Fully-qualified DependaCharta image reference. The Futured fork covers + all upstream languages plus Swift; pin via this input if you need a + specific tag. + analysis_image: + required: false + default: codecharta/codecharta-analysis:latest + description: > + Fully-qualified ccsh / tokei analysis image. Future versions of this + action will pin to a dated tag. + +outputs: + cc_path: + description: > + Path to the `${project_name}.cc.json.gz` map produced in `snapshot` mode. + Empty in `history` mode. + value: ${{ steps.run.outputs.cc_path }} + cg_path: + description: > + Path to the `${project_name}.cg.json` DependaCharta graph produced in + `snapshot` mode. Empty in `history` mode. + value: ${{ steps.run.outputs.cg_path }} + snapshots_dir: + description: > + Path to the directory containing the historical `.cc.json.gz` files + produced in `history` mode. Empty in `snapshot` mode. + value: ${{ steps.run.outputs.snapshots_dir }} + +runs: + using: composite + steps: + - name: Pull DependaCharta image + shell: bash + run: docker pull "${{ inputs.dependacharta_image }}" + + - name: Run codecharta analysis + id: run + shell: bash + run: ${{ github.action_path }}/codecharta.sh ${{ inputs.mode }} + env: + CODECHARTA_PROJECT_NAME: ${{ inputs.project_name }} + CODECHARTA_STACK: ${{ inputs.stack }} + CODECHARTA_FILE_EXTENSIONS: ${{ inputs.file_extensions }} + CODECHARTA_TOKEI_TYPES: ${{ inputs.tokei_types }} + CODECHARTA_EXTRA_EXCLUDES: ${{ inputs.extra_excludes }} + CODECHARTA_HISTORY_PULL_REQUEST_LIMIT: ${{ inputs.history_limit }} + CODECHARTA_ANALYSIS_IMAGE: ${{ inputs.analysis_image }} + DEPENDACHARTA_IMAGE: ${{ inputs.dependacharta_image }} + DEPENDACHARTA_RUNNER: docker + REPO_ROOT: ${{ github.workspace }} diff --git a/.github/actions/universal-codecharta-snapshot/codecharta.sh b/.github/actions/universal-codecharta-snapshot/codecharta.sh new file mode 100755 index 00000000..b335dd8d --- /dev/null +++ b/.github/actions/universal-codecharta-snapshot/codecharta.sh @@ -0,0 +1,455 @@ +#!/usr/bin/env bash +set -euo pipefail + +# CodeCharta + DependaCharta analysis pipeline. +# +# Invoked by the universal-codecharta-snapshot composite action. Reads +# configuration from environment variables (set by action.yml from action +# inputs). Operates in two modes: +# +# snapshot — analyze HEAD; produces `${OUT_DIR}/${PROJECT_NAME}.cc.json.gz` +# plus `${OUT_DIR}/${PROJECT_NAME}.cg.json`. +# history — walk N recent first-parent merge commits; produces one +# `${PROJECT_NAME}--.cc.json.gz` (plus optional +# `.meta.json` sidecar) per merge into +# `${OUT_DIR}/pull-requests/`. +# +# All paths emitted to $GITHUB_OUTPUT use absolute paths so consumers don't +# have to know REPO_ROOT. + +REPO_ROOT="${REPO_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)}" +PROJECT_NAME="${CODECHARTA_PROJECT_NAME:?CODECHARTA_PROJECT_NAME is required}" +OUT_DIR="${CODECHARTA_OUT_DIR:-build/codecharta}" +STACK="${CODECHARTA_STACK:-multi}" +ANALYSIS_IMAGE="${CODECHARTA_ANALYSIS_IMAGE:-codecharta/codecharta-analysis:latest}" +DEPENDACHARTA_IMAGE="${DEPENDACHARTA_IMAGE:-ghcr.io/futuredapp/dependacharta-swift:fork-v0.2.1}" +DEPENDACHARTA_RUNNER="${DEPENDACHARTA_RUNNER:-docker}" +PLATFORM="${CODECHARTA_PLATFORM-linux/amd64}" +HISTORY_LIMIT="${CODECHARTA_HISTORY_PULL_REQUEST_LIMIT:-}" + +DOCKER_PLATFORM_ARGS=() +if [[ -n "${PLATFORM}" ]]; then + DOCKER_PLATFORM_ARGS=(--platform "${PLATFORM}") +fi + +# ── Stack profile resolution ───────────────────────────────────────────────── +# Stack maps to a default file-extension list (for ccsh `-fe=`) and a default +# tokei type list (for `--type`). Explicit overrides via CODECHARTA_FILE_ +# EXTENSIONS / CODECHARTA_TOKEI_TYPES always win. + +case "${STACK}" in + swift) + STACK_FE="swift" + STACK_TOKEI="Swift,Objective-C" + ;; + kotlin) + STACK_FE="kt" + STACK_TOKEI="Kotlin,Java" + ;; + kmp) + STACK_FE="kt,swift" + STACK_TOKEI="Kotlin,Swift,Java,Objective-C" + ;; + ft-fullstack) + STACK_FE="ts,tsx,js,jsx,mjs,cjs,vue" + STACK_TOKEI="TypeScript,JavaScript,Vue" + ;; + multi) + STACK_FE="" + STACK_TOKEI="" + ;; + *) + echo "Unknown CODECHARTA_STACK: '${STACK}'. Expected one of: swift, kotlin, kmp, ft-fullstack, multi." >&2 + exit 64 + ;; +esac + +FILE_EXTENSIONS="${CODECHARTA_FILE_EXTENSIONS:-${STACK_FE}}" +TOKEI_TYPES="${CODECHARTA_TOKEI_TYPES:-${STACK_TOKEI}}" +EXTRA_EXCLUDES="${CODECHARTA_EXTRA_EXCLUDES:-}" + +# ── ccsh helper invocations ────────────────────────────────────────────────── + +# Build the `-fe=...` argument when FILE_EXTENSIONS is non-empty. Emit nothing +# when empty (multi mode) so ccsh uses its built-in default. +ccsh_fe_arg() { + if [[ -n "${FILE_EXTENSIONS}" ]]; then + printf -- "-fe=%s" "${FILE_EXTENSIONS}" + fi +} + +# Build the `-e=...` argument list from EXTRA_EXCLUDES (comma-separated regex +# patterns). Emit nothing when empty. +ccsh_extra_excludes_args() { + if [[ -z "${EXTRA_EXCLUDES}" ]]; then + return + fi + local IFS=',' + for pattern in ${EXTRA_EXCLUDES}; do + printf -- "-e=%s\n" "${pattern}" + done +} + +# Build the `--type ...` argument when TOKEI_TYPES is non-empty. +tokei_type_args() { + if [[ -n "${TOKEI_TYPES}" ]]; then + printf -- "--type\n%s\n" "${TOKEI_TYPES}" + fi +} + +# Build the `--exclude pattern` argument list from EXTRA_EXCLUDES. Tokei uses +# globs/paths, not regex; we pass patterns verbatim and let tokei interpret. +tokei_extra_excludes_args() { + if [[ -z "${EXTRA_EXCLUDES}" ]]; then + return + fi + local IFS=',' + for pattern in ${EXTRA_EXCLUDES}; do + printf -- "--exclude\n%s\n" "${pattern}" + done +} + +# ── Dependency metric descriptors ──────────────────────────────────────────── + +add_dependency_metric_descriptions() { + local map_path="$1" + + if ! command -v node >/dev/null 2>&1; then + echo "node is required to add dependency metric descriptions to ${map_path}" >&2 + exit 69 + fi + + node - "${map_path}" <<'NODE' +const fs = require("fs"); +const zlib = require("zlib"); + +const mapPath = process.argv[2]; +const isGzip = mapPath.endsWith(".gz"); +const raw = fs.readFileSync(mapPath); +const json = JSON.parse(isGzip ? zlib.gunzipSync(raw) : raw); +json.data.attributeDescriptors ??= {}; +json.data.attributeDescriptors.dependencies = { + ...json.data.attributeDescriptors.dependencies, + title: "Dependencies", + description: "Number of incoming and outgoing dependency edges aggregated from the DependaCharta graph.", + hintLowValue: "Fewer dependency edges", + hintHighValue: "More dependency edges", + direction: -1, + analyzers: ["DependaCharta", "CodeCharta EdgeFilter"], +}; + +const output = JSON.stringify(json); +fs.writeFileSync(mapPath, isGzip ? zlib.gzipSync(output) : output); +NODE + + echo "Added dependency metric descriptions to ${map_path}" +} + +# ── DependaCharta runner ───────────────────────────────────────────────────── + +run_dependacharta() { + local absolute_out_dir="$1" + + if [[ "${DEPENDACHARTA_RUNNER}" != "docker" ]]; then + echo "DEPENDACHARTA_RUNNER must be 'docker' inside the action (got '${DEPENDACHARTA_RUNNER}')" >&2 + exit 64 + fi + + mkdir -p "${absolute_out_dir}" + + docker run --rm \ + -v "${REPO_ROOT}:/source:ro" \ + -v "${absolute_out_dir}:/output" \ + "${DEPENDACHARTA_IMAGE}" \ + -d /source \ + -o /output \ + -f "${PROJECT_NAME}" \ + -c \ + --logLevel info +} + +# ── Pull-request metadata sidecars ─────────────────────────────────────────── + +pull_request_metadata() { + local snapshot_dir="$1" + + if ! command -v gh >/dev/null 2>&1; then + echo "gh CLI not found on PATH; skipping pull-request metadata generation." + return 0 + fi + + local repo_slug + if [[ -n "${GITHUB_REPOSITORY:-}" ]]; then + repo_slug="${GITHUB_REPOSITORY}" + elif ! repo_slug=$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null); then + echo "Could not determine GitHub repo slug; skipping pull-request metadata." + return 0 + fi + + shopt -s nullglob + local cc + for cc in "${snapshot_dir}/${PROJECT_NAME}"-*-pr-*.cc.json.gz; do + local base="${cc%.cc.json.gz}" + local meta="${base}.meta.json" + + if [[ -f "${meta}" ]]; then + continue + fi + + local pr_number + if [[ "${base}" =~ -pr-([0-9]+)$ ]]; then + pr_number="${BASH_REMATCH[1]}" + else + continue + fi + + if gh pr view "${pr_number}" --repo "${repo_slug}" \ + --json title,author,mergedAt,url > "${meta}" 2>/dev/null; then + echo "Wrote ${meta}" + else + echo "Could not fetch metadata for PR #${pr_number}; skipping." + rm -f "${meta}" + fi + done +} + +# ── Snapshot pipeline (single snapshot at HEAD) ────────────────────────────── + +snapshot() { + local absolute_out_dir="${REPO_ROOT}/${OUT_DIR}" + local final_map_path="${absolute_out_dir}/${PROJECT_NAME}.cc.json.gz" + local graph_path="${absolute_out_dir}/${PROJECT_NAME}.cg.json" + + mkdir -p "${absolute_out_dir}/tmp" + + run_dependacharta "${absolute_out_dir}" + + # Run ccsh (unifiedparser, gitlogparser, tokei, merge) in a single Docker + # invocation so intermediate files share an in-container working dir. + docker run --rm \ + "${DOCKER_PLATFORM_ARGS[@]}" \ + --user "$(id -u):$(id -g)" \ + -e HOME="/tmp/codecharta-home" \ + -e CC_PROJECT_NAME="${PROJECT_NAME}" \ + -e CC_OUT_DIR="${OUT_DIR}" \ + -e CC_FILE_EXTENSIONS="${FILE_EXTENSIONS}" \ + -e CC_TOKEI_TYPES="${TOKEI_TYPES}" \ + -e CC_EXTRA_EXCLUDES="${EXTRA_EXCLUDES}" \ + -v "${REPO_ROOT}:/mnt/src" \ + -w /mnt/src \ + "${ANALYSIS_IMAGE}" \ + bash -lc ' + set -euo pipefail + + mkdir -p "${HOME}" "${CC_OUT_DIR}/tmp" + git config --global --add safe.directory /mnt/src + + source_map="${CC_OUT_DIR}/tmp/${CC_PROJECT_NAME}-source" + git_map="${CC_OUT_DIR}/tmp/${CC_PROJECT_NAME}-git" + tokei_json="${CC_OUT_DIR}/tmp/${CC_PROJECT_NAME}-tokei.json" + tokei_map="${CC_OUT_DIR}/tmp/${CC_PROJECT_NAME}-tokei" + graph="${CC_OUT_DIR}/${CC_PROJECT_NAME}.cg.json" + dependency_edges="${CC_OUT_DIR}/${CC_PROJECT_NAME}-dependencies.cc.json.gz" + dependency_metrics="${CC_OUT_DIR}/${CC_PROJECT_NAME}-dependency-metrics.cc.json" + final_map="${CC_OUT_DIR}/${CC_PROJECT_NAME}" + + # Build ccsh flag arrays from env. Empty values produce zero array + # elements, leaving ccsh to use its built-in defaults. + fe_args=() + if [[ -n "${CC_FILE_EXTENSIONS}" ]]; then + fe_args+=("-fe=${CC_FILE_EXTENSIONS}") + fi + exclude_args=() + if [[ -n "${CC_EXTRA_EXCLUDES}" ]]; then + IFS="," read -ra patterns <<< "${CC_EXTRA_EXCLUDES}" + for p in "${patterns[@]}"; do + exclude_args+=("-e=${p}") + done + fi + tokei_type_args=() + if [[ -n "${CC_TOKEI_TYPES}" ]]; then + tokei_type_args+=(--type "${CC_TOKEI_TYPES}") + fi + tokei_exclude_args=() + if [[ -n "${CC_EXTRA_EXCLUDES}" ]]; then + IFS="," read -ra patterns <<< "${CC_EXTRA_EXCLUDES}" + for p in "${patterns[@]}"; do + tokei_exclude_args+=(--exclude "${p}") + done + fi + + # Source structure (+ tokei LOC + git churn) merged into one map. + ccsh unifiedparser \ + "${fe_args[@]}" \ + "${exclude_args[@]}" \ + -o="${source_map}" \ + . + + tokei \ + --output json \ + "${tokei_type_args[@]}" \ + "${tokei_exclude_args[@]}" \ + . > "${tokei_json}" + + ccsh tokeiimporter "${tokei_json}" --root-name . -o "${tokei_map}" + + base_inputs=("${source_map}.cc.json.gz" "${tokei_map}.cc.json.gz") + if ccsh gitlogparser repo-scan --repo-path /mnt/src --silent -o "${git_map}"; then + base_inputs+=("${git_map}.cc.json.gz") + else + echo "CodeCharta git history parser failed; continuing without git metrics." + fi + + # DependaCharta graph → CodeCharta dependency metrics → merged map. + if [[ -f "${graph}" ]]; then + ccsh dependachartaimport "${graph}" -o "${dependency_edges}" + ccsh edgefilter "${dependency_edges}" -o "${dependency_metrics}" + base_inputs+=("${dependency_metrics}") + else + echo "DependaCharta graph missing at ${graph}; continuing without dependency metrics." + fi + + ccsh merge -o="${final_map}" "${base_inputs[@]}" + ccsh check "${final_map}.cc.json.gz" + echo "Created ${final_map}.cc.json.gz" + ' + + if [[ -f "${final_map_path}" ]]; then + add_dependency_metric_descriptions "${final_map_path}" || true + fi + + # Expose outputs to the composite action. + if [[ -n "${GITHUB_OUTPUT:-}" ]]; then + echo "cc_path=${final_map_path}" >> "${GITHUB_OUTPUT}" + if [[ -f "${graph_path}" ]]; then + echo "cg_path=${graph_path}" >> "${GITHUB_OUTPUT}" + else + echo "cg_path=" >> "${GITHUB_OUTPUT}" + fi + echo "snapshots_dir=" >> "${GITHUB_OUTPUT}" + fi +} + +# ── History pipeline (N snapshots over recent first-parent merges) ─────────── + +history() { + local limit="${HISTORY_LIMIT}" + local absolute_out_dir="${REPO_ROOT}/${OUT_DIR}/pull-requests" + + if [[ -n "${limit}" ]] && (! [[ "${limit}" =~ ^[0-9]+$ ]] || (( limit < 1 ))); then + echo "history: limit must be empty or a positive integer (got '${limit}')" >&2 + exit 64 + fi + + mkdir -p "${absolute_out_dir}" + + docker run --rm \ + "${DOCKER_PLATFORM_ARGS[@]}" \ + --user "$(id -u):$(id -g)" \ + -e HOME="/tmp/codecharta-home" \ + -e CC_PROJECT_NAME="${PROJECT_NAME}" \ + -e CC_OUT_DIR="${OUT_DIR}/pull-requests" \ + -e CC_FILE_EXTENSIONS="${FILE_EXTENSIONS}" \ + -e CC_EXTRA_EXCLUDES="${EXTRA_EXCLUDES}" \ + -e CC_PULL_REQUEST_LIMIT="${limit}" \ + -v "${REPO_ROOT}:/mnt/src" \ + -w /mnt/src \ + "${ANALYSIS_IMAGE}" \ + bash -lc ' + set -euo pipefail + + mkdir -p "${HOME}" "${CC_OUT_DIR}" + git config --global --add safe.directory /mnt/src + + fe_args=() + if [[ -n "${CC_FILE_EXTENSIONS}" ]]; then + fe_args+=("-fe=${CC_FILE_EXTENSIONS}") + fi + exclude_args=() + if [[ -n "${CC_EXTRA_EXCLUDES}" ]]; then + IFS="," read -ra patterns <<< "${CC_EXTRA_EXCLUDES}" + for p in "${patterns[@]}"; do + exclude_args+=("-e=${p}") + done + fi + + generated=0 + log_args=(--first-parent --merges "--format=%H|%cs|%s") + if [[ -n "${CC_PULL_REQUEST_LIMIT}" ]]; then + log_args=(-n "${CC_PULL_REQUEST_LIMIT}" "${log_args[@]}") + fi + + mapfile -t merge_commits < <(git log "${log_args[@]}" HEAD) + + if (( ${#merge_commits[@]} == 0 )); then + echo "No first-parent merge commits found." + exit 0 + fi + + for merge_commit in "${merge_commits[@]}"; do + IFS="|" read -r sha snapshot_date subject <<< "${merge_commit}" + short_sha="${sha:0:7}" + + if [[ "${subject}" =~ [Pp]ull[[:space:]]+[Rr]equest[[:space:]]+\#([0-9]+) ]]; then + pull_request_id="pr-${BASH_REMATCH[1]}" + else + pull_request_id="merge-${short_sha}" + fi + + output_path="${CC_OUT_DIR}/${CC_PROJECT_NAME}-${snapshot_date}-${pull_request_id}" + + if [[ -f "${output_path}.cc.json.gz" ]]; then + echo "[${snapshot_date} ${pull_request_id}] snapshot already exists; skipping." + continue + fi + + echo "[${snapshot_date} ${pull_request_id}] generating snapshot at ${short_sha}: ${subject}" + + ccsh unifiedparser \ + "${fe_args[@]}" \ + "${exclude_args[@]}" \ + --commit="${sha}" \ + -o="${output_path}" \ + . + + # When --commit is set, ccsh prefixes the output basename with + # the short SHA. Find that file and rename it back. + produced=$(ls -1 "${CC_OUT_DIR}"/*."${CC_PROJECT_NAME}-${snapshot_date}-${pull_request_id}.cc.json.gz" 2>/dev/null | head -1 || true) + if [[ -z "${produced}" ]]; then + echo "[${snapshot_date} ${pull_request_id}] parser did not produce expected output; skipping." + continue + fi + mv "${produced}" "${output_path}.cc.json.gz" + + ccsh check "${output_path}.cc.json.gz" >/dev/null + generated=$((generated + 1)) + done + + echo "" + echo "Generated ${generated} new pull-request snapshot(s) in ${CC_OUT_DIR}/" + ' + + pull_request_metadata "${absolute_out_dir}" + + # Expose outputs. + if [[ -n "${GITHUB_OUTPUT:-}" ]]; then + echo "cc_path=" >> "${GITHUB_OUTPUT}" + echo "cg_path=" >> "${GITHUB_OUTPUT}" + echo "snapshots_dir=${absolute_out_dir}" >> "${GITHUB_OUTPUT}" + fi +} + +# ── Entrypoint ─────────────────────────────────────────────────────────────── + +case "${1:-snapshot}" in + snapshot) + snapshot + ;; + history) + history + ;; + *) + echo "Usage: codecharta.sh {snapshot|history}" >&2 + exit 64 + ;; +esac diff --git a/.github/workflows/universal-codecharta.yml b/.github/workflows/universal-codecharta.yml new file mode 100644 index 00000000..24ee73ec --- /dev/null +++ b/.github/workflows/universal-codecharta.yml @@ -0,0 +1,338 @@ +name: CodeCharta + DependaCharta snapshot + +# Reusable workflow that runs the full CodeCharta + DependaCharta pipeline: +# - PR events: generate a preview snapshot, publish it under +# `projects//previews/`, post a PR comment with picker links. +# - PR closed: delete the preview entry. +# - Push to develop/main: generate a history snapshot, publish under +# `projects//history/` and refresh the `latest.*` pointers. +# - workflow_dispatch with backfill=true: walk N recent merges, publish each. + +on: + workflow_call: + inputs: + stack: + type: string + required: false + default: multi + description: > + Stack profile (swift | kotlin | kmp | ft-fullstack | multi). + Determines default ccsh `-fe=` and tokei `--type` filters. + project_name: + type: string + required: false + description: > + Override for the data-repo folder name. Defaults to the consumer's + repository name (`github.event.repository.name`). + file_extensions: + type: string + required: false + description: > + Optional override for ccsh `-fe=` (comma-separated extensions). + tokei_types: + type: string + required: false + description: > + Optional override for tokei `--type` (comma-separated language names). + extra_excludes: + type: string + required: false + description: > + Comma-separated patterns added to ccsh `-e=` and tokei `--exclude`. + backfill: + type: boolean + required: false + default: false + description: > + When true (workflow_dispatch only), runs the backfill job that walks + recent merges and publishes a history entry per PR. + pr_limit: + type: string + required: false + default: '20' + description: > + Max number of merges to walk in backfill mode. + dependacharta_image_tag: + type: string + required: false + default: fork-v0.2.1 + description: > + Tag for the Futured DependaCharta fork image. Pin per release; + floating tags are discouraged. + secrets: + CODEBASE_ARCHITECTURES_TOKEN: + required: true + description: > + Fine-grained PAT or GitHub App token with write access to the data + repo (`futuredapp/codebase-architectures`). + +# Per-event concurrency. PR pushes serialize per PR; push events serialize per +# ref; workflow_dispatch (typically backfill) serializes globally so the +# manifest rebuild loop converges. +concurrency: + group: codecharta-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: false + +env: + # Org-wide constants. Hardcoded by design; varying them would mean a + # cross-repo data migration, not a per-consumer setting. + DATA_REPO: futuredapp/codebase-architectures + PICKER_BASE_URL: https://codebase-arch.futured.app + DEPENDACHARTA_IMAGE_BASE: ghcr.io/futuredapp/dependacharta-swift + +jobs: + generate: + name: Generate snapshot + if: | + github.event_name == 'push' || + (github.event_name == 'pull_request' && github.event.action != 'closed') + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + packages: read + pull-requests: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # full history for git metrics + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Resolve project name + id: project + shell: bash + run: | + name="${{ inputs.project_name }}" + if [[ -z "${name}" ]]; then + name="${{ github.event.repository.name }}" + fi + echo "name=${name}" >> "$GITHUB_OUTPUT" + + - name: Run CodeCharta snapshot + id: snapshot + uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@ci/codecharta-actions + with: + mode: snapshot + project_name: ${{ steps.project.outputs.name }} + stack: ${{ inputs.stack }} + file_extensions: ${{ inputs.file_extensions }} + tokei_types: ${{ inputs.tokei_types }} + extra_excludes: ${{ inputs.extra_excludes }} + dependacharta_image: ${{ env.DEPENDACHARTA_IMAGE_BASE }}:${{ inputs.dependacharta_image_tag }} + + - name: Determine target identifiers + id: ids + shell: bash + run: | + set -euo pipefail + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + echo "kind=preview" >> "$GITHUB_OUTPUT" + echo "pr=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT" + else + # push to develop/main — parse merge commit subject for "#NNN". + subject=$(git log -1 --format=%s HEAD) + pr="" + stem="" + if [[ "${subject}" =~ \#([0-9]+) ]]; then + pr="${BASH_REMATCH[1]}" + stem="pr-${pr}" + else + short_sha=$(git rev-parse --short=7 HEAD) + stem="merge-${short_sha}" + fi + echo "kind=history" >> "$GITHUB_OUTPUT" + echo "stem=${stem}" >> "$GITHUB_OUTPUT" + echo "pr=${pr}" >> "$GITHUB_OUTPUT" + echo "date=$(git log -1 --format=%cs HEAD)" >> "$GITHUB_OUTPUT" + fi + + - name: Fetch PR metadata sidecar (history) + if: steps.ids.outputs.kind == 'history' && steps.ids.outputs.pr != '' + id: meta + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: bash + run: | + set -euo pipefail + meta_path="${RUNNER_TEMP}/${{ steps.project.outputs.name }}-pr-${{ steps.ids.outputs.pr }}.meta.json" + if gh pr view "${{ steps.ids.outputs.pr }}" \ + --json title,author,mergedAt,url > "${meta_path}" 2>/dev/null; then + echo "path=${meta_path}" >> "$GITHUB_OUTPUT" + echo "Wrote ${meta_path}" + else + echo "PR metadata fetch failed; continuing without sidecar." >&2 + rm -f "${meta_path}" + fi + + - name: Publish (preview) + if: steps.ids.outputs.kind == 'preview' + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + with: + mode: preview + project_name: ${{ steps.project.outputs.name }} + data_repo: ${{ env.DATA_REPO }} + data_repo_token: ${{ secrets.CODEBASE_ARCHITECTURES_TOKEN }} + pr_number: ${{ steps.ids.outputs.pr }} + cc_path: ${{ steps.snapshot.outputs.cc_path }} + cg_path: ${{ steps.snapshot.outputs.cg_path }} + + - name: Publish (history) + if: steps.ids.outputs.kind == 'history' + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + with: + mode: history + project_name: ${{ steps.project.outputs.name }} + data_repo: ${{ env.DATA_REPO }} + data_repo_token: ${{ secrets.CODEBASE_ARCHITECTURES_TOKEN }} + stem: ${{ steps.ids.outputs.stem }} + date: ${{ steps.ids.outputs.date }} + cc_path: ${{ steps.snapshot.outputs.cc_path }} + cg_path: ${{ steps.snapshot.outputs.cg_path }} + meta_path: ${{ steps.meta.outputs.path }} + + - name: Compose PR comment links + if: steps.ids.outputs.kind == 'preview' + id: links + shell: bash + run: | + set -euo pipefail + pr="${{ steps.ids.outputs.pr }}" + name="${{ steps.project.outputs.name }}" + base="${{ env.PICKER_BASE_URL }}" + ccPath="projects/${name}/previews/pr-${pr}.cc.json.gz" + cgPath="projects/${name}/previews/pr-${pr}.cg.json" + latestPath="projects/${name}/history/latest.cc.json.gz" + + ccUrl=$(jq -rn --arg p "${ccPath}" '@uri "/api/map?path=\($p)"') + cgUrl=$(jq -rn --arg p "${cgPath}" '@uri "/api/graph?path=\($p)"') + latestUrl=$(jq -rn --arg p "${latestPath}" '@uri "/api/map?path=\($p)"') + + ccStudio="${base}/studio/?file=$(jq -rn --arg u "${ccUrl}" '$u | @uri')" + cgVis="${base}/dependacharta/?graph=$(jq -rn --arg u "${cgUrl}" '$u | @uri')" + ccDelta="${base}/studio/?file=$(jq -rn --arg u "${ccUrl}" '$u | @uri')&file=$(jq -rn --arg u "${latestUrl}" '$u | @uri')" + + { + echo "ccStudio=${ccStudio}" + echo "cgVis=${cgVis}" + echo "ccDelta=${ccDelta}" + } >> "$GITHUB_OUTPUT" + + - name: Post or update PR comment + if: steps.ids.outputs.kind == 'preview' + uses: peter-evans/create-or-update-comment@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + issue-number: ${{ github.event.pull_request.number }} + body: | + + ### Codebase architecture snapshot + + - [Open this PR in CodeCharta](${{ steps.links.outputs.ccStudio }}) + - [Open this PR in DependaCharta](${{ steps.links.outputs.cgVis }}) + - [Compare PR vs latest develop (CodeCharta delta view)](${{ steps.links.outputs.ccDelta }}) + + Snapshot is regenerated on every push. Picker: ${{ env.PICKER_BASE_URL }}/ + edit-mode: replace + comment-author: 'github-actions[bot]' + body-includes: '' + + cleanup: + name: Cleanup preview + if: github.event_name == 'pull_request' && github.event.action == 'closed' + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + + steps: + - name: Resolve project name + id: project + shell: bash + run: | + name="${{ inputs.project_name }}" + if [[ -z "${name}" ]]; then + name="${{ github.event.repository.name }}" + fi + echo "name=${name}" >> "$GITHUB_OUTPUT" + + - name: Delete preview + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + with: + mode: delete-preview + project_name: ${{ steps.project.outputs.name }} + data_repo: ${{ env.DATA_REPO }} + data_repo_token: ${{ secrets.CODEBASE_ARCHITECTURES_TOKEN }} + pr_number: ${{ github.event.pull_request.number }} + + backfill: + name: Backfill history + if: github.event_name == 'workflow_dispatch' && inputs.backfill == true + runs-on: ubuntu-latest + timeout-minutes: 60 + permissions: + contents: read + packages: read + pull-requests: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Resolve project name + id: project + shell: bash + run: | + name="${{ inputs.project_name }}" + if [[ -z "${name}" ]]; then + name="${{ github.event.repository.name }}" + fi + echo "name=${name}" >> "$GITHUB_OUTPUT" + + - name: Generate historical snapshots + id: history + uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@ci/codecharta-actions + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + mode: history + project_name: ${{ steps.project.outputs.name }} + stack: ${{ inputs.stack }} + file_extensions: ${{ inputs.file_extensions }} + tokei_types: ${{ inputs.tokei_types }} + extra_excludes: ${{ inputs.extra_excludes }} + history_limit: ${{ inputs.pr_limit }} + dependacharta_image: ${{ env.DEPENDACHARTA_IMAGE_BASE }}:${{ inputs.dependacharta_image_tag }} + + - name: Publish backfill batch + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + with: + mode: bulk-history + project_name: ${{ steps.project.outputs.name }} + data_repo: ${{ env.DATA_REPO }} + data_repo_token: ${{ secrets.CODEBASE_ARCHITECTURES_TOKEN }} + snapshots_dir: ${{ steps.history.outputs.snapshots_dir }} + + - name: Refresh latest pointer + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + with: + mode: set-latest + project_name: ${{ steps.project.outputs.name }} + data_repo: ${{ env.DATA_REPO }} + data_repo_token: ${{ secrets.CODEBASE_ARCHITECTURES_TOKEN }} From 3b76f4e98ca34ed479f2a710e1c27decdb558a47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Potoc=CC=8Cek?= Date: Mon, 11 May 2026 13:55:03 +0200 Subject: [PATCH 02/22] Annotate workflow with walk-through comments and fix CI failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three changes bundled, all on the reusable workflow: 1. Heavy commenting: top-of-file walk-through of the four scenarios (PR preview, PR cleanup, develop history, manual backfill) and per- job / per-step "what happens here, why" comments throughout. 2. actionlint SC2129: collapse multi-line `>> $GITHUB_OUTPUT` redirects into single `{ ... } >> "$GITHUB_OUTPUT"` blocks in the "Determine target identifiers" step. 3. actionlint action-input fix: peter-evans/create-or-update-comment@v4 doesn't accept `comment-author` / `body-includes`. The previous step silently ignored them and stacked a new comment on every push. Split into find-comment + create-or-update-comment with `comment-id`, the canonical idiom — updates the existing bot comment instead of duplicating. 4. Flip internal @ci/codecharta-actions refs → @main so the bump-action-refs / validate-action-refs bats tests pass. Smoke testing is complete; the dev-branch refs are no longer needed and they break the project's bump/validate symmetry tests. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/universal-codecharta.yml | 270 +++++++++++++++++---- 1 file changed, 225 insertions(+), 45 deletions(-) diff --git a/.github/workflows/universal-codecharta.yml b/.github/workflows/universal-codecharta.yml index 24ee73ec..b1d30230 100644 --- a/.github/workflows/universal-codecharta.yml +++ b/.github/workflows/universal-codecharta.yml @@ -1,15 +1,48 @@ name: CodeCharta + DependaCharta snapshot -# Reusable workflow that runs the full CodeCharta + DependaCharta pipeline: -# - PR events: generate a preview snapshot, publish it under -# `projects//previews/`, post a PR comment with picker links. -# - PR closed: delete the preview entry. -# - Push to develop/main: generate a history snapshot, publish under -# `projects//history/` and refresh the `latest.*` pointers. -# - workflow_dispatch with backfill=true: walk N recent merges, publish each. +# ============================================================================ +# What this workflow does (high-level walk-through) +# ============================================================================ +# +# This is a *reusable workflow* — consumer repos invoke it from their own +# .github/workflows/codecharta.yml via `uses: futuredapp/.github/.github/workflows/universal-codecharta.yml@`. +# The consumer-side YAML only declares WHEN to run; this file owns the actual +# pipeline. +# +# The pipeline produces "snapshots" — single .cc.json.gz files containing the +# project's file tree, per-file complexity, LOC, churn, and dependency edges. +# Snapshots are stored in the data repo (futuredapp/codebase-architectures) and +# rendered by the picker UI at https://codebase-arch.futured.app/. +# +# Four scenarios trigger this workflow, each handled by a different job: +# +# 1. PR opened / pushed-to (targeting develop or main) +# → generate job, kind=preview +# → publishes to projects//previews/pr-.cc.json.gz +# → posts/updates a PR comment with picker links +# +# 2. PR closed (merged or abandoned) +# → cleanup job +# → deletes the preview entry from the data repo +# +# 3. Push to develop / main +# → generate job, kind=history +# → publishes to projects//history/-.cc.json.gz +# → refreshes latest.cc.json.gz pointer +# +# 4. Manual workflow_dispatch with backfill=true +# → backfill job +# → walks the last N merges, generates one snapshot per commit, +# publishes them in a single bulk commit, refreshes latest pointer +# +# The actual analysis (ccsh / tokei / gitlogparser / DependaCharta / merge) +# lives in the universal-codecharta-snapshot composite action. The data-repo +# orchestration (clone, copy, manifest rebuild, commit, push) lives in +# universal-codecharta-publish. This workflow is just the glue. on: workflow_call: + # ── Inputs the consumer can set ────────────────────────────────────────── inputs: stack: type: string @@ -17,88 +50,123 @@ on: default: multi description: > Stack profile (swift | kotlin | kmp | ft-fullstack | multi). - Determines default ccsh `-fe=` and tokei `--type` filters. + Resolves to default ccsh `-fe=` (file extensions) and tokei `--type` + (language filter) inside the snapshot action. Pick the named profile + that matches your repo; `multi` omits filters entirely (analyzes + everything the tools recognize). project_name: type: string required: false description: > Override for the data-repo folder name. Defaults to the consumer's - repository name (`github.event.repository.name`). + repository name (`github.event.repository.name`) — e.g. a workflow + running in `smsticket-ios` publishes to `projects/smsticket-ios/`. file_extensions: type: string required: false description: > - Optional override for ccsh `-fe=` (comma-separated extensions). + Optional override for ccsh `-fe=` (comma-separated extensions like + `kt,swift`). When set, supersedes the stack profile's default. tokei_types: type: string required: false description: > - Optional override for tokei `--type` (comma-separated language names). + Optional override for tokei `--type` (comma-separated language names + like `Kotlin,Swift`). When set, supersedes the stack profile's default. extra_excludes: type: string required: false description: > - Comma-separated patterns added to ccsh `-e=` and tokei `--exclude`. + Comma-separated patterns appended to ccsh `-e=` and tokei `--exclude`. + Use for repo-specific noise (vendored dirs, generated folders, etc.). backfill: type: boolean required: false default: false description: > - When true (workflow_dispatch only), runs the backfill job that walks - recent merges and publishes a history entry per PR. + When true (workflow_dispatch only), routes execution to the backfill + job instead of the generate/cleanup pair. pr_limit: type: string required: false default: '20' description: > - Max number of merges to walk in backfill mode. + Max number of first-parent merges to walk in backfill mode. Larger + values produce more historical snapshots at proportionally higher + cost. The 60-min job timeout caps this in practice around ~100. dependacharta_image_tag: type: string required: false default: fork-v0.2.1 description: > - Tag for the Futured DependaCharta fork image. Pin per release; - floating tags are discouraged. + Tag on the Futured DependaCharta fork image. Pin per release of this + workflow; floating tags (e.g. `latest`) break reproducibility. + + # ── Secrets the consumer must pass through ─────────────────────────────── secrets: CODEBASE_ARCHITECTURES_TOKEN: required: true description: > - Fine-grained PAT or GitHub App token with write access to the data - repo (`futuredapp/codebase-architectures`). + Fine-grained PAT or GitHub App token with `Contents: read+write` on + futuredapp/codebase-architectures. The publish jobs use it to clone + the data repo and push commits. -# Per-event concurrency. PR pushes serialize per PR; push events serialize per -# ref; workflow_dispatch (typically backfill) serializes globally so the -# manifest rebuild loop converges. +# ── Concurrency ────────────────────────────────────────────────────────────── +# Groups runs by (event_name, PR_number_or_ref): +# - Two pushes to the same PR serialize behind each other (so the second +# push doesn't overwrite the first's in-flight preview). +# - Two pushes to develop serialize too (the manifest rebuild loop needs to +# converge — concurrent pushes would race on it). +# - cancel-in-progress=false because the snapshot is the artifact; aborting +# mid-run leaves the data repo half-updated. concurrency: group: codecharta-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: false +# ── Workflow-level env (org-wide constants) ────────────────────────────────── +# These never vary per consumer. Hardcoded here rather than as inputs because +# changing one of these would mean a coordinated migration across every +# consumer repo, not a per-repo preference. env: - # Org-wide constants. Hardcoded by design; varying them would mean a - # cross-repo data migration, not a per-consumer setting. DATA_REPO: futuredapp/codebase-architectures PICKER_BASE_URL: https://codebase-arch.futured.app DEPENDACHARTA_IMAGE_BASE: ghcr.io/futuredapp/dependacharta-swift jobs: + # ========================================================================== + # JOB 1: generate + # Runs for: PR open/sync/reopen AND push to develop/main. + # Produces ONE snapshot, publishes it as either a preview (PR) or a history + # entry (push). For PRs, also posts a comment with picker links. + # ========================================================================== generate: name: Generate snapshot + # Fires on push (any branch the consumer's `on:` allows) OR on PR events + # that aren't `closed` (open / synchronize / reopen). Closed PRs go to the + # cleanup job below. if: | github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') runs-on: ubuntu-latest timeout-minutes: 15 permissions: - contents: read - packages: read - pull-requests: write + contents: read # checkout the consumer repo + packages: read # pull the DependaCharta image from GHCR + pull-requests: write # post/update the picker-links comment steps: + # ── Step 1: clone the consumer repo at the triggering ref ───────────── + # fetch-depth: 0 pulls the full git history, which gitlogparser needs to + # compute per-file churn (commits / authors over time). - name: Checkout uses: actions/checkout@v4 with: - fetch-depth: 0 # full history for git metrics + fetch-depth: 0 + # ── Step 2: authenticate Docker against GHCR ────────────────────────── + # The DependaCharta fork image (ghcr.io/futuredapp/dependacharta-swift) + # is private-by-default. GITHUB_TOKEN inherits read access for packages + # the calling repo can see. - name: Login to GHCR uses: docker/login-action@v3 with: @@ -106,6 +174,9 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + # ── Step 3: figure out the data-repo folder name ────────────────────── + # `projects//` is where snapshots land in the data repo. + # Defaults to the consumer's repo name; consumer can override via input. - name: Resolve project name id: project shell: bash @@ -116,9 +187,15 @@ jobs: fi echo "name=${name}" >> "$GITHUB_OUTPUT" + # ── Step 4: run the analysis pipeline ───────────────────────────────── + # The composite action wraps the full ccsh + tokei + gitlogparser + + # DependaCharta + merge flow in one step. Outputs: + # - cc_path: absolute path to ${project}.cc.json.gz (the map) + # - cg_path: absolute path to ${project}.cg.json (the graph) + # These outputs are consumed by the publish steps below. - name: Run CodeCharta snapshot id: snapshot - uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@main with: mode: snapshot project_name: ${{ steps.project.outputs.name }} @@ -128,14 +205,25 @@ jobs: extra_excludes: ${{ inputs.extra_excludes }} dependacharta_image: ${{ env.DEPENDACHARTA_IMAGE_BASE }}:${{ inputs.dependacharta_image_tag }} + # ── Step 5: decide whether this is a preview or a history snapshot ──── + # Branches the rest of the pipeline: + # - kind=preview → pr=, publish to previews/pr-.* + # - kind=history → stem=pr- or merge-, date=, + # publish to history/-.* + # + # For history runs, we parse "Merge pull request #NNN" from the commit + # subject. Rebase-merged commits (no #NNN in subject) fall back to a + # short-sha stem. - name: Determine target identifiers id: ids shell: bash run: | set -euo pipefail if [[ "${{ github.event_name }}" == "pull_request" ]]; then - echo "kind=preview" >> "$GITHUB_OUTPUT" - echo "pr=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT" + { + echo "kind=preview" + echo "pr=${{ github.event.pull_request.number }}" + } >> "$GITHUB_OUTPUT" else # push to develop/main — parse merge commit subject for "#NNN". subject=$(git log -1 --format=%s HEAD) @@ -148,12 +236,19 @@ jobs: short_sha=$(git rev-parse --short=7 HEAD) stem="merge-${short_sha}" fi - echo "kind=history" >> "$GITHUB_OUTPUT" - echo "stem=${stem}" >> "$GITHUB_OUTPUT" - echo "pr=${pr}" >> "$GITHUB_OUTPUT" - echo "date=$(git log -1 --format=%cs HEAD)" >> "$GITHUB_OUTPUT" + { + echo "kind=history" + echo "stem=${stem}" + echo "pr=${pr}" + echo "date=$(git log -1 --format=%cs HEAD)" + } >> "$GITHUB_OUTPUT" fi + # ── Step 6: fetch PR metadata (history runs only) ───────────────────── + # Writes a sidecar JSON ({title, author, mergedAt, url}) next to the + # snapshot. The picker UI reads this to display PR titles in the + # timeline. Skipped for previews (the PR is the live thing being viewed) + # and for rebase-merge commits with no PR number. - name: Fetch PR metadata sidecar (history) if: steps.ids.outputs.kind == 'history' && steps.ids.outputs.pr != '' id: meta @@ -172,9 +267,14 @@ jobs: rm -f "${meta_path}" fi + # ── Step 7a: publish a preview entry (PR runs only) ─────────────────── + # The publish composite clones the data repo, copies the snapshot into + # projects//previews/pr-.{cc.json.gz,cg.json}, rebuilds + # manifest.json, and pushes one commit. Retry-on-conflict handles + # concurrent writers. - name: Publish (preview) if: steps.ids.outputs.kind == 'preview' - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main with: mode: preview project_name: ${{ steps.project.outputs.name }} @@ -184,9 +284,13 @@ jobs: cc_path: ${{ steps.snapshot.outputs.cc_path }} cg_path: ${{ steps.snapshot.outputs.cg_path }} + # ── Step 7b: publish a history entry (develop/main pushes only) ─────── + # Same as preview but writes to projects//history/-.* + # and ALSO refreshes the latest.{cc.json.gz,cg.json,meta.json} pointers + # so the picker can offer a delta view against current develop. - name: Publish (history) if: steps.ids.outputs.kind == 'history' - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main with: mode: history project_name: ${{ steps.project.outputs.name }} @@ -198,6 +302,13 @@ jobs: cg_path: ${{ steps.snapshot.outputs.cg_path }} meta_path: ${{ steps.meta.outputs.path }} + # ── Step 8: build picker URLs for the PR comment (PR runs only) ─────── + # Three URL-encoded links: + # 1. CodeCharta Studio with the PR's map loaded + # 2. DependaCharta visualizer with the PR's graph loaded + # 3. Studio delta view comparing the PR against latest develop + # The picker proxy at PICKER_BASE_URL serves the data repo's files via + # /api/map and /api/graph endpoints. - name: Compose PR comment links if: steps.ids.outputs.kind == 'preview' id: links @@ -225,12 +336,30 @@ jobs: echo "ccDelta=${ccDelta}" } >> "$GITHUB_OUTPUT" + # ── Step 9: post or update the picker-links comment ─────────────────── + # The comment carries an HTML marker () so the + # idempotent find-then-update pattern can locate the existing comment + # on re-runs and replace its body rather than stacking duplicates. + # (The find-comment step below replaces the old single-step + # peter-evans/create-or-update-comment usage which silently ignored + # comment-author/body-includes and created a fresh comment every push.) + - name: Find existing picker-links comment + if: steps.ids.outputs.kind == 'preview' + id: find-comment + uses: peter-evans/find-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: '' + - name: Post or update PR comment if: steps.ids.outputs.kind == 'preview' uses: peter-evans/create-or-update-comment@v4 with: token: ${{ secrets.GITHUB_TOKEN }} + comment-id: ${{ steps.find-comment.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} + edit-mode: replace body: | ### Codebase architecture snapshot @@ -240,10 +369,14 @@ jobs: - [Compare PR vs latest develop (CodeCharta delta view)](${{ steps.links.outputs.ccDelta }}) Snapshot is regenerated on every push. Picker: ${{ env.PICKER_BASE_URL }}/ - edit-mode: replace - comment-author: 'github-actions[bot]' - body-includes: '' + # ========================================================================== + # JOB 2: cleanup + # Runs for: PR closed (merged or abandoned). + # No analysis runs here — we already published the preview on the prior PR + # event. This job's only job is to delete the now-stale preview entry from + # the data repo so it doesn't accumulate dead snapshots. + # ========================================================================== cleanup: name: Cleanup preview if: github.event_name == 'pull_request' && github.event.action == 'closed' @@ -263,8 +396,11 @@ jobs: fi echo "name=${name}" >> "$GITHUB_OUTPUT" + # Removes projects//previews/pr-.{cc.json.gz,cg.json} from the + # data repo, rebuilds manifest, commits ("Drop preview for PR + # #"), pushes. Idempotent — no-op if the preview was never created. - name: Delete preview - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main with: mode: delete-preview project_name: ${{ steps.project.outputs.name }} @@ -272,6 +408,17 @@ jobs: data_repo_token: ${{ secrets.CODEBASE_ARCHITECTURES_TOKEN }} pr_number: ${{ github.event.pull_request.number }} + # ========================================================================== + # JOB 3: backfill + # Runs for: manual workflow_dispatch with backfill=true. + # Walks the last N first-parent merge commits on the current branch and + # produces one historical snapshot per commit. Publishes them in a single + # bulk commit (vs N individual commits — much faster), then refreshes the + # latest.* pointers. + # + # Useful for seeding the timeline when first adopting this workflow on an + # existing repo with many past merges. + # ========================================================================== backfill: name: Backfill history if: github.event_name == 'workflow_dispatch' && inputs.backfill == true @@ -280,7 +427,7 @@ jobs: permissions: contents: read packages: read - pull-requests: read + pull-requests: read # gh pr view (for meta sidecars per snapshot) steps: - name: Checkout @@ -305,9 +452,24 @@ jobs: fi echo "name=${name}" >> "$GITHUB_OUTPUT" + # ── Step: generate N historical snapshots in one Docker invocation ──── + # The snapshot action's `history` mode walks `git log --first-parent + # --merges -n N`, and for each merge: + # 1. Computes stem (pr-NNN from "Merge pull request #NNN", or + # merge-) and the commit date. + # 2. Runs `ccsh unifiedparser --commit=` to analyze the repo at + # that historical commit (without checking it out). + # 3. Writes --.cc.json.gz to a shared output dir. + # 4. For pr-NNN stems, fetches PR metadata via gh and writes the + # .meta.json sidecar. + # + # NOTE: history mode does NOT run DependaCharta per snapshot (too + # expensive — JVM analysis per commit). Historical entries have no + # .cg.json files; only the latest live entry has one. This is a + # deliberate trade-off. - name: Generate historical snapshots id: history - uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@main env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -320,8 +482,20 @@ jobs: history_limit: ${{ inputs.pr_limit }} dependacharta_image: ${{ env.DEPENDACHARTA_IMAGE_BASE }}:${{ inputs.dependacharta_image_tag }} + # ── Step: bulk-publish the N generated snapshots in one commit ──────── + # `bulk-history` mode does: + # 1. ONE clone of the data repo. + # 2. For each --.cc.json.gz in snapshots_dir: + # - Skip if target already exists (idempotent re-runs). + # - Copy into projects//history/-.cc.json.gz. + # - Copy meta sidecar if present. + # 3. ONE manifest rebuild. + # 4. ONE commit ("Backfill history snapshots for "). + # 5. ONE push, with retry-on-conflict. + # + # Replaces the previous loop-of-publishes which did N clones / N pushes. - name: Publish backfill batch - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main with: mode: bulk-history project_name: ${{ steps.project.outputs.name }} @@ -329,8 +503,14 @@ jobs: data_repo_token: ${{ secrets.CODEBASE_ARCHITECTURES_TOKEN }} snapshots_dir: ${{ steps.history.outputs.snapshots_dir }} + # ── Step: refresh the latest.* pointers from the newest entry ───────── + # `set-latest` mode picks the lexicographically newest non-`latest` + # file in projects//history/ and copies it over + # latest.cc.json.gz (+ .cg.json + .meta.json if available). Separate + # commit from bulk-history so the history entries and the pointer + # refresh are easy to revert independently if something goes wrong. - name: Refresh latest pointer - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main with: mode: set-latest project_name: ${{ steps.project.outputs.name }} From 9e569ac6e1d92187f897835327ee304defd3a81d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Potoc=CC=8Cek?= Date: Mon, 11 May 2026 14:29:08 +0200 Subject: [PATCH 03/22] Strip literal `futuredapp/.github/` from top-of-file comment validate-action-refs.sh greps for `futuredapp/.github/` line-by-line and flags any line where the ref isn't @. The walk-through comment contained a literal `uses: futuredapp/.github/.../universal-codecharta.yml@` example which the validator counted as a mismatched ref. Rephrased the example so it no longer matches the validator's pattern. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/universal-codecharta.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/universal-codecharta.yml b/.github/workflows/universal-codecharta.yml index b1d30230..2b11275a 100644 --- a/.github/workflows/universal-codecharta.yml +++ b/.github/workflows/universal-codecharta.yml @@ -5,9 +5,9 @@ name: CodeCharta + DependaCharta snapshot # ============================================================================ # # This is a *reusable workflow* — consumer repos invoke it from their own -# .github/workflows/codecharta.yml via `uses: futuredapp/.github/.github/workflows/universal-codecharta.yml@`. -# The consumer-side YAML only declares WHEN to run; this file owns the actual -# pipeline. +# .github/workflows/codecharta.yml with a `uses:` line targeting this file at +# a tagged ref. The consumer-side YAML only declares WHEN to run; this file +# owns the actual pipeline. # # The pipeline produces "snapshots" — single .cc.json.gz files containing the # project's file tree, per-file complexity, LOC, churn, and dependency edges. From 1708e021713133bc335b623652eda72ff0558903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Potoc=CC=8Cek?= Date: Mon, 11 May 2026 14:56:25 +0200 Subject: [PATCH 04/22] Move data_repo and picker_base_url from hardcoded env to required inputs The .github repo is public; baking specific (often private) repo names into the reusable workflow leaks them to anyone reading the source. Promote `data_repo` and `picker_base_url` to required workflow inputs so the public file contains no specific repo names, and scrub all private-repo references from comments and action descriptions. Consumer YAMLs gain two extra lines: with: data_repo: / picker_base_url: https://... Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/actions/ios-kmp-build/action.yml | 4 +- .../universal-codecharta-publish/action.yml | 12 ++--- .../codecharta-publish.sh | 7 ++- .github/workflows/android-cloud-check.yml | 4 +- ...droid-cloud-generate-baseline-profiles.yml | 4 +- .../workflows/android-cloud-nightly-build.yml | 8 +-- ...-cloud-release-firebaseAppDistribution.yml | 4 +- .../android-cloud-release-googlePlay.yml | 4 +- .../workflows/ios-kmp-selfhosted-build.yml | 4 +- .../workflows/ios-kmp-selfhosted-release.yml | 4 +- .github/workflows/ios-kmp-selfhosted-test.yml | 2 +- .github/workflows/ios-selfhosted-build.yml | 2 +- .../ios-selfhosted-nightly-build.yml | 8 +-- .../ios-selfhosted-on-demand-build.yml | 6 +-- .github/workflows/ios-selfhosted-release.yml | 4 +- .github/workflows/ios-selfhosted-test.yml | 2 +- .../workflows/kmp-cloud-detect-changes.yml | 4 +- .../workflows/kmp-combined-nightly-build.yml | 12 ++--- .github/workflows/universal-codecharta.yml | 51 ++++++++++++------- 19 files changed, 79 insertions(+), 67 deletions(-) diff --git a/.github/actions/ios-kmp-build/action.yml b/.github/actions/ios-kmp-build/action.yml index ae400623..54e28747 100644 --- a/.github/actions/ios-kmp-build/action.yml +++ b/.github/actions/ios-kmp-build/action.yml @@ -48,7 +48,7 @@ runs: steps: - name: Export secrets to .xcconfig file if: ${{ inputs.secret_xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 + uses: futuredapp/.github/.github/actions/ios-export-secrets@main with: XCCONFIG_PATH: ${{ inputs.secret_xcconfig_path }} SECRET_PROPERTIES: ${{ inputs.secret_properties }} @@ -63,7 +63,7 @@ runs: cd ${{ inputs.kmp_swift_package_path }} make build - name: Fastlane Beta - uses: futuredapp/.github/.github/actions/ios-fastlane-beta@2.3.3 + uses: futuredapp/.github/.github/actions/ios-fastlane-beta@main with: match_password: ${{ inputs.match_password }} testflight_changelog: ${{ inputs.testflight_changelog }} diff --git a/.github/actions/universal-codecharta-publish/action.yml b/.github/actions/universal-codecharta-publish/action.yml index dc015802..162873d5 100644 --- a/.github/actions/universal-codecharta-publish/action.yml +++ b/.github/actions/universal-codecharta-publish/action.yml @@ -1,8 +1,8 @@ name: CodeCharta publish description: > - Publishes a CodeCharta snapshot (or removes a preview) into a data repo — - `futuredapp/codebase-architectures` by default. Clones the data repo, copies - the artifacts into `projects//...`, rebuilds the manifest, and + Publishes a CodeCharta snapshot (or removes a preview) into the data repo + the consumer specifies via `data_repo`. Clones the data repo, copies the + artifacts into `projects//...`, rebuilds the manifest, and pushes back to the data repo's default branch. Handles concurrent commits via retry-on-conflict. @@ -19,11 +19,11 @@ inputs: description: > Output folder name in the data repo (`projects//`). data_repo: - required: false - default: futuredapp/codebase-architectures + required: true description: > `/` of the data repository that holds the published - snapshots and the manifest. + snapshots and the manifest. No default — supplied per consumer so this + public action doesn't bake in a specific (potentially private) repo. data_repo_branch: required: false default: main diff --git a/.github/actions/universal-codecharta-publish/codecharta-publish.sh b/.github/actions/universal-codecharta-publish/codecharta-publish.sh index c192c812..e0a5b35b 100755 --- a/.github/actions/universal-codecharta-publish/codecharta-publish.sh +++ b/.github/actions/universal-codecharta-publish/codecharta-publish.sh @@ -1,15 +1,14 @@ #!/usr/bin/env bash set -euo pipefail -# Publish CodeCharta snapshots to the data repo (default -# `futuredapp/codebase-architectures`). Invoked by the -# universal-codecharta-publish composite action. +# Publish CodeCharta snapshots to the data repo specified via DATA_REPO env. +# Invoked by the universal-codecharta-publish composite action. # # Inputs (env, set by action.yml from action inputs): # CODECHARTA_PUBLISH_MODE One of: preview, history, bulk-history, # delete-preview, set-latest. # PROJECT_NAME Folder name under `projects/`. -# DATA_REPO `/`, e.g. `futuredapp/codebase-architectures`. +# DATA_REPO `/` of the data repository. # DATA_REPO_BRANCH Branch to push to (default: main). # CODEBASE_ARCHITECTURES_TOKEN # Write-scoped token for the data repo. diff --git a/.github/workflows/android-cloud-check.yml b/.github/workflows/android-cloud-check.yml index 7ad86449..28c9f6c8 100644 --- a/.github/workflows/android-cloud-check.yml +++ b/.github/workflows/android-cloud-check.yml @@ -61,13 +61,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Run checks - uses: futuredapp/.github/.github/actions/android-check@2.3.3 + uses: futuredapp/.github/.github/actions/android-check@main with: lint_gradle_task: ${{ inputs.LINT_GRADLE_TASKS }} test_gradle_task: ${{ inputs.TEST_GRADLE_TASKS }} diff --git a/.github/workflows/android-cloud-generate-baseline-profiles.yml b/.github/workflows/android-cloud-generate-baseline-profiles.yml index 41bfa92b..39ae2cac 100644 --- a/.github/workflows/android-cloud-generate-baseline-profiles.yml +++ b/.github/workflows/android-cloud-generate-baseline-profiles.yml @@ -75,13 +75,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Generate baseline profiles - uses: futuredapp/.github/.github/actions/android-generate-baseline-profiles@2.3.3 + uses: futuredapp/.github/.github/actions/android-generate-baseline-profiles@main with: generate_gradle_task: ${{ inputs.TASK_NAME }} signing_keystore_password: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }} diff --git a/.github/workflows/android-cloud-nightly-build.yml b/.github/workflows/android-cloud-nightly-build.yml index 505d8b14..83318a41 100644 --- a/.github/workflows/android-cloud-nightly-build.yml +++ b/.github/workflows/android-cloud-nightly-build.yml @@ -113,7 +113,7 @@ jobs: steps: - name: Detect changes and generate changelog id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.3.3 + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main with: checkout_depth: ${{ inputs.CHANGELOG_CHECKOUT_DEPTH }} debug: ${{ inputs.CHANGELOG_DEBUG }} @@ -132,13 +132,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Build and upload to App Distribution - uses: futuredapp/.github/.github/actions/android-build-firebase@2.3.3 + uses: futuredapp/.github/.github/actions/android-build-firebase@main with: test_gradle_task: ${{ inputs.TEST_GRADLE_TASKS }} package_gradle_task: ${{ inputs.PACKAGE_GRADLE_TASK }} @@ -185,7 +185,7 @@ jobs: fi - name: Transition JIRA tickets if: steps.check.outputs.enabled == 'true' - uses: futuredapp/.github/.github/actions/jira-transition-tickets@2.3.3 + uses: futuredapp/.github/.github/actions/jira-transition-tickets@main with: jira_context: ${{ secrets.JIRA_CONTEXT }} transition: ${{ inputs.JIRA_TRANSITION }} diff --git a/.github/workflows/android-cloud-release-firebaseAppDistribution.yml b/.github/workflows/android-cloud-release-firebaseAppDistribution.yml index 896fd680..dedba2a0 100644 --- a/.github/workflows/android-cloud-release-firebaseAppDistribution.yml +++ b/.github/workflows/android-cloud-release-firebaseAppDistribution.yml @@ -96,13 +96,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Build and upload to App Distribution - uses: futuredapp/.github/.github/actions/android-build-firebase@2.3.3 + uses: futuredapp/.github/.github/actions/android-build-firebase@main with: test_gradle_task: ${{ inputs.TEST_GRADLE_TASKS }} package_gradle_task: ${{ inputs.PACKAGE_GRADLE_TASK }} diff --git a/.github/workflows/android-cloud-release-googlePlay.yml b/.github/workflows/android-cloud-release-googlePlay.yml index 19556e7d..c8dd78f3 100644 --- a/.github/workflows/android-cloud-release-googlePlay.yml +++ b/.github/workflows/android-cloud-release-googlePlay.yml @@ -108,12 +108,12 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} - name: Build and release - uses: futuredapp/.github/.github/actions/android-build-googlePlay@2.3.3 + uses: futuredapp/.github/.github/actions/android-build-googlePlay@main with: bundle_gradle_task: ${{ inputs.BUNDLE_GRADLE_TASK }} version_name: ${{ inputs.VERSION_NAME }} diff --git a/.github/workflows/ios-kmp-selfhosted-build.yml b/.github/workflows/ios-kmp-selfhosted-build.yml index 3c7be03b..3802aa22 100644 --- a/.github/workflows/ios-kmp-selfhosted-build.yml +++ b/.github/workflows/ios-kmp-selfhosted-build.yml @@ -97,14 +97,14 @@ jobs: with: lfs: ${{ inputs.use_git_lfs }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.java_version }} java_distribution: ${{ inputs.java_distribution }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} ruby: 'false' - name: Build and upload to TestFlight - uses: futuredapp/.github/.github/actions/ios-kmp-build@2.3.3 + uses: futuredapp/.github/.github/actions/ios-kmp-build@main with: match_password: ${{ secrets.MATCH_PASSWORD }} app_store_connect_api_key_key: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY }} diff --git a/.github/workflows/ios-kmp-selfhosted-release.yml b/.github/workflows/ios-kmp-selfhosted-release.yml index c703cb95..832d546a 100644 --- a/.github/workflows/ios-kmp-selfhosted-release.yml +++ b/.github/workflows/ios-kmp-selfhosted-release.yml @@ -86,7 +86,7 @@ jobs: lfs: ${{ inputs.use_git_lfs }} - name: Export secrets to .xcconfig file if: ${{ inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 + uses: futuredapp/.github/.github/actions/ios-export-secrets@main with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} @@ -109,7 +109,7 @@ jobs: cd ${{ inputs.kmp_swift_package_path }} make build - name: Fastlane Release - uses: futuredapp/.github/.github/actions/ios-fastlane-release@2.3.3 + uses: futuredapp/.github/.github/actions/ios-fastlane-release@main with: match_password: ${{ secrets.MATCH_PASSWORD }} version_number: ${{ github.ref_name }} diff --git a/.github/workflows/ios-kmp-selfhosted-test.yml b/.github/workflows/ios-kmp-selfhosted-test.yml index 1954a1e8..8794b1c3 100644 --- a/.github/workflows/ios-kmp-selfhosted-test.yml +++ b/.github/workflows/ios-kmp-selfhosted-test.yml @@ -83,7 +83,7 @@ jobs: cd ${{ inputs.kmp_swift_package_path }} make build - name: Fastlane Test - uses: futuredapp/.github/.github/actions/ios-fastlane-test@2.3.3 + uses: futuredapp/.github/.github/actions/ios-fastlane-test@main with: github_token: ${{ secrets.GITHUB_TOKEN_DANGER }} custom_values: ${{ inputs.custom_values }} diff --git a/.github/workflows/ios-selfhosted-build.yml b/.github/workflows/ios-selfhosted-build.yml index 234e1748..43ae49e6 100644 --- a/.github/workflows/ios-selfhosted-build.yml +++ b/.github/workflows/ios-selfhosted-build.yml @@ -63,7 +63,7 @@ jobs: echo "::warning::This workflow ('ios-selfhosted-build.yml') is deprecated and will be removed in the future." echo "::warning::Please use 'ios-selfhosted-nightly-build.yml' instead." build: - uses: futuredapp/.github/.github/workflows/ios-selfhosted-nightly-build.yml@2.3.3 + uses: futuredapp/.github/.github/workflows/ios-selfhosted-nightly-build.yml@main with: use_git_lfs: ${{ inputs.use_git_lfs }} custom_values: ${{ inputs.custom_values }} diff --git a/.github/workflows/ios-selfhosted-nightly-build.yml b/.github/workflows/ios-selfhosted-nightly-build.yml index 3835ee9c..3f39355a 100644 --- a/.github/workflows/ios-selfhosted-nightly-build.yml +++ b/.github/workflows/ios-selfhosted-nightly-build.yml @@ -80,7 +80,7 @@ jobs: steps: - name: Detect changes and generate changelog id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.3.3 + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main with: checkout_depth: ${{ inputs.checkout_depth }} fallback_lookback: ${{ inputs.changelog_fallback_lookback }} @@ -94,7 +94,7 @@ jobs: - name: Export secrets if: ${{ steps.detect_changes.outputs.skip_build == 'false' && inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 + uses: futuredapp/.github/.github/actions/ios-export-secrets@main with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} @@ -102,7 +102,7 @@ jobs: - name: Fastlane Beta if: ${{ steps.detect_changes.outputs.skip_build == 'false' }} - uses: futuredapp/.github/.github/actions/ios-fastlane-beta@2.3.3 + uses: futuredapp/.github/.github/actions/ios-fastlane-beta@main with: match_password: ${{ secrets.MATCH_PASSWORD }} testflight_changelog: ${{ steps.set_changelog.outputs.changelog }} @@ -158,7 +158,7 @@ jobs: fi - name: Transition JIRA tickets if: steps.check.outputs.enabled == 'true' - uses: futuredapp/.github/.github/actions/jira-transition-tickets@2.3.3 + uses: futuredapp/.github/.github/actions/jira-transition-tickets@main with: jira_context: ${{ secrets.JIRA_CONTEXT }} transition: ${{ inputs.jira_transition }} diff --git a/.github/workflows/ios-selfhosted-on-demand-build.yml b/.github/workflows/ios-selfhosted-on-demand-build.yml index 78e61466..5f992af4 100644 --- a/.github/workflows/ios-selfhosted-on-demand-build.yml +++ b/.github/workflows/ios-selfhosted-on-demand-build.yml @@ -81,7 +81,7 @@ jobs: - name: Generate changelog if not provided if: ${{ inputs.changelog == '' }} id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.3.3 + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main with: checkout_depth: ${{ inputs.checkout_depth }} fallback_lookback: ${{ inputs.changelog_fallback_lookback }} @@ -104,14 +104,14 @@ jobs: - name: Export secrets if: ${{ inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 + uses: futuredapp/.github/.github/actions/ios-export-secrets@main with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} REQUIRED_KEYS: ${{ inputs.required_keys }} - name: Fastlane Beta - uses: futuredapp/.github/.github/actions/ios-fastlane-beta@2.3.3 + uses: futuredapp/.github/.github/actions/ios-fastlane-beta@main with: MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} testflight_changelog: ${{ steps.set_changelog.outputs.changelog }} diff --git a/.github/workflows/ios-selfhosted-release.yml b/.github/workflows/ios-selfhosted-release.yml index 18d7f9dc..4f38a95c 100644 --- a/.github/workflows/ios-selfhosted-release.yml +++ b/.github/workflows/ios-selfhosted-release.yml @@ -62,14 +62,14 @@ jobs: - name: Export secrets if: ${{ inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 + uses: futuredapp/.github/.github/actions/ios-export-secrets@main with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} REQUIRED_KEYS: ${{ inputs.required_keys }} - name: Fastlane Release - uses: futuredapp/.github/.github/actions/ios-fastlane-release@2.3.3 + uses: futuredapp/.github/.github/actions/ios-fastlane-release@main with: match_password: ${{ secrets.MATCH_PASSWORD }} version_number: ${{ github.ref_name }} diff --git a/.github/workflows/ios-selfhosted-test.yml b/.github/workflows/ios-selfhosted-test.yml index 7d08307f..4f06ba20 100644 --- a/.github/workflows/ios-selfhosted-test.yml +++ b/.github/workflows/ios-selfhosted-test.yml @@ -41,7 +41,7 @@ jobs: lfs: ${{ inputs.use_git_lfs }} - name: Fastlane Test - uses: futuredapp/.github/.github/actions/ios-fastlane-test@2.3.3 + uses: futuredapp/.github/.github/actions/ios-fastlane-test@main with: github_token: ${{ secrets.GITHUB_TOKEN_DANGER }} custom_values: ${{ inputs.custom_values }} \ No newline at end of file diff --git a/.github/workflows/kmp-cloud-detect-changes.yml b/.github/workflows/kmp-cloud-detect-changes.yml index b9c0b4c0..50caa63d 100644 --- a/.github/workflows/kmp-cloud-detect-changes.yml +++ b/.github/workflows/kmp-cloud-detect-changes.yml @@ -28,7 +28,7 @@ name: Detect Changes # # Note: For direct action usage in other workflows, you can also use: # - name: Detect Changes -# uses: futuredapp/.github/.github/actions/kmp-detect-changes@2.3.3 +# uses: futuredapp/.github/.github/actions/kmp-detect-changes@main on: workflow_call: @@ -58,6 +58,6 @@ jobs: steps: - name: Detect Changes id: detect - uses: futuredapp/.github/.github/actions/kmp-detect-changes@2.3.3 + uses: futuredapp/.github/.github/actions/kmp-detect-changes@main with: USE_GIT_LFS: ${{ inputs.USE_GIT_LFS }} diff --git a/.github/workflows/kmp-combined-nightly-build.yml b/.github/workflows/kmp-combined-nightly-build.yml index ef4b5842..1d85884a 100644 --- a/.github/workflows/kmp-combined-nightly-build.yml +++ b/.github/workflows/kmp-combined-nightly-build.yml @@ -152,7 +152,7 @@ jobs: steps: - name: Detect changes and generate changelog id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.3.3 + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main with: checkout_depth: ${{ inputs.CHANGELOG_CHECKOUT_DEPTH }} debug: ${{ inputs.CHANGELOG_DEBUG }} @@ -171,14 +171,14 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} ruby: 'false' - name: Build and upload to TestFlight - uses: futuredapp/.github/.github/actions/ios-kmp-build@2.3.3 + uses: futuredapp/.github/.github/actions/ios-kmp-build@main with: match_password: ${{ secrets.IOS_MATCH_PASSWORD }} app_store_connect_api_key_key: ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_KEY }} @@ -208,13 +208,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Build and upload to App Distribution - uses: futuredapp/.github/.github/actions/android-build-firebase@2.3.3 + uses: futuredapp/.github/.github/actions/android-build-firebase@main with: test_gradle_task: ${{ inputs.ANDROID_TEST_GRADLE_TASK }} package_gradle_task: ${{ inputs.ANDROID_PACKAGE_GRADLE_TASK }} @@ -270,7 +270,7 @@ jobs: fi - name: Transition JIRA tickets if: steps.check.outputs.enabled == 'true' - uses: futuredapp/.github/.github/actions/jira-transition-tickets@2.3.3 + uses: futuredapp/.github/.github/actions/jira-transition-tickets@main with: jira_context: ${{ secrets.JIRA_CONTEXT }} transition: ${{ inputs.JIRA_TRANSITION }} diff --git a/.github/workflows/universal-codecharta.yml b/.github/workflows/universal-codecharta.yml index 2b11275a..07847468 100644 --- a/.github/workflows/universal-codecharta.yml +++ b/.github/workflows/universal-codecharta.yml @@ -11,8 +11,8 @@ name: CodeCharta + DependaCharta snapshot # # The pipeline produces "snapshots" — single .cc.json.gz files containing the # project's file tree, per-file complexity, LOC, churn, and dependency edges. -# Snapshots are stored in the data repo (futuredapp/codebase-architectures) and -# rendered by the picker UI at https://codebase-arch.futured.app/. +# Snapshots are stored in a data repo (which the consumer specifies via the +# `data_repo` input) and rendered by a picker UI (`picker_base_url` input). # # Four scenarios trigger this workflow, each handled by a different job: # @@ -59,8 +59,22 @@ on: required: false description: > Override for the data-repo folder name. Defaults to the consumer's - repository name (`github.event.repository.name`) — e.g. a workflow - running in `smsticket-ios` publishes to `projects/smsticket-ios/`. + repository name (`github.event.repository.name`), so a workflow + running in a repo named `my-app` publishes to `projects/my-app/`. + data_repo: + type: string + required: true + description: > + `/` of the data repository that stores published + snapshots and the manifest the picker reads. Provided by the + consumer so this public workflow doesn't hardcode a specific + (potentially private) repo name. + picker_base_url: + type: string + required: true + description: > + Base URL of the picker UI. Used to compose links in the PR comment + (CodeCharta studio, DependaCharta visualizer, delta view). file_extensions: type: string required: false @@ -108,8 +122,8 @@ on: required: true description: > Fine-grained PAT or GitHub App token with `Contents: read+write` on - futuredapp/codebase-architectures. The publish jobs use it to clone - the data repo and push commits. + the data repo (see `data_repo` input). The publish jobs use it to + clone the data repo and push commits. # ── Concurrency ────────────────────────────────────────────────────────────── # Groups runs by (event_name, PR_number_or_ref): @@ -123,13 +137,12 @@ concurrency: group: codecharta-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: false -# ── Workflow-level env (org-wide constants) ────────────────────────────────── -# These never vary per consumer. Hardcoded here rather than as inputs because -# changing one of these would mean a coordinated migration across every -# consumer repo, not a per-repo preference. +# ── Workflow-level env ─────────────────────────────────────────────────────── +# Only the DependaCharta image base lives here — the fork is publicly hosted +# and shared by all stacks. The data-repo and picker URL come from inputs so +# this workflow can be reused without baking specific (often private) repo +# names into a public file. env: - DATA_REPO: futuredapp/codebase-architectures - PICKER_BASE_URL: https://codebase-arch.futured.app DEPENDACHARTA_IMAGE_BASE: ghcr.io/futuredapp/dependacharta-swift jobs: @@ -278,7 +291,7 @@ jobs: with: mode: preview project_name: ${{ steps.project.outputs.name }} - data_repo: ${{ env.DATA_REPO }} + data_repo: ${{ inputs.data_repo }} data_repo_token: ${{ secrets.CODEBASE_ARCHITECTURES_TOKEN }} pr_number: ${{ steps.ids.outputs.pr }} cc_path: ${{ steps.snapshot.outputs.cc_path }} @@ -294,7 +307,7 @@ jobs: with: mode: history project_name: ${{ steps.project.outputs.name }} - data_repo: ${{ env.DATA_REPO }} + data_repo: ${{ inputs.data_repo }} data_repo_token: ${{ secrets.CODEBASE_ARCHITECTURES_TOKEN }} stem: ${{ steps.ids.outputs.stem }} date: ${{ steps.ids.outputs.date }} @@ -317,7 +330,7 @@ jobs: set -euo pipefail pr="${{ steps.ids.outputs.pr }}" name="${{ steps.project.outputs.name }}" - base="${{ env.PICKER_BASE_URL }}" + base="${{ inputs.picker_base_url }}" ccPath="projects/${name}/previews/pr-${pr}.cc.json.gz" cgPath="projects/${name}/previews/pr-${pr}.cg.json" latestPath="projects/${name}/history/latest.cc.json.gz" @@ -368,7 +381,7 @@ jobs: - [Open this PR in DependaCharta](${{ steps.links.outputs.cgVis }}) - [Compare PR vs latest develop (CodeCharta delta view)](${{ steps.links.outputs.ccDelta }}) - Snapshot is regenerated on every push. Picker: ${{ env.PICKER_BASE_URL }}/ + Snapshot is regenerated on every push. Picker: ${{ inputs.picker_base_url }}/ # ========================================================================== # JOB 2: cleanup @@ -404,7 +417,7 @@ jobs: with: mode: delete-preview project_name: ${{ steps.project.outputs.name }} - data_repo: ${{ env.DATA_REPO }} + data_repo: ${{ inputs.data_repo }} data_repo_token: ${{ secrets.CODEBASE_ARCHITECTURES_TOKEN }} pr_number: ${{ github.event.pull_request.number }} @@ -499,7 +512,7 @@ jobs: with: mode: bulk-history project_name: ${{ steps.project.outputs.name }} - data_repo: ${{ env.DATA_REPO }} + data_repo: ${{ inputs.data_repo }} data_repo_token: ${{ secrets.CODEBASE_ARCHITECTURES_TOKEN }} snapshots_dir: ${{ steps.history.outputs.snapshots_dir }} @@ -514,5 +527,5 @@ jobs: with: mode: set-latest project_name: ${{ steps.project.outputs.name }} - data_repo: ${{ env.DATA_REPO }} + data_repo: ${{ inputs.data_repo }} data_repo_token: ${{ secrets.CODEBASE_ARCHITECTURES_TOKEN }} From 3254b03f0be297853b71b9079860531aeba14532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Potoc=CC=8Cek?= Date: Mon, 11 May 2026 14:57:41 +0200 Subject: [PATCH 05/22] =?UTF-8?q?Revert=20accidental=20@2.3.3=20=E2=86=92?= =?UTF-8?q?=20@main=20flip=20on=20unrelated=20workflows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit (1708e02) inadvertently bumped 16 unrelated android/ios/kmp workflows from @2.3.3 to @main as a side-effect of running bump-action-refs.sh locally during validation. Restoring them to the released @2.3.3 tag they were pinned to before this branch. Net effect: only the four codecharta-related files (workflow + two publish-action files + scrubbed comments) carry intentional changes. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/actions/ios-kmp-build/action.yml | 4 ++-- .github/workflows/android-cloud-check.yml | 4 ++-- .../android-cloud-generate-baseline-profiles.yml | 4 ++-- .github/workflows/android-cloud-nightly-build.yml | 8 ++++---- ...android-cloud-release-firebaseAppDistribution.yml | 4 ++-- .../workflows/android-cloud-release-googlePlay.yml | 4 ++-- .github/workflows/ios-kmp-selfhosted-build.yml | 4 ++-- .github/workflows/ios-kmp-selfhosted-release.yml | 4 ++-- .github/workflows/ios-kmp-selfhosted-test.yml | 2 +- .github/workflows/ios-selfhosted-build.yml | 2 +- .github/workflows/ios-selfhosted-nightly-build.yml | 8 ++++---- .github/workflows/ios-selfhosted-on-demand-build.yml | 6 +++--- .github/workflows/ios-selfhosted-release.yml | 4 ++-- .github/workflows/ios-selfhosted-test.yml | 2 +- .github/workflows/kmp-cloud-detect-changes.yml | 4 ++-- .github/workflows/kmp-combined-nightly-build.yml | 12 ++++++------ 16 files changed, 38 insertions(+), 38 deletions(-) diff --git a/.github/actions/ios-kmp-build/action.yml b/.github/actions/ios-kmp-build/action.yml index 54e28747..ae400623 100644 --- a/.github/actions/ios-kmp-build/action.yml +++ b/.github/actions/ios-kmp-build/action.yml @@ -48,7 +48,7 @@ runs: steps: - name: Export secrets to .xcconfig file if: ${{ inputs.secret_xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@main + uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 with: XCCONFIG_PATH: ${{ inputs.secret_xcconfig_path }} SECRET_PROPERTIES: ${{ inputs.secret_properties }} @@ -63,7 +63,7 @@ runs: cd ${{ inputs.kmp_swift_package_path }} make build - name: Fastlane Beta - uses: futuredapp/.github/.github/actions/ios-fastlane-beta@main + uses: futuredapp/.github/.github/actions/ios-fastlane-beta@2.3.3 with: match_password: ${{ inputs.match_password }} testflight_changelog: ${{ inputs.testflight_changelog }} diff --git a/.github/workflows/android-cloud-check.yml b/.github/workflows/android-cloud-check.yml index 28c9f6c8..7ad86449 100644 --- a/.github/workflows/android-cloud-check.yml +++ b/.github/workflows/android-cloud-check.yml @@ -61,13 +61,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Run checks - uses: futuredapp/.github/.github/actions/android-check@main + uses: futuredapp/.github/.github/actions/android-check@2.3.3 with: lint_gradle_task: ${{ inputs.LINT_GRADLE_TASKS }} test_gradle_task: ${{ inputs.TEST_GRADLE_TASKS }} diff --git a/.github/workflows/android-cloud-generate-baseline-profiles.yml b/.github/workflows/android-cloud-generate-baseline-profiles.yml index 39ae2cac..41bfa92b 100644 --- a/.github/workflows/android-cloud-generate-baseline-profiles.yml +++ b/.github/workflows/android-cloud-generate-baseline-profiles.yml @@ -75,13 +75,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Generate baseline profiles - uses: futuredapp/.github/.github/actions/android-generate-baseline-profiles@main + uses: futuredapp/.github/.github/actions/android-generate-baseline-profiles@2.3.3 with: generate_gradle_task: ${{ inputs.TASK_NAME }} signing_keystore_password: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }} diff --git a/.github/workflows/android-cloud-nightly-build.yml b/.github/workflows/android-cloud-nightly-build.yml index 83318a41..505d8b14 100644 --- a/.github/workflows/android-cloud-nightly-build.yml +++ b/.github/workflows/android-cloud-nightly-build.yml @@ -113,7 +113,7 @@ jobs: steps: - name: Detect changes and generate changelog id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.3.3 with: checkout_depth: ${{ inputs.CHANGELOG_CHECKOUT_DEPTH }} debug: ${{ inputs.CHANGELOG_DEBUG }} @@ -132,13 +132,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Build and upload to App Distribution - uses: futuredapp/.github/.github/actions/android-build-firebase@main + uses: futuredapp/.github/.github/actions/android-build-firebase@2.3.3 with: test_gradle_task: ${{ inputs.TEST_GRADLE_TASKS }} package_gradle_task: ${{ inputs.PACKAGE_GRADLE_TASK }} @@ -185,7 +185,7 @@ jobs: fi - name: Transition JIRA tickets if: steps.check.outputs.enabled == 'true' - uses: futuredapp/.github/.github/actions/jira-transition-tickets@main + uses: futuredapp/.github/.github/actions/jira-transition-tickets@2.3.3 with: jira_context: ${{ secrets.JIRA_CONTEXT }} transition: ${{ inputs.JIRA_TRANSITION }} diff --git a/.github/workflows/android-cloud-release-firebaseAppDistribution.yml b/.github/workflows/android-cloud-release-firebaseAppDistribution.yml index dedba2a0..896fd680 100644 --- a/.github/workflows/android-cloud-release-firebaseAppDistribution.yml +++ b/.github/workflows/android-cloud-release-firebaseAppDistribution.yml @@ -96,13 +96,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Build and upload to App Distribution - uses: futuredapp/.github/.github/actions/android-build-firebase@main + uses: futuredapp/.github/.github/actions/android-build-firebase@2.3.3 with: test_gradle_task: ${{ inputs.TEST_GRADLE_TASKS }} package_gradle_task: ${{ inputs.PACKAGE_GRADLE_TASK }} diff --git a/.github/workflows/android-cloud-release-googlePlay.yml b/.github/workflows/android-cloud-release-googlePlay.yml index c8dd78f3..19556e7d 100644 --- a/.github/workflows/android-cloud-release-googlePlay.yml +++ b/.github/workflows/android-cloud-release-googlePlay.yml @@ -108,12 +108,12 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} - name: Build and release - uses: futuredapp/.github/.github/actions/android-build-googlePlay@main + uses: futuredapp/.github/.github/actions/android-build-googlePlay@2.3.3 with: bundle_gradle_task: ${{ inputs.BUNDLE_GRADLE_TASK }} version_name: ${{ inputs.VERSION_NAME }} diff --git a/.github/workflows/ios-kmp-selfhosted-build.yml b/.github/workflows/ios-kmp-selfhosted-build.yml index 3802aa22..3c7be03b 100644 --- a/.github/workflows/ios-kmp-selfhosted-build.yml +++ b/.github/workflows/ios-kmp-selfhosted-build.yml @@ -97,14 +97,14 @@ jobs: with: lfs: ${{ inputs.use_git_lfs }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 with: java_version: ${{ inputs.java_version }} java_distribution: ${{ inputs.java_distribution }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} ruby: 'false' - name: Build and upload to TestFlight - uses: futuredapp/.github/.github/actions/ios-kmp-build@main + uses: futuredapp/.github/.github/actions/ios-kmp-build@2.3.3 with: match_password: ${{ secrets.MATCH_PASSWORD }} app_store_connect_api_key_key: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY }} diff --git a/.github/workflows/ios-kmp-selfhosted-release.yml b/.github/workflows/ios-kmp-selfhosted-release.yml index 832d546a..c703cb95 100644 --- a/.github/workflows/ios-kmp-selfhosted-release.yml +++ b/.github/workflows/ios-kmp-selfhosted-release.yml @@ -86,7 +86,7 @@ jobs: lfs: ${{ inputs.use_git_lfs }} - name: Export secrets to .xcconfig file if: ${{ inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@main + uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} @@ -109,7 +109,7 @@ jobs: cd ${{ inputs.kmp_swift_package_path }} make build - name: Fastlane Release - uses: futuredapp/.github/.github/actions/ios-fastlane-release@main + uses: futuredapp/.github/.github/actions/ios-fastlane-release@2.3.3 with: match_password: ${{ secrets.MATCH_PASSWORD }} version_number: ${{ github.ref_name }} diff --git a/.github/workflows/ios-kmp-selfhosted-test.yml b/.github/workflows/ios-kmp-selfhosted-test.yml index 8794b1c3..1954a1e8 100644 --- a/.github/workflows/ios-kmp-selfhosted-test.yml +++ b/.github/workflows/ios-kmp-selfhosted-test.yml @@ -83,7 +83,7 @@ jobs: cd ${{ inputs.kmp_swift_package_path }} make build - name: Fastlane Test - uses: futuredapp/.github/.github/actions/ios-fastlane-test@main + uses: futuredapp/.github/.github/actions/ios-fastlane-test@2.3.3 with: github_token: ${{ secrets.GITHUB_TOKEN_DANGER }} custom_values: ${{ inputs.custom_values }} diff --git a/.github/workflows/ios-selfhosted-build.yml b/.github/workflows/ios-selfhosted-build.yml index 43ae49e6..234e1748 100644 --- a/.github/workflows/ios-selfhosted-build.yml +++ b/.github/workflows/ios-selfhosted-build.yml @@ -63,7 +63,7 @@ jobs: echo "::warning::This workflow ('ios-selfhosted-build.yml') is deprecated and will be removed in the future." echo "::warning::Please use 'ios-selfhosted-nightly-build.yml' instead." build: - uses: futuredapp/.github/.github/workflows/ios-selfhosted-nightly-build.yml@main + uses: futuredapp/.github/.github/workflows/ios-selfhosted-nightly-build.yml@2.3.3 with: use_git_lfs: ${{ inputs.use_git_lfs }} custom_values: ${{ inputs.custom_values }} diff --git a/.github/workflows/ios-selfhosted-nightly-build.yml b/.github/workflows/ios-selfhosted-nightly-build.yml index 3f39355a..3835ee9c 100644 --- a/.github/workflows/ios-selfhosted-nightly-build.yml +++ b/.github/workflows/ios-selfhosted-nightly-build.yml @@ -80,7 +80,7 @@ jobs: steps: - name: Detect changes and generate changelog id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.3.3 with: checkout_depth: ${{ inputs.checkout_depth }} fallback_lookback: ${{ inputs.changelog_fallback_lookback }} @@ -94,7 +94,7 @@ jobs: - name: Export secrets if: ${{ steps.detect_changes.outputs.skip_build == 'false' && inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@main + uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} @@ -102,7 +102,7 @@ jobs: - name: Fastlane Beta if: ${{ steps.detect_changes.outputs.skip_build == 'false' }} - uses: futuredapp/.github/.github/actions/ios-fastlane-beta@main + uses: futuredapp/.github/.github/actions/ios-fastlane-beta@2.3.3 with: match_password: ${{ secrets.MATCH_PASSWORD }} testflight_changelog: ${{ steps.set_changelog.outputs.changelog }} @@ -158,7 +158,7 @@ jobs: fi - name: Transition JIRA tickets if: steps.check.outputs.enabled == 'true' - uses: futuredapp/.github/.github/actions/jira-transition-tickets@main + uses: futuredapp/.github/.github/actions/jira-transition-tickets@2.3.3 with: jira_context: ${{ secrets.JIRA_CONTEXT }} transition: ${{ inputs.jira_transition }} diff --git a/.github/workflows/ios-selfhosted-on-demand-build.yml b/.github/workflows/ios-selfhosted-on-demand-build.yml index 5f992af4..78e61466 100644 --- a/.github/workflows/ios-selfhosted-on-demand-build.yml +++ b/.github/workflows/ios-selfhosted-on-demand-build.yml @@ -81,7 +81,7 @@ jobs: - name: Generate changelog if not provided if: ${{ inputs.changelog == '' }} id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.3.3 with: checkout_depth: ${{ inputs.checkout_depth }} fallback_lookback: ${{ inputs.changelog_fallback_lookback }} @@ -104,14 +104,14 @@ jobs: - name: Export secrets if: ${{ inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@main + uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} REQUIRED_KEYS: ${{ inputs.required_keys }} - name: Fastlane Beta - uses: futuredapp/.github/.github/actions/ios-fastlane-beta@main + uses: futuredapp/.github/.github/actions/ios-fastlane-beta@2.3.3 with: MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} testflight_changelog: ${{ steps.set_changelog.outputs.changelog }} diff --git a/.github/workflows/ios-selfhosted-release.yml b/.github/workflows/ios-selfhosted-release.yml index 4f38a95c..18d7f9dc 100644 --- a/.github/workflows/ios-selfhosted-release.yml +++ b/.github/workflows/ios-selfhosted-release.yml @@ -62,14 +62,14 @@ jobs: - name: Export secrets if: ${{ inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@main + uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} REQUIRED_KEYS: ${{ inputs.required_keys }} - name: Fastlane Release - uses: futuredapp/.github/.github/actions/ios-fastlane-release@main + uses: futuredapp/.github/.github/actions/ios-fastlane-release@2.3.3 with: match_password: ${{ secrets.MATCH_PASSWORD }} version_number: ${{ github.ref_name }} diff --git a/.github/workflows/ios-selfhosted-test.yml b/.github/workflows/ios-selfhosted-test.yml index 4f06ba20..7d08307f 100644 --- a/.github/workflows/ios-selfhosted-test.yml +++ b/.github/workflows/ios-selfhosted-test.yml @@ -41,7 +41,7 @@ jobs: lfs: ${{ inputs.use_git_lfs }} - name: Fastlane Test - uses: futuredapp/.github/.github/actions/ios-fastlane-test@main + uses: futuredapp/.github/.github/actions/ios-fastlane-test@2.3.3 with: github_token: ${{ secrets.GITHUB_TOKEN_DANGER }} custom_values: ${{ inputs.custom_values }} \ No newline at end of file diff --git a/.github/workflows/kmp-cloud-detect-changes.yml b/.github/workflows/kmp-cloud-detect-changes.yml index 50caa63d..b9c0b4c0 100644 --- a/.github/workflows/kmp-cloud-detect-changes.yml +++ b/.github/workflows/kmp-cloud-detect-changes.yml @@ -28,7 +28,7 @@ name: Detect Changes # # Note: For direct action usage in other workflows, you can also use: # - name: Detect Changes -# uses: futuredapp/.github/.github/actions/kmp-detect-changes@main +# uses: futuredapp/.github/.github/actions/kmp-detect-changes@2.3.3 on: workflow_call: @@ -58,6 +58,6 @@ jobs: steps: - name: Detect Changes id: detect - uses: futuredapp/.github/.github/actions/kmp-detect-changes@main + uses: futuredapp/.github/.github/actions/kmp-detect-changes@2.3.3 with: USE_GIT_LFS: ${{ inputs.USE_GIT_LFS }} diff --git a/.github/workflows/kmp-combined-nightly-build.yml b/.github/workflows/kmp-combined-nightly-build.yml index 1d85884a..ef4b5842 100644 --- a/.github/workflows/kmp-combined-nightly-build.yml +++ b/.github/workflows/kmp-combined-nightly-build.yml @@ -152,7 +152,7 @@ jobs: steps: - name: Detect changes and generate changelog id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.3.3 with: checkout_depth: ${{ inputs.CHANGELOG_CHECKOUT_DEPTH }} debug: ${{ inputs.CHANGELOG_DEBUG }} @@ -171,14 +171,14 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} ruby: 'false' - name: Build and upload to TestFlight - uses: futuredapp/.github/.github/actions/ios-kmp-build@main + uses: futuredapp/.github/.github/actions/ios-kmp-build@2.3.3 with: match_password: ${{ secrets.IOS_MATCH_PASSWORD }} app_store_connect_api_key_key: ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_KEY }} @@ -208,13 +208,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Build and upload to App Distribution - uses: futuredapp/.github/.github/actions/android-build-firebase@main + uses: futuredapp/.github/.github/actions/android-build-firebase@2.3.3 with: test_gradle_task: ${{ inputs.ANDROID_TEST_GRADLE_TASK }} package_gradle_task: ${{ inputs.ANDROID_PACKAGE_GRADLE_TASK }} @@ -270,7 +270,7 @@ jobs: fi - name: Transition JIRA tickets if: steps.check.outputs.enabled == 'true' - uses: futuredapp/.github/.github/actions/jira-transition-tickets@main + uses: futuredapp/.github/.github/actions/jira-transition-tickets@2.3.3 with: jira_context: ${{ secrets.JIRA_CONTEXT }} transition: ${{ inputs.JIRA_TRANSITION }} From ad60896f455f0a638cee739ed03bf4134c55bc6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Potoc=CC=8Cek?= Date: Mon, 11 May 2026 15:15:06 +0200 Subject: [PATCH 06/22] Flip internal refs to @ci/codecharta-actions for pre-merge smoke testing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Temporarily reverting the @main flip so smsticket-ios PR #86 can actually invoke the workflow before this PR merges. Without this, the consumer's `uses: …@ci/codecharta-actions` resolves the workflow file, but the workflow's own `uses: …@main` for the composite actions 404s because those files aren't on main yet. The test_action-refs.bats tests will go red while this commit is on the branch (their bump script can't rewrite non-main / non-semver refs). They'll go green again once flipped back to @main pre-merge. NOT FOR MERGE in its current state — needs a follow-up commit to revert to @main before this PR can land. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/universal-codecharta.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/universal-codecharta.yml b/.github/workflows/universal-codecharta.yml index 07847468..f8caee0f 100644 --- a/.github/workflows/universal-codecharta.yml +++ b/.github/workflows/universal-codecharta.yml @@ -208,7 +208,7 @@ jobs: # These outputs are consumed by the publish steps below. - name: Run CodeCharta snapshot id: snapshot - uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@main + uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@ci/codecharta-actions with: mode: snapshot project_name: ${{ steps.project.outputs.name }} @@ -287,7 +287,7 @@ jobs: # concurrent writers. - name: Publish (preview) if: steps.ids.outputs.kind == 'preview' - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions with: mode: preview project_name: ${{ steps.project.outputs.name }} @@ -303,7 +303,7 @@ jobs: # so the picker can offer a delta view against current develop. - name: Publish (history) if: steps.ids.outputs.kind == 'history' - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions with: mode: history project_name: ${{ steps.project.outputs.name }} @@ -413,7 +413,7 @@ jobs: # data repo, rebuilds manifest, commits ("Drop preview for PR # #"), pushes. Idempotent — no-op if the preview was never created. - name: Delete preview - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions with: mode: delete-preview project_name: ${{ steps.project.outputs.name }} @@ -482,7 +482,7 @@ jobs: # deliberate trade-off. - name: Generate historical snapshots id: history - uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@main + uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@ci/codecharta-actions env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -508,7 +508,7 @@ jobs: # # Replaces the previous loop-of-publishes which did N clones / N pushes. - name: Publish backfill batch - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions with: mode: bulk-history project_name: ${{ steps.project.outputs.name }} @@ -523,7 +523,7 @@ jobs: # commit from bulk-history so the history entries and the pointer # refresh are easy to revert independently if something goes wrong. - name: Refresh latest pointer - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions with: mode: set-latest project_name: ${{ steps.project.outputs.name }} From c5f57395b8a30da84a576dee02bf3679ced15421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Potoc=CC=8Cek?= Date: Mon, 11 May 2026 15:17:22 +0200 Subject: [PATCH 07/22] Restore internal refs to @main now that smoke test is green Pre-merge smoke test passed (smsticket-ios run 25672470737 produced the expected data-repo commit and PR comment). Flipping the internal composite-action refs back to @main so the test_action-refs.bats tests pass and PR is mergeable. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/universal-codecharta.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/universal-codecharta.yml b/.github/workflows/universal-codecharta.yml index f8caee0f..07847468 100644 --- a/.github/workflows/universal-codecharta.yml +++ b/.github/workflows/universal-codecharta.yml @@ -208,7 +208,7 @@ jobs: # These outputs are consumed by the publish steps below. - name: Run CodeCharta snapshot id: snapshot - uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@main with: mode: snapshot project_name: ${{ steps.project.outputs.name }} @@ -287,7 +287,7 @@ jobs: # concurrent writers. - name: Publish (preview) if: steps.ids.outputs.kind == 'preview' - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main with: mode: preview project_name: ${{ steps.project.outputs.name }} @@ -303,7 +303,7 @@ jobs: # so the picker can offer a delta view against current develop. - name: Publish (history) if: steps.ids.outputs.kind == 'history' - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main with: mode: history project_name: ${{ steps.project.outputs.name }} @@ -413,7 +413,7 @@ jobs: # data repo, rebuilds manifest, commits ("Drop preview for PR # #"), pushes. Idempotent — no-op if the preview was never created. - name: Delete preview - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main with: mode: delete-preview project_name: ${{ steps.project.outputs.name }} @@ -482,7 +482,7 @@ jobs: # deliberate trade-off. - name: Generate historical snapshots id: history - uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@main env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -508,7 +508,7 @@ jobs: # # Replaces the previous loop-of-publishes which did N clones / N pushes. - name: Publish backfill batch - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main with: mode: bulk-history project_name: ${{ steps.project.outputs.name }} @@ -523,7 +523,7 @@ jobs: # commit from bulk-history so the history entries and the pointer # refresh are easy to revert independently if something goes wrong. - name: Refresh latest pointer - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main with: mode: set-latest project_name: ${{ steps.project.outputs.name }} From fe20c4101580c688fece58bebf41c0fb38b21337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Potoc=CC=8Cek?= Date: Mon, 11 May 2026 15:24:04 +0200 Subject: [PATCH 08/22] Flip internal refs to @ci/codecharta-actions for second smoke test smsticket-android consumer about to open a PR; needs the actions resolvable against the branch since they don't exist on main yet. Action-refs bats tests will go red until flipped back to @main (post smoke-test, pre-merge). Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/universal-codecharta.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/universal-codecharta.yml b/.github/workflows/universal-codecharta.yml index 07847468..f8caee0f 100644 --- a/.github/workflows/universal-codecharta.yml +++ b/.github/workflows/universal-codecharta.yml @@ -208,7 +208,7 @@ jobs: # These outputs are consumed by the publish steps below. - name: Run CodeCharta snapshot id: snapshot - uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@main + uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@ci/codecharta-actions with: mode: snapshot project_name: ${{ steps.project.outputs.name }} @@ -287,7 +287,7 @@ jobs: # concurrent writers. - name: Publish (preview) if: steps.ids.outputs.kind == 'preview' - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions with: mode: preview project_name: ${{ steps.project.outputs.name }} @@ -303,7 +303,7 @@ jobs: # so the picker can offer a delta view against current develop. - name: Publish (history) if: steps.ids.outputs.kind == 'history' - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions with: mode: history project_name: ${{ steps.project.outputs.name }} @@ -413,7 +413,7 @@ jobs: # data repo, rebuilds manifest, commits ("Drop preview for PR # #"), pushes. Idempotent — no-op if the preview was never created. - name: Delete preview - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions with: mode: delete-preview project_name: ${{ steps.project.outputs.name }} @@ -482,7 +482,7 @@ jobs: # deliberate trade-off. - name: Generate historical snapshots id: history - uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@main + uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@ci/codecharta-actions env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -508,7 +508,7 @@ jobs: # # Replaces the previous loop-of-publishes which did N clones / N pushes. - name: Publish backfill batch - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions with: mode: bulk-history project_name: ${{ steps.project.outputs.name }} @@ -523,7 +523,7 @@ jobs: # commit from bulk-history so the history entries and the pointer # refresh are easy to revert independently if something goes wrong. - name: Refresh latest pointer - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@main + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions with: mode: set-latest project_name: ${{ steps.project.outputs.name }} From feb2391009e572c083b1ef29cba22b17f3a3d07a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Potoc=CC=8Cek?= Date: Mon, 11 May 2026 16:07:28 +0200 Subject: [PATCH 09/22] Address Copilot review feedback on PR #105 Eight fixes from the Copilot review: 1. Fix double URL-encoding in PR comment links. Inner API URLs no longer encode the path, so the outer @uri wrapping is the only encoding step. Picker links should now decode correctly. 2. Anchor the push-event PR-number regex to "^Merge pull request #NNN". Previously any commit subject containing "#NNN" (e.g. "Fix #123") would be misclassified as a PR merge. 3. Replace the retry-on-conflict's git pull --rebase with a hard reset + reapply pattern. Under `set -e`, a rebase conflict on manifest.json aborted the script; the new flow tolerates any concurrent change because apply_change is idempotent. 4. Refresh latest.cg.json / latest.meta.json correctly. set-latest now `rm -f`s the existing latest sidecar when the newest entry lacks one, instead of leaving a stale pointer to an older entry. 5. Switch git log field separator from `|` to ASCII unit separator (\x1f). Pipe is legal in commit subjects and would split records incorrectly when present. 6. Skip the DependaCharta image pull in history/backfill mode. That mode doesn't run DependaCharta; the pull was wasted bandwidth. 7. Remove dead helper functions (ccsh_fe_arg etc.) from codecharta.sh. The real arg-building logic lives inside the Docker bash -lc heredocs; the host-level helpers were never called. 8. Update action.yml input descriptions referencing the renamed `backfill-history` mode (now `bulk-history`). Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/actions/ios-kmp-build/action.yml | 4 +- .../universal-codecharta-publish/action.yml | 14 ++--- .../codecharta-publish.sh | 34 +++++++++--- .../universal-codecharta-snapshot/action.yml | 4 ++ .../codecharta.sh | 52 ++++--------------- .github/workflows/android-cloud-check.yml | 4 +- ...droid-cloud-generate-baseline-profiles.yml | 4 +- .../workflows/android-cloud-nightly-build.yml | 8 +-- ...-cloud-release-firebaseAppDistribution.yml | 4 +- .../android-cloud-release-googlePlay.yml | 4 +- .../workflows/ios-kmp-selfhosted-build.yml | 4 +- .../workflows/ios-kmp-selfhosted-release.yml | 4 +- .github/workflows/ios-kmp-selfhosted-test.yml | 2 +- .github/workflows/ios-selfhosted-build.yml | 2 +- .../ios-selfhosted-nightly-build.yml | 8 +-- .../ios-selfhosted-on-demand-build.yml | 6 +-- .github/workflows/ios-selfhosted-release.yml | 4 +- .github/workflows/ios-selfhosted-test.yml | 2 +- .../workflows/kmp-cloud-detect-changes.yml | 4 +- .../workflows/kmp-combined-nightly-build.yml | 12 ++--- .github/workflows/universal-codecharta.yml | 20 +++++-- 21 files changed, 101 insertions(+), 99 deletions(-) diff --git a/.github/actions/ios-kmp-build/action.yml b/.github/actions/ios-kmp-build/action.yml index ae400623..54e28747 100644 --- a/.github/actions/ios-kmp-build/action.yml +++ b/.github/actions/ios-kmp-build/action.yml @@ -48,7 +48,7 @@ runs: steps: - name: Export secrets to .xcconfig file if: ${{ inputs.secret_xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 + uses: futuredapp/.github/.github/actions/ios-export-secrets@main with: XCCONFIG_PATH: ${{ inputs.secret_xcconfig_path }} SECRET_PROPERTIES: ${{ inputs.secret_properties }} @@ -63,7 +63,7 @@ runs: cd ${{ inputs.kmp_swift_package_path }} make build - name: Fastlane Beta - uses: futuredapp/.github/.github/actions/ios-fastlane-beta@2.3.3 + uses: futuredapp/.github/.github/actions/ios-fastlane-beta@main with: match_password: ${{ inputs.match_password }} testflight_changelog: ${{ inputs.testflight_changelog }} diff --git a/.github/actions/universal-codecharta-publish/action.yml b/.github/actions/universal-codecharta-publish/action.yml index 162873d5..f859053b 100644 --- a/.github/actions/universal-codecharta-publish/action.yml +++ b/.github/actions/universal-codecharta-publish/action.yml @@ -42,23 +42,25 @@ inputs: required: false description: > Snapshot stem, either `pr-` or `merge-`. Required for - `history`, `backfill-history`, `set-latest`. + `history`. Not used by `bulk-history` (which discovers stems from the + snapshot filenames in `snapshots_dir`). date: required: false description: > - Snapshot date as `YYYY-MM-DD`. Required for `history`, `backfill-history`. - Optional for `set-latest` (will pick newest entry if omitted). + Snapshot date as `YYYY-MM-DD`. Required for `history`. Not used by + `bulk-history` or `set-latest`. cc_path: required: false description: > Absolute path to the local `.cc.json.gz` to publish. Required for - `preview`, `history`, `backfill-history`. + `preview` and `history`. Not used by `bulk-history` (paths come from + `snapshots_dir`). cg_path: required: false description: > Absolute path to the local `.cg.json` to publish. Required for `preview` - and `history`. Optional for `backfill-history` (history snapshots from - `mode: history` of the snapshot action do not include `.cg.json`). + and `history`. Bulk-history snapshots don't include `.cg.json` so this + isn't applicable there. meta_path: required: false description: > diff --git a/.github/actions/universal-codecharta-publish/codecharta-publish.sh b/.github/actions/universal-codecharta-publish/codecharta-publish.sh index e0a5b35b..8ec0791c 100755 --- a/.github/actions/universal-codecharta-publish/codecharta-publish.sh +++ b/.github/actions/universal-codecharta-publish/codecharta-publish.sh @@ -166,11 +166,19 @@ apply_change() { fi local base="${newest%.cc.json.gz}" cp "${PROJECT_DIR}/history/${newest}" "${PROJECT_DIR}/history/latest.cc.json.gz" + # Mirror the cg/meta sidecars onto the latest pointer — but DELETE + # the existing latest.* if the new newest entry has no sidecar. + # Otherwise the latest.cg.json could refer to a different (older) + # entry than latest.cc.json.gz. if [[ -f "${PROJECT_DIR}/history/${base}.cg.json" ]]; then cp "${PROJECT_DIR}/history/${base}.cg.json" "${PROJECT_DIR}/history/latest.cg.json" + else + rm -f "${PROJECT_DIR}/history/latest.cg.json" fi if [[ -f "${PROJECT_DIR}/history/${base}.meta.json" ]]; then cp "${PROJECT_DIR}/history/${base}.meta.json" "${PROJECT_DIR}/history/latest.meta.json" + else + rm -f "${PROJECT_DIR}/history/latest.meta.json" fi ;; esac @@ -210,23 +218,33 @@ esac git commit -m "${COMMIT_MSG}" -# Retry-on-conflict push: pull-rebase, regenerate manifest from filesystem -# state (deterministic), and try again. Up to 5 attempts with linear backoff. +# Retry-on-conflict push: instead of `git pull --rebase` (which can fail with +# a merge conflict on manifest.json under concurrent writers and abort the +# script via `set -e`), reset hard to remote HEAD and re-apply our changes. +# This works because: +# - apply_change is idempotent on each mode (history/bulk-history skip +# existing entries; preview overwrites; delete-preview is `rm -f`). +# - manifest.json is rebuilt deterministically from the filesystem. +# Up to 5 attempts with linear backoff. attempt=1 while (( attempt <= 5 )); do if git push origin "${DATA_REPO_BRANCH}"; then echo "Pushed on attempt ${attempt}." exit 0 fi - echo "Push rejected (attempt ${attempt}); rebasing and retrying…" - git pull --rebase origin "${DATA_REPO_BRANCH}" + echo "Push rejected (attempt ${attempt}); resetting to remote and reapplying…" + git fetch origin "${DATA_REPO_BRANCH}" + git reset --hard "origin/${DATA_REPO_BRANCH}" + apply_change if [[ -f scripts/build-manifest.mjs ]]; then node scripts/build-manifest.mjs - if ! git diff --quiet manifest.json; then - git add manifest.json - git commit --amend --no-edit - fi fi + if git diff --quiet && git diff --staged --quiet; then + echo "After reapply, nothing to commit — concurrent writer already covered our changes." + exit 0 + fi + git add . + git commit -m "${COMMIT_MSG}" sleep $(( attempt * 3 )) (( attempt++ )) done diff --git a/.github/actions/universal-codecharta-snapshot/action.yml b/.github/actions/universal-codecharta-snapshot/action.yml index 6ab4760f..6a68f8b5 100644 --- a/.github/actions/universal-codecharta-snapshot/action.yml +++ b/.github/actions/universal-codecharta-snapshot/action.yml @@ -84,6 +84,10 @@ runs: using: composite steps: - name: Pull DependaCharta image + # History mode doesn't run DependaCharta (it walks merge commits via + # ccsh unifiedparser --commit only). Skip the pull to avoid 100+ MB of + # wasted network traffic on backfill runs. + if: inputs.mode == 'snapshot' shell: bash run: docker pull "${{ inputs.dependacharta_image }}" diff --git a/.github/actions/universal-codecharta-snapshot/codecharta.sh b/.github/actions/universal-codecharta-snapshot/codecharta.sh index b335dd8d..0bdad463 100755 --- a/.github/actions/universal-codecharta-snapshot/codecharta.sh +++ b/.github/actions/universal-codecharta-snapshot/codecharta.sh @@ -68,46 +68,10 @@ FILE_EXTENSIONS="${CODECHARTA_FILE_EXTENSIONS:-${STACK_FE}}" TOKEI_TYPES="${CODECHARTA_TOKEI_TYPES:-${STACK_TOKEI}}" EXTRA_EXCLUDES="${CODECHARTA_EXTRA_EXCLUDES:-}" -# ── ccsh helper invocations ────────────────────────────────────────────────── - -# Build the `-fe=...` argument when FILE_EXTENSIONS is non-empty. Emit nothing -# when empty (multi mode) so ccsh uses its built-in default. -ccsh_fe_arg() { - if [[ -n "${FILE_EXTENSIONS}" ]]; then - printf -- "-fe=%s" "${FILE_EXTENSIONS}" - fi -} - -# Build the `-e=...` argument list from EXTRA_EXCLUDES (comma-separated regex -# patterns). Emit nothing when empty. -ccsh_extra_excludes_args() { - if [[ -z "${EXTRA_EXCLUDES}" ]]; then - return - fi - local IFS=',' - for pattern in ${EXTRA_EXCLUDES}; do - printf -- "-e=%s\n" "${pattern}" - done -} - -# Build the `--type ...` argument when TOKEI_TYPES is non-empty. -tokei_type_args() { - if [[ -n "${TOKEI_TYPES}" ]]; then - printf -- "--type\n%s\n" "${TOKEI_TYPES}" - fi -} - -# Build the `--exclude pattern` argument list from EXTRA_EXCLUDES. Tokei uses -# globs/paths, not regex; we pass patterns verbatim and let tokei interpret. -tokei_extra_excludes_args() { - if [[ -z "${EXTRA_EXCLUDES}" ]]; then - return - fi - local IFS=',' - for pattern in ${EXTRA_EXCLUDES}; do - printf -- "--exclude\n%s\n" "${pattern}" - done -} +# Note: the ccsh `-fe=…` flag, tokei `--type` flag, and exclude lists are +# re-derived inside the Docker `bash -lc` heredocs below because the host +# environment doesn't transit naturally into the container. The env vars above +# are passed in via `-e CC_*=…`; the inner bash builds the array forms from them. # ── Dependency metric descriptors ──────────────────────────────────────────── @@ -374,7 +338,11 @@ history() { fi generated=0 - log_args=(--first-parent --merges "--format=%H|%cs|%s") + # Use ASCII unit separator (\x1f) as field delimiter rather than `|` + # — pipe characters are legal in commit subjects and would otherwise + # split a single record across multiple fields. + sep=$'\x1f' + log_args=(--first-parent --merges "--format=%H${sep}%cs${sep}%s") if [[ -n "${CC_PULL_REQUEST_LIMIT}" ]]; then log_args=(-n "${CC_PULL_REQUEST_LIMIT}" "${log_args[@]}") fi @@ -387,7 +355,7 @@ history() { fi for merge_commit in "${merge_commits[@]}"; do - IFS="|" read -r sha snapshot_date subject <<< "${merge_commit}" + IFS="${sep}" read -r sha snapshot_date subject <<< "${merge_commit}" short_sha="${sha:0:7}" if [[ "${subject}" =~ [Pp]ull[[:space:]]+[Rr]equest[[:space:]]+\#([0-9]+) ]]; then diff --git a/.github/workflows/android-cloud-check.yml b/.github/workflows/android-cloud-check.yml index 7ad86449..28c9f6c8 100644 --- a/.github/workflows/android-cloud-check.yml +++ b/.github/workflows/android-cloud-check.yml @@ -61,13 +61,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Run checks - uses: futuredapp/.github/.github/actions/android-check@2.3.3 + uses: futuredapp/.github/.github/actions/android-check@main with: lint_gradle_task: ${{ inputs.LINT_GRADLE_TASKS }} test_gradle_task: ${{ inputs.TEST_GRADLE_TASKS }} diff --git a/.github/workflows/android-cloud-generate-baseline-profiles.yml b/.github/workflows/android-cloud-generate-baseline-profiles.yml index 41bfa92b..39ae2cac 100644 --- a/.github/workflows/android-cloud-generate-baseline-profiles.yml +++ b/.github/workflows/android-cloud-generate-baseline-profiles.yml @@ -75,13 +75,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Generate baseline profiles - uses: futuredapp/.github/.github/actions/android-generate-baseline-profiles@2.3.3 + uses: futuredapp/.github/.github/actions/android-generate-baseline-profiles@main with: generate_gradle_task: ${{ inputs.TASK_NAME }} signing_keystore_password: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }} diff --git a/.github/workflows/android-cloud-nightly-build.yml b/.github/workflows/android-cloud-nightly-build.yml index 505d8b14..83318a41 100644 --- a/.github/workflows/android-cloud-nightly-build.yml +++ b/.github/workflows/android-cloud-nightly-build.yml @@ -113,7 +113,7 @@ jobs: steps: - name: Detect changes and generate changelog id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.3.3 + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main with: checkout_depth: ${{ inputs.CHANGELOG_CHECKOUT_DEPTH }} debug: ${{ inputs.CHANGELOG_DEBUG }} @@ -132,13 +132,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Build and upload to App Distribution - uses: futuredapp/.github/.github/actions/android-build-firebase@2.3.3 + uses: futuredapp/.github/.github/actions/android-build-firebase@main with: test_gradle_task: ${{ inputs.TEST_GRADLE_TASKS }} package_gradle_task: ${{ inputs.PACKAGE_GRADLE_TASK }} @@ -185,7 +185,7 @@ jobs: fi - name: Transition JIRA tickets if: steps.check.outputs.enabled == 'true' - uses: futuredapp/.github/.github/actions/jira-transition-tickets@2.3.3 + uses: futuredapp/.github/.github/actions/jira-transition-tickets@main with: jira_context: ${{ secrets.JIRA_CONTEXT }} transition: ${{ inputs.JIRA_TRANSITION }} diff --git a/.github/workflows/android-cloud-release-firebaseAppDistribution.yml b/.github/workflows/android-cloud-release-firebaseAppDistribution.yml index 896fd680..dedba2a0 100644 --- a/.github/workflows/android-cloud-release-firebaseAppDistribution.yml +++ b/.github/workflows/android-cloud-release-firebaseAppDistribution.yml @@ -96,13 +96,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Build and upload to App Distribution - uses: futuredapp/.github/.github/actions/android-build-firebase@2.3.3 + uses: futuredapp/.github/.github/actions/android-build-firebase@main with: test_gradle_task: ${{ inputs.TEST_GRADLE_TASKS }} package_gradle_task: ${{ inputs.PACKAGE_GRADLE_TASK }} diff --git a/.github/workflows/android-cloud-release-googlePlay.yml b/.github/workflows/android-cloud-release-googlePlay.yml index 19556e7d..c8dd78f3 100644 --- a/.github/workflows/android-cloud-release-googlePlay.yml +++ b/.github/workflows/android-cloud-release-googlePlay.yml @@ -108,12 +108,12 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} - name: Build and release - uses: futuredapp/.github/.github/actions/android-build-googlePlay@2.3.3 + uses: futuredapp/.github/.github/actions/android-build-googlePlay@main with: bundle_gradle_task: ${{ inputs.BUNDLE_GRADLE_TASK }} version_name: ${{ inputs.VERSION_NAME }} diff --git a/.github/workflows/ios-kmp-selfhosted-build.yml b/.github/workflows/ios-kmp-selfhosted-build.yml index 3c7be03b..3802aa22 100644 --- a/.github/workflows/ios-kmp-selfhosted-build.yml +++ b/.github/workflows/ios-kmp-selfhosted-build.yml @@ -97,14 +97,14 @@ jobs: with: lfs: ${{ inputs.use_git_lfs }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.java_version }} java_distribution: ${{ inputs.java_distribution }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} ruby: 'false' - name: Build and upload to TestFlight - uses: futuredapp/.github/.github/actions/ios-kmp-build@2.3.3 + uses: futuredapp/.github/.github/actions/ios-kmp-build@main with: match_password: ${{ secrets.MATCH_PASSWORD }} app_store_connect_api_key_key: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY }} diff --git a/.github/workflows/ios-kmp-selfhosted-release.yml b/.github/workflows/ios-kmp-selfhosted-release.yml index c703cb95..832d546a 100644 --- a/.github/workflows/ios-kmp-selfhosted-release.yml +++ b/.github/workflows/ios-kmp-selfhosted-release.yml @@ -86,7 +86,7 @@ jobs: lfs: ${{ inputs.use_git_lfs }} - name: Export secrets to .xcconfig file if: ${{ inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 + uses: futuredapp/.github/.github/actions/ios-export-secrets@main with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} @@ -109,7 +109,7 @@ jobs: cd ${{ inputs.kmp_swift_package_path }} make build - name: Fastlane Release - uses: futuredapp/.github/.github/actions/ios-fastlane-release@2.3.3 + uses: futuredapp/.github/.github/actions/ios-fastlane-release@main with: match_password: ${{ secrets.MATCH_PASSWORD }} version_number: ${{ github.ref_name }} diff --git a/.github/workflows/ios-kmp-selfhosted-test.yml b/.github/workflows/ios-kmp-selfhosted-test.yml index 1954a1e8..8794b1c3 100644 --- a/.github/workflows/ios-kmp-selfhosted-test.yml +++ b/.github/workflows/ios-kmp-selfhosted-test.yml @@ -83,7 +83,7 @@ jobs: cd ${{ inputs.kmp_swift_package_path }} make build - name: Fastlane Test - uses: futuredapp/.github/.github/actions/ios-fastlane-test@2.3.3 + uses: futuredapp/.github/.github/actions/ios-fastlane-test@main with: github_token: ${{ secrets.GITHUB_TOKEN_DANGER }} custom_values: ${{ inputs.custom_values }} diff --git a/.github/workflows/ios-selfhosted-build.yml b/.github/workflows/ios-selfhosted-build.yml index 234e1748..43ae49e6 100644 --- a/.github/workflows/ios-selfhosted-build.yml +++ b/.github/workflows/ios-selfhosted-build.yml @@ -63,7 +63,7 @@ jobs: echo "::warning::This workflow ('ios-selfhosted-build.yml') is deprecated and will be removed in the future." echo "::warning::Please use 'ios-selfhosted-nightly-build.yml' instead." build: - uses: futuredapp/.github/.github/workflows/ios-selfhosted-nightly-build.yml@2.3.3 + uses: futuredapp/.github/.github/workflows/ios-selfhosted-nightly-build.yml@main with: use_git_lfs: ${{ inputs.use_git_lfs }} custom_values: ${{ inputs.custom_values }} diff --git a/.github/workflows/ios-selfhosted-nightly-build.yml b/.github/workflows/ios-selfhosted-nightly-build.yml index 3835ee9c..3f39355a 100644 --- a/.github/workflows/ios-selfhosted-nightly-build.yml +++ b/.github/workflows/ios-selfhosted-nightly-build.yml @@ -80,7 +80,7 @@ jobs: steps: - name: Detect changes and generate changelog id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.3.3 + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main with: checkout_depth: ${{ inputs.checkout_depth }} fallback_lookback: ${{ inputs.changelog_fallback_lookback }} @@ -94,7 +94,7 @@ jobs: - name: Export secrets if: ${{ steps.detect_changes.outputs.skip_build == 'false' && inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 + uses: futuredapp/.github/.github/actions/ios-export-secrets@main with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} @@ -102,7 +102,7 @@ jobs: - name: Fastlane Beta if: ${{ steps.detect_changes.outputs.skip_build == 'false' }} - uses: futuredapp/.github/.github/actions/ios-fastlane-beta@2.3.3 + uses: futuredapp/.github/.github/actions/ios-fastlane-beta@main with: match_password: ${{ secrets.MATCH_PASSWORD }} testflight_changelog: ${{ steps.set_changelog.outputs.changelog }} @@ -158,7 +158,7 @@ jobs: fi - name: Transition JIRA tickets if: steps.check.outputs.enabled == 'true' - uses: futuredapp/.github/.github/actions/jira-transition-tickets@2.3.3 + uses: futuredapp/.github/.github/actions/jira-transition-tickets@main with: jira_context: ${{ secrets.JIRA_CONTEXT }} transition: ${{ inputs.jira_transition }} diff --git a/.github/workflows/ios-selfhosted-on-demand-build.yml b/.github/workflows/ios-selfhosted-on-demand-build.yml index 78e61466..5f992af4 100644 --- a/.github/workflows/ios-selfhosted-on-demand-build.yml +++ b/.github/workflows/ios-selfhosted-on-demand-build.yml @@ -81,7 +81,7 @@ jobs: - name: Generate changelog if not provided if: ${{ inputs.changelog == '' }} id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.3.3 + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main with: checkout_depth: ${{ inputs.checkout_depth }} fallback_lookback: ${{ inputs.changelog_fallback_lookback }} @@ -104,14 +104,14 @@ jobs: - name: Export secrets if: ${{ inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 + uses: futuredapp/.github/.github/actions/ios-export-secrets@main with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} REQUIRED_KEYS: ${{ inputs.required_keys }} - name: Fastlane Beta - uses: futuredapp/.github/.github/actions/ios-fastlane-beta@2.3.3 + uses: futuredapp/.github/.github/actions/ios-fastlane-beta@main with: MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} testflight_changelog: ${{ steps.set_changelog.outputs.changelog }} diff --git a/.github/workflows/ios-selfhosted-release.yml b/.github/workflows/ios-selfhosted-release.yml index 18d7f9dc..4f38a95c 100644 --- a/.github/workflows/ios-selfhosted-release.yml +++ b/.github/workflows/ios-selfhosted-release.yml @@ -62,14 +62,14 @@ jobs: - name: Export secrets if: ${{ inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 + uses: futuredapp/.github/.github/actions/ios-export-secrets@main with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} REQUIRED_KEYS: ${{ inputs.required_keys }} - name: Fastlane Release - uses: futuredapp/.github/.github/actions/ios-fastlane-release@2.3.3 + uses: futuredapp/.github/.github/actions/ios-fastlane-release@main with: match_password: ${{ secrets.MATCH_PASSWORD }} version_number: ${{ github.ref_name }} diff --git a/.github/workflows/ios-selfhosted-test.yml b/.github/workflows/ios-selfhosted-test.yml index 7d08307f..4f06ba20 100644 --- a/.github/workflows/ios-selfhosted-test.yml +++ b/.github/workflows/ios-selfhosted-test.yml @@ -41,7 +41,7 @@ jobs: lfs: ${{ inputs.use_git_lfs }} - name: Fastlane Test - uses: futuredapp/.github/.github/actions/ios-fastlane-test@2.3.3 + uses: futuredapp/.github/.github/actions/ios-fastlane-test@main with: github_token: ${{ secrets.GITHUB_TOKEN_DANGER }} custom_values: ${{ inputs.custom_values }} \ No newline at end of file diff --git a/.github/workflows/kmp-cloud-detect-changes.yml b/.github/workflows/kmp-cloud-detect-changes.yml index b9c0b4c0..50caa63d 100644 --- a/.github/workflows/kmp-cloud-detect-changes.yml +++ b/.github/workflows/kmp-cloud-detect-changes.yml @@ -28,7 +28,7 @@ name: Detect Changes # # Note: For direct action usage in other workflows, you can also use: # - name: Detect Changes -# uses: futuredapp/.github/.github/actions/kmp-detect-changes@2.3.3 +# uses: futuredapp/.github/.github/actions/kmp-detect-changes@main on: workflow_call: @@ -58,6 +58,6 @@ jobs: steps: - name: Detect Changes id: detect - uses: futuredapp/.github/.github/actions/kmp-detect-changes@2.3.3 + uses: futuredapp/.github/.github/actions/kmp-detect-changes@main with: USE_GIT_LFS: ${{ inputs.USE_GIT_LFS }} diff --git a/.github/workflows/kmp-combined-nightly-build.yml b/.github/workflows/kmp-combined-nightly-build.yml index ef4b5842..1d85884a 100644 --- a/.github/workflows/kmp-combined-nightly-build.yml +++ b/.github/workflows/kmp-combined-nightly-build.yml @@ -152,7 +152,7 @@ jobs: steps: - name: Detect changes and generate changelog id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.3.3 + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main with: checkout_depth: ${{ inputs.CHANGELOG_CHECKOUT_DEPTH }} debug: ${{ inputs.CHANGELOG_DEBUG }} @@ -171,14 +171,14 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} ruby: 'false' - name: Build and upload to TestFlight - uses: futuredapp/.github/.github/actions/ios-kmp-build@2.3.3 + uses: futuredapp/.github/.github/actions/ios-kmp-build@main with: match_password: ${{ secrets.IOS_MATCH_PASSWORD }} app_store_connect_api_key_key: ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_KEY }} @@ -208,13 +208,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Build and upload to App Distribution - uses: futuredapp/.github/.github/actions/android-build-firebase@2.3.3 + uses: futuredapp/.github/.github/actions/android-build-firebase@main with: test_gradle_task: ${{ inputs.ANDROID_TEST_GRADLE_TASK }} package_gradle_task: ${{ inputs.ANDROID_PACKAGE_GRADLE_TASK }} @@ -270,7 +270,7 @@ jobs: fi - name: Transition JIRA tickets if: steps.check.outputs.enabled == 'true' - uses: futuredapp/.github/.github/actions/jira-transition-tickets@2.3.3 + uses: futuredapp/.github/.github/actions/jira-transition-tickets@main with: jira_context: ${{ secrets.JIRA_CONTEXT }} transition: ${{ inputs.JIRA_TRANSITION }} diff --git a/.github/workflows/universal-codecharta.yml b/.github/workflows/universal-codecharta.yml index f8caee0f..8d8b4b7e 100644 --- a/.github/workflows/universal-codecharta.yml +++ b/.github/workflows/universal-codecharta.yml @@ -238,11 +238,14 @@ jobs: echo "pr=${{ github.event.pull_request.number }}" } >> "$GITHUB_OUTPUT" else - # push to develop/main — parse merge commit subject for "#NNN". + # push to develop/main — parse merge commit subject for the actual + # GitHub-generated "Merge pull request #NNN from …" format. A + # broader `#NNN` match would mis-classify commits that merely + # mention an issue (e.g. "Fix #123") as if they were PR merges. subject=$(git log -1 --format=%s HEAD) pr="" stem="" - if [[ "${subject}" =~ \#([0-9]+) ]]; then + if [[ "${subject}" =~ ^Merge\ pull\ request\ \#([0-9]+) ]]; then pr="${BASH_REMATCH[1]}" stem="pr-${pr}" else @@ -335,9 +338,16 @@ jobs: cgPath="projects/${name}/previews/pr-${pr}.cg.json" latestPath="projects/${name}/history/latest.cc.json.gz" - ccUrl=$(jq -rn --arg p "${ccPath}" '@uri "/api/map?path=\($p)"') - cgUrl=$(jq -rn --arg p "${cgPath}" '@uri "/api/graph?path=\($p)"') - latestUrl=$(jq -rn --arg p "${latestPath}" '@uri "/api/map?path=\($p)"') + # Build the inner API URLs without encoding the path — our paths + # are alphanumeric + `-` + `.` + `/` which are all URL-safe in a + # query parameter value. Then encode each whole API URL exactly + # once when wrapping it into the studio/visualizer `file=`/`graph=` + # parameter. (Previously the path was encoded inside the API URL + # AND the whole URL was encoded again, producing `%252F` literals + # that broke the picker's path lookup.) + ccUrl="/api/map?path=${ccPath}" + cgUrl="/api/graph?path=${cgPath}" + latestUrl="/api/map?path=${latestPath}" ccStudio="${base}/studio/?file=$(jq -rn --arg u "${ccUrl}" '$u | @uri')" cgVis="${base}/dependacharta/?graph=$(jq -rn --arg u "${cgUrl}" '$u | @uri')" From 5fd2f1bd8f20617390777c4de93d2ff628a80392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Potoc=CC=8Cek?= Date: Mon, 11 May 2026 16:07:54 +0200 Subject: [PATCH 10/22] =?UTF-8?q?Revert=20accidental=20@2.3.3=20=E2=86=92?= =?UTF-8?q?=20@main=20flip=20on=20unrelated=20workflows=20(again)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same collateral as commit 3254b03 — the bump-action-refs.sh test cycle I ran locally to validate the previous fixes left the 16 android/ios/kmp workflow refs flipped to @main. Restoring to @2.3.3. I need to stop running bump-action-refs locally inside this branch. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/actions/ios-kmp-build/action.yml | 4 ++-- .github/workflows/android-cloud-check.yml | 4 ++-- .../android-cloud-generate-baseline-profiles.yml | 4 ++-- .github/workflows/android-cloud-nightly-build.yml | 8 ++++---- ...android-cloud-release-firebaseAppDistribution.yml | 4 ++-- .../workflows/android-cloud-release-googlePlay.yml | 4 ++-- .github/workflows/ios-kmp-selfhosted-build.yml | 4 ++-- .github/workflows/ios-kmp-selfhosted-release.yml | 4 ++-- .github/workflows/ios-kmp-selfhosted-test.yml | 2 +- .github/workflows/ios-selfhosted-build.yml | 2 +- .github/workflows/ios-selfhosted-nightly-build.yml | 8 ++++---- .github/workflows/ios-selfhosted-on-demand-build.yml | 6 +++--- .github/workflows/ios-selfhosted-release.yml | 4 ++-- .github/workflows/ios-selfhosted-test.yml | 2 +- .github/workflows/kmp-cloud-detect-changes.yml | 4 ++-- .github/workflows/kmp-combined-nightly-build.yml | 12 ++++++------ 16 files changed, 38 insertions(+), 38 deletions(-) diff --git a/.github/actions/ios-kmp-build/action.yml b/.github/actions/ios-kmp-build/action.yml index 54e28747..ae400623 100644 --- a/.github/actions/ios-kmp-build/action.yml +++ b/.github/actions/ios-kmp-build/action.yml @@ -48,7 +48,7 @@ runs: steps: - name: Export secrets to .xcconfig file if: ${{ inputs.secret_xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@main + uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 with: XCCONFIG_PATH: ${{ inputs.secret_xcconfig_path }} SECRET_PROPERTIES: ${{ inputs.secret_properties }} @@ -63,7 +63,7 @@ runs: cd ${{ inputs.kmp_swift_package_path }} make build - name: Fastlane Beta - uses: futuredapp/.github/.github/actions/ios-fastlane-beta@main + uses: futuredapp/.github/.github/actions/ios-fastlane-beta@2.3.3 with: match_password: ${{ inputs.match_password }} testflight_changelog: ${{ inputs.testflight_changelog }} diff --git a/.github/workflows/android-cloud-check.yml b/.github/workflows/android-cloud-check.yml index 28c9f6c8..7ad86449 100644 --- a/.github/workflows/android-cloud-check.yml +++ b/.github/workflows/android-cloud-check.yml @@ -61,13 +61,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Run checks - uses: futuredapp/.github/.github/actions/android-check@main + uses: futuredapp/.github/.github/actions/android-check@2.3.3 with: lint_gradle_task: ${{ inputs.LINT_GRADLE_TASKS }} test_gradle_task: ${{ inputs.TEST_GRADLE_TASKS }} diff --git a/.github/workflows/android-cloud-generate-baseline-profiles.yml b/.github/workflows/android-cloud-generate-baseline-profiles.yml index 39ae2cac..41bfa92b 100644 --- a/.github/workflows/android-cloud-generate-baseline-profiles.yml +++ b/.github/workflows/android-cloud-generate-baseline-profiles.yml @@ -75,13 +75,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Generate baseline profiles - uses: futuredapp/.github/.github/actions/android-generate-baseline-profiles@main + uses: futuredapp/.github/.github/actions/android-generate-baseline-profiles@2.3.3 with: generate_gradle_task: ${{ inputs.TASK_NAME }} signing_keystore_password: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }} diff --git a/.github/workflows/android-cloud-nightly-build.yml b/.github/workflows/android-cloud-nightly-build.yml index 83318a41..505d8b14 100644 --- a/.github/workflows/android-cloud-nightly-build.yml +++ b/.github/workflows/android-cloud-nightly-build.yml @@ -113,7 +113,7 @@ jobs: steps: - name: Detect changes and generate changelog id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.3.3 with: checkout_depth: ${{ inputs.CHANGELOG_CHECKOUT_DEPTH }} debug: ${{ inputs.CHANGELOG_DEBUG }} @@ -132,13 +132,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Build and upload to App Distribution - uses: futuredapp/.github/.github/actions/android-build-firebase@main + uses: futuredapp/.github/.github/actions/android-build-firebase@2.3.3 with: test_gradle_task: ${{ inputs.TEST_GRADLE_TASKS }} package_gradle_task: ${{ inputs.PACKAGE_GRADLE_TASK }} @@ -185,7 +185,7 @@ jobs: fi - name: Transition JIRA tickets if: steps.check.outputs.enabled == 'true' - uses: futuredapp/.github/.github/actions/jira-transition-tickets@main + uses: futuredapp/.github/.github/actions/jira-transition-tickets@2.3.3 with: jira_context: ${{ secrets.JIRA_CONTEXT }} transition: ${{ inputs.JIRA_TRANSITION }} diff --git a/.github/workflows/android-cloud-release-firebaseAppDistribution.yml b/.github/workflows/android-cloud-release-firebaseAppDistribution.yml index dedba2a0..896fd680 100644 --- a/.github/workflows/android-cloud-release-firebaseAppDistribution.yml +++ b/.github/workflows/android-cloud-release-firebaseAppDistribution.yml @@ -96,13 +96,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Build and upload to App Distribution - uses: futuredapp/.github/.github/actions/android-build-firebase@main + uses: futuredapp/.github/.github/actions/android-build-firebase@2.3.3 with: test_gradle_task: ${{ inputs.TEST_GRADLE_TASKS }} package_gradle_task: ${{ inputs.PACKAGE_GRADLE_TASK }} diff --git a/.github/workflows/android-cloud-release-googlePlay.yml b/.github/workflows/android-cloud-release-googlePlay.yml index c8dd78f3..19556e7d 100644 --- a/.github/workflows/android-cloud-release-googlePlay.yml +++ b/.github/workflows/android-cloud-release-googlePlay.yml @@ -108,12 +108,12 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} - name: Build and release - uses: futuredapp/.github/.github/actions/android-build-googlePlay@main + uses: futuredapp/.github/.github/actions/android-build-googlePlay@2.3.3 with: bundle_gradle_task: ${{ inputs.BUNDLE_GRADLE_TASK }} version_name: ${{ inputs.VERSION_NAME }} diff --git a/.github/workflows/ios-kmp-selfhosted-build.yml b/.github/workflows/ios-kmp-selfhosted-build.yml index 3802aa22..3c7be03b 100644 --- a/.github/workflows/ios-kmp-selfhosted-build.yml +++ b/.github/workflows/ios-kmp-selfhosted-build.yml @@ -97,14 +97,14 @@ jobs: with: lfs: ${{ inputs.use_git_lfs }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 with: java_version: ${{ inputs.java_version }} java_distribution: ${{ inputs.java_distribution }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} ruby: 'false' - name: Build and upload to TestFlight - uses: futuredapp/.github/.github/actions/ios-kmp-build@main + uses: futuredapp/.github/.github/actions/ios-kmp-build@2.3.3 with: match_password: ${{ secrets.MATCH_PASSWORD }} app_store_connect_api_key_key: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY }} diff --git a/.github/workflows/ios-kmp-selfhosted-release.yml b/.github/workflows/ios-kmp-selfhosted-release.yml index 832d546a..c703cb95 100644 --- a/.github/workflows/ios-kmp-selfhosted-release.yml +++ b/.github/workflows/ios-kmp-selfhosted-release.yml @@ -86,7 +86,7 @@ jobs: lfs: ${{ inputs.use_git_lfs }} - name: Export secrets to .xcconfig file if: ${{ inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@main + uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} @@ -109,7 +109,7 @@ jobs: cd ${{ inputs.kmp_swift_package_path }} make build - name: Fastlane Release - uses: futuredapp/.github/.github/actions/ios-fastlane-release@main + uses: futuredapp/.github/.github/actions/ios-fastlane-release@2.3.3 with: match_password: ${{ secrets.MATCH_PASSWORD }} version_number: ${{ github.ref_name }} diff --git a/.github/workflows/ios-kmp-selfhosted-test.yml b/.github/workflows/ios-kmp-selfhosted-test.yml index 8794b1c3..1954a1e8 100644 --- a/.github/workflows/ios-kmp-selfhosted-test.yml +++ b/.github/workflows/ios-kmp-selfhosted-test.yml @@ -83,7 +83,7 @@ jobs: cd ${{ inputs.kmp_swift_package_path }} make build - name: Fastlane Test - uses: futuredapp/.github/.github/actions/ios-fastlane-test@main + uses: futuredapp/.github/.github/actions/ios-fastlane-test@2.3.3 with: github_token: ${{ secrets.GITHUB_TOKEN_DANGER }} custom_values: ${{ inputs.custom_values }} diff --git a/.github/workflows/ios-selfhosted-build.yml b/.github/workflows/ios-selfhosted-build.yml index 43ae49e6..234e1748 100644 --- a/.github/workflows/ios-selfhosted-build.yml +++ b/.github/workflows/ios-selfhosted-build.yml @@ -63,7 +63,7 @@ jobs: echo "::warning::This workflow ('ios-selfhosted-build.yml') is deprecated and will be removed in the future." echo "::warning::Please use 'ios-selfhosted-nightly-build.yml' instead." build: - uses: futuredapp/.github/.github/workflows/ios-selfhosted-nightly-build.yml@main + uses: futuredapp/.github/.github/workflows/ios-selfhosted-nightly-build.yml@2.3.3 with: use_git_lfs: ${{ inputs.use_git_lfs }} custom_values: ${{ inputs.custom_values }} diff --git a/.github/workflows/ios-selfhosted-nightly-build.yml b/.github/workflows/ios-selfhosted-nightly-build.yml index 3f39355a..3835ee9c 100644 --- a/.github/workflows/ios-selfhosted-nightly-build.yml +++ b/.github/workflows/ios-selfhosted-nightly-build.yml @@ -80,7 +80,7 @@ jobs: steps: - name: Detect changes and generate changelog id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.3.3 with: checkout_depth: ${{ inputs.checkout_depth }} fallback_lookback: ${{ inputs.changelog_fallback_lookback }} @@ -94,7 +94,7 @@ jobs: - name: Export secrets if: ${{ steps.detect_changes.outputs.skip_build == 'false' && inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@main + uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} @@ -102,7 +102,7 @@ jobs: - name: Fastlane Beta if: ${{ steps.detect_changes.outputs.skip_build == 'false' }} - uses: futuredapp/.github/.github/actions/ios-fastlane-beta@main + uses: futuredapp/.github/.github/actions/ios-fastlane-beta@2.3.3 with: match_password: ${{ secrets.MATCH_PASSWORD }} testflight_changelog: ${{ steps.set_changelog.outputs.changelog }} @@ -158,7 +158,7 @@ jobs: fi - name: Transition JIRA tickets if: steps.check.outputs.enabled == 'true' - uses: futuredapp/.github/.github/actions/jira-transition-tickets@main + uses: futuredapp/.github/.github/actions/jira-transition-tickets@2.3.3 with: jira_context: ${{ secrets.JIRA_CONTEXT }} transition: ${{ inputs.jira_transition }} diff --git a/.github/workflows/ios-selfhosted-on-demand-build.yml b/.github/workflows/ios-selfhosted-on-demand-build.yml index 5f992af4..78e61466 100644 --- a/.github/workflows/ios-selfhosted-on-demand-build.yml +++ b/.github/workflows/ios-selfhosted-on-demand-build.yml @@ -81,7 +81,7 @@ jobs: - name: Generate changelog if not provided if: ${{ inputs.changelog == '' }} id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.3.3 with: checkout_depth: ${{ inputs.checkout_depth }} fallback_lookback: ${{ inputs.changelog_fallback_lookback }} @@ -104,14 +104,14 @@ jobs: - name: Export secrets if: ${{ inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@main + uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} REQUIRED_KEYS: ${{ inputs.required_keys }} - name: Fastlane Beta - uses: futuredapp/.github/.github/actions/ios-fastlane-beta@main + uses: futuredapp/.github/.github/actions/ios-fastlane-beta@2.3.3 with: MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} testflight_changelog: ${{ steps.set_changelog.outputs.changelog }} diff --git a/.github/workflows/ios-selfhosted-release.yml b/.github/workflows/ios-selfhosted-release.yml index 4f38a95c..18d7f9dc 100644 --- a/.github/workflows/ios-selfhosted-release.yml +++ b/.github/workflows/ios-selfhosted-release.yml @@ -62,14 +62,14 @@ jobs: - name: Export secrets if: ${{ inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@main + uses: futuredapp/.github/.github/actions/ios-export-secrets@2.3.3 with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} REQUIRED_KEYS: ${{ inputs.required_keys }} - name: Fastlane Release - uses: futuredapp/.github/.github/actions/ios-fastlane-release@main + uses: futuredapp/.github/.github/actions/ios-fastlane-release@2.3.3 with: match_password: ${{ secrets.MATCH_PASSWORD }} version_number: ${{ github.ref_name }} diff --git a/.github/workflows/ios-selfhosted-test.yml b/.github/workflows/ios-selfhosted-test.yml index 4f06ba20..7d08307f 100644 --- a/.github/workflows/ios-selfhosted-test.yml +++ b/.github/workflows/ios-selfhosted-test.yml @@ -41,7 +41,7 @@ jobs: lfs: ${{ inputs.use_git_lfs }} - name: Fastlane Test - uses: futuredapp/.github/.github/actions/ios-fastlane-test@main + uses: futuredapp/.github/.github/actions/ios-fastlane-test@2.3.3 with: github_token: ${{ secrets.GITHUB_TOKEN_DANGER }} custom_values: ${{ inputs.custom_values }} \ No newline at end of file diff --git a/.github/workflows/kmp-cloud-detect-changes.yml b/.github/workflows/kmp-cloud-detect-changes.yml index 50caa63d..b9c0b4c0 100644 --- a/.github/workflows/kmp-cloud-detect-changes.yml +++ b/.github/workflows/kmp-cloud-detect-changes.yml @@ -28,7 +28,7 @@ name: Detect Changes # # Note: For direct action usage in other workflows, you can also use: # - name: Detect Changes -# uses: futuredapp/.github/.github/actions/kmp-detect-changes@main +# uses: futuredapp/.github/.github/actions/kmp-detect-changes@2.3.3 on: workflow_call: @@ -58,6 +58,6 @@ jobs: steps: - name: Detect Changes id: detect - uses: futuredapp/.github/.github/actions/kmp-detect-changes@main + uses: futuredapp/.github/.github/actions/kmp-detect-changes@2.3.3 with: USE_GIT_LFS: ${{ inputs.USE_GIT_LFS }} diff --git a/.github/workflows/kmp-combined-nightly-build.yml b/.github/workflows/kmp-combined-nightly-build.yml index 1d85884a..ef4b5842 100644 --- a/.github/workflows/kmp-combined-nightly-build.yml +++ b/.github/workflows/kmp-combined-nightly-build.yml @@ -152,7 +152,7 @@ jobs: steps: - name: Detect changes and generate changelog id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.3.3 with: checkout_depth: ${{ inputs.CHANGELOG_CHECKOUT_DEPTH }} debug: ${{ inputs.CHANGELOG_DEBUG }} @@ -171,14 +171,14 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} ruby: 'false' - name: Build and upload to TestFlight - uses: futuredapp/.github/.github/actions/ios-kmp-build@main + uses: futuredapp/.github/.github/actions/ios-kmp-build@2.3.3 with: match_password: ${{ secrets.IOS_MATCH_PASSWORD }} app_store_connect_api_key_key: ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_KEY }} @@ -208,13 +208,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.3.3 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Build and upload to App Distribution - uses: futuredapp/.github/.github/actions/android-build-firebase@main + uses: futuredapp/.github/.github/actions/android-build-firebase@2.3.3 with: test_gradle_task: ${{ inputs.ANDROID_TEST_GRADLE_TASK }} package_gradle_task: ${{ inputs.ANDROID_PACKAGE_GRADLE_TASK }} @@ -270,7 +270,7 @@ jobs: fi - name: Transition JIRA tickets if: steps.check.outputs.enabled == 'true' - uses: futuredapp/.github/.github/actions/jira-transition-tickets@main + uses: futuredapp/.github/.github/actions/jira-transition-tickets@2.3.3 with: jira_context: ${{ secrets.JIRA_CONTEXT }} transition: ${{ inputs.JIRA_TRANSITION }} From 8bbb277ebf61b198d701d64947c8ac645cee0e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Potoc=CC=8Cek?= Date: Mon, 11 May 2026 17:09:10 +0200 Subject: [PATCH 11/22] Write PR title sidecar on preview path, not only on history MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The picker UI surfaces PR titles by reading `.meta.json` next to each snapshot (codebase-architectures PR #22 contract). Previously we only generated this sidecar for history snapshots (push to develop/ main with a Merge-pull-request commit), so: - History entries: 60/61 sidecars present. - Preview entries: 0/N present. This commit extends the meta-fetch step to run whenever the snapshot is keyed to a PR number — that's every PR preview AND every history merge with a parseable PR number. The publish action's `preview` mode now copies the sidecar alongside `.cc.json.gz` / `.cg.json`, and `delete-preview` cleans it up on PR close. Net effect: every preview gets a `.meta.json` with {title, author, mergedAt, url}; the picker UI now shows the PR title for in-flight PRs instead of falling back to the legacy `PR #N` label. Rebase-merge history entries (stem `merge-`, no PR number) still skip the sidecar — there's nothing to query. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../codecharta-publish.sh | 6 +++++- .github/workflows/universal-codecharta.yml | 15 +++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/actions/universal-codecharta-publish/codecharta-publish.sh b/.github/actions/universal-codecharta-publish/codecharta-publish.sh index 8ec0791c..57923dff 100755 --- a/.github/actions/universal-codecharta-publish/codecharta-publish.sh +++ b/.github/actions/universal-codecharta-publish/codecharta-publish.sh @@ -100,6 +100,9 @@ apply_change() { require_env SOURCE_CG_PATH cp "${SOURCE_CC_PATH}" "${PROJECT_DIR}/previews/pr-${PR_NUMBER}.cc.json.gz" cp "${SOURCE_CG_PATH}" "${PROJECT_DIR}/previews/pr-${PR_NUMBER}.cg.json" + if [[ -n "${SOURCE_META_PATH:-}" && -f "${SOURCE_META_PATH}" ]]; then + cp "${SOURCE_META_PATH}" "${PROJECT_DIR}/previews/pr-${PR_NUMBER}.meta.json" + fi ;; history) require_env SOURCE_CC_PATH @@ -151,7 +154,8 @@ apply_change() { ;; delete-preview) rm -f "${PROJECT_DIR}/previews/pr-${PR_NUMBER}.cc.json.gz" \ - "${PROJECT_DIR}/previews/pr-${PR_NUMBER}.cg.json" + "${PROJECT_DIR}/previews/pr-${PR_NUMBER}.cg.json" \ + "${PROJECT_DIR}/previews/pr-${PR_NUMBER}.meta.json" ;; set-latest) local newest diff --git a/.github/workflows/universal-codecharta.yml b/.github/workflows/universal-codecharta.yml index 8d8b4b7e..d7b573af 100644 --- a/.github/workflows/universal-codecharta.yml +++ b/.github/workflows/universal-codecharta.yml @@ -260,13 +260,15 @@ jobs: } >> "$GITHUB_OUTPUT" fi - # ── Step 6: fetch PR metadata (history runs only) ───────────────────── + # ── Step 6: fetch PR metadata ───────────────────────────────────────── # Writes a sidecar JSON ({title, author, mergedAt, url}) next to the - # snapshot. The picker UI reads this to display PR titles in the - # timeline. Skipped for previews (the PR is the live thing being viewed) - # and for rebase-merge commits with no PR number. - - name: Fetch PR metadata sidecar (history) - if: steps.ids.outputs.kind == 'history' && steps.ids.outputs.pr != '' + # snapshot whenever we have a PR number — that's every preview (PR + # events) and every history entry whose merge commit subject parsed as + # "Merge pull request #NNN". The picker UI reads this to display PR + # titles alongside snapshots. Rebase-merge history entries (stem + # `merge-`) skip this step because there's no PR to query. + - name: Fetch PR metadata sidecar + if: steps.ids.outputs.pr != '' id: meta env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -299,6 +301,7 @@ jobs: pr_number: ${{ steps.ids.outputs.pr }} cc_path: ${{ steps.snapshot.outputs.cc_path }} cg_path: ${{ steps.snapshot.outputs.cg_path }} + meta_path: ${{ steps.meta.outputs.path }} # ── Step 7b: publish a history entry (develop/main pushes only) ─────── # Same as preview but writes to projects//history/-.* From 58be6c27182a94d81723705417e51443b585884f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Potoc=CC=8Cek?= Date: Mon, 11 May 2026 17:34:01 +0200 Subject: [PATCH 12/22] Address second Copilot review: input validation + best-effort fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. PROJECT_NAME path traversal — validate against [A-Za-z0-9._-]+ before using it in path construction. A malicious value like `../foo` could have caused writes outside the projects/ tree (the script uses it unquoted in `cp` and `rm -f`). 2. DATA_REPO unconstrained input — validate as / with the same safe charset. Shell quoting prevented command injection, but bounding the value avoids targeting unintended repos by accident. 3. add_dependency_metric_descriptions used `exit` on missing node — but the caller wraps it in `|| true` to make annotation best-effort. Use `return 0` instead so the action keeps going. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../codecharta-publish.sh | 16 ++++++++++++++++ .../universal-codecharta-snapshot/codecharta.sh | 7 +++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/actions/universal-codecharta-publish/codecharta-publish.sh b/.github/actions/universal-codecharta-publish/codecharta-publish.sh index 57923dff..01b9e5da 100755 --- a/.github/actions/universal-codecharta-publish/codecharta-publish.sh +++ b/.github/actions/universal-codecharta-publish/codecharta-publish.sh @@ -34,6 +34,22 @@ require_env PROJECT_NAME require_env DATA_REPO require_env CODEBASE_ARCHITECTURES_TOKEN +# Validate PROJECT_NAME — used unquoted in path construction (e.g. +# "projects/${PROJECT_NAME}/previews/…"). A value like `../foo` would let the +# script write outside the projects/ tree. Restrict to a safe character set. +if [[ ! "${PROJECT_NAME}" =~ ^[A-Za-z0-9._-]+$ ]]; then + echo "PROJECT_NAME must match [A-Za-z0-9._-]+ (got '${PROJECT_NAME}')" >&2 + exit 64 +fi + +# Validate DATA_REPO — interpolated into the clone URL. Shell-quoting prevents +# command injection, but an unconstrained value can still target unintended +# repositories. Restrict to / with the same safe charset. +if [[ ! "${DATA_REPO}" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]]; then + echo "DATA_REPO must match / (got '${DATA_REPO}')" >&2 + exit 64 +fi + DATA_REPO_BRANCH="${DATA_REPO_BRANCH:-main}" MODE="${CODECHARTA_PUBLISH_MODE}" diff --git a/.github/actions/universal-codecharta-snapshot/codecharta.sh b/.github/actions/universal-codecharta-snapshot/codecharta.sh index 0bdad463..5ef48c88 100755 --- a/.github/actions/universal-codecharta-snapshot/codecharta.sh +++ b/.github/actions/universal-codecharta-snapshot/codecharta.sh @@ -78,9 +78,12 @@ EXTRA_EXCLUDES="${CODECHARTA_EXTRA_EXCLUDES:-}" add_dependency_metric_descriptions() { local map_path="$1" + # The caller invokes this with `|| true` because metric-descriptor + # annotation is a nice-to-have, not a pipeline-critical step. Use `return` + # (not `exit`) on missing node so the rest of the action can continue. if ! command -v node >/dev/null 2>&1; then - echo "node is required to add dependency metric descriptions to ${map_path}" >&2 - exit 69 + echo "node not found; skipping dependency-metric descriptions for ${map_path}" >&2 + return 0 fi node - "${map_path}" <<'NODE' From c834d79d34f373fa8bde1d1d68152f3c4bb96839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Potoc=CC=8Cek?= Date: Mon, 11 May 2026 18:05:34 +0200 Subject: [PATCH 13/22] Address third Copilot review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five fixes from the latest pass: 1. Tokei multi-language filter was broken — passing the comma-list as a single `--type Kotlin,Java` searches for a literal language named "Kotlin,Java" (nothing matches). Split on comma and emit one `--type LANG` flag per entry. Explains why our stack-profile tokei filtering hasn't been narrowing the LOC views. 2. & 3. Replaced `git diff --quiet` early-exit checks with `git add -A && git diff --cached --quiet`. Untracked files weren't detected by the previous check, so the first publish for a new project (or after the retry-loop reset+reapply) could skip the commit and silently exit. Now any new file participates in the change-detection. 4. & 5. Both `preview` and `history` modes now actively delete the target meta sidecar when SOURCE_META_PATH is missing, instead of leaving the prior run's stale file in place. Same for the `latest.meta.json` pointer in `history` mode. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../codecharta-publish.sh | 25 +++++++++++++++---- .../codecharta.sh | 8 +++++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/.github/actions/universal-codecharta-publish/codecharta-publish.sh b/.github/actions/universal-codecharta-publish/codecharta-publish.sh index 01b9e5da..a50836ad 100755 --- a/.github/actions/universal-codecharta-publish/codecharta-publish.sh +++ b/.github/actions/universal-codecharta-publish/codecharta-publish.sh @@ -116,8 +116,13 @@ apply_change() { require_env SOURCE_CG_PATH cp "${SOURCE_CC_PATH}" "${PROJECT_DIR}/previews/pr-${PR_NUMBER}.cc.json.gz" cp "${SOURCE_CG_PATH}" "${PROJECT_DIR}/previews/pr-${PR_NUMBER}.cg.json" + # Mirror the meta sidecar OR explicitly remove it when source is + # missing. Without the removal a previous run's stale meta (e.g. + # an older PR title before a rename) would persist. if [[ -n "${SOURCE_META_PATH:-}" && -f "${SOURCE_META_PATH}" ]]; then cp "${SOURCE_META_PATH}" "${PROJECT_DIR}/previews/pr-${PR_NUMBER}.meta.json" + else + rm -f "${PROJECT_DIR}/previews/pr-${PR_NUMBER}.meta.json" fi ;; history) @@ -126,13 +131,20 @@ apply_change() { local basename="${DATE}-${STEM}" cp "${SOURCE_CC_PATH}" "${PROJECT_DIR}/history/${basename}.cc.json.gz" cp "${SOURCE_CG_PATH}" "${PROJECT_DIR}/history/${basename}.cg.json" + # As with preview: mirror or actively remove. A new history entry + # with no source meta should NOT inherit a stale per-entry meta + # from a prior write, and the latest.* pointers below must follow. if [[ -n "${SOURCE_META_PATH:-}" && -f "${SOURCE_META_PATH}" ]]; then cp "${SOURCE_META_PATH}" "${PROJECT_DIR}/history/${basename}.meta.json" + else + rm -f "${PROJECT_DIR}/history/${basename}.meta.json" fi cp "${PROJECT_DIR}/history/${basename}.cc.json.gz" "${PROJECT_DIR}/history/latest.cc.json.gz" cp "${PROJECT_DIR}/history/${basename}.cg.json" "${PROJECT_DIR}/history/latest.cg.json" if [[ -f "${PROJECT_DIR}/history/${basename}.meta.json" ]]; then cp "${PROJECT_DIR}/history/${basename}.meta.json" "${PROJECT_DIR}/history/latest.meta.json" + else + rm -f "${PROJECT_DIR}/history/latest.meta.json" fi ;; bulk-history) @@ -211,13 +223,16 @@ if [[ -f scripts/build-manifest.mjs ]]; then node scripts/build-manifest.mjs fi -if git diff --quiet && git diff --staged --quiet; then +# Stage everything first so untracked files (new snapshots for a project that +# didn't exist in the data repo yet) are visible to the diff check. Without +# this, `git diff --quiet` reports a clean tree because it doesn't look at +# untracked paths, and we'd exit without publishing the new snapshot. +git add -A +if git diff --cached --quiet; then echo "No changes after publish; nothing to commit." exit 0 fi -git add . - case "${MODE}" in preview) COMMIT_MSG="Update preview for ${PROJECT_NAME} PR #${PR_NUMBER}" @@ -259,11 +274,11 @@ while (( attempt <= 5 )); do if [[ -f scripts/build-manifest.mjs ]]; then node scripts/build-manifest.mjs fi - if git diff --quiet && git diff --staged --quiet; then + git add -A + if git diff --cached --quiet; then echo "After reapply, nothing to commit — concurrent writer already covered our changes." exit 0 fi - git add . git commit -m "${COMMIT_MSG}" sleep $(( attempt * 3 )) (( attempt++ )) diff --git a/.github/actions/universal-codecharta-snapshot/codecharta.sh b/.github/actions/universal-codecharta-snapshot/codecharta.sh index 5ef48c88..8069904d 100755 --- a/.github/actions/universal-codecharta-snapshot/codecharta.sh +++ b/.github/actions/universal-codecharta-snapshot/codecharta.sh @@ -235,7 +235,13 @@ snapshot() { fi tokei_type_args=() if [[ -n "${CC_TOKEI_TYPES}" ]]; then - tokei_type_args+=(--type "${CC_TOKEI_TYPES}") + # tokei takes each language as its own `--type LANG` flag; + # passing the comma-list as a single value would search for + # a literal language named "Kotlin,Java" (nothing matches). + IFS="," read -ra tokei_types <<< "${CC_TOKEI_TYPES}" + for t in "${tokei_types[@]}"; do + tokei_type_args+=(--type "${t}") + done fi tokei_exclude_args=() if [[ -n "${CC_EXTRA_EXCLUDES}" ]]; then From cdebf2c6b73066c8789c351e83fdd84ca496f847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Potoc=CC=8Cek?= Date: Mon, 11 May 2026 18:13:15 +0200 Subject: [PATCH 14/22] Stop deleting latest.{cg,meta}.json when newest entry lacks them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backfill produces history entries without .cg.json (DependaCharta isn't run per historical commit). The previous set-latest behavior deleted latest.cg.json whenever the newest entry lacked one, which meant a backfill run wiped out the project's working DependaCharta graph entirely. Same problem for latest.meta.json against rebase-merge entries with no parseable PR number. Switch to "update when available, leave alone otherwise". Stale older data beats no data — the picker's DependaCharta and title displays keep working until a fresh develop push refreshes the pointers. Reverses part of the pass-2 fix (which proactively deleted to avoid mismatch). The mismatch is acceptable here because the cc/cg/meta viewers are separate picker surfaces, not a unified delta query. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../codecharta-publish.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/actions/universal-codecharta-publish/codecharta-publish.sh b/.github/actions/universal-codecharta-publish/codecharta-publish.sh index a50836ad..7cb699a5 100755 --- a/.github/actions/universal-codecharta-publish/codecharta-publish.sh +++ b/.github/actions/universal-codecharta-publish/codecharta-publish.sh @@ -198,19 +198,19 @@ apply_change() { fi local base="${newest%.cc.json.gz}" cp "${PROJECT_DIR}/history/${newest}" "${PROJECT_DIR}/history/latest.cc.json.gz" - # Mirror the cg/meta sidecars onto the latest pointer — but DELETE - # the existing latest.* if the new newest entry has no sidecar. - # Otherwise the latest.cg.json could refer to a different (older) - # entry than latest.cc.json.gz. + # Update the cg/meta sidecars only when the selected newest entry + # has them. Otherwise leave the existing latest.* alone — backfill + # entries intentionally omit `.cg.json` (DependaCharta isn't run + # per historical commit), and we'd rather keep a graph from an + # older entry than break the picker's DependaCharta view entirely. + # `latest.meta.json` follows the same policy: rebase-merge history + # entries have no PR to query, but a prior PR-merge entry's title + # is more useful than no title at all. if [[ -f "${PROJECT_DIR}/history/${base}.cg.json" ]]; then cp "${PROJECT_DIR}/history/${base}.cg.json" "${PROJECT_DIR}/history/latest.cg.json" - else - rm -f "${PROJECT_DIR}/history/latest.cg.json" fi if [[ -f "${PROJECT_DIR}/history/${base}.meta.json" ]]; then cp "${PROJECT_DIR}/history/${base}.meta.json" "${PROJECT_DIR}/history/latest.meta.json" - else - rm -f "${PROJECT_DIR}/history/latest.meta.json" fi ;; esac From e4f7bf35b9de8af69ceed67a3031ae336ae9f926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Potoc=CC=8Cek?= Date: Mon, 11 May 2026 19:03:14 +0200 Subject: [PATCH 15/22] Address fifth Copilot review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four fixes from the pass-4/5 cumulative feedback: 1. PROJECT_NAME could escape with '.' or '..'. The earlier regex [A-Za-z0-9._-]+ accepts both — `PROJECT_NAME=..` would make PROJECT_DIR `projects/..` (the data repo root). Add an explicit reject for the pure-dot cases on top of the charset check. 2. analysis_image default was `:latest` (floating). Pin to `codecharta/codecharta-analysis:1.143.0` (current latest stable on Docker Hub, 2026-04-28) for reproducibility. Bumped both action.yml input default and the codecharta.sh env fallback. 3. pr_limit was `type: string`. Switch to `type: number` so the workflow_call boundary catches non-numeric values before they reach the snapshot script's runtime validation. 4. Workflow header described all snapshots as having complexity/ LOC/churn/dependency edges, but backfill-history snapshots intentionally omit tokei/gitlogparser/DependaCharta (rerunning them per historical commit would blow CI budget). Clarified the "lightweight" backfill caveat inline. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../codecharta-publish.sh | 8 ++++--- .../universal-codecharta-snapshot/action.yml | 7 +++--- .../codecharta.sh | 2 +- .github/workflows/universal-codecharta.yml | 22 ++++++++++++++----- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/.github/actions/universal-codecharta-publish/codecharta-publish.sh b/.github/actions/universal-codecharta-publish/codecharta-publish.sh index 7cb699a5..17a75218 100755 --- a/.github/actions/universal-codecharta-publish/codecharta-publish.sh +++ b/.github/actions/universal-codecharta-publish/codecharta-publish.sh @@ -36,9 +36,11 @@ require_env CODEBASE_ARCHITECTURES_TOKEN # Validate PROJECT_NAME — used unquoted in path construction (e.g. # "projects/${PROJECT_NAME}/previews/…"). A value like `../foo` would let the -# script write outside the projects/ tree. Restrict to a safe character set. -if [[ ! "${PROJECT_NAME}" =~ ^[A-Za-z0-9._-]+$ ]]; then - echo "PROJECT_NAME must match [A-Za-z0-9._-]+ (got '${PROJECT_NAME}')" >&2 +# script write outside the projects/ tree. Restrict to a safe character set +# AND reject pure-dot values which the regex accepts but are still traversal: +# `PROJECT_NAME=..` makes PROJECT_DIR `projects/..` (i.e. the data repo root). +if [[ ! "${PROJECT_NAME}" =~ ^[A-Za-z0-9._-]+$ ]] || [[ "${PROJECT_NAME}" == "." || "${PROJECT_NAME}" == ".." ]]; then + echo "PROJECT_NAME must match [A-Za-z0-9._-]+ and not be '.' or '..' (got '${PROJECT_NAME}')" >&2 exit 64 fi diff --git a/.github/actions/universal-codecharta-snapshot/action.yml b/.github/actions/universal-codecharta-snapshot/action.yml index 6a68f8b5..44655e0d 100644 --- a/.github/actions/universal-codecharta-snapshot/action.yml +++ b/.github/actions/universal-codecharta-snapshot/action.yml @@ -58,10 +58,11 @@ inputs: specific tag. analysis_image: required: false - default: codecharta/codecharta-analysis:latest + default: codecharta/codecharta-analysis:1.143.0 description: > - Fully-qualified ccsh / tokei analysis image. Future versions of this - action will pin to a dated tag. + Fully-qualified ccsh / tokei analysis image. Pinned to a specific + upstream release for reproducibility — bump intentionally when picking + up upstream changes. Consumers can override via this input. outputs: cc_path: diff --git a/.github/actions/universal-codecharta-snapshot/codecharta.sh b/.github/actions/universal-codecharta-snapshot/codecharta.sh index 8069904d..9a7e716c 100755 --- a/.github/actions/universal-codecharta-snapshot/codecharta.sh +++ b/.github/actions/universal-codecharta-snapshot/codecharta.sh @@ -21,7 +21,7 @@ REPO_ROOT="${REPO_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)}" PROJECT_NAME="${CODECHARTA_PROJECT_NAME:?CODECHARTA_PROJECT_NAME is required}" OUT_DIR="${CODECHARTA_OUT_DIR:-build/codecharta}" STACK="${CODECHARTA_STACK:-multi}" -ANALYSIS_IMAGE="${CODECHARTA_ANALYSIS_IMAGE:-codecharta/codecharta-analysis:latest}" +ANALYSIS_IMAGE="${CODECHARTA_ANALYSIS_IMAGE:-codecharta/codecharta-analysis:1.143.0}" DEPENDACHARTA_IMAGE="${DEPENDACHARTA_IMAGE:-ghcr.io/futuredapp/dependacharta-swift:fork-v0.2.1}" DEPENDACHARTA_RUNNER="${DEPENDACHARTA_RUNNER:-docker}" PLATFORM="${CODECHARTA_PLATFORM-linux/amd64}" diff --git a/.github/workflows/universal-codecharta.yml b/.github/workflows/universal-codecharta.yml index d7b573af..35667f71 100644 --- a/.github/workflows/universal-codecharta.yml +++ b/.github/workflows/universal-codecharta.yml @@ -9,10 +9,20 @@ name: CodeCharta + DependaCharta snapshot # a tagged ref. The consumer-side YAML only declares WHEN to run; this file # owns the actual pipeline. # -# The pipeline produces "snapshots" — single .cc.json.gz files containing the -# project's file tree, per-file complexity, LOC, churn, and dependency edges. -# Snapshots are stored in a data repo (which the consumer specifies via the -# `data_repo` input) and rendered by a picker UI (`picker_base_url` input). +# The pipeline produces "snapshots" — `.cc.json.gz` files containing the +# project's file tree plus, in the standard live path (PR previews and +# develop/main pushes), per-file complexity, LOC, churn, and dependency +# edges. Snapshots are stored in a data repo (which the consumer specifies +# via the `data_repo` input) and rendered by a picker UI (`picker_base_url` +# input). +# +# Backfilled history snapshots (the manual `workflow_dispatch` + `backfill` +# path) are intentionally "lightweight": they walk past merge commits via +# `ccsh unifiedparser --commit=` to capture per-commit file structure +# but DO NOT re-run DependaCharta, tokei, or gitlogparser per commit — +# rerunning those across N historical commits would explode CI cost. So +# backfilled entries have file structure + complexity metrics but no LOC / +# churn / dependency-graph data per historical entry. # # Four scenarios trigger this workflow, each handled by a different job: # @@ -101,9 +111,9 @@ on: When true (workflow_dispatch only), routes execution to the backfill job instead of the generate/cleanup pair. pr_limit: - type: string + type: number required: false - default: '20' + default: 20 description: > Max number of first-parent merges to walk in backfill mode. Larger values produce more historical snapshots at proportionally higher From 2fa7d15f4d753c8bc5e54fd4829a50dcdd394808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Potoc=CC=8Cek?= Date: Tue, 12 May 2026 10:02:58 +0200 Subject: [PATCH 16/22] Revert pr_limit type back to string The pass-5 Copilot suggestion to switch pr_limit from `type: string` to `type: number` looked correct in isolation but broke the established consumer pattern. Consumer YAMLs pass pr_limit: ${{ inputs.pr_limit || '20' }} which evaluates to a STRING expression. workflow_call's `type: number` validation rejects it at call time, so the reusable workflow never starts (failure with zero jobs run). Caught by smsticket-ios's PR #86 run 25721524846 going red with no logs. Revert to `type: string`. The snapshot script's history mode already validates the value is a positive integer. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/universal-codecharta.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/universal-codecharta.yml b/.github/workflows/universal-codecharta.yml index 35667f71..157d57e6 100644 --- a/.github/workflows/universal-codecharta.yml +++ b/.github/workflows/universal-codecharta.yml @@ -111,9 +111,14 @@ on: When true (workflow_dispatch only), routes execution to the backfill job instead of the generate/cleanup pair. pr_limit: - type: number + # Kept as `string` (not `number`) for compatibility with the typical + # consumer pattern `pr_limit: ${{ inputs.pr_limit || '20' }}` which + # evaluates to a string and would be rejected by workflow_call's + # strict number-type validation at call time. Numeric coercion happens + # inside the snapshot script (`history` mode validates the value). + type: string required: false - default: 20 + default: '20' description: > Max number of first-parent merges to walk in backfill mode. Larger values produce more historical snapshots at proportionally higher From 6c9b4ce7f274105df2a8f9241912b8809d43da29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Potoc=CC=8Cek?= Date: Tue, 12 May 2026 10:05:47 +0200 Subject: [PATCH 17/22] Revert tokei --type splitting; use the single-flag comma-list form MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pass-3 Copilot suggestion to emit one `--type LANG` per value broke tokei at runtime: error: The argument '--type ' was provided more than once, but cannot be used multiple times Tokei's CLI takes one `--type` flag whose value is a comma-separated list of languages (`--type Swift,Objective-C`), not repeated flags. The original code (which Copilot claimed didn't filter correctly) was actually right. Two reviewer-loop regressions in a row from over-trusting the bot's suggestions verbatim — both this commit and 2fa7d15 (pr_limit type) reverse review-driven changes that broke real consumers. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../universal-codecharta-snapshot/codecharta.sh | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/actions/universal-codecharta-snapshot/codecharta.sh b/.github/actions/universal-codecharta-snapshot/codecharta.sh index 9a7e716c..52c34b37 100755 --- a/.github/actions/universal-codecharta-snapshot/codecharta.sh +++ b/.github/actions/universal-codecharta-snapshot/codecharta.sh @@ -235,13 +235,11 @@ snapshot() { fi tokei_type_args=() if [[ -n "${CC_TOKEI_TYPES}" ]]; then - # tokei takes each language as its own `--type LANG` flag; - # passing the comma-list as a single value would search for - # a literal language named "Kotlin,Java" (nothing matches). - IFS="," read -ra tokei_types <<< "${CC_TOKEI_TYPES}" - for t in "${tokei_types[@]}"; do - tokei_type_args+=(--type "${t}") - done + # tokei accepts ONE `--type` flag with a comma-separated value + # (e.g. `--type Swift,Objective-C`). Passing multiple `--type` + # flags fails with "argument provided more than once". Pass the + # comma-list verbatim. + tokei_type_args+=(--type "${CC_TOKEI_TYPES}") fi tokei_exclude_args=() if [[ -n "${CC_EXTRA_EXCLUDES}" ]]; then From 0e8a91b766c070f03597e18f49ff7d9c69463150 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Potoc=CC=8Cek?= Date: Tue, 12 May 2026 11:07:34 +0200 Subject: [PATCH 18/22] Fix nested-quote bug in history mode field separator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `sep=$'\x1f'` lived inside an outer `bash -lc '…'` single-quoted block. ANSI-C quoting needs `$'...'` literally; the `'` after `$` was closing the outer quote, so the inner bash actually saw `$\x1f` (interpreted as a reference to an unbound `x1f` variable). Under `set -u`, every backfill run died with: bash: line 23: x1f: unbound variable Caught by slovak-telekom-young-kmp PR's backfill workflow (run 25724033946). Build the separator with `sep=$(printf "\x1f")` instead — printf interprets `\xNN` regardless of quote style, and the inner DOUBLE quotes don't conflict with the outer single-quoted heredoc. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../actions/universal-codecharta-snapshot/codecharta.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/actions/universal-codecharta-snapshot/codecharta.sh b/.github/actions/universal-codecharta-snapshot/codecharta.sh index 52c34b37..14da72d3 100755 --- a/.github/actions/universal-codecharta-snapshot/codecharta.sh +++ b/.github/actions/universal-codecharta-snapshot/codecharta.sh @@ -348,7 +348,14 @@ history() { # Use ASCII unit separator (\x1f) as field delimiter rather than `|` # — pipe characters are legal in commit subjects and would otherwise # split a single record across multiple fields. - sep=$'\x1f' + # + # NB: must use DOUBLE-quoted printf format inside this inner block + # because the whole block is wrapped in OUTER `bash -lc '…'` + # single quotes. Both ANSI-C quoting (`$'\x1f'`) and + # single-quoted printf (`printf '\x1f'`) would prematurely close + # the outer quote. printf interprets `\xNN` escapes in the format + # arg regardless of which quote style wraps it. + sep=$(printf "\x1f") log_args=(--first-parent --merges "--format=%H${sep}%cs${sep}%s") if [[ -n "${CC_PULL_REQUEST_LIMIT}" ]]; then log_args=(-n "${CC_PULL_REQUEST_LIMIT}" "${log_args[@]}") From 238645ed2a2612a0a221484a392c5f8e17b8d5f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Poto=C4=8Dek?= Date: Sun, 17 May 2026 10:54:51 +0200 Subject: [PATCH 19/22] Expose dependacharta_image_base as a workflow input Consumers outside the futuredapp GHCR access boundary (personal accounts, other orgs) can't pull the private fork image. Adding a backwards-compatible input lets them point at upstream maibornwolff/dependacharta-analysis on Docker Hub for non-Swift projects. Inlined the image-ref construction at both call sites and removed the workflow-level env that just aliased a single value; the input expression itself is now the source of truth. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/universal-codecharta.yml | 30 +++++++++++++--------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/.github/workflows/universal-codecharta.yml b/.github/workflows/universal-codecharta.yml index 157d57e6..3b572a23 100644 --- a/.github/workflows/universal-codecharta.yml +++ b/.github/workflows/universal-codecharta.yml @@ -123,13 +123,27 @@ on: Max number of first-parent merges to walk in backfill mode. Larger values produce more historical snapshots at proportionally higher cost. The 60-min job timeout caps this in practice around ~100. + dependacharta_image_base: + type: string + required: false + default: ghcr.io/futuredapp/dependacharta-swift + description: > + Fully-qualified image reference *without* tag. Defaults to the + Futured fork (covers Swift + the upstream languages). Override to + `maibornwolff/dependacharta-analysis` (Docker Hub, public) when the + Swift parser isn't needed and a public registry is preferable — + e.g. consumer repos outside the futuredapp GHCR access boundary. + Pair with a matching `dependacharta_image_tag`. dependacharta_image_tag: type: string required: false default: fork-v0.2.1 description: > - Tag on the Futured DependaCharta fork image. Pin per release of this - workflow; floating tags (e.g. `latest`) break reproducibility. + Tag on the DependaCharta image (paired with `dependacharta_image_base`). + Default targets the Futured fork; if you switch the base to upstream + `maibornwolff/dependacharta-analysis`, also set this to a matching + upstream tag (e.g. `0.25.0`). Pin per release of this workflow; + floating tags (e.g. `latest`) break reproducibility. # ── Secrets the consumer must pass through ─────────────────────────────── secrets: @@ -152,14 +166,6 @@ concurrency: group: codecharta-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: false -# ── Workflow-level env ─────────────────────────────────────────────────────── -# Only the DependaCharta image base lives here — the fork is publicly hosted -# and shared by all stacks. The data-repo and picker URL come from inputs so -# this workflow can be reused without baking specific (often private) repo -# names into a public file. -env: - DEPENDACHARTA_IMAGE_BASE: ghcr.io/futuredapp/dependacharta-swift - jobs: # ========================================================================== # JOB 1: generate @@ -231,7 +237,7 @@ jobs: file_extensions: ${{ inputs.file_extensions }} tokei_types: ${{ inputs.tokei_types }} extra_excludes: ${{ inputs.extra_excludes }} - dependacharta_image: ${{ env.DEPENDACHARTA_IMAGE_BASE }}:${{ inputs.dependacharta_image_tag }} + dependacharta_image: ${{ inputs.dependacharta_image_base }}:${{ inputs.dependacharta_image_tag }} # ── Step 5: decide whether this is a preview or a history snapshot ──── # Branches the rest of the pipeline: @@ -521,7 +527,7 @@ jobs: tokei_types: ${{ inputs.tokei_types }} extra_excludes: ${{ inputs.extra_excludes }} history_limit: ${{ inputs.pr_limit }} - dependacharta_image: ${{ env.DEPENDACHARTA_IMAGE_BASE }}:${{ inputs.dependacharta_image_tag }} + dependacharta_image: ${{ inputs.dependacharta_image_base }}:${{ inputs.dependacharta_image_tag }} # ── Step: bulk-publish the N generated snapshots in one commit ──────── # `bulk-history` mode does: From 5508bf2cc20385c940458a00dc68129bf1777bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Poto=C4=8Dek?= Date: Sun, 17 May 2026 11:54:52 +0200 Subject: [PATCH 20/22] Move mkdir into apply_change to fix retry-path failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The retry loop does `git reset --hard origin/` and re-runs apply_change. If the winning concurrent push only created one of {previews, history} (e.g. a preview run wins, then a history run loses and resets), the loser's local directory for the OTHER kind is gone and the subsequent `cp` fails with "No such file or directory" — killing the loop on attempt 2 due to set -euo pipefail. The script's own comment claims apply_change is idempotent; hoisting mkdir out of the function quietly broke that invariant. Moving it back inside restores idempotency at no cost (mkdir -p is a no-op when the directory exists). Triggered in the wild during a PR-sync + push-to-develop race on a consumer repo. After the fix, the second-attempt push succeeds. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../universal-codecharta-publish/codecharta-publish.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/actions/universal-codecharta-publish/codecharta-publish.sh b/.github/actions/universal-codecharta-publish/codecharta-publish.sh index 17a75218..48949a0d 100755 --- a/.github/actions/universal-codecharta-publish/codecharta-publish.sh +++ b/.github/actions/universal-codecharta-publish/codecharta-publish.sh @@ -109,9 +109,16 @@ git config user.name "codecharta-bot" git remote set-url origin "https://x-access-token:${CODEBASE_ARCHITECTURES_TOKEN}@github.com/${DATA_REPO}.git" PROJECT_DIR="projects/${PROJECT_NAME}" -mkdir -p "${PROJECT_DIR}/previews" "${PROJECT_DIR}/history" apply_change() { + # Re-create the destination directories on every call. Important on the + # retry path: `git reset --hard origin/` wipes any local-only + # directory that the remote winner didn't create. For example, a preview + # run that wins the race leaves `previews/` but not `history/` in the + # tree; a losing history run then resets to that state and would `cp` + # into a missing directory without this mkdir. `mkdir -p` is idempotent, + # so it costs nothing on the happy path. + mkdir -p "${PROJECT_DIR}/previews" "${PROJECT_DIR}/history" case "${MODE}" in preview) require_env SOURCE_CC_PATH From a3256a59a46a4327057edbb4f3e4733c77936722 Mon Sep 17 00:00:00 2001 From: Simon Sestak Date: Thu, 8 Oct 2026 16:01:00 +0200 Subject: [PATCH 21/22] Load the PR graph into DependaCharta via ?file= The DependaCharta visualizer reads its graph URL from the `file` query parameter; `graph` was ignored, so the PR link fell back to the bundled java-example graph instead of the PR's snapshot. Claude-Session: https://claude.ai/code/session_01TSHYNC2rk9twx378HU3W2X --- .github/workflows/universal-codecharta.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/universal-codecharta.yml b/.github/workflows/universal-codecharta.yml index 3b572a23..c1267d40 100644 --- a/.github/workflows/universal-codecharta.yml +++ b/.github/workflows/universal-codecharta.yml @@ -374,7 +374,7 @@ jobs: latestUrl="/api/map?path=${latestPath}" ccStudio="${base}/studio/?file=$(jq -rn --arg u "${ccUrl}" '$u | @uri')" - cgVis="${base}/dependacharta/?graph=$(jq -rn --arg u "${cgUrl}" '$u | @uri')" + cgVis="${base}/dependacharta/?file=$(jq -rn --arg u "${cgUrl}" '$u | @uri')" ccDelta="${base}/studio/?file=$(jq -rn --arg u "${ccUrl}" '$u | @uri')&file=$(jq -rn --arg u "${latestUrl}" '$u | @uri')" { From c75984d944ce8070ea3d0485acf9b928f29f7e1e Mon Sep 17 00:00:00 2001 From: Simon Sestak Date: Thu, 8 Oct 2026 16:46:30 +0200 Subject: [PATCH 22/22] Point codecharta action refs at the current release validate-action-refs.sh requires every internal futuredapp/.github ref to match the release version; bump-action-refs.sh only rewrites @main and semver refs, so the branch refs failed test_action-refs.bats. The next release bump moves them to the version that ships these actions. Claude-Session: https://claude.ai/code/session_01TSHYNC2rk9twx378HU3W2X --- .github/workflows/universal-codecharta.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/universal-codecharta.yml b/.github/workflows/universal-codecharta.yml index c1267d40..42e80615 100644 --- a/.github/workflows/universal-codecharta.yml +++ b/.github/workflows/universal-codecharta.yml @@ -229,7 +229,7 @@ jobs: # These outputs are consumed by the publish steps below. - name: Run CodeCharta snapshot id: snapshot - uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@2.6.0 with: mode: snapshot project_name: ${{ steps.project.outputs.name }} @@ -313,7 +313,7 @@ jobs: # concurrent writers. - name: Publish (preview) if: steps.ids.outputs.kind == 'preview' - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@2.6.0 with: mode: preview project_name: ${{ steps.project.outputs.name }} @@ -330,7 +330,7 @@ jobs: # so the picker can offer a delta view against current develop. - name: Publish (history) if: steps.ids.outputs.kind == 'history' - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@2.6.0 with: mode: history project_name: ${{ steps.project.outputs.name }} @@ -447,7 +447,7 @@ jobs: # data repo, rebuilds manifest, commits ("Drop preview for PR # #"), pushes. Idempotent — no-op if the preview was never created. - name: Delete preview - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@2.6.0 with: mode: delete-preview project_name: ${{ steps.project.outputs.name }} @@ -516,7 +516,7 @@ jobs: # deliberate trade-off. - name: Generate historical snapshots id: history - uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-snapshot@2.6.0 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -542,7 +542,7 @@ jobs: # # Replaces the previous loop-of-publishes which did N clones / N pushes. - name: Publish backfill batch - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@2.6.0 with: mode: bulk-history project_name: ${{ steps.project.outputs.name }} @@ -557,7 +557,7 @@ jobs: # commit from bulk-history so the history entries and the pointer # refresh are easy to revert independently if something goes wrong. - name: Refresh latest pointer - uses: futuredapp/.github/.github/actions/universal-codecharta-publish@ci/codecharta-actions + uses: futuredapp/.github/.github/actions/universal-codecharta-publish@2.6.0 with: mode: set-latest project_name: ${{ steps.project.outputs.name }}