# syntax=docker/dockerfile:1
ARG         AWS_ACCOUNT=086679231553
ARG         PARENT_IMAGE_TAG=node24
ARG         BASE=${AWS_ACCOUNT}.dkr.ecr.us-east-1.amazonaws.com/docker-parent-images:${PARENT_IMAGE_TAG}
ARG         DDTRACE_ADDONS="@datadog/native-appsec @datadog/native-iast-taint-tracking @datadog/native-metrics @datadog/pprof"


##### deps #####
# Workspace manifests + all dependencies. Split from dev-deps so the
# expensive install + native addon rebuild layers are only busted by
# package.json / lockfile changes, not source changes.
################
FROM        ${BASE} AS deps
WORKDIR     /var/app
ENV         CI=true
COPY        .npmrc package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY        --parents apps/*/package.json packages/*/package.json ./
USER        root
RUN         corepack enable \
 && corepack prepare --activate \
 && chown -R node:node /var/app \
 && mkdir -p /home/node/.local/share/pnpm \
 && chown -R node:node /home/node/.local
USER        node
RUN         --mount=type=secret,id=GITHUB_NPM_TOKEN,required=true,uid=1000 \
            --mount=type=cache,target=/home/node/.local/share/pnpm/store,uid=1000 \
            GITHUB_NPM_TOKEN=$(cat /run/secrets/GITHUB_NPM_TOKEN) \
            pnpm install --frozen-lockfile --ignore-scripts
# Compile dd-trace native addons that --ignore-scripts skipped.
# These provide native profiling, metrics, and AppSec instrumentation
# in production. Without them dd-trace falls back to JS-only mode.
ARG         DDTRACE_ADDONS
RUN         pnpm rebuild ${DDTRACE_ADDONS}


##### dev-deps #####
# Full source for every package, with library packages built.
####################
FROM        deps AS dev-deps
# Root configs
COPY        --chown=node:node tsconfig.base.json tsconfig.lib.json tsconfig.app-base.json eslint.base.mjs vitest.shared.ts .prettierrc .prettierignore ./

