Skip to content

Conversation

@lifeiscontent
Copy link
Collaborator

@lifeiscontent lifeiscontent commented Aug 19, 2025

Description

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring
  • Performance improvements
  • Documentation update

Screenshots and Media (if applicable)

Test Scenarios

References

Summary by CodeRabbit

  • New Features

    • Introduced a CLI to run local Docker-based smoke tests for web, admin, space, and live apps with configurable ports/paths and selective services.
    • Added a build benchmarking tool to compare cold vs warm Docker builds and report timings and image sizes.
  • Tests

    • Automated smoke tests in CI run per app via a dynamic matrix based on changed paths, with logs, readiness checks, and automatic cleanup.
  • Chores

    • New CI workflow with path-based triggers, conditional execution, concurrency control, and timeouts for reliable PR and push validation.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 19, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a Docker smoke-test GitHub Actions workflow that dynamically selects app targets based on changed paths, plus two Bash scripts: one to benchmark Docker builds (cold vs warm) and another to build, run, and probe containerized Next.js apps (web, admin, space, live) with up/down lifecycle and readiness checks.

Changes

Cohort / File(s) Summary
CI: Docker smoke workflow
.github/workflows/docker-smoke.yml
New workflow using dorny/paths-filter to compute a per-app test matrix (web/admin/space/live) and run smoke tests via scripts/smoke.sh; includes triggers, permissions, concurrency, and conditional execution based on changed paths.
Tooling: Build benchmarking
scripts/bench-builds.sh
New Bash script to benchmark cold vs warm Docker builds for web/admin/space/live; measures times, image sizes, cache usage; supports pruning modes, verbosity, and service selection.
Tooling: Smoke test harness
scripts/smoke.sh
New Bash script to build images, run containers on configured ports, probe readiness URLs, and clean up for web/admin/space/live; supports up/down commands, selective services, and environment overrides.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Developer/PR
  participant GA as GitHub Actions
  participant PF as paths-filter
  participant GS as github-script
  participant SM as scripts/smoke.sh
  participant DK as Docker

  Dev->>GA: Push/PR triggers workflow
  GA->>PF: Detect changed paths
  PF-->>GA: Changed components map
  GA->>GS: Build matrix (targets with config)
  alt has_targets
    GA->>SM: smoke up (per target)
    SM->>DK: build images (DOCKER_BUILDKIT=1)
    SM->>DK: run containers (port mapping)
    SM->>SM: probe readiness (HTTP 2xx/3xx)
    alt failure
      SM->>DK: fetch container logs
      SM-->>GA: non-zero exit
    else success
      SM-->>GA: success for target
    end
    GA->>SM: smoke down (cleanup)
  else no targets
    GA-->>Dev: Skip smoke job
  end
Loading
sequenceDiagram
  autonumber
  actor Eng as Engineer
  participant BB as scripts/bench-builds.sh
  participant DK as Docker
  participant BX as buildx (optional)

  Eng->>BB: Run with selected services
  BB->>DK: Validate Docker availability
  opt prune (unless disabled)
    BB->>BX: buildx prune/du (if available)
    BB->>DK: docker system prune/df (fallback)
  end
  loop per service
    BB->>DK: Cold build (no-cache) -> time, size
    BB->>DK: Warm build (with cache) -> time
  end
  BB-->>Eng: Summary table (times, sizes, cache notes)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • sriramveeraghanta
  • prateekshourya29

Poem

I thump my paw: containers rise,
Four apps puff steam beneath the skies.
Smoke says “ready!”—logs say “fine!”
Benchmarks tick in tidy time.
Ports all mapped, the burrow’s bright—
Hop, ship, nibble—green checks tonight! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description is empty aside from the template placeholders and does not include any of the required information such as a detailed description, selected type of change, test scenarios, or references, making it largely incomplete. Please fill out the PR description by providing a clear summary of the changes, marking the appropriate type of change, describing the tests performed, and adding any relevant issue or reference links as per the template.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title succinctly states the addition of Docker smoke tests and correctly reflects the primary focus of the changes without unnecessary detail or noise.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/docker-frozen-lock

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01dd81c and 8b1d431.

