code-foundry 1.48.7 → 1.50.0

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.
@@ -43,6 +43,11 @@ on:
43
43
  required: false
44
44
  type: boolean
45
45
  default: false
46
+ npm-publish-auth:
47
+ description: Select automatic token compatibility or force trusted OIDC publication.
48
+ required: false
49
+ type: string
50
+ default: auto
46
51
  outputs:
47
52
  release_created:
48
53
  description: Whether Release Please created a release (possibly a draft).
@@ -63,6 +68,8 @@ on:
63
68
  required: false
64
69
  NPM_TOKEN:
65
70
  required: false
71
+ NPM_READ_TOKEN:
72
+ required: false
66
73
 
67
74
  permissions:
68
75
  actions: write
@@ -271,11 +278,12 @@ jobs:
271
278
  // Writes intermittently hit GitHub's secondary rate limiting from
272
279
  // busy automation accounts (403 with the generic not-accessible
273
280
  // message); back off and retry before giving up.
274
- const gh = (args) => {
281
+ const gh = (args, options = {}) => {
275
282
  for (const wait of [0, 10, 30]) {
276
283
  try {
277
284
  if (wait) sleep(wait);
278
285
  return execFileSync('gh', args, {
286
+ ...options,
279
287
  encoding: 'utf8',
280
288
  maxBuffer: 32 * 1024 * 1024,
281
289
  });
@@ -316,6 +324,18 @@ jobs:
316
324
  const latestTag = versionedTags[0] ?? '';
317
325
  const prefix = latestTag.replace(/v?\d+\.\d+\.\d+.*$/, '') || 'v';
318
326
 
327
+ // Include drafts and every page before deciding an object is absent.
328
+ const releasePages = JSON.parse(gh([
329
+ 'api', `repos/${repo}/releases`, '-X', 'GET', '-f', 'per_page=100',
330
+ '--paginate', '--slurp',
331
+ ]));
332
+ if (!Array.isArray(releasePages) || releasePages.some((page) =>
333
+ !Array.isArray(page) || page.some((release) =>
334
+ !release || typeof release.tag_name !== 'string' || !release.tag_name))) {
335
+ throw new Error('Invalid existing release listing; refusing release creation');
336
+ }
337
+ const existingReleaseTags = new Set(releasePages.flat().map((release) => release.tag_name));
338
+
319
339
  let ensured = 0;
320
340
  let failures = 0;
321
341
  let lastTag = '';
@@ -324,12 +344,7 @@ jobs:
324
344
  const version = /^chore\(main\): release (\S+)/.exec(pull.title)?.[1];
325
345
  if (!version) continue;
326
346
  const tag = `${prefix}v${version}`;
327
- // Draft releases are invisible to get-release-by-tag, so scan
328
- // the list endpoint (which returns drafts) for the tag.
329
- const releasesForCheck = JSON.parse(gh([
330
- 'api', `repos/${repo}/releases`, '-X', 'GET', '-f', 'per_page=100',
331
- ]));
332
- if (releasesForCheck.some((release) => release.tag_name === tag)) {
347
+ if (existingReleaseTags.has(tag)) {
333
348
  console.log(`${tag}: release object already exists`);
334
349
  } else {
335
350
  // No release object for this tag: create it. The tag may also
@@ -354,6 +369,7 @@ jobs:
354
369
  prerelease: version.includes('-'),
355
370
  });
356
371
  gh(['api', `repos/${repo}/releases`, '-X', 'POST', '--input', '-'], { input: body });
372
+ existingReleaseTags.add(tag);
357
373
  console.log(`${tag}: release object created`);
358
374
  } catch (error) {
359
375
  failures += 1;
@@ -514,96 +530,106 @@ jobs:
514
530
  fi
515
531
 
516
532
  for pr in "${release_prs[@]}"; do
517
- pr_state=$(gh pr view "$pr" --repo "$GITHUB_REPOSITORY" \
518
- --json isDraft,headRefOid,headRefName \
519
- --jq '[.isDraft, .headRefOid, .headRefName] | @tsv')
520
- read -r PR_DRAFT PR_HEAD PR_BRANCH <<< "$pr_state"
521
- if [[ ! "$PR_DRAFT" =~ ^(true|false)$ || ! "$PR_HEAD" =~ ^[a-f0-9]{40}$ ]]; then
522
- echo 'Unable to verify generated release PR state.' >&2
523
- exit 1
524
- fi
525
- case "$PR_BRANCH" in
526
- release-please--branches--main|release-please--branches--main--*) ;;
527
- *) echo 'Release PR head left the generated namespace; refusing readiness mutation.' >&2; exit 1 ;;
528
- esac
529
- if [ "$AUTO_MERGE" = true ]; then
530
- if [ "$PR_DRAFT" = false ]; then
531
- # A ready head already receives synchronize validation. Preserve
532
- # its live/completed owner instead of cancelling and reallocating
533
- # CI every time another queued main release run prepares it.
534
- runs=$(GH_TOKEN="$VALIDATION_TOKEN" gh api \
535
- "repos/$GITHUB_REPOSITORY/actions/runs" --method GET \
536
- -f event=pull_request -f head_sha="$PR_HEAD" -f per_page=100)
537
- validation=$(jq -er --arg head "$PR_HEAD" --arg branch "$PR_BRANCH" \
538
- --arg repo "$GITHUB_REPOSITORY" '
539
- def active: .status == "queued" or .status == "in_progress" or
540
- .status == "waiting" or .status == "pending" or .status == "requested";
541
- def valid_run:
542
- type == "object" and
543
- (.id | type == "number" and . > 0 and floor == .) and
544
- (.run_attempt | type == "number" and . >= 1 and floor == .) and
545
- (.head_sha | type == "string" and test("^[a-f0-9]{40}$")) and
546
- (.head_branch | type == "string" and length > 0) and
547
- (.event | type == "string" and length > 0) and
548
- (.repository | type == "object") and
549
- (.repository.full_name | type == "string" and length > 0) and
550
- (.path | type == "string" and length > 0) and
551
- ((active and has("conclusion") and .conclusion == null) or
552
- (.status == "completed" and
553
- (.conclusion == "success" or .conclusion == "failure" or
554
- .conclusion == "timed_out" or .conclusion == "action_required" or
555
- .conclusion == "cancelled" or .conclusion == "skipped" or
556
- .conclusion == "neutral")));
557
- if (.workflow_runs | type) != "array" or
558
- (.total_count | type) != "number" then
559
- error("Invalid validation owner response")
560
- elif .total_count != (.workflow_runs | length) then
561
- error("Incomplete validation owner response")
562
- elif any(.workflow_runs[]; valid_run | not) then
563
- error("Invalid validation run identity or state")
564
- else
565
- [.workflow_runs[] | (.path | split("@")[0]) as $workflow | select(
566
- .head_sha == $head and .head_branch == $branch and
567
- .event == "pull_request" and .repository.full_name == $repo and
568
- ($workflow == ".github/workflows/validation.yml" or
569
- $workflow == ".github/workflows/validation_self-ci.yml")
570
- )] | sort_by(.id) | last |
571
- if . == null then "missing"
572
- elif active or (.status == "completed" and .conclusion == "success") then "owned"
573
- elif .status == "completed" and
574
- (.conclusion == "failure" or .conclusion == "timed_out" or
575
- .conclusion == "action_required") then "failed"
576
- elif .status == "completed" and
577
- (.conclusion == "cancelled" or .conclusion == "skipped" or
578
- .conclusion == "neutral") then "missing"
579
- else error("Unconfirmed validation owner state") end
580
- end' <<< "$runs")
581
- fresh_state=$(gh pr view "$pr" --repo "$GITHUB_REPOSITORY" \
582
- --json isDraft,headRefOid,headRefName \
583
- --jq '[.isDraft, .headRefOid, .headRefName] | @tsv')
584
- if [ "$fresh_state" != "$pr_state" ]; then
585
- echo 'Release PR state changed during validation lookup; refusing a stale readiness mutation.' >&2
586
- exit 1
587
- fi
588
- if [ "$validation" = owned ]; then
589
- echo "Release PR #$pr already has current-head validation; preserving readiness."
590
- continue
533
+ # A lock-sync/synchronize transition can move the PR while Actions is
534
+ # being read. Re-evaluate the whole tuple and its owner; never carry
535
+ # eligibility across heads or retry a stable failed qualification.
536
+ for state_attempt in 1 2 3; do
537
+ pr_state=$(gh pr view "$pr" --repo "$GITHUB_REPOSITORY" \
538
+ --json isDraft,headRefOid,headRefName \
539
+ --jq '[.isDraft, .headRefOid, .headRefName] | @tsv')
540
+ read -r PR_DRAFT PR_HEAD PR_BRANCH <<< "$pr_state"
541
+ if [[ ! "$PR_DRAFT" =~ ^(true|false)$ || ! "$PR_HEAD" =~ ^[a-f0-9]{40}$ ]]; then
542
+ echo 'Unable to verify generated release PR state.' >&2
543
+ exit 1
544
+ fi
545
+ case "$PR_BRANCH" in
546
+ release-please--branches--main|release-please--branches--main--*) ;;
547
+ *) echo 'Release PR head left the generated namespace; refusing readiness mutation.' >&2; exit 1 ;;
548
+ esac
549
+ if [ "$AUTO_MERGE" = true ]; then
550
+ if [ "$PR_DRAFT" = false ]; then
551
+ # A ready head already receives synchronize validation. Preserve
552
+ # its live/completed owner instead of cancelling and reallocating
553
+ # CI every time another queued main release run prepares it.
554
+ runs=$(GH_TOKEN="$VALIDATION_TOKEN" gh api \
555
+ "repos/$GITHUB_REPOSITORY/actions/runs" --method GET \
556
+ -f event=pull_request -f head_sha="$PR_HEAD" -f per_page=100)
557
+ validation=$(jq -er --arg head "$PR_HEAD" --arg branch "$PR_BRANCH" \
558
+ --arg repo "$GITHUB_REPOSITORY" '
559
+ def active: .status == "queued" or .status == "in_progress" or
560
+ .status == "waiting" or .status == "pending" or .status == "requested";
561
+ def valid_run:
562
+ type == "object" and
563
+ (.id | type == "number" and . > 0 and floor == .) and
564
+ (.run_attempt | type == "number" and . >= 1 and floor == .) and
565
+ (.head_sha | type == "string" and test("^[a-f0-9]{40}$")) and
566
+ (.head_branch | type == "string" and length > 0) and
567
+ (.event | type == "string" and length > 0) and
568
+ (.repository | type == "object") and
569
+ (.repository.full_name | type == "string" and length > 0) and
570
+ (.path | type == "string" and length > 0) and
571
+ ((active and has("conclusion") and .conclusion == null) or
572
+ (.status == "completed" and
573
+ (.conclusion == "success" or .conclusion == "failure" or
574
+ .conclusion == "timed_out" or .conclusion == "action_required" or
575
+ .conclusion == "cancelled" or .conclusion == "skipped" or
576
+ .conclusion == "neutral")));
577
+ if (.workflow_runs | type) != "array" or
578
+ (.total_count | type) != "number" then
579
+ error("Invalid validation owner response")
580
+ elif .total_count != (.workflow_runs | length) then
581
+ error("Incomplete validation owner response")
582
+ elif any(.workflow_runs[]; valid_run | not) then
583
+ error("Invalid validation run identity or state")
584
+ else
585
+ [.workflow_runs[] | (.path | split("@")[0]) as $workflow | select(
586
+ .head_sha == $head and .head_branch == $branch and
587
+ .event == "pull_request" and .repository.full_name == $repo and
588
+ ($workflow == ".github/workflows/validation.yml" or
589
+ $workflow == ".github/workflows/validation_self-ci.yml")
590
+ )] | sort_by(.id) | last |
591
+ if . == null then "missing"
592
+ elif active or (.status == "completed" and .conclusion == "success") then "owned"
593
+ elif .status == "completed" and
594
+ (.conclusion == "failure" or .conclusion == "timed_out" or
595
+ .conclusion == "action_required") then "failed"
596
+ elif .status == "completed" and
597
+ (.conclusion == "cancelled" or .conclusion == "skipped" or
598
+ .conclusion == "neutral") then "missing"
599
+ else error("Unconfirmed validation owner state") end
600
+ end' <<< "$runs")
601
+ fresh_state=$(gh pr view "$pr" --repo "$GITHUB_REPOSITORY" \
602
+ --json isDraft,headRefOid,headRefName \
603
+ --jq '[.isDraft, .headRefOid, .headRefName] | @tsv')
604
+ if [ "$fresh_state" != "$pr_state" ]; then
605
+ if [ "$state_attempt" -eq 3 ]; then
606
+ echo 'Release PR state did not stabilize during validation lookup; refusing readiness mutation.' >&2
607
+ exit 1
608
+ fi
609
+ echo "Release PR #$pr changed during validation lookup; reading its complete state and owner again."
610
+ continue
611
+ fi
612
+ if [ "$validation" = owned ]; then
613
+ echo "Release PR #$pr already has current-head validation; preserving readiness."
614
+ break
615
+ fi
616
+ if [ "$validation" = failed ]; then
617
+ echo "Current-head validation failed for release PR #$pr; resolve or explicitly retry it before release." >&2
618
+ exit 1
619
+ fi
620
+ # A missing, cancelled or skipped owner still needs the existing
621
+ # explicit ready_for_review transition with the validated token.
622
+ gh pr ready --undo "$pr" --repo "$GITHUB_REPOSITORY"
591
623
  fi
592
- if [ "$validation" = failed ]; then
593
- echo "Current-head validation failed for release PR #$pr; resolve or explicitly retry it before release." >&2
594
- exit 1
624
+ gh pr ready "$pr" --repo "$GITHUB_REPOSITORY"
625
+ else
626
+ if [ "$PR_DRAFT" = false ]; then
627
+ gh pr ready --undo "$pr" --repo "$GITHUB_REPOSITORY"
595
628
  fi
596
- # A missing, cancelled or skipped owner still needs the existing
597
- # explicit ready_for_review transition with the validated token.
598
- gh pr ready --undo "$pr" --repo "$GITHUB_REPOSITORY"
629
+ echo "::notice title=Manual release merge required::Release Please created or updated a version pull request. No valid automation token is available (absent or rejected by GitHub), so release PRs are left in draft and need manual readiness before validation and merge workflows trigger."
599
630
  fi
600
- gh pr ready "$pr" --repo "$GITHUB_REPOSITORY"
601
- else
602
- if [ "$PR_DRAFT" = false ]; then
603
- gh pr ready --undo "$pr" --repo "$GITHUB_REPOSITORY"
604
- fi
605
- echo "::notice title=Manual release merge required::Release Please created or updated a version pull request. No valid automation token is available (absent or rejected by GitHub), so release PRs are left in draft and need manual readiness before validation and merge workflows trigger."
606
- fi
631
+ break
632
+ done
607
633
  done
608
634
  - name: Leave release pull request for manual merge
609
635
  if: steps.release.outputs.prs_created == 'true' && steps.credentials.outputs.auto_merge != 'true'
@@ -824,24 +850,30 @@ jobs:
824
850
  contents: read
825
851
  id-token: write
826
852
  env:
827
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
853
+ NPM_PUBLISH_AUTH: ${{ inputs['npm-publish-auth'] }}
854
+ HAS_NPM_TOKEN: ${{ secrets.NPM_TOKEN != '' }}
855
+ HAS_NPM_READ_TOKEN: ${{ secrets.NPM_READ_TOKEN != '' }}
828
856
  steps:
829
857
  - name: Checkout release
830
858
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
831
859
  with:
832
860
  persist-credentials: false
833
861
  ref: ${{ needs.release.outputs.tag_name }}
834
- - name: Setup Node (trusted)
835
- if: env.NPM_TOKEN == ''
836
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
837
- with:
838
- node-version: 24
839
- - name: Setup Node (token)
840
- if: env.NPM_TOKEN != ''
862
+ - name: Validate npm publication authentication
863
+ run: |
864
+ set -euo pipefail
865
+ case "$NPM_PUBLISH_AUTH" in
866
+ auto|oidc|token) ;;
867
+ *) echo "Unsupported npm-publish-auth: $NPM_PUBLISH_AUTH" >&2; exit 1 ;;
868
+ esac
869
+ if [ "$NPM_PUBLISH_AUTH" = token ] && [ "$HAS_NPM_TOKEN" != true ]; then
870
+ echo 'npm-publish-auth token requires the NPM_TOKEN secret.' >&2
871
+ exit 1
872
+ fi
873
+ - name: Setup Node
841
874
  uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
