Skip to content

Commit 6c545fa

Browse files
authored
Updating v1 from v1.0.1 (#11)
2 parents d0ba63f + ecd15a7 commit 6c545fa

4 files changed

Lines changed: 195 additions & 1 deletion

File tree

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# The MIT License (MIT)
2+
#
3+
# Copyright © 2026 The pyTooling Authors
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6+
# documentation files (the “Software”), to deal in the Software without restriction, including without limitation the
7+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8+
# persons to whom the Software is furnished to do so, subject to the following conditions:
9+
#
10+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11+
# Software.
12+
#
13+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16+
name: Check Marketplace Metadata
17+
18+
on:
19+
workflow_call:
20+
inputs:
21+
action_file:
22+
description: |
23+
Action metadata file. GitHub Marketplace requires it at the repository root.
24+
type: string
25+
required: false
26+
default: 'action.yml'
27+
description_limit:
28+
description: |
29+
Maximum length of the action's description. The Marketplace form rejects 'description' with
30+
"Description must be less than 125 characters.", so the limit is exclusive.
31+
type: number
32+
required: false
33+
default: 125
34+
ubuntu_image:
35+
description: 'Name of the Ubuntu image.'
36+
type: string
37+
required: false
38+
default: 'ubuntu-26.04'
39+
40+
workflow_dispatch:
41+
42+
jobs:
43+
CheckMarketplaceMetadata:
44+
name: 🛒 Check Marketplace metadata
45+
runs-on: ${{ inputs.ubuntu_image || 'ubuntu-26.04' }}
46+
47+
steps:
48+
- name: ⏬ Checkout repository
49+
uses: actions/checkout@v7
50+
51+
- name: 🛒 Check the action's metadata against the Marketplace rules
52+
id: check
53+
shell: bash
54+
env:
55+
ACTION_FILE: ${{ inputs.action_file || 'action.yml' }}
56+
DESCRIPTION_LIMIT: ${{ inputs.description_limit || 125 }}
57+
REPOSITORY_PRIVATE: ${{ github.event.repository.private }}
58+
run: |
59+
# 🛒 Check the action's metadata against the Marketplace rules
60+
set +e
61+
62+
ANSI_CYAN=$'\x1b[36m'
63+
ANSI_LIGHT_RED=$'\x1b[91m'
64+
ANSI_LIGHT_GREEN=$'\x1b[92m'
65+
ANSI_LIGHT_YELLOW=$'\x1b[93m'
66+
ANSI_NOCOLOR=$'\x1b[0m'
67+
68+
errors=0
69+
70+
fail() {
71+
printf "%s\n" "${ANSI_LIGHT_RED}[FAILED]${ANSI_NOCOLOR}"
72+
printf " ↪ %s\n" "${ANSI_LIGHT_RED}${1}${ANSI_NOCOLOR}"
73+
printf "::error title=%s::%s\n" "Marketplace" "${1}"
74+
errors=$((errors + 1))
75+
}
76+
77+
printf "%s\n" "${ANSI_CYAN}Checking '${ACTION_FILE}' against the GitHub Marketplace rules ...${ANSI_NOCOLOR}"
78+
79+
printf "%s" "Metadata file at the repository root ... "
80+
if [[ ! -f "${ACTION_FILE}" ]]; then
81+
fail "GitHub Marketplace requires an 'action.yml' or 'action.yaml' at the repository root; '${ACTION_FILE}' doesn't exist."
82+
printf "::error title=%s::%s\n" "Marketplace" "Aborting: no metadata file to check."
83+
exit 1
84+
fi
85+
printf "%s\n" "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}"
86+
87+
# 'name', 'description' and 'branding' are read with the YAML parser rather than with grep, so a folded or
88+
# quoted value is measured as the Marketplace sees it.
89+
read -r -d '' script <<'PYTHON'
90+
import sys, yaml
91+
action = yaml.safe_load(open(sys.argv[1], encoding="utf-8")) or {}
92+
branding = action.get("branding") or {}
93+
# 'or ""' rather than a default: a key present with an empty value parses as None, and str(None) is the
94+
# four-character string "None", which would pass every check below.
95+
for key, value in (
96+
("name", action.get("name") or ""),
97+
("description", action.get("description") or ""),
98+
("icon", branding.get("icon") or ""),
99+
("color", branding.get("color") or ""),
100+
):
101+
print(f"{key}={str(value).strip()}")
102+
PYTHON
103+
metadata="$(python3 -c "${script}" "${ACTION_FILE}" 2>&1)"
104+
if [[ $? -ne 0 ]]; then
105+
fail "'${ACTION_FILE}' isn't valid YAML: ${metadata//$'\n'/ }"
106+
exit 1
107+
fi
108+
109+
name="$( sed -n 's/^name=//p' <<< "${metadata}")"
110+
description="$(sed -n 's/^description=//p' <<< "${metadata}")"
111+
icon="$( sed -n 's/^icon=//p' <<< "${metadata}")"
112+
color="$( sed -n 's/^color=//p' <<< "${metadata}")"
113+
114+
printf "%s\n" ""
115+
printf " %-14s%s\n" "name:" "${name}"
116+
printf " %-14s%s\n" "description:" "${description}"
117+
printf " %-14s%s\n" "icon:" "${icon}"
118+
printf " %-14s%s\n" "color:" "${color}"
119+
printf "%s\n" ""
120+
121+
printf "%s" "Action name is set ... "
122+
if [[ -z "${name}" ]]; then
123+
fail "'name' is required. It also has to be unique across GitHub Marketplace, which can't be checked here."
124+
else
125+
printf "%s\n" "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}"
126+
fi
127+
128+
printf "%s" "Description is set ... "
129+
if [[ -z "${description}" ]]; then
130+
fail "'description' is required."
131+
else
132+
printf "%s\n" "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}"
133+
134+
printf "%s" "Description is shorter than ${DESCRIPTION_LIMIT} characters ... "
135+
length="$(printf "%s" "${description}" | wc -m)"
136+
if [[ ${length} -ge ${DESCRIPTION_LIMIT} ]]; then
137+
fail "'description' is ${length} characters; the Marketplace requires fewer than ${DESCRIPTION_LIMIT}."
138+
else
139+
printf "%s\n" "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR} (${length} characters)"
140+
fi
141+
fi
142+
143+
printf "%s" "Branding icon is set ... "
144+
if [[ -z "${icon}" ]]; then
145+
fail "'branding.icon' is required to list an action on the Marketplace."
146+
elif [[ ! "${icon}" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then
147+
fail "'branding.icon' is '${icon}', which isn't a Feather icon name."
148+
else
149+
printf "%s\n" "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}"
150+
# The accepted set is a subset of Feather, too long and too changeable to hard-code here, so the name is
151+
# only checked for shape. A name that isn't in the set is rejected by the Marketplace form.
152+
printf " ↪ %s\n" "${ANSI_LIGHT_YELLOW}not verified against the Feather set — see https://feathericons.com${ANSI_NOCOLOR}"
153+
fi
154+
155+
printf "%s" "Branding colour is one of the accepted values ... "
156+
case "${color}" in
157+
white|yellow|blue|green|orange|red|purple|gray-dark)
158+
printf "%s\n" "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}" ;;
159+
"")
160+
fail "'branding.color' is required to list an action on the Marketplace." ;;
161+
*)
162+
fail "'branding.color' is '${color}'; accepted are white, yellow, blue, green, orange, red, purple and gray-dark." ;;
163+
esac
164+
165+
printf "%s" "Repository is public ... "
166+
if [[ "${REPOSITORY_PRIVATE}" == "true" ]]; then
167+
fail "The Marketplace only lists actions from public repositories."
168+
else
169+
printf "%s\n" "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}"
170+
fi
171+
172+
printf "%s\n" ""
173+
if [[ ${errors} -ne 0 ]]; then
174+
printf "%s\n" "${ANSI_LIGHT_RED}Counted ${errors} errors.${ANSI_NOCOLOR}"
175+
exit 1
176+
fi
177+
printf "%s\n" "${ANSI_LIGHT_GREEN}No errors found.${ANSI_NOCOLOR}"
178+
printf "%s\n" "${ANSI_LIGHT_YELLOW}Publishing itself is a checkbox when editing the release — there is no API for it.${ANSI_NOCOLOR}"

