diff --git a/.devcontainer/base.Dockerfile b/.devcontainer/base.Dockerfile index ee46d3bb..c4b58bc3 100644 --- a/.devcontainer/base.Dockerfile +++ b/.devcontainer/base.Dockerfile @@ -3,9 +3,9 @@ FROM mcr.microsoft.com/devcontainers/go:1.22-bookworm -ARG NODE_VERSION=20 +ARG NODE_VERSION=22 ARG PNPM_VERSION=10.14.0 -ARG HUGO_VERSION=0.152.2 +ARG HUGO_VERSION=0.161.0 ENV DEBIAN_FRONTEND=noninteractive ENV PNPM_HOME=/home/vscode/.local/share/pnpm diff --git a/.github/workflows/ci-build-starters-matrix.yml b/.github/workflows/ci-build-starters-matrix.yml index 80095f4c..21479fdb 100644 --- a/.github/workflows/ci-build-starters-matrix.yml +++ b/.github/workflows/ci-build-starters-matrix.yml @@ -43,7 +43,25 @@ env: NODE_VERSION: "22" jobs: + audit-template-deps: + name: Audit template Tailwind dependencies + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: "${{ env.NODE_VERSION }}" + + - name: Verify every template declares @tailwindcss/cli + tailwindcss + # Hugo >= 0.161.0 requires the npm @tailwindcss/cli package; the + # standalone tailwindcss binary is no longer accepted. + run: node scripts/check-template-deps.mjs + build-templates: + needs: audit-template-deps runs-on: ubuntu-latest strategy: fail-fast: false @@ -99,6 +117,13 @@ jobs: # and may not have lock files checked into the repo run: pnpm install --no-frozen-lockfile + - name: Verify @tailwindcss/cli resolves via node_modules + # Hugo >= 0.161.0 invokes node @tailwindcss/cli/dist/index.mjs directly. + # If require.resolve fails here, Hugo will fall back to PATH and emit + # "binary tailwindcss is not a Node.js script" at build time. + working-directory: ${{ matrix.starter.path }} + run: node -e "require.resolve('@tailwindcss/cli/package.json')" + - name: Read Hugo version from template id: hugo run: | @@ -123,6 +148,7 @@ jobs: smoke-test-latest: name: Test site on latest Hugo (allowed to fail) + needs: audit-template-deps runs-on: ubuntu-latest continue-on-error: true steps: @@ -148,6 +174,10 @@ jobs: working-directory: test run: pnpm install --no-frozen-lockfile + - name: Verify @tailwindcss/cli resolves via node_modules + working-directory: test + run: node -e "require.resolve('@tailwindcss/cli/package.json')" + - name: Cache Hugo resources (test) uses: actions/cache@v5 with: @@ -168,6 +198,7 @@ jobs: canary-academic-latest: name: academic-cv on latest Hugo (allowed to fail) + needs: audit-template-deps runs-on: ubuntu-latest continue-on-error: true steps: @@ -190,6 +221,10 @@ jobs: working-directory: templates/academic-cv run: pnpm install --no-frozen-lockfile + - name: Verify @tailwindcss/cli resolves via node_modules + working-directory: templates/academic-cv + run: node -e "require.resolve('@tailwindcss/cli/package.json')" + - name: Cache Hugo resources (template) uses: actions/cache@v5 with: @@ -210,6 +245,7 @@ jobs: smoke-test-min: name: Test site on minimum supported Hugo + needs: audit-template-deps runs-on: ubuntu-latest steps: - name: Checkout @@ -234,6 +270,10 @@ jobs: working-directory: test run: pnpm install --no-frozen-lockfile + - name: Verify @tailwindcss/cli resolves via node_modules + working-directory: test + run: node -e "require.resolve('@tailwindcss/cli/package.json')" + - name: Read minimum Hugo version from framework id: min run: | diff --git a/.github/workflows/devcontainer-image.yml b/.github/workflows/devcontainer-image.yml index f19c8228..bcbea4ac 100644 --- a/.github/workflows/devcontainer-image.yml +++ b/.github/workflows/devcontainer-image.yml @@ -14,8 +14,9 @@ on: env: IMAGE_NAME: ghcr.io/hugoblox/hugo-blox-dev - DEFAULT_HUGO_VERSION: 0.152.2 - NODE_VERSION: 20 + # Hugo >= 0.161.0 requires Node >= 22 for css.TailwindCSS Node permissions. + DEFAULT_HUGO_VERSION: 0.161.0 + NODE_VERSION: 22 PNPM_VERSION: 10.14.0 jobs: diff --git a/modules/blox/hugo.yaml b/modules/blox/hugo.yaml index 76885d33..eb270ef8 100644 --- a/modules/blox/hugo.yaml +++ b/modules/blox/hugo.yaml @@ -72,6 +72,11 @@ security: - ^HUGO_ # Allow continuous integration vars - ^CI$ + # Note: security.node.permissions is intentionally NOT set here. Hugo's + # built-in defaults already permit `tailwindcss` for allowAddons, + # allowChildProcess, and allowWorker (see https://gohugo.io/configuration/security/). + # Restating the defaults would create maintenance debt if upstream evolves them + # (e.g. when @tailwindcss/oxide native bindings need new permissions). outputFormats: backlinks: mediaType: application/json @@ -103,7 +108,10 @@ params: address_format: en-us module: hugoVersion: - min: "0.160.0" + # 0.161.0 introduced css.TailwindCSS Node.js permission sandbox + # (Node >= 22 required) and dropped support for the standalone + # tailwindcss binary. The npm @tailwindcss/cli package is required. + min: "0.161.0" extended: true imports: - path: github.com/HugoBlox/kit/modules/analytics diff --git a/package.json b/package.json index 4903c62e..4e227584 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "vendor:libs": "vite build --config vite.config.js", "vendor:libs:watch": "vite build --config vite.config.js --watch", "vendor:update-and-build": "pnpm up katex mermaid markmap-autoloader alpinejs plotly.js preact --latest && pnpm vendor:libs", - "test:releaser": "poetry run python scripts/test_release_modules.py" + "test:releaser": "poetry run python scripts/test_release_modules.py", + "check:template-deps": "node scripts/check-template-deps.mjs" }, "browserslist": "> 1%" } diff --git a/scripts/check-template-deps.mjs b/scripts/check-template-deps.mjs new file mode 100755 index 00000000..7e8bc3f9 --- /dev/null +++ b/scripts/check-template-deps.mjs @@ -0,0 +1,44 @@ +#!/usr/bin/env node +import { readFileSync, readdirSync, statSync } from "node:fs"; +import { join, resolve } from "node:path"; + +const REQUIRED = ["tailwindcss", "@tailwindcss/cli"]; +const TEMPLATES_DIR = resolve(process.argv[2] ?? "templates"); + +const failures = []; +const checked = []; + +for (const name of readdirSync(TEMPLATES_DIR).sort()) { + const dir = join(TEMPLATES_DIR, name); + if (!statSync(dir).isDirectory()) continue; + + const pkgPath = join(dir, "package.json"); + let pkg; + try { + pkg = JSON.parse(readFileSync(pkgPath, "utf8")); + } catch (err) { + if (err.code === "ENOENT") continue; + failures.push(`${name}: package.json unreadable — ${err.message}`); + continue; + } + + const deps = { ...pkg.dependencies, ...pkg.devDependencies }; + const missing = REQUIRED.filter((dep) => !(dep in deps)); + if (missing.length) { + failures.push(`${name}: missing ${missing.join(", ")}`); + } else { + checked.push(name); + } +} + +if (failures.length) { + console.error("✗ Template dependency audit failed:\n"); + for (const f of failures) console.error(` ${f}`); + console.error( + `\nHugo >= 0.161.0 requires the npm @tailwindcss/cli package — the standalone tailwindcss binary is no longer accepted.`, + ); + process.exit(1); +} + +console.log(`✓ All ${checked.length} templates declare required Tailwind dependencies:`); +for (const name of checked) console.log(` ${name}`); diff --git a/templates/academic-cv/.devcontainer/devcontainer.json b/templates/academic-cv/.devcontainer/devcontainer.json index 4ca19c85..09d90bea 100644 --- a/templates/academic-cv/.devcontainer/devcontainer.json +++ b/templates/academic-cv/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "HugoBlox Codespace", - "image": "ghcr.io/HugoBlox/hugo-blox-dev:hugo0.159.2", + "image": "ghcr.io/HugoBlox/hugo-blox-dev:hugo0.161.0", "updateContentCommand": "pnpm install --frozen-lockfile --prefer-offline", "postCreateCommand": "pnpm --version && hugo version", "customizations": { diff --git a/templates/academic-cv/hugoblox.yaml b/templates/academic-cv/hugoblox.yaml index 6c993613..9c2e3e7d 100644 --- a/templates/academic-cv/hugoblox.yaml +++ b/templates/academic-cv/hugoblox.yaml @@ -1,5 +1,5 @@ build: - hugo_version: '0.159.2' + hugo_version: '0.161.0' deploy: # Deployment target: github-pages, netlify, vercel, cloudflare, or none host: 'github-pages' diff --git a/templates/academic-cv/netlify.toml b/templates/academic-cv/netlify.toml index d90c77e9..d9121f35 100644 --- a/templates/academic-cv/netlify.toml +++ b/templates/academic-cv/netlify.toml @@ -21,7 +21,7 @@ publish = "public" [build.environment] - HUGO_VERSION = "0.159.2" + HUGO_VERSION = "0.161.0" GO_VERSION = "1.21.5" NODE_VERSION = "22" # Netlify runs an implicit install step; ensure it never enforces frozen lockfiles diff --git a/templates/data-science-blog/.devcontainer/devcontainer.json b/templates/data-science-blog/.devcontainer/devcontainer.json index 4ca19c85..09d90bea 100644 --- a/templates/data-science-blog/.devcontainer/devcontainer.json +++ b/templates/data-science-blog/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "HugoBlox Codespace", - "image": "ghcr.io/HugoBlox/hugo-blox-dev:hugo0.159.2", + "image": "ghcr.io/HugoBlox/hugo-blox-dev:hugo0.161.0", "updateContentCommand": "pnpm install --frozen-lockfile --prefer-offline", "postCreateCommand": "pnpm --version && hugo version", "customizations": { diff --git a/templates/data-science-blog/hugoblox.yaml b/templates/data-science-blog/hugoblox.yaml index c8448975..9fba0a9b 100644 --- a/templates/data-science-blog/hugoblox.yaml +++ b/templates/data-science-blog/hugoblox.yaml @@ -1,5 +1,5 @@ build: - hugo_version: '0.159.2' + hugo_version: '0.161.0' deploy: # Deployment target: github-pages, netlify, vercel, cloudflare, or none host: 'github-pages' diff --git a/templates/data-science-blog/netlify.toml b/templates/data-science-blog/netlify.toml index d5fd7ec1..5ae08f10 100644 --- a/templates/data-science-blog/netlify.toml +++ b/templates/data-science-blog/netlify.toml @@ -21,7 +21,7 @@ publish = "public" [build.environment] - HUGO_VERSION = "0.159.2" + HUGO_VERSION = "0.161.0" GO_VERSION = "1.21.5" NODE_VERSION = "22" # Ensure Netlify's implicit install does not force a frozen lockfile diff --git a/templates/dev-portfolio/.devcontainer/devcontainer.json b/templates/dev-portfolio/.devcontainer/devcontainer.json index 4ca19c85..09d90bea 100644 --- a/templates/dev-portfolio/.devcontainer/devcontainer.json +++ b/templates/dev-portfolio/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "HugoBlox Codespace", - "image": "ghcr.io/HugoBlox/hugo-blox-dev:hugo0.159.2", + "image": "ghcr.io/HugoBlox/hugo-blox-dev:hugo0.161.0", "updateContentCommand": "pnpm install --frozen-lockfile --prefer-offline", "postCreateCommand": "pnpm --version && hugo version", "customizations": { diff --git a/templates/dev-portfolio/hugoblox.yaml b/templates/dev-portfolio/hugoblox.yaml index 4babd896..fd20ca3d 100644 --- a/templates/dev-portfolio/hugoblox.yaml +++ b/templates/dev-portfolio/hugoblox.yaml @@ -1,5 +1,5 @@ build: - hugo_version: '0.159.2' + hugo_version: '0.161.0' deploy: # Deployment target: github-pages, netlify, vercel, cloudflare, or none host: 'github-pages' diff --git a/templates/dev-portfolio/netlify.toml b/templates/dev-portfolio/netlify.toml index d90c77e9..d9121f35 100644 --- a/templates/dev-portfolio/netlify.toml +++ b/templates/dev-portfolio/netlify.toml @@ -21,7 +21,7 @@ publish = "public" [build.environment] - HUGO_VERSION = "0.159.2" + HUGO_VERSION = "0.161.0" GO_VERSION = "1.21.5" NODE_VERSION = "22" # Netlify runs an implicit install step; ensure it never enforces frozen lockfiles diff --git a/templates/documentation/.devcontainer/devcontainer.json b/templates/documentation/.devcontainer/devcontainer.json index 4ca19c85..09d90bea 100644 --- a/templates/documentation/.devcontainer/devcontainer.json +++ b/templates/documentation/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "HugoBlox Codespace", - "image": "ghcr.io/HugoBlox/hugo-blox-dev:hugo0.159.2", + "image": "ghcr.io/HugoBlox/hugo-blox-dev:hugo0.161.0", "updateContentCommand": "pnpm install --frozen-lockfile --prefer-offline", "postCreateCommand": "pnpm --version && hugo version", "customizations": { diff --git a/templates/documentation/hugoblox.yaml b/templates/documentation/hugoblox.yaml index caa551fe..19b5dd3a 100644 --- a/templates/documentation/hugoblox.yaml +++ b/templates/documentation/hugoblox.yaml @@ -1,5 +1,5 @@ build: - hugo_version: '0.159.2' + hugo_version: '0.161.0' deploy: # Deployment target: github-pages, netlify, vercel, cloudflare, or none host: 'github-pages' diff --git a/templates/documentation/netlify.toml b/templates/documentation/netlify.toml index bcd3abb7..311102e8 100644 --- a/templates/documentation/netlify.toml +++ b/templates/documentation/netlify.toml @@ -21,7 +21,7 @@ publish = "public" [build.environment] - HUGO_VERSION = "0.159.2" + HUGO_VERSION = "0.161.0" GO_VERSION = "1.21.5" NODE_VERSION = "22" # Ensure Netlify's implicit install does not enforce frozen lockfiles diff --git a/templates/link-in-bio/.devcontainer/devcontainer.json b/templates/link-in-bio/.devcontainer/devcontainer.json index 4ca19c85..09d90bea 100644 --- a/templates/link-in-bio/.devcontainer/devcontainer.json +++ b/templates/link-in-bio/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "HugoBlox Codespace", - "image": "ghcr.io/HugoBlox/hugo-blox-dev:hugo0.159.2", + "image": "ghcr.io/HugoBlox/hugo-blox-dev:hugo0.161.0", "updateContentCommand": "pnpm install --frozen-lockfile --prefer-offline", "postCreateCommand": "pnpm --version && hugo version", "customizations": { diff --git a/templates/link-in-bio/hugoblox.yaml b/templates/link-in-bio/hugoblox.yaml index b574b9ef..715e26bb 100644 --- a/templates/link-in-bio/hugoblox.yaml +++ b/templates/link-in-bio/hugoblox.yaml @@ -1,5 +1,5 @@ build: - hugo_version: '0.159.2' + hugo_version: '0.161.0' deploy: # Deployment target: github-pages, netlify, vercel, cloudflare, or none host: 'github-pages' diff --git a/templates/link-in-bio/netlify.toml b/templates/link-in-bio/netlify.toml index 39a9e2ad..6004d01d 100644 --- a/templates/link-in-bio/netlify.toml +++ b/templates/link-in-bio/netlify.toml @@ -21,7 +21,7 @@ publish = "public" [build.environment] - HUGO_VERSION = "0.159.2" + HUGO_VERSION = "0.161.0" GO_VERSION = "1.21.5" NODE_VERSION = "22" # Ensure Netlify's implicit install step never enforces frozen lockfiles diff --git a/templates/markdown-slides/.devcontainer/devcontainer.json b/templates/markdown-slides/.devcontainer/devcontainer.json index 4ca19c85..09d90bea 100644 --- a/templates/markdown-slides/.devcontainer/devcontainer.json +++ b/templates/markdown-slides/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "HugoBlox Codespace", - "image": "ghcr.io/HugoBlox/hugo-blox-dev:hugo0.159.2", + "image": "ghcr.io/HugoBlox/hugo-blox-dev:hugo0.161.0", "updateContentCommand": "pnpm install --frozen-lockfile --prefer-offline", "postCreateCommand": "pnpm --version && hugo version", "customizations": { diff --git a/templates/markdown-slides/hugoblox.yaml b/templates/markdown-slides/hugoblox.yaml index 64e8b85f..df4cf3e4 100644 --- a/templates/markdown-slides/hugoblox.yaml +++ b/templates/markdown-slides/hugoblox.yaml @@ -1,5 +1,5 @@ build: - hugo_version: '0.159.2' + hugo_version: '0.161.0' deploy: # Deployment target: github-pages, netlify, vercel, cloudflare, or none host: 'github-pages' diff --git a/templates/markdown-slides/netlify.toml b/templates/markdown-slides/netlify.toml index b8035461..322439ae 100644 --- a/templates/markdown-slides/netlify.toml +++ b/templates/markdown-slides/netlify.toml @@ -20,7 +20,7 @@ publish = "public" [build.environment] - HUGO_VERSION = "0.159.2" + HUGO_VERSION = "0.161.0" GO_VERSION = "1.21.5" NODE_VERSION = "22" PNPM_FLAGS = "--no-frozen-lockfile" diff --git a/templates/resume/.devcontainer/devcontainer.json b/templates/resume/.devcontainer/devcontainer.json index 4ca19c85..09d90bea 100644 --- a/templates/resume/.devcontainer/devcontainer.json +++ b/templates/resume/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "HugoBlox Codespace", - "image": "ghcr.io/HugoBlox/hugo-blox-dev:hugo0.159.2", + "image": "ghcr.io/HugoBlox/hugo-blox-dev:hugo0.161.0", "updateContentCommand": "pnpm install --frozen-lockfile --prefer-offline", "postCreateCommand": "pnpm --version && hugo version", "customizations": { diff --git a/templates/resume/hugoblox.yaml b/templates/resume/hugoblox.yaml index 96e83687..bba9f2d2 100644 --- a/templates/resume/hugoblox.yaml +++ b/templates/resume/hugoblox.yaml @@ -1,5 +1,5 @@ build: - hugo_version: '0.159.2' + hugo_version: '0.161.0' deploy: # Deployment target: github-pages, netlify, vercel, cloudflare, or none host: 'github-pages' diff --git a/templates/resume/netlify.toml b/templates/resume/netlify.toml index 581d202e..88501a19 100644 --- a/templates/resume/netlify.toml +++ b/templates/resume/netlify.toml @@ -21,7 +21,7 @@ publish = "public" [build.environment] - HUGO_VERSION = "0.159.2" + HUGO_VERSION = "0.161.0" GO_VERSION = "1.21.5" NODE_VERSION = "22" # Ensure Netlify's implicit install step does not enforce frozen lockfiles diff --git a/templates/starter/hugoblox.yaml b/templates/starter/hugoblox.yaml index 6a6c40f2..b073b766 100644 --- a/templates/starter/hugoblox.yaml +++ b/templates/starter/hugoblox.yaml @@ -1,5 +1,5 @@ build: - hugo_version: '0.159.2' + hugo_version: '0.161.0' deploy: # Deployment target: github-pages, netlify, vercel, cloudflare, or none host: 'github-pages' diff --git a/templates/startup-landing-page/.devcontainer/devcontainer.json b/templates/startup-landing-page/.devcontainer/devcontainer.json index 4ca19c85..09d90bea 100644 --- a/templates/startup-landing-page/.devcontainer/devcontainer.json +++ b/templates/startup-landing-page/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "HugoBlox Codespace", - "image": "ghcr.io/HugoBlox/hugo-blox-dev:hugo0.159.2", + "image": "ghcr.io/HugoBlox/hugo-blox-dev:hugo0.161.0", "updateContentCommand": "pnpm install --frozen-lockfile --prefer-offline", "postCreateCommand": "pnpm --version && hugo version", "customizations": { diff --git a/templates/startup-landing-page/hugoblox.yaml b/templates/startup-landing-page/hugoblox.yaml index 38a84bb9..b7ae0a5b 100644 --- a/templates/startup-landing-page/hugoblox.yaml +++ b/templates/startup-landing-page/hugoblox.yaml @@ -1,5 +1,5 @@ build: - hugo_version: '0.159.2' + hugo_version: '0.161.0' deploy: # Deployment target: github-pages, netlify, vercel, cloudflare, or none host: 'github-pages' diff --git a/templates/startup-landing-page/netlify.toml b/templates/startup-landing-page/netlify.toml index 581d202e..88501a19 100644 --- a/templates/startup-landing-page/netlify.toml +++ b/templates/startup-landing-page/netlify.toml @@ -21,7 +21,7 @@ publish = "public" [build.environment] - HUGO_VERSION = "0.159.2" + HUGO_VERSION = "0.161.0" GO_VERSION = "1.21.5" NODE_VERSION = "22" # Ensure Netlify's implicit install step does not enforce frozen lockfiles