Revert "Upgrade to express 5" (#6303)

Reverts transloadit/uppy#6175
This commit is contained in:
Prakash 2026-05-21 02:36:00 +05:30 committed by GitHub
parent 489d76717c
commit d9742ba4e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 2332 additions and 439 deletions

View file

@ -1,5 +0,0 @@
---
"@uppy/aws-s3": patch
---
Improve type

View file

@ -1,5 +0,0 @@
---
"@uppy/companion": major
---
Upgrade to express 5 - Companion no longer works when used as a middleware with Express 4.

View file

@ -15,7 +15,7 @@
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"express": "^5.2.1", "express": "^4.22.0",
"express-session": "^1.17.3", "express-session": "^1.17.3",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"vite": "^7.1.11" "vite": "^7.1.11"

View file

@ -15,6 +15,6 @@
"@aws-sdk/s3-request-presigner": "^3.338.0", "@aws-sdk/s3-request-presigner": "^3.338.0",
"body-parser": "^1.20.4", "body-parser": "^1.20.4",
"dotenv": "^16.0.0", "dotenv": "^16.0.0",
"express": "^5.2.1" "express": "^4.22.0"
} }
} }

View file

@ -19,7 +19,7 @@
"@uppy/companion": "workspace:*", "@uppy/companion": "workspace:*",
"body-parser": "^1.20.4", "body-parser": "^1.20.4",
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"express": "^5.2.1", "express": "^4.22.0",
"express-session": "^1.15.6", "express-session": "^1.15.6",
"npm-run-all": "^4.1.2", "npm-run-all": "^4.1.2",
"vite": "^7.1.11" "vite": "^7.1.11"

View file

@ -11,7 +11,7 @@
}, },
"devDependencies": { "devDependencies": {
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"express": "^5.2.1", "express": "^4.22.0",
"vite": "^7.1.11" "vite": "^7.1.11"
}, },
"private": true, "private": true,

View file

@ -4,7 +4,7 @@
"dependencies": { "dependencies": {
"@uppy/companion": "workspace:*", "@uppy/companion": "workspace:*",
"body-parser": "^1.20.4", "body-parser": "^1.20.4",
"express": "^5.2.1", "express": "^4.22.0",
"express-session": "^1.15.6", "express-session": "^1.15.6",
"light-server": "^2.4.0" "light-server": "^2.4.0"
}, },

View file

@ -20,14 +20,14 @@
"@uppy/transloadit": "workspace:*", "@uppy/transloadit": "workspace:*",
"@uppy/tus": "workspace:*", "@uppy/tus": "workspace:*",
"@uppy/xhr-upload": "workspace:*", "@uppy/xhr-upload": "workspace:*",
"express": "^5.2.1", "express": "^4.22.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router": "^7.12.0" "react-router": "^7.12.0"
}, },
"devDependencies": { "devDependencies": {
"@react-router/dev": "^7.12.0", "@react-router/dev": "^7.12.0",
"@types/express": "^5.0.6", "@types/express": "^4.17.20",
"@types/react": "^18.2.20", "@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7", "@types/react-dom": "^18.2.7",
"tsx": "^4.0.0", "tsx": "^4.0.0",

View file

@ -41,9 +41,7 @@ async function startServer() {
// TUS upload endpoints (before React Router) // TUS upload endpoints (before React Router)
app.all('/api/upload/tus', (req, res) => tusServer.handle(req, res)) app.all('/api/upload/tus', (req, res) => tusServer.handle(req, res))
app.all('/api/upload/tus/{*splat}', (req, res) => app.all('/api/upload/tus/*', (req, res) => tusServer.handle(req, res))
tusServer.handle(req, res),
)
// Handle Chrome DevTools requests silently // Handle Chrome DevTools requests silently
app.get('/.well-known/appspecific/com.chrome.devtools.json', (req, res) => { app.get('/.well-known/appspecific/com.chrome.devtools.json', (req, res) => {
@ -51,7 +49,7 @@ async function startServer() {
}) })
// React Router handles all other routes // React Router handles all other routes
app.all('/{*splat}', reactRouterHandler) app.all('*', reactRouterHandler)
const port = process.env.PORT || 3000 const port = process.env.PORT || 3000
const server = app.listen(port, () => { const server = app.listen(port, () => {

View file

@ -15,7 +15,7 @@
"@uppy/remote-sources": "workspace:*", "@uppy/remote-sources": "workspace:*",
"@uppy/transloadit": "workspace:*", "@uppy/transloadit": "workspace:*",
"@uppy/webcam": "workspace:*", "@uppy/webcam": "workspace:*",
"express": "^5.2.1", "express": "^4.22.0",
"he": "^1.2.0" "he": "^1.2.0"
}, },
"private": true, "private": true,

View file

@ -6,7 +6,7 @@
"@uppy/dashboard": "workspace:*", "@uppy/dashboard": "workspace:*",
"@uppy/xhr-upload": "workspace:*", "@uppy/xhr-upload": "workspace:*",
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^5.2.1", "express": "^4.22.0",
"multer": "^2.0.2" "multer": "^2.0.2"
}, },
"devDependencies": { "devDependencies": {

View file

@ -662,11 +662,10 @@ export default class AwsS3Multipart<
querify: true, querify: true,
}) })
const query = new URLSearchParams({ const query = new URLSearchParams({ filename, type, ...metadata } as Record<
filename, string,
...(type != null && { type }), string
...metadata, >)
})
return this.#client.get(`s3/params?${query}`, options) return this.#client.get(`s3/params?${query}`, options)
} }

