@vzn/vx-ci 0.0.631 → 0.0.633

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -56,7 +56,8 @@ On Actions (`GITHUB_ACTIONS=true`), when `vx.workspace.ts` sets no `cacheScope`,
56
56
  `config` stage sets it from the ref (`GITHUB_REF`, `GITHUB_REF_NAME`, and
57
57
  the default branch from the `GITHUB_EVENT_PATH` payload): a push to the default branch stays
58
58
  trusted (reads and writes the task keys), a pull request becomes
59
- `pr-<n>` and any other branch or tag `ref-<name>`, which read their
59
+ `pr-<n>` (so is a `pull_request_target`, `issue_comment` or `workflow_run`
60
+ run on a PR's behalf, by `GITHUB_EVENT_NAME`, though its ref is main's) and any other branch or tag `ref-<name>`, which read their
60
61
  own keys, then the trusted ones, and write only their own. A PR never writes what main
61
62
  reads. `VX_CACHE_SCOPE` or a `cacheScope` in `vx.workspace.ts` overrides it. This is a client-side convention: the real boundary is a cache
62
63
  token the server limits, so give PR jobs one that cannot write the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vzn/vx-ci",
3
- "version": "0.0.631",
3
+ "version": "0.0.633",
4
4
  "license": "MIT",
5
5
  "description": "GitHub Actions integration for vx — a telemetry plugin that writes the run as a GitHub Actions job summary.",
6
6
  "keywords": [
@@ -33,7 +33,7 @@
33
33
  "access": "public"
34
34
  },
35
35
  "peerDependencies": {
36
- "@vzn/vx": "^0.0.631"
36
+ "@vzn/vx": "^0.0.633"
37
37
  },
38
38
  "repository": {
39
39
  "type": "git",
@@ -6,6 +6,10 @@ import { readFile } from 'node:fs/promises'
6
6
  * `undefined` (leave the workspace's default, trusted) on a push to the
7
7
  * default branch, outside Actions, or when the ref is unknown; else
8
8
  * `pr-<n>` for a pull request and `ref-<name>` for any other branch or tag.
9
+ * An event that runs in the default branch's context on a pull request's
10
+ * behalf (`pull_request_target`, `issue_comment` on a PR, a `workflow_run`
11
+ * a PR started) is the PR's, whatever `GITHUB_REF` says: a workflow that
12
+ * checks out the PR's head there would otherwise write the trusted keys.
9
13
  */
10
14
  export async function githubCacheScope(
11
15
  env: Readonly<Record<string, string | undefined>>,
@@ -14,22 +18,50 @@ export async function githubCacheScope(
14
18
  const ref = env['GITHUB_REF'] ?? ''
15
19
  const pr = /^refs\/pull\/(\d+)\//.exec(ref)
16
20
  if (pr !== null) return `pr-${pr[1]}`
21
+ const event = await eventOf(env['GITHUB_EVENT_PATH'])
22
+ const onBehalf = prOf(env['GITHUB_EVENT_NAME'], event)
23
+ if (onBehalf !== undefined) return onBehalf
17
24
  const name = env['GITHUB_REF_NAME']
18
25
  if (ref === '' || name === undefined || name === '') return undefined
19
- if (ref === `refs/heads/${await defaultBranch(env['GITHUB_EVENT_PATH'])}`) return undefined
26
+ const branch = event?.repository?.default_branch
27
+ if (typeof branch === 'string' && branch !== '' && ref === `refs/heads/${branch}`)
28
+ return undefined
20
29
  // `cacheScope` takes letters, digits and . _ - / @; a ref may hold more.
21
30
  return `ref-${name.replace(/[^\w.@/-]/g, '_').slice(0, 124)}`
22
31
  }
23
32
 
24
- /** The repository's default branch, from the event payload the runner writes. */
25
- async function defaultBranch(eventPath: string | undefined): Promise<string | undefined> {
33
+ interface ActionsEvent {
34
+ repository?: { default_branch?: unknown }
35
+ pull_request?: { number?: unknown }
36
+ issue?: { number?: unknown; pull_request?: unknown }
37
+ workflow_run?: { event?: unknown; pull_requests?: { number?: unknown }[] }
38
+ }
39
+
40
+ /** The PR scope of an event that runs in the base branch's context for a PR; `pr-target` when its number is unknown. */
41
+ function prOf(name: string | undefined, event: ActionsEvent | undefined): string | undefined {
42
+ const scope = (n: unknown): string =>
43
+ typeof n === 'number' && Number.isInteger(n) && n > 0 ? `pr-${n}` : 'pr-target'
44
+ if (name === 'pull_request_target') return scope(event?.pull_request?.number)
45
+ if (name === 'issue_comment' && event?.issue?.pull_request != null) {
46
+ return scope(event.issue.number)
47
+ }
48
+ const run = event?.workflow_run
49
+ if (
50
+ name === 'workflow_run' &&
51
+ typeof run?.event === 'string' &&
52
+ run.event.startsWith('pull_request')
53
+ ) {
54
+ return scope(run.pull_requests?.[0]?.number)
55
+ }
56
+ return undefined
57
+ }
58
+
59
+ /** The event payload the runner writes; undefined when it cannot be read. */
60
+ async function eventOf(eventPath: string | undefined): Promise<ActionsEvent | undefined> {
26
61
  if (eventPath === undefined || eventPath === '') return undefined
27
62
  try {
28
- const event = JSON.parse(await readFile(eventPath, 'utf8')) as {
29
- repository?: { default_branch?: unknown }
30
- }
31
- const branch = event.repository?.default_branch
32
- return typeof branch === 'string' && branch !== '' ? branch : undefined
63
+ const event = JSON.parse(await readFile(eventPath, 'utf8')) as unknown
64
+ return typeof event === 'object' && event !== null ? (event as ActionsEvent) : undefined
33
65
  } catch {
34
66
  return undefined
35
67
  }