diff --git a/.forgejo/workflows/deploy.yaml b/.forgejo/workflows/deploy.yaml deleted file mode 100644 index c6a05d0..0000000 --- a/.forgejo/workflows/deploy.yaml +++ /dev/null @@ -1,58 +0,0 @@ -name: Deploy - -on: - push: - branches: - - main - - astra/ci - -jobs: - deploy: - name: Deploy - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: '20' - - - name: Setup Deno - uses: https://github.com/denoland/setup-deno@v2 - - - name: Install dependencies - run: deno install - - - name: Build project - run: deno task build - - - name: Setup SSH - run: | - mkdir -p ~/.ssh - echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 - cat > ~/.ssh/config << EOF - Host deploy - HostName ${{ vars.SERVER_HOST }} - User ${{ vars.SERVER_USER }} - IdentityFile ~/.ssh/id_ed25519 - StrictHostKeyChecking accept-new - BatchMode yes - PasswordAuthentication no - PubkeyAuthentication yes - EOF - chmod 600 ~/.ssh/config - ssh-keyscan -H ${{ vars.SERVER_HOST }} >> ~/.ssh/known_hosts - echo "Deploying to ${{ vars.SERVER_HOST }} as ${{ vars.SERVER_USER }} to /var/www/pds/${{ github.ref_name }}" - - - name: Debug SSH Connection - run: ssh -v deploy echo "SSH Connection Successful" - - - name: Create folder if not exists - run: ssh deploy "mkdir -p /var/www/pds/${{ github.ref_name }}" - - - name: Deploy via SCP - run: scp -r ./dist/* deploy:/var/www/pds/${{ github.ref_name }} diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 581839a..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -# MIT License - -Copyright (c) 2025 Witchcraft Systems - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index d9eb2ea..6194e14 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,3 @@ # pds-dash -a frontend dashboard with stats for your ATProto PDS. - -## setup - -### prerequisites - -- [deno](https://deno.com/manual/getting_started/installation) - -### installing - -clone the repo, install dependencies using deno: - -```sh -deno install -``` - -### development server - -local develompent server with hot reloading: - -```sh -deno task dev -``` - -### building - -to build the optimized bundle run: - -```sh -deno task build -``` - -the output will be in the `dist/` directory. - -## deploying - -we use our own CI/CD workflow at [`.forgejo/workflows/deploy.yaml`](.forgejo/workflows/deploy.yaml), but it boils down to building the project bundle and deploying it to a web server. it'll probably make more sense to host it on the same domain as your PDS, but it doesn't affect anything if you host it somewhere else. - -## configuring - -[`config.ts`](config.ts) is the main configuration file, you can find more information in the file itself. - -## theming - -the colors are designated in [`src/app.css`](src/app.css) as variables, go crazy with them - -the rest is done by editing the css files and style tags directly, good luck - -relevant files: - -- [`src/App.svelte`](src/App.svelte) -- [`src/app.css`](src/app.css) -- [`src/lib/AccountComponent.svelte`](src/lib/AccountComponent.svelte) -- [`src/lib/PostComponent.svelte`](src/lib/PostComponent.svelte) - -the favicon is located at [`public/favicon.ico`](public/favicon.ico) - -## license - -MIT +Frontend with stats for your ATProto PDS \ No newline at end of file diff --git a/config.ts b/config.ts index 8d09cf6..af29271 100644 --- a/config.ts +++ b/config.ts @@ -2,36 +2,15 @@ * Configuration module for the PDS Dashboard */ export class Config { - /** - * The base URL of the PDS (Personal Data Server) - * @default "https://pds.witchcraft.systems" - */ - static readonly PDS_URL: string = "https://pds.witchcraft.systems"; + /** + * The base URL of the PDS (Personal Data Server) + * @default "https://pds.witchcraft.systems" + */ + static readonly PDS_URL: string = "https://pds.witchcraft.systems"; - /** - * The base URL of the frontend service for linking to replies/quotes/accounts etc. - * @default "https://deer.social" - */ - static readonly FRONTEND_URL: string = "https://deer.social"; - - /** - * Maximum number of posts to fetch from the PDS per request - * Should be around 20 for about 10 users on the pds - * The more users you have, the lower the number should be - * since sorting is slow and is done on the frontend - * @default 20 - */ - static readonly MAX_POSTS: number = 20; - - /** - * Footer text for the dashboard, you probably want to change this - */ - static readonly FOOTER_TEXT: string = - "Astrally projected from witchcraft.systems

