diff --git a/CLAUDE.md b/CLAUDE.md index 1d4d955..2b8c94d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -264,6 +264,23 @@ composes them in one process. - `createNodeRequestHandler` returns its argument unchanged, so the exported `reqHandler` *is* the Express app and mounts directly as middleware. +**The App Hosting buildpack reads `engines.pnpm`, and ignores `packageManager`.** +It resolves that field as a *range* against the npm registry and installs the +highest match, so `">=10"` quietly became pnpm 12 on the build machine while +local and CI stayed on 10.14.0. The buildpack's own >= 11 branch is broken — it +unpacks the standalone GitHub tarball, then launches it as +`node /bin/dist/pnpm.mjs`, which only exists in the npm package layout — +so the build died with `MODULE_NOT_FOUND` before installing a single dependency. +`engines.pnpm` is now an exact `10.14.0` matching `packageManager`. Bumping it +past 11 breaks the deploy and nothing else, so no local check will catch it. + +**Never give `NODE_ENV` BUILD availability in `apphosting.yaml`.** The buildpack +installs with `pnpm install --prod`, which under `NODE_ENV=production` drops +every devDependency — and this build *is* devDependencies: the Angular CLI, the +Nest CLI, typescript. Nothing in the build reads `NODE_ENV` anyway (Angular +takes `production` from `angular.json`); the single reader in the repo is +`EnvService.partnerOrigin`, per request, at runtime. + **`NG_ALLOWED_HOSTS` is load-bearing, and it fails silently.** Angular 21 checks the `Host` header against an allowlist (SSRF protection). Off the list it does not error — it falls back to **client-side rendering**, quietly discarding the SSR and diff --git a/apphosting.yaml b/apphosting.yaml index 41d138f..7bea59c 100644 --- a/apphosting.yaml +++ b/apphosting.yaml @@ -8,6 +8,16 @@ # subdirectory; `rootDir: "/"` in firebase.json keeps pnpm-workspace.yaml and # pnpm-lock.yaml at the root of the build workspace, where the installer looks. +# The pnpm version is pinned EXACTLY in package.json `engines.pnpm`, not as a +# range. The App Hosting Node buildpack resolves `engines.pnpm` against the npm +# registry and takes the highest match — it ignores `packageManager` — so the +# old `">=10"` silently selected pnpm 12. Its own >= 11 branch is broken: it +# downloads the standalone GitHub tarball and then launches it as +# `node /bin/dist/pnpm.mjs`, a path that only exists in the npm package +# layout, and the build dies with MODULE_NOT_FOUND before installing anything. +# Keep `engines.pnpm` an exact 10.x that matches `packageManager`; bumping it +# past 11 re-breaks the deploy while local and CI stay green. + runConfig: cpu: 1 memoryMiB: 1024 @@ -29,9 +39,17 @@ env: # Marks this as the deployed environment. EnvService reads it to decide that # PARTNER_DEMO_ORIGIN has no localhost default here — otherwise /agent would # embed an iframe pointing at each visitor's own machine. + # + # RUNTIME only, and that is load-bearing. `EnvService.partnerOrigin` is the + # only reader in the repo and it runs per request; nothing in the build looks + # at NODE_ENV (Angular takes `production` from angular.json, not the env). But + # the buildpack installs with `pnpm install --prod` under NODE_ENV=production, + # which drops every devDependency — and the whole build is devDependencies: + # the Angular CLI, the Nest CLI, typescript. Granting this BUILD availability + # produces a build that cannot find `ng`. - variable: NODE_ENV value: production - availability: [BUILD, RUNTIME] + availability: [RUNTIME] # Angular 21 refuses to server-render a request whose Host header is not on an # allowlist (SSRF protection). Off the allowlist it does NOT fail loudly — it @@ -49,7 +67,7 @@ env: # Verify after a deploy: the HTML for `/` must contain `ng-server-context`. # If it does not, this value is wrong and the site is rendering client-side. - variable: NG_ALLOWED_HOSTS - value: "*.web.app,*.run.app,*.firebaseapp.com,*.programmersingh.dev" + value: "*.hosted.app,*.run.app,*.web.app,*.firebaseapp.com,*.programmersingh.dev" availability: [RUNTIME] # The Supabase project URL is not a credential; the service-role key below is. @@ -83,10 +101,21 @@ env: # from and the value has to be present while the build runs. # # Unset, every URL stays root-relative — valid, but the sitemap is not - # spec-compliant and scrapers may not resolve og:image. Set it to the - # backend's real hostname. + # spec-compliant and scrapers may not resolve og:image. + # + # This is an ORIGIN, so it must carry the scheme. stamp-seo.mjs substitutes the + # value verbatim (it only trims trailing slashes), so a bare hostname yields + # `actuo.programmersingh.dev/` — not a URL, and `new URL()` rejects + # it. Nothing fails the build; the sitemap is just quietly invalid. + # + # PREREQUISITE: this domain must actually resolve and be attached to the + # backend. It was NXDOMAIN when this value was set, and a canonical pointing at + # a host that does not resolve is worse than a relative one — it tells crawlers + # the real URL is dead. Add it as a custom domain on the App Hosting backend, + # point DNS at it, then confirm with `host actuo.programmersingh.dev`. Until + # that is done the deploy is fine and only the SEO metadata is wrong. - variable: PUBLIC_ORIGIN - value: "actuo.programmersingh.dev" + value: "https://actuo.programmersingh.dev" availability: [BUILD] # --- Optional, with sane defaults in EnvService -------------------------- diff --git a/package.json b/package.json index bd18a89..fd17976 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "description": "AI-native expense intelligence platform with a universal WebMCP Copilot", "engines": { "node": ">=22", - "pnpm": ">=10" + "pnpm": ">=9.0.0 <10.0.0" }, "scripts": { "dev": "pnpm run build:shared && concurrently -n backend,frontend,partner -c blue,magenta,yellow \"pnpm run dev:backend\" \"pnpm run dev:frontend\" \"pnpm run dev:partner\"", @@ -23,5 +23,5 @@ "concurrently": "^10.0.5", "supabase": "^2.116.0" }, - "packageManager": "pnpm@10.14.0" + "packageManager": "pnpm@9.15.5+sha512.845196026aab1cc3f098a0474b64dfbab2afe7a1b4e91dd86895d8e4aa32a7a6d03049e2d0ad770bbe4de023a7122fb68c1a1d6e0d033c7076085f9d5d4800d4" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 483008d..053e049 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,7 +150,7 @@ importers: devDependencies: '@angular/build': specifier: ^21.2.21 - version: 21.2.22(addca2e8b0d1cdbe785c1221ede9343a) + version: 21.2.22(tvsdneph7b2ppo44lsnrlossqi) '@angular/cli': specifier: ^21.2.21 version: 21.2.22(@types/node@20.19.43)(chokidar@5.0.0) @@ -2782,8 +2782,8 @@ packages: exponential-backoff@3.1.3: resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} - express-rate-limit@8.6.2: - resolution: {integrity: sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A==} + express-rate-limit@8.7.0: + resolution: {integrity: sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==} engines: {node: '>= 16'} peerDependencies: express: '>= 4.11' @@ -2982,8 +2982,8 @@ packages: resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} engines: {node: '>=12.0.0'} - ip-address@10.5.0: - resolution: {integrity: sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==} + ip-address@10.7.0: + resolution: {integrity: sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==} engines: {node: '>= 12'} ipaddr.js@1.9.1: @@ -4441,7 +4441,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular/build@21.2.22(addca2e8b0d1cdbe785c1221ede9343a)': + '@angular/build@21.2.22(tvsdneph7b2ppo44lsnrlossqi)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2102.22(chokidar@5.0.0) @@ -5208,7 +5208,7 @@ snapshots: eventsource: 3.0.7 eventsource-parser: 3.1.1 express: 5.2.1 - express-rate-limit: 8.6.2(express@5.2.1) + express-rate-limit: 8.7.0(express@5.2.1) hono: 4.13.5 jose: 6.2.10 json-schema-typed: 8.0.2 @@ -6088,7 +6088,7 @@ snapshots: obug: 2.1.4 std-env: 4.2.0 tinyrainbow: 3.1.1 - vitest: 4.1.11(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.97.3)) + vitest: 4.1.11(@types/node@24.13.3)(@vitest/coverage-v8@4.1.11)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.97.3)) '@vitest/expect@4.1.11': dependencies: @@ -6667,11 +6667,11 @@ snapshots: exponential-backoff@3.1.3: {} - express-rate-limit@8.6.2(express@5.2.1): + express-rate-limit@8.7.0(express@5.2.1): dependencies: debug: 4.4.3 express: 5.2.1 - ip-address: 10.5.0 + ip-address: 10.7.0 transitivePeerDependencies: - supports-color @@ -6924,7 +6924,7 @@ snapshots: through: 2.3.8 wrap-ansi: 6.2.0 - ip-address@10.5.0: {} + ip-address@10.7.0: {} ipaddr.js@1.9.1: {} @@ -7828,7 +7828,7 @@ snapshots: socks@2.8.9: dependencies: - ip-address: 10.5.0 + ip-address: 10.7.0 smart-buffer: 4.2.0 source-map-js@1.2.1: {}