📒 Files selected for processing (1)
  • .github/workflows/docker-smoke.yml (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build and smoke test live
  • GitHub Check: Build and smoke test admin
  • GitHub Check: Build and smoke test space
  • GitHub Check: Build and smoke test web

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@lifeiscontent lifeiscontent force-pushed the feat/docker-frozen-lock branch from 74c8564 to 62d51c9 Compare August 19, 2025 22:34
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (15)
apps/live/Dockerfile.live (4)

57-59: Align user/group and make group explicit

Minor hardening/nit:

  • Ensure the nextjs user is in the nodejs group so UID/GID pairing is explicit.
  • Optionally set USER by uid:gid for clarity.

Proposed change:

 RUN addgroup --system --gid 1001 nodejs
-RUN adduser --system --uid 1001 nextjs
+RUN adduser --system --uid 1001 -G nodejs nextjs
 ...
-USER nextjs
+USER 1001:1001

Also applies to: 70-70


61-63: Consider pruning to production-only dependencies for a slimmer runtime

Currently, node_modules from the installer stage likely includes dev deps. For this non-Next runtime, you can reduce image size by installing/pruning production-only deps in the installer stage before copying.

Add this in the installer stage (outside this range):

# Prefer production-only deps in the final artifact
# Option A: install with prod-only directly
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
  pnpm install --offline --frozen-lockfile --prod --store-dir=/pnpm/store

# Option B: if you need dev deps for build, then prune after build:
# RUN pnpm turbo run build --filter=live
# RUN pnpm prune --prod

65-66: Set NODE_ENV=production in runner

Adds sensible defaults and can improve performance.

 ENV TURBO_TELEMETRY_DISABLED=1
+ENV NODE_ENV=production
 ENV NEXT_TELEMETRY_DISABLED=1

68-73: Optional: Add a container HEALTHCHECK

Useful for local smoke tests and orchestrators.

 EXPOSE 3000

 USER 1001:1001

 ENTRYPOINT ["dumb-init", "--"]
+HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=5 \
+  CMD wget -qO- http://localhost:3000/live/health > /dev/null || exit 1
 CMD ["node", "live/server.js"]
apps/admin/Dockerfile.admin (3)

79-83: Make nextjs a member of nodejs group; ensure explicit uid:gid

Small hardening and clarity improvement.

 RUN addgroup --system --gid 1001 nodejs
-RUN adduser --system --uid 1001 nextjs
+RUN adduser --system --uid 1001 -G nodejs nextjs
 # Ensure writable Next.js cache directory
 RUN mkdir -p /app/apps/admin/.next/cache/fetch-cache && chown -R 1001:1001 /app/apps/admin/.next
-USER nextjs
+USER 1001:1001

109-111: Set NODE_ENV=production in runner

Helps ensure Next.js runs optimized for production.

 ENV NEXT_TELEMETRY_DISABLED=1
 ENV TURBO_TELEMETRY_DISABLED=1
+ENV NODE_ENV=production

112-116: Optional: Add a HEALTHCHECK

Simple HTTP probe to the admin base path.

 EXPOSE 3000

 ENTRYPOINT ["dumb-init", "--"]
+HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=5 \
+  CMD wget -qO- http://localhost:3000/god-mode > /dev/null || exit 1
 CMD ["node", "apps/admin/server.js"]
apps/space/Dockerfile.space (3)

80-82: Ensure nextjs joins nodejs group and use explicit uid:gid

Keeps ownership semantics clear and consistent across files.

 RUN addgroup --system --gid 1001 nodejs
-RUN adduser --system --uid 1001 nextjs
+RUN adduser --system --uid 1001 -G nodejs nextjs
 ...
-USER nextjs
+USER 1001:1001

Also applies to: 92-92


112-114: Set NODE_ENV=production in runner

Toggles Next.js into production mode explicitly.

 ENV NEXT_TELEMETRY_DISABLED=1
 ENV TURBO_TELEMETRY_DISABLED=1
+ENV NODE_ENV=production

115-120: Optional: Add a HEALTHCHECK

Basic liveness for the space app:

 EXPOSE 3000

 ENTRYPOINT ["dumb-init", "--"]
+HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=5 \
+  CMD wget -qO- http://localhost:3000/spaces > /dev/null || exit 1

 CMD ["node", "apps/space/server.js"]
apps/web/Dockerfile.web (2)

129-131: Set NODE_ENV=production in runner

Explicitly set production mode for Next.js runtime.

 ENV NEXT_TELEMETRY_DISABLED=1
 ENV TURBO_TELEMETRY_DISABLED=1
+ENV NODE_ENV=production

132-136: Optional: Add a HEALTHCHECK

Adds a basic HTTP liveness probe to the root path.

 EXPOSE 3000

 ENTRYPOINT ["dumb-init", "--"]
+HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=5 \
+  CMD wget -qO- http://localhost:3000/ > /dev/null || exit 1
 CMD ["node", "apps/web/server.js"]
.github/workflows/docker-smoke.yml (3)

45-58: Paths filter patterns: drop leading './' for consistency and to avoid false negatives

Some tools interpret leading './' differently. Using repo-relative globs without './' aligns with the trigger filters above and reduces risk of mismatches.

           filters: |
             web:
-              - './apps/web/**'
+              - 'apps/web/**'
             space:
-              - './apps/space/**'
+              - 'apps/space/**'
             admin:
-              - './apps/admin/**'
+              - 'apps/admin/**'
             live:
-              - './apps/live/**'
+              - 'apps/live/**'
             common:
               - 'turbo.json'
               - 'pnpm-lock.yaml'
               - 'pnpm-workspace.yaml'
               - '.github/workflows/docker-smoke.yml'

105-109: Optionally pass build args to make smoke builds configurable

If you ever need to validate base paths/URLs in smoke builds, wire through the NEXT_PUBLIC_* args. Defaults keep current behavior.

-          docker build -f ${{ matrix.dockerfile }} -t ${{ matrix.image }} .
+          docker build \
+            -f ${{ matrix.dockerfile }} \
+            --build-arg NEXT_PUBLIC_API_BASE_URL=${NEXT_PUBLIC_API_BASE_URL:-} \
+            --build-arg NEXT_PUBLIC_ADMIN_BASE_URL=${NEXT_PUBLIC_ADMIN_BASE_URL:-} \
+            --build-arg NEXT_PUBLIC_ADMIN_BASE_PATH=${NEXT_PUBLIC_ADMIN_BASE_PATH:-/god-mode} \
+            --build-arg NEXT_PUBLIC_LIVE_BASE_URL=${NEXT_PUBLIC_LIVE_BASE_URL:-} \
+            --build-arg NEXT_PUBLIC_LIVE_BASE_PATH=${NEXT_PUBLIC_LIVE_BASE_PATH:-/live} \
+            --build-arg NEXT_PUBLIC_SPACE_BASE_URL=${NEXT_PUBLIC_SPACE_BASE_URL:-} \
+            --build-arg NEXT_PUBLIC_SPACE_BASE_PATH=${NEXT_PUBLIC_SPACE_BASE_PATH:-/spaces} \
+            --build-arg NEXT_PUBLIC_WEB_BASE_URL=${NEXT_PUBLIC_WEB_BASE_URL:-} \
+            -t ${{ matrix.image }} .

115-136: Improve diagnostics on failures by printing response body on the last attempt

Helps triage issues when status != 200.

           for i in {1..60}; do
             STATUS="$(curl -sS -o /dev/null -w "%{http_code}" -L "${URL}" || true)"
             if [ "${STATUS}" = "200" ]; then
               echo "Success: HTTP ${STATUS} from ${URL}"
               exit 0
             fi
             echo "Attempt ${i}: HTTP ${STATUS} (waiting 2s)"
             sleep 2
           done
           echo "Failed to get HTTP 200 from ${URL}"
+          echo "::group::Last response body (${URL})"
+          curl -sS -L "${URL}" | sed -n '1,200p' || true
+          echo "::endgroup::"
           echo "::group::Container logs (${{ matrix.container }})"
           docker logs ${{ matrix.container }} || true
           echo "::endgroup::"
           exit 1
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6636b88 and 62d51c9.

📒 Files selected for processing (5)
  • .github/workflows/docker-smoke.yml (1 hunks)
  • apps/admin/Dockerfile.admin (2 hunks)
  • apps/live/Dockerfile.live (1 hunks)
  • apps/space/Dockerfile.space (3 hunks)
  • apps/web/Dockerfile.web (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build and smoke test live
  • GitHub Check: Build and smoke test web
  • GitHub Check: Build and smoke test space
  • GitHub Check: Build and smoke test admin
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (11)
apps/live/Dockerfile.live (1)

54-54: Good adoption of dumb-init and proper PID 1 handling

Using dumb-init as ENTRYPOINT will correctly forward signals and reap zombies. This should improve shutdown behavior in orchestrators.

Also applies to: 72-73

apps/admin/Dockerfile.admin (3)

61-68: Build-time env propagation looks good

Writing NEXT_PUBLIC_* into .env.production (and copying to .env) is a pragmatic way to ensure values are present at build-time and runtime. Keep in mind these values are public by design, so this is safe.


76-76: Good: Using dumb-init and an ENTRYPOINT wrapper

This will improve signal handling and shutdown behavior of the Next.js server.

Also applies to: 114-115


87-90: COPY with ownership is correct for non-root runtime

Ensuring correct ownership on standalone, static, and public assets prevents permission issues after dropping privileges. Well done.

apps/space/Dockerfile.space (3)

61-68: Build-time NEXT_PUBLIC env generation is solid

Mirrors the web/admin pattern; good for consistent build-time values.


77-78: dumb-init and ENTRYPOINT look good

Signal forwarding and zombie reaping in place.

Also applies to: 117-118


85-88: COPY with --chown is correct

Prevents permission issues after switching to non-root. Nice consistency with other apps.

apps/web/Dockerfile.web (3)

71-81: Build-time env generation for Next.js is well done

Clear, explicit mapping of NEXT_PUBLIC_* values. This matches the approach in space/admin and keeps behavior consistent.


89-89: Good: dumb-init installed and used as ENTRYPOINT

PID 1 handled properly; improves signal handling in containers.

Also applies to: 134-135


95-96: Permissions and ownership look correct for non-root runtime

  • Preparing a writable Next.js cache avoids runtime write errors.
  • COPY with --chown ensures the files are owned by the runtime user.

Also applies to: 101-104

.github/workflows/docker-smoke.yml (1)

28-34: Dynamic matrix generation is clear and robust

Good use of dorny/paths-filter and fallback to web when no targets detected.

@lifeiscontent lifeiscontent force-pushed the feat/docker-frozen-lock branch 3 times, most recently from 663c1be to 08e0ce7 Compare August 20, 2025 00:53
@lifeiscontent lifeiscontent force-pushed the feat/docker-frozen-lock branch 5 times, most recently from 59d118a to c266814 Compare August 21, 2025 22:49
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/live/src/server.ts (1)

26-29: Initialization race: async setupHocusPocus is not awaited before routes use it

setupHocusPocus() is async but not awaited. A WS connection that arrives before hocuspocusServer resolves will throw (handleConnection on undefined). Mount routes only after the server is ready.

Minimal change within current structure:

   this.setupMiddleware();
-  this.setupHocusPocus();
-  this.setupRoutes();
+  // Mount routes only after HocusPocus is ready
+  void this.setupHocusPocus(); // setupRoutes() will be called after init

And in setupHocusPocus (see fix below on Lines 47-51) call setupRoutes() after successful initialization.

🧹 Nitpick comments (13)
scripts/bench-builds.sh (3)

176-191: Capture build logs on failure (optional), enable verbose mode for diagnostics

All build output is redirected to /dev/null, making failures hard to debug. Consider a --verbose (or VERBOSE=1) mode that streams logs, and on failures, save logs per service in a temp file and print its path.

Example tweak:

-  if [[ "$cold" == "true" ]]; then
-    DOCKER_BUILDKIT=1 docker build --no-cache --progress=plain -f "$dockerfile" -t "$tag" "$ROOT_DIR" >/dev/null
-  else
-    DOCKER_BUILDKIT=1 docker build --progress=plain -f "$dockerfile" -t "$tag" "$ROOT_DIR" >/dev/null
-  fi
+  local out="/dev/null"
+  [[ "${VERBOSE:-0}" == "1" ]] && out="/dev/stdout"
+  if [[ "$cold" == "true" ]]; then
+    DOCKER_BUILDKIT=1 docker build --no-cache --progress=plain -f "$dockerfile" -t "$tag" "$ROOT_DIR" >"$out"
+  else
+    DOCKER_BUILDKIT=1 docker build --progress=plain -f "$dockerfile" -t "$tag" "$ROOT_DIR" >"$out"
+  fi

247-254: Be forgiving with user input: trim spaces and allow case-insensitive service names

A trailing space in --services "web, admin" currently causes a lookup failure. Normalize names to lowercase and trim spaces to reduce friction.

Apply this diff:

 # Normalize service list
-IFS=',' read -r -a SELECTED <<< "$SERVICES"
+IFS=',' read -r -a SELECTED <<< "$SERVICES"
+# trim spaces and lowercase
+for i in "${!SELECTED[@]}"; do
+  s="${SELECTED[$i]}"
+  s="${s//[[:space:]]/}"
+  SELECTED[$i]="${s,,}"
+done

266-289: Pruning before every cold build isolates results but can be very slow; consider an opt-in “prune-once” mode

You already support --no-prune and --aggressive-prune. A --prune-once option could strike a balance: prune once at the start, then run cold builds for all services without re-pruning.

If desirable, I can draft a follow-up patch adding --prune-once with clear precedence over existing flags.

scripts/smoke.sh (3)

28-33: Rename misleading comment: these are runtime envs, not build args.

The header says “Build args”, but you’re exporting env vars to containers at runtime (not Docker build-args). This can confuse maintainers.

Apply this diff:

-# Build args (override to change the built-in defaults)
-# e.g. ADMIN_BASE_PATH="/admin" SPACE_BASE_PATH="/my-space"
+# Base path runtime env (override defaults)
+# e.g. ADMIN_BASE_PATH="/admin" SPACE_BASE_PATH="/my-space"

136-146: Expose base-path env overrides in --help.

Documenting the runtime base-path envs in the help output improves discoverability and aligns with how containers are started.

Apply this diff:

   Images:  WEB_IMAGE=${WEB_IMAGE}, ADMIN_IMAGE=${ADMIN_IMAGE}, SPACE_IMAGE=${SPACE_IMAGE}, LIVE_IMAGE=${LIVE_IMAGE}
   Names:   WEB_CONT=${WEB_CONT}, ADMIN_CONT=${ADMIN_CONT}, SPACE_CONT=${SPACE_CONT}, LIVE_CONT=${LIVE_CONT}
+  Base:    ADMIN_BASE_PATH=${ADMIN_BASE_PATH}, SPACE_BASE_PATH=${SPACE_BASE_PATH}, LIVE_BASE_PATH=${LIVE_BASE_PATH}

185-186: Also check Docker daemon availability, not just CLI presence.

have docker passes even if the daemon is not running, causing later commands to fail noisily. Add a lightweight daemon check.

Apply this diff:

-have docker || die "Docker is required. Please install Docker and ensure the daemon is running."
+have docker || die "Docker CLI is required. Please install Docker."
+docker info >/dev/null 2>&1 || die "Docker daemon is not reachable. Start Docker and try again."
apps/live/package.json (1)

41-41: I’ve run a deeper inspection of your apps/live setup:

  1. Dependencies:

    • pino: 9.9.0
    • morgan: 1.10.1
    • pino-http: not present in dependencies
  2. Code references:

    • No require('pino') or import pino anywhere.
    • No require('pino-http') or import pino-http.
    • No require('morgan') or morgan( calls in any .js/.ts files.

This tells us:

  • You’ve added pino to package.json, but haven’t wired it into the code yet.
  • pino-http isn’t installed or referenced—if you intend HTTP middleware, you need to add and configure it.
  • morgan is declared but unused in code and can be removed to avoid dead deps.

Next steps:

• If your goal is application-level logging only (no HTTP logger middleware):

  • Remove "morgan": "1.10.1" from apps/live/package.json.
  • Import and instantiate pino where needed (e.g. const logger = require('pino')()).

• If you intend to use Pino for HTTP requests:

  • Add "pino-http": "^10.x" to dependencies.
  • In your server setup, replace any morgan middleware with:
    const pino = require('pino');
    const pinoHttp = require('pino-http');
    const logger = pinoHttp({ logger: pino(/* options */) });
    app.use(logger);
  • Remove morgan afterward.

Please verify which approach matches your requirements, wire up pino (and pino-http if needed), and trim the unused morgan dependency.

apps/live/src/server.ts (6)

23-25: applyTo(router) is unused — either use router.ws(...) or drop applyTo

You attach express-ws to both app and router but register the WS route on app. This extra applyTo is redundant unless you plan to use router.ws.

Apply one of the following diffs:

Option A — keep app.ws, drop applyTo:

-    const wsInstance = expressWs(this.app);
-    wsInstance.applyTo(this.router);
+    expressWs(this.app);

Option B — prefer router.ws (base path comes from the router mount):

-    const wsInstance = expressWs(this.app);
-    wsInstance.applyTo(this.router);
+    const wsInstance = expressWs(this.app);
+    wsInstance.applyTo(this.router);

…and update the route below (see comment on Lines 58-66).


58-66: Prefer router.ws("/collaboration") to avoid duplicating basePath logic

You already mount the router at LIVE_BASE_PATH and applied express-ws to the router. Using app.ws with a recomputed basePath duplicates configuration and can drift.

-    const basePath = process.env.LIVE_BASE_PATH || "/live";
-    this.app.ws(`${basePath}/collaboration`, (ws: any, req: Request) => {
+    this.router.ws(`/collaboration`, (ws: any, req: Request) => {
       try {
         this.hocuspocusServer.handleConnection(ws, req);
       } catch (err) {
-        logger.error(err, "WebSocket connection error:");
+        logger.error(err, "WebSocket connection error");
         ws.close();
       }
     });

86-90: Solid error handler for convert endpoint

Error-first logging plus a generic 500 response is appropriate. Consider attaching a correlation/request id from httpLogger for easier tracing, if available.


101-104: Startup log: include basePath and WS path for operability

Add structured context so logs show where to probe health and WS.

-      logger.info(`Plane Live server has started at port ${this.app.get("port")}`);
+      const port = this.app.get("port");
+      const basePath = process.env.LIVE_BASE_PATH || "/live";
+      logger.info({ port, basePath, wsPath: `${basePath}/collaboration` }, "Plane Live server started");

109-115: Graceful shutdown exits with code 1 — consider making the exit code explicit

Destroy always exits(1), which is fine for fatal paths (unhandled errors) but brittle if reused for normal shutdown. Pass an exitCode or let the caller decide.

-  public async destroy() {
+  public async destroy(exitCode = 1) {
     // Close the HocusPocus server WebSocket connections
     await this.hocuspocusServer.destroy();
     logger.info("HocusPocus server WebSocket connections closed gracefully.");
     // Close the Express server
     this.serverInstance.close(() => {
       logger.info("Express server closed gracefully.");
-      process.exit(1);
+      process.exit(exitCode);
     });
   }

And when called in error handlers (below), pass 1 explicitly; for future normal shutdown (e.g., SIGTERM), pass 0.


122-126: Global fatal handlers look good; consider also handling SIGTERM/SIGINT

You already cover unhandledRejection/uncaughtException. Adding SIGTERM/SIGINT improves graceful exits in containers and local dev.

Additional snippet (outside current ranges):

process.once("SIGTERM", async () => {
  logger.info("SIGTERM received — shutting down gracefully");
  await server.destroy(0);
});
process.once("SIGINT", async () => {
  logger.info("SIGINT received — shutting down gracefully");
  await server.destroy(0);
});

Also applies to: 129-133

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5f374c4 and c266814.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • .github/workflows/docker-smoke.yml (1 hunks)
  • apps/live/Dockerfile.live (1 hunks)
  • apps/live/package.json (2 hunks)
  • apps/live/src/core/extensions/index.ts (6 hunks)
  • apps/live/src/core/helpers/error-handler.ts (1 hunks)
  • apps/live/src/core/helpers/logger.ts (1 hunks)
  • apps/live/src/core/lib/authentication.ts (2 hunks)
  • apps/live/src/core/lib/page.ts (4 hunks)
  • apps/live/src/server.ts (8 hunks)
  • apps/live/tsconfig.json (1 hunks)
  • apps/live/tsup.config.ts (1 hunks)
  • scripts/bench-builds.sh (1 hunks)
  • scripts/smoke.sh (1 hunks)
  • turbo.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • apps/live/src/core/helpers/error-handler.ts
  • apps/live/Dockerfile.live
  • apps/live/tsconfig.json
  • apps/live/tsup.config.ts
  • apps/live/src/core/lib/page.ts
  • turbo.json
  • apps/live/src/core/helpers/logger.ts
  • apps/live/src/core/extensions/index.ts
  • .github/workflows/docker-smoke.yml
🧰 Additional context used
🪛 Shellcheck (0.10.0)
scripts/bench-builds.sh

[warning] 120-120: unit appears unused. Verify use (or export if used externally).

(SC2034)

🔇 Additional comments (13)
scripts/bench-builds.sh (6)

296-307: Nice, concise summary output

The fixed-width table plus humanized durations/sizes reads well and is CI-log friendly.


59-61: Nit: use stderr for die(), timestamps for log() — both already good

Minor kudos: die() writes to stderr and log() timestamps messages. No action needed.


33-38: Paths and tags mapping look consistent with the repo’s app set

The mapping aligns with web/admin/space/live and distinct bench tags.


193-213: Clear help text and examples

Usage and options are unambiguous; good developer ergonomics.


63-86: Timing functions verified and correct

The now_ns and elapsed_ms helpers have been sanity-checked using the provided script, measuring ~105 ms for a 100 ms sleep on your environment. No changes are needed here.


113-132: Remove unused unit from build_cache_size_str

  • scripts/bench-builds.sh (around line 120): update the local declaration
    -      local line size unit
    +      local line size

Shellcheck no longer reports SC2034 after dropping unit.

scripts/smoke.sh (4)

50-55: Nice: small, reusable utilities with strict mode.

The have, log, and die helpers paired with set -Eeuo pipefail make the script robust and readable. Good discipline.


187-199: Good: per-app Dockerfiles with explicit tags and idempotent rebuilds.

Clear naming for images/containers and pre-run cleanup via ensure_not_running makes iterative testing smooth.


211-225: Solid readiness probing with timeout and backoff.

wait_for_url is clean, logs well, and leaves containers up for inspection. With the regex fix above, this will be reliable.


234-246: Output UX is helpful.

The summarized docker ps table and ready-to-use URLs are exactly what folks need during smoke testing.

apps/live/src/core/lib/authentication.ts (1)

4-4: Centralized logger import looks good

Importing the shared logger via the ESM path with .js extension aligns with the new build/runtime layout. No issues.

apps/live/package.json (1)

8-8: Package privacy retained

"private": true remains — good for a non-published package.

apps/live/src/server.ts (1)

37-37: HTTP logger middleware placement is fine

Mounted early in the chain and before routers — good for end-to-end request timing. Keep it before body parsers if you don’t need to log parsed bodies.

@lifeiscontent lifeiscontent force-pushed the feat/docker-frozen-lock branch from c266814 to a28ee72 Compare August 21, 2025 23:07
@lifeiscontent lifeiscontent changed the title chore(repo): initial frozen lock fix Aug 21, 2025
@lifeiscontent lifeiscontent force-pushed the feat/docker-frozen-lock branch 2 times, most recently from 4568e40 to f1e290e Compare August 25, 2025 23:11
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (8)
scripts/bench-builds.sh (5)

88-105: Simplify and harden human_bytes() (avoid awk log() pitfalls, handle 0 robustly)

Current mix of integer loop + log recompute is brittle and can break on some awk variants.

 human_bytes() {
-  # Convert integer bytes to human readable
-  local bytes="$1"
-  local units=("B" "KB" "MB" "GB" "TB" "PB")
-  local idx=0
-  local value="$bytes"
-  while (( value >= 1024 && idx < ${#units[@]}-1 )); do
-    value=$(( value/1024 ))
-    idx=$(( idx+1 ))
-  done
-  # value is integer; but to preserve more precision, recompute with awk
-  awk -v b="$bytes" -v i="$idx" 'BEGIN {
-    split("B KB MB GB TB PB", u, " ");
-    val=b;
-    while (val>=1024 && i>0){ val/=1024; i-- }
-    printf("%.2f %s", val, u[(int((log(b)/log(1024)))+1)]);
-  }' 2>/dev/null || printf "%d %s" "$value" "${units[$idx]}"
+  # Convert integer bytes to human-readable (two decimals)
+  local bytes="${1:-0}"
+  awk -v b="$bytes" 'BEGIN {
+    if (b < 0) b = 0
+    split("B KB MB GB TB PB", u, " ")
+    i = 1
+    while (b >= 1024 && i < 6) { b /= 1024; i++ }
+    printf("%.2f %s", b, u[i])
+  }' 2>/dev/null || printf "%s %s" "${bytes}" "B"
 }

218-241: Resolve conflicting flags: --no-prune vs --aggressive-prune

Currently both can be set and behavior is implicit. Make precedence explicit.

 done
+
+# Resolve conflicting options
+if [[ "$AGGRESSIVE_PRUNE" == "true" && "$NO_PRUNE" == "true" ]]; then
+  log "Both --no-prune and --aggressive-prune specified; ignoring --no-prune."
+  NO_PRUNE="false"
+fi

245-246: Validate Docker daemon availability, not just CLI

The CLI may exist while the daemon is down; fail fast with a clearer error.

-have docker || die "Docker is required. Please install Docker and ensure the daemon is running."
+have docker || die "Docker is required. Please install Docker and ensure the daemon is running."
+docker info >/dev/null 2>&1 || die "Docker daemon is not running or not reachable. Start Docker Desktop or your Docker service."

26-26: Require Bash ≥ 4 early (associative arrays in use)

The script uses declare -A; macOS default bash 3.2 will fail.

 set -Eeuo pipefail
+
+# Require bash >= 4 for associative arrays (macOS ships 3.2 by default)
+if (( BASH_VERSINFO[0] < 4 )); then
+  echo "ERROR: bash >= 4 is required. On macOS, install via Homebrew: brew install bash && chsh -s /usr/local/bin/bash" >&2
+  exit 1
+fi

63-70: now_ns() misdetects BSD date nanoseconds — returns “%N” and breaks math

On macOS, date +%s%N echoes “%N” with exit 0. Validate numerically and add robust fallbacks.

 now_ns() {
-  # Prefer nanoseconds if supported, fallback to seconds*1e9
-  if date +%s%N >/dev/null 2>&1; then
-    date +%s%N
-  else
-    echo "$(( $(date +%s) * 1000000000 ))"
-  fi
+  # Prefer GNU date; then BSD date if it truly supports %N; else EPOCHREALTIME/perl/seconds
+  if command -v gdate >/dev/null 2>&1; then
+    local t
+    t="$(gdate +%s%N 2>/dev/null || true)"
+    [[ "$t" =~ ^[0-9]+$ ]] && { echo "$t"; return; }
+  fi
+  local t
+  t="$(date +%s%N 2>/dev/null || true)"
+  if [[ "$t" =~ ^[0-9]+$ ]]; then
+    echo "$t"; return
+  fi
+  if [[ -n "${EPOCHREALTIME-}" ]]; then
+    local sec="${EPOCHREALTIME%.*}" frac="${EPOCHREALTIME#*.}"
+    frac="${frac}000000000"; frac="${frac:0:9}"
+    printf '%s%s\n' "$sec" "$frac"; return
+  fi
+  if command -v perl >/dev/null 2>&1; then
+    perl -MTime::HiRes=time -e 'printf("%.0f\n", time()*1e9)'; return
+  fi
+  echo "$(( $(date +%s) * 1000000000 ))"
 }
scripts/smoke.sh (3)

61-64: Fix 2xx/3xx detection; current regex has precedence bug (accepts “^2” OR “3xx”)

Use a case pattern or anchored regex; also consider following redirects with -L.

-    local code
-    code="$(curl -fsS -o /dev/null -w "%{http_code}" "$url" || true)"
-    [[ "$code" =~ ^2|3[0-9]{2}$ ]] && return 0
+    local code
+    code="$(curl -fsSL -o /dev/null -w "%{http_code}" "$url" || true)"
+    case "$code" in
+      2??|3??) return 0 ;;
+      *)       return 1 ;;
+    esac

189-189: CLI --services should override the environment variable SERVICES

Current precedence favors env over CLI, which is surprising.

-IFS=',' read -r -a SELECTED <<< "${SERVICES:-$services}"
+IFS=',' read -r -a SELECTED <<< "${services:-$SERVICES}"

201-208: Add default branch in run loop to fail fast on unknown services

Without a default, typos are silently ignored during “run” when not building.

   case "$svc" in
     web) run_container "$WEB_CONT" "$WEB_IMAGE" "$WEB_PORT" ;;
     admin) run_container "$ADMIN_CONT" "$ADMIN_IMAGE" "$ADMIN_PORT" -e NEXT_PUBLIC_ADMIN_BASE_PATH="${ADMIN_BASE_PATH}" ;;
     space) run_container "$SPACE_CONT" "$SPACE_IMAGE" "$SPACE_PORT" -e NEXT_PUBLIC_SPACE_BASE_PATH="${SPACE_BASE_PATH}" ;;
     live) run_container "$LIVE_CONT" "$LIVE_IMAGE" "$LIVE_PORT" -e LIVE_BASE_PATH="${LIVE_BASE_PATH}" ;;
+    *) die "Unknown service: $svc" ;;
   esac