## ── Tier 0: library packages with no workspace dependencies ─────────
# These build first. Changing a tier-0 package busts only tiers 0+1.
# @coda/async (standalone — zero deps)
COPY        --chown=node:node packages/async/tsconfig.json packages/async/tsconfig.test.json packages/async/tsdown.config.ts packages/async/eslint.config.mjs ./packages/async/
COPY        --chown=node:node packages/async/src ./packages/async/src
# @coda/data-structures (standalone — zero deps)
COPY        --chown=node:node packages/data-structures/tsconfig.json packages/data-structures/tsconfig.test.json packages/data-structures/tsdown.config.ts packages/data-structures/eslint.config.mjs ./packages/data-structures/
COPY        --chown=node:node packages/data-structures/src ./packages/data-structures/src
# @coda/search (depends on @coda/data-structures)
COPY        --chown=node:node packages/search/tsconfig.json packages/search/tsconfig.test.json packages/search/tsdown.config.ts packages/search/eslint.config.mjs ./packages/search/
COPY        --chown=node:node packages/search/src ./packages/search/src
# @coda/sandbox (standalone — zero deps)
COPY        --chown=node:node packages/sandbox/tsconfig.json packages/sandbox/tsconfig.test.json packages/sandbox/tsdown.config.ts packages/sandbox/eslint.config.mjs ./packages/sandbox/
COPY        --chown=node:node packages/sandbox/src ./packages/sandbox/src
# api-common (proto-generated pagination + RPC utilities)
COPY        --chown=node:node packages/api-common/tsconfig.json packages/api-common/tsconfig.test.json packages/api-common/tsdown.config.ts packages/api-common/eslint.config.mjs ./packages/api-common/
COPY        --chown=node:node packages/api-common/proto ./packages/api-common/proto
COPY        --chown=node:node packages/api-common/gen ./packages/api-common/gen
COPY        --chown=node:node packages/api-common/src ./packages/api-common/src
# common (depends on @coda/data-structures)
COPY        --chown=node:node packages/common/tsconfig.json packages/common/tsconfig.test.json packages/common/tsdown.config.ts packages/common/eslint.config.mjs packages/common/.prettierignore ./packages/common/
COPY        --chown=node:node packages/common/src ./packages/common/src
# core-api (REST types + proto-generated ConnectRPC services)
COPY        --chown=node:node packages/core-api/tsconfig.json packages/core-api/tsconfig.test.json packages/core-api/tsdown.config.ts packages/core-api/eslint.config.mjs packages/core-api/.prettierignore ./packages/core-api/
COPY        --chown=node:node packages/core-api/src ./packages/core-api/src
COPY        --chown=node:node packages/core-api/gen ./packages/core-api/gen
# db (+ Prisma schema)
COPY        --chown=node:node packages/db/tsconfig.json packages/db/tsconfig.test.json packages/db/tsdown.config.ts packages/db/prisma.config.ts packages/db/eslint.config.mjs packages/db/.prettierignore ./packages/db/
COPY        --chown=node:node packages/db/src ./packages/db/src
COPY        --chown=node:node packages/db/prisma ./packages/db/prisma
# Proto-generated RPC packages (depend on common for RpcResult type)
COPY        --chown=node:node packages/search-api/tsconfig.json packages/search-api/tsconfig.test.json packages/search-api/tsdown.config.ts packages/search-api/eslint.config.mjs packages/search-api/.prettierignore ./packages/search-api/
COPY        --chown=node:node packages/search-api/src ./packages/search-api/src
COPY        --chown=node:node packages/search-api/gen ./packages/search-api/gen
COPY        --chown=node:node packages/runner-api/tsconfig.json packages/runner-api/tsconfig.test.json packages/runner-api/tsdown.config.ts packages/runner-api/eslint.config.mjs packages/runner-api/.prettierignore ./packages/runner-api/
COPY        --chown=node:node packages/runner-api/src ./packages/runner-api/src
COPY        --chown=node:node packages/runner-api/gen ./packages/runner-api/gen
COPY        --chown=node:node packages/admin-api/tsconfig.json packages/admin-api/tsconfig.test.json packages/admin-api/tsdown.config.ts packages/admin-api/eslint.config.mjs packages/admin-api/.prettierignore ./packages/admin-api/
COPY        --chown=node:node packages/admin-api/src ./packages/admin-api/src
COPY        --chown=node:node packages/admin-api/gen ./packages/admin-api/gen
# tools-api (proto-generated tool service definitions)
COPY        --chown=node:node packages/tools-api/tsconfig.json packages/tools-api/tsconfig.test.json packages/tools-api/tsdown.config.ts packages/tools-api/eslint.config.mjs packages/tools-api/.prettierignore ./packages/tools-api/
COPY        --chown=node:node packages/tools-api/src ./packages/tools-api/src
COPY        --chown=node:node packages/tools-api/gen ./packages/tools-api/gen
# datasources-api (proto-generated datasource service definitions)
COPY        --chown=node:node packages/datasources-api/tsconfig.json packages/datasources-api/tsconfig.test.json packages/datasources-api/tsdown.config.ts packages/datasources-api/eslint.config.mjs packages/datasources-api/.prettierignore ./packages/datasources-api/
COPY        --chown=node:node packages/datasources-api/src ./packages/datasources-api/src
COPY        --chown=node:node packages/datasources-api/gen ./packages/datasources-api/gen
# dashboards-api (proto-generated dashboard service definitions)
COPY        --chown=node:node packages/dashboards-api/tsconfig.json packages/dashboards-api/tsconfig.test.json packages/dashboards-api/tsdown.config.ts packages/dashboards-api/eslint.config.mjs packages/dashboards-api/.prettierignore ./packages/dashboards-api/
COPY        --chown=node:node packages/dashboards-api/src ./packages/dashboards-api/src
COPY        --chown=node:node packages/dashboards-api/gen ./packages/dashboards-api/gen
# Generate Prisma client (skipped by --ignore-scripts).
RUN         pnpm --filter @coda/db db:generate
# Build tier 0 — pnpm respects the dependency graph:
# @coda/async, @coda/data-structures, @coda/sandbox build first (zero deps),
# then @coda/search (depends on data-structures),
# then common (depends on data-structures),
# then API packages (depend on common).
RUN         pnpm --filter @coda/async \
                 --filter @coda/data-structures \
                 --filter @coda/search \
                 --filter @coda/sandbox \
                 --filter @coda/common \
                 --filter @coda/api-common \
                 --filter @coda/core-api \
                 --filter @coda/db \
                 --filter @coda/admin-api \
                 --filter @coda/search-api \
                 --filter @coda/runner-api \
                 --filter @coda/tools-api \
                 --filter @coda/datasources-api \
                 --filter @coda/dashboards-api \
                 build

