infernoflow 0.18.0 → 0.20.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,142 @@
1
+ /**
2
+ * Shared error handling utilities for infernoflow commands.
3
+ *
4
+ * Provides consistent, friendly error messages with actionable hints.
5
+ */
6
+
7
+ import { warn, info, bold, cyan, gray, red } from "./output.mjs";
8
+
9
+ // ── Common error types ────────────────────────────────────────────────────────
10
+
11
+ export const ERR = {
12
+ NO_INFERNO_DIR: "inferno/ directory not found.",
13
+ NO_CONTRACT: "No contract.json or capabilities.json found.",
14
+ NO_GIT: "This directory is not a git repository.",
15
+ NO_NETWORK: "Network request failed.",
16
+ INVALID_JSON: "Invalid JSON in response.",
17
+ PERMISSION: "Permission denied.",
18
+ TIMEOUT: "Command timed out.",
19
+ };
20
+
21
+ // ── Error formatter ───────────────────────────────────────────────────────────
22
+
23
+ export function fatalError(message, hint, jsonMode = false) {
24
+ if (jsonMode) {
25
+ console.log(JSON.stringify({ ok: false, error: message, hint: hint || undefined }));
26
+ } else {
27
+ console.error();
28
+ console.error(` ${red("✗")} ${bold(message)}`);
29
+ if (hint) console.error(` ${gray(hint)}`);
30
+ console.error();
31
+ }
32
+ process.exit(1);
33
+ }
34
+
35
+ export function softWarn(message, hint, jsonMode = false) {
36
+ if (!jsonMode) {
37
+ warn(message);
38
+ if (hint) console.error(` ${gray(hint)}`);
39
+ }
40
+ }
41
+
42
+ // ── Pre-flight checks ─────────────────────────────────────────────────────────
43
+
44
+ import * as fs from "node:fs";
45
+ import * as path from "node:path";
46
+ import { execSync } from "node:child_process";
47
+
48
+ /**
49
+ * Ensure inferno/ exists. Exits with a friendly message if not.
50
+ */
51
+ export function requireInfernoDir(cwd, jsonMode = false) {
52
+ const infernoDir = path.join(cwd, "inferno");
53
+ if (!fs.existsSync(infernoDir)) {
54
+ fatalError(
55
+ ERR.NO_INFERNO_DIR,
56
+ "Run: infernoflow init (or: infernoflow setup for full setup)",
57
+ jsonMode
58
+ );
59
+ }
60
+ return infernoDir;
61
+ }
62
+
63
+ /**
64
+ * Ensure a git repo exists. Exits with a friendly message if not.
65
+ */
66
+ export function requireGitRepo(cwd, jsonMode = false) {
67
+ try {
68
+ execSync("git rev-parse --git-dir", { cwd, stdio: "ignore" });
69
+ } catch {
70
+ fatalError(
71
+ ERR.NO_GIT,
72
+ "Run: git init && git add . && git commit -m 'init'",
73
+ jsonMode
74
+ );
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Read and parse a JSON file, with a friendly error on failure.
80
+ */
81
+ export function readJsonFile(filePath, label, jsonMode = false) {
82
+ if (!fs.existsSync(filePath)) return null;
83
+ try {
84
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
85
+ } catch (err) {
86
+ fatalError(
87
+ `Could not parse ${label}: ${err.message}`,
88
+ `Check the file for syntax errors: ${filePath}`,
89
+ jsonMode
90
+ );
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Read contract.json or capabilities.json, whichever exists.
96
+ */
97
+ export function readContract(infernoDir, jsonMode = false) {
98
+ for (const f of ["contract.json", "capabilities.json"]) {
99
+ const p = path.join(infernoDir, f);
100
+ const data = readJsonFile(p, f, jsonMode);
101
+ if (data) return data;
102
+ }
103
+ fatalError(ERR.NO_CONTRACT, "Run: infernoflow init", jsonMode);
104
+ }
105
+
106
+ /**
107
+ * Parse a semver string. Returns { major, minor, patch } or null.
108
+ */
109
+ export function parseSemver(v) {
110
+ const m = String(v || "").match(/^(\d+)\.(\d+)\.(\d+)/);
111
+ if (!m) return null;
112
+ return { major: +m[1], minor: +m[2], patch: +m[3], raw: m[0] };
113
+ }
114
+
115
+ /**
116
+ * Validate a URL string. Returns true if valid http/https URL.
117
+ */
118
+ export function isValidUrl(str) {
119
+ try {
120
+ const u = new URL(str);
121
+ return u.protocol === "http:" || u.protocol === "https:";
122
+ } catch { return false; }
123
+ }
124
+
125
+ /**
126
+ * Wrap an async command handler with top-level error catching.
127
+ * Prints a friendly message instead of a raw stack trace.
128
+ */
129
+ export function withErrorHandling(fn, jsonMode = false) {
130
+ return fn().catch(err => {
131
+ if (jsonMode) {
132
+ console.log(JSON.stringify({ ok: false, error: err.message }));
133
+ } else {
134
+ console.error();
135
+ console.error(` ${red("✗")} ${bold("Unexpected error:")} ${err.message}`);
136
+ if (process.env.DEBUG) console.error(err.stack);
137
+ else console.error(` ${gray("Set DEBUG=1 for a full stack trace.")}`);
138
+ console.error();
139
+ }
140
+ process.exit(1);
141
+ });
142
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infernoflow",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "description": "The forge for liquid code - keep capabilities, contracts, and docs in sync.",
5
5
  "type": "module",
6
6
  "bin": {