Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
11 changes: 11 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Bundled action distributions: emitted by ncc as LF on every platform.
# Forcing LF here prevents Windows checkouts from producing phantom diffs
# against the committed bundles and keeps the check-dist guard reliable.
#
# Scope is intentionally narrow: `*/dist/**` matches only `<action>/dist/**`
# (single path segment + /dist/), not nested `<action>/node_modules/<pkg>/dist/**`.
# The wider `**/dist/**` would reach into committed node_modules trees of
# the 9 legacy unbundled actions, where the explicit `text` attribute
# would override git's binary auto-detection and risk corrupting any
# binary content (e.g. .node native modules) shipped in upstream dist dirs.
*/dist/** text eol=lf
17 changes: 17 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,20 @@ updates:
actions:
patterns:
- "*"

# Bundled Node actions: every directory with a package.json gets its own
# weekly PR. Minor + patch updates are grouped per action; majors arrive
# as individual PRs so each can be canary-tested separately.
- package-ecosystem: npm
directories:
- "/*"
schedule:
interval: weekly
open-pull-requests-limit: 10
groups:
minor-and-patch:
patterns:
- "*"
update-types:
- "minor"
- "patch"
163 changes: 163 additions & 0 deletions .github/workflows/check-dist.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
name: check-dist

# Verifies that each Node action's committed dist/ matches what ncc would
# emit from its current src/ + package-lock.json. Fails the PR if the bundle
# is stale, so contributors cannot ship a src/ change without rebuilding.

on:
pull_request:
paths:
- "*/src/**"
- "*/index.js" # actions like changelog-generator that bundle a root index.js
- "*/package.json"
- "*/package-lock.json"
- "*/tsconfig.json"
- ".github/workflows/check-dist.yml"

permissions:
contents: read

