diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 6051c71..8e0543f 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -30,9 +30,6 @@ jobs:
with:
useLockFile: false
- - name: ⚙️ Build CSS
- run: npm run generate:css
-
- name: 🔬 Lint
run: npm run lint
diff --git a/.github/workflows/for-this-stack-repo-only.yml b/.github/workflows/for-this-stack-repo-only.yml
index f665d9a..5ec5f82 100644
--- a/.github/workflows/for-this-stack-repo-only.yml
+++ b/.github/workflows/for-this-stack-repo-only.yml
@@ -30,9 +30,6 @@ jobs:
with:
useLockFile: false
- - name: ⚙️ Build CSS
- run: npm run generate:css
-
- name: 🔬 Lint
run: npm run lint
diff --git a/app/integrations/i18n/i18next.server.tsx b/app/integrations/i18n/i18next.server.tsx
index 688f6fa..c0c46ce 100644
--- a/app/integrations/i18n/i18next.server.tsx
+++ b/app/integrations/i18n/i18next.server.tsx
@@ -24,12 +24,13 @@ export const i18nextServer = new RemixI18Next({
// The backend you want to use to load the translations
// Tip: You could pass `resources` to the `i18next` configuration and avoid
// a backend here
+ // @ts-expect-error - `i18next-fs-backend` is not typed
backend: Backend,
});
export async function createI18nextServerInstance(
request: Request,
- remixContext: EntryContext
+ remixContext: EntryContext,
) {
// Create a new instance of i18next so every request will have a
// completely unique instance and not share any state
diff --git a/app/integrations/supabase/client.ts b/app/integrations/supabase/client.ts
index 87a8c85..8b16e69 100644
--- a/app/integrations/supabase/client.ts
+++ b/app/integrations/supabase/client.ts
@@ -29,17 +29,6 @@ function getSupabaseClient(supabaseKey: string, accessToken?: string) {
});
}
-/**
- * Provides a Supabase Client for the logged in user or get back a public and safe client without admin privileges
- *
- * It's a per request scoped client to prevent access token leaking over multiple concurrent requests and from different users.
- *
- * Reason : https://github.com/rphlmr/supa-fly-stack/pull/43#issue-1336412790
- */
-function getSupabase(accessToken?: string) {
- return getSupabaseClient(SUPABASE_ANON_PUBLIC, accessToken);
-}
-
/**
* Provides a Supabase Admin Client with full admin privileges
*
@@ -56,4 +45,6 @@ function getSupabaseAdmin() {
return getSupabaseClient(SUPABASE_SERVICE_ROLE);
}
-export { getSupabaseAdmin, getSupabase };
+const supabaseClient = getSupabaseClient(SUPABASE_ANON_PUBLIC);
+
+export { getSupabaseAdmin, supabaseClient };
diff --git a/app/modules/user/service.server.test.ts b/app/modules/user/service.server.test.ts
index 3c849b5..9d8ae12 100644
--- a/app/modules/user/service.server.test.ts
+++ b/app/modules/user/service.server.test.ts
@@ -12,6 +12,9 @@ import { db } from "~/database";
import { createUserAccount } from "./service.server";
+// @vitest-environment node
+// 👋 see https://vitest.dev/guide/environment.html#environments-for-specific-files
+
// mock db
vitest.mock("~/database", () => ({
db: {
@@ -113,7 +116,6 @@ describe(createUserAccount.name, () => {
expect(signInRequest.body).toEqual({
email: USER_EMAIL,
password: USER_PASSWORD,
- data: {},
gotrue_meta_security: {},
});
expect(fetchAuthAdminUserAPI.size).toEqual(1);
diff --git a/app/routes/notes/new.tsx b/app/routes/notes/new.tsx
index 5fbc6a1..c09e96a 100644
--- a/app/routes/notes/new.tsx
+++ b/app/routes/notes/new.tsx
@@ -11,7 +11,7 @@ import { createNote } from "~/modules/note";
import { assertIsPost, isFormProcessing } from "~/utils";
export const NewNoteFormSchema = z.object({
- title: z.string().min(2, "require-title"),
+ title: z.string().min(1, "require-title"),
body: z.string().min(1, "require-body"),
});
@@ -31,7 +31,7 @@ export async function action({ request }: LoaderArgs) {
headers: {
"Set-Cookie": await commitAuthSession(request, { authSession }),
},
- }
+ },
);
}
diff --git a/app/routes/oauth.callback.tsx b/app/routes/oauth.callback.tsx
index 8096ef5..0d984af 100644
--- a/app/routes/oauth.callback.tsx
+++ b/app/routes/oauth.callback.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useMemo } from "react";
+import { useEffect } from "react";
import { json, redirect } from "@remix-run/node";
import type { LoaderArgs, ActionArgs } from "@remix-run/node";
@@ -6,7 +6,7 @@ import { useActionData, useFetcher, useSearchParams } from "@remix-run/react";
import { parseFormAny } from "react-zorm";
import { z } from "zod";
-import { getSupabase } from "~/integrations/supabase";
+import { supabaseClient } from "~/integrations/supabase";
import {
refreshAccessToken,
commitAuthSession,
@@ -98,12 +98,11 @@ export default function LoginCallback() {
const fetcher = useFetcher();
const [searchParams] = useSearchParams();
const redirectTo = searchParams.get("redirectTo") ?? "/notes";
- const supabase = useMemo(() => getSupabase(), []);
useEffect(() => {
const {
data: { subscription },
- } = supabase.auth.onAuthStateChange((event, supabaseSession) => {
+ } = supabaseClient.auth.onAuthStateChange((event, supabaseSession) => {
if (event === "SIGNED_IN") {
// supabase sdk has ability to read url fragment that contains your token after third party provider redirects you here
// this fragment url looks like https://.....#access_token=evxxxxxxxx&refresh_token=xxxxxx, and it's not readable server-side (Oauth security)
@@ -129,7 +128,7 @@ export default function LoginCallback() {
// prevent memory leak. Listener stays alive 👨🎤
subscription.unsubscribe();
};
- }, [fetcher, redirectTo, supabase.auth]);
+ }, [fetcher, redirectTo]);
return error ?
{error.message}
: null;
}
diff --git a/app/routes/reset-password.tsx b/app/routes/reset-password.tsx
index 6f1fff4..d0d675e 100644
--- a/app/routes/reset-password.tsx
+++ b/app/routes/reset-password.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useMemo, useState } from "react";
+import { useEffect, useState } from "react";
import type { ActionArgs, LoaderArgs } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
@@ -8,7 +8,7 @@ import { parseFormAny, useZorm } from "react-zorm";
import { z } from "zod";
import { i18nextServer } from "~/integrations/i18n";
-import { getSupabase } from "~/integrations/supabase";
+import { supabaseClient } from "~/integrations/supabase";
import {
commitAuthSession,
getAuthSession,
@@ -104,12 +104,11 @@ export default function ResetPassword() {
const actionData = useActionData();
const transition = useTransition();
const disabled = isFormProcessing(transition.state);
- const supabase = useMemo(() => getSupabase(), []);
useEffect(() => {
const {
data: { subscription },
- } = supabase.auth.onAuthStateChange((event, supabaseSession) => {
+ } = supabaseClient.auth.onAuthStateChange((event, supabaseSession) => {
// In local development, we doesn't see "PASSWORD_RECOVERY" event because:
// Effect run twice and break listener chain
if (event === "PASSWORD_RECOVERY" || event === "SIGNED_IN") {
@@ -125,7 +124,7 @@ export default function ResetPassword() {
// prevent memory leak. Listener stays alive 👨🎤
subscription.unsubscribe();
};
- }, [supabase.auth]);
+ }, []);
return (
diff --git a/app/utils/http.test.ts b/app/utils/http.test.ts
index f979afa..0c5130c 100644
--- a/app/utils/http.test.ts
+++ b/app/utils/http.test.ts
@@ -8,6 +8,9 @@ import {
safeRedirect,
} from "./http.server";
+// @vitest-environment node
+// 👋 see https://vitest.dev/guide/environment.html#environments-for-specific-files
+
const BASE_URL = "https://my-app.com";
describe(getCurrentPath.name, () => {
@@ -19,7 +22,7 @@ describe(getCurrentPath.name, () => {
describe(makeRedirectToFromHere.name, () => {
it("should return search params with redirectTo set with current request url path", () => {
expect(makeRedirectToFromHere(new Request(`${BASE_URL}/profile`))).toEqual(
- new URLSearchParams([["redirectTo", "/profile"]])
+ new URLSearchParams([["redirectTo", "/profile"]]),
);
});
});
@@ -31,13 +34,13 @@ describe(getRedirectTo.name, () => {
it("should return url redirectTo param value", () => {
expect(getRedirectTo(new Request(`${BASE_URL}?redirectTo=/profile`))).toBe(
- "/profile"
+ "/profile",
);
});
it("should return root redirectTo param value if invalid param value", () => {
expect(getRedirectTo(new Request(`${BASE_URL}?redirectTo=//profile`))).toBe(
- "/"
+ "/",
);
});
});
@@ -75,7 +78,7 @@ describe(notFound.name, () => {
it("should return message", async () => {
expect(await notFound("not-found-message").text()).toBe(
- "not-found-message"
+ "not-found-message",
);
});
});
diff --git a/cypress/support/delete-user.ts b/cypress/support/delete-user.ts
index c762fbe..7593d71 100644
--- a/cypress/support/delete-user.ts
+++ b/cypress/support/delete-user.ts
@@ -35,7 +35,9 @@ async function deleteUser(email: string) {
}
}
- await deleteAuthAccount(user?.id!);
+ if (user?.id) {
+ await deleteAuthAccount(user.id);
+ }
}
deleteUser(process.argv[2]);
diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json
index 4b0b5d0..0d07fa8 100644
--- a/cypress/tsconfig.json
+++ b/cypress/tsconfig.json
@@ -12,7 +12,7 @@
"compilerOptions": {
"baseUrl": ".",
"noEmit": true,
- "types": ["node", "cypress", "@testing-library/cypress"],
+ "types": ["node"],
"esModuleInterop": true,
"jsx": "react-jsx",
"moduleResolution": "node",
diff --git a/package.json b/package.json
index b7d77a3..a6286e2 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
"@remix-run/node": "*",
"@remix-run/react": "*",
"@remix-run/serve": "*",
- "@supabase/supabase-js": "^2.24.0",
+ "@supabase/supabase-js": "^2.26.0",
"cookie": "^0.5.0",
"i18next": "^21.9.1",
"i18next-browser-languagedetector": "^6.1.5",
@@ -42,46 +42,45 @@
"react-i18next": "^11.18.5",
"react-zorm": "^0.6.1",
"remix-i18next": "^4.1.1",
- "tailwind-merge": "^1.10.0",
- "zod": "^3.19.1"
+ "tailwind-merge": "^1.13.2",
+ "zod": "^3.21.4"
},
"devDependencies": {
- "@faker-js/faker": "^7.6.0",
+ "@faker-js/faker": "^8.0.2",
"@remix-run/dev": "*",
- "@remix-run/eslint-config": "*",
+ "@remix-run/eslint-config": "1.18.1",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.5.3",
- "@tailwindcss/line-clamp": "^0.4.2",
"@tailwindcss/typography": "^0.5.9",
- "@testing-library/cypress": "^8.0.3",
+ "@testing-library/cypress": "^9.0.0",
"@testing-library/jest-dom": "^5.16.5",
- "@testing-library/react": "^13.4.0",
+ "@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/i18next-fs-backend": "^1.1.2",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@vitejs/plugin-react": "^3.1.0",
- "@vitest/coverage-c8": "^0.28.5",
+ "@vitest/coverage-v8": "^0.33.0",
"cross-env": "^7.0.3",
- "cypress": "^10.10.0",
+ "cypress": "^12.17.1",
"dotenv-cli": "^7.0.0",
- "eslint": "^8.34.0",
+ "eslint": "^8.44.0",
"eslint-config-prettier": "^8.6.0",
- "eslint-plugin-tailwindcss": "^3.9.0",
- "happy-dom": "^7.6.0",
- "msw": "^0.49.0",
+ "eslint-plugin-tailwindcss": "^3.13.0",
+ "happy-dom": "^10.1.1",
+ "msw": "^1.2.2",
"npm-run-all": "^4.1.5",
- "prettier": "2.8.4",
- "prettier-plugin-tailwindcss": "^0.2.3",
+ "prettier": "3.0.0",
+ "prettier-plugin-tailwindcss": "^0.4.0",
"prisma": "^4.10.1",
- "start-server-and-test": "^1.14.0",
- "tailwind-scrollbar": "^2.1.0",
- "tailwindcss": "^3.2.7",
+ "start-server-and-test": "^2.0.0",
+ "tailwind-scrollbar": "^3.0.4",
+ "tailwindcss": "^3.3.2",
"ts-node": "^10.9.1",
- "tsconfig-paths": "^4.1.2",
- "typescript": "^4.9.5",
- "vite-tsconfig-paths": "^4.0.5",
- "vitest": "^0.28.5"
+ "tsconfig-paths": "^4.2.0",
+ "typescript": "^5.1.6",
+ "vite-tsconfig-paths": "^4.2.0",
+ "vitest": "^0.33.0"
},
"engines": {
"node": ">=16"
diff --git a/tailwind.config.js b/tailwind.config.js
index ecc91c8..cc0b78c 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -7,7 +7,6 @@ module.exports = {
require("@tailwindcss/typography"),
require("@tailwindcss/forms"),
require("@tailwindcss/aspect-ratio"),
- require("@tailwindcss/line-clamp"),
require("tailwind-scrollbar"),
],
};
diff --git a/test/setup-test-env.ts b/test/setup-test-env.ts
index fd958e1..262a737 100644
--- a/test/setup-test-env.ts
+++ b/test/setup-test-env.ts
@@ -8,6 +8,13 @@ process.env.SUPABASE_ANON_PUBLIC = "{ANON_PUBLIC}";
process.env.SUPABASE_URL = "https://supabase-project.supabase.co";
process.env.SERVER_URL = "http://localhost:3000";
+
+if(typeof window !== 'undefined'){
+ // @ts-expect-error missing vitest type
+ window.happyDOM.settings.enableFileSystemHttpRequests = true;
+}
+
+
installGlobals();
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));