## ── Tier 1: library packages that depend on tier 0 ───────────────────
# extensions depends on common.
# memory depends on common.
# Changing only these re-runs this tier without rebuilding tier 0.
COPY        --chown=node:node packages/extensions/tsconfig.json packages/extensions/tsdown.config.ts packages/extensions/eslint.config.mjs packages/extensions/.prettierignore ./packages/extensions/
COPY        --chown=node:node packages/extensions/src ./packages/extensions/src
COPY        --chown=node:node packages/extensions/graphql ./packages/extensions/graphql
COPY        --chown=node:node packages/extensions/snowflake ./packages/extensions/snowflake
COPY        --chown=node:node packages/extensions/tools ./packages/extensions/tools
# @coda/memory (depends on @coda/data-structures + @coda/common)
COPY        --chown=node:node packages/memory/tsconfig.json packages/memory/tsconfig.test.json packages/memory/tsdown.config.ts packages/memory/eslint.config.mjs ./packages/memory/
COPY        --chown=node:node packages/memory/src ./packages/memory/src
# Build tier 1
RUN         pnpm --filter @coda/extensions \
                 --filter @coda/memory \
                 build

## ── App source (not built in dev-deps — apps use tsx --watch in dev) ─
COPY        --chown=node:node apps/server/tsdown.config.ts apps/server/tsconfig.json apps/server/eslint.config.mjs ./apps/server/
COPY        --chown=node:node apps/server/src ./apps/server/src
COPY        --chown=node:node apps/search/tsconfig.json apps/search/tsdown.config.ts apps/search/eslint.config.mjs apps/search/.prettierignore ./apps/search/
COPY        --chown=node:node apps/search/src ./apps/search/src
COPY        --chown=node:node apps/search/benchmarks ./apps/search/benchmarks
COPY        --chown=node:node apps/runner/tsconfig.json apps/runner/tsdown.config.ts apps/runner/eslint.config.mjs apps/runner/.prettierignore ./apps/runner/
COPY        --chown=node:node apps/runner/src ./apps/runner/src
COPY        --chown=node:node apps/platform/tsconfig.json apps/platform/tsconfig.test.json apps/platform/tsdown.config.ts apps/platform/eslint.config.mjs apps/platform/.prettierignore ./apps/platform/
COPY        --chown=node:node apps/platform/src ./apps/platform/src
COPY        --chown=node:node apps/client/tsconfig.app.json apps/client/tsconfig.node.json apps/client/tsconfig.json apps/client/tsconfig.test.json \
            apps/client/eslint.config.mjs apps/client/.prettierignore apps/client/vite.config.ts apps/client/index.html apps/client/components.json ./apps/client/
COPY        --chown=node:node apps/client/src ./apps/client/src
COPY        --chown=node:node apps/client/public ./apps/client/public


##### dev #####
# Hot-reloading dev server (used by docker-compose).
# Library packages are pre-built in dev-deps — dist/ is already present.
###############
FROM        dev-deps AS dev
ENV         PORT=8080
EXPOSE      8080
# Ensure .env exists so `node --env-file=.env` doesn't crash in Docker;
# docker-compose env_file provides the actual variables.
RUN         touch apps/server/.env
CMD         ["pnpm", "--filter", "@coda/server-app", "dev"]


##### platform-dev #####
# Hot-reloading platform service (permissions, tenancy) for Docker Compose.
########################
FROM        dev-deps AS platform-dev
ENV         PORT=8082
EXPOSE      8082
RUN         touch apps/platform/.env
CMD         ["pnpm", "--filter", "@coda/platform-service", "dev"]


##### migrate #####
# One-shot Prisma migration runner for Docker Compose.
# Applies pending migrations, seeds reference data, and exits.
####################
FROM        dev-deps AS migrate
ENTRYPOINT  ["sh", "-c", "set -e; pnpm --filter @coda/db migrate:deploy && pnpm db:seed"]


##### client-dev #####
# Vite dev server with HMR for local Docker development.
# Port 6005 matches the host-mapped port so log output shows the correct URL.
#######################
FROM        dev-deps AS client-dev
EXPOSE      6005
CMD         ["pnpm", "dev:client"]


##### test-base #####
# Shared base for all test targets.
# Test files are co-located in src/**/__tests__/ (already in dev-deps).
#####################
FROM        dev-deps AS test-base
# Auto-discover vitest configs and test tsconfigs.
# New packages are picked up without editing this section.
COPY        --chown=node:node --parents packages/*/vitest.config* packages/*/tsconfig.test.json ./
COPY        --chown=node:node --parents apps/*/vitest.config* apps/*/tsconfig.test.json ./


##### test-functional #####
# Functional tests — runs against real MySQL + Redis.
############################
FROM        test-base AS test-functional
ENTRYPOINT  ["sh", "-c", "pnpm -r ${PNPM_FILTER:-} --if-present test:functional"]


##### test-integration #####
# Integration tests — HTTP requests against a running server.
#############################
FROM        test-base AS test-integration
ENTRYPOINT  ["sh", "-c", "pnpm -r ${PNPM_FILTER:-} --if-present test:integration"]


