diff --git a/.agents/bin/README.md b/.agents/bin/README.md index 0885878..1bc277e 100644 --- a/.agents/bin/README.md +++ b/.agents/bin/README.md @@ -11,7 +11,7 @@ means that capability is n/a here. | `validate` | Pre-push gate | JavaScript and Tailwind CSS builds, production Shakapacker compilation, then `bin/rails test` | | `test` | Run tests | `bin/rails test` | | `lint` | Lint / format | n/a | -| `build` | Build / type-check | `bin/yarn build`, `bin/yarn build:css`, then `RAILS_ENV=production bin/shakapacker` | +| `build` | Build / type-check | JavaScript and Tailwind builds, React on Rails pack generation, then production Shakapacker compilation | | `docs` | Validate documentation | n/a | | `ci-detect` | Detect CI impact | n/a | diff --git a/.agents/bin/build b/.agents/bin/build index 463aa91..4d89345 100755 --- a/.agents/bin/build +++ b/.agents/bin/build @@ -3,5 +3,6 @@ set -euo pipefail cd "$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)" bin/yarn build -bin/yarn build:css +bin/rails tailwindcss:build +bin/shakapacker-precompile-hook exec env RAILS_ENV=production bin/shakapacker "$@" diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b86c026 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,19 @@ +.git +.github +.agents +.bundle +.env +.env.* +config/master.key +config/credentials/*.key +db/*.sqlite3 +log/* +storage/* +tmp/* +node_modules +vendor/bundle +app/assets/builds/* +public/assets +public/packs +public/packs-test +coverage diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..1cf8253 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.4.6 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d72bad3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,97 @@ +ARG RUBY_VERSION=3.4.6 + +# Node and Ruby base images share the same Debian release so that glibc, libssl, and +# other system libraries line up between stages. +FROM docker.io/library/node:22-bookworm-slim AS node +FROM ruby:$RUBY_VERSION-slim-bookworm + +# Yarn Classic / pnpm fallbacks for projects that haven't adopted corepack via +# `packageManager` in package.json. Override at build time (`--build-arg=YARN_CLASSIC_VERSION=...`) +# rather than editing this file so regenerating the Dockerfile keeps your pin. +ARG YARN_CLASSIC_VERSION=1.22.22 +ARG PNPM_FALLBACK_VERSION=9.12.3 + +WORKDIR /app + +# Keep Node.js available both for asset compilation and for SSR runtimes that +# rely on ExecJS in production. Narrowed to just what the node stage actually +# ships under /usr/local so we don't drag in unused Debian libs from that image. +COPY --from=node /usr/local/bin/node /usr/local/bin/node +COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules +COPY --from=node /usr/local/include/node /usr/local/include/node + +RUN ln -sf ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \ + ln -sf ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx && \ + ln -sf ../lib/node_modules/corepack/dist/corepack.js /usr/local/bin/corepack && \ + chmod +x /usr/local/lib/node_modules/npm/bin/npm-cli.js \ + /usr/local/lib/node_modules/npm/bin/npx-cli.js \ + /usr/local/lib/node_modules/corepack/dist/corepack.js && \ + node --version && npm --version && corepack --version + +# Expose Corepack-managed shims so later build steps can call yarn/pnpm +# directly during asset precompilation hooks. +RUN printf '%s\n' '#!/bin/sh' 'exec corepack yarn "$@"' > /usr/bin/yarn && \ + chmod +x /usr/bin/yarn && \ + printf '%s\n' '#!/bin/sh' 'exec corepack pnpm "$@"' > /usr/bin/pnpm && \ + chmod +x /usr/bin/pnpm + +# install ruby gems +COPY Gemfile* ./ + +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y build-essential && \ + bundle config set without 'development test' && \ + bundle config set with 'production' && \ + bundle install --jobs=3 --retry=3 && \ + apt-get purge -y --auto-remove build-essential && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + +COPY . ./ + +# Install JavaScript dependencies only when the project actually has them. +# Pin `packageManager` in package.json to take the corepack path and avoid the +# YARN_CLASSIC_VERSION / PNPM_FALLBACK_VERSION fallbacks below; the fallbacks exist for +# projects that haven't adopted corepack and should be reviewed periodically as they go stale. +RUN if [ -f package.json ]; then \ + package_manager="$(node -p "require('./package.json').packageManager || ''")"; \ + if [ -f yarn.lock ]; then \ + if printf '%s' "$package_manager" | grep -q '^yarn@'; then \ + corepack prepare "$package_manager" --activate && \ + (corepack yarn install --immutable || corepack yarn install --frozen-lockfile); \ + else \ + npm install -g "yarn@${YARN_CLASSIC_VERSION}" && \ + (yarn install --immutable || yarn install --frozen-lockfile); \ + fi; \ + elif [ -f pnpm-lock.yaml ]; then \ + if printf '%s' "$package_manager" | grep -q '^pnpm@'; then \ + corepack prepare "$package_manager" --activate && \ + corepack pnpm install --frozen-lockfile; \ + else \ + corepack prepare "pnpm@${PNPM_FALLBACK_VERSION}" --activate && \ + corepack pnpm install --frozen-lockfile; \ + fi; \ + elif [ -f package-lock.json ]; then \ + npm ci; \ + else \ + npm install; \ + fi; \ + fi + +ENV RAILS_ENV=production +ENV RAILS_SERVE_STATIC_FILES=true + +# Compiling assets requires any non-blank value, but the placeholder must not +# remain in the final image environment. +RUN SECRET_KEY_BASE=NOT_USED_NON_BLANK bin/shakapacker-precompile-hook + + +RUN SECRET_KEY_BASE=NOT_USED_NON_BLANK rails assets:precompile + +# add entrypoint +COPY bin/docker-entrypoint /app/entrypoint.sh +RUN chmod +x /app/entrypoint.sh +ARG GIT_COMMIT=unknown +ENV GIT_COMMIT=$GIT_COMMIT +ENTRYPOINT ["/app/entrypoint.sh"] + +CMD ["rails", "s"] diff --git a/Gemfile b/Gemfile index ed96902..64ded9b 100644 --- a/Gemfile +++ b/Gemfile @@ -13,6 +13,8 @@ gem "mutex_m" # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] gem "sprockets-rails" +# Sprockets 4.2 still passes JSON options removed in JSON 3. +gem "json", "< 3" # Use sqlite3 as the database for Active Record gem "sqlite3", "~> 2.9" @@ -27,7 +29,7 @@ gem "jbuilder" gem "mini_racer", "~> 0.22.1" # The library which provides react helpers and more in rails -gem "react_on_rails", "17.2.0.rc.0" +gem "react_on_rails", "17.2.0" # Modern javscript compressor gem "terser" diff --git a/Gemfile.lock b/Gemfile.lock index 8f434aa..4671667 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -122,7 +122,7 @@ GEM sorbet-runtime jsbundling-rails (1.3.1) railties (>= 6.0.0) - json (3.0.2) + json (2.21.2) libv8-node (24.12.0.1-arm64-darwin) libv8-node (24.12.0.1-x86_64-linux) logger (1.7.0) @@ -226,7 +226,7 @@ GEM prism (>= 1.6.0) rbs (>= 4.0.0) tsort - react_on_rails (17.2.0.rc.0) + react_on_rails (17.2.0) addressable connection_pool execjs (~> 2.5) @@ -309,13 +309,14 @@ DEPENDENCIES jbuilder js-routes jsbundling-rails (~> 1.3) + json (< 3) mini_racer (~> 0.22.1) minitest (< 7) mutex_m pry-remote puma (~> 8.0) rails (~> 8.1.4) - react_on_rails (= 17.2.0.rc.0) + react_on_rails (= 17.2.0) selenium-webdriver shakapacker (= 10.3.2) sprockets-rails diff --git a/README.md b/README.md index 01d2fdf..ea259af 100644 --- a/README.md +++ b/README.md @@ -23,3 +23,18 @@ This repository is a migrated version of [ganchdev/react-rails-example](https:// ## Notes - This app is intentionally minimal and focused on migration outcomes. - Generated React on Rails files are kept in-repo so users can inspect exact changes after migration. + +## Temporary Verification + +Run `bin/verify-container` to build the production image and check server-rendered +HTML on `/`, `/posts/0`, and `/hello_world`. Docker must be running. The command +uses a random signing key, an ephemeral SQLite database, and a random localhost +port. It removes its container and image tag on success, failure, or interruption. +No cloud app, persistent volume, registry upload, or permanent endpoint is created. + +The image prepares SQLite automatically only for direct `rails server` / `rails s` +commands, including `bin/rails`. Pass runtime overrides with Docker's `--env` +flags. Custom wrapped commands must prepare their database explicitly. + +For interactive browser checks, use `bin/dev` and stop it afterward. This repository +has no deployment workflows, automatic deployments, or permanent staging app. diff --git a/app/javascript/src/Navbar/ror_components/Navbar.client.jsx b/app/javascript/src/Navbar/ror_components/Navbar.client.jsx new file mode 100644 index 0000000..c7eb39e --- /dev/null +++ b/app/javascript/src/Navbar/ror_components/Navbar.client.jsx @@ -0,0 +1,3 @@ +import Navbar from '../../components/Navbar'; + +export default Navbar; diff --git a/app/javascript/src/Navbar/ror_components/Navbar.server.jsx b/app/javascript/src/Navbar/ror_components/Navbar.server.jsx new file mode 100644 index 0000000..f91e1a2 --- /dev/null +++ b/app/javascript/src/Navbar/ror_components/Navbar.server.jsx @@ -0,0 +1,3 @@ +import Navbar from './Navbar.client'; + +export default Navbar; diff --git a/app/javascript/src/PostsIndex/ror_components/PostsIndex.client.jsx b/app/javascript/src/PostsIndex/ror_components/PostsIndex.client.jsx new file mode 100644 index 0000000..3e9ce9a --- /dev/null +++ b/app/javascript/src/PostsIndex/ror_components/PostsIndex.client.jsx @@ -0,0 +1,3 @@ +import PostsIndex from '../../pages/PostsIndex'; + +export default PostsIndex; diff --git a/app/javascript/src/PostsIndex/ror_components/PostsIndex.server.jsx b/app/javascript/src/PostsIndex/ror_components/PostsIndex.server.jsx new file mode 100644 index 0000000..729577b --- /dev/null +++ b/app/javascript/src/PostsIndex/ror_components/PostsIndex.server.jsx @@ -0,0 +1,3 @@ +import PostsIndex from './PostsIndex.client'; + +export default PostsIndex; diff --git a/app/javascript/src/PostsShow/ror_components/PostsShow.client.jsx b/app/javascript/src/PostsShow/ror_components/PostsShow.client.jsx new file mode 100644 index 0000000..164bb24 --- /dev/null +++ b/app/javascript/src/PostsShow/ror_components/PostsShow.client.jsx @@ -0,0 +1,3 @@ +import PostsShow from '../../pages/PostsShow'; + +export default PostsShow; diff --git a/app/javascript/src/PostsShow/ror_components/PostsShow.server.jsx b/app/javascript/src/PostsShow/ror_components/PostsShow.server.jsx new file mode 100644 index 0000000..f342d72 --- /dev/null +++ b/app/javascript/src/PostsShow/ror_components/PostsShow.server.jsx @@ -0,0 +1,3 @@ +import PostsShow from './PostsShow.client'; + +export default PostsShow; diff --git a/app/javascript/src/components/Navbar.jsx b/app/javascript/src/components/Navbar.jsx index 8615b56..19241b9 100644 --- a/app/javascript/src/components/Navbar.jsx +++ b/app/javascript/src/components/Navbar.jsx @@ -6,9 +6,10 @@ import Card from "./Card"; /** * @param {String} menus * @param {String} homeUrl + * @param {Number} avatarSeed * @return {JSX.Element} */ -function Navbar({ menus, homeUrl }) { +function Navbar({ menus, homeUrl, avatarSeed = 0 }) { return (
{parsedPost.content}
@@ -18,6 +18,6 @@ function PostsShow({ post }) { PostsShow.propTypes = { post: PropTypes.string -} +}; export default PostsShow; diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 988f871..cfef32b 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -6,18 +6,17 @@ <%= csrf_meta_tags %> <%= csp_meta_tag %> - <%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %> + <%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %> <%= stylesheet_link_tag "application" %> <%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %> - <%= javascript_pack_tag "application" %> <%= react_component( "Navbar", - { - menus: JSON.generate([{name: "Posts", href: root_path}]), + props: { + menus: JSON.generate([{ name: "Posts", href: root_path }]), homeUrl: root_path }, prerender: true) @@ -26,5 +25,6 @@