fix: correct lint issues, format, move to tailwind

main
Carsten Kragelund 2023-10-06 23:37:29 +02:00
parent 8f9ba2a3c5
commit 1f2ba0abed
25 changed files with 140 additions and 156 deletions

@ -1,5 +1,5 @@
/** @type {import("eslint").Linter.Config} */
const config = {
module.exports = {
root: true,
parser: "@typescript-eslint/parser",
plugins: ["isaacscript", "import"],
@ -12,32 +12,29 @@ const config = {
ecmaVersion: "latest",
sourceType: "module",
tsconfigRootDir: __dirname,
project: [
"./tsconfig.json",
"./cli/tsconfig.eslint.json", // separate eslint config for the CLI since we want to lint and typecheck differently due to template files
"./upgrade/tsconfig.json",
"./www/tsconfig.json",
],
project: ["./tsconfig.json"],
},
overrides: [
// Template files don't have reliable type information
{
files: ["./cli/template/**/*.{ts,tsx}"],
extends: ["plugin:@typescript-eslint/disable-type-checked"],
},
],
overrides: [],
rules: {
// These off/not-configured-the-way-we-want lint rules we like & opt into
"@typescript-eslint/no-explicit-any": "error",
// Note: you must disable the base rule as it can report incorrect errors
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_", destructuredArrayIgnorePattern: "^_" },
"warn",
{
argsIgnorePattern: "^_",
destructuredArrayIgnorePattern: "^_",
varsIgnorePattern: "^_",
ignoreRestSiblings: true,
},
],
"@typescript-eslint/consistent-type-imports": [
"error",
{ prefer: "type-imports", fixStyle: "inline-type-imports" },
],
"import/consistent-type-specifier-style": ["error", "prefer-inline"],
"import/consistent-type-specifier-style": ["error", "prefer-top-level"],
"@typescript-eslint/consistent-type-definitions": ["error", "type"],
// For educational purposes we format our comments/jsdoc nicely
"isaacscript/complete-sentences-jsdoc": "warn",
@ -51,5 +48,3 @@ const config = {
"@typescript-eslint/prefer-nullish-coalescing": "off",
},
};
module.exports = config;

@ -1,5 +1,7 @@
# This project was created using `create-beth-app`
## To open an issue: https://github.com/ethanniser/the-beth-stack
## To discuss: https://discord.gg/Z3yUtMfkwa
### To run locally:
@ -22,4 +24,4 @@
3. Run `fly secrets set <NAME>=<VALUE>` (probably want to set `NODE_ENV` to `"production"`)
5. Run `fly deploy`
4. Run `fly deploy`

Binary file not shown.

@ -0,0 +1 @@
preload = ["bun-postcss-plugin"]

@ -1,7 +1,6 @@
import type { Config } from "drizzle-kit";
import { config } from "./src/config";
const dbCredentials = {
url: config.env.DATABASE_URL,
authToken: config.env.DATABASE_AUTH_TOKEN!,
@ -14,4 +13,4 @@ export default {
verbose: true,
strict: true,
tablesFilter: ["!libsql_wasm_func_table"],
} satisfies Config;
} satisfies Config;

@ -3,48 +3,51 @@
"module": "src/main.ts",
"type": "module",
"scripts": {
"dev": "concurrently \"bun run --hot src/main.ts\" \"bun run uno:dev\" \"bun run liveReload\"",
"dev": "concurrently \"bun run --hot src/main.ts\" \"bun run liveReload\"",
"liveReload": "bunx beth-stack",
"start": "bun run uno && bun run src/main.ts",
"start": "bun run src/main.ts",
"db:push": "bunx drizzle-kit push:sqlite",
"db:studio": "bunx drizzle-kit studio",
"db:seed": "bun run src/model/store/seed.ts",
"uno": "bunx --bun unocss",
"uno:dev": "bunx --bun unocss --watch",
"typecheck": "bunx --bun tsc",
"format:check": "prettier --check .",
"format": "prettier --write . --list-different",
"lint": "eslint . --report-unused-disable-directives",
"lint:fix": "eslint . --report-unused-disable-directives --fix"
},
"postcss": {
"plugins": {
"tailwindcss": {}
}
},
"devDependencies": {
"@ianvs/prettier-plugin-sort-imports": "^4.1.0",
"@kitajs/ts-html-plugin": "^1.0.1",
"@total-typescript/ts-reset": "^0.5.1",
"@unocss/transformer-variant-group": "^0.55.7",
"@types/eslint": "^8.44.2",
"@typescript-eslint/eslint-plugin": "^6.7.0",
"@typescript-eslint/parser": "^6.7.0",
"better-sqlite3": "^8.6.0",
"bun-postcss-plugin": "^1.0.1",
"bun-types": "latest",
"concurrently": "^8.2.1",
"drizzle-kit": "^0.19.13",
"@typescript-eslint/eslint-plugin": "^6.7.0",
"@typescript-eslint/parser": "^6.7.0",
"eslint": "^8.49.0",
"eslint-config-prettier": "^9.0.0",
"eslint-config-turbo": "^1.10.14",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-isaacscript": "^3.5.5",
"eslint-plugin-prettier": "^5.0.0",
"@types/eslint": "^8.44.2",
"pino": "^8.15.1",
"prettier": "^3.0.3",
"prettier-plugin-tailwindcss": "^0.5.4",
"typescript": "^5.2.2",
"unocss": "^0.55.7"
"tailwindcss": "0.0.0-insiders.614c7e2",
"typescript": "^5.2.2"
},
"dependencies": {
"@bogeychan/elysia-logger": "0.0.9",
"@bogeychan/elysia-logger": "0.0.11",
"@elysiajs/cron": "^0.6.0",
"@elysiajs/static": "^0.6.0",
"@elysiajs/static": "^0.7.1",
"@elysiajs/swagger": "^0.6.2",
"@iconify-json/logos": "^1.1.37",
"@iconify-json/lucide": "^1.1.128",
@ -56,7 +59,7 @@
"beth-stack": "0.0.33",
"drizzle-orm": "^0.28.6",
"drizzle-typebox": "^0.1.1",
"elysia": "0.7.4",
"elysia": "0.7.15",
"lucia": "^2.6.0",
"pino-pretty": "^10.2.0",
"zod": "^3.22.2"

@ -1,17 +1,7 @@
/** @typedef {import("prettier").Config} PrettierConfig */
/** @type { PrettierConfig | SortImportsConfig } */
const config = {
arrowParens: "always",
printWidth: 80,
singleQuote: false,
semi: true,
trailingComma: "all",
tabWidth: 2,
/** @type {import("prettier").Config} */
module.exports = {
plugins: [
"@ianvs/prettier-plugin-sort-imports",
"prettier-plugin-tailwindcss",
],
};
module.exports = config;

2
reset.d.ts vendored

@ -1 +1 @@
import "@total-typescript/ts-reset";
import "@total-typescript/ts-reset";

@ -1,6 +1,7 @@
import { libsql } from "@lucia-auth/adapter-sqlite";
import { github } from "@lucia-auth/oauth/providers";
import { lucia, Middleware } from "lucia";
import { lucia } from "lucia";
import type { Middleware } from "lucia";
import { config } from "../config";
import { client } from "../db";

@ -1,5 +1,5 @@
import { liveReloadScript } from "beth-stack/dev";
import { type PropsWithChildren } from "beth-stack/jsx";
import type { PropsWithChildren } from "beth-stack/jsx";
import { config } from "../config";
const safeScript =
@ -14,11 +14,7 @@ export const BaseHtml = ({ children }: PropsWithChildren) => (
<script src="https://unpkg.com/htmx.org@1.9.5"></script>
<script src="https://unpkg.com/htmx.org/dist/ext/response-targets.js"></script>
<script src="https://unpkg.com/hyperscript.org@0.9.11"></script>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@unocss/reset/tailwind.min.css"
/>
<link rel="stylesheet" href="/public/dist/unocss.css" />
<link rel="stylesheet" href="/styles.css" />
<script>{safeScript}</script>
</head>
<body hx-boost="true" class="h-screen">

@ -1,5 +1,4 @@
import { db } from "../db";
import { tweets } from "../db/schema/tweets";
export function TweetCard({
author: { handle },

@ -1,5 +1,4 @@
import { logger } from "@bogeychan/elysia-logger";
import { Logger } from "@bogeychan/elysia-logger/types";
import { cron } from "@elysiajs/cron";
import { HoltLogger } from "@tlscipher/holt";
import { bethStack } from "beth-stack/elysia";
@ -30,13 +29,13 @@ export const ctx = new Elysia({
.use(bethStack())
.use(logger(loggerConfig))
.use(
// @ts-expect-error
// @ts-expect-error missing toString symbol
config.env.NODE_ENV === "development"
? new HoltLogger().getLogger()
: (a) => a,
)
.use(
// @ts-expect-error
// @ts-expect-error missing toString symbol
config.env.DATABASE_CONNECTION_TYPE === "local-replica"
? cron({
name: "heartbeat",
@ -66,7 +65,7 @@ export const ctx = new Elysia({
log.debug(`Request received: ${request.method}: ${request.url}`);
}
})
.onResponse(({ log, request, set }) => {
.onResponse(({ log, request }) => {
if (log && config.env.NODE_ENV === "production") {
log.debug(`Response sent: ${request.method}: ${request.url}`);
}

@ -30,8 +30,13 @@ export const authController = new Elysia({
handle,
},
})
.catch((err) => {
if (err.code === "SQLITE_CONSTRAINT") {
.catch((err: unknown) => {
if (
typeof err === "object" &&
err &&
"code" in err &&
err.code === "SQLITE_CONSTRAINT"
) {
throw new DuplicateEmailError();
} else {
throw err;
@ -68,7 +73,6 @@ export const authController = new Elysia({
}), // Enum to validate action type
}),
error({ code, error, set, log }) {
log.error(error);
let errorMessage = "";

@ -1,6 +1,5 @@
import { eq } from "drizzle-orm";
import { Elysia, t } from "elysia";
import { authed } from "../auth/middleware";
import { AdditionalTweetList, TweetCard } from "../components/tweets";
import { ctx } from "../context";
import { tweets } from "../db/schema/tweets";
@ -10,10 +9,12 @@ export const tweetsController = new Elysia({
})
.use(ctx)
.derive(async (ctx) => {
const authRequest = ctx.auth.handleRequest(ctx);
const session = await authRequest.validate();
if (ctx.request) {
const authRequest = ctx.auth.handleRequest(ctx);
const session = await authRequest.validate();
return { session };
return { session };
}
})
.get(
"/",
@ -32,7 +33,7 @@ export const tweetsController = new Elysia({
)
.post(
"/",
async ({ session, db, body, set, log }) => {
async ({ session, db, body, set }) => {
if (!session) {
set.status = "Unauthorized";
set.headers["HX-Redirect"] = "/signin";

@ -1,4 +1,5 @@
import { createClient, type Config } from "@libsql/client";
import { createClient } from "@libsql/client";
import type { Config } from "@libsql/client";
import { drizzle } from "drizzle-orm/libsql";
import { config } from "../config";
import * as schema from "./schema";

@ -1,6 +1,5 @@
import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
import { createInsertSchema, createSelectSchema } from "drizzle-typebox";
import { user } from ".";
export const tweets = sqliteTable(
"tweet",

@ -1,27 +1,29 @@
import { staticPlugin } from "@elysiajs/static";
// import { swagger } from "@elysiajs/swagger";
import { Elysia } from "elysia";
import { config } from "./config";
import { ctx } from "./context";
import { api } from "./controllers/*";
import { pages } from "./pages/*";
import styles from "./styles.css";
const app = new Elysia()
// .use(swagger())
// @ts-expect-error
.use(staticPlugin())
.use(api)
.use(pages)
.get("/styles.css", ({ set }) => {
set.headers["Content-Type"] = "text/css";
return styles;
})
.onStart(({ log }) => {
if (config.env.NODE_ENV === "development") {
void fetch("http://localhost:3001/restart");
// log.debug("🦊 Triggering Live Reload");
console.log("🦊 Triggering Live Reload");
log.debug("🦊 Triggering Live Reload");
// console.log("🦊 Triggering Live Reload");
}
})
.onError(({ code, error, request, log }) => {
// log.error(` ${request.method} ${request.url}`, code, error);
console.error(error);
log.error(` ${request.method} ${request.url}`, code, error);
// console.error(error);
})
.listen(3000);

@ -111,57 +111,54 @@ export const login = new Elysia()
set.headers["Set-Cookie"] = stateCookie;
set.redirect = url.toString();
})
.get(
"/login/github/callback",
async ({ request, log, path, query, set, auth }) => {
const { code, state } = query;
.get("/login/github/callback", async ({ request, log, query, set, auth }) => {
const { code, state } = query;
const cookies = parseCookie(request.headers.get("Cookie") ?? "");
const storedState = cookies.github_oauth_state;
const cookies = parseCookie(request.headers.get("Cookie") ?? "");
const storedState = cookies.github_oauth_state;
if (!storedState || !state || storedState !== state || !code) {
set.status = 400;
return "Invalid state";
}
try {
const { getExistingUser, githubUser, createUser } =
await githubAuth.validateCallback(code);
if (!storedState || !state || storedState !== state || !code) {
set.status = 400;
return "Invalid state";
}
const getUser = async () => {
const existingUser = await getExistingUser();
if (existingUser) return existingUser;
const user = await createUser({
attributes: {
handle: githubUser.login,
},
});
return user;
};
try {
const { getExistingUser, githubUser, createUser } =
await githubAuth.validateCallback(code);
const user = await getUser();
const session = await auth.createSession({
userId: user.userId,
attributes: {},
});
const sessionCookie = auth.createSessionCookie(session);
// redirect to profile page
return new Response(null, {
headers: {
Location: "/",
"Set-Cookie": sessionCookie.serialize(), // store session cookie
const getUser = async () => {
const existingUser = await getExistingUser();
if (existingUser) return existingUser;
const user = await createUser({
attributes: {
handle: githubUser.login,
},
status: 302,
});
} catch (e) {
if (e instanceof OAuthRequestError) {
// invalid code
set.status = 400;
return e.message;
}
set.status = 500;
log.error(e);
return "Internal server error";
return user;
};
const user = await getUser();
const session = await auth.createSession({
userId: user.userId,
attributes: {},
});
const sessionCookie = auth.createSessionCookie(session);
// redirect to profile page
return new Response(null, {
headers: {
Location: "/",
"Set-Cookie": sessionCookie.serialize(), // store session cookie
},
status: 302,
});
} catch (e) {
if (e instanceof OAuthRequestError) {
// invalid code
set.status = 400;
return e.message;
}
},
);
set.status = 500;
log.error(e);
return "Internal server error";
}
});

@ -1,6 +1,4 @@
import { Suspense } from "beth-stack/jsx";
import { Elysia } from "elysia";
import { authed } from "../auth/middleware";
import { BaseHtml } from "../components/base";
import { InitialTweetList, TweetCreationForm } from "../components/tweets";
import { ctx } from "../context";
@ -8,12 +6,14 @@ import { ctx } from "../context";
export const index = new Elysia()
.use(ctx)
.derive(async (ctx) => {
const authRequest = ctx.auth.handleRequest(ctx);
const session = await authRequest.validate();
if (ctx.request) {
const authRequest = ctx.auth.handleRequest(ctx);
const session = await authRequest.validate();
return { session };
return { session };
}
})
.get("/", async ({ htmlStream, session, db }) => {
.get("/", ({ htmlStream, session }) => {
return htmlStream(() => (
<BaseHtml>
<div class="flex flex-col items-center py-3">

@ -0,0 +1 @@
@import "tailwindcss";

@ -1,4 +1,5 @@
type RoutesByType<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Schema extends Record<string, any>, // Ensure keys are strings
Type extends "get" | "post" | "put" | "delete" | "patch",
> = RouterPattern<
@ -20,22 +21,23 @@ type RemoveSlash<S extends string> = S extends `${infer T}/`
: S;
type RouterPattern<T extends string> =
T extends `${infer Start}:${infer Param}/${infer Rest}`
T extends `${infer Start}:${infer _}/${infer Rest}`
? `${Start}${string}/${RouterPattern<Rest>}`
: T extends `${infer Start}:${infer Param}`
: T extends `${infer Start}:${infer _}`
? `${Start}${string}`
: T extends `${infer Start}*`
? `${Start}${string}`
: T;
type StartsWithApi<T extends string> = T extends `${"/api"}${infer Rest}`
type StartsWithApi<T extends string> = T extends `${"/api"}${infer _}`
? T
: never;
type DoesntStartWithApi<T extends string> = T extends `${"/api"}${infer Rest}`
type DoesntStartWithApi<T extends string> = T extends `${"/api"}${infer _}`
? never
: T;
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
type Schema = import("../main").App["schema"];
type PostRoutes = RoutesByType<Schema, "post">;
@ -44,9 +46,12 @@ type PutRoutes = RoutesByType<Schema, "put">;
type DeleteRoutes = RoutesByType<Schema, "delete">;
type PatchRoutes = RoutesByType<Schema, "patch">;
type AllowQuery<T extends string> = `${T}?${string}` | T;
declare namespace JSX {
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface HtmlTag extends Htmx.Attributes {
["hx-get"]?: StartsWithApi<GetRoutes>;
["hx-get"]?: AllowQuery<StartsWithApi<GetRoutes>>;
["hx-post"]?: StartsWithApi<PostRoutes>;
["hx-put"]?: StartsWithApi<PutRoutes>;
["hx-delete"]?: StartsWithApi<DeleteRoutes>;

@ -1,8 +1,9 @@
/// <reference types="lucia" />
declare namespace Lucia {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
type Auth = import("../auth/index").Auth;
type DatabaseUserAttributes = {
handle: string;
};
type DatabaseSessionAttributes = {};
type DatabaseSessionAttributes = object;
}

@ -1,5 +1,7 @@
{
"$schema": "https://json.schemastore.org/tsconfig.json",
"compilerOptions": {
"rootDir": ".",
"lib": ["ESNext"],
"module": "esnext",
"target": "esnext",
@ -18,16 +20,15 @@
"forceConsistentCasingInFileNames": true,
"allowJs": true,
"types": [
"bun-types" // add Bun global
"bun-types", // add Bun global
"bun-postcss-plugin"
],
// non bun init
// "plugins": [{ "name": "@kitajs/ts-html-plugin" }],
"noUncheckedIndexedAccess": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true
// "noImplicitReturns": true
}
},
"files": [".eslintrc.cjs"],
"include": ["**/*", "**/*.d.ts"],
"exclude": ["./node_modules"]
}

@ -1,13 +0,0 @@
import transformerVariantGroup from "@unocss/transformer-variant-group";
import { defineConfig, presetIcons, presetWebFonts, presetWind } from "unocss";
export default defineConfig({
cli: {
entry: {
patterns: ["src/**/*.{ts,tsx}"],
outFile: "public/dist/unocss.css",
},
},
presets: [presetWind(), presetIcons(), presetWebFonts()],
transformers: [transformerVariantGroup()],
});