Skip to content

Commit

Permalink
Support matrix (#196)
Browse files Browse the repository at this point in the history
Co-authored-by: Jared Rhizor <[email protected]>
  • Loading branch information
tuliren and jrhizor authored Feb 27, 2023
1 parent 77aac58 commit 841ae2e
Show file tree
Hide file tree
Showing 18 changed files with 943 additions and 46 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/test-types.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,17 @@ defaults:

jobs:
build:
strategy:
matrix:
node-version: [ 16, 18 ]
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: '16'
node-version: ${{ matrix.node-version }}
- name: Setup cache
uses: actions/cache@v3
with:
Expand All @@ -57,6 +60,8 @@ jobs:
git diff
exit 1
fi
- name: Generate ts coverage report
run: yarn ts-coverage
- name: Run stoat action
uses: ./
if: always()
4 changes: 4 additions & 0 deletions .stoat/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ plugins:
metadata:
name: CLI Test Coverage
path: cli/coverage/lcov-report
types_ts_coverage:
metadata:
name: Types TS Coverage
path: types/coverage-ts
docs:
metadata:
name: Documentation
Expand Down
9 changes: 3 additions & 6 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,15 @@ inputs:
actual_sha:
default: ${{ github.event.pull_request.head.sha || github.sha }}
description: the commit pushed, not the commit post merge like github.sha
run_id:
default: ${{ github.run_id }}
description: A unique number for each workflow run within a repository. This number does not change if you re-run the workflow run.
run_number:
default: ${{ github.run_number }}
description: A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run.
run_attempt:
default: ${{ github.run_attempt }}
description: A unique number for each attempt of a particular workflow run in a repository. This number begins at 1 for the workflow run's first attempt, and increments with each re-run.
job_status:
default: ${{ job.status }}
description: current job status
run_matrix:
default: ${{ toJSON(matrix) }}
description: Matrix of the current run
runs:
using: 'node16'
main: 'action/dist/index.js'
91 changes: 91 additions & 0 deletions action/__tests__/matrixHelpers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { describe, expect, it } from '@jest/globals';

import { getMatrixId, getMatrixVariantString, isJobMatchMatrixVariant } from '../src/matrixHelpers';

describe('isJobMatchMatrixVariant', () => {
it('returns true when matrix map is null', () => {
expect(isJobMatchMatrixVariant('test', null)).toBe(true);
});

it('returns true when the job name matches a single variant', () => {
// string variant
expect(isJobMatchMatrixVariant('test (ubuntu-latest)', { os: 'ubuntu-latest' })).toBe(true);
expect(isJobMatchMatrixVariant('test (ubuntu-latest)', { os: 'windows-latest' })).toBe(false);

// number variant
expect(isJobMatchMatrixVariant('test (16)', { nodeVersion: 16 })).toBe(true);
expect(isJobMatchMatrixVariant('test (16)', { nodeVersion: 18 })).toBe(false);
});

it('returns true when the job name includes all variants', () => {
expect(
isJobMatchMatrixVariant('test (ubuntu-latest, 12, 12)', {
os: 'ubuntu-latest',
nodeVersion: 12,
number: '12'
})
).toBe(true);

expect(
isJobMatchMatrixVariant('test (ubuntu-latest, 12, 122)', {
os: 'ubuntu-latest',
nodeVersion: 12,
// this variant does not match
number: '12'
})
).toBe(false);
});

it('correctly parses the variants in the name', () => {
// The job includes a distraction that looks like variants.
// Only the strings in the second set of parentheses are true variants.
const jobName = 'test (ubuntu-latest, 14, 12) (ubuntu-latest, 12, 12)';
expect(
isJobMatchMatrixVariant(jobName, {
os: 'ubuntu-latest',
nodeVersion: 12,
number: '12'
})
).toBe(true);

expect(
isJobMatchMatrixVariant(jobName, {
os: 'ubuntu-latest',
nodeVersion: 14,
number: '12'
})
).toBe(false);
});
});

describe('getMatrixId', () => {
it('returns an empty string when matrix map is null', () => {
expect(getMatrixId(null)).toBe('');
});

it('returns a unique id from matrix variants', () => {
expect(
getMatrixId({
fieldC: 'b',
fieldA: 14,
fieldB: 'a'
})
).toBe('14-a-b');
});
});

describe('getMatrixVariantString', () => {
it('returns an empty string when matrix map is null', () => {
expect(getMatrixVariantString(null)).toBe('');
});

it('returns a string from matrix variants', () => {
expect(
getMatrixVariantString({
fieldC: 'b',
fieldA: 14,
fieldB: 'a'
})
).toBe('fieldA: 14, fieldB: a, fieldC: b');
});
});
1 change: 1 addition & 0 deletions action/__tests__/plugins/staticHosting/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ describe('static hosting plugin', () => {
ghRunNumber: 2000,
ghRunAttempt: 1,
ghToken: 'token',
ghRunMatrix: null,
stepsSucceeded: true
};

Expand Down
81 changes: 70 additions & 11 deletions action/dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -87418,6 +87418,51 @@ function getTypedStoatConfig(stoatConfig) {
});
}

