composter-cli 1.0.11 → 1.0.15

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.
@@ -1,5 +1,6 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
+ import { log } from "./log.js";
3
4
 
4
5
  const IMPORT_REGEX = /(?:import|export)\s+(?:[\w*\s{},]*\s+from\s+)?['"]([^'"]+)['"]/g;
5
6
 
@@ -37,14 +38,16 @@ export function scanComponent(entryFilePath) {
37
38
  if (fs.existsSync(localPkgPath)) {
38
39
  localPkg = JSON.parse(fs.readFileSync(localPkgPath, "utf-8"));
39
40
  }
40
- } catch (e) {}
41
+ } catch (e) {
42
+ log.warn("Could not read local package.json. The file may be corrupted")
43
+ }
41
44
 
42
45
  while (queue.length > 0) {
43
46
  const fullPath = queue.shift();
44
47
  if (processed.has(fullPath)) continue;
45
48
 
46
49
  if (!fs.existsSync(fullPath)) {
47
- console.warn(`⚠️ Warning: File not found: ${fullPath}`);
50
+ log.warn(`Warning: File not found - ${fullPath}`);
48
51
  continue;
49
52
  }
50
53
 
@@ -0,0 +1,36 @@
1
+ import { log } from "../log.js";
2
+
3
+ export function handleFetchError(err) {
4
+ switch (err.message) {
5
+ case "NETWORK_UNREACHABLE":
6
+ log.error("Cannot reach server. Check your internet or VPN connection.");
7
+ break;
8
+
9
+ case "SESSION_EXPIRED":
10
+ log.warn("Your session has expired. Please log in again.");
11
+ break;
12
+
13
+ case "NETWORK_TIMEOUT":
14
+ log.warn("Network request timed out. Please try again.");
15
+ break;
16
+
17
+ case "UNAUTHORIZED":
18
+ log.error("Invalid email or password.");
19
+ break;
20
+
21
+ case "NOT_FOUND":
22
+ log.error("Requested resource does not exist.");
23
+ break;
24
+
25
+ case "SERVER_ERROR":
26
+ log.error("Service temporarily unavailable. Try again later.");
27
+ break;
28
+
29
+ default:
30
+ log.error("An unexpected error occurred.");
31
+ }
32
+
33
+ if (process.env.DEBUG === "1" || process.env.DEBUG === "true") {
34
+ console.error(err);
35
+ }
36
+ }
@@ -0,0 +1,32 @@
1
+ import { log } from "../log.js";
2
+
3
+ export function handleSessionError(err) {
4
+ switch (err.message) {
5
+ case "FAILED_SESSION_SAVE":
6
+ log.error("Failed to save session data. Please try logging in again.");
7
+ break;
8
+
9
+ case "NO_SESSION":
10
+ log.warn("You need to log in first.");
11
+ log.info("Run: composter login");
12
+ break;
13
+
14
+ case "SESSION_FILE_CORRUPT":
15
+ log.warn("Session file is corrupt. Please log in again.");
16
+ break;
17
+
18
+ case "SESSION_INVALID":
19
+ log.warn("Session data is invalid. Please log in again.");
20
+ break;
21
+
22
+ case "SESSION_EXPIRED":
23
+ log.warn("Your session has expired. Please log in again.");
24
+ break;
25
+ default:
26
+ log.error("An unexpected error occurred.");
27
+ }
28
+
29
+ if (process.env.DEBUG === "1" || process.env.DEBUG === "true") {
30
+ console.error(err);
31
+ }
32
+ }
@@ -0,0 +1,8 @@
1
+ import chalk from "chalk";
2
+
3
+ export const log = {
4
+ info: (msg) => console.log(chalk.blue.bold(`${msg}`)),
5
+ success: (msg) => console.log(chalk.green.bold(`✔ ${msg}`)),
6
+ warn: (msg) => console.log(chalk.yellow.bold(`⚠ ${msg}`)),
7
+ error: (msg) => console.error(chalk.red.bold(`✖ ${msg}`)),
8
+ };
@@ -1,35 +1,44 @@
1
- import fetch from "node-fetch";
2
- import { loadSession, clearSession } from "./session.js";
3
- import dotenv from "dotenv";
4
- dotenv.config({ silent: true });
1
+ import { clearSession, loadSession } from "./session.js";
2
+ import { safeFetch } from "./safeFetch.js";
3
+ import { handleSessionError } from "./errorHandlers/sessionErrorHandler.js";
4
+ import { handleFetchError } from "./errorHandlers/fetchErrorHandler.js";
5
+ import { log } from "./log.js";
5
6
 
6
- const BASE_URL = process.env.BASE_URL || "https://composter.onrender.com/api";
7
+ const BASE_URL = process.env.BASE_URL;
7
8
 
8
9
  export async function apiRequest(path, options = {}) {
9
- const session = loadSession();
10
+
11
+ try {
12
+ const session = loadSession();
13
+ const headers = options.headers || {};
10
14
 
11
- if (!session) {
12
- console.error("Not authenticated. Please run 'composter login'");
13
- process.exit(1);
14
- }
15
+ if (session?.jwt) {
16
+ headers["Authorization"] = `Bearer ${session.jwt}`;
17
+ }
15
18
 
16
- const headers = options.headers || {};
17
-
18
- if (session?.jwt) {
19
- headers["Authorization"] = `Bearer ${session.jwt}`;
20
- }
21
-
22
- const res = await fetch(`${BASE_URL}${path}`, {
23
- ...options,
24
- headers,
25
- });
26
-
27
- // Handle 401 Unauthorized (expired/invalid session)
28
- if (res.status === 401) {
29
- console.error("Authentication failed. Please run 'composter login' again");
30
- clearSession();
31
- process.exit(1);
19
+ const res = await safeFetch(`${BASE_URL}${path}`, {
20
+ ...options,
21
+ headers,
22
+ });
23
+
24
+ return res;
25
+ } catch (error) {
26
+ if (error.type === "SESSION_ERROR") {
27
+ handleSessionError(error);
28
+ process.exit(1);
29
+ }
30
+ else {
31
+ // if we get an 401 error, it usually means the session is invalid or expired
32
+ // hence we clear the session and handle the error accordingly
33
+ if(error.type === "FETCH_ERROR" && error.message === "UNAUTHORIZED") {
34
+ clearSession();
35
+ log.warn("Session invalid or expired. Please log in again.");
36
+ log.info("Run: composter login");
37
+ process.exit(1);
38
+ }
39
+ handleFetchError(error);
40
+ process.exit(1);
41
+ }
32
42
  }
33
43
 
34
- return res;
35
44
  }
@@ -0,0 +1,49 @@
1
+ import fetch from "node-fetch";
2
+ import { FetchError } from "node-fetch";
3
+
4
+ export async function safeFetch(url, options = {}) {
5
+
6
+ let res;
7
+
8
+ const controller = new AbortController(); // to handle timeouts
9
+ const timeout = setTimeout(() => {
10
+ controller.abort();
11
+ }, 10000);
12
+
13
+ try {
14
+ res = await fetch(url, { ...options, signal: controller.signal });
15
+ } catch (err) {
16
+ if (err.code === "ECONNREFUSED" || err.code === "ENOTFOUND") {
17
+ throw new FetchError("NETWORK_UNREACHABLE", 'FETCH_ERROR');
18
+ }
19
+
20
+ if (err.name === "AbortError") {
21
+ throw new FetchError("NETWORK_TIMEOUT", 'FETCH_ERROR');
22
+ }
23
+
24
+ throw new FetchError("NETWORK_ERROR", 'FETCH_ERROR');
25
+ } finally {
26
+ clearTimeout(timeout);
27
+ }
28
+
29
+ if (!res.ok) {
30
+ if (res.status === 401 || res.status === 403) {
31
+ throw new FetchError("UNAUTHORIZED", 'FETCH_ERROR');
32
+ }
33
+
34
+ if (res.status === 404) {
35
+ throw new FetchError("NOT_FOUND", 'FETCH_ERROR');
36
+ }
37
+
38
+ if (res.status >= 500) {
39
+ throw new FetchError("SERVER_ERROR", 'FETCH_ERROR');
40
+ }
41
+
42
+ // Other HTTP errors will be handled by the caller
43
+ // these are mostly client errors (4xx) like 400, 409 etc.
44
+ return res;
45
+ }
46
+
47
+ return res;
48
+ }
49
+
@@ -1,30 +1,48 @@
1
1
  import fs from "fs";
2
2
  import { SESSION_PATH, ensureConfigDir } from "./paths.js";
3
3
 
4
+ class SessionError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.type = "SESSION_ERROR";
8
+ }
9
+ }
4
10
  export function saveSession(sessionData) {
5
- ensureConfigDir();
6
- fs.writeFileSync(SESSION_PATH, JSON.stringify(sessionData, null, 2), "utf-8");
11
+ try {
12
+ ensureConfigDir();
13
+ fs.writeFileSync(SESSION_PATH, JSON.stringify(sessionData, null, 2), "utf-8");
14
+ } catch (error) {
15
+ throw new SessionError("FAILED_SESSION_SAVE");
16
+ }
7
17
  }
