diff --git a/.github/workflow_scripts/changed.mjs b/.github/workflow_scripts/changed.mjs new file mode 100755 index 00000000..5a54debf --- /dev/null +++ b/.github/workflow_scripts/changed.mjs @@ -0,0 +1,153 @@ +#!/usr/bin/env node + +import os from "node:os"; +import path from "node:path"; + +import PackageJson from "@npmcli/package-json"; +import { execa } from "execa"; +import fse from "fs-extra"; +import PQueue from "p-queue"; + +const concurrency = os.cpus().length; + +console.log({ concurrency }); + +const installQueue = new PQueue({ concurrency, autoStart: false }); +const buildQueue = new PQueue({ concurrency, autoStart: false }); +const typecheckQueue = new PQueue({ concurrency, autoStart: false }); + +const TO_IGNORE = new Set([ + ".git", + ".github", + ".gitignore", + "package.json", + "prettier.config.js", + "yarn.lock", +]); + +const yarnExamples = new Set(["yarn-pnp"]); +const pnpmExamples = new Set([]); + +let examples = []; + +if (process.env.CI) { + const { stderr, stdout, exitCode } = await execa( + "git", + ["--no-pager", "diff", "--name-only", "HEAD~1"], + { cwd: process.cwd() } + ); + + if (exitCode !== 0) { + console.error(stderr); + process.exit(exitCode); + } + + const files = stdout.split("\n"); + const dirs = files.map((f) => f.split("/").at(0)); + examples = [...new Set(dirs)].filter((d) => !TO_IGNORE.has(d)); +} else { + const entries = await fse.readdir(process.cwd(), { withFileTypes: true }); + examples = entries + .filter((entry) => entry.isDirectory()) + .filter((entry) => !TO_IGNORE.has(entry.name)) + .map((entry) => entry.name) + .filter((entry) => fse.existsSync(path.join(entry, "package.json"))); +} + +const list = new Intl.ListFormat("en", { style: "long", type: "conjunction" }); + +console.log(`Testing changed examples: ${list.format(examples)}`); + +for (const example of examples) { + const pkgJson = await PackageJson.load(example); + + /** @type {import('execa').Options} */ + const options = { cwd: example, reject: false }; + + const pm = pnpmExamples.has(example) + ? "pnpm" + : yarnExamples.has(example) + ? "yarn" + : "npm"; + + installQueue.add(async () => { + const hasSetup = !!pkgJson.content.scripts?.__setup; + + if (hasSetup) { + console.log("🔧\u00A0Running setup script for", example); + const setupResult = await execa(pm, ["run", "__setup"], options); + if (setupResult.exitCode) { + console.error(setupResult.stderr); + throw new Error(`Error running setup script for ${example}`); + } + } + + console.log(`📥\u00A0Installing ${example} with "${pm}"`); + const installResult = await execa( + pm, + pm === "npm" + ? ["install", "--silent", "--legacy-peer-deps"] + : ["install", "--silent"], + options + ); + + if (installResult.exitCode) { + console.error(installResult.stderr); + throw new Error(`Error installing ${example}`); + } + + const hasPrisma = fse.existsSync( + path.join(example, "prisma", "schema.prisma") + ); + + if (hasPrisma) { + console.log("Generating prisma types for", example); + const prismaGenerateCommand = await execa( + "npx", + ["prisma", "generate"], + options + ); + + if (prismaGenerateCommand.exitCode) { + console.error(prismaGenerateCommand.stderr); + throw new Error(`Error generating prisma types for ${example}`); + } + } + }); + + buildQueue.add(async () => { + console.log(`📦\u00A0Building ${example}`); + const buildResult = await execa(pm, ["run", "build"], options); + + if (buildResult.exitCode) { + console.error(buildResult.stderr); + throw new Error(`Error building ${example}`); + } + }); + + typecheckQueue.add(async () => { + console.log(`🕵️\u00A0Typechecking ${example}`); + const typecheckResult = await execa(pm, ["run", "typecheck"], options); + + if (typecheckResult.exitCode) { + console.error(typecheckResult.stderr); + throw new Error(`Error typechecking ${example}`); + } + }); +} + +installQueue.start(); + +installQueue.on("empty", () => { + console.log(`installQueue is complete, moving on to buildQueue`); + return buildQueue.start(); +}); + +buildQueue.on("empty", () => { + console.log(`buildQueue is complete, moving on to typecheckQueue`); + return typecheckQueue.start(); +}); + +installQueue.on("error", (error) => console.error("🚨", error)); +buildQueue.on("error", (error) => console.error("🚨", error)); +typecheckQueue.on("error", (error) => console.error("🚨", error)); diff --git a/.github/workflows/changed.yml b/.github/workflows/changed.yml new file mode 100644 index 00000000..c8583453 --- /dev/null +++ b/.github/workflows/changed.yml @@ -0,0 +1,36 @@ +name: 📦 Validate examples + +on: + push: + branches: + - main + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + typecheck: + name: 📦 Validate examples + if: github.repository == 'remix-run/examples' + runs-on: ubuntu-latest + + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v3 + with: + # we only need to compare the current commit with the previous one + fetch-depth: 2 + + - name: ⎔ Setup node + uses: actions/setup-node@v3 + with: + node-version-file: ".nvmrc" + cache: "yarn" + + - name: 📥 Install dependencies + run: yarn --frozen-lockfile + + - name: 📦 Validate examples + run: node ./.github/workflow_scripts/changed.mjs diff --git a/.gitignore b/.gitignore index b57c8544..fde832d3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ yarn.lock pnpm-lock.yaml pnpm-lock.yml -!./yarn.lock +!/yarn.lock +!yarn-pnp/yarn.lock diff --git a/__template/tsconfig.json b/__template/tsconfig.json index 20f8a386..29d8538a 100644 --- a/__template/tsconfig.json +++ b/__template/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/_official-blog-tutorial/tsconfig.json b/_official-blog-tutorial/tsconfig.json index 9bacef83..39808a96 100644 --- a/_official-blog-tutorial/tsconfig.json +++ b/_official-blog-tutorial/tsconfig.json @@ -2,6 +2,8 @@ "exclude": ["./cypress", "./cypress.config.ts"], "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"], "compilerOptions": { + "allowJs": true, + "forceConsistentCasingInFileNames": true, "lib": ["DOM", "DOM.Iterable", "ES2019"], "types": ["vitest/globals"], "isolatedModules": true, diff --git a/_official-jokes/app/routes/login.tsx b/_official-jokes/app/routes/login.tsx index 16d14d8a..f3fd0e35 100644 --- a/_official-jokes/app/routes/login.tsx +++ b/_official-jokes/app/routes/login.tsx @@ -36,11 +36,10 @@ function validatePassword(password: string) { } } -function validateUrl(url: string) { +function validateUrl(url: FormDataEntryValue | null) { + if (typeof url !== "string") return "/jokes"; const urls = ["/jokes", "/", "https://remix.run"]; - if (urls.includes(url)) { - return url; - } + if (urls.includes(url)) return url; return "/jokes"; } @@ -49,9 +48,7 @@ export const action = async ({ request }: ActionArgs) => { const loginType = form.get("loginType"); const password = form.get("password"); const username = form.get("username"); - const redirectTo = validateUrl( - (form.get("redirectTo") as string) || "/jokes" - ); + const redirectTo = validateUrl(form.get("redirectTo")); if ( typeof loginType !== "string" || typeof password !== "string" || diff --git a/_official-jokes/tsconfig.json b/_official-jokes/tsconfig.json index 20f8a386..29d8538a 100644 --- a/_official-jokes/tsconfig.json +++ b/_official-jokes/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/_official-realtime-app/tsconfig.json b/_official-realtime-app/tsconfig.json index 20f8a386..29d8538a 100644 --- a/_official-realtime-app/tsconfig.json +++ b/_official-realtime-app/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/_official-tutorial/app/data.ts b/_official-tutorial/app/data.ts index 05f6649d..c366c152 100644 --- a/_official-tutorial/app/data.ts +++ b/_official-tutorial/app/data.ts @@ -3,7 +3,6 @@ //////////////////////////////////////////////////////////////////////////////// import { matchSorter } from "match-sorter"; -// @ts-ignore - no types, but it's a tiny function import sortBy from "sort-by"; import invariant from "tiny-invariant"; diff --git a/_official-tutorial/app/root.tsx b/_official-tutorial/app/root.tsx index 24f29d91..3aad5c14 100644 --- a/_official-tutorial/app/root.tsx +++ b/_official-tutorial/app/root.tsx @@ -176,10 +176,8 @@ function OptimisticFavorite({ contact }: { contact: ContactRecord }) { // Now check if there are any pending fetchers that are changing this contact for (const fetcher of fetchers) { - // @ts-expect-error https://github.com/remix-run/remix/pull/5476 if (fetcher.formAction === `/contacts/${contact.id}`) { // Ask for the optimistic version of the data - // @ts-expect-error https://github.com/remix-run/remix/pull/5476 isFavorite = fetcher.formData.get("favorite") === "true"; } } diff --git a/_official-tutorial/app/routes/contacts.$contactId.tsx b/_official-tutorial/app/routes/contacts.$contactId.tsx index 08b32b94..a34d0dba 100644 --- a/_official-tutorial/app/routes/contacts.$contactId.tsx +++ b/_official-tutorial/app/routes/contacts.$contactId.tsx @@ -94,9 +94,7 @@ export default function Contact() { function Favorite({ contact }: { contact: ContactRecord }) { const fetcher = useFetcher(); let favorite = contact.favorite; - // @ts-expect-error if (fetcher.formData) { - // @ts-expect-error favorite = fetcher.formData.get("favorite") === "true"; } return ( diff --git a/_official-tutorial/package.json b/_official-tutorial/package.json index 51c10402..e85ea708 100644 --- a/_official-tutorial/package.json +++ b/_official-tutorial/package.json @@ -23,6 +23,7 @@ "@remix-run/eslint-config": "~1.14.2", "@types/react": "^18.0.25", "@types/react-dom": "^18.0.8", + "@types/sort-by": "^1.2.0", "eslint": "^8.27.0", "typescript": "^4.8.4" }, diff --git a/_official-tutorial/tsconfig.json b/_official-tutorial/tsconfig.json index 20f8a386..0f5de0fa 100644 --- a/_official-tutorial/tsconfig.json +++ b/_official-tutorial/tsconfig.json @@ -11,6 +11,7 @@ "strict": true, "allowJs": true, "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, "baseUrl": ".", "paths": { "~/*": ["./app/*"] diff --git a/basic/app/routes/demos/params/$id.tsx b/basic/app/routes/demos/params/$id.tsx index 0c16fb95..59a16637 100644 --- a/basic/app/routes/demos/params/$id.tsx +++ b/basic/app/routes/demos/params/$id.tsx @@ -29,6 +29,7 @@ export const loader = async ({ params }: LoaderArgs) => { // Sometimes your code just blows up and you never anticipated it. Remix will // automatically catch it and send the UI to the error boundary. if (params.id === "kaboom") { + // @ts-expect-error - this is a deliberate error to test the error boundary lol(); } diff --git a/basic/tsconfig.json b/basic/tsconfig.json index 20f8a386..29d8538a 100644 --- a/basic/tsconfig.json +++ b/basic/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/bullmq-task-queue/tsconfig.json b/bullmq-task-queue/tsconfig.json index 20f8a386..29d8538a 100644 --- a/bullmq-task-queue/tsconfig.json +++ b/bullmq-task-queue/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/catch-boundary/tsconfig.json b/catch-boundary/tsconfig.json index 20f8a386..29d8538a 100644 --- a/catch-boundary/tsconfig.json +++ b/catch-boundary/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/chakra-ui/tsconfig.json b/chakra-ui/tsconfig.json index 20f8a386..29d8538a 100644 --- a/chakra-ui/tsconfig.json +++ b/chakra-ui/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/client-only-components/app/components/complex-component.tsx b/client-only-components/app/components/complex-component.tsx index c1ce4cc1..a6270f5e 100644 --- a/client-only-components/app/components/complex-component.tsx +++ b/client-only-components/app/components/complex-component.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; export function ComplexComponent() { - const [count, setCount] = useState(() => { + const [count, setCount] = useState(() => { const stored = localStorage.getItem("count"); if (!stored) return 0; return JSON.parse(stored); diff --git a/client-only-components/tsconfig.json b/client-only-components/tsconfig.json index 20f8a386..29d8538a 100644 --- a/client-only-components/tsconfig.json +++ b/client-only-components/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/client-side-validation/tsconfig.json b/client-side-validation/tsconfig.json index 20f8a386..29d8538a 100644 --- a/client-side-validation/tsconfig.json +++ b/client-side-validation/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/collected-notes/tsconfig.json b/collected-notes/tsconfig.json index 20f8a386..29d8538a 100644 --- a/collected-notes/tsconfig.json +++ b/collected-notes/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/combobox-resource-route/app/routes/index.tsx b/combobox-resource-route/app/routes/index.tsx index a60dd33b..90546b7e 100644 --- a/combobox-resource-route/app/routes/index.tsx +++ b/combobox-resource-route/app/routes/index.tsx @@ -6,7 +6,7 @@ import { ComboboxPopover, } from "@reach/combobox"; import comboboxStyles from "@reach/combobox/styles.css"; -import type { LinksFunction } from "@remix-run/react"; +import type { LinksFunction } from "@remix-run/node"; import { Form, useFetcher, useSearchParams } from "@remix-run/react"; import type { Lang } from "~/models/langs"; diff --git a/combobox-resource-route/package.json b/combobox-resource-route/package.json index 89075dc0..649c1215 100644 --- a/combobox-resource-route/package.json +++ b/combobox-resource-route/package.json @@ -12,8 +12,8 @@ "@remix-run/node": "~1.14.2", "@remix-run/react": "~1.14.2", "@remix-run/serve": "~1.14.2", - "match-sorter": "^6.3.1", "isbot": "^3.6.5", + "match-sorter": "^6.3.1", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/combobox-resource-route/tsconfig.json b/combobox-resource-route/tsconfig.json index 20f8a386..29d8538a 100644 --- a/combobox-resource-route/tsconfig.json +++ b/combobox-resource-route/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/dark-mode/tsconfig.json b/dark-mode/tsconfig.json index 20f8a386..29d8538a 100644 --- a/dark-mode/tsconfig.json +++ b/dark-mode/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/dataloader/app/data.server.ts b/dataloader/app/data.server.ts index 7b0efdbe..6efc7929 100644 --- a/dataloader/app/data.server.ts +++ b/dataloader/app/data.server.ts @@ -1,4 +1,4 @@ -interface User { +export interface User { id: string; email: string; name: string; diff --git a/dataloader/app/loaders/userLoader.ts b/dataloader/app/loaders/userLoader.ts index 5cc506f4..b9388a8c 100644 --- a/dataloader/app/loaders/userLoader.ts +++ b/dataloader/app/loaders/userLoader.ts @@ -1,10 +1,11 @@ +import type { DataFunctionArgs } from "@remix-run/node"; import DataLoader from "dataloader"; import { db } from "~/data.server"; export const createUsersByIdLoader = () => new DataLoader(async (ids: Readonly) => { - const users = await db.user.findMany({ + const users = db.user.findMany({ where: { id: { in: ids, @@ -14,3 +15,11 @@ export const createUsersByIdLoader = () => const userMap = new Map(users.map((user) => [user.id, user])); return ids.map((id) => userMap.get(id) ?? null); }); + +export interface DataLoaderArgs extends DataFunctionArgs { + context: { + loaders: { + usersById: ReturnType; + }; + }; +} diff --git a/dataloader/app/routes/users.tsx b/dataloader/app/routes/users.tsx index 62e644ab..4cd7f39e 100644 --- a/dataloader/app/routes/users.tsx +++ b/dataloader/app/routes/users.tsx @@ -1,13 +1,15 @@ -import type { LoaderArgs } from "@remix-run/node"; import { json } from "@remix-run/node"; import { Outlet, useLoaderData } from "@remix-run/react"; -export const loader = async ({ context }: LoaderArgs) => { +import type { User } from "~/data.server"; +import type { DataLoaderArgs } from "~/loaders/userLoader"; + +export const loader = async ({ context }: DataLoaderArgs) => { const users = await context.loaders.usersById.loadMany([ "ef3fcb93-0623-4d10-adbf-4dd865d6688c", "2cbad877-2da6-422d-baa6-c6a96a9e085f", ]); - return json({ users }); + return json({ users: users.filter((u): u is User => !!u) }); }; export default function UserNames() { diff --git a/dataloader/app/routes/users/index.tsx b/dataloader/app/routes/users/index.tsx index bdd504e1..2cda57ea 100644 --- a/dataloader/app/routes/users/index.tsx +++ b/dataloader/app/routes/users/index.tsx @@ -1,8 +1,9 @@ -import type { LoaderArgs } from "@remix-run/node"; import { json } from "@remix-run/node"; import { useLoaderData } from "@remix-run/react"; -export const loader = async ({ context }: LoaderArgs) => { +import type { DataLoaderArgs } from "~/loaders/userLoader"; + +export const loader = async ({ context }: DataLoaderArgs) => { /* * For demo purposes: * Batching & caching also works with multiple calls to `DataLoader#load` diff --git a/dataloader/package.json b/dataloader/package.json index 6eb14f26..90d18834 100644 --- a/dataloader/package.json +++ b/dataloader/package.json @@ -16,14 +16,17 @@ "cross-env": "^7.0.3", "dataloader": "^2.0.0", "express": "^4.17.3", - "morgan": "^1.10.0", "isbot": "^3.6.5", + "morgan": "^1.10.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@remix-run/dev": "~1.14.2", "@remix-run/eslint-config": "~1.14.2", + "@types/compression": "1.7.2", + "@types/express": "4.17.15", + "@types/morgan": "1.9.3", "@types/react": "^18.0.25", "@types/react-dom": "^18.0.8", "esbuild-register": "^3.3.2", diff --git a/dataloader/server/index.ts b/dataloader/server/index.ts index 8d7ad5d9..058a2164 100644 --- a/dataloader/server/index.ts +++ b/dataloader/server/index.ts @@ -1,11 +1,11 @@ -const path = require("path"); +import path from "node:path"; -const { createRequestHandler } = require("@remix-run/express"); -const compression = require("compression"); -const express = require("express"); -const morgan = require("morgan"); +import { createRequestHandler } from "@remix-run/express"; +import compression from "compression"; +import express from "express"; +import morgan from "morgan"; -const { createUsersByIdLoader } = require("../app/loaders/userLoader"); +import { createUsersByIdLoader } from "../app/loaders/userLoader"; const MODE = process.env.NODE_ENV; const BUILD_DIR = path.join(process.cwd(), "server/build"); diff --git a/dataloader/tsconfig.json b/dataloader/tsconfig.json index 0700df63..29d8538a 100644 --- a/dataloader/tsconfig.json +++ b/dataloader/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx", "server/index.ts"], + "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"], "compilerOptions": { "lib": ["DOM", "DOM.Iterable", "ES2019"], "isolatedModules": true, @@ -9,10 +9,13 @@ "resolveJsonModule": true, "target": "ES2019", "strict": true, + "allowJs": true, + "forceConsistentCasingInFileNames": true, "baseUrl": ".", "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/emotion/app/entry.server.tsx b/emotion/app/entry.server.tsx index b4a28d3f..5d564e75 100644 --- a/emotion/app/entry.server.tsx +++ b/emotion/app/entry.server.tsx @@ -19,6 +19,7 @@ export default function handleRequest( const html = renderToString( + {/* @ts-expect-error */} @@ -29,6 +30,7 @@ export default function handleRequest( const markup = renderToString( + {/* @ts-expect-error */} diff --git a/emotion/tsconfig.json b/emotion/tsconfig.json index 20f8a386..29d8538a 100644 --- a/emotion/tsconfig.json +++ b/emotion/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/file-and-cloudinary-upload/app/routes/cloudinary-upload.tsx b/file-and-cloudinary-upload/app/routes/cloudinary-upload.tsx index 8fef5a8c..31bf8bc0 100644 --- a/file-and-cloudinary-upload/app/routes/cloudinary-upload.tsx +++ b/file-and-cloudinary-upload/app/routes/cloudinary-upload.tsx @@ -33,7 +33,7 @@ export const action = async ({ request }: ActionArgs) => { }; export default function Index() { - const data = useActionData(); + const actionData = useActionData(); return ( <> @@ -44,12 +44,15 @@ export default function Index() { - {data?.error ?