Source (github mirror)"; - - /** - * Whether to show the posts that are in the future - * @default false - */ - static readonly SHOW_FUTURE_POSTS: boolean = false; -} + /** + * The base URL of the frontend service for linking to replies + * @default "https://deer.social" + */ + static readonly FRONTEND_URL: string = "https://deer.social"; +} \ No newline at end of file diff --git a/deno.lock b/deno.lock index 724a5c0..df8c920 100644 --- a/deno.lock +++ b/deno.lock @@ -6,9 +6,7 @@ "npm:@atcute/identity-resolver@~0.1.2": "0.1.2_@atcute+identity@0.1.3", "npm:@sveltejs/vite-plugin-svelte@^5.0.3": "5.0.3_svelte@5.28.1__acorn@8.14.1_vite@6.3.2__picomatch@4.0.2", "npm:@tsconfig/svelte@^5.0.4": "5.0.4", - "npm:moment@^2.30.1": "2.30.1", "npm:svelte-check@^4.1.5": "4.1.6_svelte@5.28.1__acorn@8.14.1_typescript@5.7.3", - "npm:svelte-infinite-loading@^1.4.0": "1.4.0", "npm:svelte@^5.23.1": "5.28.1_acorn@8.14.1", "npm:typescript@~5.7.2": "5.7.3", "npm:vite@^6.3.1": "6.3.2_picomatch@4.0.2" @@ -339,9 +337,6 @@ "@jridgewell/sourcemap-codec" ] }, - "moment@2.30.1": { - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==" - }, "mri@1.2.0": { "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==" }, @@ -416,9 +411,6 @@ "typescript" ] }, - "svelte-infinite-loading@1.4.0": { - "integrity": "sha512-Jo+f/yr/HmZQuIiiKKzAHVFXdAUWHW2RBbrcQTil8JVk1sCm/riy7KTJVzjBgQvHasrFQYKF84zvtc9/Y4lFYg==" - }, "svelte@5.28.1_acorn@8.14.1": { "integrity": "sha512-iOa9WmfNG95lSOSJdMhdjJ4Afok7IRAQYXpbnxhd5EINnXseG0GVa9j6WPght4eX78XfFez45Fi+uRglGKPV/Q==", "dependencies": [ @@ -478,9 +470,7 @@ "npm:@atcute/identity-resolver@~0.1.2", "npm:@sveltejs/vite-plugin-svelte@^5.0.3", "npm:@tsconfig/svelte@^5.0.4", - "npm:moment@^2.30.1", "npm:svelte-check@^4.1.5", - "npm:svelte-infinite-loading@^1.4.0", "npm:svelte@^5.23.1", "npm:typescript@~5.7.2", "npm:vite@^6.3.1" diff --git a/index.html b/index.html index adcfab3..f71d006 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,4 @@ - + diff --git a/package.json b/package.json index 1db6461..59269d2 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,7 @@ "dependencies": { "@atcute/bluesky": "^2.0.2", "@atcute/client": "^3.0.1", - "@atcute/identity-resolver": "^0.1.2", - "moment": "^2.30.1", - "svelte-infinite-loading": "^1.4.0" + "@atcute/identity-resolver": "^0.1.2" }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.3", diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.svelte b/src/App.svelte index 733320e..a8a0033 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -1,71 +1,40 @@
- {#await accountsPromise} -

Loading...

- {:then accountsData} -
-

ATProto PDS

-

Home to {accountsData.length} accounts

-
- {#each accountsData as accountObject} - - {/each} -
-

{@html Config.FOOTER_TEXT}

-
- {:catch error} -

Error: {error.message}

- {/await} + {#await accountsPromise} +

Loading...

+ {:then accountsData} +
+

ATProto PDS

+

Home to {accountsData.length} accounts

+ {#each accountsData as accountObject} + + {/each} +
+ {:catch error} +

Error: {error.message}

+ {/await} + {#await postsPromise} +

Loading...

+ {:then postsData}
-
- {#each posts as postObject} + {#each postsData as postObject} {/each} - -
+ {/await}
diff --git a/src/app.css b/src/app.css index 4c343e2..2232051 100644 --- a/src/app.css +++ b/src/app.css @@ -1,44 +1,15 @@ @font-face { - font-family: "ProggyClean"; + font-family: 'ProggyClean'; src: url(https://witchcraft.systems/ProggyCleanNerdFont-Regular.ttf); } -:root { - --link-color: #646cff; - --link-hover-color: #535bf2; - --background-color: #12082b; - --header-background-color: #1f1145; - --content-background-color: #0d0620; - --text-color: white; - --border-color: #8054f0; - --indicator-inactive-color: #4a4a4a; - --indicator-active-color: #8054f0; -} - ::-webkit-scrollbar { width: 0px; background: transparent; - padding: 0; - margin: 0; -} -::-webkit-scrollbar-thumb { - background: transparent; - border-radius: 0; -} -::-webkit-scrollbar-track { - background: transparent; - border-radius: 0; -} -::-webkit-scrollbar-corner { - background: transparent; - border-radius: 0; -} -::-webkit-scrollbar-button { - background: transparent; - border-radius: 0; } + * { - scrollbar-width: none; + scrollbar-width: thin; scrollbar-color: transparent transparent; -ms-overflow-style: none; /* IE and Edge */ -webkit-overflow-scrolling: touch; @@ -47,12 +18,11 @@ a { font-weight: 500; - color: var(--link-color); + color: #646cff; text-decoration: inherit; } a:hover { - color: var(--link-hover-color); - text-decoration: underline; + color: #535bf2; } body { @@ -61,11 +31,11 @@ body { place-items: center; min-width: 320px; min-height: 100vh; - background-color: var(--background-color); - font-family: "ProggyClean", monospace; + background-color: #12082b; + font-family: 'ProggyClean', monospace; font-size: 24px; - color: var(--text-color); - border-color: var(--border-color); + color: white; + border-color: #8054f0; } h1 { @@ -75,9 +45,9 @@ h1 { #app { max-width: 1400px; - margin: 0; - padding: 0; - margin-left: auto; - margin-right: auto; + margin: 0 auto; + padding: 2rem; text-align: center; } + + diff --git a/src/assets/svelte.svg b/src/assets/svelte.svg new file mode 100644 index 0000000..c5e0848 --- /dev/null +++ b/src/assets/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/lib/AccountComponent.svelte b/src/lib/AccountComponent.svelte index e324393..4c987c8 100644 --- a/src/lib/AccountComponent.svelte +++ b/src/lib/AccountComponent.svelte @@ -1,48 +1,49 @@ -
- {#if account.avatarCid} - avatar of {account.displayName} - {/if} -
- {account.displayName || account.handle || account.did} +
+ {#if account.avatarCid} + avatar of {account.displayName} + {/if} +
+ {account.displayName || account.handle || account.did} +
-
diff --git a/src/lib/PostComponent.svelte b/src/lib/PostComponent.svelte index dc0b874..f077582 100644 --- a/src/lib/PostComponent.svelte +++ b/src/lib/PostComponent.svelte @@ -1,59 +1,7 @@
@@ -66,229 +14,110 @@ /> {/if}
{#if post.replyingUri} replying to {post.replyingUri.repo} {/if} +

{post.text}

+ {#if post.quotingUri} quoting {post.quotingUri.repo} {/if} -
{post.text}
- {#if post.imagesCid && post.imagesCid.length > 0} -
- Post Image {currentImageIndex + 1} of {post.imagesCid.length} - - {#if post.imagesCid.length > 1} -
- -
- {#each post.imagesCid as _, i} -
- {/each} -
- -
- {/if} + {#if post.imagesCid} +
+ {#each post.imagesCid as imageLink} + Post Image + {/each}
{/if} {#if post.videosLinkCid} - + /> {/if}
diff --git a/src/lib/pdsfetch.ts b/src/lib/pdsfetch.ts index 79edab0..ed24c45 100644 --- a/src/lib/pdsfetch.ts +++ b/src/lib/pdsfetch.ts @@ -18,15 +18,11 @@ import { Config } from "../../config"; // import { AppBskyActorDefs } from "@atcute/client/lexicons"; interface AccountMetadata { - did: At.Did; + did: string; displayName: string; handle: string; avatarCid: string | null; - currentCursor?: string; } - -let accountsMetadata: AccountMetadata[] = []; - interface atUriObject { repo: string; collection: string; @@ -36,7 +32,6 @@ class Post { authorDid: string; authorAvatarCid: string | null; postCid: string; - recordName: string; authorHandle: string; displayName: string; text: string; @@ -52,7 +47,6 @@ class Post { account: AccountMetadata, ) { this.postCid = record.cid; - this.recordName = processAtUri(record.uri).rkey; this.authorDid = account.did; this.authorAvatarCid = account.avatarCid; this.authorHandle = account.handle; @@ -71,8 +65,8 @@ class Post { this.videosLinkCid = null; switch (post.embed?.$type) { case "app.bsky.embed.images": - this.imagesCid = post.embed.images.map( - (imageRecord: any) => imageRecord.image.ref.$link, + this.imagesCid = post.embed.images.map((imageRecord: any) => + imageRecord.image.ref.$link ); break; case "app.bsky.embed.video": @@ -85,8 +79,8 @@ class Post { this.quotingUri = processAtUri(post.embed.record.record.uri); switch (post.embed.media.$type) { case "app.bsky.embed.images": - this.imagesCid = post.embed.media.images.map( - (imageRecord) => imageRecord.image.ref.$link, + this.imagesCid = post.embed.media.images.map((imageRecord) => + imageRecord.image.ref.$link ); break; @@ -115,50 +109,77 @@ const rpc = new XRPC({ }), }); -const getDidsFromPDS = async (): Promise => { +const getDidsFromPDS = async () => { const { data } = await rpc.get("com.atproto.sync.listRepos", { params: {}, }); - return data.repos.map((repo: any) => repo.did) as At.Did[]; + return data.repos.map((repo: any) => (repo.did)); }; -const getAccountMetadata = async ( - did: `did:${string}:${string}`, -) => { +const getAccountMetadata = async (did: `did:${string}:${string}`) => { // gonna assume self exists in the app.bsky.actor.profile try { - const { data } = await rpc.get("com.atproto.repo.getRecord", { - params: { - repo: did, - collection: "app.bsky.actor.profile", - rkey: "self", - }, - }); - const value = data.value as AppBskyActorProfile.Record; - const handle = await blueskyHandleFromDid(did); - const account: AccountMetadata = { - did: did, - handle: handle, - displayName: value.displayName || "", + const { data } = await rpc.get("com.atproto.repo.getRecord", { + params: { + repo: did, + collection: "app.bsky.actor.profile", + rkey: "self", + }, + }); + const value = data.value as AppBskyActorProfile.Record; + const handle = await blueskyHandleFromDid(did); + const account: AccountMetadata = { + did: did, + handle: handle, + displayName: value.displayName || "", + avatarCid: null, + }; + if (value.avatar) { + account.avatarCid = value.avatar.ref["$link"]; + } + return account; + } + catch (e) { + console.error(`Error fetching metadata for ${did}:`, e); + return { + did: "error", + displayName: "", avatarCid: null, }; - if (value.avatar) { - account.avatarCid = value.avatar.ref["$link"]; - } - return account; - } catch (e) { - console.error(`Error fetching metadata for ${did}:`, e); - return null; } }; -const getAllMetadataFromPds = async (): Promise => { +const getAllMetadataFromPds = async () => { const dids = await getDidsFromPDS(); const metadata = await Promise.all( dids.map(async (repo: `did:${string}:${string}`) => { return await getAccountMetadata(repo); }), ); - return metadata.filter((account) => account !== null) as AccountMetadata[]; + return metadata.filter(account => account.did !== "error"); +}; + +const fetchPosts = async (did: string) => { + try { + const { data } = await rpc.get("com.atproto.repo.listRecords", { + params: { + repo: did as At.Identifier, + collection: "app.bsky.feed.post", + limit: 5, + }, + }); + return { + records: data.records as ComAtprotoRepoListRecords.Record[], + did: did, + error: false + }; + } catch (e) { + console.error(`Error fetching posts for ${did}:`, e); + return { + records: [], + did: did, + error: true + }; + } }; const identityResolve = async (did: At.Did) => { @@ -194,146 +215,34 @@ const blueskyHandleFromDid = async (did: At.Did) => { } }; -interface PostsAcc { - posts: ComAtprotoRepoListRecords.Record[]; - account: AccountMetadata; -} -const getCutoffDate = (postAccounts: PostsAcc[]) => { - const now = Date.now(); - let cutoffDate: Date | null = null; - postAccounts.forEach((postAcc) => { - const latestPost = new Date( - (postAcc.posts[postAcc.posts.length - 1].value as AppBskyFeedPost.Record) - .createdAt, - ); - if (!cutoffDate) { - cutoffDate = latestPost; - } else { - if (latestPost > cutoffDate) { - cutoffDate = latestPost; +const fetchAllPosts = async () => { + const users: AccountMetadata[] = await getAllMetadataFromPds(); + const postRecords = await Promise.all( + users.map(async (metadata: AccountMetadata) => + await fetchPosts(metadata.did) + ), + ); + const validPostRecords = postRecords.filter(record => !record.error); + const posts: Post[] = validPostRecords.flatMap((userFetch) => + userFetch.records.map((record) => { + const user = users.find((user: AccountMetadata) => + user.did == userFetch.did + ); + if (!user) { + throw new Error(`User with DID ${userFetch.did} not found`); } - } - }); - if (cutoffDate) { - return cutoffDate; - } else { - return new Date(now); - } -}; - -const filterPostsByDate = (posts: PostsAcc[], cutoffDate: Date) => { - // filter posts for each account that are older than the cutoff date and save the cursor of the last post included - const filteredPosts: PostsAcc[] = posts.map((postAcc) => { - const filtered = postAcc.posts.filter((post) => { - const postDate = new Date( - (post.value as AppBskyFeedPost.Record).createdAt, - ); - return postDate >= cutoffDate; - }); - if (filtered.length > 0) { - postAcc.account.currentCursor = processAtUri(filtered[filtered.length - 1].uri).rkey; - } - return { - posts: filtered, - account: postAcc.account, - }; - }); - return filteredPosts; -}; -// nightmare function. However it works so I am not touching it -const getNextPosts = async () => { - if (!accountsMetadata.length) { - accountsMetadata = await getAllMetadataFromPds(); - } - - const postsAcc: PostsAcc[] = await Promise.all( - accountsMetadata.map(async (account) => { - const posts = await fetchPostsForUser( - account.did, - account.currentCursor || null, - ); - if (posts) { - return { - posts: posts, - account: account, - }; - } else { - return { - posts: [], - account: account, - }; - } - }), + return new Post(record, user); + }) ); - const recordsFiltered = postsAcc.filter((postAcc) => - postAcc.posts.length > 0 - ); - const cutoffDate = getCutoffDate(recordsFiltered); - const recordsCutoff = filterPostsByDate(recordsFiltered, cutoffDate); - // update the accountMetadata with the new cursor - accountsMetadata = accountsMetadata.map((account) => { - const postAcc = recordsCutoff.find( - (postAcc) => postAcc.account.did == account.did, - ); - if (postAcc) { - account.currentCursor = postAcc.account.currentCursor; - } - return account; - } - ); - // throw the records in a big single array - let records = recordsCutoff.flatMap((postAcc) => postAcc.posts); - // sort the records by timestamp - records = records.sort((a, b) => { - const aDate = new Date( - (a.value as AppBskyFeedPost.Record).createdAt, - ).getTime(); - const bDate = new Date( - (b.value as AppBskyFeedPost.Record).createdAt, - ).getTime(); - return bDate - aDate; - }); - // filter out posts that are in the future - if (!Config.SHOW_FUTURE_POSTS) { - const now = Date.now(); - records = records.filter((post) => { - const postDate = new Date( - (post.value as AppBskyFeedPost.Record).createdAt, - ).getTime(); - return postDate <= now; - }); - } - - const newPosts = records.map((record) => { - const account = accountsMetadata.find( - (account) => account.did == processAtUri(record.uri).repo, - ); - if (!account) { - throw new Error( - `Account with DID ${processAtUri(record.uri).repo} not found`, - ); - } - return new Post(record, account); - }); - return newPosts; + posts.sort((a, b) => b.timestamp - a.timestamp); + return posts; }; -const fetchPostsForUser = async (did: At.Did, cursor: string | null) => { - try { - const { data } = await rpc.get("com.atproto.repo.listRecords", { - params: { - repo: did as At.Identifier, - collection: "app.bsky.feed.post", - limit: Config.MAX_POSTS, - cursor: cursor || undefined, - }, - }); - return data.records as ComAtprotoRepoListRecords.Record[]; - } catch (e) { - console.error(`Error fetching posts for ${did}:`, e); - return null; - } +const testApiCall = async () => { + const { data } = await rpc.get("com.atproto.sync.listRepos", { + params: {}, + }); + console.log(data); }; - -export { getAllMetadataFromPds, getNextPosts, Post }; +export { fetchAllPosts, getAllMetadataFromPds, Post }; export type { AccountMetadata }; diff --git a/src/main.ts b/src/main.ts index d47b930..664a057 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,9 +1,9 @@ -import { mount } from "svelte"; -import "./app.css"; -import App from "./App.svelte"; +import { mount } from 'svelte' +import './app.css' +import App from './App.svelte' const app = mount(App, { - target: document.getElementById("app")!, -}); + target: document.getElementById('app')!, +}) -export default app; +export default app diff --git a/svelte.config.js b/svelte.config.js index de2ddd6..b0683fd 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,7 +1,7 @@ -import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' export default { // Consult https://svelte.dev/docs#compile-time-svelte-preprocess // for more information about preprocessors preprocess: vitePreprocess(), -}; +} diff --git a/vite.config.ts b/vite.config.ts index 20d2272..d32eba1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,7 @@ -import { defineConfig } from "vite"; -import { svelte } from "@sveltejs/vite-plugin-svelte"; +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' // https://vite.dev/config/ export default defineConfig({ plugins: [svelte()], -}); +})