8
18
 
9
19
  export function loadSession() {
10
- if (!fs.existsSync(SESSION_PATH)) return null;
20
+ if (!fs.existsSync(SESSION_PATH)) {
21
+ throw new SessionError("NO_SESSION");
22
+ }
11
23
 
24
+ let session;
25
+
26
+ // read and parse session file
12
27
  try {
13
- const session = JSON.parse(fs.readFileSync(SESSION_PATH, "utf-8"));
14
-
15
- // Check if session is expired
16
- if (session.expiresAt && new Date(session.expiresAt) < new Date()) {
17
- console.log("Session expired. Please run 'composter login' again.");
18
- clearSession();
19
- return null;
20
- }
21
-
22
- return session;
28
+ session = JSON.parse(fs.readFileSync(SESSION_PATH, "utf-8"));
23
29
  } catch (error) {
24
- console.error("Invalid session file. Please run 'composter login' again.");
25
30
  clearSession();
26
- return null;
31
+ throw new SessionError("SESSION_FILE_CORRUPT");
32
+ }
33
+
34
+ // validate session structure and if expiry is valid date
35
+ if (!session.expiresAt || isNaN(Date.parse(session.expiresAt))) {
36
+ clearSession();
37
+ throw new SessionError("SESSION_INVALID");
27
38
  }
39
+
40
+ // check if session is expired
41
+ if (new Date(session.expiresAt) < new Date()) {
42
+ clearSession();
43
+ throw new SessionError("SESSION_EXPIRED");
44
+ }
45
+ return session;
28
46
  }
