Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c05a5b7
Add `ActionsEnvVars` enum
mbg Jun 16, 2026
652296e
Allow abstracting over `process.env`
mbg Jun 16, 2026
9c4ee01
Add `RemoteFileAddress` type
mbg Jun 23, 2026
07b6b1e
Move `getRemoteConfig` to `config/file.ts`
mbg Jun 23, 2026
9ef8be7
Refactor `parseRemoteFileAddress` out of `getRemoteConfig`
mbg Jun 23, 2026
82e5ca6
Return `RemoteFileAddress` from `parseRemoteFileAddress`
mbg Jun 23, 2026
c7a94c9
Add `getRemoteConfig` JSDoc
mbg Jun 23, 2026
85c8a8c
Add tests for `parseRemoteFileAddress`
mbg Jun 23, 2026
598d008
Make `ref` optional in `parseRemoteFileAddress`
mbg Jun 23, 2026
e537ff2
Make `path` optional in `parseRemoteFileAddress`
mbg Jun 23, 2026
12821cf
Make `owner` optional in `parseRemoteFileAddress`
mbg Jun 23, 2026
81ad479
Fix test names
mbg Jun 23, 2026
8102fa6
Anchor regex and trim input
mbg Jun 23, 2026
00e5a58
Update format in error message
mbg Jun 23, 2026
8d69da9
Improve whitespace handling
mbg Jun 23, 2026
3fe7ef9
Fix `getEnv` not using `getOptionalEnvVarFrom`
mbg Jun 23, 2026
812b882
Merge remote-tracking branch 'origin/main' into mbg/repo-props/config…
mbg Jun 23, 2026
f77cf55
Support old and new formats
mbg Jun 26, 2026
d8d1d6d
Add FF for shorthands
mbg Jun 24, 2026
e446d55
Add initial `ActionState` type
mbg Jun 30, 2026
f86aa52
Use `ActionState` for `getRemoteConfig`
mbg Jun 30, 2026
7b2af89
Add `TestEnv` class to simplify testing of `ActionState`-dependent fu…
mbg Jun 30, 2026
b6be81d
Gate new remote file format behind FF
mbg Jun 30, 2026
708e106
Index `ActionState` over the types of state used
mbg Jun 30, 2026
5094019
Refactor `Config` type out of `config-utils`
mbg Jul 6, 2026
73f4741
Refactor pack registry code out of `config-utils`
mbg Jul 6, 2026
69c9e8c
Mark some `status-report` imports as `type`-only to avoid circular de…
mbg Jul 6, 2026
8e22350
Thread `ActionState` to `initConfig`
mbg Jul 6, 2026
7dc37cb
Merge remote-tracking branch 'origin/main' into mbg/repo-props/config…
mbg Jul 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Index ActionState over the types of state used
  • Loading branch information
mbg committed Jun 30, 2026
commit 708e10653366421552971c6d675904adccdcc2f4
39 changes: 31 additions & 8 deletions src/action-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,36 @@ import { Env } from "./environment";
import { FeatureEnablement } from "./feature-flags";
import { Logger } from "./logging";

export interface ActionState {
/** The logger that is in use. */
logger: Logger;
/** Describes different state features that an Action may have. */
export interface FeatureState {
Logger: {
/** The logger that is in use. */
logger: Logger;
};
Env: {
/** Information about environment variables. */
env: Env;
};
FeatureFlags: {
/** Information about enabled feature flags. */
features: FeatureEnablement;
};
}

/** Information about environment variables. */
env: Env;
/** Identifies a type of state an Action may have. */
export type StateFeature = keyof FeatureState;

/** Information about enabled feature flags. */
features: FeatureEnablement;
}
/** Constructs the union of all state types identifies by `Fs`. */
export type FieldsOf<Fs extends readonly StateFeature[]> = Fs extends [
infer Head extends StateFeature,
]
? FeatureState[Head]
: Fs extends [
infer Head extends StateFeature,
...infer Tail extends StateFeature[],
]
? FeatureState[Head] & FieldsOf<Tail>
: never;

/** Describes the state of an Action that has access to the state corresponding to `Fs`. */
export type ActionState<Fs extends readonly StateFeature[]> = FieldsOf<Fs>;
4 changes: 2 additions & 2 deletions src/config-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ async function downloadCacheWithTime(
}

