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
10 changes: 8 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ parameters:
default: ''
description: The PR number
type: string
ghTrustedAuthor:
default: 'false'
description: Whether the PR author is a trusted author who should be allowed to persist to shared caches
type: string
workflow:
default: skipped
description: Which workflow to run
Expand All @@ -30,7 +34,7 @@ parameters:

jobs:
generate-and-run-config:
executor:
executor:
name: node/default
resource_class: large
steps:
Expand All @@ -44,7 +48,9 @@ jobs:
- run:
name: Generate config
command: |
yarn dlx jiti ./scripts/ci/main.ts --workflow=<< pipeline.parameters.workflow >>
yarn dlx jiti ./scripts/ci/main.ts \
--workflow=<< pipeline.parameters.workflow >> \
--gh-trusted-author=<< pipeline.parameters.ghTrustedAuthor >>
- continuation/continue:
configuration_path: .circleci/config.generated.yml
workflows:
Expand Down
15 changes: 13 additions & 2 deletions .github/actions/setup-node-and-install/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ runs:
shell: bash
run: npm install -g npm@latest

- name: Cache dependencies
uses: actions/cache@v4
# Restore only — save is gated below. actions/cache's post-job save step uses a
# runner-internal token that bypasses `permissions:`, so splitting is the only
# reliable way to prevent pull_request_target runs from poisoning the shared cache.
- name: Restore cached dependencies
uses: actions/cache/restore@v4
with:
path: |
~/.yarn/berry/cache
Expand All @@ -39,3 +42,11 @@ runs:
shell: bash
working-directory: code
run: yarn install

- name: Save cached dependencies
if: github.event_name != 'pull_request_target'
uses: actions/cache/save@v4
with:
path: |
~/.yarn/berry/cache
key: yarn-v1-${{ hashFiles('yarn.lock') }}
20 changes: 20 additions & 0 deletions .github/workflows/trigger-circle-ci-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,30 @@ jobs:
run: echo "workflow=merged" >> $GITHUB_ENV
- if: github.event_name == 'pull_request_target' && (contains(github.event.pull_request.labels.*.name, 'ci:daily'))
run: echo "workflow=daily" >> $GITHUB_ENV
- id: trusted-author
env:
EVENT_NAME: ${{ github.event_name }}
ASSOCIATION: ${{ github.event.pull_request.author_association }}
USER_TYPE: ${{ github.event.pull_request.user.type }}
USER_LOGIN: ${{ github.event.pull_request.user.login }}
run: |
# You can only push to `main` and `next` as a core team member, so the content is trustworthy.
if [ "$EVENT_NAME" = "push" ]; then
echo "result=true" >> $GITHUB_OUTPUT
# These commits are made by the release actions, which are gated to core team members.
elif [ "$USER_LOGIN" = "github-actions[bot]" ] && [ "$USER_TYPE" = "Bot" ]; then
echo "result=true" >> $GITHUB_OUTPUT
# Trusted members of the organization can also write to cache (core team, DX, and a few maintainers)
Comment thread
Sidnioulz marked this conversation as resolved.
elif { [ "$ASSOCIATION" = "OWNER" ] || [ "$ASSOCIATION" = "MEMBER" ]; } && [ "$USER_TYPE" != "Bot" ]; then
echo "result=true" >> $GITHUB_OUTPUT
else
echo "result=false" >> $GITHUB_OUTPUT
fi
outputs:
workflow: ${{ env.workflow }}
ghBaseBranch: ${{ github.event.pull_request.base.ref }}
ghPrNumber: ${{ github.event.pull_request.number }}
ghTrustedAuthor: ${{ steps.trusted-author.outputs.result }}

trigger-circle-ci-workflow:
runs-on: ubuntu-latest
Expand Down
3 changes: 2 additions & 1 deletion scripts/ci/common-jobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
workflow,
workspace,
} from './utils/helpers.ts';
import { isTrustedAuthor } from './utils/runtime.ts';
import { defineJob, defineNoOpJob } from './utils/types.ts';

const dirname = import.meta.dirname;
Expand All @@ -29,7 +30,7 @@ export const build_linux = defineJob('Build (linux)', (workflowName) => ({
steps: [
git.checkout(),
npm.install('.'),
cache.persist(CACHE_PATHS, CACHE_KEYS()[0]),
...(isTrustedAuthor() ? [cache.persist(CACHE_PATHS, CACHE_KEYS()[0])] : []),
git.check(),
npm.check(),
{
Expand Down
11 changes: 10 additions & 1 deletion scripts/ci/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { executors } from './utils/executors.ts';
import { ensureRequiredJobs } from './utils/helpers.ts';
import { orbs } from './utils/orbs.ts';
import { parameters } from './utils/parameters.ts';
import { setTrustedAuthor } from './utils/runtime.ts';
import type {
JobImplementationObj,
JobOrNoOpJob,
Expand Down Expand Up @@ -149,11 +150,19 @@ console.log('--------------------------------');
program
.description('Generate CircleCI config')
.requiredOption('-w, --workflow <string>', 'Workflow to generate config for')
.option(
'--gh-trusted-author <string>',
'Whether the pipeline can persist to shared caches',
'false'
)
.parse(process.argv);

const opts = program.opts();
setTrustedAuthor(opts.ghTrustedAuthor === 'true');

await fs.writeFile(
join(dirname, '../../.circleci/config.generated.yml'),
yml.stringify(generateConfig(program.opts().workflow), null, {
yml.stringify(generateConfig(opts.workflow), null, {
lineWidth: 1200,
indent: 4,
})
Expand Down
6 changes: 6 additions & 0 deletions scripts/ci/utils/parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ export const parameters = {
description: 'The PR number',
type: 'string',
},
ghTrustedAuthor: {
default: 'false',
description:
'Whether the pipeline is allowed to persist to shared caches (team member PRs and push events only)',
type: 'string',
},
workflow: {
default: 'skipped',
description: 'Which workflow to run',
Expand Down
9 changes: 9 additions & 0 deletions scripts/ci/utils/runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let trustedAuthor = false;

export function setTrustedAuthor(isTrusted: boolean): void {
trustedAuthor = isTrusted;
}

export function isTrustedAuthor(): boolean {
return trustedAuthor;
}
Loading