🧹 Nitpick comments (8)
.github/workflows/docker-smoke.yml (5)

47-61: paths-filter patterns may not match due to leading './' — drop the prefix to be safe

dorny/paths-filter patterns are typically repo-root relative. The leading './' can prevent matches depending on how the action normalizes paths. This could degrade the matrix to default “web” only on PRs when other apps changed.

Apply:

           web:
-              - './apps/web/**'
+              - 'apps/web/**'
           space:
-              - './apps/space/**'
+              - 'apps/space/**'
           admin:
-              - './apps/admin/**'
+              - 'apps/admin/**'
           live:
-              - './apps/live/**'
+              - 'apps/live/**'
           common:
               - 'turbo.json'
               - 'pnpm-lock.yaml'
               - 'pnpm-workspace.yaml'
               - '.github/workflows/docker-smoke.yml'
-              - './packages/**'
+              - 'packages/**'

If you prefer to keep './', please confirm with a quick test run that the filters fire as expected on PRs.


124-133: Accept any 2xx HTTP for smoke readiness (not just 200)

Some endpoints might legitimately return 204 or 206. You already follow redirects (-L), so broad 2xx is reasonable.

-            if [ "${STATUS}" = "200" ]; then
+            case "${STATUS}" in
+              2??)
                 echo "Success: HTTP ${STATUS} from ${URL}"
