Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 112 additions & 6 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,129 @@ jobs:
with:
persist-credentials: false

- name: Test Run
id: test-run
- name: Test with defaults
id: test-defaults
uses: ./
with:
placeholder: "test_placeholder"
matrix: '["a","b","c"]'

- name: Assert placeholder
- name: Assert default output
uses: nick-fields/assert-action@v2
with:
actual: ${{ steps.test-run.outputs.placeholder }}
expected: "test_placeholder"
actual: ${{ steps.test-defaults.outputs.matrix }}
expected: '["a b c"]'

e2e-group-size:
runs-on: ubuntu-latest

permissions:
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Test with group size
id: test-group-size
uses: ./
with:
matrix: '["a","b","c","d","e"]'
target-group-size: 2

- name: Assert grouped output
uses: nick-fields/assert-action@v2
with:
actual: ${{ steps.test-group-size.outputs.matrix }}
expected: '["a b","c d","e"]'

e2e-prefix:
runs-on: ubuntu-latest

permissions:
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Test with prefix
id: test-prefix
uses: ./
with:
matrix: '["pkg-1","pkg-2","pkg-3"]'
target-group-size: 2
result-item-prefix: "/data/"

- name: Assert prefixed output
uses: nick-fields/assert-action@v2
with:
actual: ${{ steps.test-prefix.outputs.matrix }}
expected: '["/data/pkg-1 /data/pkg-2","/data/pkg-3"]'

e2e-separator:
runs-on: ubuntu-latest

permissions:
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Test with custom separator
id: test-separator
uses: ./
with:
matrix: '["a","b","c"]'
target-group-size: 2
result-format-plain-separator: ","

- name: Assert separator output
uses: nick-fields/assert-action@v2
with:
actual: ${{ steps.test-separator.outputs.matrix }}
expected: '["a,b","c"]'

e2e-empty:
runs-on: ubuntu-latest

permissions:
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Test with empty array
id: test-empty
uses: ./
with:
matrix: "[]"

- name: Assert empty output
uses: nick-fields/assert-action@v2
with:
actual: ${{ steps.test-empty.outputs.matrix }}
expected: "[]"

e2e:
runs-on: ubuntu-latest
if: always() && !cancelled()

needs:
- e2e-default
- e2e-group-size
- e2e-prefix
- e2e-separator
- e2e-empty