// EXTERNAL MODULE: ./node_modules/lodash/lodash.js
var lodash = __nccwpck_require__(250);
var lodash_default = /*#__PURE__*/__nccwpck_require__.n(lodash);
;// CONCATENATED MODULE: ./src/matrixHelpers.ts

/**
* Check whether a job id or name contains all the matrix variants.
*/
const isJobMatchMatrixVariant = (jobName, matrix) => {
if (matrix === null) {
return true;
}
const variants = jobName
.substring(jobName.lastIndexOf('(') + 1, jobName.lastIndexOf(')'))
.split(',')
.map((v) => v.trim());
return lodash_default().isEqual(lodash_default().sortBy(variants), lodash_default().sortBy(Object.values(matrix).map((v) => String(v))));
};
/**
* Generate a unique id from matrix variants.
* Variants are sorted by variant key names, and concatenated with hyphens.
* E.g. { os: 'ubuntu-latest', nodeVersion: 13 } => '13-ubuntu-latest'
*/
const getMatrixId = (runMatrix) => {
if (runMatrix === null) {
return '';
}
return lodash_default().sortBy(Object.entries(runMatrix), ([key]) => key)
.map(([, value]) => String(value))
.join('-');
};
/**
* Generate a string from matrix key and variants.
* Key variant pairs are sorted by variant key names, and concatenated with commas.
* E.g. { os: 'ubuntu-latest', nodeVersion: 13 } => 'os: 13, nodeVersion: ubuntu-latest'
*/
const getMatrixVariantString = (runMatrix) => {
if (runMatrix === null) {
return '';
}
return lodash_default().sortBy(Object.entries(runMatrix), ([key]) => key)
.map(([key, value]) => `${key}: ${value}`)
.join(', ');
};

// EXTERNAL MODULE: ./node_modules/@actions/exec/lib/exec.js
var exec = __nccwpck_require__(1514);
// EXTERNAL MODULE: external "path"
Expand Down Expand Up @@ -87478,22 +87523,32 @@ var staticHosting_helpers_awaiter = (undefined && undefined.__awaiter) || functi



