diff --git a/Dockerfile b/Dockerfile index cacad4a..271b0fb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,24 +17,47 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \ FROM deps AS builder ARG NEXT_PUBLIC_API_URL= -ARG DATABASE_URL=file:data/fiscal.sqlite ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} -ENV DATABASE_URL=${DATABASE_URL} +ENV DATABASE_URL=file:/tmp/fiscal-build.sqlite +ENV BETTER_AUTH_SECRET=fiscal-docker-build-secret-fiscal-docker-build-secret +ENV BETTER_AUTH_BASE_URL=http://127.0.0.1:3000 +ENV BETTER_AUTH_TRUSTED_ORIGINS=http://127.0.0.1:3000,http://localhost:3000 ENV NEXT_TELEMETRY_DISABLED=1 -ENV WORKFLOW_TARGET_WORLD=@workflow/world-postgres -ENV WORKFLOW_LOCAL_DATA_DIR=/app/.workflow-data -ENV RUN_WORKFLOW_SETUP_ON_START=true -ENV RUN_DB_MIGRATIONS_ON_START=true -COPY . . +ENV SKIP_NEXT_TYPECHECK_ON_BUILD=true +ENV NEXT_BUILD_CPUS=1 +ENV WORKFLOW_TARGET_WORLD=local +ENV WORKFLOW_LOCAL_DATA_DIR=/tmp/.workflow-data +ENV RUN_WORKFLOW_SETUP_ON_START=false +ENV RUN_DB_MIGRATIONS_ON_START=false + +# Force the Rust sidecar build to complete before the Next.js build starts. +# BuildKit otherwise runs these independent stages in parallel and can OOM locally. +COPY --from=rust-builder /app/bin/fiscal-xbrl /tmp/fiscal-xbrl +COPY next.config.js tsconfig.json postcss.config.js tailwind.config.js drizzle.config.ts instrumentation.ts next-env.d.ts ./ +COPY app ./app +COPY components ./components +COPY contracts ./contracts +COPY drizzle ./drizzle +COPY hooks ./hooks +COPY lib ./lib +COPY rust ./rust +COPY scripts ./scripts + RUN --mount=type=cache,target=/app/.next/cache \ - mkdir -p public /app/.workflow-data && bun run build + mkdir -p data public /tmp/.workflow-data /app/.output \ + && rm -f /tmp/fiscal-xbrl \ + && bun run generate \ + && bun --smol --bun next build --turbopack \ + && bun build --target=bun --outfile /app/.output/bootstrap-production.js scripts/bootstrap-production.ts FROM oven/bun:1.3.5-alpine AS runner WORKDIR /app ENV NODE_ENV=production +ENV HOSTNAME=0.0.0.0 ARG NEXT_PUBLIC_API_URL= ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} +ENV DATABASE_URL=file:/app/data/fiscal.sqlite ENV NEXT_TELEMETRY_DISABLED=1 ENV WORKFLOW_TARGET_WORLD=@workflow/world-postgres ENV WORKFLOW_LOCAL_DATA_DIR=/app/.workflow-data @@ -44,15 +67,10 @@ ENV RUN_DB_MIGRATIONS_ON_START=true COPY --from=builder /app/public ./public COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/static ./.next/static +COPY --from=builder /app/.output/bootstrap-production.js ./bootstrap-production.js COPY --from=builder /app/drizzle ./drizzle -COPY --from=builder /app/scripts ./scripts -COPY --from=builder /app/lib ./lib -COPY --from=builder /app/contracts ./contracts COPY --from=builder /app/rust/taxonomy ./rust/taxonomy -COPY --from=builder /app/tsconfig.json ./tsconfig.json -COPY --from=deps /app/node_modules ./node_modules -COPY --from=deps /app/package.json ./package.json -COPY --from=deps /app/bun.lock ./bun.lock +COPY --from=deps /app/node_modules/@workflow/world-postgres/src/drizzle/migrations ./workflow-migrations COPY --from=rust-builder /app/bin/fiscal-xbrl ./bin/fiscal-xbrl RUN mkdir -p /app/data /app/.workflow-data /app/bin /app/.cache/xbrl && chmod +x /app/bin/fiscal-xbrl @@ -63,5 +81,6 @@ ENV PORT=3000 ENV FISCAL_XBRL_BIN=/app/bin/fiscal-xbrl ENV FISCAL_XBRL_CACHE_DIR=/app/.cache/xbrl ENV XBRL_ENGINE_TIMEOUT_MS=45000 +ENV WORKFLOW_MIGRATIONS_DIR=/app/workflow-migrations -CMD ["sh", "-c", "if [ ! -x \"${FISCAL_XBRL_BIN:-/app/bin/fiscal-xbrl}\" ]; then echo \"Missing Rust XBRL sidecar at ${FISCAL_XBRL_BIN:-/app/bin/fiscal-xbrl}\" >&2; exit 1; fi; bun run bootstrap:prod && bun server.js"] +CMD ["sh", "-c", "if [ ! -x \"${FISCAL_XBRL_BIN:-/app/bin/fiscal-xbrl}\" ]; then echo \"Missing Rust XBRL sidecar at ${FISCAL_XBRL_BIN:-/app/bin/fiscal-xbrl}\" >&2; exit 1; fi; bun /app/bootstrap-production.js && exec bun /app/server.js"] diff --git a/bun.lock b/bun.lock index ad0a6ea..619b89e 100644 --- a/bun.lock +++ b/bun.lock @@ -9,7 +9,7 @@ "@libsql/client": "^0.17.0", "@tailwindcss/postcss": "^4.2.1", "@tanstack/react-query": "^5.90.21", - "@tanstack/react-virtual": "^3.13.23", + "@workflow/world": "^4.1.0-beta.11", "@workflow/world-postgres": "^4.1.0-beta.42", "ai": "^6.0.116", "better-auth": "^1.5.4", @@ -21,12 +21,12 @@ "html-to-image": "^1.11.13", "lucide-react": "^0.575.0", "next": "^16.1.6", + "postgres": "^3.4.8", "react": "^19.2.4", "react-dom": "^19.2.4", "recharts": "^3.8.0", "sonner": "^2.0.7", "sqlite-vec": "^0.1.7-alpha.2", - "sqlite-vec-darwin-arm64": "^0.1.7-alpha.2", "workflow": "^4.1.0-beta.63", "zhipu-ai-provider": "^0.2.2", }, @@ -35,7 +35,6 @@ "@types/node": "^25.5.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", - "autoprefixer": "^10.4.27", "bun-types": "^1.3.10", "drizzle-kit": "^0.31.9", "knip": "^5.88.1", @@ -662,10 +661,6 @@ "@tanstack/react-query": ["@tanstack/react-query@5.90.21", "", { "dependencies": { "@tanstack/query-core": "5.90.20" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg=="], - "@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.23", "", { "dependencies": { "@tanstack/virtual-core": "3.13.23" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-XnMRnHQ23piOVj2bzJqHrRrLg4r+F86fuBcwteKfbIjJrtGxb4z7tIvPVAe4B+4UVwo9G4Giuz5fmapcrnZ0OQ=="], - - "@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.23", "", {}, "sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg=="], - "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="], "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], @@ -836,8 +831,6 @@ "async-sema": ["async-sema@3.1.1", "", {}, "sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg=="], - "autoprefixer": ["autoprefixer@10.4.27", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001774", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA=="], - "aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="], "b4a": ["b4a@1.8.0", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg=="], @@ -870,8 +863,6 @@ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": "cli.js" }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], - "bson": ["bson@7.2.0", "", {}, "sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ=="], "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], @@ -904,7 +895,7 @@ "camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="], - "caniuse-lite": ["caniuse-lite@1.0.30001777", "", {}, "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ=="], + "caniuse-lite": ["caniuse-lite@1.0.30001770", "", {}, "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw=="], "cbor-extract": ["cbor-extract@2.2.0", "", { "dependencies": { "node-gyp-build-optional-packages": "5.1.1" }, "optionalDependencies": { "@cbor-extract/cbor-extract-darwin-arm64": "2.2.0", "@cbor-extract/cbor-extract-darwin-x64": "2.2.0", "@cbor-extract/cbor-extract-linux-arm": "2.2.0", "@cbor-extract/cbor-extract-linux-arm64": "2.2.0", "@cbor-extract/cbor-extract-linux-x64": "2.2.0", "@cbor-extract/cbor-extract-win32-x64": "2.2.0" }, "bin": { "download-cbor-prebuilds": "bin/download-prebuilds.js" } }, "sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA=="], @@ -1056,8 +1047,6 @@ "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], - "electron-to-chromium": ["electron-to-chromium@1.5.302", "", {}, "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg=="], - "elysia": ["elysia@1.4.27", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.7", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-2UlmNEjPJVA/WZVPYKy+KdsrfFwwNlqSBW1lHz6i2AHc75k7gV4Rhm01kFeotH7PDiHIX2G8X3KnRPc33SGVIg=="], "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -1172,8 +1161,6 @@ "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], - "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], - "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], "fs-extra": ["fs-extra@11.3.3", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg=="], @@ -1444,8 +1431,6 @@ "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.1.1", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-test": "build-test.js", "node-gyp-build-optional-packages-optional": "optional.js" } }, "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw=="], - "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], - "normalize-url": ["normalize-url@8.1.1", "", {}, "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ=="], "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], @@ -1536,8 +1521,6 @@ "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], - "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], - "postgres": ["postgres@3.4.8", "", {}, "sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg=="], "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], @@ -1808,8 +1791,6 @@ "untyped": ["untyped@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "defu": "^6.1.4", "jiti": "^2.4.2", "knitwork": "^1.2.0", "scule": "^1.3.0" }, "bin": { "untyped": "dist/cli.mjs" } }, "sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g=="], - "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": "cli.js" }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], - "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], @@ -1998,8 +1979,6 @@ "boxen/wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], - "browserslist/caniuse-lite": ["caniuse-lite@1.0.30001770", "", {}, "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw=="], - "bun-types/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="], "c12/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], @@ -2044,8 +2023,6 @@ "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], - "next/caniuse-lite": ["caniuse-lite@1.0.30001770", "", {}, "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw=="], - "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], "node-gyp-build-optional-packages/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], diff --git a/instrumentation.ts b/instrumentation.ts index 7df6f2e..5e4a2f8 100644 --- a/instrumentation.ts +++ b/instrumentation.ts @@ -3,6 +3,10 @@ export async function register() { return; } + if (process.env.WORKFLOW_TARGET_WORLD?.trim() !== '@workflow/world-postgres') { + return; + } + const { getWorld } = await import('workflow/runtime'); await getWorld().start?.(); } diff --git a/next.config.js b/next.config.js index 602ae14..40a4705 100644 --- a/next.config.js +++ b/next.config.js @@ -1,12 +1,19 @@ const { withWorkflow } = require('workflow/next'); +const skipBuildTypecheck = process.env.SKIP_NEXT_TYPECHECK_ON_BUILD === 'true'; +const buildCpus = Number.parseInt(process.env.NEXT_BUILD_CPUS || '', 10); /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, output: 'standalone', + serverExternalPackages: ['bun:sqlite'], + typescript: { + ignoreBuildErrors: skipBuildTypecheck + }, turbopack: { root: __dirname }, + experimental: buildCpus > 0 ? { cpus: buildCpus } : undefined, env: { NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || '' }, diff --git a/package.json b/package.json index 371322e..07035b7 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ }, "dependencies": { "@elysiajs/eden": "^1.4.8", + "@workflow/world": "^4.1.0-beta.11", "@libsql/client": "^0.17.0", "@tailwindcss/postcss": "^4.2.1", "@tanstack/react-query": "^5.90.21", @@ -52,6 +53,7 @@ "html-to-image": "^1.11.13", "lucide-react": "^0.575.0", "next": "^16.1.6", + "postgres": "^3.4.8", "react": "^19.2.4", "react-dom": "^19.2.4", "recharts": "^3.8.0", diff --git a/scripts/bootstrap-production.ts b/scripts/bootstrap-production.ts index 3a102ee..5e2725a 100644 --- a/scripts/bootstrap-production.ts +++ b/scripts/bootstrap-production.ts @@ -1,13 +1,14 @@ -import { spawnSync } from 'node:child_process'; import { mkdirSync } from 'node:fs'; import { dirname } from 'node:path'; import { Database } from 'bun:sqlite'; -import { drizzle } from 'drizzle-orm/bun-sqlite'; -import { migrate } from 'drizzle-orm/bun-sqlite/migrator'; +import { drizzle as drizzlePostgres } from 'drizzle-orm/postgres-js'; +import { migrate as migratePostgres } from 'drizzle-orm/postgres-js/migrator'; +import postgres from 'postgres'; import { ensureFinancialIngestionSchemaHealthy, resolveFinancialSchemaRepairMode } from '../lib/server/db/financial-ingestion-schema'; +import { ensureLocalSqliteSchema } from '../lib/server/db/sqlite-schema-compat'; import { resolveSqlitePath } from './dev-env'; function trim(value: string | undefined) { @@ -46,19 +47,27 @@ function getDatabasePath() { return databasePath; } -function runWorkflowSetup() { +async function runWorkflowSetup() { const startedAt = performance.now(); - const result = spawnSync('./node_modules/.bin/workflow-postgres-setup', [], { - env: process.env, - stdio: 'inherit' - }); + const connectionString = trim(process.env.WORKFLOW_POSTGRES_URL) + || trim(process.env.DATABASE_URL) + || 'postgres://world:world@localhost:5432/world'; + const migrationsFolder = trim(process.env.WORKFLOW_MIGRATIONS_DIR) || '/app/workflow-migrations'; + const pgClient = postgres(connectionString, { max: 1 }); - if (result.error) { - throw result.error; - } + console.info('🔧 Setting up database schema...'); + console.info(`📍 Connection: ${connectionString.replace(/^(\w+:\/\/)([^@]+)@/, '$1[redacted]@')}`); + console.info(`📂 Running migrations from: ${migrationsFolder}`); - if (result.status !== 0) { - throw new Error(`workflow-postgres-setup failed with exit code ${result.status ?? 'unknown'}`); + try { + const db = drizzlePostgres(pgClient); + await migratePostgres(db, { + migrationsFolder, + migrationsTable: 'workflow_migrations', + migrationsSchema: 'workflow_drizzle' + }); + } finally { + await pgClient.end({ timeout: 5 }); } log(`workflow-postgres-setup completed in ${formatDuration(startedAt)}`); @@ -77,7 +86,7 @@ function runDatabaseMigrations() { try { client.exec('PRAGMA foreign_keys = ON;'); - migrate(drizzle(client), { migrationsFolder: './drizzle' }); + ensureLocalSqliteSchema(client); const repairResult = ensureFinancialIngestionSchemaHealthy(client, { mode: resolveFinancialSchemaRepairMode(process.env.FINANCIAL_SCHEMA_REPAIR_MODE) @@ -102,7 +111,7 @@ try { log('starting production bootstrap'); if (shouldRunWorkflowSetup) { - runWorkflowSetup(); + await runWorkflowSetup(); } else { log('workflow-postgres-setup skipped'); }