From f957a6116485dee9d8cf0d02ef085a1f175b6c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adel=20Rodr=C3=ADguez?= Date: Mon, 17 Aug 2026 17:21:57 -0400 Subject: [PATCH 1/4] chore: upgrade adamantite to 0.36.0 and update CI actions --- .github/workflows/adamantite.yml | 10 +- .nvmrc => .node-version | 0 apps/api/src/shared/env.generated.ts | 72 +++--- apps/api/src/shared/files.ts | 57 +++-- apps/api/src/shared/trpc.ts | 9 +- .../auth/components/forgot-password-form.tsx | 4 +- .../auth/components/reset-password-form.tsx | 9 +- .../components/sign-in-with-password-form.tsx | 10 +- .../features/auth/components/sign-up-form.tsx | 22 +- apps/app/src/features/auth/validation.ts | 4 +- .../src/shared/components/last-updated.astro | 61 ++--- apps/mobile/src/app/index.tsx | 3 +- .../landing/components/ship-targets.astro | 238 +++++++++--------- bun.lock | 46 +--- oxlint.config.ts | 3 +- package.json | 4 +- .../functions/components/better-auth/auth.ts | 5 +- .../native-ui/src/components/accordion.tsx | 14 +- .../large-title-header.ios.tsx | 24 +- .../large-title-header/large-title-header.tsx | 45 ++-- .../components/native-only-animated-view.tsx | 1 + packages/native-ui/src/components/text.tsx | 54 ++-- packages/payments/src/helpers.ts | 29 ++- packages/utils/src/singleton.ts | 31 +-- packages/utils/src/type.ts | 4 +- packages/utils/src/url.ts | 7 +- scripts/template/portless.ts | 6 +- scripts/template/shared.ts | 57 +++-- turbo/generators/boundaries.ts | 79 ++++++ turbo/generators/commands/code-snippets.ts | 29 +-- turbo/generators/commands/connect-backend.ts | 65 ++--- turbo/generators/commands/files-client.ts | 15 +- turbo/generators/commands/new-feature.ts | 9 +- turbo/generators/commands/new-package.ts | 20 +- 34 files changed, 608 insertions(+), 438 deletions(-) rename .nvmrc => .node-version (100%) create mode 100644 turbo/generators/boundaries.ts diff --git a/.github/workflows/adamantite.yml b/.github/workflows/adamantite.yml index 97d9f032..88b62d9b 100644 --- a/.github/workflows/adamantite.yml +++ b/.github/workflows/adamantite.yml @@ -34,18 +34,18 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v7 - name: Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: - node-version: "24" + node-version-file: ".node-version" - name: Setup Bun uses: oven-sh/setup-bun@v2 - name: Cache dependencies - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ~/.bun/install/cache @@ -57,7 +57,7 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile - - name: Generate source and types + - name: Generate source files and types run: bun run codegen - name: Run ${{ matrix.name }} diff --git a/.nvmrc b/.node-version similarity index 100% rename from .nvmrc rename to .node-version diff --git a/apps/api/src/shared/env.generated.ts b/apps/api/src/shared/env.generated.ts index f5348d7f..1f0654e4 100644 --- a/apps/api/src/shared/env.generated.ts +++ b/apps/api/src/shared/env.generated.ts @@ -140,25 +140,18 @@ export type CoercedEnvSchema = { DATABASE_URL: string; /** - * **REDIS_URL** 🔐 _sensitive_ - * Redis connection URL. - * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M24%2021V9h-2v14h8v-2zm-4-6v-4c0-1.103-.897-2-2-2h-6v14h2v-6h1.48l2.335%206h2.145l-2.333-6H18c1.103%200%202-.897%202-2m-6-4h4v4h-4zM8%2023H4c-1.103%200-2-.897-2-2V9h2v12h4V9h2v12c0%201.103-.897%202-2%202%22%2F%3E%3C%2Fsvg%3E) + * **RUN_PRODUCTION_MIGRATIONS** + * Allows migration tooling to modify a non-local database. + * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M23%2023a7%207%200%201%201%207-7a7.01%207.01%200%200%201-7%207m0-12a5%205%200%201%200%205%205a5.006%205.006%200%200%200-5-5%22%2F%3E%3Ccircle%20cx%3D%229%22%20cy%3D%2216%22%20r%3D%227%22%20fill%3D%22%23808080%22%2F%3E%3C%2Fsvg%3E) */ - REDIS_URL: string; + RUN_PRODUCTION_MIGRATIONS: boolean; /** - * **INNGEST_BASE_URL** - * Optional self-hosted or local Inngest endpoint. + * **REDIS_URL** 🔐 _sensitive_ + * Redis connection URL. * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M24%2021V9h-2v14h8v-2zm-4-6v-4c0-1.103-.897-2-2-2h-6v14h2v-6h1.48l2.335%206h2.145l-2.333-6H18c1.103%200%202-.897%202-2m-6-4h4v4h-4zM8%2023H4c-1.103%200-2-.897-2-2V9h2v12h4V9h2v12c0%201.103-.897%202-2%202%22%2F%3E%3C%2Fsvg%3E) */ - INNGEST_BASE_URL?: string; - - /** - * **INNGEST_DEV** - * Enables Inngest development mode. - * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M23%2023a7%207%200%201%201%207-7a7.01%207.01%200%200%201-7%207m0-12a5%205%200%201%200%205%205a5.006%205.006%200%200%200-5-5%22%2F%3E%3Ccircle%20cx%3D%229%22%20cy%3D%2216%22%20r%3D%227%22%20fill%3D%22%23808080%22%2F%3E%3C%2Fsvg%3E) - */ - INNGEST_DEV: boolean; + REDIS_URL: string; /** * **INNGEST_EVENT_KEY** 🔐 _sensitive_ @@ -174,6 +167,20 @@ export type CoercedEnvSchema = { */ INNGEST_SIGNING_KEY: string; + /** + * **INNGEST_DEV** + * Enables Inngest development mode. + * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M23%2023a7%207%200%201%201%207-7a7.01%207.01%200%200%201-7%207m0-12a5%205%200%201%200%205%205a5.006%205.006%200%200%200-5-5%22%2F%3E%3Ccircle%20cx%3D%229%22%20cy%3D%2216%22%20r%3D%227%22%20fill%3D%22%23808080%22%2F%3E%3C%2Fsvg%3E) + */ + INNGEST_DEV: boolean; + + /** + * **INNGEST_BASE_URL** + * Optional self-hosted or local Inngest endpoint. + * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M24%2021V9h-2v14h8v-2zm-4-6v-4c0-1.103-.897-2-2-2h-6v14h2v-6h1.48l2.335%206h2.145l-2.333-6H18c1.103%200%202-.897%202-2m-6-4h4v4h-4zM8%2023H4c-1.103%200-2-.897-2-2V9h2v12h4V9h2v12c0%201.103-.897%202-2%202%22%2F%3E%3C%2Fsvg%3E) + */ + INNGEST_BASE_URL?: string; + /** * **SENTRY_DSN** * Server Sentry DSN. @@ -202,6 +209,20 @@ export type CoercedEnvSchema = { */ SENTRY_PROJECT: string; + /** + * **SENTRY_DEBUG** + * Enables server Sentry diagnostics. + * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M23%2023a7%207%200%201%201%207-7a7.01%207.01%200%200%201-7%207m0-12a5%205%200%201%200%205%205a5.006%205.006%200%200%200-5-5%22%2F%3E%3Ccircle%20cx%3D%229%22%20cy%3D%2216%22%20r%3D%227%22%20fill%3D%22%23808080%22%2F%3E%3C%2Fsvg%3E) + */ + SENTRY_DEBUG: boolean; + + /** + * **SENTRY_SPOTLIGHT** + * Enables local Sentry Spotlight. + * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M23%2023a7%207%200%201%201%207-7a7.01%207.01%200%200%201-7%207m0-12a5%205%200%201%200%205%205a5.006%205.006%200%200%200-5-5%22%2F%3E%3Ccircle%20cx%3D%229%22%20cy%3D%2216%22%20r%3D%227%22%20fill%3D%22%23808080%22%2F%3E%3C%2Fsvg%3E) + */ + SENTRY_SPOTLIGHT: boolean; + /** * **EMAIL_FROM** * Sender used when a message does not provide one. @@ -223,27 +244,6 @@ export type CoercedEnvSchema = { */ MOCK_RESEND: boolean; - /** - * **RUN_PRODUCTION_MIGRATIONS** - * Allows migration tooling to modify a non-local database. - * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M23%2023a7%207%200%201%201%207-7a7.01%207.01%200%200%201-7%207m0-12a5%205%200%201%200%205%205a5.006%205.006%200%200%200-5-5%22%2F%3E%3Ccircle%20cx%3D%229%22%20cy%3D%2216%22%20r%3D%227%22%20fill%3D%22%23808080%22%2F%3E%3C%2Fsvg%3E) - */ - RUN_PRODUCTION_MIGRATIONS: boolean; - - /** - * **SENTRY_DEBUG** - * Enables server Sentry diagnostics. - * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M23%2023a7%207%200%201%201%207-7a7.01%207.01%200%200%201-7%207m0-12a5%205%200%201%200%205%205a5.006%205.006%200%200%200-5-5%22%2F%3E%3Ccircle%20cx%3D%229%22%20cy%3D%2216%22%20r%3D%227%22%20fill%3D%22%23808080%22%2F%3E%3C%2Fsvg%3E) - */ - SENTRY_DEBUG: boolean; - - /** - * **SENTRY_SPOTLIGHT** - * Enables local Sentry Spotlight. - * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M23%2023a7%207%200%201%201%207-7a7.01%207.01%200%200%201-7%207m0-12a5%205%200%201%200%205%205a5.006%205.006%200%200%200-5-5%22%2F%3E%3Ccircle%20cx%3D%229%22%20cy%3D%2216%22%20r%3D%227%22%20fill%3D%22%23808080%22%2F%3E%3C%2Fsvg%3E) - */ - SENTRY_SPOTLIGHT: boolean; - /** * **INNGEST_SIGNING_KEY_FALLBACK** 🔐 _sensitive_ * Previous signing credential accepted during rotation. @@ -263,7 +263,7 @@ export type EnvSchemaAsStrings = { type _EnvSchemaAsStrings_3b9f7a4f = EnvSchemaAsStrings; -export type PublicCoercedEnvSchema = Readonly>; +export type PublicCoercedEnvSchema = Readonly>; // re-export of the runtime ENV proxy, typed to this package's schema export const ENV = _ENV as unknown as Readonly; diff --git a/apps/api/src/shared/files.ts b/apps/api/src/shared/files.ts index 1f7e6427..099e8163 100644 --- a/apps/api/src/shared/files.ts +++ b/apps/api/src/shared/files.ts @@ -1,4 +1,3 @@ -import type { DeleteManyResult, StoredFile, UploadResult } from "files-sdk" import { operators } from "@init/db/helpers" import { assets, type UserId } from "@init/db/schema" import * as z from "@init/utils/schema" @@ -14,6 +13,24 @@ import { context } from "#shared/utils.ts" export const FILES_MAX_UPLOAD_SIZE = 10 * 1024 * 1024 export const FILES_MAX_URL_AGE = 15 * 60 +const userIdSchema = z.branded("UserId") +const uploadResultSchema = z.object({ + contentType: z.string(), + etag: z.string(), + lastModified: z.number(), + metadata: z.record(z.string(), z.string()).optional(), + name: z.string(), + size: z.number(), +}) +const storedFileSchema = z.object({ + etag: z.string(), + lastModified: z.number(), + metadata: z.record(z.string(), z.string()).optional(), + size: z.number(), + type: z.string(), +}) +const deleteManyResultSchema = z.object({ deleted: z.array(z.string()) }) + export const files = createFiles({ adapter: bunS3({ accessKeyId: ENV.S3_ACCESS_KEY_ID, @@ -29,13 +46,13 @@ export const files = createFiles({ switch (event.type) { case "upload": - if (event.key) handleUpload(event.key, event.result as UploadResult) + if (event.key) handleUpload(event.key, uploadResultSchema.parse(event.result)) break case "head": - if (event.key) handleUpload(event.key, event.result as StoredFile) + if (event.key) handleUpload(event.key, storedFileSchema.parse(event.result)) break case "delete": { - const keys = event.key ? [event.key] : (event.result as DeleteManyResult).deleted + const keys = event.key ? [event.key] : deleteManyResultSchema.parse(event.result).deleted handleDelete(keys) break @@ -67,10 +84,17 @@ export const files = createFiles({ ], }) -function handleUpload(key: string, file: UploadResult | StoredFile) { +type ParsedUploadResult = z.infer +type ParsedStoredFile = z.infer + +function handleUpload(key: string, file: ParsedUploadResult | ParsedStoredFile) { const ctx = context() const mimeType = "contentType" in file ? file.contentType : file.type const name = "name" in file ? file.name : (key.split("/").at(-1) ?? key) + const userId: UserId = userIdSchema.parse(ctx.var.session.user.id) + const logFailure = (cause: unknown) => { + ctx.var.logger.error(`Failed to record asset: ${String(cause)}`) + } void ctx.var.db .insert(assets) @@ -80,10 +104,10 @@ function handleUpload(key: string, file: UploadResult | StoredFile) { lastModified: file.lastModified, metadata: "metadata" in file ? file.metadata : undefined, name, - ownerId: ctx.var.session.user.id as UserId, + ownerId: userId, size: file.size, type: mimeType, - uploaderId: ctx.var.session.user.id as UserId, + uploaderId: userId, }) .onConflictDoUpdate({ set: { @@ -97,25 +121,20 @@ function handleUpload(key: string, file: UploadResult | StoredFile) { }, target: assets.key, }) - .catch((error: unknown) => { - ctx.var.logger.error(`Failed to record asset: ${String(error)}`) - }) + .catch(logFailure) } function handleDelete(keys: string[]) { if (keys.length === 0) return const ctx = context() + const userId: UserId = userIdSchema.parse(ctx.var.session.user.id) + const logFailure = (cause: unknown) => { + ctx.var.logger.error(`Failed to delete asset records: ${String(cause)}`) + } void ctx.var.db .delete(assets) - .where( - operators.and( - operators.inArray(assets.key, keys), - operators.eq(assets.ownerId, ctx.var.session.user.id as UserId) - ) - ) - .catch((error: unknown) => { - ctx.var.logger.error(`Failed to delete asset records: ${String(error)}`) - }) + .where(operators.and(operators.inArray(assets.key, keys), operators.eq(assets.ownerId, userId))) + .catch(logFailure) } diff --git a/apps/api/src/shared/trpc.ts b/apps/api/src/shared/trpc.ts index 8d5796c0..95f56914 100644 --- a/apps/api/src/shared/trpc.ts +++ b/apps/api/src/shared/trpc.ts @@ -22,11 +22,14 @@ export function createTRPCContext(opts: FetchCreateContextFnOptions, c: Context< export type TRPCContext = Awaited> export const t = initTRPC.context().create({ - errorFormatter({ shape, error }) { + errorFormatter(formatterInput) { + const { error } = formatterInput + const formattedError = formatterInput["shape"] + return { - ...shape, + ...formattedError, data: { - ...shape.data, + ...formattedError.data, zodError: error.cause instanceof z.ZodError ? error.cause.flatten() : null, }, } diff --git a/apps/app/src/features/auth/components/forgot-password-form.tsx b/apps/app/src/features/auth/components/forgot-password-form.tsx index b4032b41..b1bfbde2 100644 --- a/apps/app/src/features/auth/components/forgot-password-form.tsx +++ b/apps/app/src/features/auth/components/forgot-password-form.tsx @@ -3,7 +3,7 @@ import { useForm } from "@init/ui/components/form" import { toast } from "@init/ui/components/toast" import { useServerFn } from "@tanstack/react-start" import { forgotPassword } from "#features/auth/server/functions.ts" -import { ForgotPasswordFormSchema as schema } from "#features/auth/validation.ts" +import { EmailSchema, ForgotPasswordFormSchema as schema } from "#features/auth/validation.ts" export default function ForgotPasswordForm() { const execute = useServerFn(forgotPassword) @@ -36,7 +36,7 @@ export default function ForgotPasswordForm() { > - + {(field) => ( Email address diff --git a/apps/app/src/features/auth/components/reset-password-form.tsx b/apps/app/src/features/auth/components/reset-password-form.tsx index 1ff9b4b3..bfad23dc 100644 --- a/apps/app/src/features/auth/components/reset-password-form.tsx +++ b/apps/app/src/features/auth/components/reset-password-form.tsx @@ -2,7 +2,7 @@ import { FieldGroup } from "@init/ui/components/field" import { useForm } from "@init/ui/components/form" import { toast } from "@init/ui/components/toast" import { useNavigate } from "@tanstack/react-router" -import { ResetPasswordFormSchema as schema } from "#features/auth/validation.ts" +import { PasswordSchema, ResetPasswordFormSchema as schema } from "#features/auth/validation.ts" import { authClient } from "#shared/auth.ts" export default function ResetPasswordForm({ token }: { token: string }) { @@ -39,7 +39,7 @@ export default function ResetPasswordForm({ token }: { token: string }) { > - + {(field) => ( New password @@ -48,10 +48,7 @@ export default function ResetPasswordForm({ token }: { token: string }) { )} - + {(field) => ( Confirm new password diff --git a/apps/app/src/features/auth/components/sign-in-with-password-form.tsx b/apps/app/src/features/auth/components/sign-in-with-password-form.tsx index 8f3fc36b..76c9dec8 100644 --- a/apps/app/src/features/auth/components/sign-in-with-password-form.tsx +++ b/apps/app/src/features/auth/components/sign-in-with-password-form.tsx @@ -4,7 +4,11 @@ import { useForm } from "@init/ui/components/form" import { toast } from "@init/ui/components/toast" import { Link, useNavigate } from "@tanstack/react-router" import { AUTHENTICATED_PATHNAME } from "#features/auth/constants.ts" -import { SignInWithPasswordFormSchema as schema } from "#features/auth/validation.ts" +import { + EmailSchema, + PasswordSchema, + SignInWithPasswordFormSchema as schema, +} from "#features/auth/validation.ts" import { signIn } from "#shared/auth.ts" export default function SignInWithPasswordForm() { @@ -37,7 +41,7 @@ export default function SignInWithPasswordForm() { > - + {(field) => ( Email address @@ -47,7 +51,7 @@ export default function SignInWithPasswordForm() { )} - + {(field) => ( Password diff --git a/apps/app/src/features/auth/components/sign-up-form.tsx b/apps/app/src/features/auth/components/sign-up-form.tsx index 97618668..a117011b 100644 --- a/apps/app/src/features/auth/components/sign-up-form.tsx +++ b/apps/app/src/features/auth/components/sign-up-form.tsx @@ -3,7 +3,12 @@ import { useForm } from "@init/ui/components/form" import { useNavigate } from "@tanstack/react-router" import { AUTHENTICATED_PATHNAME } from "#features/auth/constants.ts" import { checkEmailAvailability } from "#features/auth/server/functions.ts" -import { SignUpFormSchema as schema } from "#features/auth/validation.ts" +import { + EmailSchema, + NameSchema, + PasswordSchema, + SignUpFormSchema as schema, +} from "#features/auth/validation.ts" import { signUp } from "#shared/auth.ts" export default function SignUpForm() { @@ -32,7 +37,7 @@ export default function SignUpForm() { > - + {(field) => ( Name @@ -46,7 +51,7 @@ export default function SignUpForm() { { const { isAvailable } = await checkEmailAvailability({ data: { @@ -71,7 +76,7 @@ export default function SignUpForm() { )} - + {(field) => ( Password @@ -85,12 +90,9 @@ export default function SignUpForm() { v === form.getFieldValue("password"), - { - message: "Passwords don't match", - } - ), + onBlur: PasswordSchema.refine((v) => v === form.getFieldValue("password"), { + message: "Passwords don't match", + }), onBlurListenTo: ["password"], onChangeListenTo: ["password"], }} diff --git a/apps/app/src/features/auth/validation.ts b/apps/app/src/features/auth/validation.ts index de0c5154..8f143a13 100644 --- a/apps/app/src/features/auth/validation.ts +++ b/apps/app/src/features/auth/validation.ts @@ -10,10 +10,12 @@ export const PasswordSchema = z .min(8, { error: "Password must be more than 8 characters" }) .max(32, { error: "Password must be less than 32 characters" }) +export const NameSchema = z.string().min(1, { error: "Name is required" }) + export const SignUpFormSchema = z.object({ confirmPassword: PasswordSchema, email: EmailSchema, - name: z.string().min(1, { error: "Name is required" }), + name: NameSchema, password: PasswordSchema, }) diff --git a/apps/docs/src/shared/components/last-updated.astro b/apps/docs/src/shared/components/last-updated.astro index d2c0460e..20844658 100644 --- a/apps/docs/src/shared/components/last-updated.astro +++ b/apps/docs/src/shared/components/last-updated.astro @@ -1,38 +1,43 @@ --- -import { spawnSync } from "node:child_process" -import { basename, dirname, resolve } from "node:path" +import { spawnSync } from "node:child_process"; +import { basename, dirname, resolve } from "node:path"; -const { entry, lang, lastUpdated: routeLastUpdated } = Astro.locals.starlightRoute +const { + entry, + lang, + lastUpdated: routeLastUpdated, +} = Astro.locals.starlightRoute; const lastUpdated = - routeLastUpdated ?? (entry.filePath ? getLastUpdated(entry.filePath) : undefined) + routeLastUpdated ?? + (entry.filePath ? getLastUpdated(entry.filePath) : undefined); function getLastUpdated(filePath: string) { - const absolutePath = resolve(import.meta.dirname, "../../..", filePath) - const result = spawnSync( - "git", - ["log", "--format=%ct", "--max-count=1", basename(absolutePath)], - { - cwd: dirname(absolutePath), - encoding: "utf8", - }, - ) - const timestamp = typeof result.stdout === "string" ? Number(result.stdout.trim()) : Number.NaN - return result.status === 0 && Number.isFinite(timestamp) && timestamp > 0 - ? new Date(timestamp * 1000) - : undefined + const absolutePath = resolve(import.meta.dirname, "../../..", filePath); + const result = spawnSync( + "git", + ["log", "--format=%ct", "--max-count=1", basename(absolutePath)], + { + cwd: dirname(absolutePath), + encoding: "utf8", + }, + ); + const timestamp = Number(result.stdout.trim()); + return result.status === 0 && Number.isFinite(timestamp) && timestamp > 0 + ? new Date(timestamp * 1000) + : undefined; } --- { - lastUpdated && ( -

- {Astro.locals.t("page.lastUpdated")} {" "} - -

- ) + lastUpdated && ( +

+ {Astro.locals.t("page.lastUpdated")}{" "} + +

+ ) } diff --git a/apps/mobile/src/app/index.tsx b/apps/mobile/src/app/index.tsx index c5413ce9..ffa6aaff 100644 --- a/apps/mobile/src/app/index.tsx +++ b/apps/mobile/src/app/index.tsx @@ -23,8 +23,7 @@ function checkIsLocale(value: string): value is Locale { } export default function Screen() { - const backgroundValue = useCSSVariable("--color-background") - const background = typeof backgroundValue === "string" ? backgroundValue : undefined + const background = useCSSVariable("--color-background")?.toString() const [isSearchFocused, setIsSearchFocused] = useState(false) const [locale, setLocale] = useState(() => getLocale()) const [searchQuery, setSearchQuery] = useState("") diff --git a/apps/web/src/features/landing/components/ship-targets.astro b/apps/web/src/features/landing/components/ship-targets.astro index 0fd0c942..115d47ad 100644 --- a/apps/web/src/features/landing/components/ship-targets.astro +++ b/apps/web/src/features/landing/components/ship-targets.astro @@ -1,125 +1,139 @@ --- -import { m } from "#shared/internationalization/messages.js" +import { m } from "#shared/internationalization/messages.js"; const targets = [ - m.web_landing_ship_targets_web(), - m.web_landing_ship_targets_mobile(), - m.web_landing_ship_targets_desktop(), - m.web_landing_ship_targets_marketing(), - m.web_landing_ship_targets_docs(), - m.web_landing_ship_targets_extension(), -] as const -const prefix = m.web_landing_ship_targets_prefix() + m.web_landing_ship_targets_web(), + m.web_landing_ship_targets_mobile(), + m.web_landing_ship_targets_desktop(), + m.web_landing_ship_targets_marketing(), + m.web_landing_ship_targets_docs(), + m.web_landing_ship_targets_extension(), +] as const; +const prefix = m.web_landing_ship_targets_prefix(); ---
-

{m.web_landing_ship_targets_summary()}

- + + { + targets.map((target) => ( + + )) + } + + {prefix}  + {targets[0]} + +

diff --git a/bun.lock b/bun.lock index fe27ee91..e24aa413 100644 --- a/bun.lock +++ b/bun.lock @@ -7,7 +7,7 @@ "devDependencies": { "@turbo/gen": "2.10.7", "@types/bun": "1.3.14", - "adamantite": "0.35.0", + "adamantite": "0.36.0", "citty": "0.2.2", "consola": "3.4.2", "knip": "6.32.2", @@ -1918,12 +1918,6 @@ "@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.86.2", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.2.0", "react": "*", "react-native": "0.86.2" }, "optionalPeers": ["@types/react"] }, "sha512-uO0J72gh3EvE+1/GHRk18QRyBDTRHRB0AraAfojsRjbT7VMuJwKrZYaKGshavoaEud6aw00ZB9/8mTMIKjjcAw=="], - "@react-navigation/core": ["@react-navigation/core@7.21.11", "", { "dependencies": { "@react-navigation/routers": "^7.6.4", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-bCW1PsLA/eOXDOukcJFEzlcL3Zpy8DJuDCfkDDwAQlAgoSZ/J9+ZeDRUMmCUi6xbnFgvFEEIMertaLeErOFP0Q=="], - - "@react-navigation/native": ["@react-navigation/native@7.3.14", "", { "dependencies": { "@react-navigation/core": "^7.21.11", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "standard-navigation": "^0.0.8", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-hcKTDNBuuAA1/xW6QeKYmMPVhk5W9dKGQpPmn5dQeeePwMpu5OZ14NOgwKH0w9D3tg2jupojTcVL0tsx5DTFXg=="], - - "@react-navigation/routers": ["@react-navigation/routers@7.6.4", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-GI7eJm8/KsZUQaYcXvEExikKurRZRgEsSzyZ7faENfi65yqJBCXjDMwyN1pF6pNW1MoLH1ErDwDivFxY6BzD3w=="], - "@reduxjs/toolkit": ["@reduxjs/toolkit@2.12.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^11.0.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw=="], "@rn-primitives/accordion": ["@rn-primitives/accordion@1.5.2", "", { "dependencies": { "@radix-ui/react-accordion": "^1.2.12", "@rn-primitives/hooks": "1.5.2", "@rn-primitives/slot": "1.5.2", "@rn-primitives/types": "1.5.2" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native", "react-native-web"] }, "sha512-sK9GuVWAIIUcOZO7Hizes5i5oC6IW+PFb6KWh0nQRzET2LzETnLIPgbce+FC+F615tr6r5s4CuOrN19szZcYhQ=="], @@ -2558,7 +2552,7 @@ "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - "adamantite": ["adamantite@0.35.0", "", { "dependencies": { "@clack/prompts": "1.7.0", "@effect/platform-node": "4.0.0-rc.108", "defu": "6.1.7", "effect": "4.0.0-rc.108", "esrap": "2.3.2", "jsonc-parser": "3.3.1", "nypm": "0.6.9", "oxc-parser": "^0.144.0" }, "peerDependencies": { "typescript": ">=7" }, "bin": { "adamantite": "bin/adamantite" } }, "sha512-QdqzLWrLvXr6jcGRvmd65Q79l56k2xyFFkEUaFgL7TrVYCqGKW/QapC9TlgLnCBWVO49dQG8p0TtKhbUIjU4/A=="], + "adamantite": ["adamantite@0.36.0", "", { "dependencies": { "@clack/prompts": "1.7.0", "@effect/platform-node": "4.0.0-rc.108", "defu": "6.1.7", "effect": "4.0.0-rc.108", "esrap": "2.3.2", "jsonc-parser": "3.3.1", "nypm": "0.6.9", "oxc-parser": "^0.144.0" }, "peerDependencies": { "typescript": ">=7" }, "bin": { "adamantite": "bin/adamantite" } }, "sha512-U0LfhmL7QrXIspcel6XP8++xzyLn22RpfmNANI5fYa7XA5QVVRUPYcmbftyMjMDtmdEZ4yKTaXENj+LXGgn3ag=="], "addons-linter": ["addons-linter@10.8.0", "", { "dependencies": { "@fluent/syntax": "0.19.0", "@fregante/relaxed-json": "2.0.0", "@mdn/browser-compat-data": "8.0.4", "addons-moz-compare": "1.3.0", "addons-scanner-utils": "15.4.0", "ajv": "8.20.0", "cheerio": "1.2.0", "columnify": "1.6.0", "common-tags": "1.8.2", "css-tree": "3.2.1", "deepmerge": "4.3.1", "eslint": "9.39.4", "eslint-plugin-no-unsanitized": "4.1.5", "eslint-visitor-keys": "5.0.1", "espree": "11.2.0", "esprima": "4.0.1", "fast-json-patch": "3.1.1", "image-size": "2.0.2", "json-merge-patch": "1.0.2", "pino": "10.3.1", "semver": "7.8.5", "source-map-support": "0.5.21", "upath": "3.0.7", "yargs": "17.7.2", "yauzl": "3.4.0" }, "bin": { "addons-linter": "bin/addons-linter" } }, "sha512-AotpGWlp5FrmUIH8kjyYjQvCmthnSqPLpw7kOEl7DfoAWEndOCV/ONuekAVM8QObiei802RyyIClEaekA51n5g=="], @@ -3202,7 +3196,7 @@ "expo-doctor": ["expo-doctor@1.20.1", "", { "bin": { "expo-doctor": "bin/expo-doctor.js" } }, "sha512-u0QYTm3hTZbuMuNdnvscA1X6Xm31ilQ6uxrf8FfzO0ddpOEBrgVEYp8jXgFOBwrRf6DqJoD2j3eKUTWTqvKVzg=="], - "expo-file-system": ["expo-file-system@19.0.23", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-MeGkid9OeNILfT/qonaXHp4f2c15xaB28U/bcN7pqZej0Kx0+6+V7e9ZIXpPHm07zVatxA+QkMTPQEGfmvVOxA=="], + "expo-file-system": ["expo-file-system@57.0.1", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-w7/ERvQFrGP2apTO9lDtZ+O6JQIhfakL7+Xqzh+rfMO9B4LB4qwrz+YvLgir8KFRVX64JHBnRuYBVLY1oQZcqw=="], "expo-font": ["expo-font@57.0.1", "", { "dependencies": { "fontfaceobserver": "^2.1.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-QyS9L1Kh9sKJg4gfU6rdbpxpmH+DyzBX8z6jVvXMUDoqLr1GqmkO/Wu379KCXjL///kWbhpNlbi7AgBuj4VdIQ=="], @@ -3324,8 +3318,6 @@ "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], - "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], - "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], "firefox-profile": ["firefox-profile@4.7.0", "", { "dependencies": { "adm-zip": "~0.5.x", "fs-extra": "^11.2.0", "ini": "^4.1.3", "minimist": "^1.2.8", "xml2js": "^0.6.2" }, "bin": { "firefox-profile": "lib/cli.js" } }, "sha512-aGApEu5bfCNbA4PGUZiRJAIU6jKmghV2UVdklXAofnNtiDjqYw0czLS46W7IfFqVKgKhFB8Ao2YoNGHY4BoIMQ=="], @@ -4094,8 +4086,6 @@ "multimatch": ["multimatch@6.0.0", "", { "dependencies": { "@types/minimatch": "^3.0.5", "array-differ": "^4.0.0", "array-union": "^3.0.1", "minimatch": "^3.0.4" } }, "sha512-I7tSVxHGPlmPN/enE3mS1aOSo6bWBfls+3HmuEeCUBCE7gWnm3cBXCBkpurzFjVRwC6Kld8lLaZ1Iv5vOcjvcQ=="], - "multipasta": ["multipasta@0.2.8", "", {}, "sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q=="], - "multitars": ["multitars@1.0.1", "", {}, "sha512-Do9rHaSDfQ2Fk5Dpg2RjQ4M48Ol7rSq1gvKn/dXJYnhKEm8RRjjUvyiGDDEhJqb0hJPGjRaTB1kx6beqKO/i6Q=="], "mute-stream": ["mute-stream@1.0.0", "", {}, "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA=="], @@ -4942,8 +4932,6 @@ "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], - "toml": ["toml@4.3.0", "", {}, "sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A=="], - "toqr": ["toqr@0.1.1", "", {}, "sha512-FWAPzCIHZHnrE/5/w9MPk0kK25hSQSH2IKhYh9PyjS3SG/+IEMvlwIHbhz+oF7xl54I+ueZlVnMjyzdSwLmAwA=="], "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], @@ -5620,14 +5608,6 @@ "@react-native/metro-babel-transformer/hermes-parser": ["hermes-parser@0.36.0", "", { "dependencies": { "hermes-estree": "0.36.0" } }, "sha512-GdpwMmH5x6IpC1cijvcvYnlPB60Mh6kTSF/NFdYV/j56gYdi+0RIakYs+eqOV+bbO0SW7mgVVGSsTJxyPQfo3w=="], - "@react-navigation/core/nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="], - - "@react-navigation/native/nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="], - - "@react-navigation/native/standard-navigation": ["standard-navigation@0.0.8", "", { "peerDependencies": { "react": "*" } }, "sha512-TyVbo7INUDWtsUWDFn8RR7kwR87U0S4xHfLfbbnyeC581TmmyqQ+eM+nPw8rQTSD8QitRVcYfPaSHr/QJiUy1g=="], - - "@react-navigation/routers/nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="], - "@rolldown/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], "@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], @@ -5648,10 +5628,6 @@ "@sentry/types/@sentry/core": ["@sentry/core@10.37.0", "", {}, "sha512-hkRz7S4gkKLgPf+p3XgVjVm7tAfvcEPZxeACCC6jmoeKhGkzN44nXwLiqqshJ25RMcSrhfFvJa/FlBg6zupz7g=="], - "@standard-community/standard-json/effect": ["effect@4.0.0-beta.99", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.9.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.4", "multipasta": "^0.2.8", "toml": "^4.1.2", "uuid": "^14.0.1", "yaml": "^2.9.0" } }, "sha512-hP1C61uzINfLl/4kKMwcqksxd34s4sQ3VSjsWjhGrkx9CRlXaqnfOK9dpTEKynQ6rA7wU9rb3c+48eDYw7uzxA=="], - - "@standard-community/standard-openapi/effect": ["effect@4.0.0-beta.99", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.9.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.4", "multipasta": "^0.2.8", "toml": "^4.1.2", "uuid": "^14.0.1", "yaml": "^2.9.0" } }, "sha512-hP1C61uzINfLl/4kKMwcqksxd34s4sQ3VSjsWjhGrkx9CRlXaqnfOK9dpTEKynQ6rA7wU9rb3c+48eDYw7uzxA=="], - "@tailwindcss/node/lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], "@tailwindcss/node/tailwindcss": ["tailwindcss@4.3.2", "", {}, "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA=="], @@ -5898,8 +5874,6 @@ "estree-util-build-jsx/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], - "expo/expo-file-system": ["expo-file-system@57.0.1", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-w7/ERvQFrGP2apTO9lDtZ+O6JQIhfakL7+Xqzh+rfMO9B4LB4qwrz+YvLgir8KFRVX64JHBnRuYBVLY1oQZcqw=="], - "expo-modules-autolinking/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "expo-modules-autolinking/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], @@ -5928,8 +5902,6 @@ "fbjs/promise": ["promise@7.3.1", "", { "dependencies": { "asap": "~2.0.3" } }, "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg=="], - "files-sdk/ai": ["ai@6.0.27", "", { "dependencies": { "@ai-sdk/gateway": "3.0.11", "@ai-sdk/provider": "3.0.2", "@ai-sdk/provider-utils": "4.0.4", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-a4ToMt5+4xOzHuYdoTy2GQXuNa2/Tpuhxfs5QESHtEV/Vf2HAzSwmTwikrIs8CDKPuMWV1I9YqAvaN5xexMCiQ=="], - "files-sdk/commander": ["commander@15.0.0", "", {}, "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg=="], "flora-colossus/fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], @@ -6670,10 +6642,6 @@ "@sentry/react/@sentry/browser/@sentry-internal/replay-canvas": ["@sentry-internal/replay-canvas@10.37.0", "", { "dependencies": { "@sentry-internal/replay": "10.37.0", "@sentry/core": "10.37.0" } }, "sha512-PyIYSbjLs+L5essYV0MyIsh4n5xfv2eV7l0nhUoPJv9Bak3kattQY3tholOj0EP3SgKgb+8HSZnmazgF++Hbog=="], - "@standard-community/standard-json/effect/ini": ["ini@7.0.0", "", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="], - - "@standard-community/standard-openapi/effect/ini": ["ini@7.0.0", "", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="], - "@tailwindcss/node/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], "@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], @@ -6994,12 +6962,6 @@ "express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - "files-sdk/ai/@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.11", "", { "dependencies": { "@ai-sdk/provider": "3.0.2", "@ai-sdk/provider-utils": "4.0.4", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-gLrgNXA95wdo/zAlA0miX/SJEYKSYCHg+e0Y/uQeABLScZAMjPw3jWaeANta/Db1T4xfi8cBvY3nnV8Pa27z+w=="], - - "files-sdk/ai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.2", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-HrEmNt/BH/hkQ7zpi2o6N3k1ZR1QTb7z85WYhYygiTxOQuaml4CMtHCWRbric5WPU+RNsYI7r1EpyVQMKO1pYw=="], - - "files-sdk/ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.4", "", { "dependencies": { "@ai-sdk/provider": "3.0.2", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-VxhX0B/dWGbpNHxrKCWUAJKXIXV015J4e7qYjdIU9lLWeptk0KMLGcqkB4wFxff5Njqur8dt8wRi1MN9lZtDqg=="], - "fs-minipass/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], "fx-runner/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], @@ -7336,8 +7298,6 @@ "electron-installer-redhat/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "files-sdk/ai/@ai-sdk/gateway/@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="], - "gaxios/rimraf/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], diff --git a/oxlint.config.ts b/oxlint.config.ts index e9d227c2..5b7dbc23 100644 --- a/oxlint.config.ts +++ b/oxlint.config.ts @@ -1,10 +1,11 @@ import core from "adamantite/lint" +import antislop from "adamantite/lint/antislop" import node from "adamantite/lint/node" import react from "adamantite/lint/react" import { defineConfig } from "oxlint" export default defineConfig({ - extends: [core, react, node], + extends: [core, react, antislop, node], ignorePatterns: [ "**/*.hbs", "**/src/**/_generated", diff --git a/package.json b/package.json index 823ed0e2..ca8b5b96 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "boundaries": "turbo boundaries", "build": "turbo build", "bump:deps": "bun update --interactive --recursive", - "check": "bun run codegen && adamantite check", + "check": "adamantite check", "check:monorepo": "adamantite monorepo", "clean": "turbo clean && git clean -xdf .cache .turbo dist node_modules", "codegen": "turbo run codegen --continue --concurrency=1", @@ -41,7 +41,7 @@ "devDependencies": { "@turbo/gen": "2.10.7", "@types/bun": "1.3.14", - "adamantite": "0.35.0", + "adamantite": "0.36.0", "citty": "0.2.2", "consola": "3.4.2", "knip": "6.32.2", diff --git a/packages/backend/src/functions/components/better-auth/auth.ts b/packages/backend/src/functions/components/better-auth/auth.ts index 0b104d1e..69e98560 100644 --- a/packages/backend/src/functions/components/better-auth/auth.ts +++ b/packages/backend/src/functions/components/better-auth/auth.ts @@ -4,6 +4,9 @@ import type { DataModel } from "#functions/_generated/dataModel.js" import { createAuthOptions } from "#functions/shared/auth.ts" // Static instance for Better Auth schema generation only +// SAFETY: Better Auth reads this context only when a database operation runs; schema generation does not run database operations. +const schemaGenerationContext = {} as GenericCtx + export const auth = createAuth({ - ...createAuthOptions({} as GenericCtx), + ...createAuthOptions(schemaGenerationContext), }) diff --git a/packages/native-ui/src/components/accordion.tsx b/packages/native-ui/src/components/accordion.tsx index 70ac2240..6e6b5922 100644 --- a/packages/native-ui/src/components/accordion.tsx +++ b/packages/native-ui/src/components/accordion.tsx @@ -15,17 +15,10 @@ import Animated, { import { Icon } from "#components/icon.tsx" import { TextClassContext } from "#components/text.tsx" -function Accordion({ - children, - ref: _ref, - ...props -}: Omit, "asChild">) { +function Accordion({ children, ref: _ref, ...props }: AccordionProps) { return ( - + {children} @@ -59,6 +52,9 @@ function AccordionItem({ ) } +type AccordionProps = AccordionPrimitive.RootProps & + Pick, "ref"> + const Trigger = Platform.OS === "web" ? View : Pressable const TRIGGER_TEXT_CLASS = cn( diff --git a/packages/native-ui/src/components/large-title-header/large-title-header.ios.tsx b/packages/native-ui/src/components/large-title-header/large-title-header.ios.tsx index 1b9c1b7a..2d912e27 100644 --- a/packages/native-ui/src/components/large-title-header/large-title-header.ios.tsx +++ b/packages/native-ui/src/components/large-title-header/large-title-header.ios.tsx @@ -20,10 +20,10 @@ function LargeTitleHeader(props: LargeTitleHeaderProps) { const foregroundValue = useCSSVariable("--color-foreground") const mutedValue = useCSSVariable("--color-muted-foreground") - const background = typeof backgroundValue === "string" ? backgroundValue : undefined - const card = typeof cardValue === "string" ? cardValue : background - const foreground = typeof foregroundValue === "string" ? foregroundValue : undefined - const mutedForeground = typeof mutedValue === "string" ? mutedValue : foreground + const background = colorVariableToString(backgroundValue) + const card = colorVariableToString(cardValue) ?? background + const foreground = colorVariableToString(foregroundValue) + const mutedForeground = colorVariableToString(mutedValue) ?? foreground return ( <> @@ -77,16 +77,12 @@ function propsToScreenOptions( headerLargeTitleStyle: foregroundColor ? { color: foregroundColor } : undefined, headerLeft: props.leftView ? (headerProps) => ( - - {typeof props.leftView === "function" ? props.leftView(headerProps) : props.leftView} - + {props.leftView?.(headerProps)} ) : undefined, headerRight: props.rightView ? (headerProps) => ( - - {typeof props.rightView === "function" ? props.rightView(headerProps) : props.rightView} - + {props.rightView?.(headerProps)} ) : undefined, headerSearchBarOptions: props.searchBar @@ -111,6 +107,7 @@ function propsToScreenOptions( }, onSearchButtonPress: props.searchBar.onSearchButtonPress, placeholder: props.searchBar.placeholder ?? "Search...", + // SAFETY: The native search bar writes the full command set to a ref that exposes a safe subset. ref: props.searchBar.ref as NativeStackNavigationSearchBarOptions["ref"], textColor: props.searchBar.textColor ?? foregroundColor, tintColor: props.searchBar.iosTintColor ?? mutedForegroundColor, @@ -130,4 +127,11 @@ function propsToScreenOptions( } } +function colorVariableToString(value: string | number | undefined): string | undefined { + if (value?.constructor === Number) return undefined + + // SAFETY: Uniwind returns only strings, numbers, or undefined, and the numeric case exits above. + return value as string | undefined +} + export { LargeTitleHeader } diff --git a/packages/native-ui/src/components/large-title-header/large-title-header.tsx b/packages/native-ui/src/components/large-title-header/large-title-header.tsx index 3eda9c2d..ca0fa8e0 100644 --- a/packages/native-ui/src/components/large-title-header/large-title-header.tsx +++ b/packages/native-ui/src/components/large-title-header/large-title-header.tsx @@ -1,4 +1,3 @@ -import type { ReactNode } from "react" import { Stack } from "expo-router" import * as React from "react" import { StyleSheet, Text, TextInput, View } from "react-native" @@ -19,13 +18,13 @@ function LargeTitleHeader(props: LargeTitleHeaderProps) { const borderValue = useCSSVariable("--color-border") const mutedValue = useCSSVariable("--color-muted-foreground") - const background = typeof backgroundValue === "string" ? backgroundValue : undefined - const card = typeof cardValue === "string" ? cardValue : background - const foreground = typeof foregroundValue === "string" ? foregroundValue : undefined - const borderColor = typeof borderValue === "string" ? borderValue : undefined - const input = typeof inputValue === "string" ? inputValue : borderColor - const primary = typeof primaryValue === "string" ? primaryValue : foreground - const muted = typeof mutedValue === "string" ? mutedValue : undefined + const background = colorVariableToString(backgroundValue) + const card = colorVariableToString(cardValue) ?? background + const foreground = colorVariableToString(foregroundValue) + const borderColor = colorVariableToString(borderValue) + const input = colorVariableToString(inputValue) ?? borderColor + const primary = colorVariableToString(primaryValue) ?? foreground + const muted = colorVariableToString(mutedValue) const canShowOverlay = Boolean(props.searchBar?.content) && (isFocused || searchValue.length > 0) const isInline = props.materialPreset === "inline" @@ -189,17 +188,18 @@ function LargeTitleHeader(props: LargeTitleHeaderProps) { ) } -function renderHeaderView(view: unknown, tintColor: string | undefined): ReactNode { - if (typeof view === "function") { - const renderView = view as (headerProps: { - canGoBack: boolean - tintColor?: string - }) => ReactNode +function renderHeaderView( + renderView: LargeTitleHeaderProps["leftView"], + tintColor: string | undefined +) { + return renderView?.({ canGoBack: false, tintColor }) +} - return renderView({ canGoBack: false, tintColor }) - } +function colorVariableToString(value: string | number | undefined): string | undefined { + if (value?.constructor === Number) return undefined - return view as ReactNode + // SAFETY: Uniwind returns only strings, numbers, or undefined, and the numeric case exits above. + return value as string | undefined } function searchBarInputTypeToKeyboardType( @@ -211,9 +211,14 @@ function searchBarInputTypeToKeyboardType( phone: "phone-pad", } as const - return inputType && inputType in keyboardTypeByInputType - ? keyboardTypeByInputType[inputType as keyof typeof keyboardTypeByInputType] - : "default" + switch (inputType) { + case "email": + case "number": + case "phone": + return keyboardTypeByInputType[inputType] + default: + return "default" + } } export { LargeTitleHeader } diff --git a/packages/native-ui/src/components/native-only-animated-view.tsx b/packages/native-ui/src/components/native-only-animated-view.tsx index 14c06f4f..16deb4ba 100644 --- a/packages/native-ui/src/components/native-only-animated-view.tsx +++ b/packages/native-ui/src/components/native-only-animated-view.tsx @@ -23,6 +23,7 @@ function NativeOnlyAnimatedView( React.RefAttributes & { as: "Pressable" }) ) { if (Platform.OS === "web") { + // SAFETY: React component children satisfy ReactNode even when Reanimated widens its children prop. return props.children as React.ReactNode } if (props.as === "Pressable") { diff --git a/packages/native-ui/src/components/text.tsx b/packages/native-ui/src/components/text.tsx index afccda63..be1daa71 100644 --- a/packages/native-ui/src/components/text.tsx +++ b/packages/native-ui/src/components/text.tsx @@ -46,21 +46,10 @@ type TextVariantProps = VariantProps type TextVariant = NonNullable -const ROLE: Partial> = { - blockquote: Platform.select({ web: "blockquote" as Role }), - code: Platform.select({ web: "code" as Role }), - h1: "heading", - h2: "heading", - h3: "heading", - h4: "heading", -} - -const ARIA_LEVEL: Partial> = { - h1: "1", - h2: "2", - h3: "3", - h4: "4", -} +// SAFETY: React Native for Web supports the blockquote role although the native Role type omits it. +const WEB_BLOCKQUOTE_ROLE = "blockquote" as Role +// SAFETY: React Native for Web supports the code role although the native Role type omits it. +const WEB_CODE_ROLE = "code" as Role const TextClassContext = React.createContext(undefined) @@ -79,11 +68,42 @@ function Text({ return ( ) } +function roleForVariant(variant: TextVariant | null | undefined): Role | undefined { + switch (variant) { + case "blockquote": + return Platform.OS === "web" ? WEB_BLOCKQUOTE_ROLE : undefined + case "code": + return Platform.OS === "web" ? WEB_CODE_ROLE : undefined + case "h1": + case "h2": + case "h3": + case "h4": + return "heading" + default: + return undefined + } +} + +function ariaLevelForVariant(variant: TextVariant | null | undefined): string | undefined { + switch (variant) { + case "h1": + return "1" + case "h2": + return "2" + case "h3": + return "3" + case "h4": + return "4" + default: + return undefined + } +} + export { Text, TextClassContext } diff --git a/packages/payments/src/helpers.ts b/packages/payments/src/helpers.ts index 52d7bd0c..0e3792ba 100644 --- a/packages/payments/src/helpers.ts +++ b/packages/payments/src/helpers.ts @@ -1,9 +1,19 @@ import type { Stripe } from "stripe" import { kv, namespaceKey } from "@init/kv/client" +import * as z from "@init/utils/schema" import { payments } from "#client.ts" import { ENV } from "#env.generated.ts" const paymentsKey = namespaceKey("payments") +const expandedPaymentMethodSchema = z.object({ + card: z + .object({ + brand: z.string(), + last4: z.string(), + }) + .nullable() + .optional(), +}) export type SubscriptionCache = | { @@ -66,18 +76,21 @@ export async function syncSubscription(customerId: string): Promise(name: string, getValue: () => T) { - const thusly = globalThis as unknown as { - __remember_init?: Map - } +const registryKey = "__remember_init" +type SingletonRegistry = Map +type GlobalSingletonRegistry = typeof globalThis & { + [registryKey]?: SingletonRegistry +} - thusly.__remember_init ??= new Map() +export function singleton(name: string, getValue: () => T) { + // SAFETY: This module owns the registry property and always initializes it with a Map. + const globalRegistry = globalThis as GlobalSingletonRegistry + globalRegistry[registryKey] ??= new Map() - if (!thusly.__remember_init.has(name)) { - thusly.__remember_init.set(name, getValue()) + if (!globalRegistry[registryKey].has(name)) { + globalRegistry[registryKey].set(name, getValue()) } - return thusly.__remember_init.get(name) as T + // SAFETY: A singleton name identifies one value contract; this function inserts T before this read. + return globalRegistry[registryKey].get(name) as T } /** * Forgets a remembered value by a given name. Does not throw if the name doesn't exist. */ export function forget(name: string) { - const thusly = globalThis as unknown as { - __remember_init?: Map - } - - thusly.__remember_init ??= new Map() + // SAFETY: This module owns the registry property and always initializes it with a Map. + const globalRegistry = globalThis as GlobalSingletonRegistry + globalRegistry[registryKey] ??= new Map() - return thusly.__remember_init.delete(name) + return globalRegistry[registryKey].delete(name) } diff --git a/packages/utils/src/type.ts b/packages/utils/src/type.ts index 25398e80..0c708237 100644 --- a/packages/utils/src/type.ts +++ b/packages/utils/src/type.ts @@ -28,8 +28,8 @@ type LessThanOrEqual< */ export type DeepMerge = Omit & { [K in keyof U]: K extends keyof T - ? T[K] extends Record - ? U[K] extends Record + ? T[K] extends object + ? U[K] extends object ? DeepMerge : U[K] : U[K] diff --git a/packages/utils/src/url.ts b/packages/utils/src/url.ts index 9ae39291..ad78411d 100644 --- a/packages/utils/src/url.ts +++ b/packages/utils/src/url.ts @@ -33,9 +33,10 @@ export function createUrlBuilder(baseUrl: string, protocol: "http" | "https" = " return normalized } - const filteredQuery = Object.fromEntries( - Object.entries(options.query).filter(([, v]) => v !== undefined) - ) as Record + const filteredQuery: Record = {} + for (const [key, value] of Object.entries(options.query)) { + if (value !== undefined) filteredQuery[key] = value + } return withQuery(normalized, filteredQuery) } diff --git a/scripts/template/portless.ts b/scripts/template/portless.ts index 5726b438..8fa0b843 100644 --- a/scripts/template/portless.ts +++ b/scripts/template/portless.ts @@ -1,6 +1,6 @@ import { join, relative } from "node:path" -import { getWorkspaces, readJson, writeJson } from "./shared" +import { getJsonObject, getJsonString, getWorkspaces, readJson, writeJson } from "./shared" export function getWorkspaceRouteName(workspacePath: string, projectName: string) { const normalizedWorkspacePath = workspacePath.replaceAll("\\", "/") @@ -19,11 +19,11 @@ async function updateWorkspacePackageName( if (!(await Bun.file(packageJsonPath).exists())) return null const packageJson = await readJson(packageJsonPath) - const portless = packageJson.portless as { name?: string } | undefined + const portless = getJsonObject(packageJson, "portless") if (!portless) return null const routeName = getWorkspaceRouteName(workspacePath, projectName) - if (portless.name === routeName) return null + if (getJsonString(portless, "name") === routeName) return null portless.name = routeName await writeJson(packageJsonPath, packageJson) diff --git a/scripts/template/shared.ts b/scripts/template/shared.ts index 0579be52..e2313a0f 100644 --- a/scripts/template/shared.ts +++ b/scripts/template/shared.ts @@ -11,14 +11,48 @@ export type Workspace = { name: string } -export async function readJson(path: string): Promise> { - return JSON.parse(await Bun.file(path).text()) as Record +export type JsonValue = boolean | null | number | string | JsonObject | JsonValue[] + +export type JsonObject = { + [key: string]: JsonValue | undefined } -export async function writeJson(path: string, value: unknown) { +export async function readJson(path: string): Promise { + const value: unknown = JSON.parse(await Bun.file(path).text()) + if (value === null || !(value instanceof Object) || Array.isArray(value)) + throw new Error(`Expected ${path} to contain a JSON object.`) + + // SAFETY: JSON.parse returned a non-null, non-array object, and JSON values have the JsonValue contract. + return value as JsonObject +} + +export async function writeJson(path: string, value: JsonValue) { await Bun.write(path, `${JSON.stringify(value, null, 2)}\n`) } +export function isJsonObject(value: JsonValue | undefined): value is JsonObject { + return ( + value !== null && value !== undefined && !Array.isArray(value) && value.constructor === Object + ) +} + +export function getJsonString(object: JsonObject, key: string) { + const value = object[key] + return value?.constructor === String ? String(value) : undefined +} + +export function getJsonObject(object: JsonObject, key: string) { + const value = object[key] + return isJsonObject(value) ? value : undefined +} + +export function getJsonStringArray(object: JsonObject, key: string) { + const value = object[key] + if (!Array.isArray(value) || !value.every((entry) => entry?.constructor === String)) return + + return value.map(String) +} + export function normalizeScope(scope: string) { const normalizedScope = scope.replace(/^@/, "").trim() @@ -35,13 +69,10 @@ export function getScopePrefix(scope: string) { return `@${scope}/` } -export function getDependencyNames(packageJson: Record) { - return ["dependencies", "devDependencies", "peerDependencies"].flatMap((field) => { - const dependencies = packageJson[field] - return dependencies && typeof dependencies === "object" && !Array.isArray(dependencies) - ? Object.keys(dependencies) - : [] - }) +export function getDependencyNames(packageJson: JsonObject) { + return ["dependencies", "devDependencies", "peerDependencies"].flatMap((field) => + Object.keys(getJsonObject(packageJson, field) ?? {}) + ) } export async function getWorkspaces(rootDir: string, kind: WorkspaceKind) { @@ -120,12 +151,10 @@ export async function getProjectScope(rootDir: string) { const packageNames = await Promise.all( workspaces.flat().map(async (workspace) => { const packageJson = await readJson(join(workspace.directory, "package.json")) - return packageJson.name + return getJsonString(packageJson, "name") }) ) - const packageName = packageNames.find( - (name): name is string => typeof name === "string" && /^@[^/]+\//.test(name) - ) + const packageName = packageNames.find((name) => name !== undefined && /^@[^/]+\//.test(name)) const match = packageName && /^@([^/]+)\//.exec(packageName) const scope = match?.[1] if (scope) return normalizeScope(scope) diff --git a/turbo/generators/boundaries.ts b/turbo/generators/boundaries.ts new file mode 100644 index 00000000..531a48fd --- /dev/null +++ b/turbo/generators/boundaries.ts @@ -0,0 +1,79 @@ +import type { PlopTypes } from "@turbo/gen" +import Bun from "bun" + +export type PackageJson = { + dependencies?: Record + devDependencies?: Record + name?: string +} + +export function requireAnswers(answers: PlopTypes.Answers | undefined) { + if (!answers) throw new Error("Expected generator answers.") + + return answers +} + +export function getAnswerString(answers: PlopTypes.Answers, key: string) { + const value = answers[key] + if (value === undefined || value === null || value.constructor !== String) + throw new Error(`Expected generator answer ${key} to be a string.`) + + return String(value) +} + +export function getAnswerBoolean(answers: PlopTypes.Answers, key: string) { + const value = answers[key] + if (value === undefined || value === null || value.constructor !== Boolean) + throw new Error(`Expected generator answer ${key} to be a boolean.`) + + return Boolean(value) +} + +export function getAnswerStrings(answers: PlopTypes.Answers, key: string) { + const value = answers[key] + if (Array.isArray(value) && value.every((entry) => entry?.constructor === String)) + return value.map(String) + if (value?.constructor === String) return [String(value)] + + throw new Error(`Expected generator answer ${key} to contain strings.`) +} + +export async function readPackageJson(path: string): Promise { + const value: unknown = JSON.parse(await Bun.file(path).text()) + if (value === null || !(value instanceof Object) || Array.isArray(value)) + throw new Error(`Expected ${path} to contain a JSON object.`) + + if ("dependencies" in value) { + const dependencies = value.dependencies + if ( + dependencies !== undefined && + (dependencies === null || + !(dependencies instanceof Object) || + Array.isArray(dependencies) || + !Object.values(dependencies).every((version) => version?.constructor === String)) + ) + throw new Error(`Expected ${path} dependencies to map package names to versions.`) + } + if ("devDependencies" in value) { + const devDependencies = value.devDependencies + if ( + devDependencies !== undefined && + (devDependencies === null || + !(devDependencies instanceof Object) || + Array.isArray(devDependencies) || + !Object.values(devDependencies).every((version) => version?.constructor === String)) + ) + throw new Error(`Expected ${path} devDependencies to map package names to versions.`) + } + if ("name" in value && value.name !== undefined && value.name?.constructor !== String) + throw new Error(`Expected ${path} name to be a string.`) + + return value +} + +export async function readPackageName(path: string) { + const packageJson = await readPackageJson(path) + if (!packageJson.name) throw new Error(`Expected ${path} to declare a package name.`) + + return packageJson.name +} diff --git a/turbo/generators/commands/code-snippets.ts b/turbo/generators/commands/code-snippets.ts index ed661082..8a972f43 100644 --- a/turbo/generators/commands/code-snippets.ts +++ b/turbo/generators/commands/code-snippets.ts @@ -1,6 +1,8 @@ import type { PlopTypes } from "@turbo/gen" import Bun from "bun" +import { getAnswerStrings, readPackageJson, readPackageName, requireAnswers } from "../boundaries" + type CodeSnippetsAnswers = PlopTypes.Answers & { utilities?: Array<"assert" | "codec"> corePackage?: string @@ -9,7 +11,11 @@ type CodeSnippetsAnswers = PlopTypes.Answers & { export function registerCodeSnippetsGenerator(plop: PlopTypes.NodePlopAPI): void { plop.setGenerator("code-snippets", { actions: (rawAnswers) => { - const answers = rawAnswers as CodeSnippetsAnswers + const providedAnswers = requireAnswers(rawAnswers) + const utilities = getAnswerStrings(providedAnswers, "utilities").filter( + (utility): utility is "assert" | "codec" => utility === "assert" || utility === "codec" + ) + const answers: CodeSnippetsAnswers = Object.assign(providedAnswers, { utilities }) const actions: PlopTypes.Actions = [] @@ -25,25 +31,21 @@ export function registerCodeSnippetsGenerator(plop: PlopTypes.NodePlopAPI): void if (answers.utilities?.includes("assert")) { actions.push( async () => { - const corePackageJson = (await Bun.file("packages/core/package.json").json()) as { - name: string - } + const corePackageName = await readPackageName("packages/core/package.json") const utilsPackageJsonPath = "packages/utils/package.json" - const utilsPackageJson = (await Bun.file(utilsPackageJsonPath).json()) as { - dependencies?: Record - } + const utilsPackageJson = await readPackageJson(utilsPackageJsonPath) const dependencies = utilsPackageJson.dependencies ?? {} - answers.corePackage = corePackageJson.name - if (dependencies[corePackageJson.name] === "workspace:*") - return `[SKIPPED] ${utilsPackageJsonPath} already contains ${corePackageJson.name}` + answers.corePackage = corePackageName + if (dependencies[corePackageName] === "workspace:*") + return `[SKIPPED] ${utilsPackageJsonPath} already contains ${corePackageName}` - dependencies[corePackageJson.name] = "workspace:*" + dependencies[corePackageName] = "workspace:*" utilsPackageJson.dependencies = Object.fromEntries( Object.entries(dependencies).toSorted(([left], [right]) => left.localeCompare(right)) ) await Bun.write(utilsPackageJsonPath, `${JSON.stringify(utilsPackageJson, null, 2)}\n`) - return `${utilsPackageJsonPath}: added ${corePackageJson.name}` + return `${utilsPackageJsonPath}: added ${corePackageName}` }, { path: "packages/utils/src/assert.ts", @@ -79,8 +81,7 @@ export function registerCodeSnippetsGenerator(plop: PlopTypes.NodePlopAPI): void message: "Which utility snippets would you like to add?", name: "utilities", type: "checkbox", - validate: (values: unknown) => - (Array.isArray(values) && values.length > 0) || "Select at least one snippet", + validate: (values: string[]) => values.length > 0 || "Select at least one snippet", }, ], }) diff --git a/turbo/generators/commands/connect-backend.ts b/turbo/generators/commands/connect-backend.ts index 4bd25259..167a5ca9 100644 --- a/turbo/generators/commands/connect-backend.ts +++ b/turbo/generators/commands/connect-backend.ts @@ -1,6 +1,14 @@ import type { PlopTypes } from "@turbo/gen" import Bun from "bun" +import { + getAnswerBoolean, + getAnswerString, + readPackageJson, + readPackageName, + requireAnswers, +} from "../boundaries" + type ConnectBackendAnswers = PlopTypes.Answers & { app: "app" | "desktop" | "mobile" auth: boolean @@ -13,10 +21,22 @@ type ConnectBackendAnswers = PlopTypes.Answers & { workspaceDependenciesChanged?: boolean } -type PackageJson = { - dependencies?: Record - devDependencies?: Record - name?: string +function parseConnectBackendAnswers( + rawAnswers: PlopTypes.Answers | undefined +): ConnectBackendAnswers { + const providedAnswers = requireAnswers(rawAnswers) + const app = getAnswerString(providedAnswers, "app") + const backend = getAnswerString(providedAnswers, "backend") + if (app !== "app" && app !== "desktop" && app !== "mobile") + throw new Error(`Unsupported app workspace: ${app}`) + if (backend !== "convex" && backend !== "hono" && backend !== "trpc") + throw new Error(`Unsupported backend: ${backend}`) + + const auth = getAnswerBoolean(providedAnswers, "auth") + const example = getAnswerBoolean(providedAnswers, "example") + Object.assign(providedAnswers, { app, auth, backend, example }) + + return { ...providedAnswers, app, auth, backend, example } } export function registerConnectBackendGenerator(plop: PlopTypes.NodePlopAPI): void { @@ -31,7 +51,7 @@ export function registerConnectBackendGenerator(plop: PlopTypes.NodePlopAPI): vo plop.setGenerator("connect-backend", { actions: (rawAnswers) => { - const answers = rawAnswers as ConnectBackendAnswers + const answers = parseConnectBackendAnswers(rawAnswers) const appPath = `apps/${answers.app}` const actions: PlopTypes.Actions = [] const isSupported = @@ -51,37 +71,18 @@ export function registerConnectBackendGenerator(plop: PlopTypes.NodePlopAPI): vo actions.push( async () => { if (answers.backend === "convex") { - const backendPackageJson = (await Bun.file("packages/backend/package.json").json()) as { - name: string - } - const authPackageJson = (await Bun.file("packages/auth/package.json").json()) as { - name: string - } - const nativeUiPackageJson = (await Bun.file( - "packages/native-ui/package.json" - ).json()) as { name: string } - - answers.backendPackage = backendPackageJson.name - answers.authPackage = authPackageJson.name - answers.nativeUiPackage = nativeUiPackageJson.name + answers.backendPackage = await readPackageName("packages/backend/package.json") + answers.authPackage = await readPackageName("packages/auth/package.json") + answers.nativeUiPackage = await readPackageName("packages/native-ui/package.json") } else { - const apiPackageJson = (await Bun.file("apps/api/package.json").json()) as { - name: string - } - answers.apiPackage = apiPackageJson.name + answers.apiPackage = await readPackageName("apps/api/package.json") if (answers.app === "mobile" && (answers.auth || answers.example)) { - const nativeUiPackageJson = (await Bun.file( - "packages/native-ui/package.json" - ).json()) as { name: string } - answers.nativeUiPackage = nativeUiPackageJson.name + answers.nativeUiPackage = await readPackageName("packages/native-ui/package.json") } if (answers.app === "mobile" && answers.auth) { - const authPackageJson = (await Bun.file("packages/auth/package.json").json()) as { - name: string - } - answers.authPackage = authPackageJson.name + answers.authPackage = await readPackageName("packages/auth/package.json") } } @@ -89,7 +90,7 @@ export function registerConnectBackendGenerator(plop: PlopTypes.NodePlopAPI): vo }, async () => { const packageJsonPath = `${appPath}/package.json` - const packageJson = (await Bun.file(packageJsonPath).json()) as PackageJson + const packageJson = await readPackageJson(packageJsonPath) const dependencies = packageJson.dependencies ?? {} const packageNames = answers.backend === "convex" @@ -425,7 +426,7 @@ export function registerConnectBackendGenerator(plop: PlopTypes.NodePlopAPI): vo actions.push( async () => { if (answers.backend === "trpc") { - const packageJson = (await Bun.file(`${appPath}/package.json`).json()) as PackageJson + const packageJson = await readPackageJson(`${appPath}/package.json`) const installedPackages = { ...packageJson.dependencies, ...packageJson.devDependencies, diff --git a/turbo/generators/commands/files-client.ts b/turbo/generators/commands/files-client.ts index 91260781..adcf1d0f 100644 --- a/turbo/generators/commands/files-client.ts +++ b/turbo/generators/commands/files-client.ts @@ -1,6 +1,8 @@ import type { PlopTypes } from "@turbo/gen" import Bun from "bun" +import { getAnswerString, readPackageJson, requireAnswers } from "../boundaries" + type FilesClientAnswers = PlopTypes.Answers & { app: string dependencyRequired?: boolean @@ -8,11 +10,6 @@ type FilesClientAnswers = PlopTypes.Answers & { isInstalled?: boolean } -type PackageJson = { - dependencies?: Record - devDependencies?: Record -} - async function getMissingPaths(paths: readonly string[]): Promise { const checks = await Promise.all( paths.map(async (path) => ({ exists: await Bun.file(path).exists(), path })) @@ -32,7 +29,11 @@ export function registerFilesClientGenerator(plop: PlopTypes.NodePlopAPI): void plop.setGenerator("files-client", { actions: (rawAnswers) => { - const answers = rawAnswers as FilesClientAnswers + const providedAnswers = requireAnswers(rawAnswers) + const answers: FilesClientAnswers = Object.assign(providedAnswers, { + app: getAnswerString(providedAnswers, "app"), + endpoint: getAnswerString(providedAnswers, "endpoint"), + }) const appPath = `apps/${answers.app}` const clientPath = `${appPath}/src/shared/files.ts` const actions: PlopTypes.Actions = [ @@ -50,7 +51,7 @@ export function registerFilesClientGenerator(plop: PlopTypes.NodePlopAPI): void const [routes, packageJson, hasClient] = await Promise.all([ Bun.file("apps/api/src/routes/index.ts").text(), - Bun.file(`${appPath}/package.json`).json() as Promise, + readPackageJson(`${appPath}/package.json`), Bun.file(clientPath).exists(), ]) if (!routes.includes('.route("/files", filesRoutes)')) diff --git a/turbo/generators/commands/new-feature.ts b/turbo/generators/commands/new-feature.ts index e13924ea..716a0605 100644 --- a/turbo/generators/commands/new-feature.ts +++ b/turbo/generators/commands/new-feature.ts @@ -1,6 +1,8 @@ import type { PlopTypes } from "@turbo/gen" import Bun from "bun" +import { getAnswerString, getAnswerStrings, requireAnswers } from "../boundaries" + type NewFeatureAnswers = PlopTypes.Answers & { app: string files: string[] | string @@ -19,7 +21,12 @@ export function registerNewFeatureGenerator(plop: PlopTypes.NodePlopAPI): void { plop.setGenerator("new-feature", { actions: (rawAnswers) => { - const answers = rawAnswers as NewFeatureAnswers + const providedAnswers = requireAnswers(rawAnswers) + const answers: NewFeatureAnswers = Object.assign(providedAnswers, { + app: getAnswerString(providedAnswers, "app"), + files: getAnswerStrings(providedAnswers, "files"), + name: getAnswerString(providedAnswers, "name"), + }) const app = plop.renderString("{{kebabCase value}}", { value: answers.app }) const feature = plop.renderString("{{kebabCase value}}", { value: answers.name }) const destination = `apps/${app}/src/features/${feature}` diff --git a/turbo/generators/commands/new-package.ts b/turbo/generators/commands/new-package.ts index ef1c7f35..cec291d6 100644 --- a/turbo/generators/commands/new-package.ts +++ b/turbo/generators/commands/new-package.ts @@ -1,6 +1,8 @@ import type { PlopTypes } from "@turbo/gen" import Bun from "bun" +import { getAnswerString, readPackageJson, readPackageName, requireAnswers } from "../boundaries" + type NewPackageAnswers = PlopTypes.Answers & { name: string packageScope?: string @@ -11,26 +13,24 @@ type NewPackageAnswers = PlopTypes.Answers & { export function registerNewPackageGenerator(plop: PlopTypes.NodePlopAPI): void { plop.setGenerator("new-package", { actions: (rawAnswers) => { - const answers = rawAnswers as NewPackageAnswers + const providedAnswers = requireAnswers(rawAnswers) + const answers: NewPackageAnswers = Object.assign(providedAnswers, { + name: getAnswerString(providedAnswers, "name"), + }) const name = plop.renderString("{{kebabCase value}}", { value: answers.name }) const packagePath = `packages/${name}` return [ async () => { - const rootPackageJson = (await Bun.file("package.json").json()) as { - name?: string - devDependencies?: Record - } - const toolingPackageJson = (await Bun.file("tooling/tsconfig/package.json").json()) as { - name: string - } + const rootPackageJson = await readPackageJson("package.json") + const toolingPackageName = await readPackageName("tooling/tsconfig/package.json") const workspacePackageJsonPaths = new Bun.Glob("{apps,packages}/*/package.json") let packageScope: string | undefined for await (const packageJsonPath of workspacePackageJsonPaths.scan({ cwd: process.cwd(), })) { - const packageJson = (await Bun.file(packageJsonPath).json()) as { name?: string } + const packageJson = await readPackageJson(packageJsonPath) const match = packageJson.name?.match(/^@([^/]+)\//) if (match?.[1]) { packageScope = match[1] @@ -48,7 +48,7 @@ export function registerNewPackageGenerator(plop: PlopTypes.NodePlopAPI): void { Object.assign(answers, { name, packageScope: `@${packageScope}`, - toolingPackage: toolingPackageJson.name, + toolingPackage: toolingPackageName, typescriptVersion, }) return `Prepared ${packagePath}` From 24e303bc568f5f180b8c10cdce89f18e0848888c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adel=20Rodr=C3=ADguez?= Date: Sun, 23 Aug 2026 11:02:34 -0400 Subject: [PATCH 2/4] chore: finish adamantite 0.37.0 upgrade --- apps/api/src/shared/env.generated.ts | 72 ++++++++++++++-------------- bun.lock | 46 +++++++++--------- knip.config.ts | 5 +- oxlint.config.ts | 1 + package.json | 4 +- packages/utils/src/schema.ts | 1 + scripts/template/add.ts | 3 +- scripts/template/setup.ts | 36 +++++++------- 8 files changed, 89 insertions(+), 79 deletions(-) diff --git a/apps/api/src/shared/env.generated.ts b/apps/api/src/shared/env.generated.ts index 1f0654e4..f5348d7f 100644 --- a/apps/api/src/shared/env.generated.ts +++ b/apps/api/src/shared/env.generated.ts @@ -139,13 +139,6 @@ export type CoercedEnvSchema = { */ DATABASE_URL: string; - /** - * **RUN_PRODUCTION_MIGRATIONS** - * Allows migration tooling to modify a non-local database. - * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M23%2023a7%207%200%201%201%207-7a7.01%207.01%200%200%201-7%207m0-12a5%205%200%201%200%205%205a5.006%205.006%200%200%200-5-5%22%2F%3E%3Ccircle%20cx%3D%229%22%20cy%3D%2216%22%20r%3D%227%22%20fill%3D%22%23808080%22%2F%3E%3C%2Fsvg%3E) - */ - RUN_PRODUCTION_MIGRATIONS: boolean; - /** * **REDIS_URL** 🔐 _sensitive_ * Redis connection URL. @@ -153,6 +146,20 @@ export type CoercedEnvSchema = { */ REDIS_URL: string; + /** + * **INNGEST_BASE_URL** + * Optional self-hosted or local Inngest endpoint. + * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M24%2021V9h-2v14h8v-2zm-4-6v-4c0-1.103-.897-2-2-2h-6v14h2v-6h1.48l2.335%206h2.145l-2.333-6H18c1.103%200%202-.897%202-2m-6-4h4v4h-4zM8%2023H4c-1.103%200-2-.897-2-2V9h2v12h4V9h2v12c0%201.103-.897%202-2%202%22%2F%3E%3C%2Fsvg%3E) + */ + INNGEST_BASE_URL?: string; + + /** + * **INNGEST_DEV** + * Enables Inngest development mode. + * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M23%2023a7%207%200%201%201%207-7a7.01%207.01%200%200%201-7%207m0-12a5%205%200%201%200%205%205a5.006%205.006%200%200%200-5-5%22%2F%3E%3Ccircle%20cx%3D%229%22%20cy%3D%2216%22%20r%3D%227%22%20fill%3D%22%23808080%22%2F%3E%3C%2Fsvg%3E) + */ + INNGEST_DEV: boolean; + /** * **INNGEST_EVENT_KEY** 🔐 _sensitive_ * Inngest event ingestion credential. @@ -167,20 +174,6 @@ export type CoercedEnvSchema = { */ INNGEST_SIGNING_KEY: string; - /** - * **INNGEST_DEV** - * Enables Inngest development mode. - * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M23%2023a7%207%200%201%201%207-7a7.01%207.01%200%200%201-7%207m0-12a5%205%200%201%200%205%205a5.006%205.006%200%200%200-5-5%22%2F%3E%3Ccircle%20cx%3D%229%22%20cy%3D%2216%22%20r%3D%227%22%20fill%3D%22%23808080%22%2F%3E%3C%2Fsvg%3E) - */ - INNGEST_DEV: boolean; - - /** - * **INNGEST_BASE_URL** - * Optional self-hosted or local Inngest endpoint. - * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M24%2021V9h-2v14h8v-2zm-4-6v-4c0-1.103-.897-2-2-2h-6v14h2v-6h1.48l2.335%206h2.145l-2.333-6H18c1.103%200%202-.897%202-2m-6-4h4v4h-4zM8%2023H4c-1.103%200-2-.897-2-2V9h2v12h4V9h2v12c0%201.103-.897%202-2%202%22%2F%3E%3C%2Fsvg%3E) - */ - INNGEST_BASE_URL?: string; - /** * **SENTRY_DSN** * Server Sentry DSN. @@ -209,20 +202,6 @@ export type CoercedEnvSchema = { */ SENTRY_PROJECT: string; - /** - * **SENTRY_DEBUG** - * Enables server Sentry diagnostics. - * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M23%2023a7%207%200%201%201%207-7a7.01%207.01%200%200%201-7%207m0-12a5%205%200%201%200%205%205a5.006%205.006%200%200%200-5-5%22%2F%3E%3Ccircle%20cx%3D%229%22%20cy%3D%2216%22%20r%3D%227%22%20fill%3D%22%23808080%22%2F%3E%3C%2Fsvg%3E) - */ - SENTRY_DEBUG: boolean; - - /** - * **SENTRY_SPOTLIGHT** - * Enables local Sentry Spotlight. - * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M23%2023a7%207%200%201%201%207-7a7.01%207.01%200%200%201-7%207m0-12a5%205%200%201%200%205%205a5.006%205.006%200%200%200-5-5%22%2F%3E%3Ccircle%20cx%3D%229%22%20cy%3D%2216%22%20r%3D%227%22%20fill%3D%22%23808080%22%2F%3E%3C%2Fsvg%3E) - */ - SENTRY_SPOTLIGHT: boolean; - /** * **EMAIL_FROM** * Sender used when a message does not provide one. @@ -244,6 +223,27 @@ export type CoercedEnvSchema = { */ MOCK_RESEND: boolean; + /** + * **RUN_PRODUCTION_MIGRATIONS** + * Allows migration tooling to modify a non-local database. + * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M23%2023a7%207%200%201%201%207-7a7.01%207.01%200%200%201-7%207m0-12a5%205%200%201%200%205%205a5.006%205.006%200%200%200-5-5%22%2F%3E%3Ccircle%20cx%3D%229%22%20cy%3D%2216%22%20r%3D%227%22%20fill%3D%22%23808080%22%2F%3E%3C%2Fsvg%3E) + */ + RUN_PRODUCTION_MIGRATIONS: boolean; + + /** + * **SENTRY_DEBUG** + * Enables server Sentry diagnostics. + * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M23%2023a7%207%200%201%201%207-7a7.01%207.01%200%200%201-7%207m0-12a5%205%200%201%200%205%205a5.006%205.006%200%200%200-5-5%22%2F%3E%3Ccircle%20cx%3D%229%22%20cy%3D%2216%22%20r%3D%227%22%20fill%3D%22%23808080%22%2F%3E%3C%2Fsvg%3E) + */ + SENTRY_DEBUG: boolean; + + /** + * **SENTRY_SPOTLIGHT** + * Enables local Sentry Spotlight. + * ![icon](data:image/svg+xml;utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M23%2023a7%207%200%201%201%207-7a7.01%207.01%200%200%201-7%207m0-12a5%205%200%201%200%205%205a5.006%205.006%200%200%200-5-5%22%2F%3E%3Ccircle%20cx%3D%229%22%20cy%3D%2216%22%20r%3D%227%22%20fill%3D%22%23808080%22%2F%3E%3C%2Fsvg%3E) + */ + SENTRY_SPOTLIGHT: boolean; + /** * **INNGEST_SIGNING_KEY_FALLBACK** 🔐 _sensitive_ * Previous signing credential accepted during rotation. @@ -263,7 +263,7 @@ export type EnvSchemaAsStrings = { type _EnvSchemaAsStrings_3b9f7a4f = EnvSchemaAsStrings; -export type PublicCoercedEnvSchema = Readonly>; +export type PublicCoercedEnvSchema = Readonly>; // re-export of the runtime ENV proxy, typed to this package's schema export const ENV = _ENV as unknown as Readonly; diff --git a/bun.lock b/bun.lock index e24aa413..8c818146 100644 --- a/bun.lock +++ b/bun.lock @@ -7,12 +7,12 @@ "devDependencies": { "@turbo/gen": "2.10.7", "@types/bun": "1.3.14", - "adamantite": "0.36.0", + "adamantite": "0.37.0", "citty": "0.2.2", "consola": "3.4.2", "knip": "6.32.2", "oxfmt": "0.63.0", - "oxlint": "1.78.0", + "oxlint": "1.79.0", "oxlint-tsgolint": "7.0.2001", "portless": "0.15.5", "sherif": "1.13.0", @@ -1656,43 +1656,43 @@ "@oxlint-tsgolint/win32-x64": ["@oxlint-tsgolint/win32-x64@7.0.2001", "", { "os": "win32", "cpu": "x64" }, "sha512-FkDRm8hx9OwzGQqyWG1tO5QrTLRApff9DzSgpz9QZau37BR8d1VYKOxMLGf6shPZntJFoTwIIJYT68VndYDCog=="], - "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.78.0", "", { "os": "android", "cpu": "arm" }, "sha512-Bu819lmAfZMUHErrpe0cEWj3iaefuUODHSU8+UbXy67V/r7/7f4K3FL0NmbD85E+wiFLDYuhP8Zlv0XnVeXshw=="], + "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.79.0", "", { "os": "android", "cpu": "arm" }, "sha512-TebFaaMklO/RXzTv7PucaCq9l3X6D1gA+C8H6K4njtjFOV+zWE9MKLpulcJZN9bzytbUbQIY0mZuz12nQ5Kv4Q=="], - "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.78.0", "", { "os": "android", "cpu": "arm64" }, "sha512-CDfxZgB61B7buRdY2FJoAYYPPXCZ1EoC1LKscnC5dg3kjobdxiconvAvvN1BmHyW4PyFT3jRLDag/BY/roSNBQ=="], + "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.79.0", "", { "os": "android", "cpu": "arm64" }, "sha512-KqqnOtAVgNsPPF0YSodkFZA1O80jcKoCZCTu3bgsszxA+MrMP9TLzfXitKjEj1FmrPprKDMdRDMmY3weESO9sg=="], - "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.78.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-2Y2U9Ahrz+OO0Ej88f9SJYq51/jUBp1Mc7iZu0ukrbeeZ3gpRGfzIFnoqfHDY96xr0GEfNrPUBFEy0nN5aD7HA=="], + "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.79.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BVC2nsMzqQzRDPc5RhixkZ+m1p7iH4bxRRvqkbwDXX0PlQKm1BPy8J8cRjnAFafOq2QzI+BfO3vE8w2GZ3CBag=="], - "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.78.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-rpych6eJq6m9jDRypTEaPD1xysaEW5h9+xuxhGK/QhOg+/xaqPZrCrTNoIl/f3nEjuJeCEmstNDlrE9rJi/3/g=="], + "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.79.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-p6Lm+snmhGuLKL1+CpCV8L6ijkE/qJzK2H2jG9+eKJT0n31RbY4FLsdhexekgP3bLpw4Kgde+9DZuDZQ4yIInA=="], - "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.78.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IcMGrQT3QizkOESUJd5et+rOhVqSkNDfNik1cvrKDqIbzqx9KMtRswpFgkCuNTSwylCFLKhGUu8KmqY1ZnC0Dg=="], + "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.79.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-qDMm0dXZnoHyRqSL4N4xUq82T4sqK5cbKSjvd/dF/YbMUXc2R1wEPf+vmA5S0qUmi0nwXfNbjXBtZaIqzQLIMg=="], - "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.78.0", "", { "os": "linux", "cpu": "arm" }, "sha512-/uLdoJ0IXE6vo/0f0LKjinQAp+re+VMaCWaNT8ENIv2EOCkSsc8SGaflXAuW0Jua2dq5+GLVWm1NQK7P3UFSNQ=="], + "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.79.0", "", { "os": "linux", "cpu": "arm" }, "sha512-2od7s0nuKPzqyUZAWk9KkCyGg7eI9dwFPZg+20lB15fKFkVZ0c9ZFxqPfiBAyDTlTkh9stPI0t+JlPCqMbItVA=="], - "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.78.0", "", { "os": "linux", "cpu": "arm" }, "sha512-7xi4Wb/O8NRJhLoUXmDJMUVpNYvB5kefdhFU1Jb8rtae4QoXlTiLwI14X4YvAXVZLNZChP8m5qO9SQAlWQTbkQ=="], + "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.79.0", "", { "os": "linux", "cpu": "arm" }, "sha512-ZOQUjkzDnvlhSE3+tWC3YXx94MMl+sYMlwH+u1+YGApGHOJP/YAc8ZBRFOXZ6eOBmxtXAWuS/fBcdZr8qqNO1A=="], - "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.78.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-4hFW0+fVXa3OIh1Y4A5SPkmvI4wuuBSrCVKzOyE7PTjhc7yEqZ1pmvEEeS5Lj/MaqvegFxXyF33N+6jkehxdyg=="], + "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.79.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-lu158FR4nGqGeRS3BQvtG85wRgU/Fy4MD5Cxp1hzJXizGiLo6u2742wJSCDKh8cFcZntvX7fcxlq4mMmfryH1g=="], - "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.78.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-oC0mvsgBJjlMijSDEhx9KuvR9zYeHXceA9MjbuXB1F8NSR78Yj2unOBrstEvTVaq+pko+kuue6DajC00eqvTdg=="], + "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.79.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-mbpKQeE2aflTjddaHK7MP8KP/OFbUM++lt5M635ENM8IyIdK0jm2t9pb+2v9mVVIvhF6TqA4l7F79Pll1mi+uw=="], - "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.78.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-XAllT5SUZS+ohjuZ3/5S0cwe0r7eboiuigeStCZ5DXRYx/2KVM2UvQXvAfyzXEimtQjAB7cDQ2YxDe2Zl2WNQQ=="], + "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.79.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WpGNua7gaxaHnpSDeog2ji8IDHn/QLPl9LPzwkR/FvVv58vT5BcXjRXnU+wbu3N75cpeha8CdC7ho/U2OIsB4g=="], - "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.78.0", "", { "os": "linux", "cpu": "none" }, "sha512-trucMER/0QtecoXvc1y/UVqE3kwJipDwrx4oHfj+nNm3dq2zjP44WT0CfHNDPM3G1DXIkx/gY6lAD21NSCZVhA=="], + "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.79.0", "", { "os": "linux", "cpu": "none" }, "sha512-tK1E93A5LVzISg4ngpKJnfTs7EqtIUceGI7MQ4GyDjJiLi8wPCkEyKlj2xkyKWZ1yzkDJyLHTBJ5/iFWRdnJvg=="], - "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.78.0", "", { "os": "linux", "cpu": "none" }, "sha512-cm3O4F/HQbdzOUX5mKHqG5KDL6E5w0pnlZ+fbBy2rmLryPOowkuLagFHTopQsEIpjcaZoPOrL+BmmAytAG9HFg=="], + "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.79.0", "", { "os": "linux", "cpu": "none" }, "sha512-qhQvUIrngXivA2A9pQ+xPCychztn/5qUv7yS3gDwXv3w7Rag+eTeeXWmRyx+t7XsW5x6LuY/8AsTq36UgFIblg=="], - "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.78.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-33wRf6HqGNsybJ3qX4cGaQN2ODPxNmc1rMa0mrTmx3eFq1VzOnvQooi9bIGVYakW8a/wmqVx1mgsUm8R2xfTiw=="], + "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.79.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-sv6AaVgU/eE6u+6WFiQVDcPPwTxP6IJMSB9k701W2r/r6Tx465e8vPvVyRxquNH4Vy6KwRNu90mVbxXJN8+5gg=="], - "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.78.0", "", { "os": "linux", "cpu": "x64" }, "sha512-rRdISSYegj6VganMZ9tjRjijowfHJ09IZU01i0toBAqr6n5LEtwHq2IeS4FjW2RoskOHlb6efB26H5izYb3GEQ=="], + "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.79.0", "", { "os": "linux", "cpu": "x64" }, "sha512-iFZL02deziHslb3jEX9KdqlAkYoo4fGyotchKDzdfK1f5mxlIBeiQeHhvK3iFpuEJSB4ma/qeFn9oxPiwnhUPQ=="], - "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.78.0", "", { "os": "linux", "cpu": "x64" }, "sha512-GmsP4rW0xTL6u5CVdcDsaN5Fbc7hBc382Wmar1kttbnwSEviM+rSINKOMQ+UQ6iH+AGwC+8gaAiwu134Tgh6Lg=="], + "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.79.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3DtZR2raqObnh7wXZoFYFd0Fw7skBvcb3f7A+/lkEiDuh8hrE6vv9b/62Qxao1a9/OeHLw/FcXlXzgsW9wTRFg=="], - "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.78.0", "", { "os": "none", "cpu": "arm64" }, "sha512-sy9yeYuADc8a+n4TLBayzMCZiHPW78DcIFVpOXTmdKHWQeM9xe5uzkqIIZmi326D5hY9XVwacipEB1p7tQjPAg=="], + "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.79.0", "", { "os": "none", "cpu": "arm64" }, "sha512-Oatt4GuA1WJkqzk2ozx4HrWROOi7opV3AKDw/U8qDIqeTqzsjn5K2x3REJMNjU3/KU/Bkq96Zi3CknaiDTaC/Q=="], - "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.78.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-rjc2hF1KfMi8fZj1X/m3AmnHbdsF3rL0v6KQg0Uc880Yb2khjz+3U14sfdZ7jWTpRnN1m1NQa/TT7uU9lJWPrA=="], + "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.79.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-NAgZr9Qp8nIA9rpo0JEvwiabTF/2UVqBNnupBG9X4kxXcQoScJUTi+qHhvabb9s/thgj5wQ4XcIaJvb+ZMgoKw=="], - "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.78.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-zcuXFVrEFHIafRfkCQT8w/Xe41o07ozl/vwHq7p94vB29xVzsB0sZGYORU1jhcYKv3Lr0J3HbJ2T4fHH5rWmvA=="], + "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.79.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-+KyXjIvcpaXmWW/j9NNY5yWjrIVxaX18VyIheQy3jwc2GSYgpCr7MGI/HxIGQ/shAL5IWEKbhsqoMpAO5Stiog=="], - "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.78.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Sb5ocmLSuYeOuXd+CFOToGKp/gjXUEWDnvIGwhnh8aq8wY4TMmEnKnvbogSW7RdMZv77JSARduS7/gv+khYEjA=="], + "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.79.0", "", { "os": "win32", "cpu": "x64" }, "sha512-mEelcCMMBS57sIXh2veGMNy+pQwuGtcMxHxGIZWQ5Ba9pJ5jCCUFOZB9E2JhBaxGsURe+WGe0zJp4RVre52gpQ=="], "@pagefind/darwin-arm64": ["@pagefind/darwin-arm64@1.5.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ=="], @@ -2552,7 +2552,7 @@ "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - "adamantite": ["adamantite@0.36.0", "", { "dependencies": { "@clack/prompts": "1.7.0", "@effect/platform-node": "4.0.0-rc.108", "defu": "6.1.7", "effect": "4.0.0-rc.108", "esrap": "2.3.2", "jsonc-parser": "3.3.1", "nypm": "0.6.9", "oxc-parser": "^0.144.0" }, "peerDependencies": { "typescript": ">=7" }, "bin": { "adamantite": "bin/adamantite" } }, "sha512-U0LfhmL7QrXIspcel6XP8++xzyLn22RpfmNANI5fYa7XA5QVVRUPYcmbftyMjMDtmdEZ4yKTaXENj+LXGgn3ag=="], + "adamantite": ["adamantite@0.37.0", "", { "dependencies": { "@clack/prompts": "1.7.0", "@effect/platform-node": "4.0.0-rc.108", "defu": "6.1.7", "effect": "4.0.0-rc.108", "esrap": "2.3.2", "jsonc-parser": "3.3.1", "nypm": "0.6.9", "oxc-parser": "^0.144.0" }, "peerDependencies": { "typescript": ">=7" }, "bin": { "adamantite": "bin/adamantite" } }, "sha512-H6ms2StVVbajOZt6QWPOyWiuWt1bN44stlBoaTR+Kq7IxxdeXBsPLsVg6lKSjyu+F8qxkmqUgrk5HyAG4berIA=="], "addons-linter": ["addons-linter@10.8.0", "", { "dependencies": { "@fluent/syntax": "0.19.0", "@fregante/relaxed-json": "2.0.0", "@mdn/browser-compat-data": "8.0.4", "addons-moz-compare": "1.3.0", "addons-scanner-utils": "15.4.0", "ajv": "8.20.0", "cheerio": "1.2.0", "columnify": "1.6.0", "common-tags": "1.8.2", "css-tree": "3.2.1", "deepmerge": "4.3.1", "eslint": "9.39.4", "eslint-plugin-no-unsanitized": "4.1.5", "eslint-visitor-keys": "5.0.1", "espree": "11.2.0", "esprima": "4.0.1", "fast-json-patch": "3.1.1", "image-size": "2.0.2", "json-merge-patch": "1.0.2", "pino": "10.3.1", "semver": "7.8.5", "source-map-support": "0.5.21", "upath": "3.0.7", "yargs": "17.7.2", "yauzl": "3.4.0" }, "bin": { "addons-linter": "bin/addons-linter" } }, "sha512-AotpGWlp5FrmUIH8kjyYjQvCmthnSqPLpw7kOEl7DfoAWEndOCV/ONuekAVM8QObiei802RyyIClEaekA51n5g=="], @@ -4206,7 +4206,7 @@ "oxfmt": ["oxfmt@0.63.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.63.0", "@oxfmt/binding-android-arm64": "0.63.0", "@oxfmt/binding-darwin-arm64": "0.63.0", "@oxfmt/binding-darwin-x64": "0.63.0", "@oxfmt/binding-freebsd-x64": "0.63.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.63.0", "@oxfmt/binding-linux-arm-musleabihf": "0.63.0", "@oxfmt/binding-linux-arm64-gnu": "0.63.0", "@oxfmt/binding-linux-arm64-musl": "0.63.0", "@oxfmt/binding-linux-ppc64-gnu": "0.63.0", "@oxfmt/binding-linux-riscv64-gnu": "0.63.0", "@oxfmt/binding-linux-riscv64-musl": "0.63.0", "@oxfmt/binding-linux-s390x-gnu": "0.63.0", "@oxfmt/binding-linux-x64-gnu": "0.63.0", "@oxfmt/binding-linux-x64-musl": "0.63.0", "@oxfmt/binding-openharmony-arm64": "0.63.0", "@oxfmt/binding-win32-arm64-msvc": "0.63.0", "@oxfmt/binding-win32-ia32-msvc": "0.63.0", "@oxfmt/binding-win32-x64-msvc": "0.63.0" }, "peerDependencies": { "svelte": "^5.0.0", "vite-plus": "*" }, "optionalPeers": ["svelte", "vite-plus"], "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-kgdDwv35wvVf6554U2Ab8Jnd0zTM+TsEQWwaB70RAjK3gICFAFGO+2Hd3Be27GMoXj3XRL9IKSNRVl7KBQL6iw=="], - "oxlint": ["oxlint@1.78.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.78.0", "@oxlint/binding-android-arm64": "1.78.0", "@oxlint/binding-darwin-arm64": "1.78.0", "@oxlint/binding-darwin-x64": "1.78.0", "@oxlint/binding-freebsd-x64": "1.78.0", "@oxlint/binding-linux-arm-gnueabihf": "1.78.0", "@oxlint/binding-linux-arm-musleabihf": "1.78.0", "@oxlint/binding-linux-arm64-gnu": "1.78.0", "@oxlint/binding-linux-arm64-musl": "1.78.0", "@oxlint/binding-linux-ppc64-gnu": "1.78.0", "@oxlint/binding-linux-riscv64-gnu": "1.78.0", "@oxlint/binding-linux-riscv64-musl": "1.78.0", "@oxlint/binding-linux-s390x-gnu": "1.78.0", "@oxlint/binding-linux-x64-gnu": "1.78.0", "@oxlint/binding-linux-x64-musl": "1.78.0", "@oxlint/binding-openharmony-arm64": "1.78.0", "@oxlint/binding-win32-arm64-msvc": "1.78.0", "@oxlint/binding-win32-ia32-msvc": "1.78.0", "@oxlint/binding-win32-x64-msvc": "1.78.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2001", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-QgQePuxIqKOzo1KSjG2EnITEeWvWnKAm77eq8nrMtf6AGoA+zyGc4PFYtDNJSD25g/ibOwfQ851hZ4/SPkMVoA=="], + "oxlint": ["oxlint@1.79.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.79.0", "@oxlint/binding-android-arm64": "1.79.0", "@oxlint/binding-darwin-arm64": "1.79.0", "@oxlint/binding-darwin-x64": "1.79.0", "@oxlint/binding-freebsd-x64": "1.79.0", "@oxlint/binding-linux-arm-gnueabihf": "1.79.0", "@oxlint/binding-linux-arm-musleabihf": "1.79.0", "@oxlint/binding-linux-arm64-gnu": "1.79.0", "@oxlint/binding-linux-arm64-musl": "1.79.0", "@oxlint/binding-linux-ppc64-gnu": "1.79.0", "@oxlint/binding-linux-riscv64-gnu": "1.79.0", "@oxlint/binding-linux-riscv64-musl": "1.79.0", "@oxlint/binding-linux-s390x-gnu": "1.79.0", "@oxlint/binding-linux-x64-gnu": "1.79.0", "@oxlint/binding-linux-x64-musl": "1.79.0", "@oxlint/binding-openharmony-arm64": "1.79.0", "@oxlint/binding-win32-arm64-msvc": "1.79.0", "@oxlint/binding-win32-ia32-msvc": "1.79.0", "@oxlint/binding-win32-x64-msvc": "1.79.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2001", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-hVJ9hq9m2unPS+Of4eJJgCPdIeCC+3DHEUX3tkmrPJr3OK2hz7PhXwgC+ZP71ZcYu8cCDEtQrqLxWNvxBppBVg=="], "oxlint-tsgolint": ["oxlint-tsgolint@7.0.2001", "", { "optionalDependencies": { "@oxlint-tsgolint/darwin-arm64": "7.0.2001", "@oxlint-tsgolint/darwin-x64": "7.0.2001", "@oxlint-tsgolint/linux-arm64": "7.0.2001", "@oxlint-tsgolint/linux-x64": "7.0.2001", "@oxlint-tsgolint/win32-arm64": "7.0.2001", "@oxlint-tsgolint/win32-x64": "7.0.2001" }, "bin": { "tsgolint": "./bin/tsgolint.js" } }, "sha512-KjK/XLcXr1DSyonKhsuFqJRiuKqcyG9j3LJ8nkOsrLzGvodBPqzHOKauy10asLMDI0sUpvb+1sxlzff3udZvfg=="], diff --git a/knip.config.ts b/knip.config.ts index d79353ee..24002c87 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -34,6 +34,7 @@ const config: KnipConfig = { }, "apps/app": { entry: "src/routeTree.gen.{ts,js}", + vite: false, }, "apps/desktop": { entry: [ @@ -48,11 +49,13 @@ const config: KnipConfig = { project: "src/**/*.{astro,css,js,jsx,mdx,ts,tsx}", }, "apps/extension": { - entry: ["src/entrypoints/**/*.{ts,tsx}", "src/shared/env.generated.ts"], + entry: ["src/entrypoints/**/*.{ts,tsx}", "src/shared/env.generated.ts", "wxt.config.ts"], project: "src/**/*.{css,js,jsx,ts,tsx}", + wxt: false, }, "apps/mobile": { entry: "src/shared/env.generated.ts", + ignoreDependencies: ["lucide-react-native"], project: "src/**/*.{css,js,jsx,ts,tsx}", }, "apps/web": { diff --git a/oxlint.config.ts b/oxlint.config.ts index 5b7dbc23..8815bb3a 100644 --- a/oxlint.config.ts +++ b/oxlint.config.ts @@ -26,6 +26,7 @@ export default defineConfig({ rules: { "import/unambiguous": "off", "typescript/no-require-imports": "off", + "typescript/no-var-requires": "off", "unicorn/prefer-module": "off", }, }, diff --git a/package.json b/package.json index ca8b5b96..0ab97edb 100644 --- a/package.json +++ b/package.json @@ -41,12 +41,12 @@ "devDependencies": { "@turbo/gen": "2.10.7", "@types/bun": "1.3.14", - "adamantite": "0.36.0", + "adamantite": "0.37.0", "citty": "0.2.2", "consola": "3.4.2", "knip": "6.32.2", "oxfmt": "0.63.0", - "oxlint": "1.78.0", + "oxlint": "1.79.0", "oxlint-tsgolint": "7.0.2001", "portless": "0.15.5", "sherif": "1.13.0", diff --git a/packages/utils/src/schema.ts b/packages/utils/src/schema.ts index 8a7b4700..7e5085a6 100644 --- a/packages/utils/src/schema.ts +++ b/packages/utils/src/schema.ts @@ -18,6 +18,7 @@ export function env() { } export function branded(_: T) { + void _ return z.string().brand() } diff --git a/scripts/template/add.ts b/scripts/template/add.ts index 8584d206..b5e2e885 100644 --- a/scripts/template/add.ts +++ b/scripts/template/add.ts @@ -11,6 +11,7 @@ import { readJson, runCommand, TEMPLATE_SCOPE, + type JsonObject, type WorkspaceKind, } from "./shared" @@ -64,7 +65,7 @@ async function copyTemplateWorkspace(rootDir: string, workspace: TemplateWorkspa async function getMissingTemplateDependencies( rootDir: string, - packageJson: Record, + packageJson: JsonObject, visited: Set ) { const prefix = getScopePrefix(TEMPLATE_SCOPE) diff --git a/scripts/template/setup.ts b/scripts/template/setup.ts index ddead0e7..76e97e8a 100644 --- a/scripts/template/setup.ts +++ b/scripts/template/setup.ts @@ -5,6 +5,9 @@ import consola from "consola" import { renameProject } from "./rename" import { getDependencyNames, + getJsonObject, + getJsonString, + getJsonStringArray, getProjectScope, getWorkspaces, normalizeScope, @@ -24,12 +27,14 @@ type TemplateStamp = { } async function promptForWorkspaceNames(kind: "app" | "package", names: string[]) { - const selected = (await consola.prompt(`Select ${kind}s to keep`, { + const selected = await consola.prompt(`Select ${kind}s to keep`, { cancel: "reject", initial: names, - options: names.map((name) => ({ label: name, value: name })), + options: names, type: "multiselect", - })) as unknown as string[] + }) + if (!Array.isArray(selected) || !selected.every((value) => value.constructor === String)) + throw new Error(`Expected the ${kind} selection to contain workspace names.`) return selected } @@ -57,12 +62,15 @@ async function getCommit() { throw new Error(`Could not fetch the template commit. GitHub returned ${response.status}.`) } - const body = (await response.json()) as { sha?: unknown } - if (typeof body.sha !== "string") { + const body: unknown = await response.json() + if (body === null || !(body instanceof Object) || !("sha" in body)) { throw new Error("Could not fetch the template commit. GitHub did not return a commit SHA.") } + const sha = body.sha + if (sha?.constructor !== String) + throw new Error("Could not fetch the template commit. GitHub did not return a commit SHA.") - return body.sha + return String(sha) } catch (error) { throw new Error("Could not fetch the template commit.", { cause: error }) } @@ -119,14 +127,14 @@ async function expandWorkspaceSelection( const workspaces: WorkspaceWithDependencies[] = await Promise.all( workspaceEntries.map(async (workspace) => { const packageJson = await readJson(join(workspace.directory, "package.json")) - const packageName = packageJson.name + const packageName = getJsonString(packageJson, "name") return { dependencies: getDependencyNames(packageJson), directory: workspace.directory, kind: workspace.kind, name: workspace.name, - packageName: typeof packageName === "string" ? packageName : "", + packageName: packageName ?? "", } }) ) @@ -175,14 +183,10 @@ async function pruneWorkspaces(rootDir: string, workspaces: Workspace[], selecte async function cleanupTemplateFiles(rootDir: string) { const packageJsonPath = join(rootDir, "package.json") const packageJson = await readJson(packageJsonPath) - const init = packageJson.init as { cleanupPaths?: unknown } | undefined - const cleanupPaths = Array.isArray(init?.cleanupPaths) ? init.cleanupPaths : [] + const init = getJsonObject(packageJson, "init") + const cleanupPaths = init ? (getJsonStringArray(init, "cleanupPaths") ?? []) : [] - await Promise.all( - cleanupPaths - .filter((path): path is string => typeof path === "string") - .map((path) => removePath(rootDir, path)) - ) + await Promise.all(cleanupPaths.map((path) => removePath(rootDir, path))) delete packageJson["bun-create"] delete packageJson.init @@ -250,7 +254,7 @@ export default defineCommand({ const apps = await getWorkspaces(rootDir, "app") const packages = await getWorkspaces(rootDir, "package") const rootPackage = await readJson(join(rootDir, "package.json")) - const defaultName = typeof rootPackage.name === "string" ? rootPackage.name : "project" + const defaultName = getJsonString(rootPackage, "name") ?? "project" const sourceScope = await getProjectScope(rootDir).catch(() => TEMPLATE_SCOPE) const selectedApps = args["keep-apps"]?.split(",").filter(Boolean) const selectedPackages = args["keep-packages"]?.split(",").filter(Boolean) From 1820cd3c91267f608ff1daba94006fa1da47a08c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adel=20Rodr=C3=ADguez?= Date: Sun, 23 Aug 2026 11:07:02 -0400 Subject: [PATCH 3/4] fix: handle missing git output in docs build --- apps/docs/src/shared/components/last-updated.astro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/src/shared/components/last-updated.astro b/apps/docs/src/shared/components/last-updated.astro index 20844658..f5630cef 100644 --- a/apps/docs/src/shared/components/last-updated.astro +++ b/apps/docs/src/shared/components/last-updated.astro @@ -21,7 +21,7 @@ function getLastUpdated(filePath: string) { encoding: "utf8", }, ); - const timestamp = Number(result.stdout.trim()); + const timestamp = Number(result.stdout?.trim() ?? Number.NaN); return result.status === 0 && Number.isFinite(timestamp) && timestamp > 0 ? new Date(timestamp * 1000) : undefined; From e6f7f55dab56a43a8f0f409335c5b2894f5d1480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adel=20Rodr=C3=ADguez?= Date: Sun, 23 Aug 2026 11:11:49 -0400 Subject: [PATCH 4/4] fix: declare web utils dependency --- apps/web/package.json | 1 + bun.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/web/package.json b/apps/web/package.json index 936fbac4..0ed824a5 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -29,6 +29,7 @@ "@fontsource-variable/geist-mono": "5.3.0", "@init/analytics": "workspace:*", "@init/ui": "workspace:*", + "@init/utils": "workspace:*", "astro": "7.1.4", "react": "19.2.3", "react-dom": "19.2.3", diff --git a/bun.lock b/bun.lock index 8c818146..ef8a0074 100644 --- a/bun.lock +++ b/bun.lock @@ -256,6 +256,7 @@ "@fontsource-variable/geist-mono": "5.3.0", "@init/analytics": "workspace:*", "@init/ui": "workspace:*", + "@init/utils": "workspace:*", "astro": "7.1.4", "react": "19.2.3", "react-dom": "19.2.3",