Skip to content

Commit 8be707b

Browse files
committed
Merge remote-tracking branch 'origin/main' into mbg/config/merge
2 parents 8476401 + 006d029 commit 8be707b

11 files changed

Lines changed: 1510 additions & 1362 deletions

lib/entry-points.js

Lines changed: 1185 additions & 1195 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/action-common.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import * as core from "@actions/core";
2+
3+
import { ActionsEnv, getActionsEnv } from "./actions-util";
4+
import { Env } from "./environment";
5+
import { FeatureEnablement } from "./feature-flags";
6+
import { getActionsLogger, Logger } from "./logging";
7+
import {
8+
ActionName,
9+
getDisplayActionName,
10+
sendUnhandledErrorStatusReport,
11+
} from "./status-report";
12+
import { getEnv, getErrorMessage } from "./util";
13+
14+
/** Common state that is always available in `ActionState`. */
15+
export interface BaseState {
16+
/** The name of the Action. */
17+
name: ActionName;
18+
/** When the Action was started. */
19+
startedAt: Date;
20+
}
21+
22+
/** Describes different state features that an Action may have. */
23+
export interface FeatureState {
24+
Logger: {
25+
/** The logger that is in use. */
26+
logger: Logger;
27+
};
28+
Env: {
29+
/** Information about environment variables. */
30+
env: Env;
31+
};
32+
Actions: {
33+
/** Access to Actions-related functionality. */
34+
actions: ActionsEnv;
35+
};
36+
FeatureFlags: {
37+
/** Information about enabled feature flags. */
38+
features: FeatureEnablement;
39+
};
40+
}
41+
42+
/** Identifies a type of state an Action may have. */
43+
export type StateFeature = keyof FeatureState;
44+
45+
/** Constructs the intersection of all state types identifies by `Fs`. */
46+
export type FieldsOf<Fs extends readonly StateFeature[]> = Fs extends []
47+
? BaseState
48+
: Fs extends [
49+
infer Head extends StateFeature,
50+
...infer Tail extends readonly StateFeature[],
51+
]
52+
? FeatureState[Head] & FieldsOf<Tail>
53+
: never;
54+
55+
/** Describes the state of an Action that has access to the state corresponding to `Fs`. */
56+
export type ActionState<Fs extends readonly StateFeature[]> = FieldsOf<Fs>;
57+
58+
/** The type of an Action's main entry point. This is a function that is provided
59+
* with a basic `ActionState` object with features that are always available.
60+
* Each Action can then augment the `state` further if additional features are required.
61+
*/
62+
export type ActionMain = (
63+
state: ActionState<["Logger", "Env", "Actions"]>,
64+
) => Promise<void>;
65+
66+
/** A specification for a CodeQL Action step. */
67+
export interface Action {
68+
/** The name of the Action. */
69+
name: ActionName;
70+
/** The entry point for the Action. */
71+
run: ActionMain;
72+
}
73+
74+
/** A generic entry point that sets up the basic environment for the `action` and runs it. */
75+
export async function runInActions(action: Action) {
76+
const startedAt = new Date();
77+
const logger = getActionsLogger();
78+
const env = getEnv();
79+
const actionsEnv = getActionsEnv();
80+
81+
try {
82+
await action.run({
83+
name: action.name,
84+
startedAt,
85+
logger,
86+
env,
87+
actions: actionsEnv,
88+
});
89+
} catch (error) {
90+
core.setFailed(
91+
`${getDisplayActionName(action.name)} action failed: ${getErrorMessage(error)}`,
92+
);
93+
await sendUnhandledErrorStatusReport(action.name, startedAt, error, logger);
94+
}
95+
}

src/analyze-action.ts

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { performance } from "perf_hooks";
44

55
import * as core from "@actions/core";
66

