2026-05-12 22:00:43 +02:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
/**
|
|
|
|
|
* Pre-commit type check for staged TS files.
|
|
|
|
|
*
|
|
|
|
|
* Writes a temp tsconfig that extends the project root and pins
|
|
|
|
|
* `files` to whatever lint-staged passed in. `tsc -p` then compiles
|
|
|
|
|
* the whole dep graph from those entrypoints — catches errors in
|
|
|
|
|
* the staged code AND in anything it imports — while still skipping
|
|
|
|
|
* the 22s full-project pass.
|
|
|
|
|
*
|
|
|
|
|
* Replaces `tsc-files` (npm), which silently fails under pnpm because
|
|
|
|
|
* its tsc-resolution path (typescript/../.bin/tsc) doesn't exist in
|
|
|
|
|
* pnpm's virtual store layout.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { spawnSync } from 'node:child_process';
|
feat(audit-wave-1): real db:migrate runner with CONCURRENTLY support
Closes Wave 1.1 (CRITICAL): the production-grade migration runner the
audit flagged as missing.
Why drizzle-kit migrate alone wasn't enough:
- Wraps every migration in a single transaction. Postgres forbids
CREATE INDEX CONCURRENTLY inside a transaction (25001), so the
6 composite indexes in 0052_audit_critical_fixes.sql never landed
in prod.
- db:push silently diverges from migration-tracked truth on DDL the
kit can't infer from the schema (CHECK constraints, partial unique
indexes, the berth-pdf circular FK).
scripts/db-migrate.ts:
- Reads journal-ordered migrations from src/lib/db/migrations.
- Tracks applied state in drizzle.__drizzle_migrations (same schema
Drizzle's own tools use).
- Splits each migration on `--> statement-breakpoint`.
- Classifies each statement: CREATE/REINDEX/DROP INDEX CONCURRENTLY
→ outside transaction; everything else → batched in one tx per
migration. Transactional batch runs first, CONCURRENTLY second.
Three modes:
- `pnpm db:migrate` — apply pending migrations
- `pnpm db:migrate:status` — diff applied vs disk
- `pnpm db:migrate:baseline` — mark all as applied without running
them. Use ONCE per env when schema
was bootstrapped via db:push.
Also fixes scripts/tsc-staged.mjs: temp tsconfig now lives in
`node_modules/.cache/tsc-staged/` (was /tmp) AND explicitly lists
`types: [node, react, react-dom]` so @types/* auto-resolution works
when `include: []` short-circuits TS's default discovery.
For the existing prod cutover:
After `db:migrate:baseline`, manually verify 0052's composite
indexes exist:
SELECT indexname FROM pg_indexes
WHERE indexname IN ('idx_files_port_client', 'idx_files_port_company',
'idx_files_port_yacht', 'idx_docs_port_client',
'idx_docs_port_company', 'idx_docs_port_yacht');
If missing, paste 0052's CREATE INDEX CONCURRENTLY statements into
a `psql` session directly (each runs OUTSIDE a transaction).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:04:52 +02:00
|
|
|
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
|
2026-05-12 22:00:43 +02:00
|
|
|
import { join, relative, resolve } from 'node:path';
|
|
|
|
|
|
|
|
|
|
const cwd = process.cwd();
|
|
|
|
|
const args = process.argv.slice(2);
|
|
|
|
|
const files = args.filter((a) => /\.(ts|tsx)$/.test(a));
|
|
|
|
|
|
|
|
|
|
if (files.length === 0) {
|
|
|
|
|
process.exit(0);
|
|
|
|
|
}
|
|
|
|
|
|
feat(audit-wave-1): real db:migrate runner with CONCURRENTLY support
Closes Wave 1.1 (CRITICAL): the production-grade migration runner the
audit flagged as missing.
Why drizzle-kit migrate alone wasn't enough:
- Wraps every migration in a single transaction. Postgres forbids
CREATE INDEX CONCURRENTLY inside a transaction (25001), so the
6 composite indexes in 0052_audit_critical_fixes.sql never landed
in prod.
- db:push silently diverges from migration-tracked truth on DDL the
kit can't infer from the schema (CHECK constraints, partial unique
indexes, the berth-pdf circular FK).
scripts/db-migrate.ts:
- Reads journal-ordered migrations from src/lib/db/migrations.
- Tracks applied state in drizzle.__drizzle_migrations (same schema
Drizzle's own tools use).
- Splits each migration on `--> statement-breakpoint`.
- Classifies each statement: CREATE/REINDEX/DROP INDEX CONCURRENTLY
→ outside transaction; everything else → batched in one tx per
migration. Transactional batch runs first, CONCURRENTLY second.
Three modes:
- `pnpm db:migrate` — apply pending migrations
- `pnpm db:migrate:status` — diff applied vs disk
- `pnpm db:migrate:baseline` — mark all as applied without running
them. Use ONCE per env when schema
was bootstrapped via db:push.
Also fixes scripts/tsc-staged.mjs: temp tsconfig now lives in
`node_modules/.cache/tsc-staged/` (was /tmp) AND explicitly lists
`types: [node, react, react-dom]` so @types/* auto-resolution works
when `include: []` short-circuits TS's default discovery.
For the existing prod cutover:
After `db:migrate:baseline`, manually verify 0052's composite
indexes exist:
SELECT indexname FROM pg_indexes
WHERE indexname IN ('idx_files_port_client', 'idx_files_port_company',
'idx_files_port_yacht', 'idx_docs_port_client',
'idx_docs_port_company', 'idx_docs_port_yacht');
If missing, paste 0052's CREATE INDEX CONCURRENTLY statements into
a `psql` session directly (each runs OUTSIDE a transaction).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:04:52 +02:00
|
|
|
// Temp tsconfig lives inside the project tree (not /tmp) so @types/*
|
|
|
|
|
// resolution walks up to node_modules. tsc's "atTypes" auto-discovery
|
|
|
|
|
// is anchored to the tsconfig's directory, so a temp config in /tmp
|
|
|
|
|
// would miss our @types/node, @types/react, etc.
|
|
|
|
|
const baseDir = join(cwd, 'node_modules/.cache/tsc-staged');
|
|
|
|
|
mkdirSync(baseDir, { recursive: true });
|
|
|
|
|
const tmpDir = mkdtempSync(join(baseDir, 'run-'));
|
2026-05-12 22:00:43 +02:00
|
|
|
const tmpConfig = join(tmpDir, 'tsconfig.json');
|
|
|
|
|
|
|
|
|
|
const relFiles = files.map((f) => relative(tmpDir, resolve(cwd, f)));
|
|
|
|
|
|
feat(documenso-phase-4): recipient configurator + field placement UI
Phase 4 lands the visual half of the Documenso build — the upload-
for-signing dialog the Contract + Reservation tabs hand off to. Four
files of new code; the existing tab placeholders point at it.
Files added:
- lib/services/document-field-detector.ts — Phase 4c auto-detect
scanner. Uses pdfjs-dist to extract per-page text + positions, then
matches anchor patterns (Signature, Date, Initials, Email, Name,
underscore-runs) and produces percent-coordinate DetectedField
rows. Recipient label inference walks ±100pt of each match for
Buyer/Seller/Client/Witness/Notary keywords. Returns [] when the
PDF is image-only; UI falls back to manual placement without an
error. 6 unit tests pin the matching + coordinate math.
- app/api/v1/documents/auto-detect-fields/route.ts — multipart POST
endpoint that delegates to detectFields(). Permission-gated by
documents.send_for_signing.
- app/api/v1/documents/signing-defaults/route.ts — GET endpoint that
surfaces just the per-port developer + approver display name/email
+ sendMode flag. No secrets exposed; lets the dialog prefill the
recipient configurator without an admin-scoped settings read.
- components/documents/upload-for-signing-dialog.tsx — the Phase 4
UI. Three-step state machine inside a single Dialog:
1. select-file: drop/click PDF picker + title input
2. configure-recipients: client + developer + approver prefilled,
rep can add/remove/reorder + change role (SIGNER/APPROVER/CC)
3. place-fields: react-pdf renders the source PDF; auto-detect
runs in the background on file load and seeds the overlay;
rep places, drags, resizes, deletes, reassigns fields via the
palette + side panel. Native DOM drag (no dnd-kit dependency
added — the coordinate math stays obvious).
Send fires POST /api/v1/interests/[id]/upload-for-signing (Phase 3
service); success toast reflects port sendMode (auto fires the
invite immediately, manual leaves it for the rep).
Files modified:
- components/interests/interest-contract-tab.tsx + reservation-tab.tsx:
swap the ComingSoonDialog placeholder for the real
UploadForSigningDialog with the matching documentType prop. The
placeholder ComingSoonDialog helper is deleted from both.
- scripts/tsc-staged.mjs: pull src/types/**/*.d.ts into the temp
staged-only tsconfig so side-effect CSS imports (e.g.
react-pdf/dist/Page/AnnotationLayer.css) resolve via the existing
declare-module shim. Without this fix the staged compile reports
TS2882 even though the full tsc --noEmit pass passes.
Design choices noted in code comments:
- Native drag over dnd-kit: the field overlay's percent-based
coordinate math is short enough that adding a drag library adds
complexity without saving lines.
- Auto-detect on file-load (not on demand): runs immediately so the
rep doesn't have to click a second button — empty result drops
back to manual placement silently.
- Per-recipient color swatches indexed by signingOrder.
- Recipient seed via useMemo + user-event handler instead of
useEffect → setRecipients (Wave 3 set-state-in-effect avoidance).
Server-side, Phase 3 plumbing handles the rest: tenant guard, magic-
byte verify, Documenso round-trip with per-port v1/v2 routing,
recipient signingToken capture for Phase 2 webhook cascade, auto-
send when port.sendMode === 'auto'.
Tests: 1334 → 1340 ✅ (6 new for the detector); tsc clean.
Deferred polish (Phase 6):
- Per-field metadata side panel for DROPDOWN/RADIO option lists
- Pinch-zoom + zoom-out controls on the field-placement canvas
- Recipient drag-reorder via dnd-kit
- Required toggle per field
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:03:27 +02:00
|
|
|
// Pull in the project's ambient .d.ts files (css module shim,
|
|
|
|
|
// react-pdf JSX augment, etc.) so side-effect imports like
|
|
|
|
|
// `import 'react-pdf/dist/Page/AnnotationLayer.css'` resolve under the
|
|
|
|
|
// staged-only compile. Without this, `include: []` would shut out
|
|
|
|
|
// everything in src/types/ and tsc reports TS2882 for any CSS import.
|
|
|
|
|
const ambientTypesGlob = relative(tmpDir, join(cwd, 'src/types')) + '/**/*.d.ts';
|
|
|
|
|
|
2026-05-12 22:00:43 +02:00
|
|
|
writeFileSync(
|
|
|
|
|
tmpConfig,
|
|
|
|
|
JSON.stringify(
|
|
|
|
|
{
|
|
|
|
|
extends: relative(tmpDir, join(cwd, 'tsconfig.json')),
|
feat(audit-wave-1): real db:migrate runner with CONCURRENTLY support
Closes Wave 1.1 (CRITICAL): the production-grade migration runner the
audit flagged as missing.
Why drizzle-kit migrate alone wasn't enough:
- Wraps every migration in a single transaction. Postgres forbids
CREATE INDEX CONCURRENTLY inside a transaction (25001), so the
6 composite indexes in 0052_audit_critical_fixes.sql never landed
in prod.
- db:push silently diverges from migration-tracked truth on DDL the
kit can't infer from the schema (CHECK constraints, partial unique
indexes, the berth-pdf circular FK).
scripts/db-migrate.ts:
- Reads journal-ordered migrations from src/lib/db/migrations.
- Tracks applied state in drizzle.__drizzle_migrations (same schema
Drizzle's own tools use).
- Splits each migration on `--> statement-breakpoint`.
- Classifies each statement: CREATE/REINDEX/DROP INDEX CONCURRENTLY
→ outside transaction; everything else → batched in one tx per
migration. Transactional batch runs first, CONCURRENTLY second.
Three modes:
- `pnpm db:migrate` — apply pending migrations
- `pnpm db:migrate:status` — diff applied vs disk
- `pnpm db:migrate:baseline` — mark all as applied without running
them. Use ONCE per env when schema
was bootstrapped via db:push.
Also fixes scripts/tsc-staged.mjs: temp tsconfig now lives in
`node_modules/.cache/tsc-staged/` (was /tmp) AND explicitly lists
`types: [node, react, react-dom]` so @types/* auto-resolution works
when `include: []` short-circuits TS's default discovery.
For the existing prod cutover:
After `db:migrate:baseline`, manually verify 0052's composite
indexes exist:
SELECT indexname FROM pg_indexes
WHERE indexname IN ('idx_files_port_client', 'idx_files_port_company',
'idx_files_port_yacht', 'idx_docs_port_client',
'idx_docs_port_company', 'idx_docs_port_yacht');
If missing, paste 0052's CREATE INDEX CONCURRENTLY statements into
a `psql` session directly (each runs OUTSIDE a transaction).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:04:52 +02:00
|
|
|
compilerOptions: {
|
|
|
|
|
noEmit: true,
|
|
|
|
|
skipLibCheck: true,
|
|
|
|
|
// Explicitly list `types` so the @types/* auto-discovery
|
|
|
|
|
// finds them — without this, the temp-tsconfig location
|
|
|
|
|
// anchors discovery to .cache/ and misses node/react/etc.
|
|
|
|
|
types: ['node', 'react', 'react-dom'],
|
|
|
|
|
},
|
2026-05-12 22:00:43 +02:00
|
|
|
files: relFiles,
|
feat(documenso-phase-4): recipient configurator + field placement UI
Phase 4 lands the visual half of the Documenso build — the upload-
for-signing dialog the Contract + Reservation tabs hand off to. Four
files of new code; the existing tab placeholders point at it.
Files added:
- lib/services/document-field-detector.ts — Phase 4c auto-detect
scanner. Uses pdfjs-dist to extract per-page text + positions, then
matches anchor patterns (Signature, Date, Initials, Email, Name,
underscore-runs) and produces percent-coordinate DetectedField
rows. Recipient label inference walks ±100pt of each match for
Buyer/Seller/Client/Witness/Notary keywords. Returns [] when the
PDF is image-only; UI falls back to manual placement without an
error. 6 unit tests pin the matching + coordinate math.
- app/api/v1/documents/auto-detect-fields/route.ts — multipart POST
endpoint that delegates to detectFields(). Permission-gated by
documents.send_for_signing.
- app/api/v1/documents/signing-defaults/route.ts — GET endpoint that
surfaces just the per-port developer + approver display name/email
+ sendMode flag. No secrets exposed; lets the dialog prefill the
recipient configurator without an admin-scoped settings read.
- components/documents/upload-for-signing-dialog.tsx — the Phase 4
UI. Three-step state machine inside a single Dialog:
1. select-file: drop/click PDF picker + title input
2. configure-recipients: client + developer + approver prefilled,
rep can add/remove/reorder + change role (SIGNER/APPROVER/CC)
3. place-fields: react-pdf renders the source PDF; auto-detect
runs in the background on file load and seeds the overlay;
rep places, drags, resizes, deletes, reassigns fields via the
palette + side panel. Native DOM drag (no dnd-kit dependency
added — the coordinate math stays obvious).
Send fires POST /api/v1/interests/[id]/upload-for-signing (Phase 3
service); success toast reflects port sendMode (auto fires the
invite immediately, manual leaves it for the rep).
Files modified:
- components/interests/interest-contract-tab.tsx + reservation-tab.tsx:
swap the ComingSoonDialog placeholder for the real
UploadForSigningDialog with the matching documentType prop. The
placeholder ComingSoonDialog helper is deleted from both.
- scripts/tsc-staged.mjs: pull src/types/**/*.d.ts into the temp
staged-only tsconfig so side-effect CSS imports (e.g.
react-pdf/dist/Page/AnnotationLayer.css) resolve via the existing
declare-module shim. Without this fix the staged compile reports
TS2882 even though the full tsc --noEmit pass passes.
Design choices noted in code comments:
- Native drag over dnd-kit: the field overlay's percent-based
coordinate math is short enough that adding a drag library adds
complexity without saving lines.
- Auto-detect on file-load (not on demand): runs immediately so the
rep doesn't have to click a second button — empty result drops
back to manual placement silently.
- Per-recipient color swatches indexed by signingOrder.
- Recipient seed via useMemo + user-event handler instead of
useEffect → setRecipients (Wave 3 set-state-in-effect avoidance).
Server-side, Phase 3 plumbing handles the rest: tenant guard, magic-
byte verify, Documenso round-trip with per-port v1/v2 routing,
recipient signingToken capture for Phase 2 webhook cascade, auto-
send when port.sendMode === 'auto'.
Tests: 1334 → 1340 ✅ (6 new for the detector); tsc clean.
Deferred polish (Phase 6):
- Per-field metadata side panel for DROPDOWN/RADIO option lists
- Pinch-zoom + zoom-out controls on the field-placement canvas
- Recipient drag-reorder via dnd-kit
- Required toggle per field
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:03:27 +02:00
|
|
|
include: [ambientTypesGlob],
|
2026-05-12 22:00:43 +02:00
|
|
|
},
|
|
|
|
|
null,
|
|
|
|
|
2,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const tsc = spawnSync('pnpm', ['exec', 'tsc', '-p', tmpConfig, '--pretty'], {
|
|
|
|
|
cwd,
|
|
|
|
|
stdio: 'inherit',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
|
|
|
|
|
|
|
|
process.exit(tsc.status ?? 1);
|