-              exit 0
-            fi
+                exit 0
+                ;;
+            esac

108-112: Consider always pulling the latest base image in CI builds

Adding --pull ensures you’re not building atop stale base layers, improving reproducibility and security posture.

-          docker build -f ${{ matrix.dockerfile }} -t ${{ matrix.image }} .
+          docker build --pull -f ${{ matrix.dockerfile }} -t ${{ matrix.image }} .

118-139: Broaden failure diagnostics (inspect, top, last 200 log lines)

On failures, add concise diagnostics to speed root cause analysis.

           echo "Failed to get HTTP 200 from ${URL}"
           echo "::group::Container logs (${{ matrix.container }})"
-          docker logs ${{ matrix.container }} || true
+          docker logs --tail=200 ${{ matrix.container }} || true
           echo "::endgroup::"
+          echo "::group::Container inspect (${{ matrix.container }})"
+          docker inspect ${{ matrix.container }} | sed -n '1,200p' || true
+          echo "::endgroup::"
+          echo "::group::Container processes (${{ matrix.container }})"
+          docker top ${{ matrix.container }} || true
+          echo "::endgroup::"
           exit 1

1-2: Tighten workflow permissions and prevent overlapping runs

Least-privilege token and a concurrency group help safety and save minutes.

 name: Docker build and smoke test for apps
+
+permissions:
+  contents: read
+
+concurrency:
+  group: docker-smoke-${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
scripts/bench-builds.sh (1)

114-131: ShellCheck SC2034: remove unused ‘unit’ variable and minor parsing nit

The declared unit variable is unused; drop it to silence SC2034.

-      local line size unit
+      local line size
scripts/smoke.sh (2)

184-186: Also verify Docker daemon availability

Mirror the bench script recommendation: check docker info, not just CLI presence.

-# Check Docker is available
-have docker || die "Docker is required. Please install Docker and ensure the daemon is running."
+# Check Docker is available
+have docker || die "Docker is required. Please install Docker and ensure the daemon is running."
+docker info >/dev/null 2>&1 || die "Docker daemon is not running or not reachable. Start Docker Desktop or your Docker service."

193-199: Next.js basePath usually bakes in at build time — passing NEXT_PUBLIC_ at runtime may be ineffective*

If admin/space compute basePath at build (next build) from process.env, setting NEXT_PUBLIC_*_BASE_PATH via docker run won’t affect already-built output. If you rely on runtime injection, confirm the apps read env at runtime (e.g., server config or experimental runtime env).

Two options:

  • Build-time args: add ARG/ENV in the Dockerfiles and pass --build-arg in build_image when svc is admin/space.
  • Or switch to a runtime-friendly config pattern that reads env on boot.

Example (script-side build-time injection):

-    admin) build_image "admin" "$ADMIN_IMAGE" "apps/admin/Dockerfile.admin" ;;
-    space) build_image "space" "$SPACE_IMAGE" "apps/space/Dockerfile.space" ;;
+    admin) DOCKER_BUILDKIT=1 docker build --progress=plain \
+             --build-arg NEXT_PUBLIC_ADMIN_BASE_PATH="${ADMIN_BASE_PATH}" \
+             -f "apps/admin/Dockerfile.admin" -t "$ADMIN_IMAGE" "$ROOT_DIR" ;;
+    space) DOCKER_BUILDKIT=1 docker build --progress=plain \
+             --build-arg NEXT_PUBLIC_SPACE_BASE_PATH="${SPACE_BASE_PATH}" \
+             -f "apps/space/Dockerfile.space" -t "$SPACE_IMAGE" "$ROOT_DIR" ;;

And in the Dockerfiles:

ARG NEXT_PUBLIC_ADMIN_BASE_PATH
ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH

Same for SPACE. Please confirm how these apps source basePath today.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4568e40 and f1e290e.

📒 Files selected for processing (4)
  • .github/workflows/docker-smoke.yml (1 hunks)
  • apps/live/Dockerfile.live (1 hunks)
  • scripts/bench-builds.sh (1 hunks)
  • scripts/smoke.sh (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/live/Dockerfile.live
🧰 Additional context used
🪛 Shellcheck (0.10.0)
scripts/bench-builds.sh

[warning] 120-120: unit appears unused. Verify use (or export if used externally).

(SC2034)

@lifeiscontent lifeiscontent force-pushed the feat/docker-frozen-lock branch from f1e290e to 9efe0ec Compare August 25, 2025 23:31
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (7)
scripts/bench-builds.sh (5)

28-41: Good: Early Bash 4+ guard prevents associative-array breakage on macOS (Bash 3.2).

This directly addresses portability concerns and avoids hard-to-debug runtime failures when declare -A is used.


78-124: Good: Robust nanosecond time source with validation and multi-tier fallbacks.

The numeric-output validation fixes the BSD date “%N” pitfall; fallbacks via EPOCHREALTIME, perl Time::HiRes, then seconds×1e9 are solid.


142-159: Simplify human_bytes() and avoid awk log()/zero-edge pitfalls.

Current implementation mixes integer division with a log-based awk expression and can misbehave for 0 bytes or awk variants without reliable log(). Prefer a single-pass awk approach.

Apply this diff:

-humann_bytes() {
-  # Convert integer bytes to human readable
-  local bytes="$1"
-  local units=("B" "KB" "MB" "GB" "TB" "PB")
-  local idx=0
-  local value="$bytes"
-  while (( value >= 1024 && idx < ${#units[@]}-1 )); do
-    value=$(( value/1024 ))
-    idx=$(( idx+1 ))
-  done
-  # value is integer; but to preserve more precision, recompute with awk
-  awk -v b="$bytes" -v i="$idx" 'BEGIN {
-    split("B KB MB GB TB PB", u, " ");
-    val=b;
-    while (val>=1024 && i>0){ val/=1024; i-- }
-    printf("%.2f %s", val, u[(int((log(b)/log(1024)))+1)]);
-  }' 2>/dev/null || printf "%d %s" "$value" "${units[$idx]}"
-}
+human_bytes() {
+  # Convert integer bytes to human-readable (two decimals)
+  local bytes="${1:-0}"
+  awk -v b="$bytes" 'BEGIN {
+    if (b < 0) b = 0
+    split("B KB MB GB TB PB", u, " ")
+    i = 1
+    while (b >= 1024 && i < 6) { b /= 1024; i++ }
+    printf("%.2f %s", b, u[i])
+  }' 2>/dev/null || printf "%s %s" "${bytes}" "B"
+}

272-295: Resolve conflicting flags: --no-prune vs --aggressive-prune.

If both are passed, current logic silently skips pruning, which contradicts “aggressive.” Make precedence explicit.

Add right after the parsing loop:

 done

+# Resolve conflicting options
+if [[ "$AGGRESSIVE_PRUNE" == "true" && "$NO_PRUNE" == "true" ]]; then
+  log "Both --no-prune and --aggressive-prune specified; ignoring --no-prune."
+  NO_PRUNE="false"
+fi

299-303: Good: Check Docker daemon reachability early.

The docker info probe saves time by failing fast when the daemon is down.

scripts/smoke.sh (2)

206-215: Good: Unknown services are rejected in both build and run paths.

Fast-failing on typos is the right UX, including when --no-build is used.


189-194: Fix precedence: CLI --services should override SERVICES env.

The current split uses ${SERVICES:-$services}, giving the environment higher precedence than the parsed option. In CI and local runs this can cause surprising behavior.

-IFS=',' read -r -a SELECTED <<< "${SERVICES:-$services}"
+IFS=',' read -r -a SELECTED <<< "${services:-$SERVICES}"
🧹 Nitpick comments (3)
scripts/bench-builds.sh (1)

174-176: Remove unused local ‘unit’ variable (ShellCheck SC2034).

Declared but never used; keep the declaration minimal.

-      local line size unit
+      local line size
scripts/smoke.sh (2)

63-66: Tighten HTTP status match to exactly 3 digits (2xx/3xx).

2*|3* works but is overly permissive. Use 2??|3?? to match 200–399 precisely.

-    case "$code" in
-      2*|3*) return 0 ;;
-      *) return 1 ;;
-    esac
+    case "$code" in
+      2??|3??) return 0 ;;
+      *)       return 1 ;;
+    esac

189-193: Add Docker daemon reachability check (not just CLI presence).