##### lint-and-test #####
# Format-check, lint, typecheck, and unit-test affected packages.
# PNPM_FILTER env var controls scope (e.g. "--filter @coda/server-app --filter @coda/core-api").
# When empty, all packages are tested (safe default).
#########################
FROM        test-base AS lint-and-test
# format:check + lint run in parallel with typecheck (independent steps).
# Tests run after both pass.
ENTRYPOINT  ["sh", "-c", "pnpm exec concurrently --kill-others-on-fail --names fmt+lint,typecheck \"pnpm ${PNPM_FILTER:-} format:check && pnpm ${PNPM_FILTER:-} lint\" \"pnpm ${PNPM_FILTER:-} typecheck\" && pnpm ${PNPM_FILTER:-} test:unit --coverage"]


##### build-artifacts #####
# Build server TypeScript only — library packages use source-first exports
# and db:generate already ran in dev-deps.
###########################
FROM        dev-deps AS build-artifacts
RUN         pnpm build:server


##### server-ci #####
# Compiled server for CI integration tests (no hot-reload, no dd-trace).
# Uses the same build artifacts as production but runs in the full
# workspace context (not pnpm deploy) for simplicity.
######################
FROM        build-artifacts AS server-ci
ENV         PORT=8080
EXPOSE      8080
RUN         touch apps/server/.env
CMD         ["pnpm", "--filter", "@coda/server-app", "start"]


##### client-build #####
# Build the React client locally. Used by docker-compose for local dev.
# In CI, the client is pre-built via suiteAppBuild and passed in as an
# additional build context instead.
########################
FROM        dev-deps AS client-build
ARG         AUTH0_DOMAIN
ARG         AUTH0_CLIENT_ID
ARG         AUTH0_AUDIENCE
ARG         SENTRY_DSN
RUN         test -n "${AUTH0_DOMAIN}" || (echo "ERROR: AUTH0_DOMAIN build-arg is required" && exit 1) \
 && test -n "${AUTH0_CLIENT_ID}" || (echo "ERROR: AUTH0_CLIENT_ID build-arg is required" && exit 1) \
 && test -n "${AUTH0_AUDIENCE}" || (echo "ERROR: AUTH0_AUDIENCE build-arg is required" && exit 1) \
 && pnpm --filter @coda/client-app build


##### pnpm-deploy-server #####
# Standalone production deployment with flat node_modules.
# pnpm deploy resolves workspace deps (via their "files" field) and
# creates a self-contained directory — no symlinks, proper peer dep
# resolution. Fixes the snowflake-sdk ERR_MODULE_NOT_FOUND that
# occurred when Docker COPY broke pnpm's virtual store topology.
################################
FROM        build-artifacts AS pnpm-deploy-server
USER        root
RUN         mkdir -p /deploy && chown node:node /deploy
USER        node
RUN         pnpm deploy --filter @coda/server-app --prod /deploy/server
# Rebuild dd-trace native addons in the flat deploy output.
# pnpm deploy copies packages from the content store (pre-build),
# so native addons need a rebuild in the target directory.
ARG         DDTRACE_ADDONS
RUN         cd /deploy/server && npm rebuild ${DDTRACE_ADDONS}


##### deploy-server #####
# Production server — no frontend static files.
# Single COPY from the pnpm deploy output gives a flat, self-contained layout.
##########################
FROM        ${BASE} AS deploy-server
WORKDIR     /var/app
USER        node
COPY        --chown=node:node --from=pnpm-deploy-server /deploy/server ./
ENV         NODE_ENV=production PORT=8080
EXPOSE      8080
LABEL       org.opencontainers.image.source="https://github.com/theorchard/ows-coda"
HEALTHCHECK --interval=10s --timeout=5s --retries=5 --start-period=15s \
  CMD node -e "require('http').get('http://localhost:8080/health',r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))"
CMD         ["node", "--import", "dd-trace/initialize.mjs", "dist/index.mjs"]


##### deploy-local #####
# Full stack for local dev — builds client inside Docker.
#########################
FROM        deploy-server AS deploy-local
ENV         CLIENT_DIST_PATH=/var/app/apps/client/dist
COPY        --chown=node:node --from=client-build /var/app/apps/client/dist ./apps/client/dist


##### deploy #####
# Full stack for CI — client is pre-built via suiteAppBuild and passed
# in as the "client-dist" additional build context.
##################
FROM        deploy-server AS deploy
ENV         CLIENT_DIST_PATH=/var/app/apps/client/dist
COPY        --chown=node:node --from=client-dist / ./apps/client/dist
