@rerouter/helper 0.0.0-stage → 0.1.1

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.
package/src/lib/env.js ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Helper configuration source. SECURITY RULE: the API key is only read from
3
+ * env REROUTER_API_KEY — never accepted as an argument, printed, or written
4
+ * to any file.
5
+ */
6
+
7
+ export const DEFAULT_BASE_URL = "https://api.rerouter.id";
8
+
9
+ export function baseUrl() {
10
+ return (process.env.REROUTER_BASE_URL?.trim() || DEFAULT_BASE_URL).replace(/\/+$/, "");
11
+ }
12
+
13
+ export function apiKey() {
14
+ return process.env.REROUTER_API_KEY?.trim() || null;
15
+ }
16
+
17
+ export class MissingKeyError extends Error {
18
+ constructor() {
19
+ super(
20
+ "REROUTER_API_KEY is not set.\n" +
21
+ " POSIX: export REROUTER_API_KEY=sk-rr-...\n" +
22
+ " PowerShell: $env:REROUTER_API_KEY = \"sk-rr-...\"\n" +
23
+ "Create a key in Rerouter dashboard → API Keys.",
24
+ );
25
+ }
26
+ }
27
+
28
+ export function requireApiKey() {
29
+ const key = apiKey();
30
+ if (!key) throw new MissingKeyError();
31
+ return key;
32
+ }
@@ -0,0 +1,85 @@
1
+ import { promises as fs } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+
5
+ /** Expand `~` to the user home directory. */
6
+ export function expandHome(p) {
7
+ if (p === "~") return os.homedir();
8
+ if (p.startsWith("~/") || p.startsWith("~\\")) return path.join(os.homedir(), p.slice(2));
9
+ return p;
10
+ }
11
+
12
+ export async function readTextIfExists(file) {
13
+ try {
14
+ return await fs.readFile(file, "utf8");
15
+ } catch (err) {
16
+ if (err?.code === "ENOENT") return null;
17
+ throw err;
18
+ }
19
+ }
20
+
21
+ function stamp() {
22
+ return new Date().toISOString().replace(/[-:T]/g, "").slice(0, 14);
23
+ }
24
+
25
+ /** Copy file to `${file}.rerouter-backup-<timestamp>`; no-op if missing. */
26
+ export async function backupFile(file) {
27
+ const existing = await readTextIfExists(file);
28
+ if (existing === null) return null;
29
+ const backupPath = `${file}.rerouter-backup-${stamp()}`;
30
+ await fs.writeFile(backupPath, existing, "utf8");
31
+ return backupPath;
32
+ }
33
+
34
+ export async function writeFileEnsured(file, content) {
35
+ await fs.mkdir(path.dirname(file), { recursive: true });
36
+ await fs.writeFile(file, content, "utf8");
37
+ }
38
+
39
+ /**
40
+ * Parse loose JSONC (line and block comments outside strings + trailing comma).
41
+ * Throws Error if ambiguous/broken — the caller asks for a manual merge and
42
+ * leaves the file intact.
43
+ */
44
+ export function parseJsonc(text) {
45
+ let out = "";
46
+ let i = 0;
47
+ let inString = false;
48
+ let quote = "";
49
+ while (i < text.length) {
50
+ const ch = text[i];
51
+ const next = text[i + 1];
52
+ if (inString) {
53
+ out += ch;
54
+ if (ch === "\\") {
55
+ out += next ?? "";
56
+ i += 2;
57
+ continue;
58
+ }
59
+ if (ch === quote) inString = false;
60
+ i++;
61
+ continue;
62
+ }
63
+ if (ch === '"' || ch === "'") {
64
+ inString = true;
65
+ quote = ch;
66
+ out += ch;
67
+ i++;
68
+ continue;
69
+ }
70
+ if (ch === "/" && next === "/") {
71
+ while (i < text.length && text[i] !== "\n") i++;
72
+ continue;
73
+ }
74
+ if (ch === "/" && next === "*") {
75
+ i += 2;
76
+ while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) i++;
77
+ i += 2;
78
+ continue;
79
+ }
80
+ out += ch;
81
+ i++;
82
+ }
83
+ const stripped = out.replace(/,\s*([}\]])/g, "$1");
84
+ return JSON.parse(stripped);
85
+ }