CI: let the PR checks fail, check the built manifests, drop the auto bots

- continue-on-error came with the upstream template: lint, build and test
  have been red here while every pull request read as passing (12 test
  failures and 603 lint errors before this branch). fail-fast: false keeps
  each step running when another fails.
- after build, scripts/check-built-manifests.py checks the package.json in
  every build/ directory, the file npm receives (the January 2026 ./cjs/cjs
  fault, and the wasm 7.5.19 one).
- auto-approve/auto-merge (upstream author's actions, merge on a '-auto'
  label once checks reported) are removed, as in pezkuwi-apps.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Satoshi Qazi MuhammedandClaude Opus 5.5 authored and SatoshiQaziMuhammed committed 2026-10-07 19:21:45 +04:00
1 parent c87daf5f97
commit ad3816da8e
4 files changed
+106 -33

No files matched your search

-16
View File
@@ -1,16 +0,0 @@
name: bot
on:
pull_request:
types: [labeled]
jobs:
approve:
if: "! startsWith(github.event.head_commit.message, '[CI Skip]') && (!github.event.pull_request || github.event.pull_request.head.repo.full_name == github.repository)"
runs-on: ubuntu-latest
steps:
- uses: jacogr/action-approve@795afd1dd096a2071d7ec98740661af4e853b7da
with:
authors: jacogr, TarikGul
labels: -auto
token: ${{ secrets.GH_PAT_BOT }}
-16
View File
@@ -1,16 +0,0 @@
name: bot
on:
pull_request:
types: [labeled]
jobs:
merge:
runs-on: ubuntu-latest
steps:
- uses: jacogr/action-merge@d2d64b4545acd93b0a9575177d3d215ae3f92029
with:
checks: pr (build),pr (lint),pr (test)
labels: -auto
strategy: squash
token: ${{ secrets.GH_PAT_BOT }}
+7 -1
View File
@@ -3,8 +3,8 @@ on: [pull_request]
jobs: jobs:
pr: pr:
continue-on-error: true
strategy: strategy:
fail-fast: false
matrix: matrix:
step: ['lint', 'test', 'build', 'deno'] step: ['lint', 'test', 'build', 'deno']
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -26,3 +26,9 @@ jobs:
deno --version deno --version
yarn install --immutable yarn install --immutable
yarn ${{ matrix.step }} yarn ${{ matrix.step }}
# The package.json each build/ directory holds is what npm receives: every
# entry point must name a file that is there, with no ./build/ or ./cjs/cjs/
# path and no string spread into exports (the January 2026 fault).
- name: check built manifests
if: matrix.step == 'build'
run: python3 scripts/check-built-manifests.py .
+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""Check the package.json the build wrote into each packages/*/build directory.
This is the manifest npm receives. It fails on what broke the January 2026
releases and wasm 7.5.19: an entry point (main, module, types, browser,
react-native, every exports target) naming a file that is not in the build
directory, a path with ./build/ or a doubled ./cjs/cjs/ in it, numeric keys
inside exports (a string spread into an object), and workspace:/portal:/link:
dependency ranges.
check-built-manifests.py <repo-dir> [package ...]
Prints one line per package and exits 1 if any package fails.
"""
import json
import pathlib
import sys
ENTRY_FIELDS = ('main', 'module', 'types', 'typings', 'browser', 'react-native')
DEP_FIELDS = ('dependencies', 'peerDependencies', 'optionalDependencies')
def targets(node, path):
"""Yield (json-path, target string) for every string leaf under exports."""
if isinstance(node, str):
yield path, node
elif isinstance(node, dict):
for key, value in node.items():
yield from targets(value, f'{path}/{key}')
elif isinstance(node, list):
for i, value in enumerate(node):
yield from targets(value, f'{path}[{i}]')
def numeric_keys(node, path):
if isinstance(node, dict):
for key, value in node.items():
if key.isdigit():
yield f'{path}/{key}'
yield from numeric_keys(value, f'{path}/{key}')
def check(build):
problems = []
manifest = build / 'package.json'
if not manifest.is_file():
return ['no build/package.json']
pkg = json.loads(manifest.read_text())
def check_path(where, target):
if not isinstance(target, str) or not target.startswith('.'):
return
if '/build/' in target or target.startswith('./build') or '/cjs/cjs/' in target:
problems.append(f'{where}: suspicious path {target}')
if '*' in target:
base = build / target.split('*', 1)[0]
if not base.parent.exists():
problems.append(f'{where}: {target} has no directory')
return
if not (build / target).is_file():
problems.append(f'{where}: {target} does not exist')
for field in ENTRY_FIELDS:
value = pkg.get(field)
if isinstance(value, str):
check_path(field, value)
for where, target in targets(pkg.get('exports'), 'exports'):
check_path(where, target)
for where in numeric_keys(pkg.get('exports'), 'exports'):
problems.append(f'{where}: numeric key (a string spread into an object)')
for field in DEP_FIELDS:
for dep, rng in (pkg.get(field) or {}).items():
if str(rng).startswith(('workspace:', 'portal:', 'link:', 'file:')):
problems.append(f'{field}.{dep}: {rng}')
return problems
def main():
repo = pathlib.Path(sys.argv[1])
names = sys.argv[2:]
builds = [repo / 'packages' / n / 'build' for n in names] if names else sorted(repo.glob('packages/*/build'))
failed = 0
for build in builds:
problems = check(build)
name = build.parent.name
if problems:
failed += 1
print(f'FAIL {name}')
for p in problems[:12]:
print(f' {p}')
if len(problems) > 12:
print(f' ... {len(problems) - 12} more')
else:
print(f'ok {name}')
sys.exit(1 if failed else 0)
if __name__ == '__main__':
main()