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
6 changes: 6 additions & 0 deletions docs/self-hosting/health-monitoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ back; readiness only takes the pods out of the load balancer until the dependenc

Neither endpoint needs to be public: the orchestrator reaches them on the container port.

The web process checks the database itself once, when it starts: outside development it exits if it
cannot verify that Row-Level Security is enforced (see
[Row-Level Security](../development/row-level-security.md)). An instance started while PostgreSQL is
unreachable exits before `/livez` can answer; with a restart policy it comes back once the database
does.

### Example Usage

```bash
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"**/pdf-worker.ts",
"**/pdf-globals-shim.ts",
"**/pdf-upsert-shim.ts",
"**/env.server.ts"
"**/env.server.ts",
"**/boot.server.ts",
"**/worker-locale.server.ts"
],
"type": "module",
"scripts": {
Expand Down
57 changes: 57 additions & 0 deletions scripts/side-effect-imports.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { readdirSync, readFileSync } from 'node:fs'
import { dirname, extname, join, relative, resolve } from 'node:path'

import { describe, expect, it } from 'vitest'

const ROOT = resolve(import.meta.dirname, '..')
const APP_DIR = join(ROOT, 'app')

// `import './x'` with no binding: the module is imported for what it does when it is evaluated.
const BARE_IMPORT = /^import\s+['"]((?:~|\.{1,2})\/[^'"]+)['"]/gm
const SOURCE_FILE = /\.tsx?$/
const TEST_FILE = /\.test\.tsx?$/
const GLOB_SPECIAL_CHARS = /[.+^${}()|[\]\\]/g
const GLOBSTAR_SEGMENT = /\*\*\//g
const GLOB_WILDCARD = /\*/g

function sourceFiles(dir: string): string[] {
return readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
const path = join(dir, entry.name)
if (entry.isDirectory()) return entry.name === 'generated' || entry.name === 'paraglide' ? [] : sourceFiles(path)
return SOURCE_FILE.test(entry.name) && !TEST_FILE.test(entry.name) ? [path] : []
})
}

function importedFile(importer: string, specifier: string): string {
const target = specifier.startsWith('~/') ? join(APP_DIR, specifier.slice(2)) : resolve(dirname(importer), specifier)
return relative(ROOT, extname(target) === '.css' ? target : `${target}.ts`)
}

function globToRegExp(glob: string): RegExp {
const pattern = glob
.replace(GLOB_SPECIAL_CHARS, '\\$&')
.replace(GLOBSTAR_SEGMENT, '\u0000')
.replace(GLOB_WILDCARD, '[^/]*')
.replaceAll('\u0000', '(?:.*/)?')
return new RegExp(`^${pattern}$`)
}

describe('side-effect-only imports', () => {
// package.json lists the modules that have side effects, and the production build drops a bare
// import of any module that is not on the list. That is how the boot checks (environment
// validation, the Row-Level Security guard) went missing from the built web server while every
// test, the dev server and the worker kept running them.
it('are all declared in package.json "sideEffects", or the production build drops them', () => {
const { sideEffects } = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')) as { sideEffects: string[] }
const declared = sideEffects.map(globToRegExp)

const undeclared = sourceFiles(APP_DIR).flatMap(file =>
[...readFileSync(file, 'utf8').matchAll(BARE_IMPORT)]
.map(match => importedFile(file, match[1] as string))
.filter(imported => !declared.some(pattern => pattern.test(imported)))
.map(imported => `${relative(ROOT, file)} imports ${imported}`),
)

expect(undeclared).toEqual([])
})
})
Loading