{"_id":"@contensis/contensis-for-councils","_rev":"3-2ab2a1dc37a315c3d88dca3b24e27b6c","name":"@contensis/contensis-for-councils","dist-tags":{"latest":"2.4.1-beta.2","prerelease":"2.4.1-beta.1"},"versions":{"2.4.1-beta.0":{"name":"@contensis/contensis-for-councils","version":"2.4.1-beta.0","keywords":[],"author":{"name":"http://zengenti.com"},"license":"None","_id":"@contensis/contensis-for-councils@2.4.1-beta.0","maintainers":[{"name":"n.flatley","email":"n.flatley@zengenti.com"}],"dist":{"shasum":"653d7f88cdd73fd6021e1269185c5b011632adc5","tarball":"https://registry.npmjs.org/@contensis/contensis-for-councils/-/contensis-for-councils-2.4.1-beta.0.tgz","fileCount":3772,"integrity":"sha512-11GgoAl5V6MI/ZC4YrLPi6QUpgiZi+EHiH6Q6e5/J4Uu4FexlHawyHiQZlo6G66UQud8Twp0hKMyYrSAax/XmA==","signatures":[{"sig":"MEQCIBf+FBf8QcZTRhbIGbEC3zXh8aDDZETfvBuu6vpln1cdAiBLounedEIjxzOHXsX5y/uZwMI3JOFV6KwIhCxd9ewDPA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":5138994},"main":"lib/cjs/lib/components.js","types":"lib/esm/lib/components.d.ts","config":{"startup":""},"module":"lib/esm/lib/components.js","exports":{".":{"types":"./lib/esm/lib/components.d.ts","import":"./lib/esm/lib/components.js","require":"./lib/cjs/lib/components.js"},"./client":{"types":"./lib/esm/lib/client.d.ts","import":"./lib/esm/lib/client.js","require":"./lib/cjs/lib/client.js"},"./server":{"types":"./lib/esm/lib/server.d.ts","import":"./lib/esm/lib/server.js","require":"./lib/cjs/lib/server.js"},"./webpack":{"require":"./webpack/create-config.js"},"./webpack/define-config":{"require":"./webpack/define-config.js"}},"gitHead":"adf83ac7d25105173dd60ed4393af383cc6ba045","scripts":{"dev":"rimraf ./dist && cross-env NODE_ENV=development crb dev --config webpack/webpack.config.js","lint":"eslint .","test":"jest  -c ./config/jest.config.js --detectOpenHandles --forceExit","build":"rimraf ./dist && cross-env NODE_ENV=production webpack --config webpack/webpack.config.js && npm run build:startup","server":"node --max-http-header-size=800000 dist/start.js","analyze":"rimraf ./dist && cross-env NODE_ENV=production ANALYZE=TRUE webpack --config webpack/webpack.config.js","build:ci":"npm run build","lint:fix":"eslint --fix .","build:lib":"rimraf ./lib && rollup -c rollup.config.mjs && npm run build:types","test:watch":"jest --config=./config/jest.config.js --watch","build:types":"tsc -p tsconfig.lib.json","build:assets":"cross-env NODE_ENV=production webpack --config webpack/webpack.config.assets.js","build:server":"npm run build && npm run server","package-lock":"npm i --package-lock-only --workspaces false","build:startup":"buildstartup","test:coverage":"jest --config=./config/jest.config.js --coverage","build:lib:watch":"rollup -c rollup.config.mjs --watch"},"_npmUser":{"name":"n.flatley","email":"n.flatley@zengenti.com"},"overrides":{"sockjs":{"uuid":"^11.1.1"},"express":">=5.0.0","js-yaml":"^4.2.0","@types/react":"^18","react-router":"^6.30.4","@types/react-dom":"^18","react-router-dom":"^6.30.4","serialize-javascript":"^7.0.5","simple-update-notifier":{"semver":"^7.5.2"}},"repository":{"url":"https://gitlab.zengenti.com/ps-projects/contensis-for-councils/contensis-for-councils","type":"git"},"_npmVersion":"11.8.0","description":"A single React codebase that powers multiple UK council websites from the\r [Contensis](https://www.contensis.com/) CMS. One application serves many tenant\r sites — each driven by its own Contensis project and per-site configuration, and\r each deployed as ","directories":{},"_nodeVersion":"24.13.1","browserslist":["last 2 Chrome versions, last 2 Edge versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions"],"dependencies":{"immer":"^11.1.8","sitemap":"^8.0.0","polished":"^4.3.1","classnames":"^2.5.1","app-root-path":"^3.0.0","react-markdown":"^8.0.7","@contensis/forms":"^1.0.5","styled-components":"^6.4.2","@radix-ui/react-id":"^1.1.2","react-router-hash-link":"^2.4.3","@contensis/canvas-react":"^1.3.2","@sooro-io/react-gtm-module":"^3.0.0","@formatjs/intl-datetimeformat":"^6.16.5","insytful-ai-search-components":"^1.0.5","@zengenti/contensis-react-base":"^4.0.1-beta.12"},"_hasShrinkwrap":false,"devDependencies":{"cac":"^7.0.0","chai":"^4.3.6","jest":"^29.7.0","mocha":"^10.4.0","eslint":"^8.57.0","rimraf":"^6.0.1","rollup":"^4.62.0","nodemon":"^2.0.15","webpack":"^5.92.1","prettier":"^3.3.2","chai-http":"^4.3.0","cross-env":"^7.0.3","storybook":"^8.2.9","babel-jest":"^29.7.0","css-loader":"^7.1.2","typescript":"^5.9.3","@babel/core":"^7.25.2","@types/chai":"^4.3.16","@types/svgo":"^2.6.4","webpack-cli":"^5.1.4","@types/mocha":"^10.0.7","@types/react":"^18","babel-loader":"^9.1.3","style-loader":"^4.0.0","react-refresh":"^0.18.0","webpack-merge":"^5.9.0","@babel/runtime":"^7.25.0","@types/express":"^5.0.6","esbuild-loader":"^4.2.2","webpack-modules":"^1.0.0","@types/react-dom":"^18","@babel/preset-env":"^7.25.3","webpack-dev-server":"^5.0.4","@babel/preset-react":"^7.24.7","@rollup/plugin-json":"^6.1.0","copy-webpack-plugin":"^12.0.2","eslint-plugin-react":"^7.34.3","html-webpack-plugin":"^5.6.0","@babel/eslint-parser":"^7.25.1","@rollup/plugin-alias":"^6.0.0","@rollup/plugin-babel":"^7.1.0","@types/webfontloader":"^1.6.38","eslint-plugin-import":"^2.29.1","image-webpack-loader":"^8.1.0","eslint-webpack-plugin":"^4.2.0","@loadable/babel-plugin":"^5.16.1","eslint-config-prettier":"^9.1.0","eslint-plugin-jsx-a11y":"^6.9.0","eslint-plugin-prettier":"^5.1.3","webpack-node-externals":"^3.0.0","@rollup/plugin-commonjs":"^29.0.3","mini-css-extract-plugin":"^2.9.1","webpack-bundle-analyzer":"^4.10.2","@babel/preset-typescript":"^7.24.7","@loadable/webpack-plugin":"^5.15.2","@rollup/plugin-typescript":"^12.3.0","@typescript-eslint/parser":"^5.20.0","eslint-plugin-react-hooks":"^4.6.2","@types/loadable__component":"^5.13.9","@rollup/plugin-node-resolve":"^16.0.3","babel-plugin-module-resolver":"^4.1.0","css-minimizer-webpack-plugin":"^8.0.0","@types/react-router-hash-link":"^2.4.5","babel-plugin-transform-define":"^1.0.1","typescript-plugin-css-modules":"^5.2.0","zengenti-buildstartup-package":"^0.0.19","babel-plugin-styled-components":"^2.1.4","eslint-import-resolver-webpack":"^0.13.4","@typescript-eslint/eslint-plugin":"^5.20.0","babel-plugin-dynamic-import-node":"^2.3.3","eslint-import-resolver-typescript":"^3.6.1","@pmmmwh/react-refresh-webpack-plugin":"^0.6.2"},"_npmOperationalInternal":{"tmp":"tmp/contensis-for-councils_2.4.1-beta.0_1782833174185_0.7137955350459402","host":"s3://npm-registry-packages-npm-production"}},"2.4.1-beta.1":{"name":"@contensis/contensis-for-councils","version":"2.4.1-beta.1","keywords":[],"author":{"name":"http://zengenti.com"},"license":"None","_id":"@contensis/contensis-for-councils@2.4.1-beta.1","maintainers":[{"name":"n.flatley","email":"n.flatley@zengenti.com"}],"dist":{"shasum":"be027ba2f73c3c0249f920f0afbd3ac7cfba472f","tarball":"https://registry.npmjs.org/@contensis/contensis-for-councils/-/contensis-for-councils-2.4.1-beta.1.tgz","fileCount":3777,"integrity":"sha512-JriFn68NGDJ4doZ4xxNybcnkQkJlVxGl55Q8sqnxr3sfe7DoNwDYGRjKMjuUdgzv4c1eKfJKMY4cHturj4bbuw==","signatures":[{"sig":"MEYCIQDWYbplUjC2JzFpUUaTI1M1k0FWRVfSsYWdDevOEnhsGQIhAO1Ujc0IQkeeqksLxy/DIayJyiZcQHl6ne+GRH8ufjQJ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":5140803},"main":"lib/cjs/lib/components.js","types":"lib/esm/lib/components.d.ts","config":{"startup":""},"module":"lib/esm/lib/components.js","exports":{".":{"types":"./lib/esm/lib/components.d.ts","import":"./lib/esm/lib/components.js","require":"./lib/cjs/lib/components.js"},"./client":{"types":"./lib/esm/lib/client.d.ts","import":"./lib/esm/lib/client.js","require":"./lib/cjs/lib/client.js"},"./server":{"types":"./lib/esm/lib/server.d.ts","import":"./lib/esm/lib/server.js","require":"./lib/cjs/lib/server.js"},"./webpack":{"require":"./webpack/create-config.js"},"./webpack/define-config":{"require":"./webpack/define-config.js"}},"gitHead":"84ab955be11ceb3af5b400ce8b0b9c9394a39967","scripts":{"dev":"rimraf ./dist && cross-env NODE_ENV=development crb dev --config webpack/webpack.config.js","lint":"eslint .","test":"jest  -c ./config/jest.config.js --detectOpenHandles --forceExit","build":"rimraf ./dist && cross-env NODE_ENV=production webpack --config webpack/webpack.config.js && npm run build:startup","server":"node --max-http-header-size=800000 dist/start.js","analyze":"rimraf ./dist && cross-env NODE_ENV=production ANALYZE=TRUE webpack --config webpack/webpack.config.js","build:ci":"npm run build","lint:fix":"eslint --fix .","build:lib":"rimraf ./lib && rollup -c rollup.config.mjs && npm run build:types","test:watch":"jest --config=./config/jest.config.js --watch","build:types":"tsc -p tsconfig.lib.json","build:assets":"cross-env NODE_ENV=production webpack --config webpack/webpack.config.assets.js","build:server":"npm run build && npm run server","package-lock":"npm i --package-lock-only --workspaces false","build:startup":"buildstartup","test:coverage":"jest --config=./config/jest.config.js --coverage","build:lib:watch":"rollup -c rollup.config.mjs --watch"},"_npmUser":{"name":"n.flatley","email":"n.flatley@zengenti.com"},"overrides":{"sockjs":{"uuid":"^11.1.1"},"express":">=5.0.0","js-yaml":"^4.2.0","@types/react":"^18","react-router":"^6.30.4","@types/react-dom":"^18","react-router-dom":"^6.30.4","serialize-javascript":"^7.0.5","simple-update-notifier":{"semver":"^7.5.2"}},"repository":{"url":"https://gitlab.zengenti.com/ps-projects/contensis-for-councils/contensis-for-councils","type":"git"},"_npmVersion":"11.8.0","description":"A single React codebase that powers multiple UK council websites from the\r [Contensis](https://www.contensis.com/) CMS. One application serves many tenant\r sites — each driven by its own Contensis project and per-site configuration, and\r each deployed as ","directories":{},"_nodeVersion":"24.13.1","browserslist":["last 2 Chrome versions, last 2 Edge versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions"],"dependencies":{"immer":"^11.1.8","sitemap":"^8.0.0","polished":"^4.3.1","classnames":"^2.5.1","app-root-path":"^3.0.0","react-markdown":"^8.0.7","@contensis/forms":"^1.0.5","styled-components":"^6.4.2","@radix-ui/react-id":"^1.1.2","react-router-hash-link":"^2.4.3","@contensis/canvas-react":"^1.3.2","@sooro-io/react-gtm-module":"^3.0.0","@formatjs/intl-datetimeformat":"^6.16.5","insytful-ai-search-components":"^1.0.5","@zengenti/contensis-react-base":"^4.0.1-beta.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"cac":"^7.0.0","chai":"^4.3.6","jest":"^29.7.0","mocha":"^10.4.0","eslint":"^8.57.0","rimraf":"^6.0.1","rollup":"^4.62.0","nodemon":"^2.0.15","webpack":"^5.92.1","prettier":"^3.3.2","chai-http":"^4.3.0","cross-env":"^7.0.3","storybook":"^8.2.9","babel-jest":"^29.7.0","css-loader":"^7.1.2","typescript":"^5.9.3","@babel/core":"^7.25.2","@types/chai":"^4.3.16","@types/svgo":"^2.6.4","webpack-cli":"^5.1.4","@types/mocha":"^10.0.7","@types/react":"^18","babel-loader":"^9.1.3","style-loader":"^4.0.0","react-refresh":"^0.18.0","webpack-merge":"^5.9.0","@babel/runtime":"^7.25.0","@types/express":"^5.0.6","esbuild-loader":"^4.2.2","@types/react-dom":"^18","@babel/preset-env":"^7.25.3","webpack-dev-server":"^5.0.4","@babel/preset-react":"^7.24.7","@rollup/plugin-json":"^6.1.0","copy-webpack-plugin":"^12.0.2","eslint-plugin-react":"^7.34.3","html-webpack-plugin":"^5.6.0","@babel/eslint-parser":"^7.25.1","@rollup/plugin-alias":"^6.0.0","@rollup/plugin-babel":"^7.1.0","@types/webfontloader":"^1.6.38","eslint-plugin-import":"^2.29.1","image-webpack-loader":"^8.1.0","eslint-webpack-plugin":"^4.2.0","@loadable/babel-plugin":"^5.16.1","eslint-config-prettier":"^9.1.0","eslint-plugin-jsx-a11y":"^6.9.0","eslint-plugin-prettier":"^5.1.3","webpack-node-externals":"^3.0.0","@rollup/plugin-commonjs":"^29.0.3","mini-css-extract-plugin":"^2.9.1","webpack-bundle-analyzer":"^4.10.2","@babel/preset-typescript":"^7.24.7","@loadable/webpack-plugin":"^5.15.2","@rollup/plugin-typescript":"^12.3.0","@typescript-eslint/parser":"^5.20.0","eslint-plugin-react-hooks":"^4.6.2","@types/loadable__component":"^5.13.9","@rollup/plugin-node-resolve":"^16.0.3","babel-plugin-module-resolver":"^4.1.0","css-minimizer-webpack-plugin":"^8.0.0","@types/react-router-hash-link":"^2.4.5","babel-plugin-transform-define":"^1.0.1","typescript-plugin-css-modules":"^5.2.0","zengenti-buildstartup-package":"^0.0.19","babel-plugin-styled-components":"^2.1.4","eslint-import-resolver-webpack":"^0.13.4","@typescript-eslint/eslint-plugin":"^5.20.0","babel-plugin-dynamic-import-node":"^2.3.3","eslint-import-resolver-typescript":"^3.6.1","@pmmmwh/react-refresh-webpack-plugin":"^0.6.2"},"_npmOperationalInternal":{"tmp":"tmp/contensis-for-councils_2.4.1-beta.1_1782836687011_0.0713834526012811","host":"s3://npm-registry-packages-npm-production"}},"2.4.1-beta.2":{"name":"@contensis/contensis-for-councils","version":"2.4.1-beta.2","repository":{"type":"git","url":"https://gitlab.zengenti.com/ps-projects/contensis-for-councils/contensis-for-councils"},"author":{"name":"http://zengenti.com"},"license":"None","keywords":[],"description":"A single React codebase that powers multiple UK council websites from the\r [Contensis](https://www.contensis.com/) CMS. One application serves many tenant\r sites — each driven by its own Contensis project and per-site configuration, and\r each deployed as ","main":"lib/cjs/lib/components.js","module":"lib/esm/lib/components.js","types":"lib/esm/lib/components.d.ts","exports":{".":{"import":"./lib/esm/lib/components.js","require":"./lib/cjs/lib/components.js","types":"./lib/esm/lib/components.d.ts"},"./client":{"import":"./lib/esm/lib/client.js","require":"./lib/cjs/lib/client.js","types":"./lib/esm/lib/client.d.ts"},"./server":{"import":"./lib/esm/lib/server.js","require":"./lib/cjs/lib/server.js","types":"./lib/esm/lib/server.d.ts"},"./webpack":{"require":"./webpack/create-config.js"},"./webpack/define-config":{"require":"./webpack/define-config.js"}},"config":{"startup":""},"scripts":{"dev":"rimraf ./dist && cross-env NODE_ENV=development crb dev --config webpack/webpack.config.js","build":"rimraf ./dist && cross-env NODE_ENV=production webpack --config webpack/webpack.config.js && npm run build:startup","build:lib":"rimraf ./lib && rollup -c rollup.config.mjs && npm run build:types","build:lib:watch":"rollup -c rollup.config.mjs --watch","build:types":"tsc -p tsconfig.lib.json","package-lock":"npm i --package-lock-only --workspaces false","build:assets":"cross-env NODE_ENV=production webpack --config webpack/webpack.config.assets.js","build:ci":"npm run build","build:startup":"buildstartup","server":"node --max-http-header-size=800000 dist/start.js","build:server":"npm run build && npm run server","analyze":"rimraf ./dist && cross-env NODE_ENV=production ANALYZE=TRUE webpack --config webpack/webpack.config.js","lint":"eslint .","lint:fix":"eslint --fix .","test":"jest  -c ./config/jest.config.js --detectOpenHandles --forceExit","test:watch":"jest --config=./config/jest.config.js --watch","test:coverage":"jest --config=./config/jest.config.js --coverage"},"browserslist":["last 2 Chrome versions, last 2 Edge versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions"],"overrides":{"@types/react":"^18","@types/react-dom":"^18","express":">=5.0.0","js-yaml":"^4.2.0","react-router":"^6.30.4","react-router-dom":"^6.30.4","serialize-javascript":"^7.0.5","simple-update-notifier":{"semver":"^7.5.2"},"sockjs":{"uuid":"^11.1.1"}},"devDependencies":{"@babel/core":"^7.25.2","@babel/eslint-parser":"^7.25.1","@babel/preset-env":"^7.25.3","@babel/preset-react":"^7.24.7","@babel/preset-typescript":"^7.24.7","@babel/runtime":"^7.25.0","@loadable/babel-plugin":"^5.16.1","@loadable/webpack-plugin":"^5.15.2","@pmmmwh/react-refresh-webpack-plugin":"^0.6.2","@rollup/plugin-alias":"^6.0.0","@rollup/plugin-babel":"^7.1.0","@rollup/plugin-commonjs":"^29.0.3","@rollup/plugin-json":"^6.1.0","@rollup/plugin-node-resolve":"^16.0.3","@rollup/plugin-typescript":"^12.3.0","@types/chai":"^4.3.16","@types/express":"^5.0.6","@types/loadable__component":"^5.13.9","@types/mocha":"^10.0.7","@types/react":"^18","@types/react-dom":"^18","@types/react-router-hash-link":"^2.4.5","@types/svgo":"^2.6.4","@types/webfontloader":"^1.6.38","@typescript-eslint/eslint-plugin":"^5.20.0","@typescript-eslint/parser":"^5.20.0","babel-jest":"^29.7.0","babel-loader":"^9.1.3","babel-plugin-dynamic-import-node":"^2.3.3","babel-plugin-module-resolver":"^4.1.0","babel-plugin-styled-components":"^2.1.4","babel-plugin-transform-define":"^1.0.1","cac":"^7.0.0","chai":"^4.3.6","chai-http":"^4.3.0","copy-webpack-plugin":"^12.0.2","cross-env":"^7.0.3","css-loader":"^7.1.2","css-minimizer-webpack-plugin":"^8.0.0","esbuild-loader":"^4.2.2","eslint":"^8.57.0","eslint-config-prettier":"^9.1.0","eslint-import-resolver-typescript":"^3.6.1","eslint-import-resolver-webpack":"^0.13.4","eslint-plugin-import":"^2.29.1","eslint-plugin-jsx-a11y":"^6.9.0","eslint-plugin-prettier":"^5.1.3","eslint-plugin-react":"^7.34.3","eslint-plugin-react-hooks":"^4.6.2","eslint-webpack-plugin":"^4.2.0","html-webpack-plugin":"^5.6.0","image-webpack-loader":"^8.1.0","jest":"^29.7.0","mini-css-extract-plugin":"^2.9.1","mocha":"^10.4.0","nodemon":"^2.0.15","prettier":"^3.3.2","react-refresh":"^0.18.0","rollup":"^4.62.0","storybook":"^8.2.9","rimraf":"^6.0.1","style-loader":"^4.0.0","typescript":"^5.9.3","typescript-plugin-css-modules":"^5.2.0","webpack":"^5.92.1","webpack-bundle-analyzer":"^4.10.2","webpack-cli":"^5.1.4","webpack-dev-server":"^5.0.4","webpack-merge":"^5.9.0","webpack-node-externals":"^3.0.0","zengenti-buildstartup-package":"^0.0.19"},"dependencies":{"@contensis/canvas-react":"^1.3.2","@contensis/forms":"^1.0.5","@formatjs/intl-datetimeformat":"^6.16.5","@radix-ui/react-id":"^1.1.2","@sooro-io/react-gtm-module":"^3.0.0","@zengenti/contensis-react-base":"^4.0.1-beta.12","app-root-path":"^3.0.0","classnames":"^2.5.1","immer":"^11.1.8","insytful-ai-search-components":"^1.0.5","polished":"^4.3.1","react-markdown":"^8.0.7","react-router-hash-link":"^2.4.3","sitemap":"^8.0.0","styled-components":"^6.4.2"},"gitHead":"3f61f63833b61ad53d1f1256aa7a0beed730462d","_id":"@contensis/contensis-for-councils@2.4.1-beta.2","_nodeVersion":"24.13.1","_npmVersion":"11.8.0","dist":{"integrity":"sha512-fVnYh8ZOPJGnU9dHVDMjTa0ayI/hmWrup9SAyoBFthldWTKm8KxhYM8thn4IrHgf3Gb84RjnRcSQ4FeXjWOOKA==","shasum":"89376d8d9ee0913920c2d753a52a0ca676bc1a6d","tarball":"https://registry.npmjs.org/@contensis/contensis-for-councils/-/contensis-for-councils-2.4.1-beta.2.tgz","fileCount":3777,"unpackedSize":5141568,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIFXBAZ2wH1TgLl0IlPat1zDmaHGYLhiezHT2ET7Jvs4xAiAXzkYrwbJX2VYpZ1KIX2247S+rL+uQGW2l7uvgOJzs5g=="}]},"_npmUser":{"name":"n.flatley","email":"n.flatley@zengenti.com"},"directories":{},"maintainers":[{"name":"n.flatley","email":"n.flatley@zengenti.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/contensis-for-councils_2.4.1-beta.2_1784113748565_0.4880071256550025"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-30T15:26:13.984Z","modified":"2026-07-15T11:09:08.946Z","2.4.1-beta.0":"2026-06-30T15:26:14.488Z","2.4.1-beta.1":"2026-06-30T16:24:47.300Z","2.4.1-beta.2":"2026-07-15T11:09:08.808Z"},"author":{"name":"http://zengenti.com"},"license":"None","keywords":[],"repository":{"type":"git","url":"https://gitlab.zengenti.com/ps-projects/contensis-for-councils/contensis-for-councils"},"description":"A single React codebase that powers multiple UK council websites from the\r [Contensis](https://www.contensis.com/) CMS. One application serves many tenant\r sites — each driven by its own Contensis project and per-site configuration, and\r each deployed as ","maintainers":[{"name":"n.flatley","email":"n.flatley@zengenti.com"}],"readme":"# Contensis for Councils\r\n\r\nA single React codebase that powers multiple UK council websites from the\r\n[Contensis](https://www.contensis.com/) CMS. One application serves many tenant\r\nsites — each driven by its own Contensis project and per-site configuration, and\r\neach deployed as its own Contensis block.\r\n\r\nBuilt on [`@zengenti/contensis-react-base`](https://gitlab.zengenti.com/zengenti-packages/contensis-react-base)\r\n(CRB) with server-side rendering, the [Canvas](https://github.com/contensis/canvas)\r\nand [Forms](https://gitlab.zengenti.com/zengenti-packages/forms) packages, and\r\nfull **English / Welsh bilingual** support (including Welsh-first sites that\r\nserve Welsh at the site root).\r\n\r\n## 🧩 Tech stack\r\n\r\n- **React 18** + **TypeScript** (strict)\r\n- **`@zengenti/contensis-react-base` v4** — routing, search, SSR, Redux + sagas\r\n- **Styled Components** for styling (BEM class names for targeting)\r\n- **`@contensis/canvas-react`** for rich content, **`@contensis/forms`** for forms\r\n- **Webpack** build via the `crb` CLI; **Express**-backed SSR server\r\n- **Jest** for unit tests; **ESLint** + **Prettier**\r\n- **Docker** + **GitLab CI/CD** for block deployment\r\n\r\nRequires **Node 18** and a modern npm (7+).\r\n\r\n## 🔌 Getting started\r\n\r\n1. Create a `.env` file in the project root pointing at the CMS project you want\r\n   to run (see [Environment configuration](#-environment-configuration) below).\r\n2. Install dependencies:\r\n   ```\r\n   npm install\r\n   ```\r\n   If you hit peer-dependency install errors, remove `node_modules` and run\r\n   `npm install --legacy-peer-deps`.\r\n3. Start the dev server (HMR + SSR):\r\n   ```\r\n   npm run dev\r\n   ```\r\n\r\n## 🌎 Environment configuration\r\n\r\nThe app reads its CMS connection from a root `.env` file. Because this is a\r\nmulti-tenant project, the `.env` is what selects **which council site you run\r\nlocally** — point it at a given council's Contensis project and you're running\r\nthat site.\r\n\r\n| Variable | Purpose |\r\n| --- | --- |\r\n| `ALIAS` | Contensis environment alias (resolves the CMS/Delivery API URLs) |\r\n| `PROJECT` | Contensis project ID — the council site to serve |\r\n| `ACCESS_TOKEN` | Delivery API access token for that project |\r\n| `PUBLIC_URL` | The site's public URL (used for canonical URLs, sitemap, etc.) |\r\n\r\nYou can keep multiple environments side by side by adding a suffix —\r\ne.g. `.env.<name>` — and activating it on start/build:\r\n\r\n```\r\nnpm --env=<name> run dev\r\n```\r\n\r\n## 📜 Scripts\r\n\r\n| Script | Purpose |\r\n| --- | --- |\r\n| `npm run dev` | Development server with hot-reload and SSR |\r\n| `npm run build` | Production build (client + server bundles) |\r\n| `npm run server` | Run the built SSR server (`dist/start.js`) |\r\n| `npm run build:server` | Build, then start the SSR server (for debugging server-side code) |\r\n| `npm run analyze` | Production build with the webpack bundle analyzer |\r\n| `npm test` | Run the Jest test suite |\r\n| `npm run test:watch` | Jest in watch mode |\r\n| `npm run test:coverage` | Jest with a coverage report |\r\n| `npm run lint` / `npm run lint:fix` | ESLint check / auto-fix |\r\n\r\n## 🗂️ Project structure\r\n\r\n```\r\nsrc/app/\r\n├── App.tsx              # Root app component\r\n├── components/          # Reusable UI components (BEM + Styled Components)\r\n├── templates/           # Page templates per content type (article, event,\r\n│                        #   councillor, location, listings, search, home, …)\r\n├── listings/            # Listing/search config shared across listing pages\r\n├── routes/              # Static routes, content-type → template mappings, withEvents\r\n├── locale/              # Bilingual engine: language resolution, routes, slugs\r\n├── siteConfig/          # Per-site config (theme, nav, footer, alerts, search facets)\r\n├── schema/              # Content types, fields, locales, images, logos\r\n├── redux/               # Store wiring (reducers + sagas)\r\n├── styles/              # Theme types and global styles\r\n└── util/                # Mappers, hooks and helpers\r\n```\r\n\r\nContent types are mapped to templates in `src/app/routes/contentTypeMappings.ts`\r\n(article, event, councillor, location, post, the listing pages, etc.).\r\n\r\n## 🌐 Localisation (English / Welsh)\r\n\r\nThe app is bilingual and supports a **dynamic primary language** — a Welsh-first\r\ncouncil serves Welsh at the site root (`/`, `/chwilio`) while English lives under\r\n`/en-gb`, and an English-first council does the reverse. The primary language is\r\nresolved at runtime from the Contensis project, not hard-coded.\r\n\r\n- Locales are declared in `src/app/schema/locales.ts` (path segments, aliases,\r\n  per-locale route slugs such as `search` ↔ `chwilio`).\r\n- Route registration, language resolution, canonical redirects and localised\r\n  search facets/filters live under `src/app/locale/`.\r\n- The canonical URL form per locale is enforced with a 301 redirect (e.g.\r\n  `/en/search` → `/en-gb/search`).\r\n\r\n## 🚀 Multi-tenancy & deployment\r\n\r\nEach council site is a separate Contensis **block** deployed by GitLab CI. The\r\nshared sandbox/demo environment is `gov/website`; client sites are defined one\r\nfile per council under `.gitlab-ci/`.\r\n\r\nReleases are driven by the commit title on `master` (`vX.Y.Z` for a full release\r\nto all sites, `vX.Y.Z-beta` for a beta to the demo and opted-in sites). Routine\r\n`master` commits build but deploy nothing.\r\n\r\n**See [`docs/ci-cd.md`](docs/ci-cd.md)** for the full pipeline: branches,\r\nreleases, the image-tagging model, onboarding a new council, and the per-job\r\nreference.\r\n\r\n## 🍃 Contensis Delivery API\r\n\r\nWhen writing backing code that calls the Delivery API, prefer the helpers\r\nexported from `@zengenti/contensis-react-base/util` (see\r\n`src/app/util/contensisDeliveryClient.ts`).\r\n\r\n## 📝 Conventions\r\n\r\n- **Commits** follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)\r\n  — e.g. `feat: header component`, `fix(search): …`.\r\n- **Styling** uses Styled Components; element class names follow the\r\n  [BEM](https://getbem.com/naming/) (Block, Element, Modifier) convention.\r\n- **TypeScript** runs in strict mode; add explicit types and CMS-data mappers.\r\n\r\n## 📚 Further docs\r\n\r\n- [`docs/ci-cd.md`](docs/ci-cd.md) — CI/CD and block deployment\r\n- [`docs/security-exceptions.md`](docs/security-exceptions.md) — accepted `npm audit` exceptions\r\n- [`docs/cookie-audit.md`](docs/cookie-audit.md) — cookie audit\r\n- [`docs/plans/`](docs/plans) — design/implementation plans\r\n- [Releasing to live](https://chain-jewel-d93.notion.site/Releasing-161a0d0b10c249c39c48e8ee396991af) (Notion)\r\n","readmeFilename":"README.md"}