Begin trpc and cli work

This commit is contained in:
Ryan Pandya 2024-11-08 19:25:05 -08:00
parent 9602e55f04
commit e2fd34fa2d
38 changed files with 1941 additions and 286 deletions

View File

@ -3,5 +3,6 @@
{ {
"mode": "auto" "mode": "auto"
} }
] ],
"direnv.restart.automatic": true
} }

1
apps/cli/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
dist

1
apps/cli/README.md Normal file
View File

@ -0,0 +1 @@
# `@lifetracker/cli`

44
apps/cli/package.json Normal file
View File

@ -0,0 +1,44 @@
{
"name": "@lifetracker/cli",
"version": "0.0.1",
"description": "Command Line Interface (CLI) for Lifetracker",
"private": true,
"type": "module",
"exports": "./dist/index.mjs",
"bin": {
"lifetracker": "dist/index.mjs"
},
"scripts": {
"build": "vite build --debug",
"run": "tsx src/index.ts",
"lint": "eslint .",
"format": "prettier . --ignore-path ../../.prettierignore",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@lifetracker/db": "workspace:*",
"@lifetracker/trpc": "workspace:^",
"vite-tsconfig-paths": "^5.1.0"
},
"devDependencies": {
"@commander-js/extra-typings": "^12.1.0",
"@lifetracker/db": "workspace:*",
"@lifetracker/eslint-config": "workspace:*",
"@lifetracker/typescript-config": "workspace:*",
"@lifetracker/ui": "workspace:*",
"@trpc/client": "^10.45.2",
"@trpc/server": "^10.45.2",
"chalk": "^5.3.0",
"commander": "^12.1.0",
"superjson": "^2.2.1",
"tsx": "^4.19.1",
"vite": "^5.4.10",
"vite-tsconfig-paths": "^5.1.0"
},
"eslintConfig": {
"root": true,
"extends": [
"@lifetracker/eslint-config/base"
]
}
}

View File

@ -0,0 +1,26 @@
import {
printError,
printErrorMessageWithReason,
printObject,
} from "@/lib/output";
import { getAPIClient } from "@/lib/trpc";
import { Command } from "@commander-js/extra-typings";
export const helloWorldCmd = new Command()
.name("hello-world")
.description("returns info about the server and stats");
helloWorldCmd
.command("test")
.description("does something specific I guess")
.action(async () => {
const api = getAPIClient();
try {
console.dir(api);
} catch (error) {
printErrorMessageWithReason(
"Something went horribly wrong",
error as object,
);
}
});

31
apps/cli/src/index.ts Normal file
View File

@ -0,0 +1,31 @@
import { helloWorldCmd } from "@/commands/hello-world"
import { setGlobalOptions } from "@/lib/globals";
import { Command, Option } from "@commander-js/extra-typings";
const program = new Command()
.name("lifetracker")
.description("A CLI interface to interact with my lifetracker api")
.addOption(
new Option("--api-key <key>", "the API key to interact with the API")
.makeOptionMandatory(true)
.env("LIFETRACKER_API_KEY"),
)
.addOption(
new Option(
"--server-addr <addr>",
"the address of the server to connect to",
)
.makeOptionMandatory(true)
.env("LIFETRACKER_SERVER_ADDR"),
)
.addOption(new Option("--json", "to output the result as JSON"))
.version(
import.meta.env && "CLI_VERSION" in import.meta.env
? import.meta.env.CLI_VERSION
: "0.0.0",
);
program.addCommand(helloWorldCmd);
setGlobalOptions(program.opts());
program.parse();

View File

@ -0,0 +1,18 @@
export interface GlobalOptions {
apiKey: string;
serverAddr: string;
json?: true;
}
export let globalOpts: GlobalOptions | undefined = undefined;
export function setGlobalOptions(opts: GlobalOptions) {
globalOpts = opts;
}
export function getGlobalOptions() {
if (!globalOpts) {
throw new Error("Global options are not initalized yet");
}
return globalOpts;
}

View File

