Repository navigation
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors configuration types, exports SuperstaticRequest and SuperstaticResponse interfaces, adds TypeScript declaration files for external modules, and converts the test decorator helper to TypeScript. The review feedback suggests several key improvements: returning early in the env middleware to prevent calling next() after a response is handled, using union types to ensure at least one matcher is defined in Rewrite/Redirect/Header interfaces, making the env property optional in the middleware spec, and passing spec directly in the test decorator to preserve default parameters.
…fig matchers
- Return early after serving /__/env.json or /__/env.js without calling next()
- Add breaking change note to changelog.txt
- Require at least one matcher (source, glob, or regex) on Rewrite, Redirect, and Header
- Default env middleware spec to {} with optional env property
joehan
left a comment
There was a problem hiding this comment.
Code Review: PR #610 (refactor: add shared TypeScript types, ambient declarations, and typed test helper)
Strengths & LGTM Aspects
- Strong foundation for the JS to TS migration across superstatic.
- Solid type modeling in
src/config.tsfor cleanUrls, trailingSlash, headers, redirects, and rewrites. - Typed helper decorator in
test/helpers.tsmakes test cases much easier to follow and typesafe.
Key Findings to Address
src/middleware/env.tsmodule export mismatch:export function envmixed withmodule.exports = envcausesexport declare function envto be emitted in.d.tswhileexportsis overwritten at runtime, crashing TypeScript callers withTypeError.- Packaging / declaration issue: Moving
@types/connecttodevDependenciescauses emitted.d.tsfiles that referenceconnectto fail compilation in downstream projects that do not depend on@types/connect(or degradesuperstatic()toanyunderskipLibCheck). Rewritetype rejects custom rewriters: Firebase Hosting supportsfunctionandrunrewrites (used infirebase-tools), butRewritecurrently only modelsdestination. Alsodestinationbeing optional allows targetless rewrites that crash at runtime.MiddlewareOptions.env: stringbug: Passing a string filename compiles cleanly, butenv.tsdoes not load disk files and instead spreads string characters into object properties on/__/env.json.- Ambient
basic-auth-connectdeclaration:pass?: stringis marked optional, but the library throws synchronously ifpasswordis not a string.
| * @returns middleware. | ||
| */ | ||
| export function env(spec: { env: Record<string, string> }) { | ||
| export function env(spec: { env?: Record<string, string> } = {}) { |
There was a problem hiding this comment.
🔴 [P0 Runtime Crash] Mixed ES export and module.exports breaks TS callers
Line 36 declares export function env(...) while line 63 has module.exports = env;.
When compiling with tsconfig.publish.json, TypeScript emits export declare function env(...) in lib/middleware/env.d.ts (ignoring module.exports), but in the emitted CJS lib/middleware/env.js line 63 overwrites module.exports = env. At runtime, exports.env is undefined.
TypeScript consumers using import { env } from "superstatic/lib/middleware/env" compile with 0 errors and crash at runtime:
TypeError: (0 , env_1.env) is not a function
Consumers using import env from ... fail tsc with TS2349: Type 'typeof import(".../env")' has no call signatures.
Also note line 45 accesses req.superstatic.env without optional chaining (req.superstatic?.env), which will throw TypeError if mounted standalone.
Suggested fix:
function env(spec?: MiddlewareOptions | Record<string, string>): connect.NextHandleFunction {
// ...
const config = req.superstatic?.env;
// ...
}
export = env;There was a problem hiding this comment.
Addressed in 0c71e06: replaced mixed export with export = env; + export namespace env { export interface EnvOptions ... } so both CommonJS require() and TS import callers work, guarded req.superstatic?.env for standalone mounting, widened handler parameters to connect.IncomingMessage & { superstatic?: Configuration } and ServerResponse & { superstatic?: Responder }, and added standalone test coverage.
| "@types/chai-as-promised": "^8.0.2", | ||
| "@types/compression": "^1.8.1", | ||
| "@types/concat-stream": "^2.0.3", | ||
| "@types/connect": "^3.4.35", |
There was a problem hiding this comment.
🔴 [P1 Packaging / Declaration Defect] @types/connect in devDependencies breaks .d.ts consumers
Emitted declaration files (lib/options.d.ts, lib/server.d.ts, and lib/superstatic.d.ts) import types from "connect". Since connect ships no bundled types, downstream consumers installing superstatic:
- Under
skipLibCheck: false(or strict monorepo builds): failtscwithTS2307: Cannot find module 'connect' or its corresponding type declarations. - Under
skipLibCheck: true(e.g.firebase-tools, which does not list@types/connectin its dependencies): unresolvedconnectimports silently degradesuperstatic()andserver()return types toany.
Because connect types leak across superstatic's public declaration boundary, @types/connect should be listed in "dependencies", or src/options.ts can declare structural handler types ((req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void).
There was a problem hiding this comment.
Addressed in 0c71e06: moved @types/connect from devDependencies to dependencies (and updated package-lock.json) so emitted .d.ts declarations referencing connect resolve cleanly for downstream TypeScript consumers.
|
|
||
| export type Rewrite = Matcher & { | ||
| destination?: string; | ||
| }; |
There was a problem hiding this comment.
🔴 [P1 Type Modeling] Rewrite rejects function/run rewrites and allows targetless rewrites
export type Rewrite = Matcher & { destination?: string }; causes two issues:
- Valid Firebase Hosting rewrites used by
firebase-tools(e.g.{ source: "/api/**", function: "api" }or{ source: "/run/**", run: { serviceId: "svc" } }) fail compilation withTS2353: Object literal may only specify known properties. - Because
destinationis optional, a rewrite with{ source: "/foo" }compiles cleanly, but crashes at runtime with HTTP 500 (Error: Unable to find a matching rewriter).
Suggested fix:
export type RewriteTarget =
| { destination: string }
| { function: string | { functionId: string; region?: string; pinTag?: boolean } }
| { run: { serviceId: string; region?: string; pinTag?: boolean } }
| { dynamicLinks: boolean }
| ({ destination?: string } & Record<string, unknown>);
export type Rewrite = Matcher & RewriteTarget;There was a problem hiding this comment.
Addressed in 0c71e06: added the RewriteTarget union supporting destination, function, run, dynamicLinks, and custom rewriter objects, and defined export type Rewrite = Matcher & RewriteTarget;.
| import { Configuration } from "./config"; | ||
| import Responder from "./responder"; | ||
|
|
||
| export interface MiddlewareOptions { |
There was a problem hiding this comment.
🔴 [P1 Type / Runtime Mismatch] MiddlewareOptions.env: string spreads string characters on /__/env.json
env?: string | Record<string, string>; allows passing a file path string. But neither superstatic() nor env.ts loads files from disk when spec.env is a string (the CLI in src/cli/index.ts parses .env.json itself).
If a consumer passes superstatic({ env: ".env.json", config: {} }), env.ts executes Object.assign({}, req.superstatic.env, spec.env) and serves {"0":".","1":"e","2":"n","3":"v",...} at /__/env.json.
env should be typed Record<string, string> (or a loader function).
Also, a few other runtime options are omitted from MiddlewareOptions:
provider?: Provider(checked insrc/superstatic.js)allowedHeaders?: string[](checked inheaders.ts)compression?: boolean | connect.HandleFunction(checked as function insrc/superstatic.js)before?: { [key: string]: connect.HandleFunction | connect.HandleFunction[] }(activator supports arrays)
There was a problem hiding this comment.
Addressed in 0c71e06: removed string from MiddlewareOptions.env (now Record<string, string>), added missing runtime options (provider, allowedHeaders, compression, before, and array support on after), and exported shared provider/responder directive types from src/options.ts so downstream modules can share a single canonical definition.
|
|
||
| function basicAuth( | ||
| user: string, | ||
| pass?: string, |
There was a problem hiding this comment.
🟡 [P2 Ambient Declaration] basic-auth-connect requires pass: string
In basic-auth-connect, line 51 explicitly throws new Error("password argument required") if typeof password !== "string".
Typing pass?: string as optional hides runtime server crashes when colon-less credentials are provided to basic auth.
| pass?: string, | |
| (user: string, pass: string): connect.NextHandleFunction; |
There was a problem hiding this comment.
Addressed in 0c71e06: changed pass from optional to required pass: string in the basic-auth-connect declaration to match its runtime validation.
| ): void { | ||
| const superstaticReq = req as SuperstaticRequest; | ||
| superstaticReq.superstatic = config ?? {}; | ||
| middleware((spec ?? {}) as Spec)( |
There was a problem hiding this comment.
🟡 [P2 Test Helper] Missing return drops promises from async middleware
middleware((spec ?? {}) as Spec)(req, res, ...) drops the return value. For async middlewares (like files or redirects), any returned Promise is discarded. Adding return preserves returned promises in tests.
Also, spec ?? {} masks testing default parameters when spec is omitted, and as Spec bypasses type checking for required properties.
There was a problem hiding this comment.
Addressed in 0c71e06: added return before middleware(...)(...) (with handler return type : unknown) so promises from async middlewares are preserved in tests. Kept spec ?? {} in helpers.decorator for now because unconverted JS middleware factories (headers, protect, missing, not-found) still access spec.<prop> without default parameters when tests omit spec; we can drop spec ?? {} once those middlewares migrate.
| }); | ||
|
|
||
| it("does not call next after serving env json", (done) => { | ||
| app.use( |
There was a problem hiding this comment.
🟡 Nit: Assert rendered body for /__/env.js
The new test checks status code and Content-Type, but does not assert that the response body contains window.__env = .... Adding a body assertion ensures the template payload is formatted as expected.
There was a problem hiding this comment.
Addressed in 0c71e06: added a rendered body assertion verifying the interpolated templates/env.js.template output on /__/env.js.
…erstatic responder
* refactor(providers): convert memory provider to TypeScript * fix(providers): preserve falsy store lookup behavior in memory provider * fix(providers): address PR review feedback on memory provider
…cript (#613) * refactor(middleware): convert not-found middleware and tests to TypeScript * fix(middleware): guard against null spec with optional chaining * fix(middleware): fallback on empty errorPage and export NotFoundOptions
* refactor(utils): convert patterns to TypeScript Converts src/utils/patterns.js to TypeScript with typed pattern matching utilities and adds unit tests in test/unit/utils/patterns.spec.ts. * fix(utils): address PR review feedback on patterns typing and tests
* refactor(middleware): convert files middleware to TypeScript
Converts src/middleware/files.js to TypeScript (src/middleware/files.ts) and updates test/unit/middleware/files.spec.ts to rely on typed module exports.
- Types request/response/next handling with SuperstaticRequest, SuperstaticResponse, Configuration, and provider result types.
- Preserves compatibility with ES default imports and CommonJS require("./files") in src/middleware/index.js.
* fix(middleware): guard missing handleError in files error handler
* fix(middleware): address PR review feedback on files middleware
…es to TypeScript (#615) * refactor(utils): convert patterns to TypeScript Converts src/utils/patterns.js to TypeScript with typed pattern matching utilities and adds unit tests in test/unit/utils/patterns.spec.ts. * fix(utils): address PR review feedback on patterns typing and tests * refactor(middleware): convert protect, headers, and missing middlewares to TypeScript Converts src/middleware/protect.js, src/middleware/headers.js, src/middleware/missing.js, and their unit specs to TypeScript (src/middleware/protect.ts, src/middleware/headers.ts, src/middleware/missing.ts, test/unit/middleware/headers.spec.ts, and test/unit/middleware/missing.spec.ts). - Reuses Configuration, Header, Matcher, SuperstaticRequest, and SuperstaticResponse types and patterns.configMatcher from src/utils/patterns.ts. - Defaults spec = {} across protect, headers, and missing factories and preserves CommonJS middleware exports for unconverted JS callers. * refactor(middleware): address review feedback on headers middleware - Guard headerToSet?.key before calling toLowerCase() when filtering with allowedHeaders - Simplify normalizedConfigHeaders object construction with conditional source spread - Replace deprecated url.parse with WHATWG URL in extractPathname with double-slash and invalid-URL handling * refactor(middleware): address PR review feedback on protect, headers, and missing - Parse only absolute URIs via URL() in headers extractPathname so backslash paths and decoded non-ASCII error paths normalize consistently via slashPath - Normalize entry.glob alongside entry.source in normalizedConfigHeaders - Fall back to req.superstatic.protect when spec.protect is empty string and split credentials on the first colon only, returning 401 when missing a colon - Type middleware handlers with connect.IncomingMessage & { superstatic?: Configuration } and ServerResponse & { superstatic?: Responder } across headers, missing, and protect - Add unit tests covering backslash security headers, non-ASCII paths, unslashed globs, Connect direct mounting, and protect middleware edge cases
* refactor(responder): convert Responder to TypeScript Convert src/responder.js to TypeScript (src/responder.ts), update test/unit/responder.spec.ts to use typed imports and expand coverage, and add void on the floating promise call in src/middleware/rewrites.ts. * fix(responder): address PR review feedback on error, stream, and date handling Check headersSent in handleError before setting status or ending response, attach an error listener before piping provider streams in handleFileStream, normalize modified timestamps with explicit Number.isNaN checks in isNotModified, and add corresponding unit tests. * fix(responder): address PR review feedback on types, headers, and directives Clear stale entity/framing headers and set Content-Length on 500 error responses, remove public unknown overloads while preserving runtime directive validation, allow empty string data and null status, export Responder namespace types backed by src/options.ts, propagate async middleware errors and next(err), guard invalid modified dates, and expand unit tests.
…cript (#616) * refactor(utils): convert patterns to TypeScript Converts src/utils/patterns.js to TypeScript with typed pattern matching utilities and adds unit tests in test/unit/utils/patterns.spec.ts. * fix(utils): address PR review feedback on patterns typing and tests * refactor(middleware): convert redirects middleware and tests to TypeScript Converts src/middleware/redirects.js and test/unit/middleware/redirects.spec.js to TypeScript (src/middleware/redirects.ts and test/unit/middleware/redirects.spec.ts). - Reuses Redirect and Configuration types from src/config.ts and patterns.configMatcher / patterns.createRaw from src/utils/patterns.ts. - Replaces internal Redirect constructor function with a typed class/interface while preserving exact path-to-regexp capture, query string preservation, and CommonJS factory export compatibility. * fix(middleware): address PR review feedback on redirects and activator
Prepares the codebase for incremental
.js->.tsmigration (Phase 0):@types/*packages: Adds@types/compression,@types/concat-stream,@types/destroy,@types/is-url,@types/morgan,@types/on-finished,@types/on-headers, and@types/std-mockstodevDependencies(and updatespackage-lock.json).src/modules.d.tscovering the 3 packages without@types/*definitions (basic-auth-connect,router,update-notifier-cjs).Configuration,Rewrite,Redirect, andHeader(withMatcherrequiring at least one ofsource,glob, orregex) insrc/config.ts; exportsSuperstaticRequestandSuperstaticResponsefromsrc/options.ts; updatessrc/middleware/env.tsto use them, defaultspecto{}, and return early after serving/__/env.jsonor/__/env.js(noted inchangelog.txt).test/helpers.jstotest/helpers.tswith typeddecoratorandSuperstaticMiddlewareHandlerexports compatible with both ESM.spec.tsand CommonJS.spec.jssuites.Verification
tsc --noEmit: 0 errorstsc --build tsconfig.publish.json: 0 errorsnpm install --package-lock-only --ignore-scripts: lockfile up to dateeslint: 0 errors (0 warnings on all added/modified TypeScript files)mocha "test/unit/**" "test/integration/**": 234 passing, 15 pending