diff --git a/README.md b/README.md index 2eaae15..ac80abe 100644 --- a/README.md +++ b/README.md @@ -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. +### 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? Please read the doc @kenmuse has put together at: https://www.kenmuse.com/blog/building-github-actions-runner-images-with-an-action-archive-cache/ diff --git a/script/add-ignore-tags.sh b/script/add-ignore-tags.sh new file mode 100755 index 0000000..127e604 --- /dev/null +++ b/script/add-ignore-tags.sh @@ -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 diff --git a/script/internal/action-config.js b/script/internal/action-config.js index 353bce8..e01a7ac 100644 --- a/script/internal/action-config.js +++ b/script/internal/action-config.js @@ -26,6 +26,12 @@ class ActionConfig { */ patterns = [] + /** + * Tag patterns to ignore during packaging + * @type {string[]|undefined} + */ + ignoreTags = undefined + /** * Branch versions (ref to commit SHA) * @type {{[ref: string]: string}} @@ -63,12 +69,13 @@ exports.TagVersion = TagVersion /** * Adds a new action config file * @param {string} owner - * @param {string} repos + * @param {string} repo * @param {string[]} patternStrings * @param {string} defaultBranch + * @param {string[]|undefined} ignoreTags * @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(repo, "Arg 'repo' must not be empty") assert.ok(patternStrings, "Arg 'patternStrings' must not be null") @@ -84,6 +91,9 @@ async function add(owner, repo, patternStrings, defaultBranch) { config.owner = owner config.repo = repo config.patterns = patternStrings + if (ignoreTags && ignoreTags.length > 0) { + config.ignoreTags = ignoreTags + } config.defaultBranch = defaultBranch const tempDir = path.join(paths.temp, `${owner}_${repo}`) diff --git a/script/internal/add-action.js b/script/internal/add-action.js index 251e15f..024aabe 100644 --- a/script/internal/add-action.js +++ b/script/internal/add-action.js @@ -14,6 +14,7 @@ async function main() { const repo = args.repo const patterns = args.patterns const defaultBranch = args.defaultBranch || 'master' + const ignoreTags = args.ignoreTags // File exists? const file = actionConfig.getFilePath(owner, repo) @@ -23,7 +24,7 @@ async function main() { await fsHelper.reinitTemp() // Add the config - await actionConfig.add(owner, repo, patterns, defaultBranch) + await actionConfig.add(owner, repo, patterns, defaultBranch, ignoreTags) } catch (err) { // Help @@ -50,6 +51,7 @@ class Args { repo = '' patterns = [] defaultBranch = '' + ignoreTags = [] } /** @@ -58,7 +60,7 @@ class Args { */ function getArgs() { // Parse - const parsedArgs = argHelper.parse([], ['default-branch']) + const parsedArgs = argHelper.parse([], ['default-branch', 'ignore-tags']) if (parsedArgs.arguments.length < 1) { 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 { owner: splitNwo[0], repo: splitNwo[1], patterns: patterns, - defaultBranch: parsedArgs.options['default-branch'] + defaultBranch: parsedArgs.options['default-branch'], + ignoreTags: ignoreTags } } 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(` --ignore-tags Comma-separated version prefixes to ignore. For example: v1,v2`) console.error(` nwo Name with owner. For example: actions/checkout`) console.error(` regexp Refs to include or exclude. Default: ${actionConfig.defaultPatterns.join(' ')}`) } diff --git a/script/internal/add-ignore-tags.js b/script/internal/add-ignore-tags.js new file mode 100644 index 0000000..797dab2 --- /dev/null +++ b/script/internal/add-ignore-tags.js @@ -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() diff --git a/script/internal/generate-scripts.sh b/script/internal/generate-scripts.sh index 3530c02..6f3b64a 100755 --- a/script/internal/generate-scripts.sh +++ b/script/internal/generate-scripts.sh @@ -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'") 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_info=() 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]}" 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 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'") diff --git a/script/internal/update-action.js b/script/internal/update-action.js index 11d7251..b43a328 100644 --- a/script/internal/update-action.js +++ b/script/internal/update-action.js @@ -22,8 +22,9 @@ async function main() { const repo = config.repo const patterns = config.patterns const defaultBranch = config.defaultBranch + const ignoreTags = config.ignoreTags 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) {