Skip to content

Commit ab94fb0

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

12 files changed

Lines changed: 362 additions & 64 deletions

File tree

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.test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { filterPaths } from "./path-utils";
1+
import { filterPaths, normalizePatterns } from "./path-utils";
22

33
describe('path utils', () => {
44

@@ -94,4 +94,18 @@ describe('path utils', () => {
9494
expect(filterPaths(paths, patterns)).toEqual(expected);
9595
});
9696
});
97+
98+
describe('normalizePatterns', () => {
99+
test.each([
100+
{ patterns: undefined, expected: undefined },
101+
{ patterns: [ ], expected: undefined },
102+
{ patterns: [ undefined ], expected: undefined },
103+
{ patterns: [ '' ], expected: undefined },
104+
{ patterns: [ '', undefined ], expected: undefined },
105+
{ patterns: ['a'], expected: ['a'] },
106+
{ patterns: ['', 'a', undefined ], expected: ['a'] },
107+
])('basic cases [%#]', ({ patterns, expected }) => {
108+
expect(normalizePatterns(patterns)).toEqual(expected);
109+
});
110+
});
97111
});

common/src/path-utils.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,23 @@ function filterPathsImpl(
2424
paths: string[],
2525
patterns: string[],
2626
): string[] {
27-
return patterns?.length === 0
27+
const normalizedPatterns = normalizePatterns(patterns);
28+
return normalizedPatterns === undefined
2829
? 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-
});
30+
: paths.filter(path => testPath(path, normalizedPatterns));
31+
}
32+
33+
export function normalizePatterns(patterns?: (string | undefined)[]): string[] | undefined {
34+
if(!patterns) return undefined;
35+
36+
const notEmptyPatterns = patterns.filter(p => p != undefined && p.length > 0);
37+
return (notEmptyPatterns.length > 0 ? notEmptyPatterns : undefined) as ReturnType<typeof normalizePatterns>;
38+
}
39+
40+
export function testPath(path: string, patterns: string[]): boolean {
41+
return patterns.reduce((prevResult, pattern) => {
42+
return pattern.startsWith(NEGATION)
43+
? prevResult && !match(path, pattern.substring(1))
44+
: prevResult || match(path, pattern);
45+
}, false);
3646
}

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: 82 additions & 16 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.normalizePatterns = exports.filterPaths = void 0;
130138
const core = __importStar(__nccwpck_require__(5316));
131139
const minimatch_1 = __nccwpck_require__(148);
132140
const NEGATION = '!';
@@ -144,16 +152,26 @@ function filterPaths(paths, patterns) {
144152
}
145153
exports.filterPaths = filterPaths;
146154
function filterPathsImpl(paths, patterns) {
147-
return (patterns === null || patterns === void 0 ? void 0 : patterns.length) === 0
155+
const normalizedPatterns = normalizePatterns(patterns);
156+
return normalizedPatterns === undefined
148157
? 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-
});
158+
: paths.filter(path => testPath(path, normalizedPatterns));
159+
}
160+
function normalizePatterns(patterns) {
161+
if (!patterns)
162+
return undefined;
163+
const notEmptyPatterns = patterns.filter(p => p != undefined && p.length > 0);
164+
return (notEmptyPatterns.length > 0 ? notEmptyPatterns : undefined);
156165
}
166+
exports.normalizePatterns = normalizePatterns;
167+
function testPath(path, patterns) {
168+
return patterns.reduce((prevResult, pattern) => {
169+
return pattern.startsWith(NEGATION)
170+
? prevResult && !match(path, pattern.substring(1))
171+
: prevResult || match(path, pattern);
172+
}, false);
173+
}
174+
exports.testPath = testPath;
157175

158176

159177
/***/ }),
@@ -251,12 +269,12 @@ function getChangedFilesImpl(token) {
251269
core.setFailed('Getting changed files only works on pull request events.');
252270
return [];
253271
}
254-
const files = yield octokit.paginate(octokit.rest.pulls.listFiles, {
272+
const entries = yield octokit.paginate(octokit.rest.pulls.listFiles, {
255273
owner: github_1.context.repo.owner,
256274
repo: github_1.context.repo.repo,
257275
pull_number: github_1.context.payload.pull_request.number,
258276
});
259-
return files.map(file => file.filename);
277+
return entries.map(({ filename, status }) => ({ path: filename, status }));
260278
}
261279
catch (error) {
262280
core.setFailed(`Getting changed files failed with error: ${error}`);
@@ -266,6 +284,44 @@ function getChangedFilesImpl(token) {
266284
}
267285

268286

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

271327
/***/ 2013:
@@ -522,14 +578,24 @@ const common_1 = __nccwpck_require__(9145);
522578
function run() {
523579
return __awaiter(this, void 0, void 0, function* () {
524580
try {
525-
const pathPatterns = core.getInput(common_1.inputs.PATHS).split(';');
581+
const patterns = (0, common_1.normalizePatterns)(core.getInput(common_1.inputs.PATHS).split(';'));
526582
const token = core.getInput(common_1.inputs.GH_TOKEN, { required: true });
527583
const output = core.getInput(common_1.inputs.OUTPUT, { required: true });
528-
console.log('patterns: ' + JSON.stringify(pathPatterns, undefined, 2));
584+
console.log('patterns: ' + JSON.stringify(patterns, undefined, 2));
529585
const changedFiles = yield (0, common_1.getChangedFiles)(token);
530-
const filteredFiles = (0, common_1.filterPaths)(changedFiles, pathPatterns);
586+
const filteredFiles = patterns === undefined
587+
? changedFiles
588+
: changedFiles.filter(({ path }) => (0, common_1.testPath)(path, patterns));
531589
(0, common_1.ensureDir)(output);
532-
fs.writeFileSync(output, JSON.stringify(filteredFiles.map(filename => ({ filename })), undefined, 2));
590+
fs.writeFileSync(output, JSON.stringify(filteredFiles.map(({ path }) => ({ filename: path })), undefined, 2));
591+
(0, common_1.setOutputs)({
592+
json: JSON.stringify({
593+
files: filteredFiles,
594+
count: filteredFiles.length,
595+
}),
596+
files: filteredFiles.map(e => e.path),
597+
count: filteredFiles.length,
598+
});
533599
}
534600
catch (error) {
535601
if (error instanceof Error) {

0 commit comments

Comments
 (0)