@ -0,0 +1,61 @@
import { InspectOptions } from "util";
import chalk from "chalk";
import { getGlobalOptions } from "./globals";
/**
* Prints an object either in a nicely formatted way or as JSON (depending on the command flag --json)
*
* @param output
*/
export function printObject(
output: object,
extraOptions?: InspectOptions,
): void {
if (getGlobalOptions().json) {
console.log(JSON.stringify(output, undefined, 4));
} else {
console.dir(output, extraOptions);
}
}
/**
* Used to output a status (success/error) and a message either as string or as JSON (depending on the command flag --json)
*
* @param success if the message is a successful message or an error
* @param output the message to output
*/
export function printStatusMessage(success: boolean, message: unknown): void {
const status = success ? "Success" : "Error";
const colorFunction = success ? chalk.green : chalk.red;
console.error(colorFunction(`${status}: ${message}`));
}
/**
* @param message The message that will be printed as a successful message
* @returns a function that can be used in a Promise on success
*/
export function printSuccess(message: string) {
return () => {
printStatusMessage(true, message);
};
}
/**
* @param message The message that will be printed as an error message
* @returns a function that can be used in a Promise on rejection
*/
export function printError(message: string) {
return (error: object) => {
printErrorMessageWithReason(message, error);
};
}
/**
* @param message The message that will be printed as an error message
* @param error an error object with the reason for the error
*/
export function printErrorMessageWithReason(message: string, error: object) {
const errorMessage = "message" in error ? error.message : error;
printStatusMessage(false, `${message}. Reason: ${errorMessage}`);
}

21
apps/cli/src/lib/trpc.ts Normal file
View File

@ -0,0 +1,21 @@
import { getGlobalOptions } from "@/lib/globals";
import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
import type { AppRouter } from "@lifetracker/trpc/routers/_app";
export function getAPIClient() {
const globals = getGlobalOptions();
return createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: `${globals.serverAddr}/api/trpc`,
maxURLLength: 14000,
headers() {
return {
authorization: `Bearer ${globals.apiKey}`,
};
},
}),
],
});
}

9
apps/cli/src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly CLI_VERSION: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

25
apps/cli/tsconfig.json Normal file
View File

@ -0,0 +1,25 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@lifetracker/typescript-config/node.json",
"include": [
"src/**/*",
"vite.config.mts"
],
"exclude": [
"node_modules",
"dist"
],
"compilerOptions": {
"baseUrl": ".",
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
"strictNullChecks": true,
"paths": {
"@/*": [
"src/*"
]
},
"types": [
"vite/client"
]
}
}

36
apps/cli/vite.config.mts Normal file
View File

@ -0,0 +1,36 @@
// Hilariously, THIS is stolen from Hoarder's CLI vite config
// https://github.com/hoarder-app/hoarder/blob/main/apps/cli/vite.config.mts
// This file is shamelessly copied from immich's CLI vite config
// https://github.com/immich-app/immich/blob/main/cli/vite.config.ts
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
import viteTS from "vite-plugin-ts";
const debugTsconfigPaths = tsconfigPaths({
loose: true,
root: ".",
ignoreConfigErrors: true
});
export default defineConfig({
build: {
rollupOptions: {
input: "src/index.ts",
output: {
dir: "dist",
},
},
ssr: true,
},
ssr: {
// bundle everything except for Node built-ins
noExternal: /^(?!node:).*$/,
},
plugins: [debugTsconfigPaths, viteTS()],
define: {
"import.meta.env.CLI_VERSION": JSON.stringify(
process.env.npm_package_version,
),
},
});

View File

@ -56,6 +56,7 @@ For posterity (aka, to test Docusaurus code highlighting, since this is very muc
}); });
} }
``` ```
### Monorepo with pnpm and turbo ### Monorepo with pnpm and turbo
In the new directory, run `npx create turbo@latest`. If you're in the future trying this, yes, you'll realize it wants to make a subdirectory and complains about existing files, so you'll have to end up deleting everything, using the npx script, and then re-initializing the nix flake directory. Annoying, but manageable. In the new directory, run `npx create turbo@latest`. If you're in the future trying this, yes, you'll realize it wants to make a subdirectory and complains about existing files, so you'll have to end up deleting everything, using the npx script, and then re-initializing the nix flake directory. Annoying, but manageable.