842
875
  with:
843
876
  node-version: 24
844
- registry-url: https://registry.npmjs.org
845
877
  - name: Setup Bun (lockfile managers)
846
878
  # The install step below dispatches on lockfile: bun, pnpm, yarn, or
847
879
  # npm. Stock runners only provide Node, so provision Bun (oven-sh)
@@ -855,8 +887,18 @@ jobs:
855
887
  - name: Install dependencies
856
888
  # npm publish runs prepublishOnly (usually a build), which needs
857
889
  # devDependencies present. Install with the project's own manager.
890
+ env:
891
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_READ_TOKEN || (inputs['npm-publish-auth'] != 'oidc' && secrets.NPM_TOKEN) || '' }}
858
892
  run: |
859
893
  set -euo pipefail
894
+ npmrc=''
895
+ if [ -n "${NODE_AUTH_TOKEN:-}" ]; then
896
+ npmrc="$(mktemp)"
897
+ chmod 600 "$npmrc"
898
+ printf '//registry.npmjs.org/:_authToken=%s\n' "$NODE_AUTH_TOKEN" > "$npmrc"
899
+ export NPM_CONFIG_USERCONFIG="$npmrc"
900
+ fi
901
+ trap '[ -z "$npmrc" ] || rm -f "$npmrc"' EXIT
860
902
  if [ -f bun.lock ] || [ -f bun.lockb ]; then bun install --frozen-lockfile
