Skip to content

Choose the tech stacks #3

Description

@ABilenduke

Choose the language, framework, toolchain, quality and security checks, and container baseline for each application, before any code lands.

Status

Stage Track Next action Branch PR
done full Close the six spikes when approved; write the ADRs through PRs — —

Appetite: 3h active (Default) · Estimate: — · Active so far: see the time comment

Brief

Idea: "A research spike separately for admin, backend, checkout and storefront to determine the best tech stacks for each. I prefer TypeScript and Vue in some form (Vue, Nuxt, Ionic etc.) for frontend apps. I am open on the backend: if the best is TypeScript, is Node, Bun or Deno better? Should we do it in Rust or Go? We are not just looking for languages and frameworks though: what about linters, code quality and security checks. Each service will be in a Docker container."

Problem and audience: No repository has code yet, and none has a stack. Contributors, and the agents that scaffold each repo, need one researched choice per application. The choice covers the language, runtime and framework, plus the lint, format, type-check, test, quality and security tooling, and the container it ships in. The platform is also a portfolio, so a hiring manager reading the repos is a second audience.

Outcome: Each spike ends with a recorded decision. This epic ends with one table of stacks that agree where they meet: the generated SDK, the shared frontend tooling the design system publishes, the security checks, and the container baseline.

Scope.

Constraints:

  • Every technology must solve a real problem in the system (AGENTS.md:29).
  • Repos meet only through contracts: the API spec and the SDK generated from it, and the design system's packages (AGENTS.md:12-13).
  • The design system publishes the shared lint, formatting, stylesheet and TypeScript config for the other repos (design-system/README.md:16).
  • Security checks today are CodeQL on actions only (backend/.github/workflows/codeql.yml:24), Dependabot for actions only (backend/.github/dependabot.yml:8), and git-secrets in the commit hooks (backend/.githooks/pre-commit:3). The other repos are identical.
  • Complexity has to earn its place (README.md:59).
  • Each service runs in a Docker container (user).

Known in code: No code exists in any repo. backend/.github/workflows/codeql.yml:24 and backend/.github/dependabot.yml:8 both wait for "this repository's languages / package ecosystem as its code lands". The stacks chosen here fill them in.

Track: full — the answers need sources outside the repository, and options to compare · Appetite: 3h active for the shared baseline (Default)

ID Research question Track Why it matters to the plan
RQ-1 Which JS/TS runtime (Node LTS, Bun or Deno) and package manager (pnpm, npm or bun) for frontend builds and any TS server? technical Sets the base image, the lockfile and the CI setup for every TS repo
RQ-2 Which shared TS/Vue toolchain should the design system publish? Linting: ESLint flat with typescript-eslint and eslint-plugin-vue, or Biome, or Oxlint. Formatting: Prettier, Biome or oxfmt. Plus vue-tsc and the status of TS 7 native, Stylelint, Vitest and Knip. technical Decides what the design system ships and what each frontend runs in CI
RQ-3 Which security checks earn their place on top of CodeQL, Dependabot and git-secrets? Candidates: CodeQL languages and Dependabot ecosystems; push protection and dependency review; OSV-Scanner or audit; Scorecard and pinning actions by SHA; hadolint; Trivy, Grype or Docker Scout; SBOM and provenance. technical The CI security baseline for every repo, without duplicate tools
RQ-4 What should the container baseline be? Base image (slim, distroless, Chainguard/Wolfi or Alpine), multi-stage, non-root, read-only root, healthcheck, signal handling. technical Every Dockerfile follows it
RQ-5 Is a hosted quality gate (SonarQube Cloud, free for public repos) worth it, or are lint, type and coverage gates enough? technical Whether to add a service and a required check
RQ-6 What do Vue/Nuxt job postings ask for: Node, Go or Rust on the backend, and Ionic, Capacitor or mobile? market Breaks ties only: hiring signal never outweighs fit

Assumptions:

  • Web only: there is no native or installable mobile app (Default, set at plan approval). Ionic's UI kit would duplicate the design system's components (design-system/AGENTS.md:8-10).
  • JVM and .NET backends are out of scope, because the user named TypeScript, Go and Rust.

Spec

Not written yet.

Plan

Not written yet.

Decisions