View File

@ -22,7 +22,6 @@ Yes, everything worked fine as soon as I made the ~31 edits, ran `pnpm install`,
::: :::
## Another side-quest: Git Repository ## Another side-quest: Git Repository
There's really no reason to document this. I already had a local a git repo initialized (can't remember if this happens by default in my `init-nix` script, or if I ran `git init`, you're on your own); I created a cloud copy on `git.ryanpandya.com` and synced all this crap with it. There's really no reason to document this. I already had a local a git repo initialized (can't remember if this happens by default in my `init-nix` script, or if I ran `git init`, you're on your own); I created a cloud copy on `git.ryanpandya.com` and synced all this crap with it.

View File

@ -3,6 +3,7 @@ description: The scary stuff.
--- ---
# Database # Database
The scary part. The scary part.
This is where my casual fucking around is about to slam face-first into a brick wall. This is where my casual fucking around is about to slam face-first into a brick wall.
@ -45,7 +46,7 @@ Honestly. just copy the code from the following files:
- package.json - package.json
- schema.ts (PLACEHOLDER SHIT FROM TUTORIAL) - schema.ts (PLACEHOLDER SHIT FROM TUTORIAL)
Then, *from the db directory*, run `pnpm drizzle-kit generate` and it'll make the fucking database file, after which, from the workspace project root, `pnpm db:migrate` will do the goddamn migration, after which, `pnpm db:studio` will load Drizzle Studio at some arcane Avahi `local.drizzle.studio` URL and you'll see the fucking table it made. Then, _from the db directory_, run `pnpm drizzle-kit generate` and it'll make the fucking database file, after which, from the workspace project root, `pnpm db:migrate` will do the goddamn migration, after which, `pnpm db:studio` will load Drizzle Studio at some arcane Avahi `local.drizzle.studio` URL and you'll see the fucking table it made.
I hate Javascript development. I hate Javascript development.

View File

@ -81,3 +81,28 @@ This means that now when I run "pnpm dev" from the project root, along with Docu
## Can we actually do something now? ## Can we actually do something now?
Almost! Next snag: Trying to add `@lifetracker/db` as a dependency in the `@lifetracker/web` app, I realize I could just manually add it in the package.json, but ChatGPT suggested I run this command:
```bash
$ pnpm --filter=@lifetracker/web add @lifetracker/db
ERR_PNPM_FETCH_404 GET https://registry.npmjs.org/@lifetracker%2Fdb: Not Found - 404
This error happened while installing a direct dependency of /home/ryan/Documents/Code/lifetracker/apps/web
@lifetracker/db is not in the npm registry, or you have no permission to fetch it.
```
So, to fix this... you actually have to run
```
pnpm --filter=@lifetracker/web add @lifetracker/db@workspace:*
```
which is also why in the manually defined lines I had copied over into the package.json, it was written as:
```js
"@lifetracker/db": "workspace:*^*",
```
This made it work fine.

View File