29
47
 
30
48
  export function clearSession() {
package/mcp/lib/auth.js DELETED
@@ -1,78 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import os from "os";
4
-
5
- // Session path (same as CLI)
6
- const SESSION_PATH = path.join(os.homedir(), ".config", "composter", "session.json");
7
-
8
- // Get base URL - supports both dev and production
9
- export function getBaseUrl() {
10
- // Check for explicit env var first
11
- if (process.env.COMPOSTER_API_URL) {
12
- return process.env.COMPOSTER_API_URL;
13
- }
14
- // Check for dev mode
15
- if (process.env.COMPOSTER_DEV === "true" || process.env.NODE_ENV === "development") {
16
- return "http://localhost:3000/api";
17
- }
18
- // Default to production
19
- return "https://composter.onrender.com/api";
20
- }
21
-
22
- // Load session from CLI's session file
23
- export function loadSession() {
24
- if (!fs.existsSync(SESSION_PATH)) {
25
- return null;
26
- }
27
-
28
- try {
29
- const raw = fs.readFileSync(SESSION_PATH, "utf-8");
30
- const session = JSON.parse(raw);
31
-
32
- // Check if session is expired
33
- if (session.expiresAt && new Date(session.expiresAt) < new Date()) {
34
- return null;
35
- }
36
-
37
- return session;
38
- } catch {
39
- return null;
40
- }
41
- }
42
-
43
- // Get JWT token from session
44
- export function getAuthToken() {
45
- const session = loadSession();
46
- if (!session) {
47
- throw new Error("No session found. Please run 'composter login' first.");
48
- }
49
-
50
- const token = session.jwt || session.token || session.accessToken;
51
- if (!token) {
52
- throw new Error("Session file missing token. Please run 'composter login' again.");
53
- }
54
-
55
- return token;
56
- }
57
-
58
- // Verify session is valid by making a test API call
59
- export async function verifySession() {
60
- const token = getAuthToken();
61
- const baseUrl = getBaseUrl();
62
-
63
- const res = await fetch(`${baseUrl.replace('/api', '')}/api/me`, {
64
- headers: {
65
- "Authorization": `Bearer ${token}`,
66
- },
67
- });
68
-
69
- if (!res.ok) {
70
- if (res.status === 401) {
71
- throw new Error("Session expired. Please run 'composter login' again.");
72
- }
73
- throw new Error(`Authentication failed: ${res.statusText}`);
74
- }
75
-
76
- const data = await res.json();
77
- return data?.user?.id || data?.session?.userId;
78
- }