From f3960b1478452a6959be79c651babd372b4959b2 Mon Sep 17 00:00:00 2001 From: Luke Seers <16389426+LukeSeers@users.noreply.github.com> Date: Fri, 14 Aug 2026 15:21:25 +0100 Subject: [PATCH 1/2] feat: add support for devcontainer --- .devcontainer/Dockerfile | 9 ++++++ .devcontainer/devcontainer.json | 20 ++++++++++++ .devcontainer/setup.sh | 56 +++++++++++++++++++++++++++++++++ .husky/pre-commit | 33 +++++++++++++++++++ 4 files changed, 118 insertions(+) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/setup.sh diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..b095a6df9 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,9 @@ +FROM library/node:lts-bookworm + +ARG DEBIAN_FRONTEND=noninteractive +RUN apt update \ + && apt install -y --no-install-recommends sudo \ + && apt autoremove -y \ + && rm -rf /var/lib/apt/lists/* \ + && echo "node ALL=(ALL) NOPASSWD: ALL" >/etc/sudoers.d/node \ + && chmod 0440 /etc/sudoers.d/node diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..b5fbb5f65 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,20 @@ +{ + "name": "SonicJS Dev", + "build": { "dockerfile": "Dockerfile" }, + "forwardPorts": [9115], + "remoteUser": "node", + "mounts": [ + "source=sonicjs-node_modules,target=/IdeaProjects/sonicjs/node_modules,type=volume", + "source=sonicjs-core-node_modules,target=/IdeaProjects/sonicjs/packages/core/node_modules,type=volume", + "source=sonicjs-create-app-node_modules,target=/IdeaProjects/sonicjs/packages/create-app/node_modules,type=volume", + "source=sonicjs-stats-node_modules,target=/IdeaProjects/sonicjs/packages/stats/node_modules,type=volume", + "source=sonicjs-www-node_modules,target=/IdeaProjects/sonicjs/www/node_modules,type=volume", + "source=sonicjs-my-app-node_modules,target=/IdeaProjects/sonicjs/my-sonicjs-app/node_modules,type=volume" + ], + "postCreateCommand": "bash .devcontainer/setup.sh", + "customizations" : { + "jetbrains" : { + "backend" : "WebStorm" + } + } +} diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh new file mode 100644 index 000000000..e41f2fc17 --- /dev/null +++ b/.devcontainer/setup.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Devcontainer postCreate bootstrap. +# +# Bind-mounted workspaces inherit the host's file ownership. If the checkout +# was created by a root process, npm (running here as the `node` user) fails +# with `EPERM: operation not permitted, chmod` when it links workspace `bin` +# files (e.g. packages/core/bin/db-reset.js). This script normalises ownership +# so `npm install` works in the container without touching anything that +# Conductor or other CI flows rely on. + +# Allow git to read the mount even if `.git` is owned by root. +git config --global --add safe.directory '*' || true + +# Locate the repo root regardless of where the workspace is mounted. +REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "$PWD")" + +# Make the whole workspace writable/ownable by the container user. +if ! sudo chown -R node:node "$REPO_ROOT"; then + echo "WARNING: could not chown '$REPO_ROOT' to node:node —" \ + "npm install may fail with EPERM on bin files" >&2 +fi + +# Install dependencies so the workspace is immediately runnable. +# +# `node_modules` lives on Linux-native Docker volumes (see `mounts` in +# devcontainer.json) so npm never reifies against the host bind mount. On +# Docker Desktop + WSL2 (and some other bind mounts), `npm install` fails +# transiently with `EACCES: permission denied, rename node_modules/caniuse-lite` +# during npm's reify phase — npm renames directories while rebuilding the tree, +# and the 9p/drvfs mount intermittently rejects the rename, especially when a +# second install (e.g. one triggered by the IDE) is reifying the same tree +# concurrently. Ownership is fine; the failure is non-deterministic. +# +# Run unconditionally: install is idempotent and fast when the tree is already +# up to date, and re-running repairs any partially-installed state that the +# bind-mount failures left behind. +install_with_retry() { + local attempts=4 + local attempt=1 + while true; do + if npm --prefix "$REPO_ROOT" install; then + return 0 + fi + if (( attempt >= attempts )); then + echo "npm install failed after $attempts attempts" >&2 + return 1 + fi + echo "npm install failed (attempt $attempt/$attempts) — retrying..." >&2 + sleep 3 + attempt=$((attempt + 1)) + done +} + +install_with_retry diff --git a/.husky/pre-commit b/.husky/pre-commit index 8ead53543..807e26e1c 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,3 +1,36 @@ +#!/usr/bin/env sh + +# Resolve npm even when PATH is minimal or comes from a host-side git that +# knows nothing about the devcontainer (node_modules + npm live inside the +# container, on Docker volumes). The container installs node at /usr/local/bin; +# nvm/fnm/asdf users have shims under $HOME. +resolve_npm() { + command -v npm >/dev/null 2>&1 && return 0 + for dir in /usr/local/bin "$HOME/.local/bin" "$HOME/.nvm" "$HOME/.fnm" "$HOME/.asdf"; do + [ -x "$dir/npm" ] && { PATH="$dir:$PATH"; export PATH; return 0; } + for v in "$dir"/versions/node/*/bin; do + [ -x "$v/npm" ] && { PATH="$v:$PATH"; export PATH; return 0; } + done + done + return 1 +} + +if ! resolve_npm; then + echo "WARNING: 'npm' not found on PATH." + echo "This repo's checks run inside the devcontainer (npm/node_modules live on" + echo "Docker volumes). Commits made from the host cannot run lint/type-check, so" + echo "they are skipped here — CI enforces them on every push." + exit 0 +fi + +# Even when an npm binary exists, node_modules may still be hidden behind the +# container's Docker volumes from the host's point of view. +if [ ! -f "$(dirname "$0")/../node_modules/.package-lock.json" ]; then + echo "WARNING: node_modules is not installed in this environment." + echo "Skipping lint/type-check for this commit — CI enforces them on every push." + exit 0 +fi + echo "Running linting..." npm run lint --workspace=@sonicjs-cms/core From cf1347d0d4e7ae4de9a1e1afa41312e5cda2618a Mon Sep 17 00:00:00 2001 From: Luke Seers <16389426+LukeSeers@users.noreply.github.com> Date: Fri, 14 Aug 2026 15:23:41 +0100 Subject: [PATCH 2/2] docs: update readme contributing section --- README.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 351c0b0f8..5a3ebdab4 100644 --- a/README.md +++ b/README.md @@ -141,25 +141,28 @@ Your app includes: ### For Package Developers (Contributing to SonicJS) -```bash -# Clone this repository -git clone https://github.com/lane711/sonicjs.git -cd sonicjs +> Optionally, you can use our preconfigured [Devcontainer](https://containers.dev/) so the environment is identical +> for every local machine — no manual Node/Cloudflare setup required. You just need +> Docker and VS Code (or a JetBrains IDE with remote development support). + +This sets up a development environment with all dependencies installed and ready to go. +To see your changes live, go to http://localhost:9115 +```bash # Install dependencies npm install # Build the core package npm run build:core -# Create a test app to validate changes -npx create-sonicjs@latest my-sonicjs-app +# Prep the demo database +npm run db:reset -# Run tests -npm test +# Run the demo app +npm run dev # open http://localhost:9115/ ``` -#### Setting Up a Fresh Database +### Setting Up a Fresh Database ```bash # Create a fresh D1 database for your branch (run from project root)