Files
tutti/.github/workflows/publish-tutti-app-release.yml
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

657 lines
26 KiB
YAML

name: Publish Tutti App Release
on:
workflow_call:
inputs:
app_id:
required: true
type: string
package_command:
required: false
type: string
default: ""
package_dir:
required: false
type: string
default: ""
icon_path:
required: false
type: string
default: ""
release_tag_prefix:
required: false
type: string
default: ""
release_bump:
required: false
type: string
default: ""
min_tutti_version:
required: false
type: string
default: ""
create_release_tag:
required: false
type: boolean
default: false
runner:
required: false
type: string
default: ubuntu-latest
node_version:
required: false
type: string
default: "24"
pnpm_version:
required: false
type: string
default: "10.11.0"
release_tools_package:
required: false
type: string
default: "@tutti-os/app-release-tools@latest"
aws_region:
required: true
type: string
aws_role_arn:
required: true
type: string
s3_bucket:
required: true
type: string
s3_prefix:
required: false
type: string
default: ""
release_assets_base_url:
required: false
type: string
default: ""
publish_catalog:
required: false
type: boolean
default: false
catalog_only:
required: false
type: boolean
default: false
prepare_only:
description: Build and upload an Actions release candidate without mutating cloud release state.
required: false
type: boolean
default: false
candidate_run_id:
description: Actions run containing an approved production release candidate.
required: false
type: string
default: ""
expected_version:
description: Approved candidate version.
required: false
type: string
default: ""
expected_artifact_sha256:
description: Approved candidate artifact SHA-256.
required: false
type: string
default: ""
expected_git_sha:
description: Approved candidate source commit.
required: false
type: string
default: ""
catalog_cloudfront_distribution_id:
required: false
type: string
default: ""
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
publish:
if: ${{ inputs.catalog_only || !contains(github.event.head_commit.message || '', '[skip release]') }}
runs-on: ${{ inputs.runner }}
concurrency:
group: ${{ (!inputs.prepare_only && (inputs.publish_catalog || inputs.catalog_only)) && format('tutti-app-catalog-{0}-{1}', inputs.s3_bucket, inputs.s3_prefix) || format('tutti-app-release-{0}-{1}', inputs.app_id, github.ref_name) }}
cancel-in-progress: false
steps:
- name: Checkout app repository
uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: ${{ !inputs.prepare_only }}
- name: Validate release mode
shell: bash
env:
CATALOG_ONLY: ${{ inputs.catalog_only }}
PREPARE_ONLY: ${{ inputs.prepare_only }}
CANDIDATE_RUN_ID: ${{ inputs.candidate_run_id }}
EXPECTED_VERSION: ${{ inputs.expected_version }}
EXPECTED_ARTIFACT_SHA256: ${{ inputs.expected_artifact_sha256 }}
EXPECTED_GIT_SHA: ${{ inputs.expected_git_sha }}
run: |
if [ "${CATALOG_ONLY}" = "true" ] && [ "${PREPARE_ONLY}" = "true" ]; then
echo "catalog_only and prepare_only are mutually exclusive." >&2
exit 1
fi
candidate_values=(
"${CANDIDATE_RUN_ID}"
"${EXPECTED_VERSION}"
"${EXPECTED_ARTIFACT_SHA256}"
"${EXPECTED_GIT_SHA}"
)
candidate_value_count=0
for value in "${candidate_values[@]}"; do
if [ -n "${value}" ]; then
candidate_value_count=$((candidate_value_count + 1))
fi
done
if [ "${candidate_value_count}" -ne 0 ] && [ "${candidate_value_count}" -ne 4 ]; then
echo "candidate_run_id, expected_version, expected_artifact_sha256, and expected_git_sha must be supplied together." >&2
exit 1
fi
if [ "${PREPARE_ONLY}" = "true" ] && [ "${candidate_value_count}" -ne 0 ]; then
echo "prepare_only cannot promote an existing candidate." >&2
exit 1
fi
if [ "${CATALOG_ONLY}" = "true" ] && [ "${candidate_value_count}" -ne 0 ]; then
echo "catalog_only cannot promote a release candidate." >&2
exit 1
fi
- name: Validate release inputs
if: ${{ !inputs.catalog_only }}
shell: bash
env:
PACKAGE_COMMAND: ${{ inputs.package_command }}
PACKAGE_DIR: ${{ inputs.package_dir }}
RELEASE_ASSETS_BASE_URL: ${{ inputs.release_assets_base_url }}
RELEASE_BUMP: ${{ inputs.release_bump }}
CREATE_RELEASE_TAG: ${{ inputs.create_release_tag }}
PREPARE_ONLY: ${{ inputs.prepare_only }}
MIN_TUTTI_VERSION: ${{ inputs.min_tutti_version }}
run: |
if [ -z "${PACKAGE_COMMAND}" ]; then
echo "package_command is required unless catalog_only is true." >&2
exit 1
fi
if [ -z "${PACKAGE_DIR}" ]; then
echo "package_dir is required unless catalog_only is true." >&2
exit 1
fi
if [ -z "${RELEASE_ASSETS_BASE_URL}" ]; then
echo "release_assets_base_url is required unless catalog_only is true." >&2
exit 1
fi
if [ -n "${MIN_TUTTI_VERSION}" ] && [[ ! "${MIN_TUTTI_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then
echo "min_tutti_version must be valid SemVer." >&2
exit 1
fi
if [ -n "${RELEASE_BUMP}" ] && [[ ! "${RELEASE_BUMP}" =~ ^(major|minor|patch)$ ]]; then
echo "release_bump must be one of major, minor, or patch when provided." >&2
exit 1
fi
if [ -n "${RELEASE_BUMP}" ] && [ "${CREATE_RELEASE_TAG}" != "true" ] && [ "${PREPARE_ONLY}" != "true" ]; then
echo "release_bump requires create_release_tag." >&2
exit 1
fi
if [ "${CREATE_RELEASE_TAG}" = "true" ] && [ -z "${RELEASE_BUMP}" ]; then
echo "create_release_tag requires release_bump." >&2
exit 1
fi
- name: Setup pnpm
uses: pnpm/action-setup@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ inputs.node_version }}
cache: pnpm
- name: Download approved production release candidate
if: ${{ inputs.candidate_run_id != '' }}
uses: actions/download-artifact@v5
with:
name: ${{ format('{0}-{1}-production-candidate', inputs.app_id, inputs.expected_version) }}
path: tutti-app-release
github-token: ${{ github.token }}
run-id: ${{ inputs.candidate_run_id }}
- name: Install app dependencies
if: ${{ !inputs.catalog_only && inputs.candidate_run_id == '' }}
run: pnpm install --frozen-lockfile
- name: Build app package
if: ${{ !inputs.catalog_only && inputs.candidate_run_id == '' }}
run: ${{ inputs.package_command }}
- name: Resolve release version
if: ${{ !inputs.catalog_only }}
id: release
shell: bash
env:
RELEASE_TAG_PREFIX: ${{ inputs.release_tag_prefix }}
RELEASE_BUMP: ${{ inputs.release_bump }}
CREATE_RELEASE_TAG: ${{ inputs.create_release_tag }}
APP_ID: ${{ inputs.app_id }}
PACKAGE_DIR: ${{ inputs.package_dir }}
CANDIDATE_RUN_ID: ${{ inputs.candidate_run_id }}
EXPECTED_VERSION: ${{ inputs.expected_version }}
EXPECTED_ARTIFACT_SHA256: ${{ inputs.expected_artifact_sha256 }}
EXPECTED_GIT_SHA: ${{ inputs.expected_git_sha }}
run: |
node <<'NODE'
const fs = require("fs");
const { execFileSync } = require("child_process");
const semverPattern = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
const stableSemverPattern = /^([0-9]+)\.([0-9]+)\.([0-9]+)$/;
function fail(message) {
console.error(message);
process.exit(1);
}
function output(name, value) {
fs.appendFileSync(process.env.GITHUB_OUTPUT, `${name}=${value}\n`);
}
function defaultTagPrefix() {
return process.env.RELEASE_TAG_PREFIX || `${process.env.APP_ID}-v`;
}
function parseStableVersion(version, source) {
const match = version.match(stableSemverPattern);
if (!match) {
return null;
}
return {
source,
version,
major: Number(match[1]),
minor: Number(match[2]),
patch: Number(match[3])
};
}
function compareVersions(left, right) {
for (const key of ["major", "minor", "patch"]) {
if (left[key] !== right[key]) {
return left[key] - right[key];
}
}
return 0;
}
function latestVersionSeed(prefix, manifestVersion, requireStableManifest) {
execFileSync("git", ["fetch", "--tags", "--force"], { stdio: "inherit" });
const tags = execFileSync("git", ["tag", "--list"], { encoding: "utf8" })
.split("\n")
.map((tag) => tag.trim())
.filter((tag) => tag.startsWith(prefix));
const stableManifestVersion = String(manifestVersion || "")
.split("+", 1)[0]
.split("-", 1)[0];
const manifestSeed = parseStableVersion(
stableManifestVersion,
"package manifest"
);
if (requireStableManifest && !manifestSeed) {
fail(`Package manifest version must be stable semver x.y.z when release_bump is used, got ${manifestVersion}.`);
}
const tagSeeds = tags
.map((tag) => parseStableVersion(tag.slice(prefix.length), `tag ${tag}`))
.filter(Boolean);
const seeds = [...(manifestSeed ? [manifestSeed] : []), ...tagSeeds];
const latest = seeds.sort(compareVersions).at(-1) ?? {
source: "default",
version: "0.0.0",
major: 0,
minor: 0,
patch: 0
};
console.log(`Resolved release version seed ${latest.version} from ${latest.source}.`);
return latest;
}
function nextVersionFromSources(prefix, bump, manifestVersion) {
const latest = latestVersionSeed(prefix, manifestVersion, true);
if (bump === "major") {
return `${latest.major + 1}.0.0`;
}
if (bump === "minor") {
return `${latest.major}.${latest.minor + 1}.0`;
}
if (bump === "patch") {
return `${latest.major}.${latest.minor}.${latest.patch + 1}`;
}
fail(`release_bump must be one of major, minor, or patch when provided, got ${bump}.`);
}
let gitSha = execFileSync("git", ["rev-parse", "HEAD"], {
encoding: "utf8"
}).trim();
const releaseBump = process.env.RELEASE_BUMP;
const shouldCreateTag = process.env.CREATE_RELEASE_TAG === "true";
let releaseVersion = "";
let tagName = "";
if (process.env.CANDIDATE_RUN_ID) {
const expectedVersion = process.env.EXPECTED_VERSION;
const expectedArtifactSha256 = process.env.EXPECTED_ARTIFACT_SHA256;
const expectedGitSha = process.env.EXPECTED_GIT_SHA;
const releasePath = `tutti-app-release/apps/${process.env.APP_ID}/${expectedVersion}/release.json`;
const candidate = JSON.parse(fs.readFileSync(releasePath, "utf8"));
const mismatches = [];
if (candidate.appId !== process.env.APP_ID) mismatches.push("appId");
if (candidate.version !== expectedVersion) mismatches.push("version");
if (candidate.artifactSha256 !== expectedArtifactSha256) mismatches.push("artifactSha256");
if (candidate.gitSha !== expectedGitSha) mismatches.push("gitSha");
if (mismatches.length > 0) {
fail(`Approved release candidate mismatch: ${mismatches.join(", ")}`);
}
releaseVersion = expectedVersion;
gitSha = expectedGitSha;
tagName = `${defaultTagPrefix()}${releaseVersion}`;
} else {
const manifest = JSON.parse(
fs.readFileSync(`${process.env.PACKAGE_DIR}/tutti.app.json`, "utf8")
);
if (releaseBump) {
const tagPrefix = defaultTagPrefix();
releaseVersion = nextVersionFromSources(
tagPrefix,
releaseBump,
manifest.version
);
tagName = `${tagPrefix}${releaseVersion}`;
} else {
if (shouldCreateTag) {
fail("create_release_tag requires release_bump.");
}
// Staging publishes do not bump. Seed from max(manifest, release tags)
// so staging stays on the same product line as production tags.
const tagPrefix = defaultTagPrefix();
const seed = latestVersionSeed(tagPrefix, manifest.version, false);
releaseVersion = `${seed.version}+${gitSha.slice(0, 12)}`;
}
}
if (!semverPattern.test(releaseVersion)) {
fail(`Release version must be semver x.y.z with optional prerelease/build metadata, got ${releaseVersion}.`);
}
output("version", releaseVersion);
output("git_sha", gitSha);
output("tag_name", tagName);
NODE
- name: Build release metadata
if: ${{ !inputs.catalog_only && inputs.candidate_run_id == '' }}
shell: bash
env:
APP_ID: ${{ inputs.app_id }}
PACKAGE_DIR: ${{ inputs.package_dir }}
ICON_PATH: ${{ inputs.icon_path }}
RELEASE_TOOLS_PACKAGE: ${{ inputs.release_tools_package }}
RELEASE_ASSETS_BASE_URL: ${{ inputs.release_assets_base_url }}
RELEASE_VERSION: ${{ steps.release.outputs.version }}
run: |
args=(
--app-id "${APP_ID}"
--package-dir "${PACKAGE_DIR}"
--output-dir tutti-app-release
--base-url "${RELEASE_ASSETS_BASE_URL}"
--version "${RELEASE_VERSION}"
--git-sha "${{ steps.release.outputs.git_sha }}"
)
if [ -n "${ICON_PATH}" ]; then
args+=(--icon-path "${ICON_PATH}")
fi
pnpm --package "${RELEASE_TOOLS_PACKAGE}" dlx build-tutti-app-release "${args[@]}"
- name: Verify local app release candidate
if: ${{ !inputs.catalog_only }}
shell: bash
env:
APP_ID: ${{ inputs.app_id }}
RELEASE_VERSION: ${{ steps.release.outputs.version }}
run: |
node <<'NODE'
const fs = require("fs");
const path = require("path");
const { createHash } = require("crypto");
const root = path.resolve("tutti-app-release");
const releaseDir = path.join(root, "apps", process.env.APP_ID, process.env.RELEASE_VERSION);
const versionedPath = path.join(releaseDir, "release.json");
const latestPath = path.join(root, "apps", process.env.APP_ID, "latest.json");
const release = JSON.parse(fs.readFileSync(versionedPath, "utf8"));
const latest = JSON.parse(fs.readFileSync(latestPath, "utf8"));
if (JSON.stringify(release) !== JSON.stringify(latest)) {
throw new Error("versioned release.json and latest.json must match exactly");
}
if (release.appId !== process.env.APP_ID || release.version !== process.env.RELEASE_VERSION) {
throw new Error("local release identity does not match the resolved app and version");
}
const artifactName = decodeURIComponent(path.basename(new URL(release.artifactUrl).pathname));
const expectedArtifactName = `${release.appId}-${release.version}.zip`;
if (artifactName !== expectedArtifactName) {
throw new Error(`local release artifact name mismatch: want ${expectedArtifactName} got ${artifactName}`);
}
const artifactPath = path.join(releaseDir, artifactName);
const data = fs.readFileSync(artifactPath);
const sha256 = createHash("sha256").update(data).digest("hex");
if (sha256 !== release.artifactSha256.toLowerCase()) {
throw new Error(`local release artifact sha256 mismatch: want ${release.artifactSha256} got ${sha256}`);
}
if (data.length !== release.artifactSizeBytes) {
throw new Error(`local release artifact size mismatch: want ${release.artifactSizeBytes} got ${data.length}`);
}
console.log(`Verified local release candidate ${release.appId}@${release.version} ${sha256}`);
NODE
- name: Preflight release tag
if: ${{ !inputs.catalog_only && !inputs.prepare_only && inputs.create_release_tag }}
shell: bash
env:
RELEASE_TAG_NAME: ${{ steps.release.outputs.tag_name }}
RELEASE_GIT_SHA: ${{ steps.release.outputs.git_sha }}
run: |
git rev-parse --verify "${RELEASE_GIT_SHA}^{commit}" >/dev/null
git fetch --tags --force
if git rev-parse -q --verify "refs/tags/${RELEASE_TAG_NAME}" >/dev/null; then
existing_sha="$(git rev-list -n 1 "${RELEASE_TAG_NAME}")"
if [ "${existing_sha}" != "${RELEASE_GIT_SHA}" ]; then
echo "Release tag ${RELEASE_TAG_NAME} already points to ${existing_sha}, expected ${RELEASE_GIT_SHA}." >&2
exit 1
fi
fi
- name: Upload production release candidate
if: ${{ inputs.prepare_only }}
uses: actions/upload-artifact@v4
with:
name: ${{ format('{0}-{1}-production-candidate', inputs.app_id, steps.release.outputs.version) }}
path: tutti-app-release
if-no-files-found: error
retention-days: 7
- name: Configure AWS credentials
if: ${{ !inputs.prepare_only }}
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ inputs.aws_region }}
role-to-assume: ${{ inputs.aws_role_arn }}
- name: Upload app release to S3
if: ${{ !inputs.catalog_only && !inputs.prepare_only }}
shell: bash
env:
APP_ID: ${{ inputs.app_id }}
RELEASE_VERSION: ${{ steps.release.outputs.version }}
S3_BUCKET: ${{ inputs.s3_bucket }}
S3_PREFIX: ${{ inputs.s3_prefix }}
run: |
local_release_file="tutti-app-release/apps/${APP_ID}/${RELEASE_VERSION}/release.json"
prefix="${S3_PREFIX%/}"
if [ -n "${prefix}" ]; then
s3_root="s3://${S3_BUCKET}/${prefix}"
release_key="${prefix}/apps/${APP_ID}/${RELEASE_VERSION}/release.json"
else
s3_root="s3://${S3_BUCKET}"
release_key="apps/${APP_ID}/${RELEASE_VERSION}/release.json"
fi
if aws s3api head-object --bucket "${S3_BUCKET}" --key "${release_key}" >/dev/null 2>tutti-app-release-head-error.txt; then
mkdir -p tutti-app-release/existing
aws s3 cp "${s3_root}/apps/${APP_ID}/${RELEASE_VERSION}/release.json" tutti-app-release/existing/release.json
node -e '
const fs = require("fs");
const localRelease = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
const existingRelease = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
const comparedKeys = [
"appId",
"version",
"artifactUrl",
"artifactSha256",
"artifactSizeBytes",
"iconUrl",
"gitSha"
];
const mismatches = comparedKeys.filter(
(key) => localRelease[key] !== existingRelease[key]
);
if (mismatches.length > 0) {
console.error(
`Release ${localRelease.version} already exists for ${localRelease.appId} with different immutable metadata: ${mismatches.join(", ")}`
);
process.exit(1);
}
' "${local_release_file}" tutti-app-release/existing/release.json
echo "Release ${RELEASE_VERSION} already exists for ${APP_ID} with matching immutable metadata. Repairing mutable latest/catalog state."
elif grep -Eq "(404|Not Found|NotFound|NoSuchKey)" tutti-app-release-head-error.txt; then
aws s3 sync "tutti-app-release/apps/${APP_ID}/${RELEASE_VERSION}/" "${s3_root}/apps/${APP_ID}/${RELEASE_VERSION}/" \
--cache-control "public, max-age=31536000, immutable"
else
echo "Failed to check existing release ${release_key}." >&2
cat tutti-app-release-head-error.txt >&2
exit 1
fi
- name: Verify published app release artifact
if: ${{ !inputs.catalog_only && !inputs.prepare_only }}
shell: bash
env:
APP_ID: ${{ inputs.app_id }}
RELEASE_TOOLS_PACKAGE: ${{ inputs.release_tools_package }}
run: |
pnpm --package "${RELEASE_TOOLS_PACKAGE}" dlx verify-tutti-app-release-artifacts \
--release-file "tutti-app-release/apps/${APP_ID}/latest.json"
- name: Publish app release metadata
if: ${{ !inputs.prepare_only }}
shell: bash
env:
APP_ID: ${{ inputs.app_id }}
CATALOG_ONLY: ${{ inputs.catalog_only }}
MIN_TUTTI_VERSION: ${{ inputs.min_tutti_version }}
PUBLISH_CATALOG: ${{ inputs.publish_catalog }}
RELEASE_TOOLS_PACKAGE: ${{ inputs.release_tools_package }}
RELEASE_VERSION: ${{ steps.release.outputs.version }}
S3_BUCKET: ${{ inputs.s3_bucket }}
S3_PREFIX: ${{ inputs.s3_prefix }}
run: |
args=(
--app-id "${APP_ID}"
--s3-bucket "${S3_BUCKET}"
--s3-prefix "${S3_PREFIX}"
--output-dir tutti-app-metadata
)
if [ -n "${MIN_TUTTI_VERSION}" ]; then
args+=(--min-tutti-version "${MIN_TUTTI_VERSION}")
fi
if [ "${CATALOG_ONLY}" = "true" ]; then
args+=(--catalog-only --publish-catalog)
else
args+=(--release-file "tutti-app-release/apps/${APP_ID}/${RELEASE_VERSION}/release.json")
if [ "${PUBLISH_CATALOG}" = "true" ]; then
args+=(--publish-catalog)
fi
fi
pnpm --package "${RELEASE_TOOLS_PACKAGE}" dlx publish-tutti-app-metadata "${args[@]}"
- name: Verify published app metadata
if: ${{ !inputs.prepare_only }}
shell: bash
env:
CATALOG_ONLY: ${{ inputs.catalog_only }}
PUBLISH_CATALOG: ${{ inputs.publish_catalog }}
RELEASE_TOOLS_PACKAGE: ${{ inputs.release_tools_package }}
run: |
args=(--versions-file tutti-app-metadata/versions.json)
if [ "${CATALOG_ONLY}" != "true" ]; then
args+=(--release-file tutti-app-metadata/latest.json)
fi
if [ "${CATALOG_ONLY}" = "true" ] || [ "${PUBLISH_CATALOG}" = "true" ]; then
args+=(--catalog-file tutti-app-metadata/catalog.json)
fi
pnpm --package "${RELEASE_TOOLS_PACKAGE}" dlx verify-tutti-app-release-artifacts "${args[@]}"
- name: Create release tag
if: ${{ !inputs.catalog_only && !inputs.prepare_only && inputs.create_release_tag }}
shell: bash
env:
APP_ID: ${{ inputs.app_id }}
RELEASE_VERSION: ${{ steps.release.outputs.version }}
RELEASE_TAG_NAME: ${{ steps.release.outputs.tag_name }}
RELEASE_GIT_SHA: ${{ steps.release.outputs.git_sha }}
run: |
if [ -z "${RELEASE_TAG_NAME}" ]; then
echo "Resolved release tag name is empty." >&2
exit 1
fi
if ! git check-ref-format "refs/tags/${RELEASE_TAG_NAME}"; then
echo "Resolved release tag ${RELEASE_TAG_NAME} is not a valid git ref." >&2
exit 1
fi
git fetch --tags --force
if git rev-parse -q --verify "refs/tags/${RELEASE_TAG_NAME}" >/dev/null; then
existing_sha="$(git rev-list -n 1 "${RELEASE_TAG_NAME}")"
if [ "${existing_sha}" != "${RELEASE_GIT_SHA}" ]; then
echo "Release tag ${RELEASE_TAG_NAME} already points to ${existing_sha}, expected ${RELEASE_GIT_SHA}." >&2
exit 1
fi
echo "Release tag ${RELEASE_TAG_NAME} already exists for ${APP_ID} ${RELEASE_VERSION}."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "${RELEASE_TAG_NAME}" "${RELEASE_GIT_SHA}" -m "Release ${APP_ID} ${RELEASE_VERSION}"
git push origin "refs/tags/${RELEASE_TAG_NAME}"
- name: Invalidate app catalog
if: ${{ !inputs.prepare_only && (inputs.publish_catalog || inputs.catalog_only) && inputs.catalog_cloudfront_distribution_id != '' }}
shell: bash
env:
CATALOG_CLOUDFRONT_DISTRIBUTION_ID: ${{ inputs.catalog_cloudfront_distribution_id }}
S3_PREFIX: ${{ inputs.s3_prefix }}
run: |
prefix="${S3_PREFIX%/}"
if [ -n "${prefix}" ]; then
path_to_invalidate="/${prefix}/catalog.json"
else
path_to_invalidate="/catalog.json"
fi
aws cloudfront create-invalidation \
--distribution-id "${CATALOG_CLOUDFRONT_DISTRIBUTION_ID}" \
--paths "${path_to_invalidate}"