Add ignoreTags support to exclude old versions from packaging (#118)

* Add ignoreTags support to exclude old versions from packaging

Co-authored-by: TingluoHuang <1750815+TingluoHuang@users.noreply.github.com>

* Add ignoreTags support to add-action and update-action scripts

Co-authored-by: TingluoHuang <1750815+TingluoHuang@users.noreply.github.com>

* Fix JSDoc typo and add regex validation for ignore-tags patterns

Co-authored-by: TingluoHuang <1750815+TingluoHuang@users.noreply.github.com>

* Simplify --ignore-tags to accept version prefixes instead of regex patterns

Co-authored-by: TingluoHuang <1750815+TingluoHuang@users.noreply.github.com>

* Add helper script to add ignoreTags to existing actions and fix JSON syntax in README

Co-authored-by: TingluoHuang <1750815+TingluoHuang@users.noreply.github.com>

* Remove --all flag from add-ignore-tags.sh, require specific action

Co-authored-by: TingluoHuang <1750815+TingluoHuang@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: TingluoHuang <1750815+TingluoHuang@users.noreply.github.com>
This commit is contained in:
Copilot
2025-12-09 10:30:43 -05:00
committed by GitHub
parent 40447878b4
commit ab7f3a1ca8
7 changed files with 205 additions and 7 deletions
+33
View File
@@ -15,6 +15,39 @@ Preview versions are intentionally excluded. For example: `v2-beta`
Optional args may be supplied to control which refs are included. See `script/add-action.sh --help` for more info. Optional args may be supplied to control which refs are included. See `script/add-action.sh --help` for more info.
### Ignoring old versions
To exclude certain old version tags from being packaged, add an `ignoreTags` array to the action config JSON file. Each entry is a regex pattern that will be tested against tag names.
**When adding a new action**, use the `--ignore-tags` option with simple version prefixes:
```bash
./script/add-action.sh --ignore-tags "v1,v2" actions/checkout
```
This will automatically generate regex patterns that match `v1`, `v1.x`, `v2`, `v2.x`, etc.
**For existing actions**, use the helper script to add ignore tags:
```bash
./script/add-ignore-tags.sh --ignore-tags "v1,v2" actions/checkout
```
Or add `ignoreTags` directly to the JSON config file:
```json
{
"owner": "actions",
"repo": "checkout",
"ignoreTags": [
"^v1(\\..*)?$",
"^v2(\\..*)?$"
]
}
```
Tags matching any of the patterns will be excluded from the generated scripts while remaining in the config for historical reference. The `ignoreTags` field is preserved when running `update-action.sh`.
### How to use this in the self-hosted runner? ### How to use this in the self-hosted runner?
Please read the doc @kenmuse has put together at: https://www.kenmuse.com/blog/building-github-actions-runner-images-with-an-action-archive-cache/ Please read the doc @kenmuse has put together at: https://www.kenmuse.com/blog/building-github-actions-runner-images-with-an-action-archive-cache/
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
set -e
script_dir="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
# Minimum node version
$script_dir/internal/check-node.sh
# Add ignore tags to the action
node "$script_dir/internal/add-ignore-tags.js" $*
# Regenerate action scripts
$script_dir/internal/generate-scripts.sh
+12 -2
View File
@@ -26,6 +26,12 @@ class ActionConfig {
*/ */
patterns = [] patterns = []
/**
* Tag patterns to ignore during packaging
* @type {string[]|undefined}
*/
ignoreTags = undefined
/** /**
* Branch versions (ref to commit SHA) * Branch versions (ref to commit SHA)
* @type {{[ref: string]: string}} * @type {{[ref: string]: string}}
@@ -63,12 +69,13 @@ exports.TagVersion = TagVersion
/** /**
* Adds a new action config file * Adds a new action config file
* @param {string} owner * @param {string} owner
* @param {string} repos * @param {string} repo
* @param {string[]} patternStrings * @param {string[]} patternStrings
* @param {string} defaultBranch * @param {string} defaultBranch
* @param {string[]|undefined} ignoreTags
* @returns {Promise} * @returns {Promise}
*/ */
async function add(owner, repo, patternStrings, defaultBranch) { async function add(owner, repo, patternStrings, defaultBranch, ignoreTags) {
assert.ok(owner, "Arg 'owner' must not be empty") assert.ok(owner, "Arg 'owner' must not be empty")
assert.ok(repo, "Arg 'repo' must not be empty") assert.ok(repo, "Arg 'repo' must not be empty")
assert.ok(patternStrings, "Arg 'patternStrings' must not be null") assert.ok(patternStrings, "Arg 'patternStrings' must not be null")
@@ -84,6 +91,9 @@ async function add(owner, repo, patternStrings, defaultBranch) {
config.owner = owner config.owner = owner
config.repo = repo config.repo = repo
config.patterns = patternStrings config.patterns = patternStrings
if (ignoreTags && ignoreTags.length > 0) {
config.ignoreTags = ignoreTags
}
config.defaultBranch = defaultBranch config.defaultBranch = defaultBranch
const tempDir = path.join(paths.temp, `${owner}_${repo}`) const tempDir = path.join(paths.temp, `${owner}_${repo}`)
+21 -4
View File
@@ -14,6 +14,7 @@ async function main() {
const repo = args.repo const repo = args.repo
const patterns = args.patterns const patterns = args.patterns
const defaultBranch = args.defaultBranch || 'master' const defaultBranch = args.defaultBranch || 'master'
const ignoreTags = args.ignoreTags
// File exists? // File exists?
const file = actionConfig.getFilePath(owner, repo) const file = actionConfig.getFilePath(owner, repo)
@@ -23,7 +24,7 @@ async function main() {
await fsHelper.reinitTemp() await fsHelper.reinitTemp()
// Add the config // Add the config
await actionConfig.add(owner, repo, patterns, defaultBranch) await actionConfig.add(owner, repo, patterns, defaultBranch, ignoreTags)
} }
catch (err) { catch (err) {
// Help // Help
@@ -50,6 +51,7 @@ class Args {
repo = '' repo = ''
patterns = [] patterns = []
defaultBranch = '' defaultBranch = ''
ignoreTags = []
} }
/** /**
@@ -58,7 +60,7 @@ class Args {
*/ */
function getArgs() { function getArgs() {
// Parse // Parse
const parsedArgs = argHelper.parse([], ['default-branch']) const parsedArgs = argHelper.parse([], ['default-branch', 'ignore-tags'])
if (parsedArgs.arguments.length < 1) { if (parsedArgs.arguments.length < 1) {
argHelper.throwError('Expected at least one arg') argHelper.throwError('Expected at least one arg')
} }
@@ -81,17 +83,32 @@ function getArgs() {
} }
} }
// Parse ignore-tags (comma-separated version prefixes like v1,v2)
// These are converted to regex patterns that match the version and all its sub-versions
let ignoreTags = []
if (parsedArgs.options['ignore-tags']) {
const prefixes = parsedArgs.options['ignore-tags'].split(',').map(t => t.trim()).filter(t => t)
for (const prefix of prefixes) {
// Convert simple version prefix like "v1" to regex pattern "^v1(\\..*)?$"
// This matches "v1", "v1.0", "v1.0.0", etc.
const escapedPrefix = prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
ignoreTags.push(`^${escapedPrefix}(\\..*)?$`)
}
}
return { return {
owner: splitNwo[0], owner: splitNwo[0],
repo: splitNwo[1], repo: splitNwo[1],
patterns: patterns, patterns: patterns,
defaultBranch: parsedArgs.options['default-branch'] defaultBranch: parsedArgs.options['default-branch'],
ignoreTags: ignoreTags
} }
} }
function printUsage() { function printUsage() {
console.error('USAGE: add-action.sh [--default-branch branch] nwo [(+|-)regexp [...]]') console.error('USAGE: add-action.sh [--default-branch branch] [--ignore-tags versions] nwo [(+|-)regexp [...]]')
console.error(` --default-branch Default branch name. For example: master`) console.error(` --default-branch Default branch name. For example: master`)
console.error(` --ignore-tags Comma-separated version prefixes to ignore. For example: v1,v2`)
console.error(` nwo Name with owner. For example: actions/checkout`) console.error(` nwo Name with owner. For example: actions/checkout`)
console.error(` regexp Refs to include or exclude. Default: ${actionConfig.defaultPatterns.join(' ')}`) console.error(` regexp Refs to include or exclude. Default: ${actionConfig.defaultPatterns.join(' ')}`)
} }
+105
View File
@@ -0,0 +1,105 @@
const actionConfig = require('./action-config')
const argHelper = require('./arg-helper')
const debugHelper = require('./debug-helper')
const fs = require('fs')
async function main() {
try {
// Command line args
const args = getArgs()
// Get the action config file
const file = actionConfig.getFilePath(args.owner, args.repo)
debugHelper.debug(`file: ${file}`)
// Load the config
const config = await actionConfig.loadFromPath(file)
// Add ignore tags
if (!config.ignoreTags) {
config.ignoreTags = []
}
// Add new patterns (avoid duplicates)
for (const pattern of args.ignoreTags) {
if (!config.ignoreTags.includes(pattern)) {
config.ignoreTags.push(pattern)
}
}
// Write config back
await fs.promises.writeFile(file, JSON.stringify(config, null, ' '))
console.log(`Updated config file: ${file}`)
console.log(` ignoreTags: ${JSON.stringify(config.ignoreTags)}`)
}
catch (err) {
// Help
if (err.code === argHelper.helpCode) {
printUsage()
return
}
// Arg error?
if (err.code === argHelper.errorCode) {
printUsage()
console.error('')
}
// Print error
debugHelper.debug(err.stack)
console.error(`ERROR: ${err.message}`)
process.exitCode = 1
}
}
class Args {
owner = ''
repo = ''
ignoreTags = []
}
/**
* Get the command line args
* @returns {Args}
*/
function getArgs() {
const parsedArgs = argHelper.parse([], ['ignore-tags'])
const result = new Args()
// Validate ignore-tags is provided
if (!parsedArgs.options['ignore-tags']) {
argHelper.throwError('--ignore-tags is required')
}
// Parse ignore-tags (comma-separated version prefixes like v1,v2)
const prefixes = parsedArgs.options['ignore-tags'].split(',').map(t => t.trim()).filter(t => t)
for (const prefix of prefixes) {
// Convert simple version prefix like "v1" to regex pattern "^v1(\\..*)?$"
// This matches "v1", "v1.0", "v1.0.0", etc.
const escapedPrefix = prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
result.ignoreTags.push(`^${escapedPrefix}(\\..*)?$`)
}
// Validate exactly one arg
if (parsedArgs.arguments.length !== 1) {
argHelper.throwError('Expected exactly one arg (nwo)')
}
const nwo = parsedArgs.arguments[0]
const splitNwo = nwo.split('/')
if (splitNwo.length !== 2 || !splitNwo[0] || !splitNwo[1]) {
argHelper.throwError(`Invalid nwo '${nwo}'`)
}
result.owner = splitNwo[0]
result.repo = splitNwo[1]
return result
}
function printUsage() {
console.error('USAGE: add-ignore-tags.sh --ignore-tags versions nwo')
console.error(` --ignore-tags Comma-separated version prefixes to ignore. For example: v1,v2`)
console.error(` nwo Name with owner. For example: actions/checkout`)
}
main()
+18
View File
@@ -40,6 +40,10 @@ for json_file in $script_dir/../../config/actions/*.json; do
curl_download_commands+=("curl -s -S -L -o '$sha.zip' 'https://api.github.com/repos/$owner/$repo/zipball/$sha'") curl_download_commands+=("curl -s -S -L -o '$sha.zip' 'https://api.github.com/repos/$owner/$repo/zipball/$sha'")
done done
# Get an array of ignoreTags patterns (if present)
ignore_patterns=()
IFS=$'\n' read -r -d '' -a ignore_patterns < <( echo "$json" | jq --raw-output '.ignoreTags // [] | .[]' && printf '\0' )
# Get an array of tag info. Each item contains "<tag> <commit_sha>" # Get an array of tag info. Each item contains "<tag> <commit_sha>"
tag_info=() tag_info=()
IFS=$'\n' read -r -d '' -a tag_info < <( echo "$json" | jq --raw-output '.tags | to_entries | .[] | .key + " " + .value.commit' && printf '\0' ) IFS=$'\n' read -r -d '' -a tag_info < <( echo "$json" | jq --raw-output '.tags | to_entries | .[] | .key + " " + .value.commit' && printf '\0' )
@@ -49,6 +53,20 @@ for json_file in $script_dir/../../config/actions/*.json; do
tag="${split[0]}" tag="${split[0]}"
sha="${split[1]}" sha="${split[1]}"
# Check if the tag matches any ignore pattern
skip_tag=false
for pattern in "${ignore_patterns[@]}"; do
if [[ "$tag" =~ $pattern ]]; then
echo "Ignoring tag '$tag' (matches pattern '$pattern')"
skip_tag=true
break
fi
done
if [ "$skip_tag" = true ]; then
continue
fi
# Append curl download command # Append curl download command
curl_download_commands+=("curl -s -S -L -o '$sha.tar.gz' 'https://api.github.com/repos/$owner/$repo/tarball/$sha'") curl_download_commands+=("curl -s -S -L -o '$sha.tar.gz' 'https://api.github.com/repos/$owner/$repo/tarball/$sha'")
curl_download_commands+=("curl -s -S -L -o '$sha.zip' 'https://api.github.com/repos/$owner/$repo/zipball/$sha'") curl_download_commands+=("curl -s -S -L -o '$sha.zip' 'https://api.github.com/repos/$owner/$repo/zipball/$sha'")
+2 -1
View File
@@ -22,8 +22,9 @@ async function main() {
const repo = config.repo const repo = config.repo
const patterns = config.patterns const patterns = config.patterns
const defaultBranch = config.defaultBranch const defaultBranch = config.defaultBranch
const ignoreTags = config.ignoreTags
assert.ok(patterns && patterns.length, 'Existing patterns must not be empty') assert.ok(patterns && patterns.length, 'Existing patterns must not be empty')
await actionConfig.add(owner, repo, patterns, defaultBranch) await actionConfig.add(owner, repo, patterns, defaultBranch, ignoreTags)
} }
} }
catch (err) { catch (err) {