Mirror the bench script: the CLI can exist while the daemon is down; fail fast with a clearer message.

 have docker || die "Docker is required. Please install Docker and ensure the daemon is running."
 
+# Verify Docker daemon is reachable
+if ! docker info >/dev/null 2>&1; then
+  die "Docker daemon is not running or is not reachable. Please start Docker (e.g., Docker Desktop) and try again."
+fi
+
 cd "$ROOT_DIR"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f1e290e and 9efe0ec.

📒 Files selected for processing (4)
  • .github/workflows/docker-smoke.yml (1 hunks)
  • apps/live/Dockerfile.live (1 hunks)
  • scripts/bench-builds.sh (1 hunks)
  • scripts/smoke.sh (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/live/Dockerfile.live
  • .github/workflows/docker-smoke.yml
🧰 Additional context used
🧬 Code graph analysis (1)
scripts/smoke.sh (1)
scripts/bench-builds.sh (3)
  • have (72-72)
  • die (76-76)
  • log (74-74)
🪛 Shellcheck (0.10.0)
scripts/bench-builds.sh

[warning] 174-174: unit appears unused. Verify use (or export if used externally).

(SC2034)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build and smoke test admin
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
scripts/smoke.sh (1)

196-204: LIVE_BASE_PATH is correctly consumed at runtime in the Live app—no build-time ARG needed

I checked how LIVE_BASE_PATH is used in apps/live:

  • In apps/live/src/server.ts the router is mounted against process.env.LIVE_BASE_PATH (or "/live"), so it’s read at runtime, not during build.
  • There is no next.config.* under apps/live, confirming it’s not a Next.js build that bakes in a basePath.
  • The smoke script’s run_container … -e LIVE_BASE_PATH="${LIVE_BASE_PATH}" correctly injects the env var for runtime use.

Since Live isn’t using a Next.js basePath at build-time, passing LIVE_BASE_PATH only as a runtime -e is appropriate. No changes required to Dockerfile.live or the smoke test.

Copilot AI review requested due to automatic review settings August 26, 2025 18:56
@lifeiscontent lifeiscontent force-pushed the feat/docker-frozen-lock branch from 9efe0ec to 85ac151 Compare August 26, 2025 18:56

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
scripts/bench-builds.sh (2)

142-159: Simplify human_bytes() and avoid awk log()/0 edge cases

Current version mixes integer loop + awk with log(b), which can misbehave for 0 and varies across awk variants. A single, simple awk is more portable.

Apply this diff:

-human_bytes() {
-  # Convert integer bytes to human readable
-  local bytes="$1"
-  local units=("B" "KB" "MB" "GB" "TB" "PB")
-  local idx=0
-  local value="$bytes"
-  while (( value >= 1024 && idx < ${#units[@]}-1 )); do
-    value=$(( value/1024 ))
-    idx=$(( idx+1 ))
-  done
-  # value is integer; but to preserve more precision, recompute with awk
-  awk -v b="$bytes" -v i="$idx" 'BEGIN {
-    split("B KB MB GB TB PB", u, " ");
-    val=b;
-    while (val>=1024 && i>0){ val/=1024; i-- }
-    printf("%.2f %s", val, u[(int((log(b)/log(1024)))+1)]);
-  }' 2>/dev/null || printf "%d %s" "$value" "${units[$idx]}"
-}
+human_bytes() {
+  # Convert integer bytes to human-readable (two decimals)
+  local bytes="${1:-0}"
+  awk -v b="$bytes" 'BEGIN {
+    if (b < 0) b = 0
+    split("B KB MB GB TB PB", u, " ")
+    i = 1
+    while (b >= 1024 && i < 6) { b /= 1024; i++ }
+    printf("%.2f %s", b, u[i])
+  }' 2>/dev/null || printf "%s %s" "${bytes}" "B"
+}

296-305: Make --no-prune vs --aggressive-prune precedence explicit

If both flags are set, current behavior is implicit. Prefer failing fast or documenting precedence.

Apply this diff right after the argument parsing loop:

 done

+# Resolve conflicting options
+if [[ "$AGGRESSIVE_PRUNE" == "true" && "$NO_PRUNE" == "true" ]]; then
+  log "Both --no-prune and --aggressive-prune specified; ignoring --no-prune."
+  NO_PRUNE="false"
+fi
scripts/smoke.sh (1)

162-166: Let CLI --services override environment SERVICES

Currently, ${SERVICES:-$services} causes the environment to override the parsed CLI option, which is surprising. Make CLI take precedence.

Apply this diff:

-cmd="${1:-up}"
-no_build="false"
-services="${SERVICES:-web,admin,space,live}"
-shift $(( $# > 0 ? 1 : 0 ))
+cmd="${1:-up}"
+no_build="false"
+# Default list; CLI --services should take precedence over environment.
+services="web,admin,space,live"
+if [[ -n "${SERVICES:-}" ]]; then services="$SERVICES"; fi
+shift $(( $# > 0 ? 1 : 0 ))
-IFS=',' read -r -a SELECTED <<< "${SERVICES:-$services}"
+IFS=',' read -r -a SELECTED <<< "$services"

Also applies to: 194-204

🧹 Nitpick comments (4)
scripts/bench-builds.sh (1)

172-181: Minor: remove unused local “unit” (ShellCheck SC2034)

unit is declared but never used.

Apply this diff:

-      local line size unit
+      local line size
scripts/smoke.sh (1)

189-191: Also verify Docker daemon, not just CLI

If the daemon isn’t running, later steps fail noisily. Add a lightweight check.

Apply this diff below the existing check:

 have docker || die "Docker is required. Please install Docker and ensure the daemon is running."
+docker info >/dev/null 2>&1 || die "Docker daemon is not running or not reachable. Start Docker and retry."
.github/workflows/docker-smoke.yml (2)

118-139: Broaden success criteria to any 2xx (and optionally 3xx) HTTP status

Some endpoints may legitimately return 204 or redirect before settling. You already use -L to follow redirects, but limiting to 200 is brittle.

Apply this diff:

-          for i in {1..60}; do
-            STATUS="$(curl -sS -o /dev/null -w "%{http_code}" -L "${URL}" || true)"
-            if [ "${STATUS}" = "200" ]; then
-              echo "Success: HTTP ${STATUS} from ${URL}"
-              exit 0
-            fi
+          for i in {1..60}; do
+            STATUS="$(curl -sS -o /dev/null -w "%{http_code}" -L "${URL}" || true)"
+            case "${STATUS}" in
+              2??|3??)
+                echo "Success: HTTP ${STATUS} from ${URL}"
+                exit 0
+                ;;
+            esac
             echo "Attempt ${i}: HTTP ${STATUS} (waiting 2s)"
             sleep 2
           done
-          echo "Failed to get HTTP 200 from ${URL}"
+          echo "Failed to get a 2xx/3xx response from ${URL}"

47-61: Nit: normalize path globs (drop leading ./) to match triggers

The filters use ./apps/... while triggers use apps/.... Both often work, but unifying reduces cognitive overhead.

Apply this diff:

-            web:
-              - './apps/web/**'
+            web:
+              - 'apps/web/**'
             space:
-              - './apps/space/**'
+              - 'apps/space/**'
             admin:
-              - './apps/admin/**'
+              - 'apps/admin/**'
             live:
-              - './apps/live/**'
+              - 'apps/live/**'
             common:
               - 'turbo.json'
               - 'pnpm-lock.yaml'
               - 'pnpm-workspace.yaml'
               - '.github/workflows/docker-smoke.yml'
-              - './packages/**'
+              - 'packages/**'
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9efe0ec and 85ac151.

📒 Files selected for processing (3)
  • .github/workflows/docker-smoke.yml (1 hunks)
  • scripts/bench-builds.sh (1 hunks)
  • scripts/smoke.sh (1 hunks)
🧰 Additional context used
🪛 Shellcheck (0.10.0)
scripts/bench-builds.sh

[warning] 174-174: unit appears unused. Verify use (or export if used externally).

(SC2034)

�� Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build and smoke test web
  • GitHub Check: Build and smoke test space
  • GitHub Check: Build and smoke test admin
  • GitHub Check: Build and smoke test live
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (3)
scripts/bench-builds.sh (1)

78-124: Robust time source detection looks solid

The now_ns() implementation is portable and validates outputs to avoid BSD %N pitfalls. Good use of 10# to avoid octal issues.

scripts/smoke.sh (1)

56-67: HTTP probe logic is correct and resilient

Accepting both 2xx and 3xx via a case pattern is clear and avoids regex precedence pitfalls. Nice.

.github/workflows/docker-smoke.yml (1)

76-85: Align live app port with local smoke script for parity (3005 vs 3004)

Matrix uses host port 3005 for live, while scripts/smoke.sh defaults LIVE_PORT=3004. Consider aligning to reduce confusion.

Would you like me to change the matrix port to 3004 for live?

-            if (buildAll || changed.live) add('live', 'apps/live/Dockerfile.live', 'plane-live:ci-smoke', 'plane-live-ci', 3005, '/live/health', '-e NODE_ENV=production -e LIVE_BASE_PATH=/live');
+            if (buildAll || changed.live) add('live', 'apps/live/Dockerfile.live', 'plane-live:ci-smoke', 'plane-live-ci', 3004, '/live/health', '-e NODE_ENV=production -e LIVE_BASE_PATH=/live');
@lifeiscontent lifeiscontent force-pushed the feat/docker-frozen-lock branch from 85ac151 to c596ad3 Compare August 26, 2025 19:17
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
.github/workflows/docker-smoke.yml (1)

46-61: Paths filter looks good and is consistent with prior feedback

Filters are clear and consistently quoted; including packages/** under common is appropriate for rebuilding all. Nothing to change.

scripts/bench-builds.sh (2)

142-159: Simplify human_bytes() and avoid log(0)/awk portability pitfalls

Current logic mixes integer loop with an awk recomputation using log(); this can misbehave for 0 bytes and differs across awk variants. A single awk block (or pure shell) is clearer and safer.

Apply this diff:

 human_bytes() {
-  # Convert integer bytes to human readable
-  local bytes="$1"
-  local units=("B" "KB" "MB" "GB" "TB" "PB")
-  local idx=0
-  local value="$bytes"
-  while (( value >= 1024 && idx < ${#units[@]}-1 )); do
-    value=$(( value/1024 ))
-    idx=$(( idx+1 ))
-  done
-  # value is integer; but to preserve more precision, recompute with awk
-  awk -v b="$bytes" -v i="$idx" 'BEGIN {
-    split("B KB MB GB TB PB", u, " ");
-    val=b;
-    while (val>=1024 && i>0){ val/=1024; i-- }
-    printf("%.2f %s", val, u[(int((log(b)/log(1024)))+1)]);
-  }' 2>/dev/null || printf "%d %s" "$value" "${units[$idx]}"
+  # Convert integer bytes to human-readable (two decimals)
+  local bytes="${1:-0}"
+  awk -v b="$bytes" 'BEGIN {
+    if (b < 0) b = 0
+    split("B KB MB GB TB PB", u, " ")
+    i = 1
+    while (b >= 1024 && i < 6) { b /= 1024; i++ }
+    printf("%.2f %s", b, u[i])
+  }' 2>/dev/null || printf "%s %s" "${bytes}" "B"
 }

272-295: Resolve conflicting flags: --no-prune vs --aggressive-prune

If both are provided, current behavior is ambiguous. Make precedence explicit or error out.

Apply this diff right after the parsing loop:

 done

+# Resolve conflicting options
+if [[ "$AGGRESSIVE_PRUNE" == "true" && "$NO_PRUNE" == "true" ]]; then
+  log "Both --no-prune and --aggressive-prune specified; ignoring --no-prune."
+  NO_PRUNE="false"
+fi

If you prefer to hard-fail instead of choosing precedence, replace the body with: die "Choose either --no-prune or --aggressive-prune, not both."

🧹 Nitpick comments (5)
.github/workflows/docker-smoke.yml (3)

1-29: Add minimal permissions and a concurrency group for safer, more efficient CI

Tighten token scope and avoid redundant runs on the same ref.

Apply this diff:

 name: Docker build and smoke test for apps

 on:
   workflow_dispatch:
   pull_request:
     branches: ["preview"]
@@
     paths:
       - ".github/workflows/docker-smoke.yml"

+permissions:
+  contents: read
+
+concurrency:
+  group: docker-smoke-${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true

Note: Consider pinning all third-party actions (checkout, paths-filter, github-script) to immutable commit SHAs for supply-chain hardening.


118-139: Accept any 2xx status to reduce false negatives; keep logs on failure

Some endpoints may return 204/201 or similar. Since you already follow redirects (-L), treating any 2xx as success makes the probe more robust.

Apply this diff:

-          # Try up to ~120 seconds (60 attempts * 2s)
+          # Try up to ~120 seconds (60 attempts * 2s)
           for i in {1..60}; do
             STATUS="$(curl -sS -o /dev/null -w "%{http_code}" -L "${URL}" || true)"
-            if [ "${STATUS}" = "200" ]; then
-              echo "Success: HTTP ${STATUS} from ${URL}"
+            if [[ "${STATUS}" =~ ^2[0-9][0-9]$ ]]; then
+              echo "Success: HTTP ${STATUS} from ${URL}"
               exit 0
             fi
             echo "Attempt ${i}: HTTP ${STATUS} (waiting 2s)"
             sleep 2
           done

If some services intentionally return a 3xx terminal status, expand the regex to include ^3..$.


76-83: Safer env passing than free-form flags string

Passing env via a free-form string can break on spaces/quotes. Consider a structured matrix field (list of KEY=VAL) and expanding to -e args in the step.

Example change (conceptual, adjust as needed):

  • In the matrix, use env: ['NODE_ENV=production','LIVE_BASE_PATH=/live'] instead of env_flags.
  • In the run step:
env_args=()
for kv in ${{ join(matrix.env, ' ') }}; do
  env_args+=(-e "$kv")
done
docker run -d --name "${{ matrix.container }}" -p "${{ matrix.host_port }}:3000" "${env_args[@]}" "${{ matrix.image }}"

Also applies to: 113-117

scripts/bench-builds.sh (2)

166-181: Minor cleanup: remove unused local variable flagged by ShellCheck

The unit variable is declared but never used.

Apply this diff:

-  if have docker && docker buildx version >/dev/null 2>&1; then
+  if have docker && docker buildx version >/dev/null 2>&1; then
     # Try buildx du summary (not standardized across versions; best effort parsing)
-    local du
+    local du
     du="$(docker buildx du 2>/dev/null || true)"
     if [[ -n "$du" ]]; then
       # Look for a line with total size summary; fallback to whole output
-      local line size unit
+      local line size
       line="$(echo "$du" | tail -n 1)"

235-245: Optional: expose a verbose mode to show docker build logs during benches

Hiding build output keeps logs clean but can hinder debugging. Consider a VERBOSE switch to surface docker build logs when needed.

Example tweak:

  • Gate >/dev/null redirection on VERBOSE:
-    DOCKER_BUILDKIT=1 docker build --no-cache --progress=plain -f "$dockerfile" -t "$tag" "$ROOT_DIR" >/dev/null
+    if [[ "${VERBOSE:-}" == "1" ]]; then
+      DOCKER_BUILDKIT=1 docker build --no-cache --progress=plain -f "$dockerfile" -t "$tag" "$ROOT_DIR"
+    else
+      DOCKER_BUILDKIT=1 docker build --no-cache --progress=plain -f "$dockerfile" -t "$tag" "$ROOT_DIR" >/dev/null
+    fi

Repeat similarly for the warm build path.

Also applies to: 336-347

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 85ac151 and c596ad3.

📒 Files selected for processing (3)
  • .github/workflows/docker-smoke.yml (1 hunks)
  • scripts/bench-builds.sh (1 hunks)
  • scripts/smoke.sh (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/smoke.sh
🧰 Additional context used
🧬 Code graph analysis (1)
scripts/bench-builds.sh (1)
scripts/smoke.sh (3)
  • have (50-50)
  • log (52-52)
  • die (54-54)
🪛 Shellcheck (0.10.0)
scripts/bench-builds.sh

[warning] 174-174: unit appears unused. Verify use (or export if used externally).

(SC2034)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build and smoke test space
  • GitHub Check: Build and smoke test web
  • GitHub Check: Build and smoke test live
  • GitHub Check: Build and smoke test admin
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (3)
scripts/bench-builds.sh (3)

28-41: Solid Bash version gate for associative arrays

Clear, user-friendly guidance for macOS users. Approved.


78-124: Robust high-resolution time source selection

Good validation to avoid BSD date “%N” trap, and sane fallbacks. No change needed.


299-304: Good preflight checks for docker CLI and daemon availability

Early failure with actionable message. Approved.

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (4)
.github/workflows/docker-smoke.yml (2)

62-86: Replace fragile empty-matrix string check with a boolean output.

Comparing JSON to '{"include":[]}' is brittle. Emit and consume a has_targets boolean for clarity and safety.

       - name: Build matrix
         id: build-matrix
         uses: actions/github-script@v7
         with:
           script: |
             const include = [];
             const eventName = context.eventName;
             const anyCommon = '${{ steps.changes.outputs.common }}' === 'true';
             const changed = {
               web: '${{ steps.changes.outputs.web }}' === 'true',
               space: '${{ steps.changes.outputs.space }}' === 'true',
               admin: '${{ steps.changes.outputs.admin }}' === 'true',
               live: '${{ steps.changes.outputs.live }}' === 'true',
             };
             const add = (name, dockerfile, image, container, port, path, env_flags = "") => include.push({ name, dockerfile, image, container, host_port: port, path, env_flags });
             const buildAll = anyCommon || eventName === 'push' || eventName === 'workflow_dispatch';
             if (buildAll || changed.web) add('web', 'apps/web/Dockerfile.web', 'plane-web:ci-smoke', 'plane-web-ci', 3001, '/');
             if (buildAll || changed.space) add('space', 'apps/space/Dockerfile.space', 'plane-space:ci-smoke', 'plane-space-ci', 3002, '/spaces');
             if (buildAll || changed.admin) add('admin', 'apps/admin/Dockerfile.admin', 'plane-admin:ci-smoke', 'plane-admin-ci', 3003, '/god-mode');
             if (buildAll || changed.live) add('live', 'apps/live/Dockerfile.live', 'plane-live:ci-smoke', 'plane-live-ci', 3005, '/live/health', '-e NODE_ENV=production -e LIVE_BASE_PATH=/live');
-            if (include.length === 0) {
-              core.warning('No changes detected for any app. Matrix is empty. Skipping smoke tests.');
-            }
-            core.setOutput('matrix', JSON.stringify({ include }));
+            const hasTargets = include.length > 0;
+            if (!hasTargets) core.warning('No changes detected for any app. Matrix is empty. Skipping smoke tests.');
+            core.setOutput('matrix', JSON.stringify({ include }));
+            core.setOutput('has_targets', String(hasTargets));
@@
   smoke:
     name: Build and smoke test ${{ matrix.name }}
     runs-on: ubuntu-latest
     needs: determine-matrix
-    if: ${{ needs.determine-matrix.outputs.matrix != '{"include":[]}' }}
+    if: ${{ needs.determine-matrix.outputs.has_targets == 'true' }}

Also applies to: 87-96


7-16: Standardize quoting style for path globs and filters.

Use a consistent quoting style across triggers and filters for readability.

Example:

  • Prefer single quotes everywhere, or double quotes everywhere; avoid mixing.

Also applies to: 19-28, 47-61

scripts/bench-builds.sh (2)

142-159: Simplify human_bytes() and avoid log(0)/awk portability pitfalls.

Current awk path uses log() and can misbehave for 0 bytes. A simpler approach is safer.

 human_bytes() {
-  # Convert integer bytes to human readable
-  local bytes="$1"
-  local units=("B" "KB" "MB" "GB" "TB" "PB")
-  local idx=0
-  local value="$bytes"
-  while (( value >= 1024 && idx < ${#units[@]}-1 )); do
-    value=$(( value/1024 ))
-    idx=$(( idx+1 ))
-  done
-  # value is integer; but to preserve more precision, recompute with awk
-  awk -v b="$bytes" -v i="$idx" 'BEGIN {
-    split("B KB MB GB TB PB", u, " ");
-    val=b;
-    while (val>=1024 && i>0){ val/=1024; i-- }
-    printf("%.2f %s", val, u[(int((log(b)/log(1024)))+1)]);
-  }' 2>/dev/null || printf "%d %s" "$value" "${units[$idx]}"
+  local bytes="${1:-0}"
+  awk -v b="$bytes" 'BEGIN {
+    if (b < 0) b = 0
+    split("B KB MB GB TB PB", u, " ")
+    i = 1
+    while (b >= 1024 && i < 6) { b /= 1024; i++ }
+    printf("%.2f %s", b, u[i])
+  }' 2>/dev/null || printf "%s %s" "${bytes}" "B"
 }

272-295: Resolve conflicting flags: --no-prune vs --aggressive-prune.

Make precedence explicit to avoid surprising behavior when both are set.

 done

+# Resolve conflicting options
+if [[ "$AGGRESSIVE_PRUNE" == "true" && "$NO_PRUNE" == "true" ]]; then
+  log "Both --no-prune and --aggressive-prune specified; ignoring --no-prune."
+  NO_PRUNE="false"
+fi
🧹 Nitpick comments (5)
.github/workflows/docker-smoke.yml (3)

108-116: Consider pulling latest base layers during CI builds.

Adding --pull=always reduces cache-stale surprises on runners.

-          docker build -f ${{ matrix.dockerfile }} -t ${{ matrix.image }} .
+          docker build --pull=always -f ${{ matrix.dockerfile }} -t ${{ matrix.image }} .

118-134: Harden curl probe with timeouts to avoid long hangs.

Add connect and total timeouts; current loop interval alone won’t cap curl blocking time.

-            STATUS="$(curl -sS -o /dev/null -w "%{http_code}" -L "${URL}" || true)"
+            STATUS="$(curl --connect-timeout 2 --max-time 5 -sS -o /dev/null -w "%{http_code}" -L "${URL}" || true)"

1-2: Optional: add workflow-level concurrency to auto-cancel superseded runs.

Saves minutes on stacked pushes to the same ref.

 name: Docker build and smoke test for apps

 on:
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
scripts/bench-builds.sh (2)

171-178: Remove unused local variable to satisfy ShellCheck SC2034.

unit is declared but never used.

-      local line size unit
+      local line size

235-244: Optional: make build logs toggleable for debugging.

Silencing docker build can hinder diagnosis on failures. Consider a VERBOSE flag to tee logs.

-  if [[ "$cold" == "true" ]]; then
-    DOCKER_BUILDKIT=1 docker build --no-cache --progress=plain -f "$dockerfile" -t "$tag" "$ROOT_DIR" >/dev/null
-  else
-    DOCKER_BUILDKIT=1 docker build --progress=plain -f "$dockerfile" -t "$tag" "$ROOT_DIR" >/dev/null
-  fi
+  local quiet_redir=">/dev/null"
+  [[ "${VERBOSE:-0}" == "1" ]] && quiet_redir=""
+  if [[ "$cold" == "true" ]]; then
+    eval DOCKER_BUILDKIT=1 docker build --no-cache --progress=plain -f "\"$dockerfile\"" -t "\"$tag\"" "\"$ROOT_DIR\"" $quiet_redir
+  else
+    eval DOCKER_BUILDKIT=1 docker build --progress=plain -f "\"$dockerfile\"" -t "\"$tag\"" "\"$ROOT_DIR\"" $quiet_redir
+  fi
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c596ad3 and 066f6ac.

📒 Files selected for processing (3)
  • .github/workflows/docker-smoke.yml (1 hunks)
  • scripts/bench-builds.sh (1 hunks)
  • scripts/smoke.sh (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/smoke.sh
🧰 Additional context used
🪛 Shellcheck (0.10.0)
scripts/bench-builds.sh

[warning] 174-174: unit appears unused. Verify use (or export if used externally).

(SC2034)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build and smoke test admin
  • GitHub Check: Build and smoke test web
  • GitHub Check: Build and smoke test space
  • GitHub Check: Build and smoke test live
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
scripts/bench-builds.sh (2)

28-41: Good Bash version guard.

Clear error and actionable guidance for macOS users.


296-304: Good Docker daemon reachability check.

Early fail with clear guidance improves UX.

@lifeiscontent lifeiscontent force-pushed the feat/docker-frozen-lock branch 3 times, most recently from 9b0ec86 to f4cf0e3 Compare August 29, 2025 17:36
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
scripts/smoke.sh (1)

62-69: Add network timeouts to avoid hangs in http probes.

Without timeouts, curl/wget can block indefinitely on bad endpoints, stalling the loop.

-    local code
-    code="$(curl -fsS -o /dev/null -w "%{http_code}" "$url" || true)"
+    local code
+    code="$(curl --connect-timeout 3 --max-time 10 -fsS -o /dev/null -w "%{http_code}" "$url" || true)"
@@
-  elif have wget; then
-    wget -q --spider "$url" >/dev/null 2>&1
+  elif have wget; then
+    wget -q --timeout=10 --tries=1 --spider "$url" >/dev/null 2>&1
🧹 Nitpick comments (7)
scripts/smoke.sh (7)

63-66: Tighten 2xx/3xx match to exactly three digits.

Use 2??|3?? for clarity and precision.

-    case "$code" in
-      2*|3*) return 0 ;;
-      *) return 1 ;;
-    esac
+    case "$code" in
+      2??|3??) return 0 ;;
+      *)       return 1 ;;
+    esac

189-191: Fail fast if Docker daemon isn’t reachable.

Checking only for the CLI misses the “daemon not running” case.

-have docker || die "Docker is required. Please install Docker and ensure the daemon is running."
+have docker || die "Docker is required. Please install Docker and ensure the daemon is running."
+# Verify daemon availability early
+docker ps -q >/dev/null 2>&1 || die "Docker daemon not reachable. Start Docker and retry."

194-194: Trim whitespace when parsing --services.

Avoid accidental entries like " web" or "space ".

-IFS=',' read -r -a SELECTED <<< "$services"
+IFS=',' read -r -a SELECTED <<< "${services//[[:space:]]/}"

209-213: Confirm live’s base path is runtime-driven; if not, mirror build-arg approach.

If live resolves basePath at build time (like Next.js), passing LIVE_BASE_PATH only at docker run won’t take effect.

Option (if build-time): bake it during build and drop the runtime env.

-    live) run_container "$LIVE_CONT" "$LIVE_IMAGE" "$LIVE_PORT" -e LIVE_BASE_PATH="${LIVE_BASE_PATH}" ;;
+    live) run_container "$LIVE_CONT" "$LIVE_IMAGE" "$LIVE_PORT" ;;

And in the build case above:

-    live) build_image "live" "$LIVE_IMAGE" "apps/live/Dockerfile.live" ;;
+    live) build_image "live" "$LIVE_IMAGE" "apps/live/Dockerfile.live" --build-arg LIVE_BASE_PATH="${LIVE_BASE_PATH}" ;;

Want me to scan the repo to verify how live reads this setting?


102-107: Consider pulling base images fresh for more deterministic smoke builds.

Prevents stale cache from hiding broken base layers; toggleable if desired.

-  DOCKER_BUILDKIT=1 docker build \
+  DOCKER_BUILDKIT=1 docker build --pull=always \

233-241: Optional: show logs on failures to speed up triage.

Auto-print the last lines from failed services.

 if (( failures == 0 )); then
   log "Smoke test succeeded. Containers are running:"
 else
   log "Smoke test completed with ${failures} failure(s). Containers may still be running for inspection:"
+  # Dump brief logs to help triage
+  for svc in "${SELECTED[@]}"; do
+    case "$svc" in
+      web)   name="$WEB_CONT" ;;
+      admin) name="$ADMIN_CONT" ;;
+      space) name="$SPACE_CONT" ;;
+      live)  name="$LIVE_CONT" ;;
+    esac
+    docker ps -a --format '{{.Names}}' | grep -q "^${name}\$" && {
+      log "---- logs: ${name} (last 80 lines) ----"
+      docker logs --tail=80 "$name" || true
+    }
+  done
 fi

243-249: Nit: print URLs only for selected services.

Avoids confusion when a subset is chosen.

-URLs:
-  web:   ${web_url}
-  admin: ${admin_url}
-  space: ${space_url}
-  live:  ${live_url}
+URLs:
+$(printf '  %s\n' "${SELECTED[@]}" | awk '
+  /web/   {print "  web:   '"${web_url}"'"} 
+  /admin/ {print "  admin: '"${admin_url}"'"} 
+  /space/ {print "  space: '"${space_url}"'"} 
+  /live/  {print "  live:  '"${live_url}"'"}')
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 066f6ac and fc87b13.

📒 Files selected for processing (3)
  • .github/workflows/docker-smoke.yml (1 hunks)
  • scripts/bench-builds.sh (1 hunks)
  • scripts/smoke.sh (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/docker-smoke.yml
  • scripts/bench-builds.sh
🧰 Additional context used
🧬 Code graph analysis (1)
scripts/smoke.sh (1)
scripts/bench-builds.sh (3)
  • have (73-73)
  • die (77-77)
  • log (75-75)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
scripts/smoke.sh (1)

197-201: Good: admin/space basePath baked at build time.

Using build-args for NEXT_PUBLIC_ADMIN_BASE_PATH and NEXT_PUBLIC_SPACE_BASE_PATH aligns with Next.js build-time config.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
scripts/smoke.sh (1)

62-69: Harden HTTP check: add timeouts and precise status match (2xx/3xx)

Prevent hangs and avoid overly broad globbing by matching 3-digit codes and adding curl/wget timeouts.

-    local code
-    code="$(curl -fsS -o /dev/null -w "%{http_code}" "$url" || true)"
-    case "$code" in
-      2*|3*) return 0 ;;
-      *) return 1 ;;
-    esac
+    local code
+    code="$(curl --connect-timeout 3 --max-time 10 -fsS -o /dev/null -w "%{http_code}" "$url" || true)"
+    case "$code" in
+      2??|3??) return 0 ;;
+      *)       return 1 ;;
+    esac
   elif have wget; then
-    wget -q --spider "$url" >/dev/null 2>&1
+    wget -q --timeout=10 --spider "$url" >/dev/null 2>&1
     return $?
.github/workflows/docker-smoke.yml (2)

66-95: Expose has_targets boolean and use it instead of brittle string-compare on the matrix

Avoid comparing JSON strings; surface a boolean and gate the job on it.

     outputs:
-      matrix: ${{ steps.build-matrix.outputs.matrix }}
+      matrix: ${{ steps.build-matrix.outputs.matrix }}
+      has_targets: ${{ steps.build-matrix.outputs.has_targets }}
@@
       - name: Build matrix
         id: build-matrix
         uses: actions/github-script@v7
         with:
           script: |
             const include = [];
@@
             if (include.length === 0) {
               core.warning('No changes detected for any app. Matrix is empty. Skipping smoke tests.');
             }
             core.setOutput('matrix', JSON.stringify({ include }));
+            core.setOutput('has_targets', String(include.length > 0));
@@
   smoke:
@@
-    if: ${{ needs.determine-matrix.outputs.matrix != '{"include":[]}' }}
+    if: ${{ needs.determine-matrix.outputs.has_targets == 'true' }}

117-121: Validate env_flags before injecting into docker run to avoid command injection

Constrain env_flags to safe “-e VAR=VALUE” patterns and use a sanitized variable.

+      - name: Validate env_flags (${{ matrix.name }})
+        id: validate_env_flags
+        shell: bash
+        run: |
+          set -euo pipefail
+          ENV_FLAGS='${{ matrix.env_flags }}'
+          if [[ -z "$ENV_FLAGS" ]]; then
+            echo "SAFE_ENV_FLAGS=" >> $GITHUB_ENV
+          elif [[ "$ENV_FLAGS" =~ ^(-e[[:space:]][A-Za-z_][A-Za-z0-9_]*=[^[:space:]]+[[:space:]]?)*$ ]]; then
+            echo "SAFE_ENV_FLAGS=$ENV_FLAGS" >> $GITHUB_ENV
+          else
+            echo "Unsafe env_flags detected: $ENV_FLAGS" >&2
+            exit 1
+          fi
+
       - name: Run container (${{ matrix.name }})
         run: |
-          docker run -d --name ${{ matrix.container }} -p ${{ matrix.host_port }}:3000 ${{ matrix.env_flags }} ${{ matrix.image }}
+          docker run -d --name ${{ matrix.container }} -p ${{ matrix.host_port }}:3000 $SAFE_ENV_FLAGS ${{ matrix.image }}
           docker ps -a
🧹 Nitpick comments (1)
.github/workflows/docker-smoke.yml (1)

128-137: Clarify timeout comment

Note request time adds overhead to the nominal 120s loop timing.

-          # Try up to ~120 seconds (60 attempts * 2s)
+          # Try up to at least ~120 seconds (60 attempts * 2s), plus per-request time
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fc87b13 and f4cf0e3.

📒 Files selected for processing (3)
  • .github/workflows/docker-smoke.yml (1 hunks)
  • scripts/bench-builds.sh (1 hunks)
  • scripts/smoke.sh (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
scripts/smoke.sh (1)
scripts/bench-builds.sh (3)
  • have (73-73)
  • die (77-77)
  • log (75-75)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build and smoke test web
@lifeiscontent lifeiscontent force-pushed the feat/docker-frozen-lock branch from f4cf0e3 to c620c12 Compare August 29, 2025 17:49
@lifeiscontent lifeiscontent requested a review from Copilot August 29, 2025 17:50

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
scripts/smoke.sh (3)

60-66: HTTP status matcher fixed — LGTM

The 2xx/3xx case pattern is correct and robust.


165-173: CLI precedence over env for --services — LGTM

Parsing honors the CLI flag and later splits only the resolved services value.

Also applies to: 199-199


201-209: Unknown service validation in both build and run loops — LGTM

Fast-fail behavior is consistent now.

Also applies to: 211-219

🧹 Nitpick comments (2)
scripts/smoke.sh (2)

24-27: Validate integer timing vars or switch to time-based deadline

SLEEP_INTERVAL and MAX_WAIT are used in integer arithmetic; non-integer env values (e.g., “0.5”) will break the loop.

Minimal fix (validate integers and die early):

 MAX_WAIT="${MAX_WAIT:-120}"
 SLEEP_INTERVAL="${SLEEP_INTERVAL:-2}"
+[[ "$MAX_WAIT" =~ ^[0-9]+$ ]] || die "MAX_WAIT must be an integer"
+[[ "$SLEEP_INTERVAL" =~ ^[0-9]+$ ]] || die "SLEEP_INTERVAL must be an integer"

Alternatively, switch wait_for_url to a timestamp deadline (supports fractional sleeps) if you want to allow non-integers.

Also applies to: 75-87


192-196: Daemon reachability check added — good

You now fail fast if Docker isn’t reachable. Consider docker info >/dev/null (slightly clearer intent) instead of docker ps -q.

Optional tweak:

-docker ps -q >/dev/null 2>&1 || die "Docker daemon not reachable. Start Docker and retry."
+docker info >/dev/null 2>&1 || die "Docker daemon not reachable. Start Docker and retry."
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f4cf0e3 and c620c12.

📒 Files selected for processing (3)
  • .github/workflows/docker-smoke.yml (1 hunks)
  • scripts/bench-builds.sh (1 hunks)
  • scripts/smoke.sh (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/docker-smoke.yml
  • scripts/bench-builds.sh
🧰 Additional context used
🪛 Shellcheck (0.10.0)
scripts/smoke.sh

[warning] 262-266: Remove surrounding $() to avoid executing output (or use eval if intentional).

(SC2091)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build and smoke test admin
  • GitHub Check: Build and smoke test space
  • GitHub Check: Build and smoke test live
  • GitHub Check: Build and smoke test web
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
scripts/smoke.sh (1)

203-206: Request: verify Dockerfiles consume the passed build args

You’re passing --build-arg for admin/space/live; ensure the corresponding Dockerfiles declare ARG and wire to the build (and Next.js sees them at build time).

Would you like me to scan the repo for:

  • apps/admin|space|live Dockerfiles containing ARG NEXT_PUBLIC_*_BASE_PATH/ARG LIVE_BASE_PATH
  • next.config.js reading these envs for basePath?
@lifeiscontent lifeiscontent force-pushed the feat/docker-frozen-lock branch from 6540940 to f1d6fa2 Compare August 29, 2025 18:29
lifeiscontent

This comment was marked as resolved.

@lifeiscontent lifeiscontent force-pushed the feat/docker-frozen-lock branch from f1d6fa2 to 01dd81c Compare August 29, 2025 18:49
@lifeiscontent lifeiscontent requested a review from Copilot August 29, 2025 18:51
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds comprehensive Docker smoke testing infrastructure to automate building and testing app containers. The changes include CI workflow automation, local testing capabilities, and Docker configuration improvements for security and health monitoring.

  • Adds a CI workflow that builds app images conditionally based on changed files and runs readiness tests
  • Implements local smoke testing scripts for developer verification with build timing benchmarks
  • Enhances Dockerfiles with proper user security, health checks, and production environment setup

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
scripts/smoke.sh Local smoke test CLI for building, running, and probing multiple apps
scripts/bench-builds.sh Benchmarking script comparing cold vs warm Docker builds with timing/size metrics
apps/web/Dockerfile.web Security improvements with proper user setup and health check addition
apps/space/Dockerfile.space User security and health check enhancements matching web app pattern
apps/live/Dockerfile.live Complete security setup with user creation and health monitoring
apps/admin/Dockerfile.admin Consistent security and health check implementation
.github/workflows/docker-smoke.yml CI workflow for automated Docker builds and smoke testing

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (7)
apps/space/Dockerfile.space (1)

104-105: Healthcheck: avoid downloading bodies.

Consider using a HEAD/spider probe to save bandwidth and speed up checks. If BusyBox wget supports it in your base, switch to --spider; otherwise, current approach is acceptable.

Example:

-HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=5 \
-  CMD wget -qO- http://localhost:3000/spaces > /dev/null || exit 1
+HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=5 \
+  CMD wget -q --spider http://localhost:3000/spaces || exit 1
apps/web/Dockerfile.web (1)

121-122: Healthcheck micro-optimization.

Prefer a metadata-only probe to avoid fetching the HTML body; retain current form if BusyBox wget lacks spider mode.

-HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=5 \
-  CMD wget -qO- http://localhost:3000/ > /dev/null || exit 1
+HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=5 \
+  CMD wget -q --spider http://localhost:3000/ || exit 1
apps/live/Dockerfile.live (2)

71-72: Healthcheck path looks correct; consider spider mode.

/live/health is a good target. Optionally avoid downloading response bodies with spider/HEAD if available.

-HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=5 \
-  CMD wget -qO- http://localhost:3000/live/health > /dev/null || exit 1
+HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=5 \
+  CMD wget -q --spider http://localhost:3000/live/health || exit 1

28-29: Avoid unnecessary apk update.

apk add --no-cache ... already pulls the latest index; apk update adds an extra layer and is typically unnecessary.

-RUN apk update
 RUN apk add --no-cache libc6-compat

(Repeat for the installer stage.)

apps/admin/Dockerfile.admin (1)

104-105: Healthcheck: prefer metadata-only probe.

Optionally switch to spider to avoid body downloads; keep current if not supported in your wget.

-HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=5 \
-  CMD wget -qO- http://localhost:3000/god-mode > /dev/null || exit 1
+HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=5 \
+  CMD wget -q --spider http://localhost:3000/god-mode || exit 1
scripts/bench-builds.sh (2)

228-245: Prefer array-built docker commands; avoid duplication.

Current branching duplicates the build invocation. Building the command in an array reduces repetition and quoting pitfalls.

-  if [[ "${VERBOSE:-0}" == "1" ]]; then
-    if [[ "$cold" == "true" ]]; then
-      DOCKER_BUILDKIT=1 docker build --no-cache --progress=plain -f "$dockerfile" -t "$tag" "$ROOT_DIR"
-    else
-      DOCKER_BUILDKIT=1 docker build --progress=plain -f "$dockerfile" -t "$tag" "$ROOT_DIR"
-    fi
-  else
-    if [[ "$cold" == "true" ]]; then
-      DOCKER_BUILDKIT=1 docker build --no-cache --progress=plain -f "$dockerfile" -t "$tag" "$ROOT_DIR" >/dev/null
-    else
-      DOCKER_BUILDKIT=1 docker build --progress=plain -f "$dockerfile" -t "$tag" "$ROOT_DIR" >/dev/null
-    fi
-  fi
+  local -a cmd=(docker build --progress=plain -f "$dockerfile" -t "$tag" "$ROOT_DIR")
+  [[ "$cold" == "true" ]] && cmd=(docker build --no-cache --progress=plain -f "$dockerfile" -t "$tag" "$ROOT_DIR")
+  if [[ "${VERBOSE:-0}" == "1" ]]; then
+    DOCKER_BUILDKIT=1 "${cmd[@]}"
+  else
+    DOCKER_BUILDKIT=1 "${cmd[@]}" >/dev/null
+  fi

360-383: Nice summary output; minor: align headers for long service names.

If new services have longer names, consider dynamic column widths; current is fine for the four services.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c620c12 and 01dd81c.

📒 Files selected for processing (7)
  • .github/workflows/docker-smoke.yml (1 hunks)
  • apps/admin/Dockerfile.admin (2 hunks)
  • apps/live/Dockerfile.live (2 hunks)
  • apps/space/Dockerfile.space (2 hunks)
  • apps/web/Dockerfile.web (2 hunks)
  • scripts/bench-builds.sh (1 hunks)
  • scripts/smoke.sh (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/docker-smoke.yml
  • scripts/smoke.sh
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Build and smoke test web
  • GitHub Check: Build and smoke test admin
  • GitHub Check: Build and smoke test live
  • GitHub Check: Build and smoke test space
  • GitHub Check: Build and lint web apps
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (8)
apps/space/Dockerfile.space (1)

100-100: Good hardening: production runtime.

Setting NODE_ENV=production in the runner stage is correct and prevents dev-mode behavior.

apps/web/Dockerfile.web (1)

117-117: Good hardening: production runtime.

apps/live/Dockerfile.live (2)

47-47: Nice: prune dev deps in runtime image.

pnpm prune --prod after build reduces attack surface and image size.


67-67: Good hardening: production runtime.

apps/admin/Dockerfile.admin (1)

100-100: Good hardening: production runtime.

scripts/bench-builds.sh (3)

28-41: Bash 4+ guard: good fail-fast UX.

Clear messaging for macOS users; early exit prevents confusing runtime errors with associative arrays.


79-125: now_ns(): robust cross-platform detection — nice.

Validates %s%N output, falls back through EPOCHREALTIME and perl to seconds. Solid and portable.


310-315: Preflight docker checks: great.

Verifying both CLI presence and daemon reachability avoids late-stage failures.

@lifeiscontent lifeiscontent force-pushed the feat/docker-frozen-lock branch from 01dd81c to c9571a4 Compare October 6, 2025 14:29
Copy link
Contributor

@pratapalakshmi pratapalakshmi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

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

Labels

None yet

4 participants