@webority/repo-checks 0.78.0 → 0.79.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.
@@ -55,5 +55,5 @@ if [ -f "$local_checks" ]; then
55
55
  fi
56
56
  `;function f(e){let t=``,n=!1;for(let r=0;r<e.length;r+=1){let i=e[r];if(n){t+=i,i===`\\`?(t+=e[r+1]??``,r+=1):i===`"`&&(n=!1);continue}if(i===`"`)n=!0,t+=i;else if(i===`/`&&e[r+1]===`/`){for(;r<e.length&&e[r]!==`
57
57
  `;)r+=1;t+=`
58
- `}else if(i===`/`&&e[r+1]===`*`){for(r+=2;r<e.length&&!(e[r]===`*`&&e[r+1]===`/`);)r+=1;r+=1}else t+=i}return JSON.parse(t.replace(/,(\s*[}\]])/g,`$1`))}var p=[[`eslint`,`react.md: Biome is the only lint and format tool`],[`@eslint/js`,`react.md: Biome is the only lint and format tool`],[`prettier`,`react.md: Biome is the only lint and format tool`],[`lint-staged`,`react.md: the shared pre-commit hook replaces lint-staged`]],m=[[`@typescript-eslint/`,`react.md: no TypeScript toolchain residue`],[`eslint-plugin-`,`react.md: Biome is the only lint and format tool`],[`eslint-config-`,`react.md: Biome is the only lint and format tool`],[`prettier-plugin-`,`react.md: Biome is the only lint and format tool`]],h=`bootstrap-ui.md: hard interactive components come from @webority/ui-react`,g=`react.md: Bootstrap 5.3.3 is the only UI framework`,_=`react.md: dates format through Intl and the web-core formatters`,v=`react-native.md: retired; Intl and the mobile-core formatters`,y={web:[...p,[`typescript`,`react.md: JavaScript only, never TypeScript`],[`sass`,`react.md: the SCSS compiler is sass-embedded, never the pure-JS sass`],[`react-toastify`,`react.md: notifications are toast from @webority/ui-react`],[`downshift`,h],[`react-day-picker`,h],[`react-aria`,h],[`react-aria-components`,h],[`@webority/ui-elements`,`bootstrap-ui.md: products use the App* wrappers, and ui-react brings the elements itself`],[`crypto-js`,`react.md: stored tokens are never encrypted client-side`],[`tailwindcss`,g],[`antd`,g],[`styled-components`,`react.md: no CSS-in-JS`],[`bootstrap-icons`,`bootstrap-ui.md: icons are AppIcon from @webority/ui-react`],[`iconsax-react`,`ui-components.md: icons are AppIcon from @webority/ui-react`],[`moment`,_],[`moment-timezone`,_],[`dayjs`,_],[`date-fns`,_]],mobile:[...p,[`typescript`,`react-native.md: app code is JavaScript`],[`@types/react`,`react-native.md: app code is JavaScript`],[`@react-native/typescript-config`,`react-native.md: app code is JavaScript`],[`react-native-permissions`,`react-native.md: retired; each Expo module requests its own permission`],[`moment`,v],[`moment-timezone`,v],[`dayjs`,v],[`react-native-fast-image`,`react-native.md: retired; expo-image`],[`@d11/react-native-fast-image`,`react-native.md: retired; expo-image`],[`formik`,`react-native.md: retired; react-hook-form`],[`yup`,`react-native.md: retired; zod with zodResolver`],[`react-native-status-bar-height`,`react-native.md: retired; react-native-safe-area-context insets`],[`react-native-flash-message`,`react-native.md: retired; the mobile-ui toast`],[`react-native-toast-message`,`react-native.md: retired; the mobile-ui toast`],[`prop-types`,`react-native.md: retired; defaults in the parameter destructure`],[`@react-navigation/stack`,`react-native.md: retired; @react-navigation/native-stack`],[`react-native-version-check`,`react-native.md: retired; the server-driven minimum version`],[`@react-native-firebase/messaging`,`react-native.md: push is expo-notifications through mobile-core`],[`@notifee/react-native`,`react-native.md: push is expo-notifications through mobile-core`],[`patch-package`,`react-native-toolchain.md: patch with pnpm patch, never patch-package`]],demo:[]},b={web:[...m,[`@mui/`,g],[`@chakra-ui/`,g],[`@emotion/`,`react.md: no CSS-in-JS`],[`@radix-ui/`,h],[`@react-aria/`,h]],mobile:[...m],demo:[]},te={web:[[`axios-retry`,`@webority/web-core`,`react.md: retry lives only in @webority/web-core`]],mobile:[[`axios-retry`,`@webority/mobile-core`,`react.md: retry lives only in @webority/mobile-core`]],demo:[]},ne={web:[[`@biomejs/biome`,`react.md: Biome is mandatory`],[`@webority/biome-config`,`react.md: every repo extends the shared Biome preset`],[`@webority/web-core`,`react.md: clients, auth, errors and logging come from web-core`],[`@webority/ui-react`,`react.md: App* components come from the catalogue`],[`@webority/theme`,`bootstrap-ui.md: look and tokens come from the shared theme`],[`bootstrap`,`bootstrap-ui.md: Bootstrap 5.3.3 on every surface`],[`react-bootstrap`,`bootstrap-ui.md: react-bootstrap is a dependency from project init`],[`sass-embedded`,`react.md: the SCSS compiler is sass-embedded`]],mobile:[[`@biomejs/biome`,`react-native.md: Biome is mandatory`],[`@webority/biome-config`,`react.md: every repo extends the shared Biome preset`],[`@webority/mobile-core`,`react-native.md: apps consume the shared mobile library`],[`@webority/mobile-ui`,`react-native.md: apps consume the shared mobile library`],[`@react-native-firebase/app`,`react-native.md: Crashlytics in every app`],[`@react-native-firebase/crashlytics`,`react-native.md: Crashlytics in every app`]],demo:[[`@biomejs/biome`,`react.md: Biome is mandatory`],[`@webority/biome-config`,`react.md: every repo extends the shared Biome preset`]]},re=`5.3.3`,ie=[[`@webority/theme`,`@webority/ui-elements`,`@webority/ui-react`,`@webority/support-react`,`@webority/chat-react`,`@webority/web-core`,`@webority/biome-config`,`@webority/repo-checks`],[`@webority/mobile-core`,`@webority/mobile-ui`,`@webority/mobile-support`,`@webority/mobile-tools`]],ae=`@webority/biome-config/biome.jsonc`,x=`AppAccordion.AppActionIcon.AppAlert.AppAsyncBlock.AppAutocomplete.AppAvatar.AppAvatarGroup.AppBadge.AppBanner.AppBreadcrumbs.AppButton.AppCard.AppCheckbox.AppChip.AppChoiceCard.AppChoiceChip.AppColorField.AppColumnMenu.AppCommandPalette.AppCompareTable.AppConfirmDialog.AppConsentBanner.AppCopyButton.AppCountrySelect.AppDataTable.AppDatePicker.AppDateRangePicker.AppDescriptions.AppDropdown.AppEmptyState.AppErrorSummary.AppFileAttachment.AppFilePicker.AppFileViewer.AppFloatingDock.AppFormActions.AppFormRow.AppFormSection.AppIcon.AppInfoList.AppInfoTip.AppInput.AppKpiTile.AppLink.AppLogoPicker.AppMeter.AppModal.AppMultiSelect.AppNumberInput.AppOtpInput.AppPageHeader.AppPagination.AppPhoneNumberInput.AppPopover.AppPricingCard.AppProgress.AppQrCode.AppRadio.AppRadioGroup.AppRepeater.AppRequiredMark.AppResourceCard.AppResult.AppScrollbar.AppSearch.AppSegmented.AppSelect.AppSelectMini.AppShell.AppSidebarMenu.AppSkeleton.AppSkeletonRows.AppSlider.AppSpinner.AppSplash.AppStatusBadge.AppStepper.AppSwitch.AppTableEmptyState.AppTablePagination.AppTableToolbar.AppTabs.AppTagInput.AppTextarea.AppThemeToggle.AppTimeline.AppTimePicker.AppToast.AppToolbar.AppTooltip.AppWizard.AppWorkspaceSwitcher`.split(`.`),S=e=>e.split(`\\`).join(`/`),C=(e,t)=>S(l(e,t))||`.`;function w(e){try{return JSON.parse(r(e,`utf8`))}catch{return null}}function T(e,t){let n=ee(`git`,t,{cwd:e,encoding:`utf8`,windowsHide:!0,maxBuffer:64*1024*1024});return n.status===0?n.stdout:null}function oe(e){let t=T(e,[`rev-parse`,`--show-toplevel`]);return t?t.trim():null}function E(e,t){let n=T(e,[`ls-files`,`-z`,`--`,...t.flatMap(e=>[e,`:(glob)**/${e}`])]);return n?[...new Set(n.split(`\0`).filter(Boolean))]:[]}function se(e){let t=e?.dependencies??{};return t[`react-native`]?`mobile`:t[`react-dom`]?e?.devDependencies?.[`react-native-web`]||t[`react-native-web`]?`demo`:`web`:null}var D=e=>/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(e)||/^(?:workspace:\*|file:|link:)/.test(e),O=new Set([`@biomejs/biome`,`@webority/biome-config`]),ce=e=>/^(?:workspace:|file:|link:)/.test(e);function le(e,t,n){let r=[],i={...e.dependencies,...e.devDependencies},a=Object.keys(i);for(let[e,n]of y[t])i[e]&&r.push([`banned:${e}`,`"${e}" is a dependency. ${n}.`]);for(let[e,n]of b[t])for(let t of a.filter(t=>t.startsWith(e)))r.push([`banned:${t}`,`"${t}" is a dependency. ${n}.`]);for(let[e,n,a]of te[t])i[e]&&i[n]&&r.push([`banned:${e}`,`"${e}" is a dependency beside ${n}. ${a}.`]);for(let[e,a]of ne[t])(O.has(e)?n:i)[e]||r.push([`required:${e}`,`"${e}" is not a dependency. ${a}.`]);let o=t===`mobile`?a:a.filter(e=>e.startsWith(`@webority/`)||e===`bootstrap`);for(let e of o){let t=String(i[e]);D(t)||r.push([`not-exact:${e}`,`"${e}": "${t}" is a range. Pin the version the lockfile holds, without ^ or ~.`])}i.bootstrap&&String(i.bootstrap).replace(/^[\^~]/,``)!==`5.3.3`&&r.push([`bootstrap-version`,`bootstrap is "${i.bootstrap}". bootstrap-ui.md: every surface pins ${re}.`]);for(let e of ie){let t=e.filter(e=>i[e]&&!ce(String(i[e])));if(new Set(t.map(e=>String(i[e]).replace(/^[\^~]/,``))).size>1){let n=t.map(e=>`${e}@${i[e]}`).join(`, `);r.push([`lockstep:${e[0]}`,`${n} differ. These publish together at one version; move every pin in the set together.`])}}return r}function k(e,n,r){let i=e;for(;;){for(let e of r)if(t(c(i,e)))return c(i,e);if(i===n)return null;let e=s(i);if(e===i)return null;i=e}}function A(e,t){let n=e,i=null;for(;;){let e=k(n,t,[`biome.json`,`biome.jsonc`]);if(!e)return i;i=s(e);let a;try{a=f(r(e,`utf8`))}catch{return i}let o=s(i);if(a.root!==!1||i===t||o===i)return i;n=o}}function ue(e,t,n){let r=A(e,t),i=r&&r!==e?w(c(r,`package.json`)):null;return i?{...i.dependencies,...i.devDependencies}:n}function de(e,n){let i=[],a=k(e,n,[`pnpm-lock.yaml`]);a||i.push([`pnpm:no-lockfile`,`No pnpm-lock.yaml in this folder or above it. react.md: every JS repo installs with pnpm.`]);let o=a?s(a):e,l=w(c(o,`package.json`))??w(c(e,`package.json`));/^pnpm@/.test(l?.packageManager??``)||i.push([`pnpm:package-manager`,`package.json has no "packageManager": "pnpm@<version>". react.md: Package manager.`]);let u=c(o,`pnpm-workspace.yaml`);if(!t(u))i.push([`pnpm:no-workspace-file`,`No pnpm-workspace.yaml beside the lockfile. react.md: it holds engineStrict, strictPeerDependencies and allowBuilds.`]);else{let e=r(u,`utf8`);/^engineStrict:\s*true\s*$/m.test(e)||i.push([`pnpm:engine-strict`,`pnpm-workspace.yaml lacks engineStrict: true. react.md: a Node mismatch must fail the install.`]),/^strictPeerDependencies:\s*true\s*$/m.test(e)||i.push([`pnpm:strict-peers`,`pnpm-workspace.yaml lacks strictPeerDependencies: true. react.md: a peer conflict must fail the install.`])}for(let n of[`package-lock.json`,`yarn.lock`])t(c(e,n))&&i.push([`pnpm:${n}`,`${n} sits in this folder. react.md: pnpm only; seed pnpm-lock.yaml with pnpm import, then delete it.`]);return i}function fe(e,t,n){let i=k(e,t,[`biome.json`,`biome.jsonc`]);if(!i)return[[`biome:missing`,`No biome.json in this folder or above it. react.md: one committed root biome.json.`]];let a;try{a=f(r(i,`utf8`))}catch{return[[`biome:unreadable`,`${o(i)} does not parse.`]]}let s=[];return[].concat(a.extends??[]).some(e=>e===`@webority/biome-config/biome.jsonc`||String(e).endsWith(`biome-config/biome.jsonc`))||s.push([`biome:extends`,`${o(i)} does not extend "${ae}". react.md: every repo extends the shared preset.`]),a.formatter?.lineEnding&&s.push([`biome:line-ending`,`${o(i)} sets formatter.lineEnding. react.md: inherit it from the shared preset, never set it.`]),a.linter?.rules&&Object.keys(a.linter.rules).length>0&&s.push([`biome:root-rules`,`${o(i)} sets linter.rules at the root. react.md: never a rule's severity at the root; a per-path exception goes in overrides.`]),n===`mobile`&&!(a.javascript?.globals??[]).includes(`__DEV__`)&&s.push([`biome:dev-global`,`${o(i)} lacks "__DEV__" in javascript.globals. react-native.md: Debugging and agent access.`]),s}function pe(e){let t=c(e,`node_modules`,`@webority`,`ui-react`);try{let e=JSON.parse(r(c(t,`package.json`),`utf8`)),n=r(c(t,typeof e.module==`string`?e.module:typeof e.main==`string`?e.main:`dist/index.js`),`utf8`),i=new Set([...n.matchAll(/App[A-Z][A-Za-z0-9]*/g)].map(e=>e[0]));if(i.size>0)return i}catch{}return new Set(x)}function j(e){return i(e,{withFileTypes:!0}).flatMap(t=>t.isDirectory()?j(c(e,t.name)):[c(e,t.name)])}function me(e){let n=c(e,`src`,`components`);if(!t(n))return[];let r=pe(e),i=[];for(let t of j(n)){let n=o(t).split(`.`)[0];if(r.has(n)){let r=S(l(e,t));i.push([`catalogue-copy:${r}`,`${r} is named like the @webority/ui-react export ${n}. bootstrap-ui.md: use the catalogue component, never a local copy.`])}}return i}var M=`.husky/pre-commit`;function N(e){let n=c(e,M),i=t(n)?r(n,`utf8`).replace(/\r\n/g,`
58
+ `}else if(i===`/`&&e[r+1]===`*`){for(r+=2;r<e.length&&!(e[r]===`*`&&e[r+1]===`/`);)r+=1;r+=1}else t+=i}return JSON.parse(t.replace(/,(\s*[}\]])/g,`$1`))}var p=[[`eslint`,`react.md: Biome is the only lint and format tool`],[`@eslint/js`,`react.md: Biome is the only lint and format tool`],[`prettier`,`react.md: Biome is the only lint and format tool`],[`lint-staged`,`react.md: the shared pre-commit hook replaces lint-staged`]],m=[[`@typescript-eslint/`,`react.md: no TypeScript toolchain residue`],[`eslint-plugin-`,`react.md: Biome is the only lint and format tool`],[`eslint-config-`,`react.md: Biome is the only lint and format tool`],[`prettier-plugin-`,`react.md: Biome is the only lint and format tool`]],h=`bootstrap-ui.md: hard interactive components come from @webority/ui-react`,g=`react.md: Bootstrap 5.3.3 is the only UI framework`,_=`react.md: dates format through Intl and the web-core formatters`,v=`react-native.md: retired; Intl and the mobile-core formatters`,y={web:[...p,[`typescript`,`react.md: JavaScript only, never TypeScript`],[`sass`,`react.md: the SCSS compiler is sass-embedded, never the pure-JS sass`],[`react-toastify`,`react.md: notifications are toast from @webority/ui-react`],[`downshift`,h],[`react-day-picker`,h],[`react-aria`,h],[`react-aria-components`,h],[`@webority/ui-elements`,`bootstrap-ui.md: products use the App* wrappers, and ui-react brings the elements itself`],[`crypto-js`,`react.md: stored tokens are never encrypted client-side`],[`tailwindcss`,g],[`antd`,g],[`styled-components`,`react.md: no CSS-in-JS`],[`bootstrap-icons`,`bootstrap-ui.md: icons are AppIcon from @webority/ui-react`],[`iconsax-react`,`ui-components.md: icons are AppIcon from @webority/ui-react`],[`moment`,_],[`moment-timezone`,_],[`dayjs`,_],[`date-fns`,_]],mobile:[...p,[`typescript`,`react-native.md: app code is JavaScript`],[`@types/react`,`react-native.md: app code is JavaScript`],[`@react-native/typescript-config`,`react-native.md: app code is JavaScript`],[`react-native-permissions`,`react-native.md: retired; each Expo module requests its own permission`],[`moment`,v],[`moment-timezone`,v],[`dayjs`,v],[`react-native-fast-image`,`react-native.md: retired; expo-image`],[`@d11/react-native-fast-image`,`react-native.md: retired; expo-image`],[`formik`,`react-native.md: retired; react-hook-form`],[`yup`,`react-native.md: retired; zod with zodResolver`],[`react-native-status-bar-height`,`react-native.md: retired; react-native-safe-area-context insets`],[`react-native-flash-message`,`react-native.md: retired; the mobile-ui toast`],[`react-native-toast-message`,`react-native.md: retired; the mobile-ui toast`],[`prop-types`,`react-native.md: retired; defaults in the parameter destructure`],[`@react-navigation/stack`,`react-native.md: retired; @react-navigation/native-stack`],[`react-native-version-check`,`react-native.md: retired; the server-driven minimum version`],[`@react-native-firebase/messaging`,`react-native.md: push is expo-notifications through mobile-core`],[`@notifee/react-native`,`react-native.md: push is expo-notifications through mobile-core`],[`patch-package`,`react-native-toolchain.md: patch with pnpm patch, never patch-package`]],demo:[]},b={web:[...m,[`@mui/`,g],[`@chakra-ui/`,g],[`@emotion/`,`react.md: no CSS-in-JS`],[`@radix-ui/`,h],[`@react-aria/`,h]],mobile:[...m],demo:[]},te={web:[[`axios-retry`,`@webority/web-core`,`react.md: retry lives only in @webority/web-core`]],mobile:[[`axios-retry`,`@webority/mobile-core`,`react.md: retry lives only in @webority/mobile-core`]],demo:[]},ne={web:[[`@biomejs/biome`,`react.md: Biome is mandatory`],[`@webority/biome-config`,`react.md: every repo extends the shared Biome preset`],[`@webority/web-core`,`react.md: clients, auth, errors and logging come from web-core`],[`@webority/ui-react`,`react.md: App* components come from the catalogue`],[`@webority/theme`,`bootstrap-ui.md: look and tokens come from the shared theme`],[`bootstrap`,`bootstrap-ui.md: Bootstrap 5.3.3 on every surface`],[`react-bootstrap`,`bootstrap-ui.md: react-bootstrap is a dependency from project init`],[`sass-embedded`,`react.md: the SCSS compiler is sass-embedded`]],mobile:[[`@biomejs/biome`,`react-native.md: Biome is mandatory`],[`@webority/biome-config`,`react.md: every repo extends the shared Biome preset`],[`@webority/mobile-core`,`react-native.md: apps consume the shared mobile library`],[`@webority/mobile-ui`,`react-native.md: apps consume the shared mobile library`],[`@react-native-firebase/app`,`react-native.md: Crashlytics in every app`],[`@react-native-firebase/crashlytics`,`react-native.md: Crashlytics in every app`]],demo:[[`@biomejs/biome`,`react.md: Biome is mandatory`],[`@webority/biome-config`,`react.md: every repo extends the shared Biome preset`]]},re=`5.3.3`,ie=[[`@webority/theme`,`@webority/ui-elements`,`@webority/ui-react`,`@webority/support-react`,`@webority/chat-react`,`@webority/web-core`,`@webority/biome-config`,`@webority/repo-checks`],[`@webority/mobile-core`,`@webority/mobile-ui`,`@webority/mobile-support`,`@webority/mobile-tools`]],ae=`@webority/biome-config/biome.jsonc`,x=`AppAccordion.AppActionIcon.AppAlert.AppAsyncBlock.AppAutocomplete.AppAvatar.AppAvatarGroup.AppBadge.AppBanner.AppBreadcrumbs.AppButton.AppCard.AppCheckbox.AppChip.AppChoiceCard.AppChoiceChip.AppColorField.AppColumnMenu.AppCommandPalette.AppCompareTable.AppConfirmDialog.AppConsentBanner.AppCopyButton.AppCountrySelect.AppDataTable.AppDatePicker.AppDateRangePicker.AppDescriptions.AppDropdown.AppEmptyState.AppErrorSummary.AppFileAttachment.AppFilePicker.AppFileViewer.AppFloatingDock.AppFormActions.AppFormRow.AppFormSection.AppIcon.AppInfoList.AppInfoTip.AppInput.AppKpiTile.AppLink.AppLogoPicker.AppMeter.AppModal.AppMultiSelect.AppNumberInput.AppOtpInput.AppPageHeader.AppPagination.AppPhoneNumberInput.AppPopover.AppPricingCard.AppProgress.AppQrCode.AppRadio.AppRadioGroup.AppRepeater.AppRequiredMark.AppResourceCard.AppResult.AppScrollbar.AppSearch.AppSegmented.AppSelect.AppSelectMini.AppShell.AppSidebarMenu.AppSkeleton.AppSkeletonRows.AppSlider.AppSpinner.AppSplash.AppStatusBadge.AppStepper.AppSwitch.AppTableEmptyState.AppTablePagination.AppTableToolbar.AppTabs.AppTagInput.AppTextarea.AppThemeToggle.AppTimeline.AppTimePicker.AppToast.AppToolbar.AppTooltip.AppWizard.AppWorkspaceSwitcher`.split(`.`),S=e=>e.split(`\\`).join(`/`),C=(e,t)=>S(l(e,t))||`.`;function w(e){try{return JSON.parse(r(e,`utf8`))}catch{return null}}function T(e,t){let n=ee(`git`,t,{cwd:e,encoding:`utf8`,windowsHide:!0,maxBuffer:64*1024*1024});return n.status===0?n.stdout:null}function oe(e){let t=T(e,[`rev-parse`,`--show-toplevel`]);return t?t.trim():null}function E(e,t){let n=T(e,[`ls-files`,`-z`,`--`,...t.flatMap(e=>[e,`:(glob)**/${e}`])]);return n?[...new Set(n.split(`\0`).filter(Boolean))]:[]}function se(e){let t=e?.dependencies??{};return t[`react-native`]?`mobile`:t[`react-dom`]?e?.devDependencies?.[`react-native-web`]||t[`react-native-web`]?`demo`:`web`:null}var D=e=>/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(e)||/^(?:workspace:\*|file:|link:)/.test(e),O=new Set([`@biomejs/biome`,`@webority/biome-config`]),ce=e=>/^(?:workspace:|file:|link:)/.test(e);function le(e,t,n){let r=[],i={...e.dependencies,...e.devDependencies},a=Object.keys(i);for(let[e,n]of y[t])i[e]&&r.push([`banned:${e}`,`"${e}" is a dependency. ${n}.`]);for(let[e,n]of b[t])for(let t of a.filter(t=>t.startsWith(e)))r.push([`banned:${t}`,`"${t}" is a dependency. ${n}.`]);for(let[e,n,a]of te[t])i[e]&&i[n]&&r.push([`banned:${e}`,`"${e}" is a dependency beside ${n}. ${a}.`]);for(let[e,a]of ne[t])(O.has(e)?n:i)[e]||r.push([`required:${e}`,`"${e}" is not a dependency. ${a}.`]);let o=t===`mobile`?a:a.filter(e=>e.startsWith(`@webority/`)||e===`bootstrap`);for(let e of o){let t=String(i[e]);D(t)||r.push([`not-exact:${e}`,`"${e}": "${t}" is a range. Pin the version the lockfile holds, without ^ or ~.`])}i.bootstrap&&String(i.bootstrap).replace(/^[\^~]/,``)!==`5.3.3`&&r.push([`bootstrap-version`,`bootstrap is "${i.bootstrap}". bootstrap-ui.md: every surface pins ${re}.`]);for(let e of ie){let t=e.filter(e=>i[e]&&!ce(String(i[e])));if(new Set(t.map(e=>String(i[e]).replace(/^[\^~]/,``))).size>1){let n=t.map(e=>`${e}@${i[e]}`).join(`, `);r.push([`lockstep:${e[0]}`,`${n} differ. These publish together at one version; move every pin in the set together.`])}}return r}function k(e,n,r){let i=e;for(;;){for(let e of r)if(t(c(i,e)))return c(i,e);if(i===n)return null;let e=s(i);if(e===i)return null;i=e}}function A(e,t){let n=e,i=null;for(;;){let e=k(n,t,[`biome.json`,`biome.jsonc`]);if(!e)return i;i=s(e);let a;try{a=f(r(e,`utf8`))}catch{return i}let o=s(i);if(a.root!==!1||i===t||o===i)return i;n=o}}function ue(e,t,n){let r=A(e,t),i=r&&r!==e?w(c(r,`package.json`)):null;return i?{...i.dependencies,...i.devDependencies}:n}function de(e,n){let i=[],a=k(e,n,[`pnpm-lock.yaml`]);a||i.push([`pnpm:no-lockfile`,`No pnpm-lock.yaml in this folder or above it. react.md: every JS repo installs with pnpm.`]);let o=a?s(a):e,l=w(c(o,`package.json`))??w(c(e,`package.json`));/^pnpm@/.test(l?.packageManager??``)||i.push([`pnpm:package-manager`,`package.json has no "packageManager": "pnpm@<version>". react.md: Package manager.`]);let u=c(o,`pnpm-workspace.yaml`);if(!t(u))i.push([`pnpm:no-workspace-file`,`No pnpm-workspace.yaml beside the lockfile. react.md: it holds engineStrict, strictPeerDependencies and allowBuilds.`]);else{let e=r(u,`utf8`);/^engineStrict:\s*true\s*$/m.test(e)||i.push([`pnpm:engine-strict`,`pnpm-workspace.yaml lacks engineStrict: true. react.md: a Node mismatch must fail the install.`]),/^strictPeerDependencies:\s*true\s*$/m.test(e)||i.push([`pnpm:strict-peers`,`pnpm-workspace.yaml lacks strictPeerDependencies: true. react.md: a peer conflict must fail the install.`])}for(let n of[`package-lock.json`,`yarn.lock`])t(c(e,n))&&i.push([`pnpm:${n}`,`${n} sits in this folder. react.md: pnpm only; seed pnpm-lock.yaml with pnpm import, then delete it.`]);return i}function fe(e,t,n){let i=k(e,t,[`biome.json`,`biome.jsonc`]);if(!i)return[[`biome:missing`,`No biome.json in this folder or above it. react.md: one committed root biome.json.`]];let a;try{a=f(r(i,`utf8`))}catch{return[[`biome:unreadable`,`${o(i)} does not parse.`]]}let s=[];return[].concat(a.extends??[]).some(e=>e===`@webority/biome-config/biome.jsonc`||String(e).endsWith(`biome-config/biome.jsonc`))||s.push([`biome:extends`,`${o(i)} does not extend "${ae}". react.md: every repo extends the shared preset.`]),a.formatter?.lineEnding&&s.push([`biome:line-ending`,`${o(i)} sets formatter.lineEnding. react.md: inherit it from the shared preset, never set it.`]),a.linter?.rules&&Object.keys(a.linter.rules).length>0&&s.push([`biome:root-rules`,`${o(i)} sets linter.rules at the root. react.md: never a rule's severity at the root; a per-path exception goes in overrides.`]),n===`mobile`&&!(a.javascript?.globals??[]).includes(`__DEV__`)&&s.push([`biome:dev-global`,`${o(i)} lacks "__DEV__" in javascript.globals. react-native.md: Debugging and agent access.`]),s}function pe(e){let t=c(e,`node_modules`,`@webority`,`ui-react`);try{let e=JSON.parse(r(c(t,`package.json`),`utf8`)),n=r(c(t,typeof e.module==`string`?e.module:typeof e.main==`string`?e.main:`dist/index.js`),`utf8`),i=new Set([...n.matchAll(/App[A-Z][A-Za-z0-9]*/g)].map(e=>e[0]));if(i.size>0)return i}catch{}return new Set(x)}function j(e){return i(e,{withFileTypes:!0}).flatMap(t=>t.isDirectory()?j(c(e,t.name)):[c(e,t.name)])}function me(e){let n=c(e,`src`,`components`);if(!t(n))return[];let r=pe(e),i=[];for(let t of j(n)){let n=o(t).split(`.`)[0];if(r.has(n)){let r=S(l(e,t));i.push([`catalogue-copy:${r}`,`${r} is named like the @webority/ui-react export ${n}. bootstrap-ui.md: use the catalogue component, never a local copy.`])}}return i}var M=`.husky/pre-commit`;function N(e){if(t(c(e,`.github/workflows/production.yml`)))return[];let n=c(e,M),i=t(n)?r(n,`utf8`).replace(/\r\n/g,`
59
59
  `):null;return i===d?[]:[[`hook:drift`,`${M} ${i===null?`is missing`:`differs from the shared hook`}. Run pnpm exec webority-repo-checks --sync-hook from the git root; repo-only steps go in .husky/pre-commit.local.`]]}function P(e,n=null){let r=n?new Set(n.map(t=>C(e,t))):null,i=[],a=(e,t)=>{for(let[n,r]of t)i.push({pkg:e,id:n,message:r})};(!r||r.has(`.`))&&a(`.`,N(e));for(let n of E(e,[`package-lock.json`,`yarn.lock`])){if(!t(c(e,n)))continue;let i=S(s(n))||`.`;r&&!r.has(i)||a(i,[[`pnpm:${o(n)}`,`${o(n)} is committed. react.md: pnpm only; seed pnpm-lock.yaml with pnpm import, then delete it.`]])}let l=new Set(E(e,[`package.json`]).map(t=>C(e,c(e,s(t)))));for(let n of r??[])t(c(e,n,`package.json`))&&l.add(n);for(let t of l){if(r&&!r.has(t))continue;let n=c(e,t),o=w(c(n,`package.json`)),s=se(o);if(!s)continue;let l=de(n,e).filter(([e])=>!i.some(n=>n.pkg===t&&n.id===e));a(t,[...le(o,s,ue(n,e,{...o.dependencies,...o.devDependencies})),...l,...fe(n,e,s),...s===`web`?me(n):[]])}return i}var F=`.webority/repo-checks-baseline.json`,I=process.argv.slice(2),L=new Set(I.filter(e=>e.startsWith(`--`))),R=I.filter(e=>!e.startsWith(`--`)),z=L.has(`--sync-hook`),B=e=>process.stdout.write(`${e}\n`),V=e=>process.stderr.write(`${e}\n`),he=new Set([`--json`,`--baseline-init`,`--baseline-shrink`,`--sync-hook`]),H=[...L].filter(e=>!he.has(e));(H.length||L.has(`--baseline-init`)&&L.has(`--baseline-shrink`)||z&&(L.size>1||R.length))&&(V(`repo-checks: unknown or conflicting options ${H.join(` `)}`.trim()),V(`usage: webority-repo-checks [--json] [--baseline-init | --baseline-shrink] [app folder ...]`),V(` webority-repo-checks --sync-hook`),process.exit(2));var U=oe(process.cwd());if(U||(V(`repo-checks: not inside a git repository.`),process.exit(2)),z){let t=c(U,M);n(s(t),{recursive:!0}),a(t,d),e(t,493),B(`repo-checks: wrote ${t}`),process.exit(0)}var W=P(U,R.length?R.map(e=>u(process.cwd(),e)):null).sort((e,t)=>e.pkg===t.pkg?0:e.pkg<t.pkg?-1:1),G=e=>`${e.pkg}\t${e.id}`,K=c(U,F);function ge(){if(!t(K))return null;try{return JSON.parse(r(K,`utf8`))}catch{V(`repo-checks: ${F} does not parse.`),process.exit(2)}}function q(e){let t={};for(let[n,r]of e.map(e=>e.split(` `)).sort())(t[n]??=[]).push(r);n(s(K),{recursive:!0}),a(K,`${JSON.stringify(t,null,4)}\n`)}var J=ge(),Y=new Set(Object.entries(J??{}).flatMap(([e,t])=>t.map(t=>`${e}\t${t}`)));L.has(`--baseline-init`)&&(J&&(V(`repo-checks: ${F} already exists. It only shrinks: fix a new finding, or run --baseline-shrink after fixing old ones.`),process.exit(2)),q(W.map(G)),B(`repo-checks: recorded ${W.length} existing finding(s) in ${F}.`),process.exit(0));var X=new Set(W.map(G)),_e=new Set(R.map(e=>l(U,u(process.cwd(),e)).split(`\\`).join(`/`)||`.`)),ve=e=>!R.length||_e.has(e.split(` `)[0]),Z=[...Y].filter(e=>!X.has(e)&&ve(e));L.has(`--baseline-shrink`)&&(J||(V(`repo-checks: no ${F} to shrink.`),process.exit(2)),R.length&&(V(`repo-checks: --baseline-shrink reads the whole repository; run it without folder arguments.`),process.exit(2)),q([...Y].filter(e=>X.has(e))),B(`repo-checks: dropped ${Z.length} fixed entr${Z.length===1?`y`:`ies`} from ${F}.`),process.exit(0));var Q=W.filter(e=>!Y.has(G(e))),$=W.filter(e=>Y.has(G(e)));if(L.has(`--json`)&&(B(JSON.stringify({root:U,new:Q,known:$,stale:Z.map(e=>e.split(` `))},null,2)),process.exit(+!!Q.length)),Q.length){V(`repo-checks: ${Q.length} new finding(s)`);let e=null;for(let t of Q)t.pkg!==e&&(e=t.pkg,V(` ${e}`)),V(` - ${t.id}: ${t.message}`)}else B(`repo-checks: no new findings.`);if($.length&&B(`repo-checks: ${$.length} known finding(s) recorded in ${F}.`),Z.length){B(`repo-checks: ${Z.length} baseline entr${Z.length===1?`y is`:`ies are`} fixed; drop them with webority-repo-checks --baseline-shrink:`);for(let e of Z)B(` ${e.replace(` `,` `)}`)}process.exit(+!!Q.length);export{};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/repo-checks",
3
- "version": "0.78.0",
3
+ "version": "0.79.0",
4
4
  "description": "Repository checks for Webority React web portals and React Native apps: banned and required dependencies, pnpm only, exact pins, and the shared Biome preset. Run before a push; existing findings sit in a baseline that only shrinks.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",