861
903
  elif [ -f pnpm-lock.yaml ]; then pnpm install --frozen-lockfile
862
904
  elif [ -f yarn.lock ]; then yarn install --immutable
@@ -864,10 +906,16 @@ jobs:
864
906
  elif [ -f package.json ]; then npm install --no-audit --fund=false --package-lock=false
865
907
  fi
866
908
  - name: Publish npm with token
867
- if: env.NPM_TOKEN != ''
909
+ if: inputs['npm-publish-auth'] == 'token' || (inputs['npm-publish-auth'] == 'auto' && env.HAS_NPM_TOKEN == 'true')
868
910
  env:
869
- NODE_AUTH_TOKEN: ${{ env.NPM_TOKEN }}
870
- run: npm publish --provenance --access public
911
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
912
+ run: |
913
+ set -euo pipefail
914
+ npmrc="$(mktemp)"
915
+ chmod 600 "$npmrc"
916
+ printf '//registry.npmjs.org/:_authToken=%s\n' "$NODE_AUTH_TOKEN" > "$npmrc"
917
+ trap 'rm -f "$npmrc"' EXIT
918
+ NPM_CONFIG_USERCONFIG="$npmrc" npm publish --provenance --access public
871
919
  - name: Publish npm with trusted publishing
872
- if: env.NPM_TOKEN == ''
920
+ if: inputs['npm-publish-auth'] == 'oidc' || (inputs['npm-publish-auth'] == 'auto' && env.HAS_NPM_TOKEN != 'true')
873
921
  run: npm publish --provenance --access public
