Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions hub/.devcontainer/cli.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ FROM node:24-bookworm-slim
COPY --from=docker /usr/local/bin/docker /usr/local/bin/docker
COPY --from=docker /usr/local/libexec/docker /usr/local/libexec/docker

ARG NPM_CONFIG_REGISTRY=https://registry.npmjs.org/

RUN npm install --global @devcontainers/cli@0.89.0

ENTRYPOINT ["devcontainer"]
3 changes: 2 additions & 1 deletion hub/.devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/node:1": {
"version": "24"
"version": "24",
"pnpmVersion": "none"
}
},
"forwardPorts": [19500, 3036, 6006],
Expand Down
69 changes: 61 additions & 8 deletions hub/bin/devcontainer
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ CLI_IMAGE="replica-hub-devcontainer-cli:0.89.0"

usage() {
printf '%s\n' \
"Usage: bin/devcontainer COMMAND [arguments]" \
"Usage: bin/devcontainer COMMAND [--use-npm-proxy URL] [arguments]" \
"" \
"Commands:" \
" up Create or start the development container" \
Expand All @@ -16,11 +16,18 @@ usage() {
" stop Stop the development container" \
" exec COMMAND... Run a command in the container" \
" shell Open a shell in the container" \
" down Remove the development container"
" down Remove the development container" \
"" \
"Options for up, rebuild, dev, storybook, exec, and shell:" \
" --use-npm-proxy URL Use an HTTP(S) npm registry proxy without credentials"
}

ensure_cli_image() {
if ! docker image inspect "$CLI_IMAGE" >/dev/null 2>&1; then
if [ -n "$npm_proxy" ]; then
CLI_IMAGE="$CLI_IMAGE-npm-proxy"
docker build --build-arg "NPM_CONFIG_REGISTRY=$npm_proxy" \
--tag "$CLI_IMAGE" --file "$APP_ROOT/.devcontainer/cli.Dockerfile" "$APP_ROOT/.devcontainer"
elif ! docker image inspect "$CLI_IMAGE" >/dev/null 2>&1; then
docker build --tag "$CLI_IMAGE" --file "$APP_ROOT/.devcontainer/cli.Dockerfile" "$APP_ROOT/.devcontainer"
fi
}
Expand All @@ -35,7 +42,14 @@ run_cli() {
"$CLI_IMAGE" "$@"
}

up() { run_cli up --workspace-folder "$APP_ROOT"; }
up() {
if [ -n "$npm_proxy" ]; then
run_cli up --workspace-folder "$APP_ROOT" --remote-env "NPM_CONFIG_REGISTRY=$npm_proxy" "$@"
run_cli exec --workspace-folder "$APP_ROOT" npm config set registry "$npm_proxy" --location=user
else
run_cli up --workspace-folder "$APP_ROOT" "$@"
fi
}

exec_in_container() {
if [ "${REPLICA_LOCAL_MODE+x}" = x ]; then
Expand All @@ -60,12 +74,53 @@ clear_server_pid() {

command=${1:-help}
[ "$#" -eq 0 ] || shift
npm_proxy=""
if [ "${1:-}" = --use-npm-proxy ]; then
case "$command" in
up|rebuild|dev|storybook|exec|shell) ;;
*) usage >&2; exit 2 ;;
esac
if [ "$#" -lt 2 ]; then
printf '%s\n' "--use-npm-proxy requires an HTTP(S) npm registry URL" >&2
exit 2
fi
npm_proxy=$2
proxy_host=""
case "$npm_proxy" in
http://*|https://*)
proxy_host=${npm_proxy#*://}
proxy_host=${proxy_host%%/*}
;;
esac
case "$npm_proxy" in
*[[:space:]]*|*\?*|*\#*) proxy_host="" ;;
esac
case "$proxy_host" in
""|*@*)
printf '%s\n' "--use-npm-proxy requires an HTTP(S) registry URL without credentials, whitespace, a query, or a fragment" >&2
exit 2
;;
esac
shift 2
fi

case "$command" in
up|rebuild|dev|storybook|shell|stop|down|help|-h|--help)
[ "$#" -eq 0 ] || { usage >&2; exit 2; }
;;
exec) [ "$#" -gt 0 ] || { usage >&2; exit 2; } ;;
*) usage >&2; exit 2 ;;
esac

case "$command" in
help|-h|--help) usage; exit 0 ;;
esac
ensure_cli_image

case "$command" in
up) up ;;
rebuild)
run_cli up --workspace-folder "$APP_ROOT" --remove-existing-container
up --remove-existing-container
clear_server_pid
;;
dev)
Expand All @@ -85,7 +140,7 @@ case "$command" in
exec_in_container bin/dev
;;
storybook) up >/dev/null; exec_in_container npm run storybook ;;
exec) [ "$#" -gt 0 ] || { usage >&2; exit 2; }; up >/dev/null; exec_in_container "$@" ;;
exec) up >/dev/null; exec_in_container "$@" ;;
shell) up >/dev/null; exec_in_container bash ;;
stop)
ids=$(container_ids)
Expand All @@ -97,6 +152,4 @@ case "$command" in
[ -z "$ids" ] || docker rm --force $ids
clear_server_pid
;;
help|-h|--help) usage ;;
*) usage >&2; exit 2 ;;
esac
21 changes: 21 additions & 0 deletions hub/docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Docker-compatible tooling is the only host requirement. Run these commands from `hub/`. The provided devcontainer provides Ruby, Node, SQLite, Git, GitHub CLI, and Chromium.

Hub uses npm. The Node devcontainer feature's optional pnpm installation is disabled.

## Start and stop

```text
Expand Down Expand Up @@ -32,6 +34,25 @@ For Copilot plugin development, follow the [contributor workflow](prototype-canv

Use `rebuild` after changing the container definition, `stop` to stop the container, and `down` to remove it.

### Use an npm registry proxy

If your network requires an npm registry proxy, pass its URL when starting the container:

```sh
bin/devcontainer up --use-npm-proxy https://npm-proxy.example.com/npm/
bin/devcontainer dev
```

The option also works with `dev`, `rebuild`, `storybook`, `exec`, and `shell`; place it before any `exec` command. It selects the registry for the CLI image's npm installation and the container's initial dependency setup. After startup succeeds, it saves the registry in the container user's npm configuration, so later npm commands, including CI's `npm ci`, use it without repeating the option. It also updates an existing container without rebuilding or removing it.

The registry must serve the locked dependencies for Hub and `packages/learning/`, plus `starter/` and `actions/build/` for local mode. `up` installs the first two; the first local-mode `dev` also prepares the example and shared build dependencies.

Use an HTTP(S) registry URL without credentials, whitespace, a query, or a fragment. This option does not configure an HTTP network proxy or disable TLS verification. It does not change the host's npm settings, repository `.npmrc` files, lockfiles, or the production image. Without the option, the wrapper leaves the registry configuration unchanged.

Tools that fetch registry URLs directly do not use npm's settings. In particular, `bin/check-dependency-age` still requests publication metadata from the public registries.

To remove the saved override, run `bin/devcontainer exec npm config delete registry --location=user`. Removing the container also removes its saved registry setting.

Restart `bin/devcontainer dev` after changing Rails routes or browser scripts served directly by Rails. Restart Vite when browser assets do not match the saved source.

The production image includes shared Controls source from `app/javascript/utils/prototype-controls/`. It also installs `replica-learning` from `packages/learning/`, with Node.js, under `/opt/replica/learning`, because Hub runs that command to reach Copilot. From `hub/`, build it with `docker build --build-context learning=../packages/learning --tag replica-hub:local .`. CI and release builds supply that same narrow named context. The final image keeps `/rails` as its application directory and `/rails/storage` as its persistent path.
Expand Down
Loading
Loading