Skip to content

Commit ddaf203

Browse files
committed
Add outputs: JSON, files, count
1 parent d6b1cb8 commit ddaf203

File tree

11 files changed

+306
-55
lines changed

11 files changed

+306
-55
lines changed

common/src/common-utils.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { setOutput } from '@actions/core';
12
import { getExecOutput } from '@actions/exec'
3+
import { stringifyForShell } from './serialization-utils';
24

35
export async function execCommand(command: string): Promise<string> {
46
const { stdout, stderr, exitCode } = await getExecOutput(command)
@@ -9,3 +11,9 @@ export async function execCommand(command: string): Promise<string> {
911

1012
return stdout.trim()
1113
}
14+
15+
export function setOutputs(values: Record<string, unknown>): void {
16+
for (const [key, value] of Object.entries(values)) {
17+
setOutput(key, stringifyForShell(value));
18+
}
19+
}

common/src/path-utils.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ function filterPathsImpl(
2626
): string[] {
2727
return patterns?.length === 0
2828
? paths
29-
: paths.filter(path => {
30-
return patterns.reduce((prevResult, pattern) => {
31-
return pattern.startsWith(NEGATION)
32-
? prevResult && !match(path, pattern.substring(1))
33-
: prevResult || match(path, pattern);
34-
}, false);
35-
});
29+
: paths.filter(path => testPath(path, patterns));
30+
}
31+
32+
export function testPath(path: string, patterns: string[]): boolean {
33+
return patterns.reduce((prevResult, pattern) => {
34+
return pattern.startsWith(NEGATION)
35+
? prevResult && !match(path, pattern.substring(1))
36+
: prevResult || match(path, pattern);
37+
}, false);
3638
}

common/src/pr-utils.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

2-
import * as core from '@actions/core'
3-
import { context, getOctokit } from '@actions/github'
2+
import * as core from '@actions/core';
3+
import { context, getOctokit } from '@actions/github';
4+
import { components } from '@octokit/openapi-types';
45
import { execCommand } from './common-utils';
56

67
export async function getPrRevisionRange(): Promise<{
@@ -42,14 +43,19 @@ function normalizeCommit(commit: string) {
4243
return commit === '0000000000000000000000000000000000000000' ? 'HEAD^' : commit;
4344
}
4445

45-
export async function getChangedFiles(token: string): Promise<string[]> {
46+
interface ChangedFile {
47+
path: string;
48+
status: components['schemas']['diff-entry']['status'];
49+
}
50+
51+
export async function getChangedFiles(token: string): Promise<ChangedFile[]> {
4652
return getChangedFilesImpl(token).then((files) => {
4753
core.info(`${files.length} changed files: ${JSON.stringify(files, undefined, 2)}`)
4854
return files;
4955
});
5056
}
5157

52-
async function getChangedFilesImpl(token: string): Promise<string[]> {
58+
async function getChangedFilesImpl(token: string): Promise<ChangedFile[]> {
5359
try {
5460
const octokit = getOctokit(token);
5561

@@ -58,13 +64,13 @@ async function getChangedFilesImpl(token: string): Promise<string[]> {
5864
return [];
5965
}
6066

61-
const files = await octokit.paginate(octokit.rest.pulls.listFiles, {
67+
const entries = await octokit.paginate(octokit.rest.pulls.listFiles, {
6268
owner: context.repo.owner,
6369
repo: context.repo.repo,
6470
pull_number: context.payload.pull_request.number,
6571
});
6672

67-
return files.map(file => file.filename);
73+
return entries.map(({ filename, status }) => ({ path: filename, status }));
6874
} catch (error) {
6975
core.setFailed(`Getting changed files failed with error: ${error}`);
7076
return [];
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { stringifyForShell } from "./serialization-utils";
2+
3+
describe('action utils', () => {
4+
5+
describe(stringifyForShell.name, () => {
6+
test.each([
7+
{ value: 123, expected: '123' },
8+
{ value: 'abc', expected: 'abc' },
9+
])('scalar cases [%#]', ({ value, expected }) => {
10+
expect(stringifyForShell(value)).toEqual(expected);
11+
});
12+
});
13+
14+
describe(stringifyForShell.name, () => {
15+
test.each([
16+
{ value: [123, 456], expected: '123 456' },
17+
{ value: ['abc', 'def'], expected: '\'abc\' \'def\'' },
18+
])('array values [%#]', ({ value, expected }) => {
19+
expect(stringifyForShell(value)).toEqual(expected);
20+
});
21+
});
22+
23+
});

common/src/serialization-utils.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
function stringifyArrayItem(item: unknown): string {
2+
switch (typeof item) {
3+
case 'number':
4+
return item.toString();
5+
6+
case 'string':
7+
return `'${item}'`;
8+
9+
default:
10+
return JSON.stringify(item);
11+
}
12+
}
13+
14+
export function stringifyForShell(value: unknown): string {
15+
16+
switch (typeof value) {
17+
18+
case 'string':
19+
return value;
20+
21+
case 'object':
22+
if (Array.isArray(value)) {
23+
return value.map(stringifyArrayItem).join(' ');
24+
}
25+
26+
if (value === null) {
27+
return '';
28+
}
29+
30+
return value.toString();
31+
32+
default:
33+
return JSON.stringify(value);
34+
}
35+
}

get-changed-files/action.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,11 @@ runs:
2121
main: dist/index.js
2222

2323
outputs:
24-
result:
25-
description: A file with the list of files in JSON format
24+
count:
25+
description: The count of all files changed in the PR
26+
files:
27+
description: Space-separated list of all files changed in the PR
28+
json:
29+
description: |
30+
Full output in JSON format.
31+
'Schema: { files: { path: string; status: string; } []; count: number; }'

get-changed-files/dist/index.js

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
1616
});
1717
};
1818
Object.defineProperty(exports, "__esModule", ({ value: true }));
19-
exports.execCommand = void 0;
19+
exports.setOutputs = exports.execCommand = void 0;
20+
const core_1 = __nccwpck_require__(5316);
2021
const exec_1 = __nccwpck_require__(110);
22+
const serialization_utils_1 = __nccwpck_require__(9091);
2123
function execCommand(command) {
2224
return __awaiter(this, void 0, void 0, function* () {
2325
const { stdout, stderr, exitCode } = yield (0, exec_1.getExecOutput)(command);
@@ -28,6 +30,12 @@ function execCommand(command) {
2830
});
2931
}
3032
exports.execCommand = execCommand;
33+
function setOutputs(values) {
34+
for (const [key, value] of Object.entries(values)) {
35+
(0, core_1.setOutput)(key, (0, serialization_utils_1.stringifyForShell)(value));
36+
}
37+
}
38+
exports.setOutputs = setOutputs;
3139

3240

3341
/***/ }),
@@ -126,7 +134,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
126134
return result;
127135
};
128136
Object.defineProperty(exports, "__esModule", ({ value: true }));
129-
exports.filterPaths = void 0;
137+
exports.testPath = exports.filterPaths = void 0;
130138
const core = __importStar(__nccwpck_require__(5316));
131139
const minimatch_1 = __nccwpck_require__(148);
132140
const NEGATION = '!';
@@ -146,14 +154,16 @@ exports.filterPaths = filterPaths;
146154
function filterPathsImpl(paths, patterns) {
147155
return (patterns === null || patterns === void 0 ? void 0 : patterns.length) === 0
148156
? paths
149-
: paths.filter(path => {
150-
return patterns.reduce((prevResult, pattern) => {
151-
return pattern.startsWith(NEGATION)
152-
? prevResult && !match(path, pattern.substring(1))
153-
: prevResult || match(path, pattern);
154-
}, false);
155-
});
157+
: paths.filter(path => testPath(path, patterns));
158+
}
159+
function testPath(path, patterns) {
160+
return patterns.reduce((prevResult, pattern) => {
161+
return pattern.startsWith(NEGATION)
162+
? prevResult && !match(path, pattern.substring(1))
163+
: prevResult || match(path, pattern);
164+
}, false);
156165
}
166+
exports.testPath = testPath;
157167

158168

159169
/***/ }),
@@ -251,12 +261,12 @@ function getChangedFilesImpl(token) {
251261
core.setFailed('Getting changed files only works on pull request events.');
252262
return [];
253263
}
254-
const files = yield octokit.paginate(octokit.rest.pulls.listFiles, {
264+
const entries = yield octokit.paginate(octokit.rest.pulls.listFiles, {
255265
owner: github_1.context.repo.owner,
256266
repo: github_1.context.repo.repo,
257267
pull_number: github_1.context.payload.pull_request.number,
258268
});
259-
return files.map(file => file.filename);
269+
return entries.map(({ filename, status }) => ({ path: filename, status }));
260270
}
261271
catch (error) {
262272
core.setFailed(`Getting changed files failed with error: ${error}`);
@@ -266,6 +276,44 @@ function getChangedFilesImpl(token) {
266276
}
267277

268278

279+
/***/ }),
280+
281+
/***/ 9091:
282+
/***/ ((__unused_webpack_module, exports) => {
283+
284+
"use strict";
285+
286+
Object.defineProperty(exports, "__esModule", ({ value: true }));
287+
exports.stringifyForShell = void 0;
288+
function stringifyArrayItem(item) {
289+
switch (typeof item) {
290+
case 'number':
291+
return item.toString();
292+
case 'string':
293+
return `'${item}'`;
294+
default:
295+
return JSON.stringify(item);
296+
}
297+
}
298+
function stringifyForShell(value) {
299+
switch (typeof value) {
300+
case 'string':
301+
return value;
302+
case 'object':
303+
if (Array.isArray(value)) {
304+
return value.map(stringifyArrayItem).join(' ');
305+
}
306+
if (value === null) {
307+
return '';
308+
}
309+
return value.toString();
310+
default:
311+
return JSON.stringify(value);
312+
}
313+
}
314+
exports.stringifyForShell = stringifyForShell;
315+
316+
269317
/***/ }),
270318

271319
/***/ 2013:
@@ -527,9 +575,19 @@ function run() {
527575
const output = core.getInput(common_1.inputs.OUTPUT, { required: true });
528576
console.log('patterns: ' + JSON.stringify(pathPatterns, undefined, 2));
529577
const changedFiles = yield (0, common_1.getChangedFiles)(token);
530-
const filteredFiles = (0, common_1.filterPaths)(changedFiles, pathPatterns);
578+
const filteredFiles = pathPatterns.length > 0
579+
? changedFiles.filter(({ path }) => (0, common_1.testPath)(path, pathPatterns))
580+
: changedFiles;
531581
(0, common_1.ensureDir)(output);
532-
fs.writeFileSync(output, JSON.stringify(filteredFiles.map(filename => ({ filename })), undefined, 2));
582+
fs.writeFileSync(output, JSON.stringify(filteredFiles.map(({ path }) => ({ filename: path })), undefined, 2));
583+
(0, common_1.setOutputs)({
584+
json: JSON.stringify({
585+
files: filteredFiles,
586+
count: filteredFiles.length,
587+
}),
588+
files: filteredFiles.map(e => e.path),
589+
count: filteredFiles.length,
590+
});
533591
}
534592
catch (error) {
535593
if (error instanceof Error) {

get-changed-files/src/main.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import * as fs from 'fs';
22
import * as core from '@actions/core';
33

4-
import { inputs, filterPaths, getChangedFiles, ensureDir } from 'common';
4+
import {
5+
inputs,
6+
getChangedFiles,
7+
ensureDir,
8+
setOutputs,
9+
testPath,
10+
} from 'common';
511

612

713
async function run(): Promise<void> {
@@ -13,10 +19,21 @@ async function run(): Promise<void> {
1319
console.log('patterns: ' + JSON.stringify(pathPatterns, undefined, 2));
1420

1521
const changedFiles = await getChangedFiles(token);
16-
const filteredFiles = filterPaths(changedFiles, pathPatterns);
22+
const filteredFiles = pathPatterns.length > 0
23+
? changedFiles.filter(({ path }) => testPath(path, pathPatterns))
24+
: changedFiles;
1725

1826
ensureDir(output);
19-
fs.writeFileSync(output, JSON.stringify(filteredFiles.map(filename => ({ filename })), undefined, 2));
27+
fs.writeFileSync(output, JSON.stringify(filteredFiles.map(({ path }) => ({ filename: path })), undefined, 2));
28+
29+
setOutputs({
30+
json: JSON.stringify({
31+
files: filteredFiles,
32+
count: filteredFiles.length,
33+
}),
34+
files: filteredFiles.map(e => e.path),
35+
count: filteredFiles.length,
36+
});
2037
} catch (error) {
2138
if (error instanceof Error) {
2239
core.setFailed(error.message)

0 commit comments

Comments
 (0)