@ -0,0 +1,142 @@
---
description: A quick and dirty command-line interface would be cool to set up.
---
# Command-Line App
In the main project root, run:
```
pnpm turbo generate workspace --name "@lifetracker/cli" --type app
```
I let the wizard walk me through it, selecting only the `@lifetracker/db` as regular dependencies, and all four (db, ui, eslint-config, and typescript-config) as devDependencies --- no idea if that was right.
I also added a few more devDependencies (`-D`):
```
pnpm --filter=@lifetracker/cli add -D tsx vite commander @commander-js/extra-typings chalk @trpc/client @trpc/server
```
As far as I can tell, Commander parses command line args, Chalk is for adding terminal colors and such, TRPC is the actual communication with the API (?), TSX is Typescript, and Vite is the build system (because apparently, pnpm and turbo aren't enough Javascript crust for one project).
Don't forget the scripts:
```js
"scripts": {
"build": "vite build",
"run": "tsx src/index.ts",
"lint": "eslint .",
"format": "prettier . --ignore-path ../../.prettierignore",
"typecheck": "tsc --noEmit"
},
```
and exports:
```js
"exports": "./dist/index.mjs",
"bin": {
"lifetracker": "dist/index.mjs"
},
```
I then copied Hoarder's `tsconfig.json`, `.gitignore`, `vite.config.mts`, and in the `src/` folder, `vite-env.d.ts`, the files in `lib` which mostly didn't need changing, and then I whipped up a Hello World command definition.
There were now dependency issues because I don't actually have `@lifetracker/trpc` yet, and there was a missing typescript definition for node files, which I quickly stole from Hoarder.
Next snag before I write some TRPC code is that `vite-tsconfig-paths`, which is referenced a bunch in Hoarder but defined/imported nowhere it seems, is... not imported for me. What is this thing?
Looking at Hoarder's repo, it seems `vite-tsconfig-paths` is in the trpc and web `package.json` files, but not in the CLI one. So, I added it and it works.
## TRPC Time
Now to make the actual TRPC interface to interface with the CLI app.
First, before worrying about what in Zombie Jesus "TRPC" is, let's mindlessly create a new package for it:
```
pnpm turbo generate workspace --name "@lifetracker/trpc" --type package
```
and shamelessly copy the devDependencies:
```
pnpm --filter=@lifetracker/trpc add -D vitest vite-tsconfig-paths @types/bcryptjs
```
:::info
This time we're not fooled by `vite-tsconfig-paths`.
:::
There are also a few non-dev dependencies we need (I'm guessing, since they're in the project I'm stealing from):
```
pnpm --filter=@lifetracker/trpc add @trpc/server bcryptjs drizzle-orm superjson tiny-invariant zod
```
Of these, the two non-obvious ones are tiny-invariant (a stupid little microrepo that converts truthy and falsy values to thrown errors or not) and zod (TypeScript-first schema validation with static type inference[^1]).
[^1]: What the fuck does that mean? Sounds like something that should have been part of Drizzle?
... and shamelessly copy the main files in Hoarder's trpc package:
- `vitest.config.ts`
- `tsconfig.json`
- `testUtils.ts`, which needs a lot of work to modify for us
- `index.ts`, same deal plus we might need to make a Shared package
- I'm gonna skip `auth.ts` for now, I don't need it for the hello world CLI function
The real magic happens in `trpc/routers` so let's steal that next.
### Routers?
Seeing as the only thing directly referenced in the CLI so far is `_app`, let's just do that and see if something can limp towards working from there:
Okay yeah so `_app.ts` is super simple, it's just a directory of the different modules in the folder. But each of those modules is a doozie to copy and simplify. Let me give it a go.
### Bare Minimum TRPC
All `index.ts` needs is this:
```js
import { initTRPC } from "@trpc/server";
const t = initTRPC.create();
export const router = t.router;
export const publicProcedure = t.procedure;
```
All the router `helloWorld.js` needs is:
```js
import { printError, printErrorMessageWithReason, printObject } from "@/lib/output";
import { getAPIClient } from "@/lib/trpc";
import { Command } from "@commander-js/extra-typings";
export const helloWorldCmd = new Command()
.name("hello-world")
.description("returns info about the server and stats");
helloWorldCmd
.command("test")
.description("does something specific I guess")
.action(async () => {
const api = getAPIClient();
try {
console.dir(api);
} catch (error) {
printErrorMessageWithReason("Something went horribly wrong", error as object);
}
});
```
Time to test, and of course `pnpm build` fails spectacularly, and ChatGPT and Google results are zero help. It seems like the plugin `vite-tsconfig-paths` (YEAH, THAT ONE) is not fucking working in the build step, so it can't resolve paths that are resolved just fine by the TS server running in VSCode.
After literally hours of nothing useful on Google and complete trash from the AI, I figured it out all on my own: the issue was the first line in the file,
```
#!/usr/bin/env node
```
Removing it works fine, but then how will

View File

@ -1,62 +1,62 @@
import { themes as prismThemes } from 'prism-react-renderer'; import { themes as prismThemes } from "prism-react-renderer";
import type { Config } from '@docusaurus/types'; import type { Config } from "@docusaurus/types";
import type * as Preset from '@docusaurus/preset-classic'; import type * as Preset from "@docusaurus/preset-classic";
const config: Config = { const config: Config = {
title: 'Lifetracker Docs', title: "Lifetracker Docs",
tagline: 'Tracking the life of the life tracker', tagline: "Tracking the life of the life tracker",
favicon: 'img/favicon.ico', favicon: "img/favicon.ico",
// Set the production url of your site here // Set the production url of your site here
url: 'https://your-docusaurus-site.example.com', url: "https://your-docusaurus-site.example.com",
// Set the /<baseUrl>/ pathname under which your site is served // Set the /<baseUrl>/ pathname under which your site is served
// For GitHub pages deployment, it is often '/<projectName>/' // For GitHub pages deployment, it is often '/<projectName>/'
baseUrl: '/', baseUrl: "/",
// GitHub pages deployment config. // GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these. // If you aren't using GitHub pages, you don't need these.
organizationName: 'facebook', // Usually your GitHub org/user name. organizationName: "facebook", // Usually your GitHub org/user name.
projectName: 'docusaurus', // Usually your repo name. projectName: "docusaurus", // Usually your repo name.
onBrokenLinks: 'throw', onBrokenLinks: "throw",
onBrokenMarkdownLinks: 'warn', onBrokenMarkdownLinks: "warn",
// Even if you don't use internationalization, you can use this field to set // Even if you don't use internationalization, you can use this field to set
// useful metadata like html lang. For example, if your site is Chinese, you // useful metadata like html lang. For example, if your site is Chinese, you
// may want to replace "en" with "zh-Hans". // may want to replace "en" with "zh-Hans".
i18n: { i18n: {
defaultLocale: 'en', defaultLocale: "en",
locales: ['en'], locales: ["en"],
}, },
presets: [ presets: [
[ [
'classic', "classic",
{ {
docs: { docs: {
sidebarPath: './sidebars.ts', sidebarPath: "./sidebars.ts",
// Please change this to your repo. // Please change this to your repo.
// Remove this to remove the "edit this page" links. // Remove this to remove the "edit this page" links.
editUrl: editUrl:
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', "https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/",
}, },
blog: { blog: {
showReadingTime: true, showReadingTime: true,
feedOptions: { feedOptions: {
type: ['rss', 'atom'], type: ["rss", "atom"],
xslt: true, xslt: true,
}, },
// Please change this to your repo. // Please change this to your repo.
// Remove this to remove the "edit this page" links. // Remove this to remove the "edit this page" links.
editUrl: editUrl:
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', "https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/",
// Useful options to enforce blogging best practices // Useful options to enforce blogging best practices
onInlineTags: 'warn', onInlineTags: "warn",
onInlineAuthors: 'warn', onInlineAuthors: "warn",
onUntruncatedBlogPosts: 'warn', onUntruncatedBlogPosts: "warn",
}, },
theme: { theme: {
customCss: './src/css/custom.css', customCss: "./src/css/custom.css",
}, },
} satisfies Preset.Options, } satisfies Preset.Options,
], ],
@ -64,67 +64,67 @@ const config: Config = {
themeConfig: { themeConfig: {
// Replace with your project's social card // Replace with your project's social card
image: 'img/docusaurus-social-card.jpg', image: "img/docusaurus-social-card.jpg",
navbar: { navbar: {
title: 'Lifetracker Docs', title: "Lifetracker Docs",
logo: { logo: {
alt: 'My Site Logo', alt: "My Site Logo",
src: 'img/logo.svg', src: "img/logo.svg",
}, },
items: [ items: [
{ {
type: 'docSidebar', type: "docSidebar",
sidebarId: 'tutorialSidebar', sidebarId: "tutorialSidebar",
position: 'left', position: "left",
label: 'Docs', label: "Docs",
}, },
{ to: '/blog', label: 'Blog', position: 'left' }, { to: "/blog", label: "Blog", position: "left" },
{ {
href: 'https://github.com/facebook/docusaurus', href: "https://github.com/facebook/docusaurus",
label: 'GitHub', label: "GitHub",
position: 'right', position: "right",
}, },
], ],
}, },
footer: { footer: {
style: 'dark', style: "dark",
links: [ links: [
{ {
title: 'Docs', title: "Docs",
items: [ items: [
{ {
label: 'Dev Environment', label: "Dev Environment",
to: '/docs/category/how-i-built-this', to: "/docs/category/how-i-built-this",
}, },
], ],
}, },
{ {
title: 'Community', title: "Community",
items: [ items: [
{ {
label: 'Stack Overflow', label: "Stack Overflow",
href: 'https://stackoverflow.com/questions/tagged/docusaurus', href: "https://stackoverflow.com/questions/tagged/docusaurus",
}, },
{ {
label: 'Discord', label: "Discord",
href: 'https://discordapp.com/invite/docusaurus', href: "https://discordapp.com/invite/docusaurus",
}, },
{ {
label: 'Twitter', label: "Twitter",
href: 'https://twitter.com/docusaurus', href: "https://twitter.com/docusaurus",
}, },
], ],
}, },
{ {
title: 'More', title: "More",
items: [ items: [
{ {
label: 'Blog', label: "Blog",
to: '/blog', to: "/blog",
}, },
{ {
label: 'GitHub', label: "GitHub",
href: 'https://github.com/facebook/docusaurus', href: "https://github.com/facebook/docusaurus",
}, },
], ],
}, },
@ -134,7 +134,7 @@ const config: Config = {
prism: { prism: {
theme: prismThemes.github, theme: prismThemes.github,
darkTheme: prismThemes.dracula, darkTheme: prismThemes.dracula,
additionalLanguages: ['nix', 'elixir'] additionalLanguages: ["nix", "elixir"],
}, },
} satisfies Preset.ThemeConfig, } satisfies Preset.ThemeConfig,
}; };

View File

@ -1,4 +1,4 @@
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; import type { SidebarsConfig } from "@docusaurus/plugin-content-docs";
/** /**
* Creating a sidebar enables you to: * Creating a sidebar enables you to:
@ -12,7 +12,7 @@ import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
*/ */
const sidebars: SidebarsConfig = { const sidebars: SidebarsConfig = {
// By default, Docusaurus generates a sidebar from the docs folder structure // By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], tutorialSidebar: [{ type: "autogenerated", dirName: "." }],
// But you can create a sidebar manually // But you can create a sidebar manually
/* /*

View File

@ -1,17 +1,17 @@
import clsx from 'clsx'; import clsx from "clsx";
import Heading from '@theme/Heading'; import Heading from "@theme/Heading";
import styles from './styles.module.css'; import styles from "./styles.module.css";
type FeatureItem = { type FeatureItem = {
title: string; title: string;
Svg: React.ComponentType<React.ComponentProps<'svg'>>; Svg: React.ComponentType<React.ComponentProps<"svg">>;
description: JSX.Element; description: JSX.Element;
}; };
const FeatureList: FeatureItem[] = [ const FeatureList: FeatureItem[] = [
{ {
title: 'Easy to Use', title: "Easy to Use",
Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, Svg: require("@site/static/img/undraw_docusaurus_mountain.svg").default,
description: ( description: (
<> <>
Docusaurus was designed from the ground up to be easily installed and Docusaurus was designed from the ground up to be easily installed and
@ -20,8 +20,8 @@ const FeatureList: FeatureItem[] = [
), ),
}, },
{ {
title: 'Focus on What Matters', title: "Focus on What Matters",
Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, Svg: require("@site/static/img/undraw_docusaurus_tree.svg").default,
description: ( description: (
<> <>
Docusaurus lets you focus on your docs, and we&apos;ll do the chores. Go Docusaurus lets you focus on your docs, and we&apos;ll do the chores. Go
@ -30,8 +30,8 @@ const FeatureList: FeatureItem[] = [
), ),
}, },
{ {
title: 'Powered by React', title: "Powered by React",
Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, Svg: require("@site/static/img/undraw_docusaurus_react.svg").default,
description: ( description: (
<> <>
Extend or customize your website layout by reusing React. Docusaurus can Extend or customize your website layout by reusing React. Docusaurus can
@ -41,9 +41,9 @@ const FeatureList: FeatureItem[] = [
}, },
]; ];
function Feature({title, Svg, description}: FeatureItem) { function Feature({ title, Svg, description }: FeatureItem) {
return ( return (
<div className={clsx('col col--4')}> <div className={clsx("col col--4")}>
<div className="text--center"> <div className="text--center">
<Svg className={styles.featureSvg} role="img" /> <Svg className={styles.featureSvg} role="img" />
</div> </div>

View File

@ -1,16 +1,16 @@
import clsx from 'clsx'; import clsx from "clsx";
import Link from '@docusaurus/Link'; import Link from "@docusaurus/Link";
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import Layout from '@theme/Layout'; import Layout from "@theme/Layout";
import HomepageFeatures from '@site/src/components/HomepageFeatures'; import HomepageFeatures from "@site/src/components/HomepageFeatures";
import Heading from '@theme/Heading'; import Heading from "@theme/Heading";
import styles from './index.module.css'; import styles from "./index.module.css";
function HomepageHeader() { function HomepageHeader() {
const { siteConfig } = useDocusaurusContext(); const { siteConfig } = useDocusaurusContext();
return ( return (
<header className={clsx('hero hero--primary', styles.heroBanner)}> <header className={clsx("hero hero--primary", styles.heroBanner)}>
<div className="container"> <div className="container">
<Heading as="h1" className="hero__title"> <Heading as="h1" className="hero__title">
{siteConfig.title} {siteConfig.title}
@ -19,7 +19,8 @@ function HomepageHeader() {
<div className={styles.buttons}> <div className={styles.buttons}>
<Link <Link
className="button button--secondary button--lg" className="button button--secondary button--lg"
to="/docs/category/how-i-built-this"> to="/docs/category/how-i-built-this"
>
Jump on in Jump on in
</Link> </Link>
</div> </div>
@ -33,7 +34,8 @@ export default function Home(): JSX.Element {
return ( return (
<Layout <Layout
title={`Hello from ${siteConfig.title}`} title={`Hello from ${siteConfig.title}`}
description="Description will go into a meta tag in <head />"> description="Description will go into a meta tag in <head />"
>
<HomepageHeader /> <HomepageHeader />
<main> <main>
<HomepageFeatures /> <HomepageFeatures />

View File

@ -9,6 +9,7 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@lifetracker/db": "workspace:^",
"@lifetracker/ui": "workspace:*", "@lifetracker/ui": "workspace:*",
"next": "14.2.6", "next": "14.2.6",
"react": "18.3.1", "react": "18.3.1",

View File

@ -1,8 +1,4 @@
import { import { sqliteTable, integer, text } from "drizzle-orm/sqlite-core";
sqliteTable,
integer,
text,
} from "drizzle-orm/sqlite-core";
export const days = sqliteTable("days", { export const days = sqliteTable("days", {
id: integer("id").primaryKey({ autoIncrement: true }), id: integer("id").primaryKey({ autoIncrement: true }),

1
packages/trpc/README.md Normal file
View File

@ -0,0 +1 @@
# `@lifetracker/trpc`

5
packages/trpc/index.ts Normal file
View File

@ -0,0 +1,5 @@
import { initTRPC } from "@trpc/server";
const t = initTRPC.create();
export const router = t.router;
export const publicProcedure = t.procedure;

View File

@ -0,0 +1,35 @@
{
"name": "@lifetracker/trpc",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"typecheck": "tsc --noEmit",
"format": "prettier . --ignore-path ../../.prettierignore",
"lint": "eslint .",
"test": "vitest"
},
"dependencies": {
"@lifetracker/db": "workspace:*",
"@lifetracker/ui": "workspace:*",
"@trpc/server": "^10.45.2",
"bcryptjs": "^2.4.3",
"drizzle-orm": "^0.33.0",
"superjson": "^2.2.1",
"tiny-invariant": "^1.3.3",
"zod": "^3.23.8"
},
"devDependencies": {
"@lifetracker/eslint-config": "workspace:*",
"@lifetracker/typescript-config": "workspace:*",
"@types/bcryptjs": "^2.4.6",
"vite-tsconfig-paths": "^5.1.0",
"vitest": "^2.1.4"
},
"eslintConfig": {
"root": true,
"extends": [
"@lifetracker/eslint-config/base"
]
}
}

View File

@ -0,0 +1,8 @@
import { router } from "../index";
import { helloWorldRouter } from "./helloWorld";
export const appRouter = router({
helloWorld: helloWorldRouter,
});
// export type definition of API
export type AppRouter = typeof appRouter;

View File

@ -0,0 +1,5 @@
import { publicProcedure, router } from "../index";
export const helloWorldRouter = router({
greeting: publicProcedure.query(() => "hello tRPC v10!"),
});

View File

@ -0,0 +1,13 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@lifetracker/typescript-config/node.json",
"include": [
"**/*.ts"
],
"exclude": [
"node_modules"
],
"compilerOptions": {
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
}
}

View File

@ -0,0 +1,14 @@
/// <reference types="vitest" />
import tsconfigPaths from "vite-tsconfig-paths";
import { defineConfig } from "vitest/config";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [tsconfigPaths()],
test: {
alias: {
"@/*": "./*",
},
},
});

View File

@ -1,20 +1,31 @@
{ {
"$schema": "https://json.schemastore.org/tsconfig", "$schema": "https://json.schemastore.org/tsconfig",
"display": "Default",
"compilerOptions": { "compilerOptions": {
"declaration": true, "target": "ES2022",
"declarationMap": true, "lib": [
"esModuleInterop": true, "dom",
"incremental": false, "dom.iterable",
"isolatedModules": true, "ES2022"
"lib": ["es2022", "DOM", "DOM.Iterable"], ],
"module": "NodeNext", "allowJs": true,
"moduleDetection": "force",
"moduleResolution": "NodeNext",
"noUncheckedIndexedAccess": true,
"resolveJsonModule": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
"target": "ES2022" "noEmit": true,
} "esModuleInterop": true,
"module": "esnext",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"moduleDetection": "force",
"jsx": "preserve",
"incremental": true,
"noUncheckedIndexedAccess": false
},
"exclude": [
"node_modules",
"build",
"dist",
".next",
".expo"
]
} }

View File

@ -0,0 +1,22 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/node21/tsconfig.json",
"include": [
"**/*.ts"
],
"exclude": [
"node_modules",
"build",
"dist",
".next",
".expo"
],
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "node",
"incremental": true,
"isolatedModules": true,
"strict": true,
"esModuleInterop": true
}
}

View File

@ -1,9 +1,11 @@
{ {
"name": "@lifetracker/typescript-config", "name": "@lifetracker/typescript-config",
"version": "0.0.0", "version": "0.1.0",
"private": true, "files": [
"license": "MIT", "base.json",
"publishConfig": { "node.json"
"access": "public" ],
"devDependencies": {
"@tsconfig/node21": "^21.0.1"
} }
} }

1403
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -24,8 +24,7 @@
"dependsOn": [ "dependsOn": [
"^dev" "^dev"
], ],
"cache": false, "cache": false
"persistent": true
} }
} }
} }