Compare commits

...

11 Commits

Author SHA1 Message Date
Mario Campos
e4e9b269fe Merge pull request #4211 from github/mario-campos/document-changenotes
Document change-notes process
2026-10-09 23:05:41 +00:00
Michael B. Gale
6e7fe2e990 Merge pull request #4220 from github/mbg/fix-action-state-type-inference
Fix type inference for `ActionState`
2026-10-09 22:31:18 +00:00
Mario Campos
fa56eef12f Delete unnecessary filename/frontmatter validation instructions 2026-10-09 16:48:00 -05:00
Michael B. Gale
e57c0e65dd Fix type inference for ActionState 2026-10-09 14:21:47 +01:00
Mario Campos
13b1321685 Refine misc. wording of unreleased-change-notes/README.md
Co-authored-by: Michael B. Gale <mbg@github.com>
2026-10-08 15:01:05 -05:00
Mario Campos
cc826bb8e3 Delete unnecessary guidance for change-note body 2026-10-08 14:57:03 -05:00
Mario Campos
40d76bc727 Use actual CHANGELOG.md entries for example change-note 2026-10-08 14:55:23 -05:00
Mario Campos
3acc10a2ce Exclude README.md from change-note validation and assembly 2026-10-08 12:53:37 -05:00
Mario Campos
2a935ca93d Move change-note documentation to unreleased-change-notes/README.md 2026-10-08 12:48:24 -05:00
Mario Campos
38fee79b02 Specify allowed characters in change-note filename 2026-10-07 15:21:38 -05:00
Mario Campos
39f1b3fa8d Document change-notes process 2026-10-07 13:11:34 -05:00
8 changed files with 134 additions and 12 deletions

View File

@@ -53,6 +53,7 @@ Here are a few things you can do that will increase the likelihood of your pull
- Write tests.
- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
- For user-facing changes, add a change-note file. See [unreleased-change-notes/README.md](unreleased-change-notes/README.md).
## Releasing (write access required)

14
lib/entry-points.js generated
View File

@@ -171726,6 +171726,9 @@ async function sendUnhandledErrorStatusReport(actionName, actionStartedAt, error
}
// src/action-common.ts
function extendActionState(state, extra) {
return { ...state, ...extra };
}
async function runInActions(action) {
const startedAt = /* @__PURE__ */ new Date();
const logger2 = getActionsLogger();
@@ -186378,7 +186381,9 @@ async function run3(actionState) {
`Failed to parse analysis kinds for 'starting' status report: ${getErrorMessage(err)}`
);
}
const actionStateWithFeatures = { ...actionState, features };
const actionStateWithFeatures = extendActionState(actionState, {
features
});
configFile = await getConfigFileInput(
actionStateWithFeatures,
repositoryProperties,
@@ -187400,7 +187405,9 @@ async function run6(actionState) {
logger2
);
const repositoryProperties = repositoryPropertiesResult.orElse({});
const actionStateWithFeatures = { ...actionState, features };
const actionStateWithFeatures = extendActionState(actionState, {
features
});
const statusReportBase = await createStatusReportBase(
"setup-codeql" /* SetupCodeQL */,
"starting",
@@ -188195,7 +188202,8 @@ async function run7(action) {
all_credentials: credentials,
ca
};
const proxyBin = await getProxyBinaryPath({ ...action, features });
const actionWithFeatures = extendActionState(action, { features });
const proxyBin = await getProxyBinaryPath(actionWithFeatures);
const proxyInfo = await startProxy(
proxyBin,
proxyConfig,

View File

@@ -28,12 +28,12 @@ interface ChangenoteFile {
/**
* Returns the absolute file paths of all files in
* {@link CHANGENOTES_DIR} (except ".gitkeep").
* {@link CHANGENOTES_DIR} (except ".gitkeep" and "README.md").
* */
function listUnreleasedChangenoteDir(): string[] {
return fs
.readdirSync(CHANGENOTES_DIR)
.filter((name) => name !== ".gitkeep")
.filter((name) => ![".gitkeep", "README.md"].includes(name))
.map((name) => path.join(CHANGENOTES_DIR, name));
}

View File

@@ -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>
@@ -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;
};
/** 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,
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.

View File

@@ -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,
@@ -277,7 +282,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,

View File

@@ -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,
@@ -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,

View File

@@ -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";
@@ -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,

View File

@@ -0,0 +1,24 @@
## Change notes
Change-notes are Markdown files used to document user-facing changes. When making a change that affects users, create a Markdown file here that describes the change. During the next release, the change-note files in `unreleased-change-notes/` will automatically be added to `CHANGELOG.md`.
### Change-note file format
Change-note files must follow a certain format so that they can be automatically validated and processed. Failure to follow the format will result in a failed PR check.
You may validate your change-note file locally by running `npx tsx pr-checks/changenotes.ts validate`. This command will scan all change-note files in `unreleased-change-notes/` and report any errors.
#### Body
The body of the change-note file must:
- Be written in valid [GitHub-Flavored Markdown](https://github.github.com/gfm/).
- Be structured as a single unordered Markdown list with hyphen (`-`) bullets. Each list item should describe a single change. If there are multiple changes, use multiple list items.
### Example change-note file
```
- Fixed a bug where a network error while streaming the download of the CodeQL bundle could terminate the `init` Action instead of falling back to downloading the bundle before extracting it. [#4061](https://github.com/github/codeql-action/pull/4061)
- Fix incorrect minimum required Git version for [improved incremental analysis](https://github.com/github/roadmap/issues/1158): it should have been 2.36.0, not 2.11.0. [#3781](https://github.com/github/codeql-action/pull/3781)
```