steps:
- name: Collect Results
Expand Down
39 changes: 33 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![CI](https://github.com/ovsds/split-matrix-action/workflows/Check%20PR/badge.svg)](https://github.com/ovsds/split-matrix-action/actions?query=workflow%3A%22%22Check+PR%22%22)
[![GitHub Marketplace](https://img.shields.io/badge/Marketplace-Split%20Matrix-blue.svg)](https://github.com/marketplace/actions/split-matrix)

Split Matrix Action
Splits a JSON array into groups for GitHub Actions matrix strategy.

## Usage

Expand All @@ -16,26 +16,53 @@ jobs:
- name: Split Matrix
id: split-matrix
uses: ovsds/split-matrix-action@v1
with:
matrix: '["pkg-1", "pkg-2", "pkg-3", "pkg-4", "pkg-5"]'
target-group-size: 2
result-item-prefix: "/data/"

- name: Use matrix
run: echo '${{ steps.split-matrix.outputs.matrix }}'
# Output: ["/data/pkg-1 /data/pkg-2","/data/pkg-3 /data/pkg-4","/data/pkg-5"]
```

### Action Inputs

```yaml
inputs:
placeholder:
matrix:
description: |
Placeholder input to be replaced by real inputs
JSON array of strings to split into groups
required: true
default: "placeholder"
target-group-size:
description: |
Number of items per group
required: false
default: "10"
result-format:
description: |
Output format for groups. Supported: "plain"
required: false
default: "plain"
result-format-plain-separator:
description: |
Separator to join items within a group (used when result-format is "plain")
required: false
default: " "
result-item-prefix:
description: |
Prefix to prepend to each item
required: false
default: ""
```

### Action Outputs

```yaml
outputs:
placeholder:
matrix:
description: |
Placeholder output to be replaced by real outputs
JSON array of grouped strings
```

## Development
Expand Down
33 changes: 26 additions & 7 deletions action.yaml
Original file line number Diff line number Diff line change
@@ -1,23 +1,42 @@
name: "Split Matrix"
description: |
Split Matrix Action
Splits a JSON array into groups for GitHub Actions matrix strategy.

# https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#branding
branding:
icon: "message-square"
icon: "grid"
color: "gray-dark"

inputs:
placeholder:
matrix:
description: |
Placeholder input to be replaced by real inputs
JSON array of strings to split into groups
required: true
default: "placeholder"
target-group-size:
description: |
Number of items per group
required: false
default: "10"
result-format:
description: |
Output format for groups. Supported: "plain"
required: false
default: "plain"
result-format-plain-separator:
description: |
Separator to join items within a group (used when result-format is "plain")
required: false
default: " "
result-item-prefix:
description: |
Prefix to prepend to each item
required: false
default: ""

outputs:
placeholder:
matrix:
description: |
Placeholder output to be replaced by real outputs
JSON array of grouped strings

runs:
using: node24
Expand Down
84 changes: 75 additions & 9 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.Action = void 0;
exports.splitIntoGroups = splitIntoGroups;
function splitIntoGroups(list, targetGroupSize) {
if (list.length === 0) {
return [];
}
const groupCount = Math.ceil(list.length / targetGroupSize);
const baseSize = Math.floor(list.length / groupCount);
const remainder = list.length % groupCount;
const groups = [];
let offset = 0;
for (let i = 0; i < groupCount; i++) {
const size = baseSize + (i < remainder ? 1 : 0);
groups.push(list.slice(offset, offset + size));
offset += size;
}
return groups;
}
class Action {
static fromOptions(actionOptions) {
return new Action(actionOptions);
Expand All @@ -26,10 +43,16 @@ class Action {
}
run() {
return __awaiter(this, void 0, void 0, function* () {
this.options.logger(`Running with placeholder: ${this.options.placeholder}`);
return {
placeholder: this.options.placeholder,
};
const { matrix, targetGroupSize, resultFormat, resultFormatPlainSeparator, resultItemPrefix, logger } = this.options;
logger(`Splitting ${matrix.length} items into groups of ${targetGroupSize}`);
logger(`Result format: ${resultFormat}, separator: "${resultFormatPlainSeparator}", prefix: "${resultItemPrefix}"`);
if (matrix.length === 0) {
return { matrix: [] };
}
const groups = splitIntoGroups(matrix, targetGroupSize);
const result = groups.map((group) => group.map((item) => `${resultItemPrefix}${item}`).join(resultFormatPlainSeparator));
logger(`Created ${result.length} groups`);
return { matrix: result };
});
}
}
Expand All @@ -48,7 +71,11 @@ exports.parseActionInput = parseActionInput;
const parse_1 = __nccwpck_require__(789);
function parseActionInput(raw) {
return {
placeholder: (0, parse_1.parseNonEmptyString)(raw.placeholder),
matrix: (0, parse_1.parseJsonStringArray)(raw.matrix),
targetGroupSize: (0, parse_1.parsePositiveInteger)(raw.targetGroupSize),
resultFormat: (0, parse_1.parseResultFormat)(raw.resultFormat),
resultFormatPlainSeparator: raw.resultFormatPlainSeparator,
resultItemPrefix: raw.resultItemPrefix,
};
}

Expand All @@ -75,12 +102,16 @@ const action_1 = __nccwpck_require__(1536);
const input_1 = __nccwpck_require__(2868);
function getActionInput() {
return (0, input_1.parseActionInput)({
placeholder: (0, core_1.getInput)("placeholder"),
matrix: (0, core_1.getInput)("matrix"),
targetGroupSize: (0, core_1.getInput)("target-group-size"),
resultFormat: (0, core_1.getInput)("result-format"),
resultFormatPlainSeparator: (0, core_1.getInput)("result-format-plain-separator", { trimWhitespace: false }),
resultItemPrefix: (0, core_1.getInput)("result-item-prefix", { trimWhitespace: false }),
});
}
function setActionOutput(actionResult) {
(0, core_1.info)(`Action result: ${JSON.stringify(actionResult)}`);
(0, core_1.setOutput)("placeholder", actionResult.placeholder);
(0, core_1.setOutput)("matrix", JSON.stringify(actionResult.matrix));
}
function _main() {
return __awaiter(this, void 0, void 0, function* () {
Expand All @@ -93,7 +124,7 @@ function _main() {
function main() {
return __awaiter(this, void 0, void 0, function* () {
try {
_main();
yield _main();
}
catch (error) {
if (error instanceof Error) {
Expand All @@ -116,7 +147,7 @@ main();
"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.parseNonEmptyString = void 0;
exports.parseResultFormat = exports.parsePositiveInteger = exports.parseJsonStringArray = exports.parseNonEmptyString = void 0;
const parseNonEmptyString = (value) => {
if (!value) {
throw new Error(`Invalid ${value}, must be a non-empty string`);
Expand All @@ -127,6 +158,41 @@ const parseNonEmptyString = (value) => {
return value;
};
exports.parseNonEmptyString = parseNonEmptyString;
const parseJsonStringArray = (value) => {
let parsed;
try {
parsed = JSON.parse(value);
}
catch (_a) {
throw new Error(`Invalid JSON: ${value}`);
}
if (!Array.isArray(parsed)) {
throw new Error(`Expected a JSON array, got: ${typeof parsed}`);
}
for (const item of parsed) {
if (typeof item !== "string") {
throw new Error(`Expected all items to be strings, got: ${typeof item}`);
}
}
return parsed;
};
exports.parseJsonStringArray = parseJsonStringArray;
const parsePositiveInteger = (value) => {
const parsed = Number(value);
if (!Number.isInteger(parsed) || parsed <= 0) {
throw new Error(`Invalid ${value}, must be a positive integer`);
}
return parsed;
};
exports.parsePositiveInteger = parsePositiveInteger;
const VALID_RESULT_FORMATS = ["plain"];
const parseResultFormat = (value) => {
if (!VALID_RESULT_FORMATS.includes(value)) {
throw new Error(`Invalid result-format "${value}", must be one of: ${VALID_RESULT_FORMATS.join(", ")}`);
}
return value;
};
exports.parseResultFormat = parseResultFormat;


/***/ }),
Expand Down
Loading