9 Commits

Author SHA1 Message Date
pheralb 55199765be 🛠️ Improve parseSvgFilename to support firstUpperCase option for component naming
📦 Build / 🛠️ Build app (push) Has been cancelled
🧑‍🚀 Check / ⚙️ Linting (push) Has been cancelled
🧑‍🚀 Check / 📦 SVGs Size (push) Has been cancelled
2025-08-31 16:35:30 +01:00
pheralb 4cd2c84273 🛠️ Refactor parseSvgFilename function for improved component name formatting; add directory and file existence checks in generate-registry 2025-08-31 16:19:44 +01:00
pheralb 733e136b3a 🛠️ Update registry command & logs 2025-08-31 15:46:43 +01:00
pheralb 2c3fdf79fe 🐋 Initial Dockerfile config with .dockerignore 2025-08-31 15:35:11 +01:00
pheralb 374fb8f2d5 🛠️ Trying nixpacks config for Node & PNPM setup 2025-08-31 14:56:11 +01:00
pheralb 2927e42659 🛠️ Trying nixpacks config for Node & PNPM setup 2025-08-31 14:45:42 +01:00
pheralb d947f7f907 🛠️ Remove nixpacks config file 2025-08-31 14:29:10 +01:00
pheralb d2e418363d 🛠️ Refactor ESLint rules for Svelte components to improve linting accuracy 2025-08-31 14:26:04 +01:00
pheralb 5df7a336ba 🛠️ Fixed eslint errors 2025-08-31 14:25:57 +01:00
15 changed files with 121 additions and 94 deletions
+19
View File
@@ -0,0 +1,19 @@
Dockerfile
.dockerignore
.git
.gitignore
.gitattributes
README.md
.npmrc
.prettierrc
prettier.config.mjs
.eslintrc.cjs
eslint.config.mjs
.graphqlrc
.editorconfig
.svelte-kit
.vscode
node_modules
build
package
**/.env
+38
View File
@@ -0,0 +1,38 @@
FROM node:22.17.0-alpine AS base
# Install pnpm
RUN npm install -g pnpm@10.13.1
# Set working directory
WORKDIR /app
# Install dependencies with cache
FROM base AS deps
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
# Build the application
FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV PUBLIC_SVGL_VERSION=beta
RUN pnpm run build:prod
# Production image
FROM node:22.17.0-alpine AS runner
WORKDIR /app
# Copy necessary files from builder
COPY --from=builder /app/build ./build
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./
# Set production environment
ENV NODE_ENV=production
ENV PUBLIC_SVGL_VERSION=beta
# Expose port
EXPOSE 3000
# Start the server
CMD ["node", "build"]
+3 -8
View File
@@ -41,14 +41,9 @@ export default ts.config(
},
rules: {
"svelte/no-unused-svelte-ignore": "warn",
"@typescript-eslint/no-unused-vars": [
"warn",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^(_|\\$\\$)",
destructuredArrayIgnorePattern: "^_",
},
],
"svelte/no-useless-mustaches": "warn",
"svelte/require-each-key": "warn",
"svelte/no-at-html-tags": "off",
},
},
);
+33 -37
View File
@@ -13,7 +13,6 @@ import { parseReactSvgContent } from "./src/utils/parseReactSvgContent";
const execAsync = promisify(exec);
// ⚙️ Settings:
const MINIFY_TSX = false;
const REGENERATE_ALL = true;
const SVGS_DATA = svgs;
const PUBLIC_FOLDER = "static";
@@ -31,7 +30,6 @@ interface RegistryItem {
name: string;
type: string;
title: string;
registryDependencies: string[];
files: RegistryFile[];
}
@@ -59,7 +57,7 @@ function prepareRegistryJson(): ShadcnSchema {
.toLowerCase()
.replace(/\s+/g, "-")
.replace(/[^a-z0-9-]/g, "");
const files: RegistryFile[] = [];
const svgPaths = extractSvgPaths(svg);
@@ -81,7 +79,6 @@ function prepareRegistryJson(): ShadcnSchema {
name: componentName,
type: "registry:component",
title: componentName,
registryDependencies: [""],
files: files,
});
}
@@ -188,41 +185,18 @@ function convertToFilesystemPath(svgPath: string): string {
return `./${PUBLIC_FOLDER}/${cleanPath}`;
}
function createSpinner() {
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
let current = 0;
let interval;
return {
start(getMessage) {
interval = setInterval(() => {
const message =
typeof getMessage === "function" ? getMessage() : getMessage;
process.stdout.write(`\r${frames[current]} ${message}`);
current = (current + 1) % frames.length;
}, 100);
},
stop() {
if (interval) {
clearInterval(interval);
process.stdout.write("\r");
}
},
};
}
async function convertSvgToReact(svgPath: string): Promise<string> {
const rawSvg = await fs.promises.readFile(svgPath, "utf-8");
const optimizedSvg = optimizeSvg({ svgCode: rawSvg });
const componentName = parseSvgFilename({
file: path.basename(svgPath, ".svg"),
log: true,
firstUpperCase: true,
});
const code = await parseReactSvgContent({
componentName,
svgCode: optimizedSvg,
typescript: true,
minify: MINIFY_TSX,
});
return code;
}
@@ -263,8 +237,35 @@ async function runShadcnBuild() {
}
}
const checkFinallyDirs = async () => {
// Check if static/r directory exists
const rDirExists = await fs.promises
.access(`./${PUBLIC_FOLDER}/r`)
.then(() => true)
.catch(() => false);
if (!rDirExists) {
console.error("[🔎] Error - Directory ./static/r does not exist");
return;
} else {
console.log("[🔎] Directory ./static/r exists");
}
// Check if registry.json exists
const registryExists = await fs.promises
.access("./registry.json")
.then(() => true)
.catch(() => false);
if (!registryExists) {
console.error("[🔎] Error - File registry.json does not exist");
return;
} else {
console.log("[🔎] File registry.json exists");
}
};
async function run() {
const spinner = createSpinner();
let convertedCount = 0;
let totalCount = 0;
@@ -287,9 +288,7 @@ async function run() {
return;
}
spinner.start(
() => `[📦] ${convertedCount}/${totalCount} SVGs converted to TSX`,
);
console.log(`[📦] Converting ${totalCount} SVGs converted to TSX...`);
// Process files
for (const svgFile of svgFiles) {
@@ -319,11 +318,8 @@ async function run() {
throw new Error(error);
}
}
spinner.stop();
console.log(
`\n[✅] Conversion completed: ${convertedCount}/${totalCount} SVGs processed`,
`\n[📦] ✨ Conversion completed: ${convertedCount}/${totalCount} SVGs processed`,
);
if (convertedCount < totalCount) {
@@ -335,10 +331,10 @@ async function run() {
await runShadcnBuild();
}
} catch (error) {
spinner.stop();
console.error("[❌] Error:", error);
throw new Error(error);
} finally {
await checkFinallyDirs();
await cleanupDirectory(OUTPUT_DIR);
console.log("[🎉] Process completed");
}
-20
View File
@@ -1,20 +0,0 @@
[variables]
NODE_VERSION = "22.17.0"
PNPM_VERSION = "10.13.1"
[providers.node]
version = "22.17.0"
[phases.setup]
cmds = ["npm install -g pnpm@10.13.1"]
[phases.install]
cmds = ["pnpm install --frozen-lockfile"]
[phases.build]
# Build command using PNPM
cmds = ["pnpm run build"]
[start]
# Start command using PNPM
cmd = "node build"
+1 -1
View File
@@ -37,7 +37,7 @@
"lint": "eslint ./src",
"lint:fix": "eslint ./src --fix",
"build:shadcn": "shadcn build --output ./static/r",
"build:prod": "vite build && pnpm build:registry",
"build:prod": "pnpm build:registry && vite build",
"build:registry": "tsx ./generate-registry.ts"
},
"dependencies": {
+10 -14
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import type { Component, Snippet, SvelteComponent } from "svelte";
import type { Component } from "svelte";
import { cn } from "@/utils/cn";
import CopyIcon from "@lucide/svelte/icons/copy";
@@ -18,21 +18,17 @@
let timeoutId: ReturnType<typeof setTimeout> | null = null;
const handleCopy = async () => {
try {
await clipboard(code);
await clipboard(code);
if (timeoutId) {
clearTimeout(timeoutId);
}
copied = true;
timeoutId = setTimeout(() => {
copied = false;
timeoutId = null;
}, copyDuration);
} catch (error) {
copied = false;
if (timeoutId) {
clearTimeout(timeoutId);
}
copied = true;
timeoutId = setTimeout(() => {
copied = false;
timeoutId = null;
}, copyDuration);
};
$effect(() => {
+1 -1
View File
@@ -19,7 +19,7 @@
});
</script>
{#each categories.sort() as category}
{#each categories.sort() as category (category)}
<a
href={`/directory/${category.toLowerCase()}`}
data-sveltekit-preload-data
+1 -1
View File
@@ -27,7 +27,7 @@
{/if}
</Select.Trigger>
<Select.Content sideOffset={1.5}>
{#each Object.entries(managers) as [value, { Icon, label }]}
{#each Object.entries(managers) as [value, { Icon, label }] (value)}
<Select.Item
{value}
onclick={() => pkgManager.set(value as PackageManager)}
@@ -18,7 +18,7 @@
<Dialog.Root>
<Dialog.Trigger>
{@render children?.()}
{@render children()}
</Dialog.Trigger>
<Dialog.Content class="text-sm">
<Dialog.Header>
@@ -33,6 +33,6 @@
</p>
<CodeBlock code={registryCode} />
<p class="mt-2">2. Then use the following command to add SVGs:</p>
<CodeBlock code={`npx shadcn@latest add @svgl/[svg-name]`} />
<CodeBlock code="npx shadcn@latest add @svgl/[svg-name]" />
</Dialog.Content>
</Dialog.Root>
+2 -2
View File
@@ -143,7 +143,7 @@
</p>
<div class="flex items-center justify-center space-x-1">
{#if Array.isArray(svgInfo.category)}
{#each svgInfo.category.slice(0, maxVisibleCategories) as c, index}
{#each svgInfo.category.slice(0, maxVisibleCategories) as c (c)}
<a
href={`/directory/${c.toLowerCase()}`}
class={badgeVariants({
@@ -176,7 +176,7 @@
</Popover.Trigger>
<Popover.Content class="flex w-auto flex-col space-y-2">
<p class="font-medium">More tags:</p>
{#each svgInfo.category.slice(maxVisibleCategories) as c}
{#each svgInfo.category.slice(maxVisibleCategories) as c (c)}
<a
href={`/directory/${c.toLowerCase()}`}
class={cn(buttonVariants({ variant: "outline" }), "w-full")}
+1 -1
View File
@@ -3,7 +3,7 @@
HTMLAnchorAttributes,
HTMLButtonAttributes,
} from "svelte/elements";
import { type VariantProps, tv } from "tailwind-variants";
import type { VariantProps } from "tailwind-variants";
import type { WithElementRef } from "@/types/components";
import { cn } from "@/utils/cn";
+1 -1
View File
@@ -105,7 +105,7 @@
</PageHeader>
<Container className="my-6">
<Grid>
{#each displaySvgs as svg}
{#each displaySvgs as svg (svg.id)}
<SvgCard svgInfo={svg} />
{/each}
</Grid>
+2 -2
View File
@@ -27,7 +27,7 @@
// States:
let searchTerm = $state<string>(data.searchTerm || "");
let filteredSvgs = $state<iSVG[]>(data.filteredSvgs);
let filteredSvgs = $derived<iSVG[]>(data.filteredSvgs);
const searchSvgs = () => {
if (!searchTerm) {
@@ -120,7 +120,7 @@
</PageHeader>
<Container className="my-6">
<Grid>
{#each filteredSvgs as svg}
{#each filteredSvgs as svg (svg.id)}
<SvgCard svgInfo={svg} />
{/each}
</Grid>
+7 -4
View File
@@ -1,15 +1,18 @@
interface ParseSvgFilename {
file: string;
log?: boolean;
firstUpperCase?: boolean;
}
export const parseSvgFilename = (params: ParseSvgFilename): string => {
const { file, log } = params;
const { file, log, firstUpperCase = false } = params;
const name = file.replace(/\.svg$/i, "");
let component = name.replace(/(^\w|[-_]\w)/g, (m) =>
m.replace(/[-_]/, "").toUpperCase(),
);
let component = name
.replace(/[-_\s]+(.)?/g, (_, char) => (char ? char.toUpperCase() : ""))
.replace(/^(.)/, (char) =>
firstUpperCase ? char.toUpperCase() : char.toLowerCase(),
);
if (/^\d/.test(component)) {
if (log) {