fix: make monorepo checks and caching consistent

This commit is contained in:
Amruth Pillai
2026-09-30 22:32:05 +02:00
parent bec152b7e9
commit 0bdf8066cd
61 changed files with 814 additions and 449 deletions
+16
View File
@@ -51,12 +51,14 @@ jobs:
uses: actions/checkout@v6
with:
persist-credentials: false
fetch-depth: 0
- name: Checkout Repository (Blacksmith)
if: ${{ vars.USE_BLACKSMITH == 'true' }}
uses: useblacksmith/checkout@v1
with:
persist-credentials: false
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v6
@@ -67,9 +69,23 @@ jobs:
node-version-file: ".nvmrc"
cache: "pnpm"
- name: Cache Turbo artifacts
uses: actions/cache@v5
with:
path: .turbo/cache
key: ${{ runner.os }}-${{ runner.arch }}-turbo-${{ github.workflow }}-${{ hashFiles('pnpm-lock.yaml', '.nvmrc') }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-turbo-${{ github.workflow }}-${{ hashFiles('pnpm-lock.yaml', '.nvmrc') }}-
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Check Package Boundaries
run: pnpm exec turbo boundaries
- name: Typecheck Affected Packages
run: pnpm exec turbo run typecheck --affected
- name: Install Playwright Browser
timeout-minutes: 10
run: pnpm exec playwright install --with-deps chromium
+8
View File
@@ -49,6 +49,14 @@ jobs:
with:
node-version-file: .nvmrc
cache: pnpm
- name: Cache Turbo artifacts
uses: actions/cache@v5
with:
path: .turbo/cache
key: ${{ runner.os }}-${{ runner.arch }}-turbo-${{ github.workflow }}-${{ hashFiles('pnpm-lock.yaml', '.nvmrc') }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-turbo-${{ github.workflow }}-${{ hashFiles('pnpm-lock.yaml', '.nvmrc') }}-
- run: pnpm install --frozen-lockfile
# Local project settings avoid authentication and API calls. Forks receive no cloud credentials.
- name: Build Vercel artifact against isolated PostgreSQL
+4 -2
View File
@@ -49,6 +49,8 @@ Internal packages are source-consumed through `package.json` export maps pointin
Prerequisites: **Node.js 24** (`.nvmrc`, root `engines`, and Dockerfile), **pnpm 12.8.1** (root `packageManager`; pnpm self-manages to this version), and **Docker with Docker Compose** for local infrastructure. The Dockerfile's `ARG PNPM_VERSION` chooses its base image, not the project's pnpm version. Start your Docker daemon before running Compose.
Shared dependency versions live in the default `catalog` in `pnpm-workspace.yaml`. Use `catalog:` in workspace manifests when that shared range applies; keep intentional exact pins and peer dependency ranges explicit.
Run commands from the workspace root unless stated otherwise:
```sh
@@ -135,7 +137,7 @@ Multi-place changes:
- **Resume data shape**: `packages/schema/src/resume/*` first, then API DTOs, importers, PDF rendering, and web forms consuming it.
- **New template**: `packages/schema/src/templates.ts`, `packages/pdf/src/templates/index.ts`, source under `packages/pdf/src/templates/<name>/`, and previews under `apps/web/public/templates/{jpg,pdf}`.
- **New DB column/table**: `packages/db/src/schema/*`, then `pnpm db:generate`.
- **New env var**: `packages/env/src/server.ts`, `.env.example`, **and** the `globalEnv` array in `turbo.json`. Add deployment aliases in `packages/env/src/deployment.ts` when needed. Turborepo strict env mode filters unlisted injected variables from task processes.
- **New env var**: `packages/env/src/server.ts`, `.env.example`, **and** the `globalPassThroughEnv` array and applicable test-task `env` arrays in `turbo.json`. Add deployment aliases in `packages/env/src/deployment.ts` when needed. Turborepo strict env mode filters unlisted injected variables from task processes.
## Environment and database
@@ -163,7 +165,7 @@ pnpm exec turbo boundaries
- Vitest tests live alongside source as `src/**/*.test.ts(x)` or `src/**/*.spec.ts(x)` (including integration tests). Paths under `pnpm --filter <package>` are package-relative. Pass paths directly after `test`: an extra `--` currently prevents Vitest from filtering the run. Shared settings live in `vitest.shared.mts` and setup in `vitest.setup.ts`; most packages use Node, while `packages/ui` uses `happy-dom`.
- Coverage uses V8 and writes package-local `coverage/` reports. No shared minimum coverage threshold is configured. `test:ci` writes JSON/JUnit results under package-local `reports/`.
- Root `pnpm test`, `pnpm test:coverage`, and `pnpm typecheck` run workspace checks through Turbo. CI runs `pnpm exec turbo run test:ci --concurrency=1` to avoid CPU contention in PDF/rate-limit suites.
- Root `pnpm test`, `pnpm test:coverage`, and `pnpm typecheck` run workspace checks through Turbo. CI checks boundaries and affected-package typechecks, then runs all unit suites with `pnpm exec turbo run test:ci --concurrency=1` to avoid CPU contention in PDF/rate-limit suites. Unit/browser and Vercel workflows persist `.turbo/cache`; cached coverage and test reports restore to package-local output directories.
- Real-database unit suites use `COVER_LETTER_TEST_DATABASE_URL` and `OAUTH_TEST_DATABASE_URL`; see `.github/workflows/e2e.yml` for isolated database setup. Never point test fixtures at production data.
- After changing shared contracts, exports, or imports, check affected consumers and run `pnpm exec turbo boundaries`.
+4 -2
View File
@@ -3,10 +3,12 @@
# Base image only; pnpm self-manages to the `packageManager` version in package.json.
ARG PNPM_VERSION=11.21.0
ARG NODE_VERSION=24
ARG TURBO_VERSION=2.11.5
FROM ghcr.io/pnpm/pnpm:${PNPM_VERSION} AS base
ARG NODE_VERSION
ARG TURBO_VERSION
RUN pnpm runtime set node ${NODE_VERSION} -g --config.store-dir=/pnpm/runtime-store
@@ -17,7 +19,7 @@ ENV TURBO_TELEMETRY_DISABLED=1
FROM base AS pruner
COPY . .
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store,sharing=locked \
pnpm dlx turbo@2.9.12 prune web server --docker
pnpm dlx turbo@${TURBO_VERSION} prune web server --docker
FROM base AS builder
COPY --from=pruner /app/out/json/ ./
@@ -31,7 +33,7 @@ RUN rm -rf apps/web/dist apps/web/dist-prerender apps/server/dist && pnpm turbo
FROM base AS runtime-pruner
COPY . .
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store,sharing=locked \
pnpm dlx turbo@2.9.12 prune server --docker
pnpm dlx turbo@${TURBO_VERSION} prune server --docker
FROM base AS runtime-deps
COPY --from=runtime-pruner /app/out/json/ ./
+25 -25
View File
@@ -29,21 +29,21 @@
"@ai-sdk/togetherai": "^3.0.60",
"@ai-sdk/xai": "^5.0.12",
"@aws-sdk/client-s3": "^3.1143.0",
"@better-auth/api-key": "^1.7.6",
"@better-auth/api-key": "catalog:",
"@better-auth/drizzle-adapter": "^1.7.6",
"@better-auth/infra": "^0.4.13",
"@better-auth/oauth-provider": "^1.7.6",
"@better-auth/passkey": "^1.7.6",
"@better-auth/infra": "catalog:",
"@better-auth/oauth-provider": "catalog:",
"@better-auth/passkey": "catalog:",
"@bramus/specificity": "^2.4.2",
"@formepdf/core": "0.25.0",
"@formepdf/react": "0.25.0",
"@hono/node-server": "^2.1.3",
"@modelcontextprotocol/sdk": "^1.31.0",
"@orpc/client": "^1.15.4",
"@orpc/client": "catalog:",
"@orpc/experimental-ratelimit": "^1.15.4",
"@orpc/json-schema": "^1.15.4",
"@orpc/openapi": "^1.15.4",
"@orpc/server": "^1.15.4",
"@orpc/server": "catalog:",
"@orpc/zod": "^1.15.4",
"@reactive-resume/api": "workspace:*",
"@reactive-resume/auth": "workspace:*",
@@ -54,50 +54,50 @@
"@reactive-resume/utils": "workspace:*",
"@sindresorhus/slugify": "^3.0.1",
"@t3-oss/env-core": "^0.13.11",
"@uiw/color-convert": "^2.10.3",
"@uiw/color-convert": "catalog:",
"@vercel/blob": "^2.8.0",
"@vercel/functions": "^3.9.9",
"ai": "^7.0.122",
"bcryptjs": "^3.0.3",
"better-auth": "1.7.6",
"ai": "catalog:",
"bcryptjs": "catalog:",
"better-auth": "catalog:",
"cjk-regex": "^3.5.0",
"css-tree": "^3.2.1",
"drizzle-orm": "1.0.0-rc.4",
"drizzle-orm": "catalog:",
"drizzle-zod": "1.0.0-beta.14-a36c63d",
"es-toolkit": "^1.52.0",
"es-toolkit": "catalog:",
"fast-json-patch": "^3.1.1",
"fast-png": "^8.0.0",
"fflate": "^0.8.3",
"fflate": "catalog:",
"firecrawl": "^4.42.0",
"hono": "^4.13.11",
"ioredis": "^6.0.0",
"ioredis": "catalog:",
"jose": "^6.2.12",
"jsonrepair": "^3.15.0",
"jsonrepair": "catalog:",
"node-html-parser": "^9.0.4",
"nodemailer": "^10.0.12",
"ollama-ai-provider-v2": "^4.0.1",
"pg": "^8.23.0",
"react": "^19.3.0",
"pg": "catalog:",
"react": "catalog:",
"react-email": "^6.11.0",
"react-reconciler": "0.34.0",
"resumable-stream": "^2.2.13",
"sanitize-html": "^2.17.7",
"sharp": "^0.35.5",
"tokenx": "^2.1.0",
"ts-pattern": "^5.9.0",
"ts-pattern": "catalog:",
"unique-names-generator": "^4.7.1",
"uuid": "^14.0.2",
"zod": "^4.6.5"
"zod": "catalog:"
},
"devDependencies": {
"@reactive-resume/config": "workspace:*",
"@types/node": "^26.6.3",
"@types/pg": "^8.23.1",
"@types/react": "^19.3.0",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"@types/node": "catalog:",
"@types/pg": "catalog:",
"@types/react": "catalog:",
"@typescript/native-preview": "catalog:",
"tsdown": "^0.23.0",
"tsx": "^4.23.15",
"typescript": "^7.0.2",
"vitest": "^5.0.2"
"typescript": "catalog:",
"vitest": "catalog:"
}
}
+10 -4
View File
@@ -2,12 +2,18 @@
"extends": ["//"],
"tags": ["app:server", "runtime:server", "role:adapter"],
"tasks": {
"test": { "env": ["OAUTH_TEST_DATABASE_URL"] },
"test:coverage": { "env": ["OAUTH_TEST_DATABASE_URL"] },
"test:agent": { "env": ["OAUTH_TEST_DATABASE_URL"] },
"test": {
"env": ["$TURBO_EXTENDS$", "OAUTH_TEST_DATABASE_URL"]
},
"test:coverage": {
"env": ["$TURBO_EXTENDS$", "OAUTH_TEST_DATABASE_URL"]
},
"test:agent": {
"env": ["$TURBO_EXTENDS$", "OAUTH_TEST_DATABASE_URL"]
},
"test:ci": {
"cache": false,
"env": ["OAUTH_TEST_DATABASE_URL"]
"env": ["$TURBO_EXTENDS$", "OAUTH_TEST_DATABASE_URL"]
}
}
}
+18 -18
View File
@@ -18,10 +18,10 @@
"dependencies": {
"@ai-sdk/react": "^4.0.125",
"@base-ui/react": "^1.8.0",
"@better-auth/api-key": "^1.7.6",
"@better-auth/infra": "^0.4.13",
"@better-auth/oauth-provider": "^1.7.6",
"@better-auth/passkey": "^1.7.6",
"@better-auth/api-key": "catalog:",
"@better-auth/infra": "catalog:",
"@better-auth/oauth-provider": "catalog:",
"@better-auth/passkey": "catalog:",
"@codemirror/autocomplete": "^6.20.3",
"@codemirror/commands": "^6.11.1",
"@codemirror/lang-css": "^6.3.1",
@@ -35,8 +35,8 @@
"@lezer/highlight": "^1.2.5",
"@lingui/core": "^6.8.0",
"@lingui/react": "^6.8.0",
"@orpc/client": "^1.15.4",
"@orpc/server": "^1.15.4",
"@orpc/client": "catalog:",
"@orpc/server": "catalog:",
"@orpc/tanstack-query": "^1.15.4",
"@phosphor-icons/react": "^2.1.10",
"@reactive-resume/ai": "workspace:*",
@@ -66,13 +66,13 @@
"@tiptap/react": "^3.31.3",
"@tiptap/starter-kit": "^3.31.3",
"@types/js-cookie": "^3.0.6",
"@uiw/color-convert": "^2.10.3",
"@uiw/color-convert": "catalog:",
"@uiw/react-color-colorful": "^2.10.3",
"ai": "^7.0.122",
"better-auth": "1.7.6",
"ai": "catalog:",
"better-auth": "catalog:",
"cmdk": "^1.1.1",
"es-toolkit": "^1.52.0",
"fflate": "^0.8.3",
"es-toolkit": "catalog:",
"fflate": "catalog:",
"fuse.js": "^7.5.0",
"immer": "^11.1.18",
"js-cookie": "^3.0.8",
@@ -80,15 +80,15 @@
"pdfjs-dist": "6.3.289",
"prettier": "^3.9.9",
"qrcode.react": "^4.2.0",
"react": "^19.3.0",
"react": "catalog:",
"react-dom": "^19.3.0",
"react-easy-crop": "^6.2.3",
"react-markdown": "^10.1.0",
"react-window": "^2.3.3",
"remark-gfm": "^4.0.1",
"ts-pattern": "^5.9.0",
"ts-pattern": "catalog:",
"usehooks-ts": "^3.1.1",
"zod": "^4.6.5",
"zod": "catalog:",
"zustand": "^5.0.15"
},
"devDependencies": {
@@ -105,13 +105,13 @@
"@tanstack/react-router-devtools": "^1.167.2",
"@tanstack/router-plugin": "^1.168.41",
"@types/babel__core": "^7.20.5",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@typescript/native-preview": "catalog:",
"@vitejs/plugin-react": "^6.1.1",
"babel-plugin-macros": "^3.1.0",
"babel-plugin-react-compiler": "^1.0.0",
"typescript": "^7.0.2",
"typescript": "catalog:",
"vite": "^8.3.1"
}
}
+6 -3
View File
@@ -233,8 +233,10 @@ export function ChipInput({
const handleEdit = React.useCallback(
(index: number) => {
const chip = chips[index];
if (chip === undefined) return;
setEditingIndex(index);
setInput(chips[index]);
setInput(chip);
inputRef.current?.focus();
},
[chips],
@@ -244,7 +246,7 @@ export function ChipInput({
(newOrder: string[]) => {
if (editingIndex !== null) {
const editingChip = chips[editingIndex];
const newIndex = newOrder.indexOf(editingChip);
const newIndex = editingChip === undefined ? -1 : newOrder.indexOf(editingChip);
if (newIndex !== -1 && newIndex !== editingIndex) {
setEditingIndex(newIndex);
}
@@ -282,6 +284,7 @@ export function ChipInput({
if (oldIndex !== -1 && newIndex !== -1 && oldIndex !== newIndex) {
const newOrder = [...chips];
const [removed] = newOrder.splice(oldIndex, 1);
if (removed === undefined) return;
newOrder.splice(newIndex, 0, removed);
handleReorder(newOrder);
}
@@ -307,7 +310,7 @@ export function ChipInput({
if (newValue.includes(",")) {
const parts = newValue.split(",");
addChips(parts.slice(0, -1));
setInput(parts[parts.length - 1]);
setInput(parts.at(-1) ?? "");
} else {
setInput(newValue);
}
@@ -6,7 +6,7 @@ interface FontDisplayProps {
family: string;
label: string;
type: "standard" | "web";
url?: string;
url?: string | undefined;
}
const loadedFonts = new Set<string>();
@@ -22,7 +22,7 @@ export function getNextWeights(fontFamily: string, single = false): Weight[] | n
// candidateIndex: 0 (first), 1 (last)
const lastIndex = uniqueWeights.length - 1;
const candidate = weights.length === 0 ? uniqueWeights[0] : uniqueWeights[lastIndex];
if (!selectedWeights.has(candidate)) {
if (candidate !== undefined && !selectedWeights.has(candidate)) {
weights.push(candidate);
selectedWeights.add(candidate);
} else break;
+4 -4
View File
@@ -31,8 +31,8 @@ type ComboboxOption<TValue extends string | number = string> = {
type SingleComboboxProps<TValue extends string | number = string> = {
options: ComboboxOption<TValue>[];
value?: TValue | null;
defaultValue?: TValue | null;
value?: TValue | null | undefined;
defaultValue?: TValue | null | undefined;
onValueChange?: (value: TValue | null) => void;
multiple?: false;
disabled?: boolean;
@@ -47,8 +47,8 @@ type SingleComboboxProps<TValue extends string | number = string> = {
type MultiComboboxProps<TValue extends string | number = string> = {
options: ComboboxOption<TValue>[];
value?: TValue[] | null;
defaultValue?: TValue[] | null;
value?: TValue[] | null | undefined;
defaultValue?: TValue[] | null | undefined;
onValueChange?: (value: TValue[] | null) => void;
multiple: true;
disabled?: boolean;
+2 -1
View File
@@ -230,7 +230,8 @@ export function mapCsvToApplications(table: string[][], mapping?: readonly (CsvF
const parsed = applicationStatusSchema.safeParse(stage === "rejected" ? "closed" : stage);
if (parsed.success) record.status = parsed.data;
} else if (field === "stageEnteredAt") {
record.stageEnteredAt = dateOnly(value);
const date = dateOnly(value);
if (date !== undefined) record.stageEnteredAt = date;
} else record[field] = value as never;
});
+4 -4
View File
@@ -1,5 +1,5 @@
import type { AgentUIMessage } from "@reactive-resume/ai/tools/agent-tool-contracts";
import type { FileUIPart, UIMessage } from "ai";
import type { ChatTransport, FileUIPart, UIMessage } from "ai";
import { useChat } from "@ai-sdk/react";
import { eventIteratorToUnproxiedDataStream } from "@orpc/client";
import { lastAssistantMessageIsCompleteWithToolCalls, parseJsonEventStream, uiMessageChunkSchema } from "ai";
@@ -84,9 +84,9 @@ export function useAssistantChat({ threadId, initialMessages, resume, context, o
contextRef.current = context;
});
const transport = useMemo(
const transport = useMemo<ChatTransport<AgentUIMessage>>(
() => ({
async sendMessages(options: { messages: UIMessage[]; abortSignal?: AbortSignal; body?: object }) {
async sendMessages(options) {
const message = options.messages.at(-1);
if (!message) throw new Error("No message to send.");
const attachmentIds = attachmentIdsOf(options.body);
@@ -94,7 +94,7 @@ export function useAssistantChat({ threadId, initialMessages, resume, context, o
eventIteratorToUnproxiedDataStream(
await streamClient.agent.messages.send(
{ threadId, message, context: contextRef.current, ...(attachmentIds ? { attachmentIds } : {}) },
{ signal: options.abortSignal },
options.abortSignal ? { signal: options.abortSignal } : {},
),
),
);
@@ -262,6 +262,11 @@ function toProposals(
const output = part.output as ProposeEditsOutput | undefined;
return (output?.edits ?? []).map((edit) => ({
...edit,
target: {
sectionId: edit.target.sectionId,
field: edit.target.field,
...(edit.target.itemId === undefined ? {} : { itemId: edit.target.itemId }),
},
location: document.locationOf(edit) ?? edit.location,
status: statuses.get(edit.id) ?? edit.status,
source: "assistant",
@@ -56,7 +56,7 @@ function SocialAuthButtons({ providers }: SocialAuthButtonsProps) {
const { callbackURL } = useSearch({ from: "/auth" });
const runSignIn = async (
fn: () => Promise<{ data?: unknown; error: { message?: string } | null }>,
fn: () => Promise<{ data?: unknown; error: { message?: string | undefined } | null }>,
isPasskey = false,
) => {
const toastId = toast.add({ type: "loading", description: t`Signing in...` });
@@ -19,7 +19,7 @@ import { useCommandPaletteStore } from "./store";
export function CommandPalette() {
const inputRef = useRef<HTMLInputElement>(null);
const commandRef = useRef<HTMLDivElement>(null);
const [selectedValue, setSelectedValue] = useState<string>();
const [selectedValue, setSelectedValue] = useState("");
const { open, search, pages, setOpen, setSearch, goBack } = useCommandPaletteStore();
const isFirstPage = pages.length === 0;
@@ -3,7 +3,7 @@
import type { ResumeData } from "@reactive-resume/schema/resume/data";
import type { Resume } from "./draft";
import { act, renderHook } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, assert, beforeEach, describe, expect, it, vi } from "vitest";
import { i18n } from "@lingui/core";
import { sortSectionItemsByPeriod } from "@reactive-resume/resume/section-sort";
import { defaultResumeData } from "@reactive-resume/schema/resume/default";
@@ -165,10 +165,12 @@ describe("builder resume autosave", () => {
settled = true;
return blocked;
});
assert.exists(completions[0]);
completions[0](withBasicsName(initial, "First"));
await flushMicrotasks();
expect(settled).toBe(false);
expect(orpcMocks.updateResume.mock.lastCall?.[0].data.basics.name).toBe("Latest");
assert.exists(completions[1]);
completions[1](withBasicsName(initial, "Latest"));
expect(await result).toBe(false);
expect(useResumeStore.getState().resume?.data.basics.name).toBe("Latest");
@@ -22,9 +22,9 @@ export type Resume = {
isLocked: boolean;
createdAt: Date;
updatedAt: Date;
hasPassword?: boolean;
isPublic?: boolean;
showDownloadButtons?: boolean;
hasPassword?: boolean | undefined;
isPublic?: boolean | undefined;
showDownloadButtons?: boolean | undefined;
/** The job application this resume was made for; Check's Job match reads its posting. */
applicationId?: string | null;
};
@@ -57,7 +57,7 @@ type UpdateResumeDataOptions = {
type ResumeStoreState = {
resume: Resume | null;
resumeId?: string;
resumeId?: string | undefined;
isReady: boolean;
saveStatus: SaveStatus;
// Undo/redo stacks of `ResumeData` references. Immer's immutable updates share structure, so a
@@ -90,21 +90,21 @@ type Runtime = {
queryClient?: QueryClient;
hasPendingLocalChanges: boolean;
isSaving: boolean;
pendingResume?: Resume;
pendingResume?: Resume | undefined;
/** The last save failed; don't loop on it. Cleared by the next edit, Retry, reconnecting or success. */
saveFailed: boolean;
slowSaveToastId?: string;
slowSaveToastId?: string | undefined;
syncResume: ReturnType<typeof debounce<(resume: Resume) => void>>;
beforeUnloadHandler?: () => void;
onlineHandler?: () => void;
deferredRemoteResume?: Resume;
deferredFocusHandler?: () => void;
deferredRemoteResume?: Resume | undefined;
deferredFocusHandler?: (() => void) | undefined;
/** This visit's saves share one autosave version in History. */
sessionId: string;
};
type ResumeUpdateSubscriptionOptions = {
resumeId?: string;
resumeId?: string | undefined;
onUpdate: (event: ResumeUpdateEvent) => Promise<void> | void;
onError?: (error: unknown) => void;
};
@@ -26,7 +26,7 @@ type MonthYearInputProps = DateSettings & {
value: YearMonth | null;
disabled?: boolean;
/** Shown instead of the value, e.g. "Present" while the entry is ongoing. */
placeholderValue?: string;
placeholderValue?: string | undefined;
onCommit: (value: YearMonth | null) => void;
onInvalid: (invalid: boolean) => void;
};
@@ -84,7 +84,7 @@ function MonthYearInput({
type DatesFieldProps = DateSettings & {
dates: ResumeDates;
/** Awards, certifications and publications have one date. */
single?: boolean;
single?: boolean | undefined;
onChange: (dates: ResumeDates) => void;
className?: string;
};
@@ -42,7 +42,7 @@ function Text({
label,
wide,
autoFocus,
}: FieldSetProps & { field: string; label: ReactNode; wide?: boolean; autoFocus?: boolean }) {
}: FieldSetProps & { field: string; label: ReactNode; wide?: boolean; autoFocus?: boolean | undefined }) {
return (
<TextField
label={label}
@@ -308,7 +308,7 @@ export function EntryFields({
type,
autoFocus,
...props
}: FieldSetProps & { type: CustomSectionType; autoFocus?: boolean }) {
}: FieldSetProps & { type: CustomSectionType; autoFocus?: boolean | undefined }) {
switch (type) {
case "experience":
return (
@@ -74,7 +74,7 @@ type TextFieldProps = Omit<ComponentProps<typeof Input>, "value" | "onChange"> &
/** Checked after the field is first left, then as you type. */
validate?: Validator;
hint?: ReactNode;
wide?: boolean;
wide?: boolean | undefined;
};
/** A labelled text field. Errors appear after the first blur and say how to fix them. */
@@ -109,7 +109,7 @@ export function TextField({
/>
}
/>
<FormMessage errors={error ? [error] : undefined} />
<FormMessage errors={error ? [error] : []} />
{hint && !error && <FormDescription>{hint}</FormDescription>}
</FormItem>
);
@@ -137,7 +137,7 @@ export function WebsiteField({ label, value, onChange, allowInlineLink = true }:
<URLInput value={value} onChange={onChange} hideLabelButton={inline} onBlur={() => setTouched(true)} />
}
/>
<FormMessage errors={error ? [error] : undefined} />
<FormMessage errors={error ? [error] : []} />
</FormItem>
{allowInlineLink && (
@@ -74,11 +74,7 @@ export async function createExportFile(
const data = getResumeExportData(resume.data, target);
if (format === "pdf") {
return createResumePdfBlob(
data,
undefined,
target === "cover-letter" ? { includeCoverLetterHeader: options?.includeCoverLetterHeader } : undefined,
);
return createResumePdfBlob(data, undefined, target === "cover-letter" ? options : undefined);
}
const resolveTitle = await createSectionTitleResolver(data);
@@ -26,14 +26,14 @@ type PdfCanvasDocumentProps = {
type PdfCanvasPageProps = {
caption?: ReactNode;
className?: string;
className?: string | undefined;
overlay?: ReactNode;
document: PDFDocumentProxy;
onLoadSuccess: (pageNumber: number, pageSize: PreviewPageSize) => void;
onRenderSuccess?: () => void;
pageNumber: number;
pageScale: number;
pageSize?: PreviewPageSize;
pageSize?: PreviewPageSize | undefined;
showPageNumbers: boolean;
totalPages: number;
};
@@ -42,7 +42,7 @@ vi.mock("pdfjs-dist/legacy/build/pdf.mjs", () => pdfjsMock.legacyModule);
describe("PDF.js browser entrypoints", () => {
beforeEach(() => {
pdfjsMock.legacyModule.GlobalWorkerOptions.workerSrc = undefined;
delete pdfjsMock.legacyModule.GlobalWorkerOptions.workerSrc;
pdfjsMock.legacyModule.getDocument.mockClear();
pdfjsMock.loadingTask.destroy.mockClear();
pdfjsMock.pdfDocument.getPage.mockClear();
@@ -1,6 +1,6 @@
import type { PageMap } from "@reactive-resume/pdf/page-map";
import type { Template } from "@reactive-resume/schema/templates";
import type { CSSProperties } from "react";
import type { MotionStyle } from "motion/react";
import type { ResolvedResumePreviewProps } from "./preview.shared";
import type { PreviewPageSize } from "./preview.shared.utils";
import { t } from "@lingui/core/macro";
@@ -217,7 +217,7 @@ export function ResumePreviewClient({
key={visiblePdf.id}
aria-hidden={visiblePdf.phase !== "active"}
data-resume-preview-template={visiblePdf.template}
style={{ "--resume-preview-page-gap": resolvedPageGap } as CSSProperties}
style={{ "--resume-preview-page-gap": resolvedPageGap } as MotionStyle}
className={cn("col-start-1 row-start-1", visiblePdf.phase !== "active" && "pointer-events-none")}
initial={{ opacity: visiblePdf.phase === "active" ? 1 : 0 }}
animate={{ opacity: visiblePdf.phase === "active" ? 1 : 0 }}
@@ -7,11 +7,11 @@ import { DEFAULT_PDF_PAGE_SIZE, getResumePreviewGapValue, getScaledPreviewPageSi
export type ResumePreviewProps = {
className?: string;
data?: ResumeData;
data?: ResumeData | undefined;
pageGap?: CSSProperties["gap"];
pageLayout?: "horizontal" | "vertical";
pageScale?: number;
pageClassName?: string;
pageClassName?: string | undefined;
showPageNumbers?: boolean;
/** Letters show the sender's header, which a document with only a letter otherwise leaves out. */
includeCoverLetterHeader?: boolean;
@@ -1,6 +1,6 @@
// @vitest-environment happy-dom
import { render, screen } from "@testing-library/react";
import { beforeAll, expect, it, vi } from "vitest";
import { assert, beforeAll, expect, it, vi } from "vitest";
import { i18n } from "@lingui/core";
import { I18nProvider } from "@lingui/react";
import { sampleResumeData } from "@reactive-resume/schema/resume/sample";
@@ -19,9 +19,11 @@ function renderAccessibleText(data: typeof sampleResumeData) {
it("exposes entry and subordinate-role headings with safe nested rich-text lists", () => {
// Characterization before this change: item labels had no heading elements and rich-text lists were flattened into one paragraph.
const data = structuredClone(sampleResumeData);
const item = data.sections.experience.items[0];
assert.exists(item);
data.sections.experience.items = [
{
...data.sections.experience.items[0],
...item,
id: "hierarchy-item",
company: "Acme Company",
position: "",
@@ -37,7 +39,7 @@ it("exposes entry and subordinate-role headings with safe nested rich-text lists
],
},
{
...data.sections.experience.items[0],
...item,
id: "blank-primary-item",
company: "",
position: "",
@@ -331,7 +331,7 @@ type AccessibleSectionProps = {
title: string;
hidden: boolean;
items: CustomSectionItem[];
keywordLayout?: "inline" | "list";
keywordLayout?: "inline" | "list" | undefined;
};
function AccessibleSection({ type, title, hidden, items, keywordLayout }: AccessibleSectionProps) {
@@ -353,7 +353,7 @@ function AccessibleSection({ type, title, hidden, items, keywordLayout }: Access
}
type ResumeAccessibleTextProps = {
data?: ResumeData;
data?: ResumeData | undefined;
};
/**
+1 -1
View File
@@ -30,7 +30,7 @@ const UNITS = [
/** Time since a moment in the largest whole unit, written short: "5m", "2h", "3d", "6w", "1y". */
export function formatTimeSince(date: Date, locale: string, now = Date.now()) {
const elapsed = Math.max(0, now - date.getTime());
const { unit, ms } = UNITS.find(({ ms, below }) => elapsed / ms < below) ?? UNITS[UNITS.length - 1];
const { unit, ms } = UNITS.find(({ ms, below }) => elapsed / ms < below) ?? UNITS[4];
return new Intl.NumberFormat(locale, { style: "unit", unit, unitDisplay: "narrow" }).format(Math.floor(elapsed / ms));
}
+3 -3
View File
@@ -1,12 +1,12 @@
import { useState } from "react";
interface CommonControlledStateProps<T> {
value?: T;
defaultValue?: T;
value?: T | undefined;
defaultValue?: T | undefined;
}
type UseControlledStateProps<T, Rest extends unknown[] = []> = CommonControlledStateProps<T> & {
onChange?: (value: T, ...args: Rest) => void;
onChange?: ((value: T, ...args: Rest) => void) | undefined;
};
export function useControlledState<T, Rest extends unknown[] = []>(
@@ -1,4 +1,4 @@
import { describe, expect, it } from "vitest";
import { assert, describe, expect, it } from "vitest";
import { i18n } from "@lingui/core";
import { produce } from "immer";
import { defaultResumeData } from "@reactive-resume/schema/resume/default";
@@ -41,12 +41,14 @@ describe("moving the last custom-section item (#3180)", () => {
it("restores the original JSON after moving an experience item to a new page and back", () => {
const initial = base();
const moved = split();
const section = moved.customSections[0];
assert.exists(section);
expect(moved.metadata.layout.pages).toHaveLength(2);
const restored = produce(moved, (draft) => {
moveItem(draft, {
itemId: "2",
type: "experience",
customSectionId: moved.customSections[0].id,
customSectionId: section.id,
target: { type: "section", sectionId: "experience" },
});
});
@@ -55,13 +57,16 @@ describe("moving the last custom-section item (#3180)", () => {
it("preserves unrelated blank pages and pages with other section references", () => {
const moved = produce(split(), (draft) => {
draft.metadata.layout.pages.push({ fullWidth: true, main: [], sidebar: [] });
assert.exists(draft.metadata.layout.pages[1]);
draft.metadata.layout.pages[1].sidebar.push("skills");
});
const section = moved.customSections[0];
assert.exists(section);
const restored = produce(moved, (draft) => {
moveItem(draft, {
itemId: "2",
type: "experience",
customSectionId: moved.customSections[0].id,
customSectionId: section.id,
target: { type: "section", sectionId: "experience" },
});
});
@@ -76,11 +81,13 @@ describe("moving the last custom-section item (#3180)", () => {
const moved = produce(split(), (draft) => {
draft.metadata.layout.pages.reverse();
});
const section = moved.customSections[0];
assert.exists(section);
const restored = produce(moved, (draft) => {
moveItem(draft, {
itemId: "2",
type: "experience",
customSectionId: moved.customSections[0].id,
customSectionId: section.id,
target: { type: "section", sectionId: "experience" },
});
});
@@ -93,16 +100,19 @@ describe("moving the last custom-section item (#3180)", () => {
const moved = produce(split(), (draft) => {
draft.metadata.layout.pages.push({ fullWidth: true, main: [], sidebar: ["skills"] });
});
const section = moved.customSections[0];
assert.exists(section);
const restored = produce(moved, (draft) => {
moveItem(draft, {
itemId: "2",
type: "experience",
customSectionId: moved.customSections[0].id,
customSectionId: section.id,
target: { type: "new-section", pageIndex: 2, title: "Later" },
});
});
expect(restored.metadata.layout.pages).toHaveLength(2);
expect(restored.customSections).toHaveLength(1);
assert.exists(restored.customSections[0]);
expect(restored.customSections[0]).toMatchObject({ title: "Later", items: [company("2")] });
expect(restored.metadata.layout.pages[1]).toEqual({
fullWidth: true,
@@ -112,16 +122,20 @@ describe("moving the last custom-section item (#3180)", () => {
});
it("keeps a custom section with hidden remaining items", () => {
const moved = produce(split(), (draft) => {
assert.exists(draft.customSections[0]);
draft.customSections[0].items.push({ ...company("3"), hidden: true });
});
const section = moved.customSections[0];
assert.exists(section);
const restored = produce(moved, (draft) => {
moveItem(draft, {
itemId: "2",
type: "experience",
customSectionId: moved.customSections[0].id,
customSectionId: section.id,
target: { type: "section", sectionId: "experience" },
});
});
assert.exists(restored.customSections[0]);
expect(restored.customSections[0].items).toEqual([{ ...company("3"), hidden: true }]);
expect(restored.metadata.layout.pages).toHaveLength(2);
});
@@ -131,9 +145,11 @@ describe("moving the last custom-section item (#3180)", () => {
{ type: "new-section", pageIndex: 99, title: "Missing" },
] as const)("leaves data intact for an invalid destination $type", (target) => {
const moved = split();
const section = moved.customSections[0];
assert.exists(section);
expect(
produce(moved, (draft) => {
moveItem(draft, { itemId: "2", type: "experience", customSectionId: moved.customSections[0].id, target });
moveItem(draft, { itemId: "2", type: "experience", customSectionId: section.id, target });
}),
).toEqual(moved);
});
+1 -2
View File
@@ -81,8 +81,7 @@ export function getCompatibleMoveTargets(
const customSectionById = new Map(resumeData.customSections.map((section) => [section.id, section]));
const result: MoveTargetPage[] = [];
for (let pageIndex = 0; pageIndex < pages.length; pageIndex++) {
const page = pages[pageIndex];
for (const [pageIndex, page] of pages.entries()) {
const allSectionIds = [...page.main, ...page.sidebar];
const compatibleSections: MoveTargetSection[] = [];
@@ -108,8 +108,7 @@ export function LayoutPages() {
if (location) return location;
// Search through all pages
for (let pageIndex = 0; pageIndex < layout.pages.length; pageIndex++) {
const page = layout.pages[pageIndex];
for (const [pageIndex, page] of layout.pages.entries()) {
const mainSections = new Set(page.main);
const sidebarSections = new Set(page.sidebar);
if (mainSections.has(id)) return { pageIndex, columnId: "main" };
@@ -141,6 +140,7 @@ export function LayoutPages() {
// Same location, reorder within column
if (activeLocation.pageIndex === overLocation.pageIndex && activeLocation.columnId === overLocation.columnId) {
const page = layout.pages[activeLocation.pageIndex];
if (!page) return;
const items = page[activeLocation.columnId];
const oldIdx = items.indexOf(activeIdStr);
let newIdx = items.indexOf(overIdStr);
@@ -148,12 +148,9 @@ export function LayoutPages() {
if (newIdx === -1) newIdx = items.length - 1;
updateResumeData((draft) => {
const colOrder = draft.metadata.layout.pages[activeLocation.pageIndex][activeLocation.columnId];
draft.metadata.layout.pages[activeLocation.pageIndex][activeLocation.columnId] = arrayMove(
colOrder,
oldIdx,
newIdx,
);
const page = draft.metadata.layout.pages[activeLocation.pageIndex];
if (!page) return;
page[activeLocation.columnId] = arrayMove(page[activeLocation.columnId], oldIdx, newIdx);
});
return;
}
@@ -161,6 +158,7 @@ export function LayoutPages() {
// Different location, move between columns/pages
const fromPage = layout.pages[activeLocation.pageIndex];
const toPage = layout.pages[overLocation.pageIndex];
if (!fromPage || !toPage) return;
const fromItems = fromPage[activeLocation.columnId];
const toItems = toPage[overLocation.columnId];
const fromIdx = fromItems.indexOf(activeIdStr);
@@ -172,6 +170,7 @@ export function LayoutPages() {
updateResumeData((draft) => {
const fromPageDraft = draft.metadata.layout.pages[activeLocation.pageIndex];
const toPageDraft = draft.metadata.layout.pages[overLocation.pageIndex];
if (!fromPageDraft || !toPageDraft) return;
const from = fromPageDraft[activeLocation.columnId];
const to = toPageDraft[overLocation.columnId];
@@ -201,6 +200,7 @@ export function LayoutPages() {
// Find the first available page that isn't being deleted
const targetPageIndex = pageIndex === 0 ? 1 : 0;
const targetPage = draft.metadata.layout.pages[targetPageIndex];
if (!pageToDelete || !targetPage) return;
// Move all sections from deleted page to target page
targetPage.main.push(...pageToDelete.main);
@@ -216,6 +216,7 @@ export function LayoutPages() {
(pageIndex: number, fullWidth: boolean) => {
updateResumeData((draft) => {
const page = draft.metadata.layout.pages[pageIndex];
if (!page) return;
page.fullWidth = fullWidth;
if (fullWidth) {
@@ -451,17 +452,20 @@ function MoveToSubmenu({ id, pageIndex, columnId }: MoveToSubmenuProps) {
const moveTo = (targetPageIndex: number, targetColumnId: ColumnId) => {
updateResumeData((draft) => {
const from = draft.metadata.layout.pages[pageIndex][columnId];
const from = draft.metadata.layout.pages[pageIndex]?.[columnId];
const to = draft.metadata.layout.pages[targetPageIndex]?.[targetColumnId];
if (!from || !to) return;
const index = from.indexOf(id);
if (index === -1) return;
from.splice(index, 1);
draft.metadata.layout.pages[targetPageIndex][targetColumnId].push(id);
to.push(id);
});
};
const moveToNewPage = () => {
updateResumeData((draft) => {
const from = draft.metadata.layout.pages[pageIndex][columnId];
const from = draft.metadata.layout.pages[pageIndex]?.[columnId];
if (!from) return;
const index = from.indexOf(id);
if (index === -1) return;
from.splice(index, 1);
@@ -11,7 +11,7 @@ import {
} from "@reactive-resume/ui/components/input-group";
import { Switch } from "@reactive-resume/ui/components/switch";
import { Combobox } from "@/components/ui/combobox";
import { useResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { useSyncFormValues } from "@/hooks/use-sync-form-values";
import { useAppForm } from "@/libs/tanstack-form";
import { SectionBase } from "../shared/section-base";
@@ -34,8 +34,8 @@ const CLAMP_MAX = 100;
const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);
function PageSectionForm() {
const resume = useResume();
const page = resume?.data.metadata.page;
const resume = useCurrentResume();
const page = resume.data.metadata.page;
const updateResumeData = useUpdateResumeData();
const persist = (data: FormValues) => {
@@ -14,7 +14,7 @@ import { Separator } from "@reactive-resume/ui/components/separator";
import { Switch } from "@reactive-resume/ui/components/switch";
import { FontFamilyCombobox, FontWeightCombobox } from "@/components/typography/combobox";
import { getNextWeights } from "@/components/typography/get-next-weights";
import { useResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { useSyncFormValues } from "@/hooks/use-sync-form-values";
import { useAppForm } from "@/libs/tanstack-form";
import { SectionBase } from "../shared/section-base";
@@ -33,7 +33,7 @@ type FormValues = z.infer<typeof formSchema>;
type FontWeight = FormValues["body"]["fontWeights"][number];
type TypographyPrefix = "body" | "heading";
function useTypographyForm(typography: FormValues | undefined, persist: (data: FormValues) => void) {
function useTypographyForm(typography: FormValues, persist: (data: FormValues) => void) {
const form = useAppForm({
defaultValues: typography,
validators: { onChange: formSchema },
@@ -48,8 +48,8 @@ function useTypographyForm(typography: FormValues | undefined, persist: (data: F
type TypographyForm = ReturnType<typeof useTypographyForm>;
function TypographySectionForm() {
const resume = useResume();
const typography = resume?.data.metadata.typography;
const resume = useCurrentResume();
const typography = resume.data.metadata.typography;
const updateResumeData = useUpdateResumeData();
const persist = (data: FormValues) => {
+1 -12
View File
@@ -1,24 +1,13 @@
{
"extends": "@reactive-resume/config/tsconfig.base.json",
"include": ["**/*.ts", "**/*.tsx"],
"compilerOptions": {
"target": "ES2022",
"jsx": "react-jsx",
"module": "ESNext",
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"types": ["vite/client", "@testing-library/jest-dom/vitest"],
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"noEmit": true,
/* Linting */
"skipLibCheck": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"paths": {
"@/*": ["./src/*"]
}
+4 -2
View File
@@ -82,8 +82,10 @@ export default defineConfig({
// `vite build` builds the app, then the marketing pages' server entry, then prerenders the pages with it.
builder: {
buildApp: async (builder) => {
await builder.build(builder.environments.client);
await builder.build(builder.environments.ssr);
const { client, ssr } = builder.environments;
if (!client || !ssr) throw new Error("Missing client or SSR build environment");
await builder.build(client);
await builder.build(ssr);
await prerenderPages();
},
},
+5 -1
View File
@@ -75,6 +75,10 @@ The server checks the environment, applies database migrations, and verifies the
Internal packages are consumed as source through the `exports` map in each `package.json`, which points at `src` files. Don't expect a `dist` folder unless a package builds one explicitly.
Shared dependency versions live in the default pnpm catalog in `pnpm-workspace.yaml`; workspace manifests use `catalog:` for those ranges. Apps and packages inherit strict TypeScript settings from `packages/config/tsconfig.base.json`.
Turbo hashes shared Vitest configuration and setup files, tracks transitive source dependencies, and restores coverage and test reports from its artifact cache. Runtime variables remain available in strict environment mode through `globalPassThroughEnv`; task-level `env` declarations hash values that affect task results. CI checks package boundaries and typechecks affected packages, runs the full unit suite, and persists `.turbo/cache` between runs.
## Where new code goes
| You are changing | Put it here |
@@ -89,7 +93,7 @@ Internal packages are consumed as source through the `exports` map in each `pack
| An MCP tool, prompt, or resource | `packages/mcp` |
| A generic UI primitive or hook | `packages/ui`; workflow-specific UI stays in its web feature |
| A database column or table | `packages/db/src/schema/*`, then `pnpm db:generate` |
| A server environment variable | `packages/env/src/server.ts`, `.env.example`, and `globalEnv` in `turbo.json` |
| A server environment variable | `packages/env/src/server.ts`, `.env.example`, and `globalPassThroughEnv` plus applicable test-task `env` arrays in `turbo.json` |
| A dev-only script | `tooling/` |
A new template touches several places: `packages/schema/src/templates.ts`, `packages/pdf/src/templates/index.ts`, the template source under `packages/pdf/src/templates/<name>/`, and preview images under `apps/web/public/templates/{jpg,pdf}`.
+5 -5
View File
@@ -54,15 +54,15 @@
"@testing-library/jest-dom": "^7.0.1",
"@testing-library/react": "^16.3.3",
"@testing-library/user-event": "^14.6.7",
"@types/node": "^26.6.3",
"@types/pg": "^8.23.1",
"@types/node": "catalog:",
"@types/pg": "catalog:",
"@vitest/coverage-v8": "^5.0.2",
"happy-dom": "^20.14.5",
"knip": "^6.38.0",
"lefthook": "^2.1.15",
"pg": "^8.23.0",
"pg": "catalog:",
"turbo": "^2.11.5",
"typescript": "^7.0.2",
"vitest": "^5.0.2"
"typescript": "catalog:",
"vitest": "catalog:"
}
}
+5 -5
View File
@@ -22,13 +22,13 @@
"@reactive-resume/resume": "workspace:*",
"@reactive-resume/schema": "workspace:*",
"@reactive-resume/utils": "workspace:*",
"jsonrepair": "^3.15.0",
"zod": "^4.6.5"
"jsonrepair": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"@reactive-resume/config": "workspace:*",
"ai": "^7.0.122",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"typescript": "^7.0.2"
"ai": "catalog:",
"@typescript/native-preview": "catalog:",
"typescript": "catalog:"
}
}
+16 -16
View File
@@ -38,9 +38,9 @@
"@ai-sdk/togetherai": "^3.0.60",
"@ai-sdk/xai": "^5.0.12",
"@aws-sdk/client-s3": "^3.1143.0",
"@orpc/client": "^1.15.4",
"@orpc/client": "catalog:",
"@orpc/experimental-ratelimit": "^1.15.4",
"@orpc/server": "^1.15.4",
"@orpc/server": "catalog:",
"@reactive-resume/ai": "workspace:*",
"@reactive-resume/auth": "workspace:*",
"@reactive-resume/db": "workspace:*",
@@ -50,30 +50,30 @@
"@reactive-resume/schema": "workspace:*",
"@reactive-resume/utils": "workspace:*",
"@vercel/blob": "^2.8.0",
"ai": "^7.0.122",
"bcryptjs": "^3.0.3",
"better-auth": "1.7.6",
"drizzle-orm": "1.0.0-rc.4",
"ai": "catalog:",
"bcryptjs": "catalog:",
"better-auth": "catalog:",
"drizzle-orm": "catalog:",
"drizzle-zod": "1.0.0-beta.14-a36c63d",
"fflate": "^0.8.3",
"fflate": "catalog:",
"firecrawl": "^4.42.0",
"ioredis": "^6.0.0",
"jsonrepair": "^3.15.0",
"ioredis": "catalog:",
"jsonrepair": "catalog:",
"ollama-ai-provider-v2": "^4.0.1",
"resumable-stream": "^2.2.13",
"sanitize-html": "^2.17.7",
"sharp": "^0.35.5",
"tokenx": "^2.1.0",
"ts-pattern": "^5.9.0",
"zod": "^4.6.5"
"ts-pattern": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"@reactive-resume/config": "workspace:*",
"@types/pg": "^8.23.1",
"@types/pg": "catalog:",
"@types/sanitize-html": "^2.16.2",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"es-toolkit": "^1.52.0",
"pg": "^8.23.0",
"typescript": "^7.0.2"
"@typescript/native-preview": "catalog:",
"es-toolkit": "catalog:",
"pg": "catalog:",
"typescript": "catalog:"
}
}
+10 -4
View File
@@ -2,12 +2,18 @@
"extends": ["//"],
"tags": ["runtime:server", "role:api"],
"tasks": {
"test": { "env": ["COVER_LETTER_TEST_DATABASE_URL", "INTEGRATIONS_TEST_DATABASE_URL"] },
"test:coverage": { "env": ["COVER_LETTER_TEST_DATABASE_URL", "INTEGRATIONS_TEST_DATABASE_URL"] },
"test:agent": { "env": ["COVER_LETTER_TEST_DATABASE_URL", "INTEGRATIONS_TEST_DATABASE_URL"] },
"test": {
"env": ["$TURBO_EXTENDS$", "COVER_LETTER_TEST_DATABASE_URL", "INTEGRATIONS_TEST_DATABASE_URL"]
},
"test:coverage": {
"env": ["$TURBO_EXTENDS$", "COVER_LETTER_TEST_DATABASE_URL", "INTEGRATIONS_TEST_DATABASE_URL"]
},
"test:agent": {
"env": ["$TURBO_EXTENDS$", "COVER_LETTER_TEST_DATABASE_URL", "INTEGRATIONS_TEST_DATABASE_URL"]
},
"test:ci": {
"cache": false,
"env": ["COVER_LETTER_TEST_DATABASE_URL", "INTEGRATIONS_TEST_DATABASE_URL"]
"env": ["$TURBO_EXTENDS$", "COVER_LETTER_TEST_DATABASE_URL", "INTEGRATIONS_TEST_DATABASE_URL"]
}
}
}
+11 -11
View File
@@ -15,25 +15,25 @@
"test:agent": "vitest run --reporter=agent --reporter=json --outputFile.json=reports/vitest-results.json --passWithNoTests"
},
"dependencies": {
"@better-auth/api-key": "^1.7.6",
"@better-auth/api-key": "catalog:",
"@better-auth/drizzle-adapter": "^1.7.6",
"@better-auth/infra": "^0.4.13",
"@better-auth/oauth-provider": "^1.7.6",
"@better-auth/passkey": "^1.7.6",
"@better-auth/infra": "catalog:",
"@better-auth/oauth-provider": "catalog:",
"@better-auth/passkey": "catalog:",
"@reactive-resume/db": "workspace:*",
"@reactive-resume/email": "workspace:*",
"@reactive-resume/env": "workspace:*",
"@reactive-resume/utils": "workspace:*",
"bcryptjs": "^3.0.3",
"better-auth": "1.7.6",
"drizzle-orm": "1.0.0-rc.4",
"bcryptjs": "catalog:",
"better-auth": "catalog:",
"drizzle-orm": "catalog:",
"jose": "^6.2.12",
"react": "^19.3.0"
"react": "catalog:"
},
"devDependencies": {
"@reactive-resume/config": "workspace:*",
"@types/react": "^19.3.0",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"typescript": "^7.0.2"
"@types/react": "catalog:",
"@typescript/native-preview": "catalog:",
"typescript": "catalog:"
}
}
+6 -6
View File
@@ -28,15 +28,15 @@
"@reactive-resume/env": "workspace:*",
"@reactive-resume/schema": "workspace:*",
"@reactive-resume/utils": "workspace:*",
"drizzle-orm": "1.0.0-rc.4",
"ioredis": "^6.0.0",
"pg": "^8.23.0"
"drizzle-orm": "catalog:",
"ioredis": "catalog:",
"pg": "catalog:"
},
"devDependencies": {
"@reactive-resume/config": "workspace:*",
"@types/pg": "^8.23.1",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"@types/pg": "catalog:",
"@typescript/native-preview": "catalog:",
"drizzle-kit": "1.0.0-rc.4",
"typescript": "^7.0.2"
"typescript": "catalog:"
}
}
+2 -2
View File
@@ -20,7 +20,7 @@
},
"devDependencies": {
"@reactive-resume/config": "workspace:*",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"typescript": "^7.0.2"
"@typescript/native-preview": "catalog:",
"typescript": "catalog:"
}
}
+3 -3
View File
@@ -62,10 +62,10 @@
"@deepseek-ai/schemastery": "^3.18.4",
"@reactive-resume/config": "workspace:*",
"@reactive-resume/mcp": "workspace:*",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"@typescript/native-preview": "catalog:",
"tsdown": "^0.23.0",
"typescript": "^7.0.2",
"vitest": "^5.0.2"
"typescript": "catalog:",
"vitest": "catalog:"
},
"dsh": {
"bundle": {
+4 -4
View File
@@ -18,15 +18,15 @@
"dependencies": {
"@reactive-resume/env": "workspace:*",
"nodemailer": "^10.0.12",
"react": "^19.3.0",
"react": "catalog:",
"react-email": "^6.11.0"
},
"devDependencies": {
"@react-email/ui": "^6.11.0",
"@reactive-resume/config": "workspace:*",
"@types/nodemailer": "^8.0.2",
"@types/react": "^19.3.0",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"typescript": "^7.0.2"
"@types/react": "catalog:",
"@typescript/native-preview": "catalog:",
"typescript": "catalog:"
}
}
+4 -4
View File
@@ -17,12 +17,12 @@
"@reactive-resume/ai": "workspace:*",
"@reactive-resume/utils": "workspace:*",
"@t3-oss/env-core": "^0.13.11",
"zod": "^4.6.5"
"zod": "catalog:"
},
"devDependencies": {
"@reactive-resume/config": "workspace:*",
"@types/node": "^26.6.3",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"typescript": "^7.0.2"
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"typescript": "catalog:"
}
}
+2 -2
View File
@@ -18,7 +18,7 @@
},
"devDependencies": {
"@reactive-resume/config": "workspace:*",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"typescript": "^7.0.2"
"@typescript/native-preview": "catalog:",
"typescript": "catalog:"
}
}
+4 -4
View File
@@ -21,12 +21,12 @@
"@reactive-resume/resume": "workspace:*",
"@reactive-resume/schema": "workspace:*",
"@reactive-resume/utils": "workspace:*",
"fflate": "^0.8.3",
"zod": "^4.6.5"
"fflate": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"@reactive-resume/config": "workspace:*",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"typescript": "^7.0.2"
"@typescript/native-preview": "catalog:",
"typescript": "catalog:"
}
}
+5 -5
View File
@@ -17,17 +17,17 @@
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.31.0",
"@orpc/server": "^1.15.4",
"@orpc/server": "catalog:",
"@reactive-resume/ai": "workspace:*",
"@reactive-resume/api": "workspace:*",
"@reactive-resume/env": "workspace:*",
"@reactive-resume/schema": "workspace:*",
"zod": "^4.6.5"
"zod": "catalog:"
},
"devDependencies": {
"@reactive-resume/config": "workspace:*",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"typescript": "^7.0.2",
"vitest": "^5.0.2"
"@typescript/native-preview": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:"
}
}
+5 -5
View File
@@ -33,18 +33,18 @@
"@reactive-resume/utils": "workspace:*",
"cjk-regex": "^3.5.0",
"fast-png": "^8.0.0",
"fflate": "^0.8.3",
"fflate": "catalog:",
"node-html-parser": "^9.0.4",
"react": "^19.3.0",
"react": "catalog:",
"react-reconciler": "0.34.0"
},
"devDependencies": {
"@napi-rs/canvas": "1.0.9",
"@reactive-resume/config": "workspace:*",
"@types/react": "^19.3.0",
"@types/react": "catalog:",
"@types/react-reconciler": "^0.33.1",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"@typescript/native-preview": "catalog:",
"pdfjs-dist": "6.3.289",
"typescript": "^7.0.2"
"typescript": "catalog:"
}
}
+3 -3
View File
@@ -32,12 +32,12 @@
"css-tree": "^3.2.1",
"fast-json-patch": "^3.1.1",
"wink-porter2-stemmer": "2.0.1",
"zod": "^4.6.5"
"zod": "catalog:"
},
"devDependencies": {
"@reactive-resume/config": "workspace:*",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"@typescript/native-preview": "catalog:",
"fast-check": "^4.10.2",
"typescript": "^7.0.2"
"typescript": "catalog:"
}
}
+3 -3
View File
@@ -19,11 +19,11 @@
"test:agent": "vitest run --reporter=agent --reporter=json --outputFile.json=reports/vitest-results.json --passWithNoTests"
},
"dependencies": {
"zod": "^4.6.5"
"zod": "catalog:"
},
"devDependencies": {
"@reactive-resume/config": "workspace:*",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"typescript": "^7.0.2"
"@typescript/native-preview": "catalog:",
"typescript": "catalog:"
}
}
+5 -5
View File
@@ -25,7 +25,7 @@
"@reactive-resume/utils": "workspace:*",
"class-variance-authority": "^0.7.1",
"cmdk": "^1.1.1",
"react": "^19.3.0",
"react": "catalog:",
"react-dom": "^19.3.0",
"shadcn": "^4.21.0",
"tw-animate-css": "^1.4.0"
@@ -34,11 +34,11 @@
"@reactive-resume/config": "workspace:*",
"@tailwindcss/postcss": "^4.3.3",
"@tailwindcss/typography": "^0.5.20",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@typescript/native-preview": "catalog:",
"postcss": "^8.5.28",
"tailwindcss": "^4.3.3",
"typescript": "^7.0.2"
"typescript": "catalog:"
}
}
+5 -5
View File
@@ -22,16 +22,16 @@
},
"dependencies": {
"@sindresorhus/slugify": "^3.0.1",
"@uiw/color-convert": "^2.10.3",
"@uiw/color-convert": "catalog:",
"cn": "^0.4.0",
"unique-names-generator": "^4.7.1",
"uuid": "^14.0.2",
"zod": "^4.6.5"
"zod": "catalog:"
},
"devDependencies": {
"@reactive-resume/config": "workspace:*",
"@types/node": "^26.6.3",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"typescript": "^7.0.2"
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"typescript": "catalog:"
}
}
+223 -149
View File
@@ -162,6 +162,87 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
catalogs:
default:
'@better-auth/api-key':
specifier: ^1.7.6
version: 1.7.6
'@better-auth/infra':
specifier: ^0.4.13
version: 0.4.13
'@better-auth/oauth-provider':
specifier: ^1.7.6
version: 1.7.6
'@better-auth/passkey':
specifier: ^1.7.6
version: 1.7.6
'@orpc/client':
specifier: ^1.15.4
version: 1.15.4
'@orpc/server':
specifier: ^1.15.4
version: 1.15.4
'@types/node':
specifier: ^26.6.3
version: 26.6.3
'@types/pg':
specifier: ^8.23.1
version: 8.23.1
'@types/react':
specifier: ^19.3.0
version: 19.3.0
'@types/react-dom':
specifier: ^19.3.0
version: 19.3.0
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
version: 7.0.0-dev.20260707.2
'@uiw/color-convert':
specifier: ^2.10.3
version: 2.10.3
ai:
specifier: ^7.0.122
version: 7.0.122
bcryptjs:
specifier: ^3.0.3
version: 3.0.3
better-auth:
specifier: 1.7.6
version: 1.7.6
drizzle-orm:
specifier: 1.0.0-rc.4
version: 1.0.0-rc.4
es-toolkit:
specifier: ^1.52.0
version: 1.52.0
fflate:
specifier: ^0.8.3
version: 0.8.3
ioredis:
specifier: ^6.0.0
version: 6.0.0
jsonrepair:
specifier: ^3.15.0
version: 3.15.0
pg:
specifier: ^8.23.0
version: 8.23.0
react:
specifier: ^19.3.0
version: 19.3.0
ts-pattern:
specifier: ^5.9.0
version: 5.9.0
typescript:
specifier: ^7.0.2
version: 7.0.2
vitest:
specifier: ^5.0.2
version: 5.0.2
zod:
specifier: ^4.6.5
version: 4.6.5
overrides:
'@better-auth/infra>@better-fetch/fetch': 1.3.2
esbuild@>=0.17.0 <0.28.1: ^0.28.1
@@ -201,10 +282,10 @@ importers:
specifier: ^14.6.7
version: 14.6.7(@testing-library/dom@10.4.2)
'@types/node':
specifier: ^26.6.3
specifier: 'catalog:'
version: 26.6.3
'@types/pg':
specifier: ^8.23.1
specifier: 'catalog:'
version: 8.23.1
'@vitest/coverage-v8':
specifier: ^5.0.2
@@ -219,16 +300,16 @@ importers:
specifier: ^2.1.15
version: 2.1.15
pg:
specifier: ^8.23.0
specifier: 'catalog:'
version: 8.23.0
turbo:
specifier: ^2.11.5
version: 2.11.5
typescript:
specifier: ^7.0.2
specifier: 'catalog:'
version: 7.0.2
vitest:
specifier: ^5.0.2
specifier: 'catalog:'
version: 5.0.2(@types/node@26.6.3)(@vitest/coverage-v8@5.0.2)(happy-dom@20.14.5)(vite@8.3.1(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))
apps/server:
@@ -276,19 +357,19 @@ importers:
specifier: ^3.1143.0
version: 3.1143.0
'@better-auth/api-key':
specifier: ^1.7.6
specifier: 'catalog:'
version: 1.7.6(@better-auth/core@1.7.6(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-call@1.4.0(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.2)(better-auth@1.7.6(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))(next@16.3.3(@babel/core@7.29.7)(@playwright/test@1.63.0)(@types/node@26.6.3)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.15)(vitest@5.0.2))(better-call@1.4.0(zod@4.6.5))
'@better-auth/drizzle-adapter':
specifier: ^1.7.6
version: 1.7.6(@better-auth/core@1.7.6(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-call@1.4.0(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.2)(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))
'@better-auth/infra':
specifier: ^0.4.13
specifier: 'catalog:'
version: 0.4.13(@better-auth/core@1.7.6(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-call@1.4.0(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(better-auth@1.7.6(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))(next@16.3.3(@babel/core@7.29.7)(@playwright/test@1.63.0)(@types/node@26.6.3)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.15)(vitest@5.0.2))(zod@4.6.5)
'@better-auth/oauth-provider':
specifier: ^1.7.6
specifier: 'catalog:'
version: 1.7.6(@better-auth/core@1.7.6(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-call@1.4.0(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-auth@1.7.6(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))(next@16.3.3(@babel/core@7.29.7)(@playwright/test@1.63.0)(@types/node@26.6.3)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.15)(vitest@5.0.2))(better-call@1.4.0(zod@4.6.5))
'@better-auth/passkey':
specifier: ^1.7.6
specifier: 'catalog:'
version: 1.7.6(@better-auth/core@1.7.6(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-call@1.4.0(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-auth@1.7.6(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))(next@16.3.3(@babel/core@7.29.7)(@playwright/test@1.63.0)(@types/node@26.6.3)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.15)(vitest@5.0.2))(better-call@1.4.0(zod@4.6.5))(nanostores@1.5.4)
'@bramus/specificity':
specifier: ^2.4.2
@@ -306,7 +387,7 @@ importers:
specifier: ^1.31.0
version: 1.31.0(supports-color@7.2.0)(zod@4.6.5)
'@orpc/client':
specifier: ^1.15.4
specifier: 'catalog:'
version: 1.15.4
'@orpc/experimental-ratelimit':
specifier: ^1.15.4
@@ -318,7 +399,7 @@ importers:
specifier: ^1.15.4
version: 1.15.4(ws@8.21.3)
'@orpc/server':
specifier: ^1.15.4
specifier: 'catalog:'
version: 1.15.4(ws@8.21.3)
'@orpc/zod':
specifier: ^1.15.4
@@ -351,7 +432,7 @@ importers:
specifier: ^0.13.11
version: 0.13.11(typescript@7.0.2)(zod@4.6.5)
'@uiw/color-convert':
specifier: ^2.10.3
specifier: 'catalog:'
version: 2.10.3(@babel/runtime@7.29.7)
'@vercel/blob':
specifier: ^2.8.0
@@ -360,13 +441,13 @@ importers:
specifier: ^3.9.9
version: 3.9.9(@aws-sdk/credential-provider-web-identity@3.972.78)(ws@8.21.3)
ai:
specifier: ^7.0.122
specifier: 'catalog:'
version: 7.0.122(zod@4.6.5)
bcryptjs:
specifier: ^3.0.3
specifier: 'catalog:'
version: 3.0.3
better-auth:
specifier: 1.7.6
specifier: 'catalog:'
version: 1.7.6(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))(next@16.3.3(@babel/core@7.29.7)(@playwright/test@1.63.0)(@types/node@26.6.3)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.15)(vitest@5.0.2)
cjk-regex:
specifier: ^3.5.0
@@ -375,13 +456,13 @@ importers:
specifier: ^3.2.1
version: 3.2.1
drizzle-orm:
specifier: 1.0.0-rc.4
specifier: 'catalog:'
version: 1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5)
drizzle-zod:
specifier: 1.0.0-beta.14-a36c63d
version: 1.0.0-beta.14-a36c63d(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))(zod@4.6.5)
es-toolkit:
specifier: ^1.52.0
specifier: 'catalog:'
version: 1.52.0
fast-json-patch:
specifier: ^3.1.1
@@ -390,7 +471,7 @@ importers:
specifier: ^8.0.0
version: 8.0.0
fflate:
specifier: ^0.8.3
specifier: 'catalog:'
version: 0.8.3
firecrawl:
specifier: ^4.42.0
@@ -399,13 +480,13 @@ importers:
specifier: ^4.13.11
version: 4.13.11
ioredis:
specifier: ^6.0.0
specifier: 'catalog:'
version: 6.0.0(supports-color@7.2.0)
jose:
specifier: ^6.2.12
version: 6.2.12
jsonrepair:
specifier: ^3.15.0
specifier: 'catalog:'
version: 3.15.0
node-html-parser:
specifier: ^9.0.4
@@ -417,10 +498,10 @@ importers:
specifier: ^4.0.1
version: 4.0.1(ai@7.0.122(zod@4.6.5))(zod@4.6.5)
pg:
specifier: ^8.23.0
specifier: 'catalog:'
version: 8.23.0
react:
specifier: ^19.3.0
specifier: 'catalog:'
version: 19.3.0
react-email:
specifier: ^6.11.0
@@ -441,7 +522,7 @@ importers:
specifier: ^2.1.0
version: 2.1.0
ts-pattern:
specifier: ^5.9.0
specifier: 'catalog:'
version: 5.9.0
unique-names-generator:
specifier: ^4.7.1
@@ -450,23 +531,23 @@ importers:
specifier: ^14.0.2
version: 14.0.2
zod:
specifier: ^4.6.5
specifier: 'catalog:'
version: 4.6.5
devDependencies:
'@reactive-resume/config':
specifier: workspace:*
version: link:../../packages/config
'@types/node':
specifier: ^26.6.3
specifier: 'catalog:'
version: 26.6.3
'@types/pg':
specifier: ^8.23.1
specifier: 'catalog:'
version: 8.23.1
'@types/react':
specifier: ^19.3.0
specifier: 'catalog:'
version: 19.3.0
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
specifier: 'catalog:'
version: 7.0.0-dev.20260707.2
tsdown:
specifier: ^0.23.0
@@ -475,10 +556,10 @@ importers:
specifier: ^4.23.15
version: 4.23.15
typescript:
specifier: ^7.0.2
specifier: 'catalog:'
version: 7.0.2
vitest:
specifier: ^5.0.2
specifier: 'catalog:'
version: 5.0.2(@types/node@26.6.3)(@vitest/coverage-v8@5.0.2)(happy-dom@20.14.5)(vite@8.3.1(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))
apps/web:
@@ -490,16 +571,16 @@ importers:
specifier: ^1.8.0
version: 1.8.0(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
'@better-auth/api-key':
specifier: ^1.7.6
specifier: 'catalog:'
version: 1.7.6(@better-auth/core@1.7.6(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-call@1.4.0(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.2)(better-auth@1.7.6(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))(next@16.3.3(@babel/core@7.29.7)(@playwright/test@1.63.0)(@types/node@26.6.3)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.15)(vitest@5.0.2))(better-call@1.4.0(zod@4.6.5))
'@better-auth/infra':
specifier: ^0.4.13
specifier: 'catalog:'
version: 0.4.13(@better-auth/core@1.7.6(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-call@1.4.0(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(better-auth@1.7.6(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))(next@16.3.3(@babel/core@7.29.7)(@playwright/test@1.63.0)(@types/node@26.6.3)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.15)(vitest@5.0.2))(zod@4.6.5)
'@better-auth/oauth-provider':
specifier: ^1.7.6
specifier: 'catalog:'
version: 1.7.6(@better-auth/core@1.7.6(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-call@1.4.0(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-auth@1.7.6(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))(next@16.3.3(@babel/core@7.29.7)(@playwright/test@1.63.0)(@types/node@26.6.3)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.15)(vitest@5.0.2))(better-call@1.4.0(zod@4.6.5))
'@better-auth/passkey':
specifier: ^1.7.6
specifier: 'catalog:'
version: 1.7.6(@better-auth/core@1.7.6(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-call@1.4.0(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-auth@1.7.6(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))(next@16.3.3(@babel/core@7.29.7)(@playwright/test@1.63.0)(@types/node@26.6.3)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.15)(vitest@5.0.2))(better-call@1.4.0(zod@4.6.5))(nanostores@1.5.4)
'@codemirror/autocomplete':
specifier: ^6.20.3
@@ -541,10 +622,10 @@ importers:
specifier: ^6.8.0
version: 6.8.0(@babel/core@7.29.7(supports-color@7.2.0))(babel-plugin-macros@3.1.0)(react@19.3.0)
'@orpc/client':
specifier: ^1.15.4
specifier: 'catalog:'
version: 1.15.4
'@orpc/server':
specifier: ^1.15.4
specifier: 'catalog:'
version: 1.15.4(ws@8.21.3)
'@orpc/tanstack-query':
specifier: ^1.15.4
@@ -634,25 +715,25 @@ importers:
specifier: ^3.0.6
version: 3.0.6
'@uiw/color-convert':
specifier: ^2.10.3
specifier: 'catalog:'
version: 2.10.3(@babel/runtime@7.29.7)
'@uiw/react-color-colorful':
specifier: ^2.10.3
version: 2.10.3(@babel/runtime@7.29.7)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
ai:
specifier: ^7.0.122
specifier: 'catalog:'
version: 7.0.122(zod@4.6.5)
better-auth:
specifier: 1.7.6
specifier: 'catalog:'
version: 1.7.6(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))(next@16.3.3(@babel/core@7.29.7)(@playwright/test@1.63.0)(@types/node@26.6.3)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.15)(vitest@5.0.2)
cmdk:
specifier: ^1.1.1
version: 1.1.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
es-toolkit:
specifier: ^1.52.0
specifier: 'catalog:'
version: 1.52.0
fflate:
specifier: ^0.8.3
specifier: 'catalog:'
version: 0.8.3
fuse.js:
specifier: ^7.5.0
@@ -676,7 +757,7 @@ importers:
specifier: ^4.2.0
version: 4.2.0(react@19.3.0)
react:
specifier: ^19.3.0
specifier: 'catalog:'
version: 19.3.0
react-dom:
specifier: ^19.3.0
@@ -694,13 +775,13 @@ importers:
specifier: ^4.0.1
version: 4.0.1(supports-color@7.2.0)
ts-pattern:
specifier: ^5.9.0
specifier: 'catalog:'
version: 5.9.0
usehooks-ts:
specifier: ^3.1.1
version: 3.1.1(react@19.3.0)
zod:
specifier: ^4.6.5
specifier: 'catalog:'
version: 4.6.5
zustand:
specifier: ^5.0.15
@@ -746,13 +827,13 @@ importers:
specifier: ^7.20.5
version: 7.20.5
'@types/react':
specifier: ^19.3.0
specifier: 'catalog:'
version: 19.3.0
'@types/react-dom':
specifier: ^19.3.0
specifier: 'catalog:'
version: 19.3.0(@types/react@19.3.0)
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
specifier: 'catalog:'
version: 7.0.0-dev.20260707.2
'@vitejs/plugin-react':
specifier: ^6.1.1
@@ -764,7 +845,7 @@ importers:
specifier: ^1.0.0
version: 1.0.0
typescript:
specifier: ^7.0.2
specifier: 'catalog:'
version: 7.0.2
vite:
specifier: ^8.3.1
@@ -782,23 +863,23 @@ importers:
specifier: workspace:*
version: link:../utils
jsonrepair:
specifier: ^3.15.0
specifier: 'catalog:'
version: 3.15.0
zod:
specifier: ^4.6.5
specifier: 'catalog:'
version: 4.6.5
devDependencies:
'@reactive-resume/config':
specifier: workspace:*
version: link:../config
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
specifier: 'catalog:'
version: 7.0.0-dev.20260707.2
ai:
specifier: ^7.0.122
specifier: 'catalog:'
version: 7.0.122(zod@4.6.5)
typescript:
specifier: ^7.0.2
specifier: 'catalog:'
version: 7.0.2
packages/api:
@@ -846,13 +927,13 @@ importers:
specifier: ^3.1143.0
version: 3.1143.0
'@orpc/client':
specifier: ^1.15.4
specifier: 'catalog:'
version: 1.15.4
'@orpc/experimental-ratelimit':
specifier: ^1.15.4
version: 1.15.4(ws@8.21.3)
'@orpc/server':
specifier: ^1.15.4
specifier: 'catalog:'
version: 1.15.4(ws@8.21.3)
'@reactive-resume/ai':
specifier: workspace:*
@@ -882,31 +963,31 @@ importers:
specifier: ^2.8.0
version: 2.8.0
ai:
specifier: ^7.0.122
specifier: 'catalog:'
version: 7.0.122(zod@4.6.5)
bcryptjs:
specifier: ^3.0.3
specifier: 'catalog:'
version: 3.0.3
better-auth:
specifier: 1.7.6
specifier: 'catalog:'
version: 1.7.6(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))(next@16.3.3(@babel/core@7.29.7)(@playwright/test@1.63.0)(@types/node@26.6.3)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.15)(vitest@5.0.2)
drizzle-orm:
specifier: 1.0.0-rc.4
specifier: 'catalog:'
version: 1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5)
drizzle-zod:
specifier: 1.0.0-beta.14-a36c63d
version: 1.0.0-beta.14-a36c63d(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))(zod@4.6.5)
fflate:
specifier: ^0.8.3
specifier: 'catalog:'
version: 0.8.3
firecrawl:
specifier: ^4.42.0
version: 4.42.0(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)
ioredis:
specifier: ^6.0.0
specifier: 'catalog:'
version: 6.0.0(supports-color@7.2.0)
jsonrepair:
specifier: ^3.15.0
specifier: 'catalog:'
version: 3.15.0
ollama-ai-provider-v2:
specifier: ^4.0.1
@@ -924,50 +1005,50 @@ importers:
specifier: ^2.1.0
version: 2.1.0
ts-pattern:
specifier: ^5.9.0
specifier: 'catalog:'
version: 5.9.0
zod:
specifier: ^4.6.5
specifier: 'catalog:'
version: 4.6.5
devDependencies:
'@reactive-resume/config':
specifier: workspace:*
version: link:../config
'@types/pg':
specifier: ^8.23.1
specifier: 'catalog:'
version: 8.23.1
'@types/sanitize-html':
specifier: ^2.16.2
version: 2.16.2
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
specifier: 'catalog:'
version: 7.0.0-dev.20260707.2
es-toolkit:
specifier: ^1.52.0
specifier: 'catalog:'
version: 1.52.0
pg:
specifier: ^8.23.0
specifier: 'catalog:'
version: 8.23.0
typescript:
specifier: ^7.0.2
specifier: 'catalog:'
version: 7.0.2
packages/auth:
dependencies:
'@better-auth/api-key':
specifier: ^1.7.6
specifier: 'catalog:'
version: 1.7.6(@better-auth/core@1.7.6(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-call@1.4.0(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.2)(better-auth@1.7.6(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))(next@16.3.3(@babel/core@7.29.7)(@playwright/test@1.63.0)(@types/node@26.6.3)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.15)(vitest@5.0.2))(better-call@1.4.0(zod@4.6.5))
'@better-auth/drizzle-adapter':
specifier: ^1.7.6
version: 1.7.6(@better-auth/core@1.7.6(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-call@1.4.0(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.2)(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))
'@better-auth/infra':
specifier: ^0.4.13
specifier: 'catalog:'
version: 0.4.13(@better-auth/core@1.7.6(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-call@1.4.0(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(better-auth@1.7.6(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))(next@16.3.3(@babel/core@7.29.7)(@playwright/test@1.63.0)(@types/node@26.6.3)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.15)(vitest@5.0.2))(zod@4.6.5)
'@better-auth/oauth-provider':
specifier: ^1.7.6
specifier: 'catalog:'
version: 1.7.6(@better-auth/core@1.7.6(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-call@1.4.0(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-auth@1.7.6(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))(next@16.3.3(@babel/core@7.29.7)(@playwright/test@1.63.0)(@types/node@26.6.3)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.15)(vitest@5.0.2))(better-call@1.4.0(zod@4.6.5))
'@better-auth/passkey':
specifier: ^1.7.6
specifier: 'catalog:'
version: 1.7.6(@better-auth/core@1.7.6(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-call@1.4.0(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.4))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-auth@1.7.6(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))(next@16.3.3(@babel/core@7.29.7)(@playwright/test@1.63.0)(@types/node@26.6.3)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.15)(vitest@5.0.2))(better-call@1.4.0(zod@4.6.5))(nanostores@1.5.4)
'@reactive-resume/db':
specifier: workspace:*
@@ -982,32 +1063,32 @@ importers:
specifier: workspace:*
version: link:../utils
bcryptjs:
specifier: ^3.0.3
specifier: 'catalog:'
version: 3.0.3
better-auth:
specifier: 1.7.6
specifier: 'catalog:'
version: 1.7.6(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5))(next@16.3.3(@babel/core@7.29.7)(@playwright/test@1.63.0)(@types/node@26.6.3)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.15)(vitest@5.0.2)
drizzle-orm:
specifier: 1.0.0-rc.4
specifier: 'catalog:'
version: 1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5)
jose:
specifier: ^6.2.12
version: 6.2.12
react:
specifier: ^19.3.0
specifier: 'catalog:'
version: 19.3.0
devDependencies:
'@reactive-resume/config':
specifier: workspace:*
version: link:../config
'@types/react':
specifier: ^19.3.0
specifier: 'catalog:'
version: 19.3.0
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
specifier: 'catalog:'
version: 7.0.0-dev.20260707.2
typescript:
specifier: ^7.0.2
specifier: 'catalog:'
version: 7.0.2
packages/config: {}
@@ -1024,29 +1105,29 @@ importers:
specifier: workspace:*
version: link:../utils
drizzle-orm:
specifier: 1.0.0-rc.4
specifier: 'catalog:'
version: 1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5)
ioredis:
specifier: ^6.0.0
specifier: 'catalog:'
version: 6.0.0(supports-color@7.2.0)
pg:
specifier: ^8.23.0
specifier: 'catalog:'
version: 8.23.0
devDependencies:
'@reactive-resume/config':
specifier: workspace:*
version: link:../config
'@types/pg':
specifier: ^8.23.1
specifier: 'catalog:'
version: 8.23.1
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
specifier: 'catalog:'
version: 7.0.0-dev.20260707.2
drizzle-kit:
specifier: 1.0.0-rc.4
version: 1.0.0-rc.4
typescript:
specifier: ^7.0.2
specifier: 'catalog:'
version: 7.0.2
packages/docx:
@@ -1065,10 +1146,10 @@ importers:
specifier: workspace:*
version: link:../config
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
specifier: 'catalog:'
version: 7.0.0-dev.20260707.2
typescript:
specifier: ^7.0.2
specifier: 'catalog:'
version: 7.0.2
packages/dsh-plugin:
@@ -1092,16 +1173,16 @@ importers:
specifier: workspace:*
version: link:../mcp
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
specifier: 'catalog:'
version: 7.0.0-dev.20260707.2
tsdown:
specifier: ^0.23.0
version: 0.23.0(@typescript/native-preview@7.0.0-dev.20260707.2)(oxc-resolver@11.24.2)(tsx@4.23.15)(typescript@7.0.2)
typescript:
specifier: ^7.0.2
specifier: 'catalog:'
version: 7.0.2
vitest:
specifier: ^5.0.2
specifier: 'catalog:'
version: 5.0.2(@types/node@26.6.3)(@vitest/coverage-v8@5.0.2)(happy-dom@20.14.5)(vite@8.3.1(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))
packages/email:
@@ -1113,7 +1194,7 @@ importers:
specifier: ^10.0.12
version: 10.0.12
react:
specifier: ^19.3.0
specifier: 'catalog:'
version: 19.3.0
react-email:
specifier: ^6.11.0
@@ -1129,13 +1210,13 @@ importers:
specifier: ^8.0.2
version: 8.0.2
'@types/react':
specifier: ^19.3.0
specifier: 'catalog:'
version: 19.3.0
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
specifier: 'catalog:'
version: 7.0.0-dev.20260707.2
typescript:
specifier: ^7.0.2
specifier: 'catalog:'
version: 7.0.2
packages/env:
@@ -1150,20 +1231,20 @@ importers:
specifier: ^0.13.11
version: 0.13.11(typescript@7.0.2)(zod@4.6.5)
zod:
specifier: ^4.6.5
specifier: 'catalog:'
version: 4.6.5
devDependencies:
'@reactive-resume/config':
specifier: workspace:*
version: link:../config
'@types/node':
specifier: ^26.6.3
specifier: 'catalog:'
version: 26.6.3
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
specifier: 'catalog:'
version: 7.0.0-dev.20260707.2
typescript:
specifier: ^7.0.2
specifier: 'catalog:'
version: 7.0.2
packages/fonts:
@@ -1176,10 +1257,10 @@ importers:
specifier: workspace:*
version: link:../config
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
specifier: 'catalog:'
version: 7.0.0-dev.20260707.2
typescript:
specifier: ^7.0.2
specifier: 'catalog:'
version: 7.0.2
packages/import:
@@ -1194,20 +1275,20 @@ importers:
specifier: workspace:*
version: link:../utils
fflate:
specifier: ^0.8.3
specifier: 'catalog:'
version: 0.8.3
zod:
specifier: ^4.6.5
specifier: 'catalog:'
version: 4.6.5
devDependencies:
'@reactive-resume/config':
specifier: workspace:*
version: link:../config
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
specifier: 'catalog:'
version: 7.0.0-dev.20260707.2
typescript:
specifier: ^7.0.2
specifier: 'catalog:'
version: 7.0.2
packages/mcp:
@@ -1216,7 +1297,7 @@ importers:
specifier: ^1.31.0
version: 1.31.0(supports-color@7.2.0)(zod@4.6.5)
'@orpc/server':
specifier: ^1.15.4
specifier: 'catalog:'
version: 1.15.4(ws@8.21.3)
'@reactive-resume/ai':
specifier: workspace:*
@@ -1231,20 +1312,20 @@ importers:
specifier: workspace:*
version: link:../schema
zod:
specifier: ^4.6.5
specifier: 'catalog:'
version: 4.6.5
devDependencies:
'@reactive-resume/config':
specifier: workspace:*
version: link:../config
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
specifier: 'catalog:'
version: 7.0.0-dev.20260707.2
typescript:
specifier: ^7.0.2
specifier: 'catalog:'
version: 7.0.2
vitest:
specifier: ^5.0.2
specifier: 'catalog:'
version: 5.0.2(@types/node@26.6.3)(@vitest/coverage-v8@5.0.2)(happy-dom@20.14.5)(vite@8.3.1(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))
packages/pdf:
@@ -1274,13 +1355,13 @@ importers:
specifier: ^8.0.0
version: 8.0.0
fflate:
specifier: ^0.8.3
specifier: 'catalog:'
version: 0.8.3
node-html-parser:
specifier: ^9.0.4
version: 9.0.4
react:
specifier: ^19.3.0
specifier: 'catalog:'
version: 19.3.0
react-reconciler:
specifier: 0.34.0
@@ -1293,19 +1374,19 @@ importers:
specifier: workspace:*
version: link:../config
'@types/react':
specifier: ^19.3.0
specifier: 'catalog:'
version: 19.3.0
'@types/react-reconciler':
specifier: ^0.33.1
version: 0.33.1(@types/react@19.3.0)
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
specifier: 'catalog:'
version: 7.0.0-dev.20260707.2
pdfjs-dist:
specifier: 6.3.289
version: 6.3.289
typescript:
specifier: ^7.0.2
specifier: 'catalog:'
version: 7.0.2
packages/resume:
@@ -1329,36 +1410,36 @@ importers:
specifier: 2.0.1
version: 2.0.1
zod:
specifier: ^4.6.5
specifier: 'catalog:'
version: 4.6.5
devDependencies:
'@reactive-resume/config':
specifier: workspace:*
version: link:../config
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
specifier: 'catalog:'
version: 7.0.0-dev.20260707.2
fast-check:
specifier: ^4.10.2
version: 4.10.2
typescript:
specifier: ^7.0.2
specifier: 'catalog:'
version: 7.0.2
packages/schema:
dependencies:
zod:
specifier: ^4.6.5
specifier: 'catalog:'
version: 4.6.5
devDependencies:
'@reactive-resume/config':
specifier: workspace:*
version: link:../config
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
specifier: 'catalog:'
version: 7.0.0-dev.20260707.2
typescript:
specifier: ^7.0.2
specifier: 'catalog:'
version: 7.0.2
packages/ui:
@@ -1385,7 +1466,7 @@ importers:
specifier: ^1.1.1
version: 1.1.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
react:
specifier: ^19.3.0
specifier: 'catalog:'
version: 19.3.0
react-dom:
specifier: ^19.3.0
@@ -1407,13 +1488,13 @@ importers:
specifier: ^0.5.20
version: 0.5.20(tailwindcss@4.3.3)
'@types/react':
specifier: ^19.3.0
specifier: 'catalog:'
version: 19.3.0
'@types/react-dom':
specifier: ^19.3.0
specifier: 'catalog:'
version: 19.3.0(@types/react@19.3.0)
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
specifier: 'catalog:'
version: 7.0.0-dev.20260707.2
postcss:
specifier: ^8.5.28
@@ -1422,7 +1503,7 @@ importers:
specifier: ^4.3.3
version: 4.3.3
typescript:
specifier: ^7.0.2
specifier: 'catalog:'
version: 7.0.2
packages/utils:
@@ -1431,7 +1512,7 @@ importers:
specifier: ^3.0.1
version: 3.0.1
'@uiw/color-convert':
specifier: ^2.10.3
specifier: 'catalog:'
version: 2.10.3(@babel/runtime@7.29.7)
cn:
specifier: ^0.4.0
@@ -1443,20 +1524,20 @@ importers:
specifier: ^14.0.2
version: 14.0.2
zod:
specifier: ^4.6.5
specifier: 'catalog:'
version: 4.6.5
devDependencies:
'@reactive-resume/config':
specifier: workspace:*
version: link:../config
'@types/node':
specifier: ^26.6.3
specifier: 'catalog:'
version: 26.6.3
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
specifier: 'catalog:'
version: 7.0.0-dev.20260707.2
typescript:
specifier: ^7.0.2
specifier: 'catalog:'
version: 7.0.2
tooling:
@@ -1481,22 +1562,22 @@ importers:
specifier: workspace:*
version: link:../packages/resume
'@types/pg':
specifier: ^8.23.1
specifier: 'catalog:'
version: 8.23.1
'@types/react':
specifier: ^19.3.0
specifier: 'catalog:'
version: 19.3.0
'@types/react-dom':
specifier: ^19.3.0
specifier: 'catalog:'
version: 19.3.0(@types/react@19.3.0)
'@typescript/native-preview':
specifier: 7.0.0-dev.20260707.2
specifier: 'catalog:'
version: 7.0.0-dev.20260707.2
drizzle-orm:
specifier: 1.0.0-rc.4
specifier: 'catalog:'
version: 1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5)
pg:
specifier: ^8.23.0
specifier: 'catalog:'
version: 8.23.0
react:
specifier: 19.3.0
@@ -1508,7 +1589,7 @@ importers:
specifier: ^4.23.15
version: 4.23.15
vitest:
specifier: ^5.0.2
specifier: 'catalog:'
version: 5.0.2(@types/node@26.6.3)(@vitest/coverage-v8@5.0.2)(happy-dom@20.14.5)(vite@8.3.1(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))
packages:
@@ -3001,9 +3082,6 @@ packages:
'@lezer/css@1.3.8':
resolution: {integrity: sha512-EJn1zcL9qoDptief6ipWKZKLiOpXkxSe0+t8CH9oiMVcZlq7NBWrjCqnc/41EIjeo/ITj1gFFiATdTkaJDL+Og==}
'@lezer/highlight@1.2.4':
resolution: {integrity: sha512-4UeRVFO0lsBaM7GA32zOqbmqNPme9jedo8Ey+5c5656MU3MLitMLIA8aCZTYjUMEpEsuSeXEm78Uea6Iq3bvSg==}
'@lezer/highlight@1.2.5':
resolution: {integrity: sha512-O1GMVKgtf5YspFaRzpmqkVgMtIk2HG9uMAbH6wdAIdNh3TVzCsch/+cRi+6l2//UGDKe6NB9BW3cvuR5ezMQ0w==}
@@ -9600,7 +9678,7 @@ snapshots:
'@codemirror/state': 6.7.6
'@codemirror/view': 6.43.13
'@lezer/common': 1.5.3
'@lezer/highlight': 1.2.4
'@lezer/highlight': 1.2.5
'@lezer/lr': 1.4.10
style-mod: 4.1.4
@@ -10379,13 +10457,9 @@ snapshots:
'@lezer/css@1.3.8':
dependencies:
'@lezer/common': 1.5.3
'@lezer/highlight': 1.2.4
'@lezer/highlight': 1.2.5
'@lezer/lr': 1.4.10
'@lezer/highlight@1.2.4':
dependencies:
'@lezer/common': 1.5.3
'@lezer/highlight@1.2.5':
dependencies:
'@lezer/common': 1.5.3
@@ -10536,7 +10610,7 @@ snapshots:
'@modelcontextprotocol/sdk@1.31.0(supports-color@7.2.0)(zod@4.6.5)':
dependencies:
'@hono/node-server': 2.1.1(hono@4.13.9)
'@hono/node-server': 2.1.3(hono@4.13.11)
ajv: 8.20.0
ajv-formats: 3.0.1
content-type: 1.0.5
@@ -10546,7 +10620,7 @@ snapshots:
eventsource-parser: 3.1.1
express: 5.2.1(supports-color@7.2.0)
express-rate-limit: 8.7.0(express@5.2.1(supports-color@7.2.0))(supports-color@7.2.0)
hono: 4.13.9
hono: 4.13.11
jose: 6.2.12
json-schema-typed: 8.0.2
pkce-challenge: 5.0.1
+28
View File
@@ -2,6 +2,34 @@ packages:
- apps/*
- packages/*
- tooling
catalogMode: prefer
catalog:
"@better-auth/api-key": "^1.7.6"
"@better-auth/infra": "^0.4.13"
"@better-auth/oauth-provider": "^1.7.6"
"@better-auth/passkey": "^1.7.6"
"@orpc/client": "^1.15.4"
"@orpc/server": "^1.15.4"
"@types/node": "^26.6.3"
"@types/pg": "^8.23.1"
"@types/react": "^19.3.0"
"@types/react-dom": "^19.3.0"
"@typescript/native-preview": "7.0.0-dev.20260707.2"
"@uiw/color-convert": "^2.10.3"
"ai": "^7.0.122"
"bcryptjs": "^3.0.3"
"better-auth": "1.7.6"
"drizzle-orm": "1.0.0-rc.4"
"es-toolkit": "^1.52.0"
"fflate": "^0.8.3"
"ioredis": "^6.0.0"
"jsonrepair": "^3.15.0"
"pg": "^8.23.0"
"react": "^19.3.0"
"ts-pattern": "^5.9.0"
"typescript": "^7.0.2"
"vitest": "^5.0.2"
"zod": "^4.6.5"
allowBuilds:
esbuild: true
lefthook: true
+7 -7
View File
@@ -22,15 +22,15 @@
"@reactive-resume/config": "workspace:*",
"@reactive-resume/env": "workspace:*",
"@reactive-resume/resume": "workspace:*",
"@types/pg": "^8.23.1",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"drizzle-orm": "1.0.0-rc.4",
"pg": "^8.23.0",
"@types/pg": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@typescript/native-preview": "catalog:",
"drizzle-orm": "catalog:",
"pg": "catalog:",
"react": "19.3.0",
"react-dom": "19.3.0",
"tsx": "^4.23.15",
"vitest": "^5.0.2"
"vitest": "catalog:"
}
}
+58 -4
View File
@@ -12,12 +12,12 @@ type DryTask = { taskId: string; task: string; hash: string };
let directory: string;
let baseline: Map<string, DryTask>;
function dryRun() {
function dryRun(environment: NodeJS.ProcessEnv = {}) {
const output = execFileSync(process.execPath, [turbo, "run", ...tasks, "--dry=json"], {
cwd: directory,
encoding: "utf8",
maxBuffer: 8 * 1024 * 1024,
env: { ...process.env, TURBO_TELEMETRY_DISABLED: "1" },
env: { ...process.env, ...environment, TURBO_TELEMETRY_DISABLED: "1" },
});
const result = JSON.parse(output) as { tasks: DryTask[] };
return new Map(result.tasks.map((task) => [task.taskId, task]));
@@ -31,8 +31,11 @@ beforeAll(async () => {
JSON.stringify({ private: true, packageManager: packageJson.packageManager }),
);
await writeFile(join(directory, "pnpm-workspace.yaml"), 'packages: ["packages/*"]\n');
await writeFile(join(directory, ".gitignore"), "node_modules/\n.turbo/\n");
await writeFile(join(directory, ".gitignore"), "node_modules/\n.turbo/\ncoverage/\nreports/\n");
await writeFile(join(directory, "turbo.json"), await readFile(join(root, "turbo.json")));
for (const file of ["vitest.shared.mts", "vitest.setup.ts"]) {
await writeFile(join(directory, file), "export {};\n");
}
// app -> api -> pdf mirrors a source-consumed dependency with no build script.
const dependencies: Record<string, string[]> = { app: ["api"], api: ["pdf"], pdf: [] };
@@ -48,7 +51,18 @@ beforeAll(async () => {
version: "0.0.0",
private: true,
dependencies: Object.fromEntries(deps.map((dependency) => [dependency, "workspace:*"])),
scripts: name === "pdf" ? {} : Object.fromEntries(tasks.map((task) => [task, "node -e ''"])),
scripts:
name === "pdf"
? {}
: Object.fromEntries(
tasks.map((task) => {
const output = task === "test:coverage" ? "coverage" : "reports";
return [
task,
`node -e 'const fs = require("node:fs"); fs.mkdirSync("${output}", { recursive: true }); fs.writeFileSync("${output}/${task.replace(":", "-")}.txt", "complete");'`,
];
}),
),
}),
);
lock += ` packages/${name}:`;
@@ -89,3 +103,43 @@ it("invalidates every cached consumer task after a transitive source-only packag
it("keeps cache keys stable when sources are unchanged", () => {
expect(dryRun()).toEqual(baseline);
});
it.each(["vitest.shared.mts", "vitest.setup.ts"])("invalidates consumer tests when %s changes", async (file) => {
const path = join(directory, file);
const original = await readFile(path);
try {
await writeFile(path, "export const changed = true;\n");
const result = dryRun();
for (const task of tasks.filter((task) => task.startsWith("test"))) {
const id = `app#${task}`;
expect(result.get(id)?.hash, id).not.toBe(baseline.get(id)?.hash);
}
} finally {
await writeFile(path, original);
}
});
it("hashes runtime secrets for tests without invalidating builds or typechecks", () => {
const before = dryRun({ AUTH_SECRET: "cache-audit-before" });
const after = dryRun({ AUTH_SECRET: "cache-audit-after" });
for (const task of tasks) {
const id = `app#${task}`;
if (task.startsWith("test")) expect(after.get(id)?.hash, id).not.toBe(before.get(id)?.hash);
else expect(after.get(id)?.hash, id).toBe(before.get(id)?.hash);
}
});
it.each(["test:coverage", "test:ci", "test:agent"])("restores %s reports from cache", async (task) => {
const output = task === "test:coverage" ? "coverage" : "reports";
const path = join(directory, "packages", "app", output, `${task.replace(":", "-")}.txt`);
const run = () =>
execFileSync(process.execPath, [turbo, "run", task, "--filter=app", "--cache=local:rw"], {
cwd: directory,
encoding: "utf8",
env: { ...process.env, TURBO_TELEMETRY_DISABLED: "1" },
});
run();
await rm(path);
expect(run()).toContain("cache hit");
expect(await readFile(path, "utf8")).toBe("complete");
});
+196 -50
View File
@@ -1,5 +1,5 @@
{
"$schema": "https://v2-10-7.turborepo.dev/schema.json",
"$schema": "https://v2-11-5.turborepo.dev/schema.json",
"ui": "stream",
"boundaries": {
"dependencies": {
@@ -44,7 +44,198 @@
}
}
},
"globalEnv": [
"tasks": {
"transit": {
"dependsOn": ["^transit"]
},
"build": {
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/.env*"],
"outputs": ["dist/**", ".vercel/**"],
"dependsOn": ["transit"],
"env": ["NODE_ENV"]
},
"typecheck": {
"dependsOn": ["transit"]
},
"test": {
"dependsOn": ["transit"],
"env": [
"PORT",
"SERVER_PORT",
"APP_URL",
"ROOT_RESUME_ID",
"DATABASE_*",
"AUTH_SECRET",
"BETTER_AUTH_*",
"GOOGLE_CLIENT_*",
"GITHUB_CLIENT_*",
"LINKEDIN_CLIENT_*",
"OAUTH_*",
"SMTP_*",
"LOCAL_STORAGE_PATH",
"S3_*",
"REDIS_URL",
"ENCRYPTION_SECRET",
"FIRECRAWL_*",
"AI_*",
"FLAG_*",
"STRICT_SCHEMA_CHECK",
"VERCEL",
"VERCEL_ENV",
"VERCEL_URL",
"VERCEL_PROJECT_PRODUCTION_URL",
"VERCEL_BRANCH_URL",
"POSTGRES_*",
"KV_URL",
"STORAGE_BACKEND",
"BLOB_*",
"DEPLOYMENT_NAMESPACE",
"ALLOW_PREVIEW_MIGRATIONS",
"NODE_ENV"
],
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/.env*"]
},
"test:coverage": {
"dependsOn": ["transit"],
"env": [
"PORT",
"SERVER_PORT",
"APP_URL",
"ROOT_RESUME_ID",
"DATABASE_*",
"AUTH_SECRET",
"BETTER_AUTH_*",
"GOOGLE_CLIENT_*",
"GITHUB_CLIENT_*",
"LINKEDIN_CLIENT_*",
"OAUTH_*",
"SMTP_*",
"LOCAL_STORAGE_PATH",
"S3_*",
"REDIS_URL",
"ENCRYPTION_SECRET",
"FIRECRAWL_*",
"AI_*",
"FLAG_*",
"STRICT_SCHEMA_CHECK",
"VERCEL",
"VERCEL_ENV",
"VERCEL_URL",
"VERCEL_PROJECT_PRODUCTION_URL",
"VERCEL_BRANCH_URL",
"POSTGRES_*",
"KV_URL",
"STORAGE_BACKEND",
"BLOB_*",
"DEPLOYMENT_NAMESPACE",
"ALLOW_PREVIEW_MIGRATIONS",
"NODE_ENV"
],
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/.env*"],
"outputs": ["coverage/**"]
},
"test:ci": {
"dependsOn": ["transit"],
"env": [
"PORT",
"SERVER_PORT",
"APP_URL",
"ROOT_RESUME_ID",
"DATABASE_*",
"AUTH_SECRET",
"BETTER_AUTH_*",
"GOOGLE_CLIENT_*",
"GITHUB_CLIENT_*",
"LINKEDIN_CLIENT_*",
"OAUTH_*",
"SMTP_*",
"LOCAL_STORAGE_PATH",
"S3_*",
"REDIS_URL",
"ENCRYPTION_SECRET",
"FIRECRAWL_*",
"AI_*",
"FLAG_*",
"STRICT_SCHEMA_CHECK",
"VERCEL",
"VERCEL_ENV",
"VERCEL_URL",
"VERCEL_PROJECT_PRODUCTION_URL",
"VERCEL_BRANCH_URL",
"POSTGRES_*",
"KV_URL",
"STORAGE_BACKEND",
"BLOB_*",
"DEPLOYMENT_NAMESPACE",
"ALLOW_PREVIEW_MIGRATIONS",
"NODE_ENV"
],
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/.env*"],
"outputs": ["reports/**"]
},
"test:agent": {
"dependsOn": ["transit"],
"env": [
"PORT",
"SERVER_PORT",
"APP_URL",
"ROOT_RESUME_ID",
"DATABASE_*",
"AUTH_SECRET",
"BETTER_AUTH_*",
"GOOGLE_CLIENT_*",
"GITHUB_CLIENT_*",
"LINKEDIN_CLIENT_*",
"OAUTH_*",
"SMTP_*",
"LOCAL_STORAGE_PATH",
"S3_*",
"REDIS_URL",
"ENCRYPTION_SECRET",
"FIRECRAWL_*",
"AI_*",
"FLAG_*",
"STRICT_SCHEMA_CHECK",
"VERCEL",
"VERCEL_ENV",
"VERCEL_URL",
"VERCEL_PROJECT_PRODUCTION_URL",
"VERCEL_BRANCH_URL",
"POSTGRES_*",
"KV_URL",
"STORAGE_BACKEND",
"BLOB_*",
"DEPLOYMENT_NAMESPACE",
"ALLOW_PREVIEW_MIGRATIONS",
"NODE_ENV"
],
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/.env*"],
"outputs": ["reports/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"start": {
"cache": false,
"persistent": true
},
"db:generate": {
"cache": false
},
"db:migrate": {
"cache": false
},
"db:studio": {
"cache": false,
"persistent": true
},
"lingui:extract": {
"cache": false
}
},
"globalDependencies": ["vitest.shared.mts", "vitest.setup.ts"],
"globalPassThroughEnv": [
"PORT",
"SERVER_PORT",
"APP_URL",
@@ -112,52 +303,7 @@
"BLOB_READ_WRITE_TOKEN",
"BLOB_STORE_ID",
"DEPLOYMENT_NAMESPACE",
"ALLOW_PREVIEW_MIGRATIONS"
],
"tasks": {
"transit": {
"dependsOn": ["^transit"]
},
"build": {
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/.env*"],
"outputs": ["dist/**", ".vercel/**"],
"dependsOn": ["transit"]
},
"typecheck": {
"dependsOn": ["transit"]
},
"test": {
"dependsOn": ["transit"]
},
"test:coverage": {
"dependsOn": ["transit"]
},
"test:ci": {
"dependsOn": ["transit"]
},
"test:agent": {
"dependsOn": ["transit"]
},
"dev": {
"cache": false,
"persistent": true
},
"start": {
"cache": false,
"persistent": true
},
"db:generate": {
"cache": false
},
"db:migrate": {
"cache": false
},
"db:studio": {
"cache": false,
"persistent": true
},
"lingui:extract": {
"cache": false
}
}
"ALLOW_PREVIEW_MIGRATIONS",
"NODE_ENV"
]
}