7+
import { Action, ActionState, runInActions } from "./action-common";
78
import * as actionsUtil from "./actions-util";
89
import * as analyses from "./analyses";
910
import {
@@ -40,7 +41,6 @@ import {
4041
createStatusReportBase,
4142
DatabaseCreationTimings,
4243
getActionsStatus,
43-
sendUnhandledErrorStatusReport,
4444
StatusReportBase,
4545
} from "./status-report";
4646
import {
@@ -212,7 +212,7 @@ async function runAutobuildIfLegacyGoWorkflow(config: Config, logger: Logger) {
212212
await runAutobuild(config, BuiltInLanguage.go, logger);
213213
}
214214

215-
async function run(startedAt: Date) {
215+
async function run({ startedAt, logger }: ActionState<["Logger"]>) {
216216
// To capture errors appropriately, keep as much code within the try-catch as
217217
// possible, and only use safe functions outside.
218218

@@ -228,7 +228,6 @@ async function run(startedAt: Date) {
228228
let didUploadTrapCaches = false;
229229
let dependencyCacheResults: DependencyCacheUploadStatusReport | undefined;
230230
let databaseUploadResults: DatabaseUploadResult[] = [];
231-
const logger = getActionsLogger();
232231

233232
try {
234233
util.initializeEnvironment(actionsUtil.getActionVersion());
@@ -523,19 +522,13 @@ async function run(startedAt: Date) {
523522
}
524523
}
525524

525+
/** Defines the `analyze` Action. */
526+
const analyze: Action = {
527+
name: ActionName.Analyze,
528+
run,
529+
};
530+
526531
export async function runWrapper() {
527-
const startedAt = new Date();
528-
const logger = getActionsLogger();
529-
try {
530-
await run(startedAt);
531-
} catch (error) {
532-
core.setFailed(`analyze action failed: ${util.getErrorMessage(error)}`);
533-
await sendUnhandledErrorStatusReport(
534-
ActionName.Analyze,
535-
startedAt,
536-
error,
537-
logger,
538-
);
539-
}
532+
await runInActions(analyze);
540533
await util.checkForTimeout();
541534
}

src/autobuild-action.ts

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as core from "@actions/core";
22

3+
import { Action, ActionState, runInActions } from "./action-common";
34
import {
45
getActionVersion,
56
getOptionalInput,
@@ -11,13 +12,12 @@ import { getCodeQL } from "./codeql";
1112
import { Config, getConfig } from "./config-utils";
1213
import { EnvVar } from "./environment";
1314
import { Language } from "./languages";
14-
import { Logger, getActionsLogger } from "./logging";
15+
import { Logger } from "./logging";
1516
import {
1617
StatusReportBase,
1718
getActionsStatus,
1819
createStatusReportBase,
1920
sendStatusReport,
20-
sendUnhandledErrorStatusReport,
2121
ActionName,
2222
} from "./status-report";
2323
import { endTracingForCluster } from "./tracer-config";
@@ -26,7 +26,6 @@ import {
2626
checkDiskUsage,
2727
checkGitHubVersionInRange,
2828
ConfigurationError,
29-
getErrorMessage,
3029
initializeEnvironment,
3130
wrapError,
3231
} from "./util";
@@ -69,11 +68,10 @@ async function sendCompletedStatusReport(
6968
}
7069
}
7170

72-
async function run(startedAt: Date) {
71+
async function run({ startedAt, logger }: ActionState<["Logger"]>) {
7372
// To capture errors appropriately, keep as much code within the try-catch as
7473
// possible, and only use safe functions outside.
7574

76-
const logger = getActionsLogger();
7775
let config: Config | undefined;
7876
let currentLanguage: Language | undefined;
7977
let languages: Language[] | undefined;
@@ -142,18 +140,12 @@ async function run(startedAt: Date) {
142140
await sendCompletedStatusReport(config, logger, startedAt, languages ?? []);
143141
}
144142

143+
/** Defines the `autobuild` Action. */
144+
const autobuild: Action = {
145+
name: ActionName.Autobuild,
146+
run,
147+
};
148+
145149
export async function runWrapper() {
146-
const startedAt = new Date();
147-
const logger = getActionsLogger();
148-
try {
149-
await run(startedAt);
150-
} catch (error) {
151-
core.setFailed(`autobuild action failed. ${getErrorMessage(error)}`);
152-
await sendUnhandledErrorStatusReport(
153-
ActionName.Autobuild,
154-
startedAt,
155-
error,
156-
logger,
157-
);
158-
}
150+
await runInActions(autobuild);
159151
}

src/config/file.test.ts

Lines changed: 46 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,96 @@
11
import test from "ava";
22
import sinon from "sinon";
33

4+
import { Feature } from "../feature-flags";
45
import { RepositoryPropertyName } from "../feature-flags/properties";
5-
import {
6-
getTestActionsEnv,
7-
RecordingLogger,
8-
setupTests,
9-
} from "../testing-utils";
6+
import { callee, setupTests } from "../testing-utils";
107

118
import { getConfigFileInput } from "./file";
129

1310
setupTests(test);
1411

1512
test("getConfigFileInput returns undefined by default", async (t) => {
16-
const logger = new RecordingLogger();
17-
const actionsEnv = getTestActionsEnv();
18-
const result = getConfigFileInput(logger, actionsEnv, {}, true);
19-
t.is(result, undefined);
13+
await callee(getConfigFileInput)
14+
.withArgs({})
15+
.withFeatures([Feature.ConfigFileRepositoryProperty])
16+
.passes(async (fn) => t.is(await fn(), undefined));
2017
});
2118

2219
const repositoryProperties = {
2320
[RepositoryPropertyName.CONFIG_FILE]: "/path/from/property",
2421
};
2522

2623
test("getConfigFileInput returns input value", async (t) => {
27-
const logger = new RecordingLogger();
28-
const actionsEnv = getTestActionsEnv();
2924
const testInput = "/some/path";
25+
const target = callee(getConfigFileInput).withFeatures([
26+
Feature.ConfigFileRepositoryProperty,
27+
]);
28+
29+
const actionsEnv = target.getState().actions;
3030
sinon
3131
.stub(actionsEnv, "getOptionalInput")
3232
.withArgs("config-file")
3333
.returns(testInput);
3434

3535
// Even though both an input and repository property are configured,
3636
// we prefer the direct input to the Action.
37-
const result = getConfigFileInput(
38-
logger,
39-
actionsEnv,
40-
repositoryProperties,
41-
true,
42-
);
43-
t.is(result, testInput);
37+
const targetWithArgs = target
38+
.withActions(actionsEnv)
39+
.withArgs(repositoryProperties);
40+
await targetWithArgs.passes(async (fn) => t.is(await fn(), testInput));
4441

4542
// Check for the expected log message.
46-
t.true(logger.hasMessage("Using configuration file input from workflow"));
43+
t.true(
44+
targetWithArgs
45+
.getLogger()
46+
.hasMessage("Using configuration file input from workflow"),
47+
);
4748
});
4849

4950
test("getConfigFileInput returns repository property value", async (t) => {
50-
const logger = new RecordingLogger();
51-
const actionsEnv = getTestActionsEnv();
52-
5351
// Since there is no direct input, we should use the repository property.
54-
const result = getConfigFileInput(
55-
logger,
56-
actionsEnv,
57-
repositoryProperties,
58-
true,
52+
const target = callee(getConfigFileInput)
53+
.withFeatures([Feature.ConfigFileRepositoryProperty])
54+
.withArgs(repositoryProperties);
55+
56+
await target.passes(async (fn) =>
57+
t.is(await fn(), repositoryProperties[RepositoryPropertyName.CONFIG_FILE]),
5958
);
60-
t.is(result, repositoryProperties[RepositoryPropertyName.CONFIG_FILE]);
6159

6260
// Check for the expected log message.
6361
t.true(
64-
logger.hasMessage(
65-
"Using configuration file input from repository property",
66-
),
62+
target
63+
.getLogger()
64+
.hasMessage("Using configuration file input from repository property"),
6765
);
6866
});
6967

7068
test("getConfigFileInput ignores empty repository property value", async (t) => {
71-
const logger = new RecordingLogger();
72-
const actionsEnv = getTestActionsEnv();
73-
7469
// Since the repository property value is an empty/whitespace string, we should ignore it.
75-
const result = getConfigFileInput(
76-
logger,
77-
actionsEnv,
78-
{
79-
[RepositoryPropertyName.CONFIG_FILE]: " ",
80-
},
81-
true,
82-
);
83-
t.is(result, undefined);
70+
await callee(getConfigFileInput)
71+
.withFeatures([Feature.ConfigFileRepositoryProperty])
72+
.withArgs({ [RepositoryPropertyName.CONFIG_FILE]: " " })
73+
.passes(async (fn) => t.is(await fn(), undefined));
8474
});
8575

8676
test("getConfigFileInput ignores repository property value when FF is off", async (t) => {
87-
const logger = new RecordingLogger();
88-
const actionsEnv = getTestActionsEnv();
89-
9077
// Since the FF is off, we should ignore the repository property value.
91-
const result = getConfigFileInput(
92-
logger,
93-
actionsEnv,
94-
repositoryProperties,
95-
false,
96-
);
97-
t.is(result, undefined);
78+
const target = callee(getConfigFileInput)
79+
.withFeatures([])
80+
.withArgs(repositoryProperties);
81+
82+
await target.passes(async (fn) => t.is(await fn(), undefined));
9883

9984
t.false(
100-
logger.hasMessage(
101-
"Using configuration file input from repository property",
102-
),
85+
target
86+
.getLogger()
87+
.hasMessage("Using configuration file input from repository property"),
10388
);
10489
t.true(
105-
logger.hasMessage(
106-
"Ignoring configuration file input from repository property, because the corresponding feature flag is disabled.",
107-
),
90+
target
91+
.getLogger()
92+
.hasMessage(
93+
"Ignoring configuration file input from repository property, because the corresponding feature flag is disabled.",
94+
),
10895
);
10996
});

0 commit comments

Comments
 (0)