From f401189c9101e85a6831a64c50bb39428e67e518 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Thu, 2 May 2024 22:28:29 +0200 Subject: [PATCH] :package: Build JS assets in CI and gitignore them The npm 'package' and the django staticfiles assets are exclusively to be built in the CI pipeline, and not kept in version control. This keeps a single source of truth and prevents us from forgetting to update the artifacts, at the cost of a little extra step for local development. --- .github/actions/build-js/action.yml | 43 +++++ .github/workflows/ci.yml | 19 +- .github/workflows/release.yml | 5 + .gitignore | 3 +- CONTRIBUTING.rst | 12 ++ .../static/cookie_consent/cookiebar.module.js | 163 ------------------ .../cookie_consent/cookiebar.module.js.map | 7 - 7 files changed, 70 insertions(+), 182 deletions(-) create mode 100644 .github/actions/build-js/action.yml delete mode 100644 cookie_consent/static/cookie_consent/cookiebar.module.js delete mode 100644 cookie_consent/static/cookie_consent/cookiebar.module.js.map diff --git a/.github/actions/build-js/action.yml b/.github/actions/build-js/action.yml new file mode 100644 index 0000000..ef916d2 --- /dev/null +++ b/.github/actions/build-js/action.yml @@ -0,0 +1,43 @@ +--- + +name: 'Build JS' +description: 'Compile the TS source code' + +inputs: + npm-package: + description: Build NPM package + required: false + default: 'false' + + django-staticfiles: + description: Bundle Django staticfiles + required: false + default: 'false' + +runs: + using: 'composite' + + steps: + + - uses: actions/setup-node@v4 + with: + node-version-file: 'js/.nvmrc' + cache: npm + cache-dependency-path: js/package-lock.json + + - name: Install dependencies + run: npm ci + shell: bash + working-directory: js + + - name: Build NPM package + if: ${{ inputs.npm-package == 'true' }} + run: npm run build + shell: bash + working-directory: js + + - name: Build Django assets package + if: ${{ inputs.django-staticfiles == 'true' }} + run: npm run build:django-static + shell: bash + working-directory: js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 069c708..6b8623e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,18 +17,10 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: ./.github/actions/build-js with: - node-version-file: 'js/.nvmrc' - cache: npm - cache-dependency-path: js/package-lock.json - - - name: Build TS - run: | - npm ci - npm run build - npm run build:django-static - working-directory: js + npm-package: 'true' + django-staticfiles: 'false' tests: runs-on: ubuntu-latest @@ -77,6 +69,11 @@ jobs: with: python-version: '3.10' + - uses: ./.github/actions/build-js + with: + npm-package: 'false' + django-staticfiles: 'true' + - name: Install dependencies run: | pip install tox tox-gh-actions pytest-playwright diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cb25d2c..4c82528 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,6 +26,11 @@ jobs: python -m pip install -U pip python -m pip install -U setuptools twine wheel + - uses: ./.github/actions/build-js + with: + npm-package: 'true' + django-staticfiles: 'true' + - name: Build package run: | python setup.py --version diff --git a/.gitignore b/.gitignore index 4456f0c..a46d039 100644 --- a/.gitignore +++ b/.gitignore @@ -24,5 +24,6 @@ reports/ testapp/*.db # frontend tooling / builds -js/lib/ js/node_modules/ +js/lib/ +cookie_consent/static/cookie_consent/cookiebar.module.* diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 3a63f46..4b9ae16 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -46,6 +46,18 @@ You can then install the project with all the dev-tools: pip install -e .[tests,pep8,coverage,docs,release] +Some frontend tooling is needed to: + +* NodeJS (for the version, see ``.nvmrc``, you can use ``nvm``) + +.. code-block:: bash + + cd js + nvm use + npm install + npm run build:django-static + npm run build # optional, but a nice check + **Running the testapp as dev environment** In Django project's, you are typically expecting a ``manage.py`` file. This is less diff --git a/cookie_consent/static/cookie_consent/cookiebar.module.js b/cookie_consent/static/cookie_consent/cookiebar.module.js deleted file mode 100644 index 3e81eb2..0000000 --- a/cookie_consent/static/cookie_consent/cookiebar.module.js +++ /dev/null @@ -1,163 +0,0 @@ -// src/cookiebar.ts -var DEFAULT_FETCH_HEADERS = { - "X-Cookie-Consent-Fetch": "1" -}; -var FetchClient = class { - constructor(statusUrl, csrfHeaderName) { - this.statusUrl = statusUrl; - this.csrfHeaderName = csrfHeaderName; - this.cookieStatus = null; - } - async getCookieStatus() { - if (this.cookieStatus === null) { - const response = await window.fetch( - this.statusUrl, - { - method: "GET", - credentials: "same-origin", - headers: DEFAULT_FETCH_HEADERS - } - ); - this.cookieStatus = await response.json(); - } - if (this.cookieStatus === null) { - throw new Error("Unexpectedly received null cookie status"); - } - return this.cookieStatus; - } - async saveCookiesStatusBackend(urlProperty) { - const cookieStatus = await this.getCookieStatus(); - const url = cookieStatus[urlProperty]; - if (!url) { - throw new Error(`Missing url for ${urlProperty} - was the cookie status not loaded properly?`); - } - await window.fetch(url, { - method: "POST", - credentials: "same-origin", - headers: { - ...DEFAULT_FETCH_HEADERS, - [this.csrfHeaderName]: cookieStatus.csrftoken - } - }); - } -}; -var loadCookieGroups = (selector) => { - const node = document.querySelector(selector); - if (!node) { - throw new Error(`No cookie groups (script) tag found, using selector: '${selector}'`); - } - return JSON.parse(node.innerText); -}; -var doInsertBefore = (beforeNode, newNode) => { - const parent = beforeNode.parentNode; - if (parent === null) - throw new Error("Reference node doesn't have a parent."); - parent.insertBefore(newNode, beforeNode); -}; -var registerEvents = ({ - client, - cookieBarNode, - cookieGroups, - acceptSelector, - onAccept, - declineSelector, - onDecline, - acceptedCookieGroups: accepted, - declinedCookieGroups: declined, - notAcceptedOrDeclinedCookieGroups: undecided -}) => { - const acceptNode = cookieBarNode.querySelector(acceptSelector); - if (acceptNode) { - acceptNode.addEventListener("click", (event) => { - event.preventDefault(); - const acceptedGroups = filterCookieGroups(cookieGroups, accepted.concat(undecided)); - onAccept == null ? void 0 : onAccept(acceptedGroups, event); - client.saveCookiesStatusBackend("acceptUrl"); - cookieBarNode.parentNode.removeChild(cookieBarNode); - }); - } - const declineNode = cookieBarNode.querySelector(declineSelector); - if (declineNode) { - declineNode.addEventListener("click", (event) => { - event.preventDefault(); - const declinedGroups = filterCookieGroups(cookieGroups, declined.concat(undecided)); - onDecline == null ? void 0 : onDecline(declinedGroups, event); - client.saveCookiesStatusBackend("declineUrl"); - cookieBarNode.parentNode.removeChild(cookieBarNode); - }); - } -}; -var filterCookieGroups = (cookieGroups, varNames) => { - return cookieGroups.filter((group) => varNames.includes(group.varname)); -}; -function cloneNode(node) { - return node.cloneNode(true); -} -var showCookieBar = async (options = {}) => { - const { - templateSelector = "#cookie-consent__cookie-bar", - cookieGroupsSelector = "#cookie-consent__cookie-groups", - acceptSelector = ".cookie-consent__accept", - declineSelector = ".cookie-consent__decline", - insertBefore = null, - onShow, - onAccept, - onDecline, - statusUrl = "", - csrfHeaderName = "X-CSRFToken" - // Django's default, can be overridden with settings.CSRF_HEADER_NAME - } = options; - const cookieGroups = loadCookieGroups(cookieGroupsSelector); - if (!cookieGroups.length) - return; - const templateNode = document.querySelector(templateSelector); - if (!templateNode) { - throw new Error(`No (template) element found for selector '${templateSelector}'.`); - } - const doInsert = insertBefore === null ? (cookieBarNode2) => document.querySelector("body").appendChild(cookieBarNode2) : typeof insertBefore === "string" ? (cookieBarNode2) => { - const referenceNode = document.querySelector(insertBefore); - if (referenceNode === null) - throw new Error(`No element found for selector '${insertBefore}'.`); - doInsertBefore(referenceNode, cookieBarNode2); - } : (cookieBarNode2) => doInsertBefore(insertBefore, cookieBarNode2); - if (!statusUrl) - throw new Error("Missing status URL option, did you forget to pass the `statusUrl` option?"); - const client = new FetchClient(statusUrl, csrfHeaderName); - const cookieStatus = await client.getCookieStatus(); - const { - acceptedCookieGroups, - declinedCookieGroups, - notAcceptedOrDeclinedCookieGroups - } = cookieStatus; - const acceptedGroups = filterCookieGroups(cookieGroups, acceptedCookieGroups); - if (acceptedGroups.length) - onAccept == null ? void 0 : onAccept(acceptedGroups); - const declinedGroups = filterCookieGroups(cookieGroups, declinedCookieGroups); - if (declinedGroups.length) - onDecline == null ? void 0 : onDecline(declinedGroups); - if (!notAcceptedOrDeclinedCookieGroups.length) - return; - const childToClone = templateNode.content.firstElementChild; - if (childToClone === null) - throw new Error("The cookie bar template element may not be empty."); - const cookieBarNode = cloneNode(childToClone); - registerEvents({ - client, - cookieBarNode, - cookieGroups, - acceptSelector, - onAccept, - declineSelector, - onDecline, - acceptedCookieGroups, - declinedCookieGroups, - notAcceptedOrDeclinedCookieGroups - }); - doInsert(cookieBarNode); - onShow == null ? void 0 : onShow(); -}; -export { - loadCookieGroups, - showCookieBar -}; -//# sourceMappingURL=cookiebar.module.js.map diff --git a/cookie_consent/static/cookie_consent/cookiebar.module.js.map b/cookie_consent/static/cookie_consent/cookiebar.module.js.map deleted file mode 100644 index 89c12be..0000000 --- a/cookie_consent/static/cookie_consent/cookiebar.module.js.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": ["../../../js/src/cookiebar.ts"], - "sourcesContent": ["/**\n * Cookiebar functionality, as a TS/JS module.\n *\n * About modules: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules\n *\n * The code is organized here in a way to make the templates work with Django's page\n * cache. This means that anything user-specific (so different django session and even\n * cookie consent cookies) cannot be baked into the templates, as that breaks caches.\n *\n * The cookie bar operates on the following principles:\n *\n * - The developer using the library includes the desired template in their django\n * templates, using the HTML