@@ -256,5 +256,3 @@ jobs:
256
256
  tag: ${{ needs.release.outputs.tag_name || needs.recovery.outputs.tag }}
257
257
  asset: ${{ needs.qualification.outputs.filename }}
258
258
  billing-pause-bypass: ${{ github.event_name == 'workflow_dispatch' && inputs['release-while-paused'] == true }}
259
- secrets:
260
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -140,7 +140,9 @@ jobs:
140
140
  dependency-audit:
141
141
  name: Dependency Audit (${{ matrix.entry.display }})
142
142
  needs: profile
143
- if: vars.CI_BILLING_PAUSED != 'true' && needs.profile.result == 'success'
143
+ # Avoid expanding an empty matrix; skip only the explicitly empty result.
144
+ # Missing or malformed output still fails closed during matrix expansion.
145
+ if: vars.CI_BILLING_PAUSED != 'true' && needs.profile.result == 'success' && needs.profile.outputs.audit_matrix != '[]'
144
146
  strategy:
145
147
  fail-fast: false
146
148
  max-parallel: 8
@@ -38,6 +38,11 @@ on:
38
38
  required: false
39
39
  type: string
40
40
  default: task-result
41
+ integration-total-shards:
42
+ description: Integration partitions (1 or 2); sharded scripts must honor INTEGRATION_SHARD_INDEX and INTEGRATION_TOTAL_SHARDS.
43
+ required: false
44
+ type: string
45
+ default: '1'
41
46
  e2e-shard-list:
42
47
  description: Comma-separated E2E shard indices, for example "1,2". A single index runs one unsharded job.
43
48
  required: false
@@ -255,10 +260,14 @@ jobs:
255
260
 
256
261
  integration:
257
262
  name: Integration
258
- if: vars.CI_BILLING_PAUSED != 'true' && inputs.unit-only != true
263
+ if: vars.CI_BILLING_PAUSED != 'true' && inputs.unit-only != true && inputs.integration-total-shards != '2'
259
264
  runs-on: ${{ inputs.runner }}
260
265
  timeout-minutes: 30
261
266
  steps:
267
+ - name: Validate integration shard count
268
+ env:
269
+ SHARDS: ${{ inputs.integration-total-shards }}
270
+ run: test "$SHARDS" = 1
262
271
  - name: Checkout
263
272
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
264
273
  with:
@@ -326,6 +335,86 @@ jobs:
326
335
  - name: Save Vite+ task cache
327
336
  uses: ./.github/actions/vite-plus-cache-save
328
337
 
338
+ integration-sharded:
339
+ name: Integration shard
340
+ if: vars.CI_BILLING_PAUSED != 'true' && inputs.unit-only != true && inputs.integration-total-shards == '2'
341
+ runs-on: ${{ inputs.runner }}
342
+ timeout-minutes: 30
343
+ strategy:
344
+ fail-fast: false
345
+ matrix:
346
+ shard: [1, 2]
347
+ steps:
348
+ - name: Checkout
349
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
350
+ with:
351
+ persist-credentials: false
352
+ fetch-depth: 2
353
+ - name: Runtime
354
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
355
+ with:
356
+ persist-credentials: false
357
+ repository: ${{ inputs.runtime-repository }}
358
+ ref: ${{ inputs.runtime-ref }}
359
+ path: .github/.code-foundry
360
+ sparse-checkout: |
361
+ .github/actions
362
+ src/lib
363
+ src/runtime-core.mjs
364
+ src/runtime.mjs
365
+ - name: Install runtime
366
+ run: |
367
+ mkdir -p .github/actions
368
+ mv .github/.code-foundry "$RUNNER_TEMP/code-foundry"
369
+ cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
370
+ - name: Detect
371
+ id: applicability
372
+ env:
373
+ EVENT_NAME: ${{ github.event_name }}
374
+ BASE_REF: ${{ github.event.pull_request.base.ref }}
375
+ BASE_SHA: ${{ github.event.pull_request.base.sha }}
376
+ HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
377
+ BEFORE_SHA: ${{ github.event.before }}
378
+ run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci task_profile integration >> "$GITHUB_OUTPUT"
379
+ - name: Setup
380
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
381
+ uses: ./.github/actions/setup
382
+ with:
383
+ install: true
384
+ install-javascript: ${{ steps.applicability.outputs.javascript == 'true' }}
385
+ install-python: ${{ steps.applicability.outputs.python == 'true' }}
386
+ install-rust: ${{ steps.applicability.outputs.rust == 'true' }}
387
+ cache-build: ${{ steps.applicability.outputs.rust == 'true' }}
388
+ cache-environment: false
389
+ cache-save: ${{ github.event_name == 'push' }}
390
+ task: integration
391
+ task-javascript: ${{ steps.applicability.outputs.javascript }}
392
+ task-python: ${{ steps.applicability.outputs.python }}
393
+ task-rust: ${{ steps.applicability.outputs.rust }}
394
+ - name: Integration
395
+ id: execute
396
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
397
+ env:
398
+ INTEGRATION_SHARD_INDEX: ${{ matrix.shard }}
399
+ INTEGRATION_TOTAL_SHARDS: ${{ inputs.integration-total-shards }}
400
+ run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci integration
401
+ - name: Retain task receipt
402
+ if: >-
403
+ always() &&
404
+ (steps.execute.outcome == 'success' || steps.execute.outcome == 'failure' ||
405
+ steps.applicability.outputs.applicable == 'false' || steps.applicability.outputs.affected == 'false') &&
406
+ hashFiles('.code-foundry/results/integration.json') != ''
407
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
408
+ with:
409
+ name: ${{ inputs.artifact-prefix }}-${{ github.run_id }}-${{ github.run_attempt }}-integration-s${{ matrix.shard }}
410
+ path: .code-foundry/results/integration.json
411
+ include-hidden-files: true
412
+ if-no-files-found: error
413
+ retention-days: 14
414
+
415
+ - name: Save Vite+ task cache
416
+ uses: ./.github/actions/vite-plus-cache-save
417
+
329
418
  e2e:
330
419
  name: E2E
331
420
  if: vars.CI_BILLING_PAUSED != 'true' && inputs.unit-only != true