1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | import {
|
18 | and,
|
19 | hasFile,
|
20 | hasFileContaining,
|
21 | hasResourceProvider,
|
22 | isBranch,
|
23 | isGoal,
|
24 | isMaterialChange,
|
25 | isRepo,
|
26 | not,
|
27 | or,
|
28 | pushTest,
|
29 | PushTest,
|
30 | SdmGoalState,
|
31 | StatefulPushListenerInvocation,
|
32 | ToDefaultBranch,
|
33 | } from "@atomist/sdm";
|
34 | import * as camelcaseKeys from "camelcase-keys";
|
35 | import * as changeCase from "change-case";
|
36 | import { toArray } from "../../util/misc/array";
|
37 |
|
38 | export type PushTestMaker<G extends Record<string, any> = any> =
|
39 | (params: G) => ((pli: StatefulPushListenerInvocation) => Promise<boolean>) | Promise<PushTest> | PushTest;
|
40 |
|
41 | export async function mapTests(tests: any,
|
42 | additionalTests: Record<string, PushTest>,
|
43 | extensionTests: Record<string, PushTestMaker>): Promise<PushTest | PushTest[]> {
|
44 | const newTests = [];
|
45 | for (const t of toArray(tests || [])) {
|
46 | const test = typeof t !== "string" && !Array.isArray(t) ? camelcaseKeys(t, { deep: true }) : t as any;
|
47 | newTests.push(await mapTest(test, additionalTests, extensionTests));
|
48 | }
|
49 | return newTests;
|
50 | }
|
51 |
|
52 | type CreatePushTest = (test: any,
|
53 | additionalTests: Record<string, PushTest>,
|
54 | extensionTests: Record<string, PushTestMaker>) => Promise<PushTest | undefined>;
|
55 |
|
56 | const HasFile: CreatePushTest = async test => {
|
57 | if (test.hasFile) {
|
58 | return hasFile(test.hasFile);
|
59 | }
|
60 | return undefined;
|
61 | };
|
62 |
|
63 | const IsRepo: CreatePushTest = async test => {
|
64 | if (test.isRepo) {
|
65 | return isRepo(typeof test.isRepo === "string" ? new RegExp(test.isRepo) : test.isRepo);
|
66 | }
|
67 | return undefined;
|
68 | };
|
69 |
|
70 | const IsBranch: CreatePushTest = async test => {
|
71 | if (test.isBranch) {
|
72 | return isBranch(typeof test.isBranch === "string" ? new RegExp(test.isBranch) : test.isBranch);
|
73 | }
|
74 | return undefined;
|
75 | };
|
76 |
|
77 | const IsDefaultBranch: CreatePushTest = async test => {
|
78 | if (["isDefaultBranch", "toDefaultBranch"].includes(changeCase.camel(test))) {
|
79 | return ToDefaultBranch;
|
80 | }
|
81 | return undefined;
|
82 | };
|
83 |
|
84 | const IsGoal: CreatePushTest = async (test, additionalTests, extensionTests) => {
|
85 | if (test.isGoal) {
|
86 | return isGoal(
|
87 | {
|
88 | name: typeof test.isGoal.name === "string" ? new RegExp(test.isGoal.name) : test.isGoal.name,
|
89 | state: test.isGoal.state || SdmGoalState.success,
|
90 | pushTest: test.isGoal.test ? await mapTest(test.isGoal.test, additionalTests, extensionTests) : undefined,
|
91 | output: typeof test.isGoal.output === "string" ? new RegExp(test.isGoal.output) : test.isGoal.output,
|
92 | data: typeof test.isGoal.data === "string" ? new RegExp(test.isGoal.data) : test.isGoal.data,
|
93 | });
|
94 | }
|
95 | return undefined;
|
96 | };
|
97 |
|
98 | const IsMaterialChange: CreatePushTest = async test => {
|
99 | if (test.isMaterialChange) {
|
100 | return isMaterialChange({
|
101 | directories: toArray(test.isMaterialChange.directories),
|
102 | extensions: toArray(test.isMaterialChange.extensions),
|
103 | files: toArray(test.isMaterialChange.files),
|
104 | globs: getGlobPatterns(test.isMaterialChange),
|
105 | });
|
106 | }
|
107 | return undefined;
|
108 | };
|
109 |
|
110 | const HasFileContaining: CreatePushTest = async test => {
|
111 | if (test.hasFileContaining) {
|
112 | if (!test.hasFileContaining.content) {
|
113 | throw new Error("Push test 'hasFileContaining' can't be used without 'content' property");
|
114 | }
|
115 | return hasFileContaining(
|
116 | getGlobPatterns(test.hasFileContaining) || "**/*",
|
117 | typeof test.hasFileContaining.content === "string" ? new RegExp(test.hasFileContaining.content) : test.hasFileContaining.content);
|
118 | }
|
119 | return undefined;
|
120 | };
|
121 |
|
122 | const HasResourceProvider: CreatePushTest = async test => {
|
123 | if (test.hasResourceProvider) {
|
124 | if (!test.hasResourceProvider.type) {
|
125 | throw new Error("Push test 'hasResourceProvider' can't be used without 'type' property");
|
126 | }
|
127 | return hasResourceProvider(test.hasResourceProvider.type, test.hasResourceProvider.name);
|
128 | }
|
129 | return undefined;
|
130 | };
|
131 |
|
132 | const Not: CreatePushTest = async (test, additionalTests, extensionTests) => {
|
133 | if (test.not) {
|
134 | return not(await mapTest(test.not, additionalTests, extensionTests));
|
135 | }
|
136 | return undefined;
|
137 | };
|
138 |
|
139 | const And: CreatePushTest = async (test, additionalTests, extensionTests) => {
|
140 | if (test.and) {
|
141 | return and(...toArray(await mapTests(test.and, additionalTests, extensionTests)));
|
142 | }
|
143 | return undefined;
|
144 | };
|
145 |
|
146 | const Or: CreatePushTest = async (test, additionalTests, extensionTests) => {
|
147 | if (test.or) {
|
148 | return or(...toArray(await mapTests(test.or, additionalTests, extensionTests)));
|
149 | }
|
150 | return undefined;
|
151 | };
|
152 |
|
153 | const AdditionalTest: CreatePushTest = async (test, additionalTests) => {
|
154 | if (!!test.use && !!additionalTests[test.use]) {
|
155 | return additionalTests[test.use];
|
156 | }
|
157 | return undefined;
|
158 | };
|
159 |
|
160 | const FunctionTest: CreatePushTest = async test => {
|
161 | if (typeof test === "function") {
|
162 | return pushTest(test.toString(), test);
|
163 | }
|
164 | return undefined;
|
165 | };
|
166 |
|
167 | const ExtensionTest = async (test, additionalTests, extensionTests) => {
|
168 | for (const extTestName in extensionTests) {
|
169 | if (test.use === extTestName) {
|
170 | const extTest = await extensionTests[extTestName](test.parameters || {});
|
171 | if (!!extTest.name && !!extTest.mapping) {
|
172 | return extTest;
|
173 | } else {
|
174 | return pushTest(extTestName, extTest);
|
175 | }
|
176 | }
|
177 | }
|
178 | return undefined;
|
179 | };
|
180 |
|
181 | export const CreatePushTests = [
|
182 | HasFile,
|
183 | IsRepo,
|
184 | IsBranch,
|
185 | IsDefaultBranch,
|
186 | IsGoal,
|
187 | IsMaterialChange,
|
188 | HasFileContaining,
|
189 | HasResourceProvider,
|
190 | Not,
|
191 | And,
|
192 | Or,
|
193 | AdditionalTest,
|
194 | FunctionTest,
|
195 | ExtensionTest,
|
196 | ];
|
197 |
|
198 | export async function mapTest(test: any,
|
199 | additionalTests: Record<string, PushTest>,
|
200 | extensionTests: Record<string, PushTestMaker>): Promise<PushTest> {
|
201 | for (const createPushTest of CreatePushTests) {
|
202 | const pt = await createPushTest(test, additionalTests, extensionTests);
|
203 | if (!!pt) {
|
204 | return pt;
|
205 | }
|
206 | }
|
207 | throw new Error(`Unable to construct push test from '${JSON.stringify(test)}'`);
|
208 | }
|
209 |
|
210 | function getGlobPatterns(test: any): string[] {
|
211 | const pattern = test.globPattern || test.pattern || test.globPatterns || test.patterns;
|
212 | return toArray(pattern);
|
213 | }
|