‎.github/workflows/Pipeline.yml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ jobs:
2727
Prepare:
2828
uses: pyTooling/Actions/.github/workflows/PrepareJob.yml@r8
2929

30+
Marketplace:
31+
uses: ./.github/workflows/CheckMarketplaceMetadata.yml
32+
3033
DryRun:
3134
name: 🐧 Dry-run with a valid configuration
3235
runs-on: ubuntu-26.04
@@ -140,6 +143,7 @@ jobs:
140143
uses: pyTooling/Actions/.github/workflows/TagReleaseCommit.yml@r8
141144
needs:
142145
- Prepare
146+
- Marketplace
143147
- DryRun
144148
- ErrorHandling
145149
- FailOnError
@@ -159,6 +163,7 @@ jobs:
159163
uses: ./.github/workflows/UpdateVersionBranch.yml
160164
needs:
161165
- Prepare
166+
- Marketplace
162167
- DryRun
163168
- ErrorHandling
164169
- FailOnError
@@ -176,6 +181,7 @@ jobs:
176181
uses: pyTooling/Actions/.github/workflows/PublishReleaseNotes.yml@r8
177182
needs:
178183
- Prepare
184+
- Marketplace
179185
- DryRun
180186
- ErrorHandling
181187
- FailOnError

‎README.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,16 @@ three jobs, no token, no repository touched. Beyond that it releases itself, wit
322322
there; where nothing needs rewriting, the pull-request is a plain merge of `main`. Only **self**-references are
323323
touched — `pyTooling/Actions@r8` and `actions/checkout@v7` are separate decisions and are left alone.
324324
325+
* **`CheckMarketplaceMetadata.yml`** — a local reusable workflow validating `action.yml` against the
326+
[Marketplace](https://github.com/marketplace?type=actions) rules on every push: the metadata file at the repository
327+
root, a name, a description shorter than 125 characters, a branding icon and one of the eight accepted branding
328+
colours, and a public repository.
329+
330+
**Publishing itself stays manual.** There is no API for it — an action is listed by ticking *Publish this Action to
331+
the GitHub Marketplace* while drafting or editing its release. A tag carries exactly one release, and the
332+
Marketplace listing is a property of that release, not a second one, so an already-published release is **edited**
333+
rather than re-created.
334+
325335
So a release is one merge: open a `dev` → `main` pull-request titled `vMM.mm.pp`, write the release notes in its
326336
description, and merge it. What follows is automatic, except for the version-branch pull-request, which waits for a
327337
review.

‎action.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ name: 🔄 Synchronize Forked Repositories
1919
branding:
2020
icon: refresh-cw
2121
color: yellow
22-
description: Synchronize branches and tags of forked repositories of a GitHub organisation or user account with their upstream repositories.
22+
description: Synchronize branches and tags of forked repositories in a GitHub namespace with their upstream repositories.
2323
author: Patrick Lehmann (@Paebbels)
2424

2525
inputs:

0 commit comments

Comments
 (0)