const processPath = (taskId, taskConfig, { ghRepository: { owner: ghOwner, repo: ghRepo }, ghBranch, ghPullRequestNumber, ghSha, ghToken, stepsSucceeded }, stoatConfigFileId, pathToUpload) => staticHosting_helpers_awaiter(void 0, void 0, void 0, function* () {

const processPath = (taskId, taskConfig, { ghRepository: { owner: ghOwner, repo: ghRepo }, ghBranch, ghPullRequestNumber, ghSha, ghJob, ghToken, ghRunMatrix, stepsSucceeded }, stoatConfigFileId, pathToUpload) => staticHosting_helpers_awaiter(void 0, void 0, void 0, function* () {
// get signed url
const isFile = external_fs_default().lstatSync(pathToUpload).isFile();
const { signedUrl, fields, objectPath, hostingUrl } = yield createSignedUrl({
ghOwner,
ghRepo,
ghSha,
ghToken,
taskId,
taskId: ghRunMatrix ? `${taskId}-${getMatrixId(ghRunMatrix)}` : taskId,
filename: isFile ? (0,external_path_.basename)(pathToUpload) : undefined
});
// upload directory
core.info(`[${taskId}] Uploading ${pathToUpload} to ${objectPath}...`);
yield uploadPath(signedUrl, fields, pathToUpload, objectPath);
// submit partial config
const renderedPlugin = Object.assign(Object.assign({}, taskConfig), { sha: ghSha, link: taskConfig.file_viewer ? `https://www.stoat.dev/file-viewer?root=${hostingUrl}` : hostingUrl, status: stepsSucceeded ? '✅' : '❌' });
const link = taskConfig.file_viewer ? `https://www.stoat.dev/file-viewer?root=${hostingUrl}` : hostingUrl;
const status = stepsSucceeded ? '✅' : '❌';
const renderedPlugin = ghRunMatrix
? Object.assign(Object.assign({}, taskConfig), { task_type: 'variants', sha: ghSha, variants: {
[getMatrixVariantString(ghRunMatrix)]: {
link,
status
}
} }) : Object.assign(Object.assign({}, taskConfig), { task_type: 'default', sha: ghSha, link,
status });
const requestBody = {
ghOwner,
ghRepo,
Expand Down Expand Up @@ -87658,7 +87713,7 @@ const getSubtaskId = (directory) => {
const subtaskId = directory.replace(/^\.\//g, '').replace(/\//g, '-');
return subtaskId === '' ? '-' : subtaskId;
};
const runAutoHostingPlugin = (taskId, taskConfig, { ghToken, ghRepository: { repo, owner }, ghBranch, ghPullRequestNumber, ghSha, stepsSucceeded }, stoatConfigFileId) => plugin_awaiter(void 0, void 0, void 0, function* () {
const runAutoHostingPlugin = (taskId, taskConfig, { ghToken, ghRepository: { repo, owner }, ghBranch, ghPullRequestNumber, ghSha, ghRunMatrix, stepsSucceeded }, stoatConfigFileId) => plugin_awaiter(void 0, void 0, void 0, function* () {
core.info(`[${taskId}] Running auto hosting plugin (stoat config ${stoatConfigFileId})`);
core.info(`[${taskId}] Current directory: ${process.cwd()}`);
const { exitCode, stdout, stderr } = yield exec.getExecOutput('/bin/sh', ['-c', "find . ! -path '*/node_modules/*' -type f -name 'index.html' | sed -r 's|/[^/]+$||' | sort | uniq"], { silent: true });
Expand Down Expand Up @@ -87687,6 +87742,7 @@ const runAutoHostingPlugin = (taskId, taskConfig, { ghToken, ghRepository: { rep
ghPullRequestNumber,
ghSha,
ghToken,
ghRunMatrix,
stepsSucceeded
}, stoatConfigFileId, directory);
}
Expand All @@ -87710,9 +87766,6 @@ var external_crypto_ = __nccwpck_require__(6113);
// EXTERNAL MODULE: ./node_modules/jimp/dist/index.js
var dist = __nccwpck_require__(3794);
var dist_default = /*#__PURE__*/__nccwpck_require__.n(dist);
// EXTERNAL MODULE: ./node_modules/lodash/lodash.js
var lodash = __nccwpck_require__(250);
var lodash_default = /*#__PURE__*/__nccwpck_require__.n(lodash);
// EXTERNAL MODULE: ./node_modules/pixelmatch/index.js
var pixelmatch = __nccwpck_require__(6097);
var pixelmatch_default = /*#__PURE__*/__nccwpck_require__.n(pixelmatch);
Expand Down Expand Up @@ -88116,7 +88169,7 @@ var staticHosting_plugin_awaiter = (undefined && undefined.__awaiter) || functio



const runStaticHostingPlugin = (taskId, taskConfig, { ghToken, ghRepository: { repo, owner }, ghBranch, ghPullRequestNumber, ghSha, stepsSucceeded }, stoatConfigFileId) => staticHosting_plugin_awaiter(void 0, void 0, void 0, function* () {
const runStaticHostingPlugin = (taskId, taskConfig, { ghToken, ghRepository: { repo, owner }, ghBranch, ghPullRequestNumber, ghSha, ghJob, ghRunMatrix, stepsSucceeded }, stoatConfigFileId) => staticHosting_plugin_awaiter(void 0, void 0, void 0, function* () {
core.info(`[${taskId}] Running static hosting plugin (stoat config ${stoatConfigFileId})`);
const currentDirectory = process.cwd();
core.info(`[${taskId}] Current directory: ${currentDirectory}`);
Expand All @@ -88135,6 +88188,8 @@ const runStaticHostingPlugin = (taskId, taskConfig, { ghToken, ghRepository: { r
ghBranch,
ghPullRequestNumber,
ghSha,
ghJob,
ghRunMatrix,
ghToken,
stepsSucceeded
}, stoatConfigFileId, pathToUpload);
Expand Down Expand Up @@ -88443,6 +88498,7 @@ var app_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _argu




function getGhCommitTimestamp(octokit, repository, repoSha) {
var _a;
return app_awaiter(this, void 0, void 0, function* () {
Expand Down Expand Up @@ -88495,6 +88551,8 @@ function run(stoatConfig) {
repo: github.context.repo.repo,
run_id: github.context.runId
});
const runMatrix = JSON.parse(core.getInput('run_matrix'));
core.info(`Run matrix: ${JSON.stringify(runMatrix, null, 2)}`);
const ghJobId = github.context.job;
const ghJobRunId = github.context.runId;
// There is no precise way to find the current running job. To do that,
Expand All @@ -88504,7 +88562,7 @@ function run(stoatConfig) {
// there are matrix variants, job.name refers to the custom name or a name
// with the matrix variants. In those cases, nothing from github.context
// can be used to find the job.
const ghJob = jobListResponse.data.jobs.find((j) => j.run_id === ghJobRunId && j.status === 'in_progress');
const ghJob = jobListResponse.data.jobs.find((j) => j.run_id === ghJobRunId && j.status === 'in_progress' && isJobMatchMatrixVariant(j.name, runMatrix));
if (ghJob !== undefined) {
core.info(`Current job: ${ghJob.name} (run id: ${ghJob.run_id})`);
}
Expand All @@ -88522,10 +88580,11 @@ function run(stoatConfig) {
ghJob,
ghSha: repoSha,
ghCommitTimestamp,
ghRunId: parseInt(core.getInput('run_id')),
ghRunNumber: parseInt(core.getInput('run_number')),
ghRunId: github.context.runId,
ghRunNumber: github.context.runNumber,
ghRunAttempt: parseInt(core.getInput('run_attempt')),
ghToken: token,
ghRunMatrix: runMatrix,
stepsSucceeded
};
core.info('Loading template...');
Expand Down
2 changes: 1 addition & 1 deletion action/dist/index.js.map

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions action/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { GitHub } from '@actions/github/lib/utils';

import { uploadWorkflowOutputs } from './commentHelpers';
import { getTypedStoatConfig, readStoatConfig } from './configHelpers';
import { isJobMatchMatrixVariant } from './matrixHelpers';
import { runPlugins } from './plugins/pluginRunner';
import { getCurrentPullRequestNumber } from './pullRequestHelpers';
import { waitForStoatDevServer } from './stoatApiHelpers';
import { getTemplate } from './templateHelpers';
import { GithubActionRun, GithubJob, Repository } from './types';
import { GithubActionRun, GithubJob, GithubRunMatrix, Repository } from './types';

async function getGhCommitTimestamp(
octokit: InstanceType<typeof GitHub>,
Expand Down Expand Up @@ -69,6 +70,8 @@ async function run(stoatConfig: any) {
repo: github.context.repo.repo,
run_id: github.context.runId
});
const runMatrix: GithubRunMatrix = JSON.parse(core.getInput('run_matrix'));
core.info(`Run matrix: ${JSON.stringify(runMatrix, null, 2)}`);
const ghJobId = github.context.job;
const ghJobRunId = github.context.runId;
// There is no precise way to find the current running job. To do that,
Expand All @@ -79,7 +82,7 @@ async function run(stoatConfig: any) {
// with the matrix variants. In those cases, nothing from github.context
// can be used to find the job.
const ghJob: GithubJob | undefined = jobListResponse.data.jobs.find(
(j) => j.run_id === ghJobRunId && j.status === 'in_progress'
(j) => j.run_id === ghJobRunId && j.status === 'in_progress' && isJobMatchMatrixVariant(j.name, runMatrix)
);
if (ghJob !== undefined) {
core.info(`Current job: ${ghJob.name} (run id: ${ghJob.run_id})`);
Expand All @@ -105,10 +108,11 @@ async function run(stoatConfig: any) {
ghJob,
ghSha: repoSha,
ghCommitTimestamp,
ghRunId: parseInt(core.getInput('run_id')),
ghRunNumber: parseInt(core.getInput('run_number')),
ghRunId: github.context.runId,
ghRunNumber: github.context.runNumber,
ghRunAttempt: parseInt(core.getInput('run_attempt')),
ghToken: token,
ghRunMatrix: runMatrix,
stepsSucceeded
};

Expand Down
Loading

0 comments on commit 841ae2e

Please sign in to comment.