mirror of
https://github.com/pezkuwichain/pezkuwi-extension.git
synced 2026-10-21 11:16:30 +00:00
- pr-any: after each step the tree must be unchanged (yarn lint runs eslint --fix, yarn build rewrites packageInfo.ts), and after build scripts/check-built-manifests.py checks the package.json of each package in scripts/published-packages.txt: entry points exist, no ./build/ or ./cjs/cjs/ paths, no workspace: ranges. - push-main bumped the patch version on every push, pushed that commit to main as a bot, and copied the zips as root to 37.60.230.9, a host that has been retired; js.pezkuwichain.io no longer serves them and the extension is distributed through the stores. It now builds, checks the manifests and keeps the Chrome and Firefox packages as artifacts of the commit. The version is set in the release PR. - The auto-approve and auto-merge bots are removed. - ff-diff/, the output of yarn diff, is ignored by git and eslint.
100 lines
3.5 KiB
Python
Executable File
100 lines
3.5 KiB
Python
Executable File
#!/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()
|