From 5628ab0d8f6d0a4e73a8a1666a77533a5e89c9e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Z=C3=BCger?= Date: Wed, 2 Sep 2026 11:28:53 +0200 Subject: [PATCH] Fix functions deploy broken by the express 5 upgrade npm update pulled firebase-functions 7.2.5 -> 7.3.2, which brought express 4.22.2 -> 5.2.1 along transitively. Express 5 uses path-to-regexp v8, where the optional-group syntax used by every API route no longer parses: TypeError: Unexpected ( at index 0: (/api)?/aerodrome/status functions/index.js therefore throws on load and firebase deploy --only functions fails during function discovery. Rewrites the six routes to path arrays, which express 5 still supports, and declares express as a direct dependency. It was only ever required, never declared, so it silently rode along on whatever firebase-functions hoisted -- which is how the major bump slipped in unnoticed. Co-Authored-By: Claude Opus 5 (1M context) --- functions/api/index.js | 12 ++++++------ functions/package-lock.json | 1 + functions/package.json | 1 + 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/functions/api/index.js b/functions/api/index.js index 5d7994af..17fb6e6f 100644 --- a/functions/api/index.js +++ b/functions/api/index.js @@ -12,7 +12,7 @@ const api = express() api.use(cors) -api.get('(/api)?/aerodrome/status', async (req, res) => { +api.get(['/aerodrome/status', '/api/aerodrome/status'], async (req, res) => { const status = await fetchAerodromeStatus(admin.database()) res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate') @@ -24,7 +24,7 @@ api.get('(/api)?/aerodrome/status', async (req, res) => { res.send(status) }) -api.get('(/api)?/customs/invoices', fbAdminAuth, async (req, res) => { +api.get(['/customs/invoices', '/api/customs/invoices'], fbAdminAuth, async (req, res) => { try { const db = admin.database() const {year, month} = req.query @@ -36,7 +36,7 @@ api.get('(/api)?/customs/invoices', fbAdminAuth, async (req, res) => { } }) -api.get('(/api)?/customs/checkouts', fbAdminAuth, async (req, res) => { +api.get(['/customs/checkouts', '/api/customs/checkouts'], fbAdminAuth, async (req, res) => { try { const db = admin.database() const {year, month} = req.query @@ -48,7 +48,7 @@ api.get('(/api)?/customs/checkouts', fbAdminAuth, async (req, res) => { } }) -api.post('(/api)?/customs/prepopulated-forms', fbAuthExcludingShared, async (req, res) => { +api.post(['/customs/prepopulated-forms', '/api/customs/prepopulated-forms'], fbAuthExcludingShared, async (req, res) => { try { const { movementType, movementKey } = req.body || {} @@ -81,7 +81,7 @@ api.post('(/api)?/customs/prepopulated-forms', fbAuthExcludingShared, async (req } }) -api.get('(/api)?/customs/availability', fbAuth, async (req, res) => { +api.get(['/customs/availability', '/api/customs/availability'], fbAuth, async (req, res) => { try { const db = admin.database() const isAvailable = await isCustomsDeclarationAppAvailable(db) @@ -92,7 +92,7 @@ api.get('(/api)?/customs/availability', fbAuth, async (req, res) => { } }) -api.get('(/api)?/users/me/invoice-recipients', fbAuth, async (req, res) => { +api.get(['/users/me/invoice-recipients', '/api/users/me/invoice-recipients'], fbAuth, async (req, res) => { try { const db = admin.database() const invoiceRecipients = await fetchUserInvoiceRecipients(db, req.fbUserEmail) diff --git a/functions/package-lock.json b/functions/package-lock.json index 8a3cf50f..c7284471 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -9,6 +9,7 @@ "@simplewebauthn/browser": "^13.3.0", "@simplewebauthn/server": "^13.3.0", "cors": "^2.8.6", + "express": "^5.2.1", "firebase-admin": "^13.9.0", "firebase-functions": "^7.0.0", "moment": "^2.30.1", diff --git a/functions/package.json b/functions/package.json index 4aa2dc48..10ee2600 100644 --- a/functions/package.json +++ b/functions/package.json @@ -8,6 +8,7 @@ "@simplewebauthn/browser": "^13.3.0", "@simplewebauthn/server": "^13.3.0", "cors": "^2.8.6", + "express": "^5.2.1", "firebase-admin": "^13.9.0", "firebase-functions": "^7.0.0", "moment": "^2.30.1",