Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 11 additions & 3 deletions lib/entry-points.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 70 additions & 1 deletion src/action-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,29 @@ export interface FeatureState {
/** Identifies a type of state an Action may have. */
export type StateFeature = keyof FeatureState;

/**
* The `Env` feature implies the availability of the `ReadOnlyEnv` feature.
*
* If `T` is `Env`, this returns `Env | ReadOnlyEnv`.
* Otherwise, it is the identity and returns T.
*/
type ImpliedFeatures<T extends StateFeature> = T extends "Env"
? "Env" | "ReadOnlyEnv"
: T;

/**
* Given an object type `Obj`, this tries to lookup a corresponding `StateFeature`
* to which the object type belongs in `FeatureState`. Resolves to `never` if there
* is no match.
*/
type FeatureNameFor<Obj extends object> = {
[K in StateFeature]: [Obj] extends [FeatureState[K]]
? [FeatureState[K]] extends [Obj]
? K
: never
: never;
}[StateFeature];

/** Constructs the intersection of all state types identifies by `Fs`. */
export type FieldsOf<Fs extends readonly StateFeature[]> = Fs extends []
? Record<never, never>
Expand All @@ -66,8 +89,54 @@ export type FieldsOf<Fs extends readonly StateFeature[]> = Fs extends []
? FeatureState[Head] & FieldsOf<Tail>
: never;

/**
* Symbol used for a field in `ActionState` that carries the type array of state features.
* This is a Symbol so that it doesn't clash with any property names we might want to have.
*/
const stateFeatures = Symbol();

/** Describes the state of an Action that has access to the state corresponding to `Fs`. */
export type ActionState<Fs extends readonly StateFeature[]> = FieldsOf<Fs>;
export type ActionState<Fs extends readonly StateFeature[]> = FieldsOf<Fs> & {
/**
* When given a chance, TypeScript will simplify an `ActionState<Fs>` type as much as possible,
* which results in a concrete object type that doesn't mention `Fs`.
*
* That causes problems for functions which accept `ActionState<Fs>` values, but need to know the
* feature keys `Fs`. This property here explicitly captures `Fs` in the concrete object type
* that results from simplifying `ActionState<Fs>`.
*
* This is a function rather than a field, because we want to be able to provide values of type
* `ActionState<Fs>` to functions expecting `ActionState<As>` where `As` is a subset of `Fs`.
*
* Since function types are contravariant in the types of their parameters, using a function
* type here allows that to happen.
*
* Because the field is optional, we don't have to explicitly provide a value
* for it anywhere while the type is still inferred.
*
* `Fs[number]` returns the union of all features in `Fs`. We wrap it in `ImpliedFeatures`
* so that `Env` is expanded into `Env | ReadOnlyEnv`, allowing functions that expect the
* `ReadOnlyEnv` feature to be provided with an `ActionState` that has the `Env` feature
* without requiring this to be made explicit.
*/
readonly [stateFeatures]?: (ts: ImpliedFeatures<Fs[number]>) => void;
Comment on lines +117 to +122
};

/** Extends `state` with an `extra` feature. */
export function extendActionState<
// In first position, so that it can be explicitly provided if `FeatureNameFor`
// should not work on `extra`.
F extends StateFeature,
Comment thread
mbg marked this conversation as resolved.
Fs extends readonly StateFeature[],
E extends FeatureState[F],
>(
state: ActionState<Fs>,
extra: E,
): ActionState<[...Fs, FeatureNameFor<E> & F]> {
return { ...state, ...extra } as unknown as ActionState<
[...Fs, FeatureNameFor<E> & F]
>;
}

/** The type of an Action's main entry point. This is a function that is provided
* with a basic `ActionState` object with features that are always available.
Expand Down
11 changes: 9 additions & 2 deletions src/init-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import * as core from "@actions/core";
import * as io from "@actions/io";
import * as semver from "semver";

import { Action, ActionState, runInActions } from "./action-common";
import {
Action,
ActionState,
extendActionState,
runInActions,
} from "./action-common";
import {
FileCmdNotFoundError,
getActionVersion,
Expand Down Expand Up @@ -278,7 +283,9 @@ async function run(
}

// Compute the value of the `config-file` input.
const actionStateWithFeatures = { ...actionState, features };
const actionStateWithFeatures = extendActionState(actionState, {
features,
});
configFile = await getConfigFileInput(
actionStateWithFeatures,
repositoryProperties,
Expand Down
11 changes: 9 additions & 2 deletions src/setup-codeql-action.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import * as core from "@actions/core";

import { Action, ActionState, runInActions } from "./action-common";
import {
Action,
ActionState,
extendActionState,
runInActions,
} from "./action-common";
import {
getActionVersion,
getOptionalInput,
Expand Down Expand Up @@ -131,7 +136,9 @@ async function run(
);
const repositoryProperties = repositoryPropertiesResult.orElse({});

const actionStateWithFeatures = { ...actionState, features };
const actionStateWithFeatures = extendActionState(actionState, {
features,
});

const statusReportBase = await createStatusReportBase(
ActionName.SetupCodeQL,
Expand Down
10 changes: 8 additions & 2 deletions src/start-proxy-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import * as path from "path";

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

import { Action, ActionState, runInActions } from "./action-common";
import {
Action,
ActionState,
extendActionState,
runInActions,
} from "./action-common";
import * as actionsUtil from "./actions-util";
import { getGitHubVersion } from "./api-client";
import { FeatureEnablement, initFeatures } from "./feature-flags";
Expand Down Expand Up @@ -98,7 +103,8 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
};

// Start the Proxy
const proxyBin = await getProxyBinaryPath({ ...action, features });
const actionWithFeatures = extendActionState(action, { features });
const proxyBin = await getProxyBinaryPath(actionWithFeatures);
const proxyInfo = await startProxy(
proxyBin,
proxyConfig,
Expand Down
Loading