View file

@ -47,9 +47,9 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"escape-goat": "3.0.0", "escape-goat": "3.0.0",
"escape-string-regexp": "4.0.0", "escape-string-regexp": "4.0.0",
"express": "^5.2.1", "express": "4.21.2",
"express-interceptor": "1.2.0", "express-interceptor": "1.2.0",
"express-prom-bundle": "^8.0.0", "express-prom-bundle": "7",
"express-session": "1.18.1", "express-session": "1.18.1",
"fast-safe-stringify": "^2.1.1", "fast-safe-stringify": "^2.1.1",
"formdata-node": "^6.0.3", "formdata-node": "^6.0.3",

View file

@ -125,9 +125,6 @@ export function app(optionsArg: CompanionInitOptions) {
const app = express() const app = express()
// Needed for e.g. s3 `/params` endpoint metadata, like `metadata[key]=value` to be parsed into a metadata object
app.set('query parser', 'extended')
if (options.metrics) { if (options.metrics) {
app.use(middlewares.metrics({ path: options.server.path })) app.use(middlewares.metrics({ path: options.server.path }))
} }
@ -139,10 +136,10 @@ export function app(optionsArg: CompanionInitOptions) {
app.use(interceptGrantErrorResponse) app.use(interceptGrantErrorResponse)
// override provider credentials at request time // override provider credentials at request time
// Making `POST` request to the `/connect/:provider{/:override}` route requires a form body parser middleware: // Making `POST` request to the `/connect/:provider/:override?` route requires a form body parser middleware:
// See https://github.com/simov/grant#dynamic-http // See https://github.com/simov/grant#dynamic-http
app.use( app.use(
'/connect/:oauthProvider{/:override}', '/connect/:oauthProvider/:override?',
express.urlencoded({ extended: false }), express.urlencoded({ extended: false }),
getCredentialsOverrideMiddleware( getCredentialsOverrideMiddleware(
providers as Record<string, typeof Provider>, providers as Record<string, typeof Provider>,
@ -162,7 +159,7 @@ export function app(optionsArg: CompanionInitOptions) {
app.use(middlewares.cors(options)) app.use(middlewares.cors(options))
// add uppy options to the request object so it can be accessed by subsequent handlers. // add uppy options to the request object so it can be accessed by subsequent handlers.
app.use(middlewares.getCompanionMiddleware(options)) app.use('*', middlewares.getCompanionMiddleware(options))
app.use('/s3', s3(options.s3)) app.use('/s3', s3(options.s3))
if (options.enableUrlEndpoint) app.use('/url', url()) if (options.enableUrlEndpoint) app.use('/url', url())
if (options.enableGooglePickerEndpoint) if (options.enableGooglePickerEndpoint)
@ -235,7 +232,7 @@ export function app(optionsArg: CompanionInitOptions) {
) )
app.get( app.get(
'/:providerName/list{/:id}', '/:providerName/list/:id?',
middlewares.hasSessionAndProvider, middlewares.hasSessionAndProvider,
middlewares.verifyToken, middlewares.verifyToken,
controllers.list, controllers.list,

View file

@ -20,7 +20,7 @@ export default function callback(
const providerName = req.params['providerName'] const providerName = req.params['providerName']
const { companion } = req const { companion } = req
if (typeof providerName !== 'string' || providerName.length === 0) { if (providerName == null || providerName.length === 0) {
res.sendStatus(400) res.sendStatus(400)
return return
} }

View file

@ -7,9 +7,8 @@ export default async function list(
next: NextFunction, next: NextFunction,
): Promise<void> { ): Promise<void> {
const { query, params, companion } = req const { query, params, companion } = req
const id = params['id']
const { providerUserSession, provider } = companion const { providerUserSession, provider } = companion
if (!provider || (typeof id !== 'string' && id != null)) { if (!provider) {
res.sendStatus(400) res.sendStatus(400)
return return
} }
@ -18,7 +17,7 @@ export default async function list(
const data = await provider.list({ const data = await provider.list({
companion, companion,
providerUserSession, providerUserSession,
directory: id, directory: params['id'],
query, query,
}) })
res.json(data) res.json(data)

View file

@ -14,7 +14,7 @@ export default function preauth(req: Request, res: Response): void {
} }
const providerName = req.params['providerName'] const providerName = req.params['providerName']
if (typeof providerName !== 'string' || providerName.length === 0) { if (providerName == null || providerName.length === 0) {
res.sendStatus(400) res.sendStatus(400)
return return
} }

View file

@ -12,7 +12,7 @@ export default async function refreshToken(
next: NextFunction, next: NextFunction,
): Promise<void> { ): Promise<void> {
const providerName = req.params['providerName'] const providerName = req.params['providerName']
if (typeof providerName !== 'string' || providerName.length === 0) { if (providerName == null) {
res.sendStatus(400) res.sendStatus(400)
return return
} }

View file

@ -1,4 +1,3 @@
import assert from 'node:assert'
import type { Part, S3Client } from '@aws-sdk/client-s3' import type { Part, S3Client } from '@aws-sdk/client-s3'
import { import {
AbortMultipartUploadCommand, AbortMultipartUploadCommand,
@ -248,11 +247,6 @@ export default function s3(
const { uploadId } = req.params const { uploadId } = req.params
const { key } = req.query const { key } = req.query
assert(
typeof uploadId === 'string' && uploadId.length > 0,
's3: uploadId must be provided.',
)
if (typeof key !== 'string') { if (typeof key !== 'string') {
res.status(400).json({ res.status(400).json({
error: error:
@ -266,7 +260,7 @@ export default function s3(
const parts: Part[] = [] const parts: Part[] = []
const listPartsPage = (startAt?: string) => { function listPartsPage(startAt?: string) {
s3Client s3Client
.send( .send(
new ListPartsCommand({ new ListPartsCommand({
@ -309,7 +303,7 @@ export default function s3(
const partNumber = req.params['partNumber'] const partNumber = req.params['partNumber']
const key = req.query['key'] const key = req.query['key']
if (typeof uploadId !== 'string' || uploadId.length === 0) { if (uploadId == null || uploadId.length === 0) {
res.status(400).json({ error: 's3: uploadId must be provided.' }) res.status(400).json({ error: 's3: uploadId must be provided.' })
return return
} }
@ -320,7 +314,7 @@ export default function s3(
}) })
return return
} }
if (typeof partNumber !== 'string' || !parseInt(partNumber, 10)) { if (partNumber == null || !parseInt(partNumber, 10)) {
res.status(400).json({ res.status(400).json({
error: 's3: the part number must be a number between 1 and 10000.', error: 's3: the part number must be a number between 1 and 10000.',
}) })
@ -369,7 +363,7 @@ export default function s3(
const key = req.query['key'] const key = req.query['key']
const partNumbers = req.query['partNumbers'] const partNumbers = req.query['partNumbers']
if (typeof uploadId !== 'string' || uploadId.length === 0) { if (uploadId == null || uploadId.length === 0) {
res.status(400).json({ error: 's3: uploadId must be provided.' }) res.status(400).json({ error: 's3: uploadId must be provided.' })
return return
} }
@ -449,11 +443,6 @@ export default function s3(
const { uploadId } = req.params const { uploadId } = req.params
const { key } = req.query const { key } = req.query
assert(
typeof uploadId === 'string' && uploadId.length > 0,
's3: uploadId must be provided.',
)
if (typeof key !== 'string') { if (typeof key !== 'string') {
res.status(400).json({ res.status(400).json({
error: error:
@ -499,11 +488,6 @@ export default function s3(
const { key } = req.query const { key } = req.query
const { parts }: { parts: unknown } = req.body const { parts }: { parts: unknown } = req.body
assert(
typeof uploadId === 'string' && uploadId.length > 0,
's3: uploadId must be provided.',
)
if (typeof key !== 'string') { if (typeof key !== 'string') {
res.status(400).json({ res.status(400).json({
error: error:

View file

@ -9,7 +9,7 @@ export default async function simpleAuth(
next: NextFunction, next: NextFunction,
): Promise<void> { ): Promise<void> {
const providerName = req.params['providerName'] const providerName = req.params['providerName']
if (typeof providerName !== 'string' || providerName.length === 0) { if (providerName == null || providerName.length === 0) {
res.sendStatus(400) res.sendStatus(400)
return return
} }

View file

@ -7,7 +7,7 @@ async function thumbnail(
next: NextFunction, next: NextFunction,
): Promise<void> { ): Promise<void> {
const id = req.params['id'] const id = req.params['id']
if (typeof id !== 'string' || id.length === 0) { if (id == null) {
res.sendStatus(400) res.sendStatus(400)
return return
} }

View file

@ -97,7 +97,7 @@ export const verifyToken: RequestHandler = (req, res, next) => {
} }
const { secret } = req.companion.options const { secret } = req.companion.options
const providerName = req.params['providerName'] const providerName = req.params['providerName']
if (typeof providerName !== 'string' || providerName.length === 0) { if (providerName == null || providerName.length === 0) {
res.sendStatus(400) res.sendStatus(400)
return return
} }
@ -124,9 +124,7 @@ export const verifyToken: RequestHandler = (req, res, next) => {
const { providerOptions } = req.companion.options const { providerOptions } = req.companion.options
const providerName = req.params['providerName'] const providerName = req.params['providerName']
const providerOption = const providerOption =
typeof providerName === 'string' providerName != null ? providerOptions?.[providerName] : undefined
? providerOptions?.[providerName]
: undefined
const key = providerOption?.key const key = providerOption?.key
if (!key) { if (!key) {
logger.info( logger.info(
@ -148,7 +146,7 @@ export const verifyToken: RequestHandler = (req, res, next) => {
// does not fail if token is invalid // does not fail if token is invalid
export const gentleVerifyToken: RequestHandler = (req, res, next) => { export const gentleVerifyToken: RequestHandler = (req, res, next) => {
const providerName = req.params['providerName'] const providerName = req.params['providerName']
if (typeof providerName !== 'string' || providerName.length === 0) { if (providerName == null || providerName.length === 0) {
next() next()
return return
} }

View file

@ -9,9 +9,7 @@ const port: string | number =
const { app, companionOptions } = standalone() const { app, companionOptions } = standalone()
const server = app.listen(port, () => { companion.socket(app.listen(port), companionOptions)
logger.info(`Welcome to Companion! v${packageJson.version}`)
logger.info(`Listening on http://localhost:${port}`)
companion.socket(server, companionOptions) logger.info(`Welcome to Companion! v${packageJson.version}`)
}) logger.info(`Listening on http://localhost:${port}`)

View file

@ -426,26 +426,4 @@ describe('S3 controller', () => {
), ),
) )
}) })
test('getUploadParameters supports bracket metadata query format', async () => {
const server = await getServer({
COMPANION_AWS_KEY: 'test_key',
COMPANION_AWS_SECRET: 'test_secret',
COMPANION_AWS_BUCKET: 'test-bucket',
COMPANION_AWS_REGION: 'us-east-1',
})
return request(server)
.get('/s3/params')
.query({
filename: 'test.txt',
type: 'text/plain',
'metadata[name]': 'demo-file',
})
.expect(200)
.then((res) => {
const fields = res.body.fields as Record<string, string>
expect(fields['x-amz-meta-name']).toBe('demo-file')
})
})
}) })

View file

@ -19,7 +19,7 @@ async function run({
next() next()
}) })
app.use(cors(corsOptions)) app.use(cors(corsOptions))
app.options('/{*splat}', (_req, res) => { app.options('*', (_req, res) => {
res.status(204).end() res.status(204).end()
}) })

View file

@ -101,7 +101,7 @@ export const getServerWithEmitter = async (
authServer.use( authServer.use(
session({ secret: 'grant', resave: true, saveUninitialized: true }), session({ secret: 'grant', resave: true, saveUninitialized: true }),
) )
authServer.all('{*splat}/callback', (req, res, next) => { authServer.all('*/callback', (req, res, next) => {
if (req.session) { if (req.session) {
req.session['grant'] = { req.session['grant'] = {
response: { access_token: grantToken }, response: { access_token: grantToken },
@ -109,19 +109,16 @@ export const getServerWithEmitter = async (
} }
next() next()
}) })
authServer.all( authServer.all(['*/send-token', '*/redirect'], (req, res, next) => {
['{*splat}/send-token', '{*splat}/redirect'], const state =
(req, res, next) => { typeof req.query['state'] === 'string' ? req.query['state'] : null
const state = if (req.session) {
typeof req.query['state'] === 'string' ? req.query['state'] : null req.session['grant'] = {
if (req.session) { dynamic: { state: state ?? 'non-empty-value' },
req.session['grant'] = {
dynamic: { state: state ?? 'non-empty-value' },
}
} }
next() }
}, next()
) })
const { app, emitter } = standalone() const { app, emitter } = standalone()
authServer.use(app) authServer.use(app)

2611
yarn.lock

File diff suppressed because it is too large Load diff