ID Decision Kind Source
DE-1 Web only, with no native or installable mobile app. Ionic and Capacitor are ruled out: Ionic's UI kit would duplicate the design system, and the job-posting sample shows no demand (RQ-6, F58). Decided @ABilenduke, 2026-10-07 (the Default was confirmed at plan approval and when the research was accepted)
DE-2 Runtime: Node.js LTS everywhere: 24 now, 26 from 2026-10-28.
Package manager: the bundled npm, hardened in .npmrc with strict-allow-scripts, min-release-age=7, allow-git=none, allow-remote=none and engine-strict, and installed with npm ci.
Move to npm 12 once Dependabot lists it. pnpm 10 is the runner-up; Bun and Deno are rejected.
Decided @ABilenduke, 2026-10-07; research RQ-1
DE-3 The design system publishes the shared toolchain:
• ESLint 10 with typescript-eslint recommendedTypeChecked and eslint-plugin-vue;
• Prettier 3;
• vue-tsc 3 on TypeScript ~6.0 until the Vue tools support TS 7.1;
• Stylelint with standard-vue;
• Vitest 5 with coverage thresholds;
• Knip.
Decided @ABilenduke, 2026-10-07; research RQ-2
DE-4 CI security, adopt:
• CodeQL for javascript-typescript and go;
• an org policy requiring actions pinned by SHA;
• Dependabot for npm, docker, compose and gomod, with groups and a 7-day cooldown;
• push protection, plus an Authorize.Net pattern in git-secrets;
• dependency-review-action;
• hadolint;
• Grype;
• buildx --sbom plus actions/attest.
Reject: audit and OSV-Scanner, Trivy, Docker Scout, Scorecard and zizmor as CI jobs, syft and cosign.
Decided @ABilenduke, 2026-10-07; research RQ-3
DE-5 Containers: multi-stage builds onto distroless nonroot: nodejs for the storefront, static for Go. Static SPAs run on nginx-unprivileged:alpine-slim, pinned by digest. Every service runs as a numeric non-root user, with an exec-form CMD, a HEALTHCHECK, init: true, a read-only root plus a /tmp tmpfs, cap_drop: ALL and no-new-privileges. Decided @ABilenduke, 2026-10-07; research RQ-4
DE-6 No hosted quality gate (SonarQube Cloud or Codecov). Lint, type checks, coverage thresholds and CodeQL in CI cover quality. Decided @ABilenduke, 2026-10-07; research RQ-5
DE-7 The frontends' SDK is openapi-typescript + openapi-fetch, generated from the backend's code-first OpenAPI 3.1 spec and published by the backend. Decided @ABilenduke, 2026-10-07; commerce-backend#1 D2
DE-8 Admin form validation schemas come from Orval in zod-only mode, pinned exactly. Orval generates Zod 4 schemas from the backend's published OpenAPI spec, next to the openapi-typescript + openapi-fetch SDK (DE-7). The generated schemas check shape only, and the backend still validates. Where the generator runs, in the backend's SDK package or in the admin build, is settled in the backend and admin ADRs. Decided @ABilenduke, 2026-10-08; commerce-admin#1 RQ-2
DE-9 Separate customer and staff sessions.
• Customer: a host-only __Host- cookie on api., SameSite=Lax. The storefront server holds no session.
• Staff: a separate __Host- cookie name, SameSite=Strict, with an admin-only CORS allowlist and Origin checks on admin endpoints.
• Both: HttpOnly, Secure, Path=/. CSRF defence is an Origin/Sec-Fetch-Site check plus a custom header on unsafe methods, because SameSite doesn't separate the same-site *.rsl-commerce.test hosts.
The backend ADR specifies the details.
Decided @ABilenduke, 2026-10-08; commerce-storefront#1 RQ-2; commerce-admin#1 RQ-3
DE-10 Design-system packages are published privately to GitHub Packages (npm.pkg.github.com), not the public npm registry, because this isn't a public library.
• Scope: @reference-systems-lab, the org's own namespace on GitHub. Packages: /tokens, /ui-core, /ui, /eslint-config, /prettier-config, /stylelint-config, /tsconfig. Read the @rsl/* names in the research comments as @reference-systems-lab/*.
• Consumers (storefront, admin, checkout, platform docs):
◦ map the scope in .npmrc with @reference-systems-lab:registry=https://npm.pkg.github.com;
◦ get read access through each package's "Manage Actions access", so CI and Dependabot authenticate with GITHUB_TOKEN;
◦ pass the token to Docker builds as a BuildKit secret, never as a layer or build arg;
◦ for developers, use a classic PAT with read:packages in ~/.npmrc.
• Unchanged: semver ranges and DE-2's allow-remote=none.
• Not affected: the platform's default pinned images need no token; only LOCAL builds of frontends do.
Decided @ABilenduke, 2026-10-08; design-system#1 DS-D5, DS-D8
DE-11 Separate docs hosts: docs.rsl-commerce.test serves the engineering docs from the platform repo, and a new design.rsl-commerce.test serves the design-system docs. That makes eight local hosts. Decided @ABilenduke, 2026-10-07 (commerce-platform#1)
DE-12 Valkey 9.1 replaces Redis as the cache server system-wide: BSD-3 licence, managed-service parity, and a passed go-redis probe. READMEs should say "Valkey (Redis-compatible)". Decided @ABilenduke, 2026-10-07 (commerce-platform#1 P-D7)
DE-13 The fake Authorize.Net gateway for secret-free E2E lives in the backend repo as a test-only image. Decided @ABilenduke, 2026-10-07 (commerce-platform#1 P-D9)

Stacks chosen

Repo Stack
backend Go 1.27, Huma v2 (code-first OpenAPI 3.1), pgx + sqlc + goose, amqp091-go (outbox relay, quorum queues + DLX), go-redis, meilisearch-go, scs + own RBAC, coder/websocket, OTel Go; golangci-lint v2, govulncheck, go-arch-lint, testcontainers-go; distroless/static
storefront Nuxt 4 on the node-server preset; rendering chosen per route, with no page cache wherever prices show; useAsyncData + Pinia Colada; nuxt-security with a nonce-based strict CSP; Unhead, sitemap, robots and schema-org; axe and Lighthouse CI; VueUse useWebSocket; useAnalytics(); no PWA; distroless nodejs
checkout A minimal Vue 3.5 + Vite 8 + TS SPA (no Nuxt, router or Pinia); Authorize.Net Accept Hosted by redirect; a header CSP of 'self' with Trusted Types and SRI; nginx-unprivileged
admin Vue 3.5 + Vite 8 + Vue Router 5 (typed file routes); TanStack Table 9 + Virtual; TanStack Form + Zod 4; ECharts 6; Pinia 4 + Pinia Colada; a flat permission list from /me; VueUse useWebSocket; Vitest 5 (Node + Browser Mode), MSW 3 and Playwright; nginx-unprivileged
design-system Turborepo 2.11 on npm workspaces; Terrazzo for DTCG 2025.10 tokens; ui-core (no dependencies) and ui (Reka UI); plain CSS in @layer with one file per component, and strict-CSP authoring rules; tsdown, Changesets, private GitHub Packages publishing; VitePress 2 docs with a /playground page; no separate workshop; Vitest Browser Mode, axe, screenshots in a pinned Playwright image, size-limit
platform Docker Compose ≥ 5 with per-app compose.platform.yaml contracts and digest-pinned GHCR images (opt-in local builds); Make over POSIX scripts; an openssl name-constrained local CA trusted through OS tools; nginx-unprivileged proxy; PostgreSQL 18, Valkey 9.1, RabbitMQ 4.3, Mailpit, Meilisearch (hardened); otel-lgtm; Playwright E2E with the backend's fake gateway, Schemathesis, Dependabot-updated matrix; VitePress 2 engineering docs at docs. with Swagger UI and pre-rendered Mermaid

ADRs to write, through PRs in the repos they affect:

  • commerce: runtime and package manager (DE-2) and containers (DE-5);
  • platform: its stack (commerce-platform#1);
  • design-system: the shared toolchain (DE-3) and its stack (design-system#1);
  • one ADR in each of backend, storefront, checkout and admin.

Revisions

Links

Activity

  1. added
    type:spikeResearch that answers a question and ends there
    track:fullNeeds evidence: brief, research, plan, execute, review
    on Oct 7, 2026
  2. self-assigned this
    on Oct 7, 2026
  3. ABilenduke commented on Oct 7, 2026

    @ABilenduke
    ContributorAuthor

    Time: issue-3

    Generated by worklog render from time.jsonl. Do not edit by hand.

    Step Estimate Started Finished Wall Active Sessions
    brief 30m 2026-10-07 00:01 2026-10-07 00:03 2m 2m 3da8d26d (claude-code)
    research 3h 2026-10-07 00:05 2026-10-07 00:41 22m 22m 3da8d26d (claude-code)
    Total 3h 30m 24m 24m

    Active time counts agent turns plus up to 5 minutes of each wait for a person, and no single gap
    counts more than 30 minutes. — means nothing was recorded.

    workflow · claude-code · 2026-10-07 23:03

  4. ABilenduke commented on Oct 7, 2026

    @ABilenduke
    ContributorAuthor

    Research: Choose the tech stacks (shared baseline)

    Status: Answered · Evidence base: d0573a0 (clean), plus each child at the SHA in its spike · 2026-10-07

    Summary

    The shared baseline for every repository:

    • Runtime: Node.js LTS everywhere. Node 24 now, and Node 26 once it becomes LTS on 2026-10-28.
    • Package manager: the npm that ships with Node, locked down in .npmrc. It is the only option that blocks install scripts and new releases and still has full Dependabot security coverage. Dependabot doesn't support pnpm 11+, npm 12 or Bun security updates.
    • Toolchain (the design system publishes it): ESLint 10 with typescript-eslint and eslint-plugin-vue, Prettier, vue-tsc on TypeScript 6.0, Stylelint, Vitest and Knip.
      • In the experiment, ESLint was the only linter that checked both Vue templates and type-aware rules.
      • TypeScript 7 can't run vue-tsc or typescript-eslint yet.
    • CI security, adopt eight checks:
      • CodeQL for the app languages;
      • a policy that pins actions by SHA;
      • Dependabot with groups and a cooldown;
      • push protection;
      • dependency review;
      • hadolint;
      • Grype;
      • SBOMs with attestations.
    • CI security, reject:
      • Trivy, which was compromised in March 2026;
      • SonarQube Cloud;
      • the tools that duplicate these.
    • Containers: multi-stage builds onto distroless or nginx-unprivileged; non-root, read-only, all capabilities dropped, and init: true. In the experiment, Node running as PID 1 ignored SIGTERM.
    • Market signal: a small sample of job postings slightly favours Node/TypeScript behind Vue. Rust, Ionic and Capacitor didn't appear.

    Confidence is high, except the package manager (medium) and the market sample (low-medium).

    Answers

    RQ Answer Confidence
    RQ-1 Node.js LTS: Node 24 until Node 26 becomes LTS on 2026-10-28; it is supported to 2029-04-30. Package manager: the bundled npm 11, locked down in .npmrc:
    • strict-allow-scripts, plus an allowScripts policy;
    • min-release-age;
    • allow-git=none and allow-remote=none.
    Dependabot supports npm v7–v11 and pnpm v7–v10, and gives Bun no security updates. pnpm 10 is the runner-up. Bun and Deno are rejected: Bun has no LTS and Deno would be a second runtime.
    High (runtime) / medium (package manager)
    RQ-2 ESLint 10 flat config with typescript-eslint recommendedTypeChecked and eslint-plugin-vue, plus Prettier 3, vue-tsc 3 on TypeScript 6.0.x, Stylelint (standard-vue), Vitest 5 with coverage thresholds, and Knip.
    Of 12 seeded problems, ESLint caught 9 with its presets and 11 with three extra rules; Biome (full Vue mode) caught 7 and Oxlint 3, with no template rules. TypeScript 7 runs neither vue-tsc nor typescript-eslint.
    High
    RQ-3 Adopt:
    • CodeQL javascript-typescript plus the backend language;
    • an org policy that pins actions by SHA;
    • Dependabot (npm, docker, compose, and the backend ecosystem) with groups and a 7-day cooldown;
    • push protection;
    • dependency-review-action;
    • hadolint;
    • Grype;
    • buildx --sbom plus actions/attest.
    Reject: audit and OSV-Scanner (duplicates), Trivy (compromised March 2026), Docker Scout (one free repo), Scorecard and zizmor as CI jobs (they overlap CodeQL's actions queries), syft and cosign (BuildKit and attest cover them).
    High
    RQ-4 Build multi-stage on Debian trixie-slim and run on distroless nonroot: nodejs for Node, static or cc for Go or Rust. Static SPAs run on nginx-unprivileged. Every container gets:
    • a numeric USER and exec-form CMD;
    • a HEALTHCHECK;
    • init: true;
    • read_only with a /tmp tmpfs;
    • cap_drop: ALL and no-new-privileges.
    Docker Hardened Images are the fallback. Chainguard's free tier (:latest only) and Alpine (musl) are rejected.
    High
    RQ-5 No hosted quality gate. ESLint, vue-tsc, Vitest coverage thresholds and CodeQL already gate quality. SonarQube Cloud duplicates them and supports TypeScript only up to 5.9.3. Revisit Codecov only if reviews need coverage deltas. Medium-high
    RQ-6 Sample: 39 US Vue/Nuxt postings. Backend mentions: Node/TS 56%, Python 36%, Java 31%, .NET 28%, Go 8%, Rust 0%. Mobile: 8%, always "preferred"; Ionic appeared in 1 posting, Capacitor in none. Node/TS slightly wins a tie; nothing supports adding a mobile app. Low-medium (small sample)

    Recommendation

    1. Runtime and package manager (F1-F12)

    • Node.js LTS for builds, Nuxt SSR and a TS backend if one is chosen. Set engines.node and .nvmrc to 24 now, and to 26 from 2026-10-28.
    • npm, the version bundled with Node. Its .npmrc:
      • strict-allow-scripts=true, with an allowScripts policy in package.json;
      • min-release-age=7, plus min-release-age-exclude[]=@<scope>/*;
      • allow-git=none and allow-remote=none;
      • engine-strict=true.
    • npm ci in CI and in Docker builds. The design system publishes through npm trusted publishing, which adds provenance automatically.
    • Move to npm 12, which blocks scripts by default, once Dependabot lists it.
    • This settles the checkout spike's "npm 12 or pnpm" question (commerce-checkout#1 RQ-5): npm 12 isn't on Dependabot's supported list yet.
    • Options considered:
      • pnpm 10 (runner-up): Dependabot supports it, it blocks scripts by default and it is strict about dependencies, but it is two majors behind.
      • pnpm 11/12: better defaults, but Dependabot doesn't list them, so we'd need Renovate as well.
      • Bun: no Dependabot security updates, no LTS, and it trusts its own allowlist.
      • Deno: a second runtime under Node-first tooling.

    2. The toolchain the design system publishes (F13-F30): @rsl/eslint-config, @rsl/tsconfig, @rsl/prettier-config and @rsl/stylelint-config.

    Tool Configuration
    ESLint 10 @vue/eslint-config-typescript recommendedTypeChecked, eslint-plugin-vue flat/recommended, eqeqeq, prefer-const, vue/no-v-html: error, with eslint-config-prettier last
    Prettier 3 defaults; oxfmt's output was byte-identical, so watch it
    vue-tsc 3 peer typescript: ~6.0 until TypeScript 7.1 ships its API and the Vue tools support it
    Stylelint 17 stylelint-config-standard-vue
    Vitest 5 coverage-v8 with thresholds
    Knip 6 unused files, dependencies and exports

    Watch rather than adopt: Oxlint running ahead of ESLint (the way create-vue sets it up), and Biome once its Vue support is no longer experimental.

    3. CI security (F31-F43)

    • CodeQL: add javascript-typescript (it covers .vue) and the backend language.
    • An org policy requiring SHA-pinned actions, with Dependabot keeping the SHAs current. The existing @v7 and @v4 references are re-pinned first.
    • Dependabot: npm, docker, docker-compose, and gomod or cargo if the backend uses one. Group minor and patch updates, with cooldown.default-days: 7.
    • Push protection. Also add an Authorize.Net pattern to git-secrets, because neither tool knows one.
    • dependency-review-action with fail-on-severity and allow-licenses.
    • hadolint.
    • Grype through anchore/scan-action, uploading SARIF and failing on high. Dependabot's docker ecosystem has no security updates, so Grype is what sees base-image CVEs.
    • docker buildx build --sbom=true plus actions/attest.

    4. Container baseline (F44-F52)

    Workload Build → run
    Storefront SSR, and a TS backend node:<lts>-trixie-slim → gcr.io/distroless/nodejs<lts>-debian13:nonroot (about 55 MB compressed)
    Admin, checkout (static) nginxinc/nginx-unprivileged:alpine-slim (about 8 MB compressed, uid 101, port 8080), pinned by digest; nginx sends the CSP
    Go distroless/static-debian13:nonroot
    Rust distroless/cc-debian13:nonroot

    Every service:

    • a numeric USER and an exec-form CMD;
    • a HEALTHCHECK, which in distroless is a Node script, not curl;
    • in Compose: init: true, read_only: true with a /tmp tmpfs, cap_drop: [ALL] and security_opt: [no-new-privileges:true];
    • an application that drains connections on SIGTERM.

    The spikes report image sizes two ways: compressed figures here, uncompressed in the storefront and checkout reports.

    5. Quality gate: none hosted (F53-F56).

    6. Market: a tiebreaker only (F57-F59).

    Risks and unknowns

    • Two TypeScript majors in the org. agent-workflow-tooling already uses TypeScript 7 (F27); the Vue repos stay on 6.0.x until vue-tsc and typescript-eslint support the 7.1 API. Neither the TypeScript 7.1 date nor the TypeScript 6 support window is known.
    • npm 11 runs install scripts by default (F8). The protection depends on .npmrc being present everywhere, including Docker build contexts.
    • Dependabot's pnpm v10 ceiling may simply be out-of-date documentation. Not tested.
    • Lint experiment limits. One SFC on WSL2, with npx start-up time included. Biome and Oxlint change fast, so re-test in about six months.
    • Debugging distroless needs :debug tags, and DHI would need a registry login in CI.
    • Org-admin settings. The SHA-pin policy and push protection are org settings, and the current tag pins must be updated before the policy is switched on.
    • RQ-6 is a weak signal: a small, keyword-biased, US-only sample, leaning toward staffing firms.
    • Agreed across spikes, which each ADR must keep consistent:
      • storefront and admin both pick Pinia Colada;
      • admin and checkout both pick nginx-unprivileged;
      • every frontend needs the backend's cookie, CORS and CSRF design and its SDK generator (commerce-backend#1).

    What this means for the plan

    • A commerce ADR, "JavaScript runtime and package manager": Node LTS, npm hardened through .npmrc, and the trigger for moving to npm 12.
    • The design-system ADR and packages:
      • the four config packages, with peers typescript ~6.0 and eslint ^10;
      • published with trusted publishing; making a warning into an error counts as a major version;
      • acceptance: a fixture SFC trips the template, floating-promise and v-html rules, and vue-tsc fails on an undeclared template identifier;
      • open: an accessibility lint plugin. The storefront spike recommends eslint-plugin-vuejs-accessibility.
    • Per-repo CI, as code lands:
      • CodeQL languages;
      • Dependabot ecosystems, groups and cooldown;
      • dependency-review, hadolint, Grype and attest;
      • actions re-pinned by SHA.
    • Org settings task: the SHA-pin policy and push protection.
    • A container ADR, in platform plus each app: the image table, Compose hardening and graceful shutdown.
    • The backend-language ADR: the market signal breaks a tie only.

    Verified by the lead before posting:

    • endoflife.date's API: Node 26 becomes LTS on 2026-10-28 with EOL 2029-04-30, and Node 24 has EOL 2028-04-30;
    • the Dependabot ecosystems page: npm v7–v11 and pnpm v7–v10 have security updates; Bun, Docker and Compose get none;
    • typescript-eslint peers typescript >=4.8.4 <6.1.0; typescript is at 7.0.2;
    • vuejs/language-tools discussion #6121, "vue-tsc support for TypeScript 7";
    • GHSA-69fq-xp46-6x23 (CVE-2026-33634, critical, "Trivy ecosystem supply chain temporarily compromised", 2026-03-21);
    • SonarQube Cloud's JS/TS docs: "All versions up to 5.9.3 are supported";
    • the experiment workspace, including its ts7/ project.
    Evidence

    RQ-1: Runtime and package manager

    • F1 [sourced] Node 24's EOL is 2028-04-30. Node 26 becomes LTS on 2026-10-28, with EOL 2029-04-30. Node 22's EOL is 2027-04-30. (endoflife.date Node.js, accessed 2026-10-07)
    • F2 [sourced] Vite "requires Node.js version 20.19+, 22.12+" (Vite guide, 8.3.1). Nuxt needs Node "22.x or newer (but we recommend the active LTS release)" (Nuxt installation, 4.x). Both accessed 2026-10-07.
    • F3 [experiment] Engines: nuxt@4.6.0 needs ^22.22.3 || ^24.15.0 || >=26.0.0, and vite@8.3.3 needs ^20.19.0 || >=22.12.0. Node 24.21.0 bundles npm 11.19.0, and Node 26.10.0 bundles npm 11.19.1. (npm view, nodejs.org/dist/index.json)
    • F4 [sourced] Type stripping is "Stable" in v25.2.0 and v24.12.0, for erasable syntax only, with no type checking. (Node.js TypeScript, accessed 2026-10-07, v26)
    • F5 [sourced] Corepack ships with Node "from version 14.19.0 up to (but not including) 25.0.0". (nodejs/corepack, accessed 2026-10-07)
    • F6 [sourced] Bun 1.4.2 came out on 2026-09-05, and Bun has no LTS (bun releases). Deno's LTS is "the Deno 2.9 line … maintained until January 31st, 2027" (Deno stability). Both accessed 2026-10-07.
    • F7 [sourced] Dependabot supports npm v7–v11 and pnpm v7–v10, both with security updates. Bun (≥1.1.39), Docker and Compose get no security updates; Deno ≥v2 does. (Dependabot ecosystems, accessed 2026-10-07)
    • F8 [experiment] Install scripts, tested with esbuild 0.25.0 (E3):
      • npm 11.19 runs esbuild's postinstall and only warns. With strict-allow-scripts=true it fails with ESTRICTALLOWSCRIPTS.
      • pnpm 12.9.1 fails with ERR_PNPM_IGNORED_BUILDS.
      • Bun 1.4.2 runs it, because esbuild is on Bun's default trusted list.
    • F9 [sourced] npm v12.0.0 (2026-07-08): "Dependency lifecycle scripts are now blocked by default"; allow-git and allow-remote default to none. (npm/cli v12.0.0, accessed 2026-10-07)
    • F10 [experiment] knip 6.40.0 was about 12 h old at test time (E4):
      • pnpm 12 (defaults), npm with --min-release-age=1 and Bun with minimumReleaseAge=86400 each resolved 6.39.0;
      • default npm and default Bun resolved 6.40.0;
      • an exact pin bypassed pnpm's window.
    • F11 [sourced] "pnpm v10 disables automatic postinstall", and minimumReleaseAge "defaults to 1440" (pnpm supply-chain security). Bun "only runs lifecycle scripts for packages on an allow list" (Bun lifecycle). Both accessed 2026-10-07.
    • F12 [sourced] Trusted publishing "requires npm CLI version 11.5.1 or later", and npm generates provenance automatically on GitHub Actions. (npm trusted publishers, accessed 2026-10-07)

    RQ-2: Toolchain

    • F13 [experiment] Twelve problems seeded into ProductList.vue (E1):

      • script: P1 an unused variable, P2 any, P3 let that should be const, P4 a floating .then, P5 a floating call, P6 ==, P7 debugger;
      • template: T1 v-for without a key, T2 v-html, T3 v-if together with v-for, T4 a duplicate attribute, T5 an undeclared identifier.
      Tool and config Caught Wall time
      ESLint 10.12.0 presets (typescript-eslint 8.71.1 recommendedTypeChecked, eslint-plugin-vue 10.11.1 flat/recommended) 9: P1 P2 P4 P5 P7 T1 T2 (as a warning) T3 T4 3.8–5.4 s
      ESLint plus prefer-const, eqeqeq and no-v-html: error 11: everything except T5 —
      Biome 2.5.15, defaults 4 (P1 P2 P6 P7) plus 2 false positives (template-used variables reported as unused) 0.5–0.7 s
      Biome with full Vue support and the extra rules 7: P1 P2 P6 P7 T1 T3 T4, plus 2 accessibility findings 1.2–2.0 s
      Oxlint 1.87.0, defaults 1: P7 ~1.0 s
      Oxlint with the vue and typescript plugins and --type-aware 3: P2 P6 P7 1.1–1.9 s
    • F14 [experiment] Biome's and Oxlint's floating-promise, unused-variable and prefer-const rules fire in .ts files but not in the SFC. Oxlint's 46 vue/* rules are all script-level, and vue/require-v-for-key is "not found". eslint-plugin-vue loaded through Oxlint's jsPlugins fails with "Use the latest vue-eslint-parser". (E1)

    • F15 [sourced] Oxlint's JS plugins are "currently in alpha", and custom file formats such as Vue are unsupported (Oxlint JS plugins). Type-aware linting needs TypeScript 7.0+ (Oxlint type-aware). Both accessed 2026-10-07.

    • F16 [sourced] Biome's Vue support is experimental and needs html.experimentalFullSupportEnabled. (Biome language support, accessed 2026-10-07, 2.x)

    • F17 [sourced] eslint-plugin-vue "requires vue-eslint-parser" for <template>, with eslint-config-prettier last (eslint-plugin-vue guide). @vue/eslint-config-typescript offers recommendedTypeChecked, and "Type-checking is much slower" (vuejs/eslint-config-typescript). Both accessed 2026-10-07.

    • F18 [experiment] In create-vue 3.24.0's oxlint template, lint:oxlint runs oxlint . --fix and then eslint . --fix --cache. Oxlint is added to ESLint, not a replacement for it. (npm pack create-vue@3.24.0)

    • F19 [inference] From F13-F18: only ESLint with eslint-plugin-vue and typescript-eslint covers template rules and type-aware rules inside SFCs.

    • F20 [experiment] Prettier 3.9.9, oxfmt 0.72.0 and Biome 2.5.15 (with html.formatter.enabled) produced byte-identical output on a messy SFC. (E2)

    • F21 [sourced] Prettier supports Vue out of the box (Prettier docs). oxfmt has been in beta since 2026-02-24 (Oxfmt Beta). Both accessed 2026-10-07.

    • F22 [sourced] TypeScript 7.0 (2026-07-08) is a native port with no API. "We expect TypeScript 7.1 to ship with a new (and different) API", and "Workflows that use Vue … will likely not yet be able to leverage TypeScript 7". (Announcing TypeScript 7.0, accessed 2026-10-07)

    • F23 [sourced] typescript-eslint supports TypeScript >=4.8.4 <6.1.0. (dependency versions, accessed 2026-10-07, 8.x; also confirmed with npm view typescript-eslint peerDependencies)

    • F24 [experiment] typescript@7.0.2 exports only ./lib/version.cjs and ./unstable/*. vue-tsc 3.3.12 on it fails with ERR_PACKAGE_PATH_NOT_EXPORTED './lib/tsc', and tsc 7 silently skips .vue files. (E1, ts7/)

    • F25 [experiment] vue-tsc 3.3.12 on TypeScript 6.0.3 caught TS2339 (T5), TS1117 (T4) and the v-if/v-for scoping error (T3) in 9–11 s. (E1)

    • F26 [sourced] vue-tsc on TypeScript 7.0.2 fails with the ./lib/tsc error. (language-tools #6121, "vue-tsc support for TypeScript 7", accessed 2026-10-07)

    • F27 [verified-code] agent-workflow-tooling already uses npm with a lockfile, Node ≥22, ESLint 10, Prettier 3 and TypeScript 7, with no Vue. (agent-workflow-tooling/package.json:27-28,42,46,47 @ c1d006c)

    • F28 [sourced] stylelint-config-standard-vue parses .vue with postcss-html, needs Stylelint ≥16, and goes last in extends. (stylelint-config-standard-vue, accessed 2026-10-07, 2.0.0)

    • F29 [sourced] Vitest "requires Vite >=v6.4.0 and Node >=v22.12.0" and reuses vite.config.*. (Vitest guide, accessed 2026-10-07, 5.0.3)

    • F30 [sourced] Knip enables its compiler automatically in a Vue project, and finds unused files, unused dependencies and unlisted dependencies. (Knip compilers, accessed 2026-10-07, 6.x)

    RQ-3: CI security

    • F31 [verified-code] CodeQL runs only [actions] (.github/workflows/codeql.yml:24). Actions are pinned by tag at :27, :30 and :36. Dependabot covers only github-actions (.github/dependabot.yml:4-8). git-secrets runs at .githooks/pre-commit:3-4. All in backend @ 38a8548; the other repos are identical.
    • F32 [sourced] CodeQL supports javascript-typescript (including .vue), Go, Rust and Actions (CodeQL languages). Rust became GA on 2025-10-14 (changelog). Both accessed 2026-10-07.
    • F33 [sourced] Dependabot's cooldown applies only to version updates, never to security updates. groups and multi-ecosystem-groups exist. (Dependabot options, accessed 2026-10-07)
    • F34 [sourced] Secret scanning is free and automatic on public repos, but push protection "is disabled by default", and there is no Authorize.Net pattern. (secret scanning; push protection; patterns, accessed 2026-10-07)
    • F35 [sourced] dependency-review-action blocks PRs that add vulnerable dependencies or disallowed licenses, and is free on public repos. Dependabot alerts cover existing vulnerabilities; dependency review stops new ones. (dependency-review-action v5.0.0; about dependency review, accessed 2026-10-07)
    • F36 [sourced] OSV-Scanner reads npm, pnpm and bun lockfiles, go.mod and Cargo.lock, and scans images (OSV-Scanner, v2.6.0). npm audit and pnpm audit query the registry's bulk advisory endpoint (npm audit; pnpm audit). All accessed 2026-10-07.
    • F37 [sourced] A full SHA "is currently the only way to use an action as an immutable release" (secure use). Repository and org policies have been able to require SHA pins since 2025-08-15 (changelog). Both accessed 2026-10-07.
    • F38 [sourced] GHSA-69fq-xp46-6x23 / CVE-2026-33634 (critical), 2026-03-19 to 23: "Trivy ecosystem supply chain temporarily compromised". trivy v0.69.4, its images and setup-trivy were affected, and attackers "force-pushed 76 of 77 version tags" of trivy-action (Trivy advisory). A separate trivy-action script injection was fixed in 0.34.1 (GHSA-9p44-j4g5-cfx5). Both accessed 2026-10-07.
    • F39 [sourced] Grype scans images and SBOMs using EPSS and KEV data (grype, v0.120.1), and scan-action emits SARIF (scan-action, v7.4.2). Docker Scout's free plan covers one repository (Docker pricing). hadolint lints Dockerfiles and runs ShellCheck on their commands (hadolint, v2.15.1). All accessed 2026-10-07.
    • F40 [sourced] zizmor's audits include template-injection, excessive-permissions, unpinned-uses and artipacked (zizmor audits, v1.30.1). CodeQL's Actions queries cover code injection, unpinned tags, permissions and cache poisoning (CodeQL actions). Both accessed 2026-10-07.
    • F41 [sourced] Scorecard's checks include Pinned-Dependencies, Token-Permissions, Branch-Protection and Dangerous-Workflow. (Scorecard checks, v5.5.0, accessed 2026-10-07)
    • F42 [sourced] GitHub artifact attestations are Sigstore-signed at SLSA v1.0 Build L2, free on public repos (artifact attestations). BuildKit adds mode=min provenance by default, and a Syft SBOM with --sbom=true (build attestations). Both accessed 2026-10-07.
    • F43 [inference] From F7 and F31-F42:
      • Tag pins leave the repos open to the attack path used against Trivy (F38); the SHA policy closes it.
      • Push protection covers contributors who never ran .githooks/setup.
      • Dependabot's docker ecosystem has no security updates (F7), so only Grype sees base-image CVEs.
      • Audit tools duplicate Dependabot plus dependency review on npm.

    RQ-4: Container baseline

    • F44 [sourced] node:24-slim is bookworm, a 24-trixie-slim variant exists, and Alpine "does use musl libc" (Docker Hub node; docker-node). golang:alpine is "not officially supported" (golang). All accessed 2026-10-07.

    • F45 [sourced] "Node.js was not designed to run as PID 1"; use --init, the node user, and node directly in CMD. (docker-node Best Practices, accessed 2026-10-07)

    • F46 [sourced] Distroless now builds only on Debian 13: nodejs22, 24 and 26, plus static, base and cc, each with nonroot and debug tags and no shell. (distroless, accessed 2026-10-07)

    • F47 [sourced] Chainguard's free tier gets only :latest; versioned tags are paid. (Chainguard lifecycle, accessed 2026-10-07)

    • F48 [sourced] DHI has been free under Apache 2.0 since 2025-12-17, with SBOM, VEX and SLSA L3; the CVE-fix SLA is paid (DHI blog). Images are non-root and pulled from dhi.io after a login (DHI docs). Both accessed 2026-10-07.

    • F49 [sourced] Runtime behaviour:

      All accessed 2026-10-07.

    • F50 [experiment] Compressed amd64 sizes (E6):

      • node:24-slim 80.8 MB; node:24-trixie-slim 82.5 MB; node:24-alpine 58.2 MB;
      • distroless nodejs24 nonroot 55.3 MB (uid 65532);
      • Chainguard node:latest 65.4 MB (Node 26.10.0);
      • nginx-unprivileged:alpine-slim 8.27 MB;
      • distroless static 0.86 MB.

      None ships tini.

    • F51 [experiment] Distroless run with --read-only --cap-drop ALL --security-opt no-new-privileges (E6):

      • the exec-form HEALTHCHECK reported healthy, and a write to /app failed with EROFS;
      • without --init, docker stop -t 5 took 6.87 s and exited 137 (killed);
      • with --init, it took 0.53 s and exited 143.
    • F52 [inference] From F44-F51: distroless on trixie is the smallest free, non-root, shell-less Node runtime, and it shares glibc with the build stage. Chainguard's free :latest isn't an LTS, DHI needs a login, and Alpine adds musl.

    RQ-5: Quality gate

    • F53 [sourced] SonarQube Cloud gives public OSI-licensed repos the Pro features for free (pricing). For TypeScript, "All versions up to 5.9.3 are supported" (JS/TS docs). Both accessed 2026-10-07.
    • F54 [sourced] Codecov is free for public repos, with PR comments and patch coverage. (Codecov pricing, accessed 2026-10-07)
    • F55 [sourced] Vitest's coverage.thresholds covers lines, functions, branches and statements, per file or per glob. (Vitest coverage, accessed 2026-10-07, 5.0.3)
    • F56 [inference] From F13, F25, F32 and F53-F55: ESLint, vue-tsc, Vitest thresholds and CodeQL already gate defects, types, regressions and security. Sonar duplicates them and supports neither TypeScript 6 nor 7.

    RQ-6: Market signal (small sample)

    • F57 [experiment] 39 unique US postings mentioning Vue or Nuxt (Dice 18, Indeed 21), opened and coded on 2026-10-07 (E5):

      Term Postings
      TypeScript 26 (67%)
      Node/Express/NestJS 22 (56%)
      Python 14 (36%)
      Docker 13
      Java 12 (31%)
      C#/.NET 11 (28%)
      Nuxt 9 (23%)
      Go 3 (8%)
      PHP 3
      Ruby 2
      Rust 0

      18 of the 39 list Vue only as one option alongside React or Angular. In the 21 where Vue is the team's stack, the backends are Node 9, Python 7, .NET 6, Java 4, Go 2 and Rust 0.

    • F58 [experiment] Mobile appears in 3 of 39 postings, always as "preferred"; Ionic in 1 and Capacitor in none. A Dice search for "Vue Ionic Capacitor" showed no Ionic or Capacitor titles in its top 10. (E5)

    • F59 [inference] From F57-F58: a Node/TS backend is the most common pairing with Vue, Go is a minority, and Rust, Ionic and Capacitor show no signal. Use this to break a tie only.

    Current state

    All seven child repos are docs-only, with the same CI scaffolding (F31): CodeQL on actions, Dependabot for github-actions, and git-secrets hooks. The only code in the org is agent-workflow-tooling: npm, ESLint 10, Prettier 3 and TypeScript 7 (F27).

    Sources

    Every source is cited inline in the findings above, with the date accessed (2026-10-07) and the version.

    Experiments ran in a scratch directory on WSL2 with Node v24.21.0, npm 11.19.0 and Docker 29.8.0, with no repository changes:

    • E1, lint and type checking. Installed vue 3.5.43, typescript 6.0.3, vue-tsc 3.3.12, eslint 10.12.0, typescript-eslint 8.71.1, eslint-plugin-vue 10.11.1, vue-eslint-parser 10.4.1, @biomejs/biome 2.5.15, oxlint 1.87.0 and oxlint-tsgolint 7.0.2003.
      • Commands: npx eslint src (with presets, then eslint.tuned.config.js); npx biome lint src (with defaults, then biome.full.json); npx oxlint src (defaults, --type-aware, then oxlint.jsplugins.json); npx vue-tsc --noEmit. In ts7/, npx vue-tsc --noEmit and npx tsc --noEmit --listFilesOnly.
      • Each was timed three times with /usr/bin/time.
    • E2, formatting. prettier --write, oxfmt and biome format, then diff; the outputs were identical. Also inspected create-vue 3.24.0's oxlint template.
    • E3, install scripts. esbuild 0.25.0, installed with npm (with and without strict-allow-scripts), pnpm 12.9.1 and Bun 1.4.2.
    • E4, release age. knip 6.40.0, published 2026-10-06T16:07:13Z and tested at 2026-10-07T04:16Z.
    • E5, job boards, read-only.
      • Dice: "Vue Nuxt TypeScript", "senior Vue developer", "full stack Vue Node", "Nuxt developer", "Vue.js Golang", "Vue.js" (remote) and "Vue Ionic Capacitor".
      • Indeed: "Vue.js developer", "Vue Nuxt", "Nuxt.js", "full stack Vue Node TypeScript" and "Vue Ionic".
      • ZipRecruiter returned titles only, so it isn't counted.
      • 44 detail calls gave 39 unique postings. Posting text was treated as data only.
    • E6, containers. docker pull of each base image, with sizes from docker image ls --tree. A distroless probe with HEALTHCHECK CMD ["/nodejs/bin/node","/app/healthcheck.mjs"], run with docker run -d [--init] --read-only --cap-drop ALL --security-opt no-new-privileges then docker stop -t 5. The probe containers and pulled images were removed.

    workflow · claude-code · 2026-10-07 00:41

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

stage:doneDonetrack:fullNeeds evidence: brief, research, plan, execute, reviewtype:spikeResearch that answers a question and ends there

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions