Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ jobs:

- run: pnpm build

- name: Smoke-test built entrypoints load in raw Node
run: pnpm smoke

- name: Check type exports (attw)
run: pnpm check-exports

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"prepare": "simple-git-hooks",
"smoke": "node scripts/smoke-load.mjs",
"test": "vitest run --project unit --project nestjs",
"test:e2e": "vitest run --project e2e",
"test:watch": "vitest --project unit --project nestjs",
Expand Down
59 changes: 59 additions & 0 deletions scripts/smoke-load.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Loads every built entrypoint in a plain Node process — no transpiler in the
// chain — to catch output that fails to parse/load as a real consumer would
// (e.g. untranspiled decorator syntax; see issue #87). vitest can't catch this
// because it re-transpiles imported modules through swc/esbuild.
//
// Entrypoints are derived from package.json `exports` so this stays in sync as
// adapters are added. Run after `pnpm build`: `pnpm smoke`.

import { createRequire } from 'node:module'
import { dirname, resolve } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { readFileSync } from 'node:fs'

const require = createRequire(import.meta.url)
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')

const pkg = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'))

// Collect (subpath, format, absolute file) triples from every export condition
// that points at a built .mjs/.cjs file.
const targets = []
for (const [subpath, entry] of Object.entries(pkg.exports ?? {})) {
if (typeof entry !== 'object') continue
const mjs = entry.import?.default
const cjs = entry.require?.default
if (mjs) targets.push({ subpath, format: 'esm', file: resolve(root, mjs) })
if (cjs) targets.push({ subpath, format: 'cjs', file: resolve(root, cjs) })
}

if (targets.length === 0) {
console.error(
'No entrypoints found in package.json exports — smoke test is testing nothing.',
)
process.exit(1)
}

const failures = []
for (const { subpath, format, file } of targets) {
try {
if (format === 'esm') await import(pathToFileURL(file).href)
else require(file)
console.log(`ok ${format.padEnd(3)} ${subpath}`)
} catch (err) {
console.error(`FAIL ${format.padEnd(3)} ${subpath}`)
console.error(` ${err?.stack ?? err}`)
failures.push({ subpath, format, file })
}
}

if (failures.length > 0) {
console.error(
`\n${failures.length} built entrypoint(s) failed to load in raw Node.`,
)
process.exit(1)
}

console.log(
`\nAll ${targets.length} built entrypoints load cleanly in raw Node.`,
)
7 changes: 6 additions & 1 deletion src/adapters/nestjs/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ function toWebRequest(req: NestRequestLike): Request {
export function withSupabase(
config?: Omit<WithSupabaseConfig, 'cors'>,
): Type<CanActivate> {
@Injectable()
// Applied programmatically rather than as an `@Injectable()` decorator:
// the build tool (tsdown/oxc) does not lower legacy `experimentalDecorators`,
// so decorator syntax here would ship verbatim to `dist` and fail to parse
// under plain Node (CJS/ESM) at load time. Calling the decorator factory on
// the class produces identical DI metadata. See issue #87.
class SupabaseAuthGuard implements CanActivate {
async canActivate(executionContext: ExecutionContext): Promise<boolean> {
// Fail loudly on non-HTTP transports rather than silently allowing them
Expand Down Expand Up @@ -123,5 +127,6 @@ export function withSupabase(
}
}

Injectable()(SupabaseAuthGuard)
return SupabaseAuthGuard
}
Loading