async function loadUserConfig(
actionState: ActionState,
actionState: ActionState<["Logger", "Env", "FeatureFlags"]>,
configFile: string,
workspacePath: string,
apiDetails: api.GitHubApiCombinedDetails,
Expand Down Expand Up @@ -1160,7 +1160,7 @@ export async function initConfig(
logger.debug("No configuration file was provided");
} else {
logger.debug(`Using configuration file: ${inputs.configFile}`);
const actionState: ActionState = { logger, features, env: getEnv() };
const actionState = { logger, features, env: getEnv() };
userConfig = await loadUserConfig(
actionState,
inputs.configFile,
Expand Down
2 changes: 1 addition & 1 deletion src/config/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function getConfigFileInput(
* @returns The `UserConfig`, if it could be fetched and parsed successfully.
*/
export async function getRemoteConfig(
actionState: ActionState,
actionState: ActionState<["Logger", "Env", "FeatureFlags"]>,
configFile: string,
apiDetails: api.GitHubApiCombinedDetails,
): Promise<UserConfig> {
Expand Down
2 changes: 1 addition & 1 deletion src/config/remote-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function parseOldRemoteFileAddress(
* @throws `ConfigurationError` if the format of `configFile` is not valid.
*/
export async function parseRemoteFileAddress(
actionState: ActionState,
actionState: ActionState<["FeatureFlags", "Env"]>,
configFile: string,
): Promise<RemoteFileAddress> {
// Try to parse the input using the old format. If successful, return the
Expand Down
37 changes: 23 additions & 14 deletions src/testing-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import test, {
import nock from "nock";
import * as sinon from "sinon";

import { ActionState } from "./action-common";
import { ActionState, StateFeature } from "./action-common";
import { ActionsEnv, ActionsEnvVars, getActionVersion } from "./actions-util";
import { AnalysisKind } from "./analyses";
import * as apiClient from "./api-client";
Expand Down Expand Up @@ -189,18 +189,25 @@ export function getTestActionsEnv(): ActionsEnv {
};
}

/** For testing purposes, we make all available state features accessible in `TestEnv`. */
type AllState = ["Logger", "Env", "FeatureFlags"];

/**
* Wraps a function that accepts an `ActionEnv` for testing in different environments.
*/
export class TestEnv<Args extends readonly any[], R> {
private readonly fn: (state: ActionState, ...args: Args) => R;
export class TestEnv<
Args extends readonly any[],
R,
Fs extends ReadonlyArray<AllState[number]>,
> {
private readonly fn: (state: ActionState<Fs>, ...args: Args) => R;
private args?: Args;
private state: ActionState;
private state: ActionState<AllState>;

constructor(
fn: (state: ActionState, ...args: Args) => R,
fn: (state: ActionState<Fs>, ...args: Args) => R,
args?: Args,
initialState?: ActionState,
initialState?: ActionState<AllState>,
) {
this.fn = fn;
this.args = args;
Expand All @@ -211,11 +218,11 @@ export class TestEnv<Args extends readonly any[], R> {
};
}

private clone(): TestEnv<Args, R> {
private clone(): TestEnv<Args, R, Fs> {
return new TestEnv(this.fn, this.args, { ...this.state });
}

public getState(): ActionState {
public getState(): ActionState<AllState> {
return this.state;
}

Expand All @@ -229,13 +236,13 @@ export class TestEnv<Args extends readonly any[], R> {
return result;
}

public withFeatures(enabled: Feature[]): TestEnv<Args, R> {
public withFeatures(enabled: Feature[]): TestEnv<Args, R, Fs> {
const result = this.clone();
result.state.features = createFeatures(enabled);
return result;
}

public withEnv(env: Env): TestEnv<Args, R> {
public withEnv(env: Env): TestEnv<Args, R, Fs> {
const result = this.clone();
result.state.env = env;
return result;
Expand All @@ -245,7 +252,7 @@ export class TestEnv<Args extends readonly any[], R> {
if (!this.args) {
throw new Error("Trying to call function in TestEnv without arguments.");
}
return this.fn(this.state, ...this.args);
return this.fn(this.state as ActionState<Fs>, ...this.args);
}

public passes<T>(
Expand All @@ -259,9 +266,11 @@ export class TestEnv<Args extends readonly any[], R> {
}

/** Utility function to construct a `TestEnv`. */
export function callee<Args extends readonly any[], R>(
fn: (state: ActionState, ...args: Args) => R,
): TestEnv<Args, R> {
export function callee<
Args extends readonly any[],
R,
Fs extends readonly StateFeature[],
>(fn: (state: ActionState<Fs>, ...args: Args) => R): TestEnv<Args, R, Fs> {
return new TestEnv(fn);
}

Expand Down
Loading