jobs:
detect:
runs-on: ubuntu-latest
outputs:
actions: ${{ steps.changed.outputs.actions }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0

- id: changed
name: List action directories with relevant changes
# Route GHA-context values through env: so they cannot be expanded
# into shell as code (defense-in-depth; see GitHub Actions
# hardening guide on script-injection risks).
env:
BASE_REF: ${{ github.base_ref }}
run: |
dirs=$(git diff --name-only \
"origin/${BASE_REF}...HEAD" \
-- '*/src/**' '*/index.js' '*/package.json' '*/package-lock.json' '*/tsconfig.json' \
| awk -F/ '$1 != "" && $1 != ".github" {print $1}' \
| sort -u \
| jq -R . | jq -sc .)
echo "actions=$dirs" >> "$GITHUB_OUTPUT"
echo "Detected: $dirs"

build-and-diff:
needs: detect
if: needs.detect.outputs.actions != '[]'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
action: ${{ fromJson(needs.detect.outputs.actions) }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

# Gate runs before setup-node so legacy / partial actions (no
# package.json, no lockfile, or no build script) short-circuit
# cleanly. setup-node with cache-dependency-path pointing at a
# missing lockfile would fail before the gate could fire.
- name: Skip actions that aren't bundled with npm
id: gate
working-directory: ${{ matrix.action }}
env:
ACTION: ${{ matrix.action }}
run: |
if [ ! -f package.json ]; then
echo "::notice::$ACTION has no package.json; skipping."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
if [ ! -f package-lock.json ]; then
echo "::notice::$ACTION has no package-lock.json; skipping."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
if ! jq -e '.scripts.build' package.json >/dev/null; then
echo "::notice::$ACTION has no build script; skipping."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
if: steps.gate.outputs.skip != 'true'
with:
node-version: 24
cache: npm
cache-dependency-path: ${{ matrix.action }}/package-lock.json

- name: Install dependencies
if: steps.gate.outputs.skip != 'true'
working-directory: ${{ matrix.action }}
run: npm ci --no-audit --no-fund

- name: Rebuild dist/
if: steps.gate.outputs.skip != 'true'
working-directory: ${{ matrix.action }}
run: npm run build

- name: Smoke-load the bundle
if: steps.gate.outputs.skip != 'true'
working-directory: ${{ matrix.action }}
run: |
# node --check only parses. require() runs module init, so a
# structurally broken bundle (missing internal require,
# undefined identifier, malformed export) surfaces here.
#
# Discriminate by error class:
# - SyntaxError / ReferenceError / "Cannot find module"
# => bundle is broken, exit 2 (step fails).
# - Anything else (TypeError from missing env, action's own
# thrown Error on missing GITHUB_TOKEN / event payload, etc.)
# => bundle loaded fine, the action just can't run without
# a real GHA runtime. Tolerate.
# process.exit(0) at the end so process.exitCode set by the
# bundle's run().catch(core.setFailed) doesn't fail the step.
node --check dist/index.js
node -e "
try { require('./dist/index.js'); }
catch (e) {
const broken =
e instanceof SyntaxError ||
e instanceof ReferenceError ||
(e && e.code === 'MODULE_NOT_FOUND') ||
/Cannot find module/i.test(e && e.message || '');
if (broken) {
console.error('Bundle load failure:', e && e.stack || e);
process.exit(2);
}
// Otherwise: the action threw on a runtime precondition
// (missing env / token / payload). Bundle is fine.
}
process.exit(0);
"

- name: Verify committed dist/ matches rebuild
if: steps.gate.outputs.skip != 'true'
working-directory: ${{ matrix.action }}
env:
ACTION: ${{ matrix.action }}
run: |
# Only the runtime bundle (dist/index.js) and the licenses file
# are gated — those must be reproducible across platforms. The
# sourcemap (dist/index.js.map) and the sourcemap-register shim
# are intentionally excluded: ncc inlines upstream source files
# whose CRLF/LF endings differ between Windows-committed and
# Linux-CI-rebuilt copies, producing a benign byte diff that
# doesn't affect runtime behaviour.
#
# Stage with `git add` before diffing --cached so a brand-new
# untracked dist/index.js (e.g. a PR adding an action without
# committing the bundle) is caught -- plain `git diff` is blind
# to untracked files. Same pattern as dependabot-rebuild.yml.
paths=(dist/index.js)
[ -f dist/licenses.txt ] && paths+=(dist/licenses.txt)
[ -f dist/LICENSES ] && paths+=(dist/LICENSES)
git add -- "${paths[@]}"
if ! git diff --cached --quiet -- "${paths[@]}"; then
echo "::error::dist/ in $ACTION is missing or stale. Run 'npm ci && npm run build' inside that directory and commit the result."
git --no-pager diff --cached --stat -- "${paths[@]}"
exit 1
fi
Comment thread
cursor[bot] marked this conversation as resolved.
120 changes: 120 additions & 0 deletions .github/workflows/dependabot-rebuild.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
name: dependabot-rebuild

# When Dependabot opens an npm PR, rebuild dist/ for the affected action
# and push the result back to the PR branch. Without this, every Dependabot
# npm PR fails check-dist (the bundle is stale by construction), forcing a
# maintainer to rebuild locally before merge.

on:
pull_request_target:
types: [opened, synchronize]

permissions:
contents: write
pull-requests: read

jobs:
rebuild:
# Gate on the immutable PR author, not `github.actor` (which reflects
# the current event triggerer and is spoofable: a `@dependabot
# recreate` style command on an attacker's PR can cause
# github.actor to surface as dependabot[bot] on the resulting
# synchronize event, even though the PR itself was authored by
# someone else (CWE-290). pull_request.user.login is set when the
# PR is created and cannot change.
if: github.event.pull_request.user.login == 'dependabot[bot]'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
# Pin to the immutable head SHA, not the mutable branch ref,
# to close the TOCTOU window where someone with push access
# could race a malicious commit onto the Dependabot branch
# between event dispatch and checkout. The actor guard above
# checks the *event*-time actor (dependabot[bot]), but the
# branch tip could have moved since then. Pinning to head.sha
# ensures we build the exact commit the event fired for.
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
# REPO_TOKEN (org-level PAT) is required so the dist/ rebuild
# push re-triggers check-dist on the PR. The default
# GITHUB_TOKEN is suppressed from triggering workflows to
# prevent recursion (see GitHub docs on
# GITHUB_TOKEN-triggered events), which would leave the PR
# stuck with a stale check-dist failure.
token: ${{ secrets.REPO_TOKEN }}
fetch-depth: 0

- id: changed
name: List action directories with package.json/lock changes
# Route GHA-context values through env: so they cannot be expanded
# into shell as code (defense-in-depth for pull_request_target
# workflows; see GitHub Actions hardening guide).
env:
BASE_REF: ${{ github.event.pull_request.base.ref }}
run: |
dirs=$(git diff --name-only \
"origin/${BASE_REF}...HEAD" \
-- '*/package.json' '*/package-lock.json' \
| awk -F/ '$1 != "" && $1 != ".github" {print $1}' \
| sort -u)
{
echo "actions<<EOF"
echo "$dirs"
echo "EOF"
} >> "$GITHUB_OUTPUT"
echo "Detected: $dirs"

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
if: steps.changed.outputs.actions != ''
with:
node-version: 24

- name: Rebuild dist/ for each changed action
if: steps.changed.outputs.actions != ''
env:
ACTIONS: ${{ steps.changed.outputs.actions }}
run: |
while IFS= read -r action; do
[ -z "$action" ] && continue
if [ ! -f "$action/package.json" ]; then
echo "::notice::Skipping $action (no package.json)"
continue
fi
if [ ! -f "$action/package-lock.json" ]; then
echo "::notice::Skipping $action (no package-lock.json)"
continue
fi
if ! jq -e '.scripts.build' "$action/package.json" >/dev/null; then
echo "::notice::Skipping $action (no build script)"
continue
fi
echo "::group::Rebuilding $action"
(cd "$action" && npm ci --no-audit --no-fund && npm run build)
git add "$action/dist/"
echo "::endgroup::"
done <<< "$ACTIONS"

- name: Commit rebuilt dist/ if changed
# head.ref is mutable, but we only consult it AFTER the build
# has run against the pinned head.sha; using it as the push
# destination (not as a build input) is safe. Routed via env
# for the same reason every other dynamic value in this file
# is: it never reaches the shell as code.
env:
BRANCH_NAME: ${{ github.event.pull_request.head.ref }}
run: |
if git diff --cached --quiet; then
echo "No dist/ changes to commit."
exit 0
fi
git config user.name "dependabot[bot]"
git config user.email "49699333+dependabot[bot]@users.noreply.github.com"
git commit -m "chore: rebuild dist/ after dependency update"
# We checked out a detached HEAD at head.sha. Push the new
# commit back to the branch by name. If the branch has moved
# since checkout (concurrent Dependabot rebase, or the TOCTOU
# attacker scenario the SHA pin guards against), the push is
# rejected non-fast-forward and the step fails -- correct: we
# don't want to overwrite work we never validated.
git push origin "HEAD:$BRANCH_NAME"
1 change: 1 addition & 0 deletions .github/workflows/remove-file.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ on:
jobs:

remove-file:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
continue-on-error: true
env:
Expand Down
31 changes: 26 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,34 @@ ClientBin/
*.jfm
# *.pfx
*.publishsettings

# Local environment overrides for docker-compose stacks (e.g. docker-env-full).
# Compose auto-loads .env from the cwd; these files may contain image tags or
# credentials and should never be committed.
.env

node_modules/
orleans.codegen.cs
package-lock.json

# ncc build artifacts (source maps)
**/dist/index.js.map
**/dist/LICENSES
# Legacy pre-bundling actions: they have no ncc dist/ and ship their
# node_modules/ tree as the deployment artifact (action.yml points runs.main
# at the root index.js, which require()s deps at runtime). Migrate to ncc
# and remove the exception when revisiting one of these actions.
#
# Ordering invariant: these un-ignores MUST stay below the `node_modules/`
# rule above. Adding a new node_modules-related pattern after this block
# would re-shadow these directories. Keep new rules above the block, or
# update the un-ignores accordingly.
!add-version-suffix/node_modules/
!build-theme/node_modules/
!build-vue-theme/node_modules/
!katalon-studio-github-action/node_modules/
!publish-docker-image/node_modules/
!publish-theme/node_modules/
!setup-vcbuild/node_modules/
!sonar-scanner-begin/node_modules/
!sonar-theme/node_modules/

orleans.codegen.cs

# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
Expand Down
Loading
Loading