{"id":50570745,"url":"https://github.com/almeidx/version-check","last_synced_at":"2026-06-07T21:00:55.450Z","repository":{"id":361874670,"uuid":"1256194635","full_name":"almeidx/version-check","owner":"almeidx","description":"Headless web app version/update detector for refresh-to-update prompts.","archived":false,"fork":false,"pushed_at":"2026-06-03T16:37:18.000Z","size":165,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-04T18:31:34.472Z","etag":null,"topics":["build-id","deployment","update-detector","version-check"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/almeidx.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-01T14:49:11.000Z","updated_at":"2026-06-03T16:36:03.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/almeidx/version-check","commit_stats":null,"previous_names":["almeidx/version-check"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/almeidx/version-check","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almeidx%2Fversion-check","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almeidx%2Fversion-check/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almeidx%2Fversion-check/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almeidx%2Fversion-check/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/almeidx","download_url":"https://codeload.github.com/almeidx/version-check/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almeidx%2Fversion-check/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33955543,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-05T02:00:06.157Z","response_time":120,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["build-id","deployment","update-detector","version-check"],"created_at":"2026-06-04T18:30:22.096Z","updated_at":"2026-06-05T19:00:57.821Z","avatar_url":"https://github.com/almeidx.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# version-check\n\nHeadless version/update detection for web apps.\n\n`@almeidx/version-check` polls a deployment version endpoint and reports when the deployed\nidentity differs from the version that rendered the current page. Framework packages keep the same\nrenderless model so applications can show their own “New version available, refresh to update.”\nmessage.\n\n## Packages\n\n| Package                        | Purpose                                             |\n| ------------------------------ | --------------------------------------------------- |\n| `@almeidx/version-check`       | Core checker, comparison helpers, polling lifecycle |\n| `@almeidx/version-check-react` | React hook                                          |\n| `@almeidx/version-check-next`  | Next.js build id route helper and client hook       |\n| `@almeidx/version-check-vue`   | Vue composable                                      |\n| `@almeidx/version-check-vite`  | Vite build id plugin and virtual module             |\n\nFramework packages re-export the core API and expose the `version-check` CLI, so apps only need to\ninstall the package they use.\n\n## Version endpoint\n\nThe default endpoint is `/version.json`. The payload can be a string or an object with one of\n`version`, `buildId`, `id`, `hash`, or `sha`.\n\n```json\n{\n\t\"buildId\": \"2026-06-01T12-00-00Z\"\n}\n```\n\nGenerate a simple `public/version.json` during builds with the package CLI.\n\n```sh\nversion-check generate public\n```\n\nFor package scripts:\n\n```json\n{\n\t\"scripts\": {\n\t\t\"prebuild\": \"version-check generate public\"\n\t}\n}\n```\n\nThe generated `buildId` uses `VERSION_CHECK_BUILD_ID`, `SOURCE_COMMIT`,\n`VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`, or `local-dev`.\n\n## Vanilla JS\n\n```ts\nimport { createVersionChecker } from \"@almeidx/version-check\";\n\nconst checker = createVersionChecker({\n\tcurrentVersion: window.__BUILD_ID__,\n\tintervalMs: 30 * 60 * 1000,\n});\n\nchecker.subscribe((state) =\u003e {\n\tif (state.updateAvailable) {\n\t\tdocument.querySelector(\"#update-banner\")?.removeAttribute(\"hidden\");\n\t}\n});\n\nchecker.start();\n```\n\n## Vite\n\nUse the Vite plugin as the single source of truth for the client build id and `/version.json`.\n\n```ts\n// vite.config.ts\nimport { versionCheck } from \"@almeidx/version-check-vite\";\nimport { defineConfig } from \"vite\";\n\nexport default defineConfig({\n\tplugins: [versionCheck()],\n});\n```\n\nRead the same id from the virtual module in client code:\n\n```ts\nimport buildId from \"virtual:version-check/build-id\";\nimport { createVersionChecker } from \"@almeidx/version-check\";\n\nconst checker = createVersionChecker({\n\tcurrentVersion: buildId,\n});\n```\n\nFor TypeScript, include the virtual module declarations in your app env file:\n\n```ts\nimport \"@almeidx/version-check-vite/virtual\";\n```\n\nBy default, the Vite plugin resolves the build id from deployment environment variables only. Use\n`buildId` or `resolveBuildId` when you want an explicit value from another source. The virtual module\nis the default client API; set `define: true` only if you explicitly want a global Vite constant.\n\n## React\n\n```tsx\nimport { useVersionCheck } from \"@almeidx/version-check-react\";\n\nfunction refreshPage() {\n\twindow.location.reload();\n}\n\nexport function VersionBanner({ currentVersion }: { currentVersion: string }) {\n\tconst versionCheck = useVersionCheck({\n\t\tcurrentVersion,\n\t\tintervalMs: 30 * 60 * 1000,\n\t});\n\n\tif (!versionCheck.updateAvailable) return null;\n\n\treturn (\n\t\t\u003cdiv role=\"status\"\u003e\n\t\t\tNew version available.\n\t\t\t\u003cbutton type=\"button\" onClick={refreshPage}\u003e\n\t\t\t\tRefresh\n\t\t\t\u003c/button\u003e\n\t\t\u003c/div\u003e\n\t);\n}\n```\n\n## Next.js\n\nCreate an App Router route:\n\n```ts\n// app/api/version/route.ts\nimport { createNextVersionHandler } from \"@almeidx/version-check-next\";\n\nexport const GET = createNextVersionHandler();\n```\n\nPass the initial build id from the root layout:\n\n```tsx\n// app/layout.tsx\nimport { getNextBuildId } from \"@almeidx/version-check-next\";\nimport type { PropsWithChildren } from \"react\";\nimport { VersionBanner } from \"./version-banner\";\n\nexport default async function RootLayout({ children }: PropsWithChildren) {\n\tconst buildId = await getNextBuildId();\n\n\treturn (\n\t\t\u003chtml lang=\"en\"\u003e\n\t\t\t\u003cbody\u003e\n\t\t\t\t\u003cVersionBanner initialVersion={buildId} /\u003e\n\t\t\t\t{children}\n\t\t\t\u003c/body\u003e\n\t\t\u003c/html\u003e\n\t);\n}\n```\n\nUse the client hook:\n\n```tsx\n\"use client\";\n\nimport { useNextVersionCheck } from \"@almeidx/version-check-next/client\";\n\nfunction refreshPage() {\n\twindow.location.reload();\n}\n\nexport function VersionBanner({ initialVersion }: { initialVersion: string }) {\n\tconst versionCheck = useNextVersionCheck({\n\t\tcurrentVersion: initialVersion,\n\t});\n\n\tif (!versionCheck.updateAvailable) return null;\n\n\treturn (\n\t\t\u003cdiv role=\"status\"\u003e\n\t\t\tNew version available.\n\t\t\t\u003cbutton type=\"button\" onClick={refreshPage}\u003e\n\t\t\t\tRefresh\n\t\t\t\u003c/button\u003e\n\t\t\u003c/div\u003e\n\t);\n}\n```\n\n## Vue\n\nThe composable returns the reactive `state` plus `check` (force a check now) and `stop` (tear down\nearly). It only polls in the browser, so it is SSR-safe, and it disposes automatically with the\nsurrounding effect scope.\n\n```ts\nimport { useVersionCheck } from \"@almeidx/version-check-vue\";\n\nconst { state } = useVersionCheck({\n\tcurrentVersion: import.meta.env.VITE_BUILD_ID,\n});\n\nfunction refreshPage() {\n\twindow.location.reload();\n}\n```\n\n```html\n\u003cbutton v-if=\"state.updateAvailable\" type=\"button\" @click=\"refreshPage\"\u003eRefresh to update\u003c/button\u003e\n```\n\n## Custom comparison\n\nBy default, any deployment identity change is considered an update. Use `compare` when your app\nneeds a stricter policy, such as semver-only upgrades or channel-aware deploys.\n\n```ts\ncreateVersionChecker({\n\tcurrentVersion: \"2.0.0\",\n\tcompare: ({ currentVersion, latestVersion }) =\u003e String(latestVersion) \u003e String(currentVersion),\n});\n```\n\n## Polling behavior\n\nThe checker polls every `intervalMs` (default 30 minutes) and also re-checks on window focus, network\nreconnect, and the tab becoming visible. A few options tune this:\n\n- `pauseWhenHidden` (default `true`) pauses the interval while the tab is hidden and checks when it\n  becomes visible again if the lifecycle cooldown has elapsed, instead of polling a backgrounded tab.\n- `minIntervalMs` (default 1 minute) sets a minimum gap between lifecycle-triggered checks. Set it to\n  `0` to disable the cooldown. Bursts of focus/online/visibility events are always collapsed to a\n  single in-flight check regardless.\n- `refetchOnWindowFocus`, `refetchOnReconnect`, and `refetchOnVisibilityChange` (each default `true`)\n  turn off individual triggers.\n\nThe React and Vue hooks also expose `check()` to force an immediate check (e.g. from a \"check for\nupdates\" button); the Vue composable additionally returns `stop()`.\n\n## Development\n\n```sh\npnpm install\npnpm check\n```\n\nUseful scripts:\n\n- `pnpm build` builds packages and examples.\n- `pnpm build:typecheck` runs TypeScript strict type checks.\n- `pnpm lint` runs oxlint.\n- `pnpm fmt` runs oxfmt.\n- `pnpm test` runs vitest.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falmeidx%2Fversion-check","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falmeidx%2Fversion-check","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falmeidx%2Fversion-check/lists"}