composter-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../src/index.js'
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "composter-cli",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "CLI tool to manage and sync React components to your personal component vault",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "composter": "./bin/composter.js"
9
+ },
10
+ "scripts": {
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "keywords": ["react", "components", "cli", "vault", "library", "shadcn"],
14
+ "author": "binit2-1",
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/binit2-1/Composter"
19
+ },
20
+ "dependencies": {
21
+ "commander": "^14.0.2",
22
+ "dotenv": "^16.4.5",
23
+ "inquirer": "^13.0.1",
24
+ "node-fetch": "^3.3.2"
25
+ }
26
+ }
@@ -0,0 +1,61 @@
1
+ import { apiRequest } from "../utils/request.js";
2
+ import { loadSession } from "../utils/session.js";
3
+
4
+ export async function listCategories() {
5
+ const session = loadSession();
6
+ if (!session || !session.jwt) {
7
+ console.log("You must be logged in. Run: composter login");
8
+ return;
9
+ }
10
+
11
+ try{
12
+ const res = await apiRequest("/categories", {
13
+ method: "GET",
14
+ headers: { "Content-Type": "application/json" },
15
+ });
16
+ let body = null;
17
+ try {
18
+ body = await res.json();
19
+ } catch {
20
+ // Ignore if no JSON
21
+ }
22
+
23
+ // Handle auth failure
24
+ if (res.status === 401) {
25
+ console.log("Session expired. Run composter login again.");
26
+ return;
27
+ }
28
+
29
+ // Handle server errors
30
+ if (res.status >= 500) {
31
+ console.log("Server error. Try again later.");
32
+ return;
33
+ }
34
+
35
+ // Handle success
36
+ if (res.ok) {
37
+ const categories = body?.categories || [];
38
+ if (categories.length === 0) {
39
+ console.log("No categories found.");
40
+ return;
41
+ }
42
+ categories.forEach((cat) => {
43
+ //list them adjacent to each other with tab space between
44
+ process.stdout.write(`${cat.name}\t\t`);
45
+ });
46
+ console.log();
47
+ return;
48
+ }
49
+
50
+ // Handle other errors
51
+ const errorMessage =
52
+ (body && (body.message || body.error || JSON.stringify(body))) ||
53
+ res.statusText ||
54
+ `HTTP ${res.status}`;
55
+ console.log("Error listing categories:", errorMessage);
56
+ return;
57
+ } catch (error) {
58
+ console.log("Error fetching categories:", error);
59
+ return;
60
+ }
61
+ }
@@ -0,0 +1,71 @@
1
+ import inquirer from "inquirer";
2
+ import fetch from "node-fetch";
3
+ import { saveSession } from "../utils/session.js";
4
+ import dotenv from "dotenv";
5
+ import { fileURLToPath } from 'url';
6
+ import { dirname, join } from 'path';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+ dotenv.config({ path: join(__dirname, '../../.env') });
11
+
12
+ const BASE_URL = `${process.env.BASE_URL || "https://composter.onrender.com/api"}/auth`;
13
+
14
+ export async function login() {
15
+ console.log("=== Composter Login ===");
16
+
17
+ const { email, password } = await inquirer.prompt([
18
+ { type: "input", name: "email", message: "Email:" },
19
+ { type: "password", name: "password", message: "Password:" }
20
+ ]);
21
+
22
+ // Step 1 — Sign in
23
+ const res = await fetch(`${BASE_URL}/sign-in/email`, {
24
+ method: "POST",
25
+ headers: { "Content-Type": "application/json" },
26
+ body: JSON.stringify({ email, password })
27
+ });
28
+
29
+ if (!res.ok) {
30
+ // try to parse JSON error body, fall back to statusText
31
+ let errBody = null;
32
+ try {
33
+ errBody = await res.json();
34
+ } catch (e) {
35
+ // body wasn't JSON or couldn't be parsed
36
+ }
37
+ const message =
38
+ (errBody && (errBody.message || errBody.error || JSON.stringify(errBody))) ||
39
+ res.statusText ||
40
+ `HTTP ${res.status}`;
41
+ console.log("\nLogin failed:", message);
42
+ return;
43
+ }
44
+
45
+ // Step 2 — Extract session cookie
46
+ const cookie = res.headers.get("set-cookie");
47
+ if (!cookie) {
48
+ console.log("Failed: No session cookie returned.");
49
+ return;
50
+ }
51
+
52
+ // Step 3 — Fetch JWT token
53
+ const tokenRes = await fetch(`${BASE_URL}/token`, {
54
+ method: "GET",
55
+ headers: { "Cookie": cookie }
56
+ });
57
+
58
+ let token = null;
59
+ if (tokenRes.ok) {
60
+ const json = await tokenRes.json();
61
+ token = json.token;
62
+ }
63
+
64
+ // Step 4 — Save session + jwt locally
65
+ saveSession({
66
+ cookies: cookie,
67
+ jwt: token
68
+ });
69
+
70
+ console.log("\nLogged in successfully!");
71
+ }
@@ -0,0 +1,71 @@
1
+ import { apiRequest } from "../utils/request.js";
2
+ import { loadSession } from "../utils/session.js";
3
+
4
+ export async function mkcat(categoryName) {
5
+ // Validate input
6
+ if (
7
+ !categoryName ||
8
+ categoryName.trim() === "" ||
9
+ categoryName.includes(" ") ||
10
+ categoryName.length > 10
11
+ ) {
12
+ console.log(
13
+ "Invalid category name. It must be non-empty, without spaces, and ≤ 10 characters."
14
+ );
15
+ return;
16
+ }
17
+
18
+ // Check session
19
+ const session = loadSession();
20
+ if (!session || !session.jwt) {
21
+ console.log("You must be logged in. Run: composter login");
22
+ return;
23
+ }
24
+
25
+ try {
26
+ // Send request
27
+ const res = await apiRequest("/categories", {
28
+ method: "POST",
29
+ headers: { "Content-Type": "application/json" },
30
+ body: JSON.stringify({ name: categoryName }),
31
+ });
32
+
33
+ // Parse JSON once
34
+ let body = null;
35
+ try {
36
+ body = await res.json();
37
+ } catch {
38
+ // Ignore if no JSON
39
+ }
40
+
41
+ // Handle auth failure
42
+ if (res.status === 401) {
43
+ console.log("Session expired. Run composter login again.");
44
+ return;
45
+ }
46
+
47
+ // Handle server errors
48
+ if (res.status >= 500) {
49
+ console.log("Server error. Try again later.");
50
+ return;
51
+ }
52
+
53
+ // Handle success
54
+ if (res.ok) {
55
+ console.log(`Category '${categoryName}' created successfully!`);
56
+ console.log("ID:", body?.category?.id);
57
+ return;
58
+ }
59
+
60
+ // Handle other errors
61
+ const msg =
62
+ body?.error ||
63
+ body?.message ||
64
+ JSON.stringify(body) ||
65
+ `HTTP ${res.status}`;
66
+
67
+ console.log("Failed to create category:", msg);
68
+ } catch (err) {
69
+ console.log("Network or unexpected error:", err.message);
70
+ }
71
+ }
@@ -0,0 +1,141 @@
1
+ import { apiRequest } from "../utils/request.js";
2
+ import { loadSession } from "../utils/session.js";
3
+ import fs from "fs";
4
+ import path from "path";
5
+
6
+ export async function pullComponent(category, title, targetDir) {
7
+ // 1. Validate Input
8
+ if (!category?.trim() || !title?.trim() || !targetDir?.trim()) {
9
+ console.log("❌ Category, title, and target directory are required.");
10
+ return;
11
+ }
12
+
13
+ // 2. Resolve Target Directory
14
+ // In multi-file mode, the target is usually a FOLDER, not a specific file.
15
+ const absoluteRoot = path.resolve(targetDir);
16
+
17
+ // 3. Check Session
18
+ const session = loadSession();
19
+ if (!session || !session.jwt) {
20
+ console.log("❌ You must be logged in. Run: composter login");
21
+ return;
22
+ }
23
+
24
+ try {
25
+ console.log(`⏳ Fetching '${title}' from '${category}'...`);
26
+
27
+ const res = await apiRequest(`/components?category=${encodeURIComponent(category)}&title=${encodeURIComponent(title)}`, {
28
+ method: "GET",
29
+ headers: { "Content-Type": "application/json" },
30
+ });
31
+
32
+ // Parse Body
33
+ let body = null;
34
+ try { body = await res.json(); } catch {}
35
+
36
+ if (res.status === 401) {
37
+ console.log("❌ Session expired. Run composter login again.");
38
+ return;
39
+ }
40
+ if (res.status === 404) {
41
+ console.log(`❌ Component '${title}' not found.`);
42
+ return;
43
+ }
44
+ if (!res.ok) {
45
+ console.log("❌ Server error:", body?.error || res.statusText);
46
+ return;
47
+ }
48
+
49
+ const component = body.component;
50
+
51
+ // --- STEP 4: PARSE FILES (Handle JSON vs String) ---
52
+ let filesMap = {};
53
+ try {
54
+ // Try to parse new multi-file format
55
+ filesMap = JSON.parse(component.code);
56
+ } catch (e) {
57
+ // Fallback for old single-file components
58
+ // We'll create a default filename based on the title
59
+ let fileName = `${title}.jsx`;
60
+ // If the user pointed to a specific file (e.g. ./src/Button.js), use that name
61
+ if (path.extname(absoluteRoot)) {
62
+ fileName = path.basename(absoluteRoot);
63
+ }
64
+ filesMap[`/${fileName}`] = component.code;
65
+ }
66
+
67
+ // --- STEP 5: WRITE FILES TO DISK ---
68
+ console.log(`📦 Unpacking ${Object.keys(filesMap).length} file(s) into: ${absoluteRoot}`);
69
+
70
+ // Ensure the root target folder exists
71
+ if (!fs.existsSync(absoluteRoot)) {
72
+ fs.mkdirSync(absoluteRoot, { recursive: true });
73
+ }
74
+
75
+ const createdFiles = [];
76
+
77
+ for (const [virtualPath, content] of Object.entries(filesMap)) {
78
+ // Remove leading slash (e.g. "/ui/Button.tsx" -> "ui/Button.tsx")
79
+ const relPath = virtualPath.startsWith('/') ? virtualPath.slice(1) : virtualPath;
80
+
81
+ // Construct full system path
82
+ const writePath = path.join(absoluteRoot, relPath);
83
+ const writeDir = path.dirname(writePath);
84
+
85
+ // Create sub-folders if needed
86
+ if (!fs.existsSync(writeDir)) {
87
+ fs.mkdirSync(writeDir, { recursive: true });
88
+ }
89
+
90
+ // Write file
91
+ fs.writeFileSync(writePath, content, "utf-8");
92
+ createdFiles.push(relPath);
93
+ console.log(` + ${relPath}`);
94
+ }
95
+
96
+ // --- STEP 6: CHECK DEPENDENCIES ---
97
+ if (component.dependencies && Object.keys(component.dependencies).length > 0) {
98
+ checkDependencies(component.dependencies);
99
+ }
100
+
101
+ console.log(`\n✅ Component '${title}' pulled successfully!`);
102
+
103
+ } catch (err) {
104
+ console.log("❌ Error pulling component:", err);
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Helper to check local package.json against required dependencies
110
+ */
111
+ function checkDependencies(requiredDeps) {
112
+ const localPkgPath = path.resolve(process.cwd(), "package.json");
113
+
114
+ // If no package.json, we can't check, so just list them all
115
+ if (!fs.existsSync(localPkgPath)) {
116
+ console.log("\n⚠️ This component requires these packages:");
117
+ Object.entries(requiredDeps).forEach(([pkg, ver]) => console.log(` - ${pkg}@${ver}`));
118
+ return;
119
+ }
120
+
121
+ try {
122
+ const localPkg = JSON.parse(fs.readFileSync(localPkgPath, "utf-8"));
123
+ const installed = { ...localPkg.dependencies, ...localPkg.devDependencies };
124
+ const missing = [];
125
+
126
+ for (const [pkg, version] of Object.entries(requiredDeps)) {
127
+ if (!installed[pkg]) {
128
+ missing.push(`${pkg}@${version}`);
129
+ }
130
+ }
131
+
132
+ if (missing.length > 0) {
133
+ console.log("\n⚠️ Missing Dependencies (Run this to fix):");
134
+ console.log(` npm install ${missing.map(d => d.split('@')[0]).join(" ")}`);
135
+ } else {
136
+ console.log("\n✨ All dependencies are already installed.");
137
+ }
138
+ } catch (e) {
139
+ // Ignore JSON parse errors
140
+ }
141
+ }
@@ -0,0 +1,73 @@
1
+ import { apiRequest } from "../utils/request.js";
2
+ import { loadSession } from "../utils/session.js";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ // IMPORT THE NEW SPIDER
6
+ import { scanComponent } from "../utils/crawler.js";
7
+
8
+ export async function pushComponent(category, title, filepath) {
9
+ // 1. Validate Input
10
+ if (!category?.trim() || !title?.trim() || !filepath?.trim()) {
11
+ console.log("❌ Category, title, and filepath are required.");
12
+ return;
13
+ }
14
+
15
+ // 2. Validate Entry File
16
+ const absolutePath = path.resolve(filepath);
17
+ if (!fs.existsSync(absolutePath)) {
18
+ console.log(`❌ File not found: ${absolutePath}`);
19
+ return;
20
+ }
21
+
22
+ // 3. Check Session
23
+ const session = loadSession();
24
+ if (!session || !session.jwt) {
25
+ console.log("❌ You must be logged in. Run: composter login");
26
+ return;
27
+ }
28
+
29
+ // 4. RUN THE CRAWLER
30
+ console.log(`Scanning ${path.basename(absolutePath)} and its dependencies...`);
31
+
32
+ const { files, dependencies } = scanComponent(absolutePath);
33
+
34
+ const fileCount = Object.keys(files).length;
35
+ const depCount = Object.keys(dependencies).length;
36
+
37
+ console.log(`📦 Bundled ${fileCount} file(s) and detected ${depCount} external package(s).`);
38
+
39
+ try {
40
+ // 5. Send Request
41
+ // We send 'files' as a JSON string because your DB 'code' column is a String.
42
+ const res = await apiRequest("/components", {
43
+ method: "POST",
44
+ headers: { "Content-Type": "application/json" },
45
+ body: JSON.stringify({
46
+ title,
47
+ category,
48
+ code: JSON.stringify(files),
49
+ dependencies: dependencies
50
+ }),
51
+ });
52
+
53
+ // 6. Handle Response
54
+ let body = null;
55
+ try { body = await res.json(); } catch {}
56
+
57
+ if (res.status === 401) {
58
+ console.log("❌ Session expired. Run composter login again.");
59
+ return;
60
+ }
61
+
62
+ if (res.ok) {
63
+ console.log(`✅ Success! Component '${title}' pushed to '${category}'.`);
64
+ return;
65
+ }
66
+
67
+ const errorMessage = body?.message || body?.error || res.statusText;
68
+ console.log("❌ Error pushing component:", errorMessage);
69
+
70
+ } catch (error) {
71
+ console.log("❌ Network Error:", error.message);
72
+ }
73
+ }
package/src/index.js ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from "commander";
4
+ import { login } from "./commands/login.js";
5
+ import { mkcat } from "./commands/mkcat.js";
6
+ import { listCategories } from "./commands/listCat.js";
7
+ import { pushComponent } from "./commands/push.js";
8
+ import { pullComponent } from "./commands/pull.js";
9
+
10
+ const program = new Command();
11
+
12
+ program
13
+ .name("composter")
14
+ .description("CLI for Composter Platform")
15
+ .version("0.1.0");
16
+
17
+ program
18
+ .command("login")
19
+ .description("Log into your Composter account")
20
+ .action(login);
21
+
22
+ program
23
+ .command("mkcat <category-name>")
24
+ .description("Create a new category")
25
+ .action((categoryName) => mkcat(categoryName));
26
+
27
+ program
28
+ .command("ls")
29
+ .description("List categories")
30
+ .action(() => {
31
+ listCategories();
32
+ });
33
+
34
+ program
35
+ .command("push <category-name> <component-title> <file-path>")
36
+ .description("Push a new component")
37
+ .action((category, title, filepath) => {
38
+ pushComponent(category, title, filepath);
39
+ });
40
+
41
+ program
42
+ .command("pull <category-name> <component-title> <file-path>")
43
+ .description("Pull a component")
44
+ .action((category, title, filepath) => {
45
+ pullComponent(category, title, filepath);
46
+ });
47
+
48
+ program.parse(process.argv);
@@ -0,0 +1,129 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ const IMPORT_REGEX = /(?:import|export)\s+(?:[\w*\s{},]*\s+from\s+)?['"]([^'"]+)['"]/g;
5
+
6
+ // Helper: Find the nearest package.json starting from a file's directory
7
+ function findPackageRoot(startDir) {
8
+ let current = startDir;
9
+ const root = path.parse(current).root;
10
+
11
+ while (current !== root) {
12
+ if (fs.existsSync(path.join(current, "package.json"))) {
13
+ return current;
14
+ }
15
+ current = path.dirname(current);
16
+ }
17
+ // Fallback: If no package.json, use the directory of the entry file itself
18
+ return startDir;
19
+ }
20
+
21
+ export function scanComponent(entryFilePath) {
22
+ const absoluteEntry = path.resolve(entryFilePath);
23
+
24
+ // FIX: Anchor the root to the package.json folder
25
+ // This ensures paths are like "src/Button.jsx", not "frontend/src/Button.jsx"
26
+ const projectRoot = findPackageRoot(path.dirname(absoluteEntry));
27
+
28
+ const filesMap = {};
29
+ const npmDependencies = {};
30
+ const processed = new Set();
31
+ const queue = [absoluteEntry];
32
+
33
+ // Load package.json for versions
34
+ const localPkgPath = path.join(projectRoot, "package.json");
35
+ let localPkg = { dependencies: {}, devDependencies: {} };
36
+ try {
37
+ if (fs.existsSync(localPkgPath)) {
38
+ localPkg = JSON.parse(fs.readFileSync(localPkgPath, "utf-8"));
39
+ }
40
+ } catch (e) {}
41
+
42
+ while (queue.length > 0) {
43
+ const fullPath = queue.shift();
44
+ if (processed.has(fullPath)) continue;
45
+
46
+ if (!fs.existsSync(fullPath)) {
47
+ console.warn(`⚠️ Warning: File not found: ${fullPath}`);
48
+ continue;
49
+ }
50
+
51
+ const content = fs.readFileSync(fullPath, "utf-8");
52
+ processed.add(fullPath);
53
+
54
+ // --- CLEAN PATH GENERATION ---
55
+ // Calculate path relative to the PROJECT ROOT (package.json location)
56
+ let relativePath = path.relative(projectRoot, fullPath);
57
+
58
+ // Normalize slashes for Sandpack
59
+ relativePath = relativePath.split(path.sep).join("/");
60
+
61
+ // SANITY CHECK: If path still starts with "..", force it into a virtual root
62
+ // This handles cases where you import a file OUTSIDE your project root
63
+ if (relativePath.startsWith("..")) {
64
+ // Strip the dots: "../external/File.js" -> "/_external/File.js"
65
+ relativePath = relativePath.replace(/^(\.\.\/)+/, "_external/");
66
+ }
67
+
68
+ const virtualPath = `/${relativePath}`;
69
+ filesMap[virtualPath] = content;
70
+
71
+ // --- SCAN IMPORTS ---
72
+ let match;
73
+ IMPORT_REGEX.lastIndex = 0;
74
+ while ((match = IMPORT_REGEX.exec(content)) !== null) {
75
+ const importPath = match[1];
76
+
77
+ if (importPath.startsWith(".")) {
78
+ // CASE A: Relative Import (e.g. "./button")
79
+ const currentFileDir = path.dirname(fullPath);
80
+ const resolvedPath = resolveLocalImport(currentFileDir, importPath);
81
+ if (resolvedPath && !processed.has(resolvedPath)) {
82
+ queue.push(resolvedPath);
83
+ }
84
+ } else if (importPath.startsWith("@/")) {
85
+ // CASE B: Alias Import (e.g. "@/components/ui/button")
86
+ // We assume "@/" maps to "<projectRoot>/src/" (Standard Shadcn/Vite convention)
87
+ const pathInsideSrc = importPath.slice(2); // Remove "@/"
88
+ const srcDir = path.join(projectRoot, "src");
89
+
90
+ const resolvedPath = resolveLocalImport(srcDir, pathInsideSrc);
91
+ if (resolvedPath && !processed.has(resolvedPath)) {
92
+ queue.push(resolvedPath);
93
+ }
94
+ } else {
95
+ // CASE C: External Package (e.g. "lucide-react")
96
+ const pkgName = getPackageName(importPath);
97
+ const version =
98
+ localPkg.dependencies?.[pkgName] ||
99
+ localPkg.devDependencies?.[pkgName] ||
100
+ "latest";
101
+ npmDependencies[pkgName] = version;
102
+ }
103
+ }
104
+ }
105
+
106
+ return { files: filesMap, dependencies: npmDependencies };
107
+ }
108
+
109
+ function resolveLocalImport(dir, importPath) {
110
+ const extensions = [".tsx", ".ts", ".jsx", ".js", ".css"];
111
+ const base = path.join(dir, importPath);
112
+ if (fs.existsSync(base) && fs.statSync(base).isFile()) return base;
113
+ for (const ext of extensions) {
114
+ if (fs.existsSync(base + ext)) return base + ext;
115
+ }
116
+ for (const ext of extensions) {
117
+ const indexPath = path.join(base, `index${ext}`);
118
+ if (fs.existsSync(indexPath)) return indexPath;
119
+ }
120
+ return null;
121
+ }
122
+
123
+ function getPackageName(importPath) {
124
+ if (importPath.startsWith("@")) {
125
+ const parts = importPath.split("/");
126
+ return `${parts[0]}/${parts[1]}`;
127
+ }
128
+ return importPath.split("/")[0];
129
+ }
@@ -0,0 +1,13 @@
1
+ import os from "os";
2
+ import path from "path";
3
+ import fs from "fs";
4
+
5
+ export const CONFIG_DIR = path.join(os.homedir(), ".config", "composter");
6
+
7
+ export function ensureConfigDir() {
8
+ if (!fs.existsSync(CONFIG_DIR)) {
9
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
10
+ }
11
+ }
12
+
13
+ export const SESSION_PATH = path.join(CONFIG_DIR, "session.json");
@@ -0,0 +1,22 @@
1
+ import fetch from "node-fetch";
2
+ import { loadSession } from "./session.js";
3
+ import dotenv from "dotenv";
4
+ dotenv.config({ silent: true });
5
+
6
+ const BASE_URL = process.env.BASE_URL || "https://composter.onrender.com/api";
7
+
8
+ export async function apiRequest(path, options = {}) {
9
+ const session = loadSession();
10
+ const headers = options.headers || {};
11
+
12
+ if (session?.jwt) {
13
+ headers["Authorization"] = `Bearer ${session.jwt}`;
14
+ }
15
+
16
+ const res = await fetch(`${BASE_URL}${path}`, {
17
+ ...options,
18
+ headers,
19
+ });
20
+
21
+ return res;
22
+ }
@@ -0,0 +1,16 @@
1
+ import fs from "fs";
2
+ import { SESSION_PATH, ensureConfigDir } from "./paths.js";
3
+
4
+ export function saveSession(sessionData) {
5
+ ensureConfigDir();
6
+ fs.writeFileSync(SESSION_PATH, JSON.stringify(sessionData, null, 2), "utf-8");
7
+ }
8
+
9
+ export function loadSession() {
10
+ if (!fs.existsSync(SESSION_PATH)) return null;
11
+ return JSON.parse(fs.readFileSync(SESSION_PATH, "utf-8"));
12
+ }
13
+
14
+ export function clearSession() {
15
+ if (fs.existsSync(SESSION_PATH)) fs.unlinkSync(SESSION_PATH);
16
+ }