diff --git a/eslint.config.mjs b/eslint.config.mjs index 530d49b..aa5f724 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -8,7 +8,7 @@ import eslintPluginPrettier from 'eslint-plugin-prettier'; export default defineConfig([ { - ignores: ['.yarn/**/*', 'coverage/**/*', 'dist/**/*'] + ignores: ['.yarn/**/*', 'coverage/**/*', 'dist/**/*', 'subaction/matrix/dist/**/*'] }, js.configs.recommended, ...tseslint.configs['flat/recommended'], diff --git a/package.json b/package.json index 13af038..e65f4dc 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ "test": "vitest run", "license": "generate-license-file --input package.json --output dist/licenses.txt --overwrite --ci --no-spinner --eol lf" }, + "workspaces": [ + "subaction/matrix" + ], "repository": { "type": "git", "url": "git+https://github.com/docker/bake-action.git" diff --git a/subaction/matrix/action.yml b/subaction/matrix/action.yml index 4982c35..ce71831 100644 --- a/subaction/matrix/action.yml +++ b/subaction/matrix/action.yml @@ -20,82 +20,7 @@ inputs: outputs: matrix: description: Matrix configuration - value: ${{ steps.generate.outputs.includes }} runs: - using: composite - steps: - - - name: Generate - id: generate - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - INPUT_WORKDIR: ${{ inputs.workdir }} - INPUT_FILES: ${{ inputs.files }} - INPUT_TARGET: ${{ inputs.target }} - INPUT_FIELDS: ${{ inputs.fields }} - with: - script: | - function getInputList(name) { - return core.getInput(name) ? core.getInput(name).split(/[\r?\n,]+/).filter(x => x !== '') : []; - } - - const workdir = core.getInput('workdir'); - const files = getInputList('files'); - const target = core.getInput('target'); - const fields = getInputList('fields'); - - let def = {}; - await core.group(`Parsing definition`, async () => { - let args = ['buildx', 'bake']; - for (const file of files) { - args.push('--file', file); - } - if (target) { - args.push(target); - } - args.push('--print'); - const res = await exec.getExecOutput('docker', args, { - ignoreReturnCode: true, - silent: true, - cwd: workdir - }); - if (res.stderr.length > 0 && res.exitCode != 0) { - throw new Error(res.stderr); - } - def = JSON.parse(res.stdout.trim()); - core.info(JSON.stringify(def, null, 2)); - }); - - await core.group(`Generating matrix`, async () => { - const result = []; - for (const targetName of Object.keys(def.target)) { - const target = def.target[targetName]; - const entry = { target: targetName }; - if (fields.length === 0) { - result.push({ ...entry }); - continue; - } - let fieldFound = false; - Object.keys(target).forEach(field => { - if (fields.includes(field)) { - fieldFound = true; - const value = target[field]; - if (Array.isArray(value)) { - value.forEach((v) => { - entry[field] = v; - result.push({ ...entry }); - }); - } else { - entry[field] = value; - result.push({ ...entry }); - } - } - }); - if (!fieldFound) { - result.push({ ...entry }); - } - } - core.info(JSON.stringify(result, null, 2)); - core.setOutput('includes', JSON.stringify(result)); - }); + using: 'node24' + main: 'dist/index.cjs' diff --git a/subaction/matrix/package.json b/subaction/matrix/package.json new file mode 100644 index 0000000..43abd8e --- /dev/null +++ b/subaction/matrix/package.json @@ -0,0 +1,26 @@ +{ + "name": "docker-buildx-bake-matrix", + "description": "GitHub Action to generate a multi-dimension GitHub matrix from bake definition files", + "type": "module", + "main": "src/main.ts", + "scripts": { + "build": "esbuild src/main.ts --bundle --platform=node --target=node24 --format=cjs --outfile=dist/index.cjs --sourcemap --minify && yarn run license", + "license": "generate-license-file --input package.json --output dist/licenses.txt --overwrite --ci --no-spinner --eol lf" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/docker/bake-action.git", + "directory": "subaction/matrix" + }, + "author": "Docker Inc.", + "license": "Apache-2.0", + "dependencies": { + "@actions/core": "catalog:", + "@actions/exec": "^3.0.0", + "@docker/actions-toolkit": "catalog:" + }, + "devDependencies": { + "esbuild": "catalog:", + "generate-license-file": "catalog:" + } +} diff --git a/subaction/matrix/src/main.ts b/subaction/matrix/src/main.ts new file mode 100644 index 0000000..72ae65f --- /dev/null +++ b/subaction/matrix/src/main.ts @@ -0,0 +1,91 @@ +import * as core from '@actions/core'; +import * as exec from '@actions/exec'; +import * as actionsToolkit from '@docker/actions-toolkit'; + +import {BakeDefinition, Target} from '@docker/actions-toolkit/lib/types/buildx/bake.js'; + +actionsToolkit.run( + // main + async () => { + const workdir = core.getInput('workdir'); + const files = getInputList('files'); + const target = core.getInput('target'); + const fields = getInputList('fields'); + + let def: BakeDefinition; + await core.group(`Parsing definition`, async () => { + const args = ['buildx', 'bake']; + for (const file of files) { + args.push('--file', file); + } + if (target) { + args.push(target); + } + args.push('--print'); + const res = await exec.getExecOutput('docker', args, { + ignoreReturnCode: true, + silent: true, + cwd: workdir + }); + if (res.stderr.length > 0 && res.exitCode != 0) { + throw new Error(res.stderr); + } + def = JSON.parse(res.stdout.trim()); + core.info(JSON.stringify(def, null, 2)); + }); + + await core.group(`Generating matrix`, async () => { + const result: Array = []; + for (const targetName of Object.keys(def.target)) { + const target = def.target[targetName]; + const entry: MatrixConfigEntry = {target: targetName}; + if (fields.length === 0) { + result.push({...entry}); + continue; + } + let fieldFound = false; + Object.keys(target).forEach(field => { + if (fields.includes(field)) { + fieldFound = true; + const value = target[field]; + if (Array.isArray(value)) { + value.forEach(v => { + entry[field] = v; + result.push({...entry}); + }); + } else { + entry[field] = value; + result.push({...entry}); + } + } + }); + if (!fieldFound) { + result.push({...entry}); + } + } + core.info(JSON.stringify(result, null, 2)); + core.setOutput('matrix', JSON.stringify(result)); + }); + } +); + +function getInputList(name: string) { + return core.getInput(name) + ? core + .getInput(name) + .split(/[\r?\n,]+/) + .filter(x => x !== '') + : []; +} + +type Optional = { + [Property in keyof Type]?: Type[Property]; +}; + +type ArrayToSingleEntry = { + [Property in keyof Type]: Type[Property] extends Array ? Entry : Type[Property]; +}; + +type MatrixConfigEntry = Optional> & { + target: string; +}; diff --git a/tsconfig.json b/tsconfig.json index be1cc9f..1750d6d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,7 @@ "useUnknownInCatchVariables": false, }, "include": [ - "src/**/*.ts" + "src/**/*.ts", + "subaction/matrix/src/**/*.ts" ] } diff --git a/vitest.config.ts b/vitest.config.ts index b5ac930..f53d140 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -9,8 +9,8 @@ export default defineConfig({ coverage: { provider: 'v8', reporter: ['clover'], - include: ['src/**/*.ts'], - exclude: ['src/**/main.ts'] + include: ['src/**/*.ts', 'subaction/matrix/src/**/*.ts'], + exclude: ['src/**/main.ts', 'subaction/matrix/src/**/main.ts'] } } }); diff --git a/yarn.lock b/yarn.lock index e6a4a18..1cfd575 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2830,23 +2830,35 @@ __metadata: languageName: node linkType: hard +"docker-buildx-bake-matrix@workspace:subaction/matrix": + version: 0.0.0-use.local + resolution: "docker-buildx-bake-matrix@workspace:subaction/matrix" + dependencies: + "@actions/core": "catalog:" + "@actions/exec": "npm:^3.0.0" + "@docker/actions-toolkit": "catalog:" + esbuild: "catalog:" + generate-license-file: "catalog:" + languageName: unknown + linkType: soft + "docker-buildx-bake@workspace:.": version: 0.0.0-use.local resolution: "docker-buildx-bake@workspace:." dependencies: - "@actions/core": "npm:^3.0.1" - "@docker/actions-toolkit": "npm:^0.100.0" + "@actions/core": "catalog:" + "@docker/actions-toolkit": "catalog:" "@eslint/js": "npm:^9.39.3" "@types/node": "npm:^24.11.0" "@typescript-eslint/eslint-plugin": "npm:^8.56.1" "@typescript-eslint/parser": "npm:^8.56.1" "@vitest/coverage-v8": "npm:^4.0.18" "@vitest/eslint-plugin": "npm:^1.6.9" - esbuild: "npm:^0.28.0" + esbuild: "catalog:" eslint: "npm:^9.39.3" eslint-config-prettier: "npm:^10.1.8" eslint-plugin-prettier: "npm:^5.5.5" - generate-license-file: "npm:^4.1.1" + generate-license-file: "catalog:" globals: "npm:^17.3.0" handlebars: "npm:^4.7.9" prettier: "npm:^3.8.1"