{data.error}

: null} + {actionData && "error" in actionData ?

{actionData.error}

: null} - {data?.imgSrc ? ( + {actionData && "imgSrc" in actionData ? ( <>

uploaded image

- {data.imgDesc + {actionData.imgDesc ) : null} diff --git a/file-and-cloudinary-upload/app/routes/local-upload.tsx b/file-and-cloudinary-upload/app/routes/local-upload.tsx index 46788914..4afa0e1d 100644 --- a/file-and-cloudinary-upload/app/routes/local-upload.tsx +++ b/file-and-cloudinary-upload/app/routes/local-upload.tsx @@ -26,7 +26,7 @@ export const action = async ({ request }: ActionArgs) => { }; export default function Index() { - const data = useActionData(); + const actionData = useActionData(); return ( <> @@ -34,12 +34,12 @@ export default function Index() { - {data?.error ?

{data.error}

: null} + {actionData && "error" in actionData ?

{actionData.error}

: null} - {data?.imgSrc ? ( + {actionData && "imgSrc" in actionData ? ( <>

uploaded image

- uploaded + uploaded ) : null} diff --git a/file-and-cloudinary-upload/app/utils/utils.server.ts b/file-and-cloudinary-upload/app/utils/utils.server.ts index 0fc45551..d264211f 100644 --- a/file-and-cloudinary-upload/app/utils/utils.server.ts +++ b/file-and-cloudinary-upload/app/utils/utils.server.ts @@ -1,4 +1,5 @@ import { writeAsyncIterableToWritable } from "@remix-run/node"; +import type { UploadApiResponse } from "cloudinary"; import cloudinary from "cloudinary"; cloudinary.v2.config({ @@ -8,21 +9,23 @@ cloudinary.v2.config({ }); async function uploadImage(data: AsyncIterable) { - const uploadPromise = new Promise(async (resolve, reject) => { - const uploadStream = cloudinary.v2.uploader.upload_stream( - { - folder: "remix", - }, - (error, result) => { - if (error) { - reject(error); - return; + const uploadPromise = new Promise( + async (resolve, reject) => { + const uploadStream = cloudinary.v2.uploader.upload_stream( + { + folder: "remix", + }, + (error, result) => { + if (error || !result) { + reject(error); + return; + } + resolve(result); } - resolve(result); - } - ); - await writeAsyncIterableToWritable(data, uploadStream); - }); + ); + await writeAsyncIterableToWritable(data, uploadStream); + } + ); return uploadPromise; } diff --git a/file-and-cloudinary-upload/tsconfig.json b/file-and-cloudinary-upload/tsconfig.json index 20f8a386..29d8538a 100644 --- a/file-and-cloudinary-upload/tsconfig.json +++ b/file-and-cloudinary-upload/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/file-and-s3-upload/tsconfig.json b/file-and-s3-upload/tsconfig.json index 20f8a386..29d8538a 100644 --- a/file-and-s3-upload/tsconfig.json +++ b/file-and-s3-upload/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/firebase/package.json b/firebase/package.json index e13e6b6c..91f1a8d5 100644 --- a/firebase/package.json +++ b/firebase/package.json @@ -30,6 +30,6 @@ "typescript": "^4.8.4" }, "engines": { - "node": "16" + "node": ">=16" } } diff --git a/firebase/tsconfig.json b/firebase/tsconfig.json index 20f8a386..29d8538a 100644 --- a/firebase/tsconfig.json +++ b/firebase/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/form-to-notion-db/tsconfig.json b/form-to-notion-db/tsconfig.json index 20f8a386..29d8538a 100644 --- a/form-to-notion-db/tsconfig.json +++ b/form-to-notion-db/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/framer-motion/tsconfig.json b/framer-motion/tsconfig.json index 20f8a386..29d8538a 100644 --- a/framer-motion/tsconfig.json +++ b/framer-motion/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/framer-route-animation/tsconfig.json b/framer-route-animation/tsconfig.json index 20f8a386..29d8538a 100644 --- a/framer-route-animation/tsconfig.json +++ b/framer-route-animation/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/gdpr-cookie-consent/tsconfig.json b/gdpr-cookie-consent/tsconfig.json index 20f8a386..29d8538a 100644 --- a/gdpr-cookie-consent/tsconfig.json +++ b/gdpr-cookie-consent/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/google-analytics/tsconfig.json b/google-analytics/tsconfig.json index 20f8a386..29d8538a 100644 --- a/google-analytics/tsconfig.json +++ b/google-analytics/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/graphql-api/app/routes/character/$id.tsx b/graphql-api/app/routes/character/$id.tsx index 74fad202..5fefda6a 100644 --- a/graphql-api/app/routes/character/$id.tsx +++ b/graphql-api/app/routes/character/$id.tsx @@ -21,8 +21,7 @@ export const loader = async ({ params }: LoaderArgs) => { * the Remix loader & route params. */ export default function Character() { - const loader = useLoaderData(); - const { data } = loader; + const { data } = useLoaderData(); const character = data.character; diff --git a/graphql-api/app/routes/character/error.tsx b/graphql-api/app/routes/character/error.tsx index 0e2a88d1..c385e651 100644 --- a/graphql-api/app/routes/character/error.tsx +++ b/graphql-api/app/routes/character/error.tsx @@ -43,12 +43,12 @@ export const loader = async () => { * an array of errors coming back from the GraphQL API. */ export default function CharacterError() { - const loader = useLoaderData(); + const data = useLoaderData(); return (

Ex: GraphQL Error

- +

Uh oh, we've intentionally triggered an error, expand the details above to see what's going on. diff --git a/graphql-api/app/routes/index.tsx b/graphql-api/app/routes/index.tsx index 6e9fcb42..994dcfb3 100644 --- a/graphql-api/app/routes/index.tsx +++ b/graphql-api/app/routes/index.tsx @@ -27,7 +27,7 @@ export default function Index() { above to see what the Remix loader returned.


- {characters.map((character) => { + {characters.map((character: any) => { if (!character) return null; const { image } = character; diff --git a/graphql-api/tsconfig.json b/graphql-api/tsconfig.json index 20f8a386..29d8538a 100644 --- a/graphql-api/tsconfig.json +++ b/graphql-api/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/image-resize/app/components/image.tsx b/image-resize/app/components/image.tsx index efdcb890..a117386a 100644 --- a/image-resize/app/components/image.tsx +++ b/image-resize/app/components/image.tsx @@ -6,8 +6,9 @@ export interface ImageProps extends React.ComponentPropsWithRef<"img"> { width?: number; // either width or height is required height?: number; fit?: keyof FitEnum; // contain is default - alt: string; + alt?: string; } + export const Image = forwardRef( ({ children, width, height, fit, src, alt = "", ...other }, forwardedRef) => { const query = new URLSearchParams(); diff --git a/image-resize/tsconfig.json b/image-resize/tsconfig.json index 20f8a386..29d8538a 100644 --- a/image-resize/tsconfig.json +++ b/image-resize/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/infinite-scrolling/app/utils/backend.server.ts b/infinite-scrolling/app/utils/backend.server.ts index afd821a7..55d26332 100644 --- a/infinite-scrolling/app/utils/backend.server.ts +++ b/infinite-scrolling/app/utils/backend.server.ts @@ -1,3 +1,12 @@ +interface Item { + id: string; + value: string; +} + +declare global { + var __items: Item[]; +} + const items = (global.__items = global.__items ?? Array.from({ length: 50_000 }, (_, i) => ({ diff --git a/infinite-scrolling/tsconfig.json b/infinite-scrolling/tsconfig.json index 20f8a386..29d8538a 100644 --- a/infinite-scrolling/tsconfig.json +++ b/infinite-scrolling/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/io-ts-formdata-decoding/tsconfig.json b/io-ts-formdata-decoding/tsconfig.json index 20f8a386..29d8538a 100644 --- a/io-ts-formdata-decoding/tsconfig.json +++ b/io-ts-formdata-decoding/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/ioredis/tsconfig.json b/ioredis/tsconfig.json index 20f8a386..29d8538a 100644 --- a/ioredis/tsconfig.json +++ b/ioredis/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/leaflet/package.json b/leaflet/package.json index 922e6710..36b00c3d 100644 --- a/leaflet/package.json +++ b/leaflet/package.json @@ -11,11 +11,11 @@ "@remix-run/node": "~1.14.2", "@remix-run/react": "~1.14.2", "@remix-run/serve": "~1.14.2", - "leaflet": "^1.8.0", - "react-leaflet": "^4.0.2", "isbot": "^3.6.5", + "leaflet": "^1.8.0", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-leaflet": "^4.0.2" }, "devDependencies": { "@remix-run/dev": "~1.14.2", diff --git a/leaflet/tsconfig.json b/leaflet/tsconfig.json index 20f8a386..29d8538a 100644 --- a/leaflet/tsconfig.json +++ b/leaflet/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/mantine/app/entry.server.tsx b/mantine/app/entry.server.tsx index e74f9d0a..8fb5a1c2 100644 --- a/mantine/app/entry.server.tsx +++ b/mantine/app/entry.server.tsx @@ -41,6 +41,7 @@ const handleBotRequest = ( const { pipe, abort } = renderToPipeableStream( injectStylesIntoStaticMarkup( + // @ts-expect-error ), { @@ -83,6 +84,7 @@ const handleBrowserRequest = ( const { pipe, abort } = renderToPipeableStream( injectStylesIntoStaticMarkup( + // @ts-expect-error ), { diff --git a/mantine/tsconfig.json b/mantine/tsconfig.json index 20f8a386..29d8538a 100644 --- a/mantine/tsconfig.json +++ b/mantine/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/msw/tsconfig.json b/msw/tsconfig.json index 20f8a386..29d8538a 100644 --- a/msw/tsconfig.json +++ b/msw/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/multiple-forms/tsconfig.json b/multiple-forms/tsconfig.json index 20f8a386..29d8538a 100644 --- a/multiple-forms/tsconfig.json +++ b/multiple-forms/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/multiple-params/tsconfig.json b/multiple-params/tsconfig.json index 20f8a386..29d8538a 100644 --- a/multiple-params/tsconfig.json +++ b/multiple-params/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/newsletter-signup/tsconfig.json b/newsletter-signup/tsconfig.json index 20f8a386..29d8538a 100644 --- a/newsletter-signup/tsconfig.json +++ b/newsletter-signup/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/nprogress/package.json b/nprogress/package.json index ad17b003..18ac0cf8 100644 --- a/nprogress/package.json +++ b/nprogress/package.json @@ -11,8 +11,8 @@ "@remix-run/node": "~1.14.2", "@remix-run/react": "~1.14.2", "@remix-run/serve": "~1.14.2", - "nprogress": "^0.2.0", "isbot": "^3.6.5", + "nprogress": "^0.2.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/nprogress/tsconfig.json b/nprogress/tsconfig.json index 20f8a386..29d8538a 100644 --- a/nprogress/tsconfig.json +++ b/nprogress/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/on-demand-hydration/tsconfig.json b/on-demand-hydration/tsconfig.json index 20f8a386..29d8538a 100644 --- a/on-demand-hydration/tsconfig.json +++ b/on-demand-hydration/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/outlet-form-rerender/tsconfig.json b/outlet-form-rerender/tsconfig.json index 20f8a386..29d8538a 100644 --- a/outlet-form-rerender/tsconfig.json +++ b/outlet-form-rerender/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/package.json b/package.json index 8cb55881..df4e2425 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "license": "MIT", "scripts": { "format": "prettier --write . && yarn lint:fix", - "lint": "eslint --ext .tsx,.ts,.js,.jsx,.md .", + "lint": "eslint .", "lint:fix": "yarn lint --fix" }, "devDependencies": { @@ -12,8 +12,8 @@ "@types/react": "^18.2.0", "eslint": "^8.39.0", "eslint-config-prettier": "^8.8.0", - "eslint-plugin-markdown": "^3.0.0", "eslint-plugin-cypress": "^2.13.3", + "eslint-plugin-markdown": "^3.0.0", "eslint-plugin-prefer-let": "^3.0.1", "jest": "^29.5.0", "prettier": "^2.8.8", @@ -22,5 +22,11 @@ }, "engines": { "node": ">=14" + }, + "dependencies": { + "@npmcli/package-json": "^3.0.0", + "execa": "^7.1.1", + "fs-extra": "^11.1.1", + "p-queue": "^7.3.4" } } diff --git a/pathless-routes/tsconfig.json b/pathless-routes/tsconfig.json index 20f8a386..29d8538a 100644 --- a/pathless-routes/tsconfig.json +++ b/pathless-routes/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/picocss/tsconfig.json b/picocss/tsconfig.json index 20f8a386..0f5de0fa 100644 --- a/picocss/tsconfig.json +++ b/picocss/tsconfig.json @@ -11,6 +11,7 @@ "strict": true, "allowJs": true, "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, "baseUrl": ".", "paths": { "~/*": ["./app/*"] diff --git a/playwright/tsconfig.json b/playwright/tsconfig.json index 20f8a386..29d8538a 100644 --- a/playwright/tsconfig.json +++ b/playwright/tsconfig.json @@ -15,6 +15,7 @@ "paths": { "~/*": ["./app/*"] }, + "skipLibCheck": true, // Remix takes care of building everything in `remix build`. "noEmit": true diff --git a/pm-app/app/routes/dashboard/projects/$projectId.tsx b/pm-app/app/routes/dashboard/projects/$projectId.tsx index ab1f0122..af1668f7 100644 --- a/pm-app/app/routes/dashboard/projects/$projectId.tsx +++ b/pm-app/app/routes/dashboard/projects/$projectId.tsx @@ -296,12 +296,15 @@ export default function ProjectRoute() { @@ -387,7 +390,9 @@ export default function ProjectRoute() { diff --git a/pm-app/app/routes/dashboard/projects/$projectId/list/$listId.tsx b/pm-app/app/routes/dashboard/projects/$projectId/list/$listId.tsx index 66f5c9bc..7aee3d60 100644 --- a/pm-app/app/routes/dashboard/projects/$projectId/list/$listId.tsx +++ b/pm-app/app/routes/dashboard/projects/$projectId/list/$listId.tsx @@ -117,6 +117,7 @@ function TodoListRoute() { const fetchers = useFetchers(); const taskFetcherMap = new Map(); + // @ts-expect-error const allTodos: Todo[] = todoList.todos; for (const fetcher of fetchers) { if (fetcher.type === "actionSubmission") { diff --git a/pm-app/app/routes/dashboard/projects/new.tsx b/pm-app/app/routes/dashboard/projects/new.tsx index 00e992a8..9e574b7c 100644 --- a/pm-app/app/routes/dashboard/projects/new.tsx +++ b/pm-app/app/routes/dashboard/projects/new.tsx @@ -98,7 +98,7 @@ export const action = async ({ request }: ActionArgs) => { function NewProject() { const { allUsers, user } = useLoaderData(); - const { fieldErrors, fields, formError } = useActionData(); + const actionData = useActionData(); const selectableUsers = React.useMemo(() => { return allUsers.filter((u) => u.id !== user.id); @@ -130,10 +130,14 @@ function NewProject() {
- {formError ? ( + {actionData && "formError" in actionData && actionData.formError ? (
@@ -162,10 +174,20 @@ function NewProject() { -